|
Nell'articolo sul Dll Injection vengono esposte le basi di questa tecnica;
quello che abbiamo è una dll che vogliamo mappare nello spazio di memoria di un
processo ed un eseguibile che va ad eseguire il mapping della medesima. In
questo articolo vediamo come creare una dll che si mappa da sola senza la
necessità di un eseguibile di supporto. Dunnnqueee ... pensiamoci un attimo, non
mi risulta che facendo doppio click su una dll, questa vada in esecuzione ...
Behh una dll no ma una cpl si. Vediamo nel seguito di cosa si tratta
1. Applet del Pannello di Controllo
Chiunque abbia un minimo di dimestichezza con Windows, si sarà trovato almeno
una volta nella vita a fare doppio click su una delle icone che si trovano nel
Pannello di Controllo. Una delle icone che attirano più il doubleclick del mouse
è quella relativa ad Installazione Applicazioni; ma cosa c'è dietro ad Installazione Applicazioni? Si tratta di
appwiz.cpl situata nella cartella di
sistema (\windows\system32). Sempre nella cartella di sistema si trovano altri
file con estensione cpl che fanno riferimento sempre ad Applet del Pannello di
Controllo. Cos'è in sostanza un .cpl? Si tratta semplicemente di una dll, niente
di più. Come fa a partire con il doppio click? Basta aprire il registro di
Windows con regedit.exe: come al solito un'immagine ha un elevato rendimento

Quindi per eseguire un .cpl viene usata la seguente linea di comando:
rundll32.exe shell32.dll,Control_RunDLL <nome.cpl>
Una linea di comando relativa all'eseguibile rundll32.exe ha le seguenti
caratteristiche:
rundll32.exe <nome.dll>,<nomefunzione> <sequenza di
parametri separati tra loro dal carattere ",">
In pratica rundll32.exe va in esecuzione, carica nel proprio spazio di
memoria la dll <nome.dll> e chiama la funzione esportata da quest'ultima
di nome <nomefunzione> passandogli i parametri in questione.
Nel caso specifico di un file .cpl si ha il seguente comportamento
- rundll32.exe va in esecuzione
- il processo rundll32 carica nel proprio spazio di memoria la dll
shell32.dll
- il processo rundll32 chiama la funzione Control_RunDLL esportata dalla
dll shell32.dll passandogli il nome del .cpl
- la funzione Control_RunDLL carica il file .cpl nello spazio di memoria
del processo chiamante (quindi rundll32): quindi in questo frangente verrà
chiamato l'entrypoint del .cpl con parametro DLL_PROCESS_ATTACH come avviene
per qualsiasi dll.
- effettuato il caricamento del file .cpl, viene invocata una funzione
esportata dal .cpl di nome CPLApplet; se tale funzione non esiste, allora il
.cpl viene scaricato e rundll32 termina; in caso contrario viene chiamata la
funzione CPLApplet assegnando determinati valori ai suoi parametri. Nel
seguito ci focalizziamo un attimo sulle caratteristiche che deve avere
questa funzione, soffermandoci su ciò che ci interessa in questo contesto
(non è questo il luogo adatto per una descrizione dettagliata dei meccanismi
comportamentali di un file .cpl).
Bene, eccoci qua ad esporre la dichiarazione della funzione CPLApplet
function CPlApplet(hwndCPl: Cardinal;
uMsg : Cardinal;
lParam1: Integer;
lParam2: Integer
): Integer; stdcall;
Il parametro che ci interessa di più è uMsg: la prima chiamata alla CPLApplet avviene col valore 1 (costante CPL_INIT, vedi Platform SDK per approfondimenti) assegnato al parametro uMsg; in corrispondenza di questo valore, la funzione può eseguire procedure di inizializzazione varie, allocazioni iniziali di memoria, etc... Se, in corrispondenza di questo parametro, si fa restituire 0 alla funzione, allora il .cpl viene scaricato dalla memoria e rundll32 termina. Ed è proprio questo che vogliamo, come vedremo nei passaggi sucessivi. Dunqueeee .... vediamo di chiarire bene cosa vogliamo fare: vogliamo che quando faccio doppio click sul .cpl, tale file venga mappato nello spazio di memoria di un processo remoto; behh ... se la matematica non è un opinione, allora... l'entrypoint del mio .cpl verrà chiamato ben 2 volte: una volta quando il .cpl viene caricato da rundll32 ed una seconda volta quando viene effettivamente caricato nello spazio di memoria del processo remoto. Nel primo caso dovrò eseguire la procedura di dll injection, mentre nel secondo caso dovrò eseguire il codice che voglio mandare in esecuzione nello spazio di memoria del processo remoto (tipo ad esempio l'Hook su alcune API). Come faccio a distinguere le 2 situazioni? La prima soluzione che mi è venuta in mente è stata quella di controllare la linea di comando (tramite GetCommandLine), a quel punto il cervello ha cominciato a rodermi un pò, i pensieri si intrecciavano, poi alla fine ... la luce: usiamo la CPLApplet per creare un Global Atom tramite l' API win32 GlobalAddAtom, e tale valore servirà per controllare se siamo in Fase 1 o in Fase 2. Vediamo la sequenza temporale degli eventi per capirci meglio 1) CPL caricata nello spazio di memoria di rundll32: chiamata all'entrypoint della CPL (Fase 1) 2) chiamata alla funzione CPLApplet con valore 1 del parametro uMsg; tale funzione crea il Global Atom, poi esegue il caricamento della CPL nello spazio di memoria del processo remoto: chiamata all'entrypoint della CPL (Fase 2); eseguito il dll injection, la funzione elimina il Global Atom, poi restituisce 0 causando l'immediato scaricamento della CPL dallo spazio di memoria di rundll32 e la terminazione di rundll32 medesimo. Quindi: Fase 1: il Global Atom non esiste Fase 2: il Global Atom esiste Behh, visto e considerato che le procedure di dll injection sono già state abbondantemente approfondite negli articoli precedenti, non resta altro da fare che implementare il tutto. 2. Implementazione Nell'implementazione facciamo uso delle procedure già esposte ed approfondite nell'articolo articolo12.htm
library Applet;
uses
Windows, SysInit, uUtils;
{$E cpl}
var
atom: integer;
procedure InjectSelf();
var
PID: dword;
lpFileName: array [0..MAX_PATH - 1] of char;
baseAddr, err: Cardinal;
begin
//
//scegliamo notepad come al solito; basta cambiare il nome dell'eseguibile
//per ottenere il self injection nello spazio di memoria di un altro processo
PID := PidProcesso('notepad.exe');
GetModuleFileName(hInstance, @lpFileName, MAX_PATH);
if not remoteloadlibrary(PID, (lpFileName), baseAddr, err, True) then
begin
MessageBox(0, pchar('errore nell''injecting'), 'Errore!', 0);
end
else
begin
if err <> 0 then
MessageBox(0,
pchar('Errore nel caricamento; Cod errore: '),
'Errore!',
0)
else
MessageBox(0,
pchar('dll caricata all''indirizzo: ' + IntToHex(baseAddr, 8)),
'Eureka!',
0);
end;
end;
function CPlApplet(hwndCPl: Cardinal;
uMsg : Cardinal;
lParam1: Integer;
lParam2: Integer
): Integer; stdcall;
begin
case uMsg of
1:
begin
atom := GlobalAddAtom('cpl_auto_injection') ;
InjectSelf;
GlobalDeleteAtom(atom);
Result := 0;
end;
end;
end;
procedure EntryPointProc(reason: integer);
begin
case reason of
DLL_PROCESS_ATTACH: //1
begin
DisableThreadLibraryCalls(HInstance);
//MessageBox(0, pchar(ParamStr(0)), 'Ciauzz', 0);
//se l'atom è già stato creato allora siamo nella fase di caricamento
//nello spazio di memoria del processo remoto
if GlobalFindAtom('cpl_auto_injection') <> 0 then
begin
//inserisco qui la chiamata alle procedure implementate nella cpl
//come ad esempio procedure di settaggio di Hook, etc...
end;
end;
DLL_THREAD_ATTACH: //2
begin
end;
DLL_PROCESS_DETACH: //3
begin
end;
DLL_THREAD_DETACH: //0
begin
end;
end;
end;
exports
CPlApplet index 1;
begin
//codice di inizializzazione della dll
DllProc := @EntryPointProc;
DllProc(DLL_PROCESS_ATTACH);
end.
Come si può vedere, basta la direttiva {$E cpl} per far sì che l'output della compilazione e del linking sia un file con estensione .cpl; in ogni caso si potrebbe anche rinomiare successivamente l'estensione da dll in cpl. cpl_AutoInjection.7z
|