| Indice Articolo |
|---|
| IBA del kernel32.dll |
| Metodo alternativo usando il PEB |
| Tutte le pagine |
La GetModuleHandle è una funzione tipica per gestire librerie e in generale la memoria e i moduli in essa contenuti. Come input prende il nome della libreria caricata in memoria e come output restituisce l' "Handle" al suo modulo. Cosa è questo suo handle? in pratica è un numero intero anzi direi meglio una DWORD (4byte) che contiene al suo interno l'indirizzo lineare dell'inizio del modulo.
In pratica si tratta dell' Image Base Address (IBA) della libreria di cui abbiamo fatto richiesta.
Questa funzione noi la importiamo implicitamente dal kernel32.dll ed è molto semplice da usare:
DLLHandle:=GetModuleHandle('kernel32.dll');
Quello che interessa a noi invece è come ottenere la stessa informazione senza dover ricorrere a questa funzione nel caso specifico della libreria kernel32.dll.
In generale, se non volessimo o non potessimo ricorrere alla GetModuleHandle potremo recuperare ugualmente l'IBA del kernel32 e i metodi sono 2:
- Cercare tra i moduli caricati dall'applicazione usando il PEB
- Usare la struttura che gestisce le eccezioni (unwind the SEH)
La seguente applicazione ricava l'indirizzo del modulo relativo al kernel32 grazie al secondo metodo. Questa procedura sarà utile sopratutto a runtime, dato che in fase di compilazione saremo sempre in grado di accedere al kernel32 e alle sue funzioni. Infatti è scontato che il kernel32.dll venga caricato in tutte le applicazioni e per poterne usare le funzioni come prima cosa è necessario conoscerne la posizione ovvero l'ImageBaseAddress.
program GetModuleHandleKernel;
{$APPTYPE CONSOLE}
uses
SysUtils;
function GetModuleHandleKernel32:cardinal;
begin
asm
//per trovare il modulo nella memoria mi servo dei frame SEH sapendo che il SEH primario
//conserva il puntatore al modulo kernel32.dll
//precorro quindi tutta la catena dei gestori delle eccezioni alla ricerca del primario
//il quale essendo l'ultimo nel campo che dovrebbe contenere l'indirizzo del successivo
//conterr il valore FFFFFFFFh
xor edx,edx
mov edx,fs:[edx] //il primo frame SEH lo trovo dal primo campo del TIB o TEB
mov ebx,[edx+4]
jmp @1
@2:
mov ebx,[edx+4]
mov edx,[edx]
@1:
cmp edx,$FFFFFFFF
jnz @2
//ora in EBX c' l'indirizzo della funzione delle eccezioni del kernel32.dll
//per trovare IBA della libreria faccio una ricerca dell'inizio del modulo che deve contenere "MZ"
//l'indirizzo di inizio sezione sempre un multiplo di 1000h
shr ebx,12 //quindi devo eliminare le ultime cifre dell'indirizzo
shl ebx,12 // e con uno shift a destra e poi a sinistra ottengo questo effetto
// non uso l'istruzione "AND EBX,$FFFFF000" perché usa gli zeri (vedi articolo sugli shellcode)
@3:
cmp word[ebx],$5A4D //confronto i 2 byte iniziali con i caratteri "MZ"
je @4 //se li trovo vado alla fine
add ebx,-$FFF // altrimenti decremento di 1000h il puntatore, ma non uso la sottrazione diretta
add ebx,-$1 // = (sub ebx,$1000) sempre per via dei byte nulli (vedi articolo sugli shellcode)
jmp @3
@4:
mov result,ebx
end;
end;
var
IBA:cardinal;
begin
IBA:=GetModuleHandleKernel32;
writeln(format('Indirizzo modulo kernel32.dll: %xh',[IBA]));
readln;
end.
Il codice è abbastanza commentato quindi il procedimento con il quale viene ricavato questo indirizzo dovrebbe essere abbastanza chiaro. Per completezza riporto anche il codice macchina relativo alla funzione assembler:
Indirizzo Codice Istruzione Commenti
// begin
00408B44 55 PUSH EBP
00408B45 8BEC MOV EBP,ESP
00408B47 51 PUSH ECX
00408B48 8D45FC LEA EAX,[EBP-$04]
00408B4B 31D2 XOR EDX,EDX
00408B4D 648B12 MOV EDX,FS:[EDX]
00408B50 8B5A04 MOV EBX,[EDX+$04] // mov ebx,[edx+4]
00408B53 EB05 JMP $00408B5A // jmp @1
00408B55 8B5A04 MOV EBX,[EDX+$04] // mov ebx,[edx+4]
00408B58 8B12 MOV EDX,[EDX]
00408B5A 81FAFFFFFFFF CMP EDX,$FFFFFFFF
00408B60 75F3 JNZ $00408B55 // jnz @2
// and ebx,$FFFFF000 che equivale alle 2 istruzioni qui sotto
00408B62 C1EB0C SHR EBX,$0C // shr ebx,12
00408B65 C1E30C SHL EBX,$0C // shl ebx,12
00408B68 66813B4D5A CMP WORD PTR [EBX],$5A4D // cmp word[ebx],$5A4D
00408B6D 740B JZ $00408B7A // je @4
// altrimenti decremento di 1000h il puntatore
// che equivale alle 2 istruzioni qui sotto
00408B6F 81C301F0FFFF ADD EBX,$FFFFF001 // add ebx,-$FFF
00408B75 83C3FF ADD EBX,-$01 // add ebx,-$1
00408B78 EBEE JMP $00408B68 // jmp @3
00408B7A 895DFC MOV [EBP-$04],EBX // mov result,ebx
// end;
00408B7D 8B00 MOV EAX,[EAX]
00408B7F 59 POP ECX
00408B80 5D POP EBP
00408B81 C3 RET
Naturalmente esistono molti modi per fare la stessa cosa, questo è abbastanza lineare anche se alcune istruzioni sono state un po' complicate per evitare byte nulli, cosa che potrebbe tornare utile in uno shellcode.
Vediamo come ottimizzare il codice iniziando dalla scansione della memoria. Abbiamo detto che una volta ottenuto un puntatore all'interno del modulo kernel32 dovremo allineare il puntatore a blocchi di 1000h = 4096 = 4Kb. L'operazione più immediata sarebbe un "AND EBX,$FFFFF000" la quale contiene dei caratteri nulli e per questo motivo l'abbiamo sostituita con 2 funzioni:
shr ebx,12
shl ebx,12
per velocizzare la scansione potremo fare la scansione della memoria a blocchi di 64Kb e non di 4Kb quindi l'allineamento sarebbe "AND EBX,$FFFF0000"
Anche qui ci sarebbero degli zeri di troppo quindi potremo sostituirla con una sola istruzione:
6631C0 = xor ax,ax
In questo modo avremo una istruzione in meno e meno iterazioni dato che i blocchi sono più grandi.
Un'altra modifica che potrebbe ridurre le dimensioni del codice riguarda il modo di verificare che un registro sia uguale a -1 :
81FAFFFFFFFF = CMP EDX,$FFFFFFFF = CMP EDX,-1
75XX = JNZ XX
questa potrebbe essere sostituita da :
42 = INC EDX
75XX = JNZ XX
Questo perché se si incrementa -1 si ottiene 0 , questo imposta la zero flag che viene presa in considerazione dal salto condizionato. Non bisogna però dimenticare di decrementare il registro che avevamo modificato per la verifica.
4A = DEC EDX





