Ok, ho detto che avrei cercato una risposta ed eccolo qui, come promesso.
In primo luogo, volevo costruire un obiettivo reale con cui giocare. Non c'è niente come un esempio funzionante e tangibile, e questo è un processo molto manuale. Quindi, senza ulteriori indugi, ho compilato questo in MSVC con /MT
:
#define WINVER 0x501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501
#define _WIN32_IE 0x0501
#define UNICODE
#include <wchar.h>
#include <windows.h>
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
MessageBox(NULL, L"Hello, world.", L"A messagebox", MB_OK | MB_ICONEXCLAMATION);
return 0;
}
Ci aspetteremmo che questo bestiale importasse user32.dll!MessageBoxW
e sicuramente lo faccia effettivamente.
Per trovarlo all'interno dell'eseguibile stesso, usiamo WinDbg. Io uso WinDbg per due motivi: in primo luogo, non posso permettermi IDA, e in secondo luogo, è lo stesso debugger che useresti per un driver del kernel (anche con IDA - che usa il suo motore) quindi è buono, anche se un po 'non intuitivo.
Ho compilato il mio eseguibile a UnpackSimple.exe
per l'architettura x86, poiché UPX non supporta Win64. Ora, la prima cosa che noterai quando lo esegui sotto WinDbg è questa linea:
ModLoad: 01360000 01370000 UnpackSimple.exe
Proprio lì, vediamo l'intervallo di indirizzi a cui è stato mappato UnpackSimple.exe
. Questo è importante, perché tutti gli indirizzi nella sua intestazione sono relativi a 01360000
.
Quindi ora, per trovare le informazioni dell'intestazione, possiamo semplicemente richiederle:
!dh UnpackSimple.exe
OPTIONAL HEADER VALUES
10B magic #
11.00 linker version
5E00 size of code
6C00 size of initialized data
0 size of uninitialized data
1193 address of entry point
1000 base of code
----- new -----
01360000 image base <--- this is the image base address.
1000 section alignment
200 file alignment
2 subsystem (Windows GUI)
6.00 operating system version
0.00 image version
... blah blah blah ....
0 [ 0] address [size] of Export Directory
8D54 [ 3C] address [size] of Import Directory
D000 [ 1E0] address [size] of Resource Directory
0 [ 0] address [size] of Exception Directory
0 [ 0] address [size] of Security Directory
E000 [ 6D0] address [size] of Base Relocation Directory
7150 [ 38] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
89D8 [ 40] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
7000 [ 108] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory
Oh guarda, uno IAT! Vive a 01360000+7000
e possiamo convincere WinDbg a fornirci tali informazioni in modo abbastanza semplice. Il comando dps
che stiamo per utilizzare è abbreviato per dump pointers, dereferencing with symbols
, richiede un indirizzo argomento e uno spazio per indirizzo di ingresso.
dps 01360000+7000 L108/4
01367000 75b3d7ea kernel32!TerminateProcessStub
01367004 75b23f3c kernel32!CreateFileWImplementation
01367008 75b2520b kernel32!GetCommandLineWStub
... <snip> ...
013670f4 75b47ab2 kernel32!WriteConsoleW
013670f8 75b21410 kernel32!CloseHandleImplementation
013670fc 00000000
01367100 76bafd3f USER32!MessageBoxW
01367104 00000000
Puoi vedere alcune importanti caratteristiche dello IAT qui. Il primo è l'indirizzo sul lato sinistro - Indirizzo di IAT. L'indirizzo a sinistra è l'indirizzo in memoria per la DLL. Infine, nota che ci sono alcune voci zero. Ogni importazione DLL termina con una voce zero, dicendo al caricatore che non ci sono più voci da caricare.
Ora esamineremo lo smontaggio della funzione principale. Per fare ciò, digita questi due comandi
bp UnpackSimple!wWinMain
g
Quindi interromperai la partecipazione al wWinMain
sopra. Ora, se guardi da vicino:
UnpackSimple!wWinMain:
01361000 6a30 push 30h
01361002 68a0893601 push offset UnpackSimple!'string' (013689a0)
01361007 68bc893601 push offset UnpackSimple!'string' (013689bc)
0136100c 6a00 push 0
0136100e ff1500713601 call dword ptr [UnpackSimple!_imp__MessageBoxW (01367100)]
01361014 33c0 xor eax,eax
01361016 c21000 ret 10h
Non sorprendentemente, c'è la nostra funzione, _imp__MessageBoxW
, che fa riferimento a una bella voce IAT:)
Quindi ora la domanda è: come mai andiamo indietro? Bene, per prima cosa costruisci qualcosa che non funziona, dobbiamo procurarci un pacchetto completo.
A tal fine, ho eseguito upx.exe -o UnpackSimplePacked.exe UnpackSimple.exe
per produrre un bel file binario. Carica questo file binario in WinDbg e la prima cosa che noterai è:
!dh UnpackSimplePacked.exe
Non fa assolutamente nulla. Tuttavia, possiamo notare che:
ModLoad: 01220000 01233000 image01220000
è stato visto - quindi l'immagine di cui abbiamo bisogno è lì, nella memoria. Semplicemente non esiste come un'immagine su disco. In questa fase, suppongo che upx
carichi effettivamente le DLL direttamente da sé, piuttosto che usare il caricatore NT. Possiamo provare questo in due modi: in primo luogo, se digiti sxe ld
in WinDbg, si interromperà su tutti i carichi del modulo. Nessuna interruzione per user32.dll
, né è già stata caricata.
In secondo luogo, come volevi vedere:
0 [ 0] address [size] of Import Address Table Directory
È completamente vuoto.
Tuttavia, se controlliamo l'elenco dei moduli caricati:
lm
start end module name
00210000 00223000 image00210000 (deferred)
74c20000 74c2c000 CRYPTBASE (deferred)
74c30000 74c90000 SspiCli (deferred)
74d80000 74e20000 ADVAPI32 (deferred)
74eb0000 74f4d000 USP10 (deferred)
74f50000 74f5a000 LPK (deferred)
75030000 750dc000 msvcrt (deferred)
755a0000 755b9000 sechost (deferred)
75720000 757b0000 GDI32 (deferred)
757e0000 758d0000 RPCRT4 (deferred)
75b10000 75c20000 kernel32 (deferred)
76ab0000 76af7000 KERNELBASE (deferred)
76b40000 76c40000 USER32 (deferred)
77550000 776d0000 ntdll (pdb symbols)
Come puoi vedere, USER32
ha spazio assegnato pronto per l'uso.
Ora ecco alcuni trucchi. Ho aperto un'altra finestra WinDbg con la mia versione di UnpackSimple.exe e ho trovato l'indirizzo di user32!MessageBoxW
. Avendo ottenuto ciò, ho trovato che era 76bafd3f
da un offset di base di 76b40000
dandomi un offset di 6FD3F
. Nella mia versione imballata, ho impostato un breakpoint su bp 76b40000+6FD3F
e sicuramente, c'era una traccia dello stack!
k
003cfd0c 013a1014 USER32!MessageBoxW
WARNING: Stack unwind information not available. Following frames may be wrong.
003cfd6c 75b233aa image013a0000+0x1014
003cfd78 77589ef2 kernel32!BaseThreadInitThunk+0xe
003cfdb8 77589ec5 ntdll!__RtlUserThreadStart+0x70
003cfdd0 00000000 ntdll!_RtlUserThreadStart+0x1b
Il disassemblaggio nella posizione in image0...
assomiglia a questo:
013a100e ff1500713a01 call dword ptr [image013a0000+0x7100 (013a7100)]
013a1014 33c0 xor eax,eax
013a1016 c21000 ret 10h
Ora stiamo arrivando da qualche parte! A quell'indirizzo troverai alcuni dati interessanti:
db 013a7100
013a7100 3f fd ba 76 00 00 00
Stranamente, c'è il nostro indirizzo! 76bafd3f
nel buon vecchio piccolo endian. Facciamo un passo indietro:
db 013a7098
013a7098 a0 22 57 77 60 22 57 77-c9 14 b2 75 ff 10 b2 75 ."Ww'"Ww...u...u
013a70a8 7b 44 b2 75 9c 17 b2 75-91 d1 b4 75 71 51 b2 75 {D.u...u...uqQ.u
013a70b8 45 49 b2 75 c4 d1 b4 75-13 49 b2 75 b3 d1 b4 75 EI.u...u.I.u...u
013a70c8 46 e0 57 77 f1 24 59 77-0d 17 b2 75 46 19 b2 75 F.Ww.$Yw...uF..u
013a70d8 2a 30 58 77 61 48 ba 75-83 46 b2 75 07 7c bc 75 *0XwaH.u.F.u.|.u
013a70e8 28 13 b2 75 bf 45 ba 75-ef c7 b3 75 b2 7a b4 75 (..u.E.u...u.z.u
013a70f8 10 14 b2 75 00 00 00 00-3f fd ba 76 00 00 00 00 ...u....?..v....
Vedo i puntatori!
Questo è lo IAT che abbiamo trovato in precedenza, o almeno un array di IMAGE_IMPORT_DESCRIPTOR
thunks.
Quindi, arrivato così lontano, cosa rimane? Bene, dovremmo cercare nella memoria di dumping per cercare di trovare le funzioni che corrispondono alle DLL di Windows conosciute. Da lì, possiamo iniziare a ricostruire uno IAT completo per il nostro nuovo file PE. Sospetto che il processo ImpRec abbia esito positivo, come descritto nella risposta di Viv è cercare di trovare queste voci come ho fatto io manualmente. Ho scelto di utilizzare i punti di interruzione, ma ci sono diversi modi in cui questo potrebbe essere automatizzato in modo diverso, immagino. Non ho mai scritto uno strumento automatico per passare il processo a mano ...! Detto questo, ad un'ipotesi che ho recuperato le istruzioni di call ptr
, esaminare le voci al di fuori di una regione mappata tentare di trovare quelle contro gli offset DLL noti farebbe il trucco.
Due note:
- Ho scritto questa risposta come risposta del tipo "ecco come attaccherei manualmente", con uno sfondo pertinente, non un "usa questo strumento".
- Fai attenzione all'ASLR. Gli indirizzi dei moduli continueranno a spostarsi, quindi tieni d'occhio il cambiamento delle basi dei moduli!