PDA

View Full Version : Hookare ntdll.dll


GordonFreeman
05-10-2005, 03:39
qualcuno ha mai provato a "spiare" le funzioni NtCreateFile() e NtOpenFile() ?
intendo dire scrivendo , nei primi byte di tali funzioni , un salto in una funzione di hook che intercetti tutte le chiamate alle funzioni originali

GordonFreeman
05-10-2005, 23:39
nessuno?

cionci
06-10-2005, 09:40
In pratica quello che fanno gli antivirus ? Ho cercato tanto tempo, ma non sono mai riuscito a trovare niente se non un documento MS con un sorgente fatto in assembly...

GordonFreeman
06-10-2005, 19:55
In pratica quello che fanno gli antivirus ? Ho cercato tanto tempo, ma non sono mai riuscito a trovare niente se non un documento MS con un sorgente fatto in assembly...

no,gli antivirus hanno un filter driver,con cui comunicano e che ovviamente è in modo kernel e intercetta ogni scrittura sul file system,basta installare il driver come un (filter) driver di file system e il kernel quando riceve un IORP lo passa a tale driver perchè lo elabori

io dicevo di fare l'hooking in modo utente,come fanno i RootKit modo utente per nascondere i file che usano,i loro processi e backdoor

71104
06-10-2005, 20:15
come vuoi che si faccia scusa, si fa come tutte le altre funzioni... non ho mai provato con NtXxx ma l'ho fatto spesso con molte altre; per le NtXxx è la stessa cosa, anche se sarebbe preferibile scrivere un driver e hookare direttamente la int 2E (se se ne ha la possibilità ovviamente).

GordonFreeman
06-10-2005, 20:34
come vuoi che si faccia scusa, si fa come tutte le altre funzioni... non ho mai provato con NtXxx ma l'ho fatto spesso con molte altre; per le NtXxx è la stessa cosa, anche se sarebbe preferibile scrivere un driver e hookare direttamente la int 2E (se se ne ha la possibilità ovviamente).

si lo so che si fà scrivendo il salto relativo nei primi byte,ma il problema è questo:

nella funzione hook prima o poi bisogna chiamare la funzione originale,e quindi bisogna prima scrivere i byte originali dove c'è il salto,poi chiamare la funzione,e poi riscrivere il salto

ora,supponiamo che il processo abbia più di un thread,e che uno dei thread stia per chiamare una funzione hookata ma viene bloccato dal OS e ne viene schedulato un'altro...quello che è in esecuzione adesso viene eseguito per un po',poi facciamo conto che cominci a scrivere bytes nella funzione da hookare (i soliti byte di salto),e che venga sospeso a metà scrittura...ecco,quando il thread che prima è stato bloccato viene risvegliato,esso eseguirà spazzatura e il processo terminerà,o comunque non farà quello che dovrebbe

bisognerebbe sincronizzare l'accesso alla funzione da hookare,non mi sembra un problema banale...

ma io non parlo del mio processo,quello chi se ne frega,basterebbe che io usi una sezione critica per chiamare la funzione da hookare,così i miei thread saranno sincronizzati...il problema sono i processi remoti a cui io inietto del codice per spiarlo...

insomma la stessa cosa che fanno i RootKit modo utente o altro software malware,sempre modo utente...come fanno a risolvere questo problema??

fantoibed
06-10-2005, 20:50
qualcuno ha mai provato a "spiare" le funzioni NtCreateFile() e NtOpenFile() ?
Se devi semplicemente monitorare la creazione e/o l'apertura dei file, ti consiglio di scaricare gratuitamente l'utility fiemon da sysinternals.com

Se vuoi invece metterti a studiare le API native di Windows, allora ti consiglio di cercare gli articoli di Matt Pietrek su MSDN. Una volta teneva una rubrica chiamata "under the hood" che era molto interessante, comunque qualcosa si può già accennare anche qui.

In pratica le funzioni di tipo NtBlablabla() e ZwBlblabla() ecc... di ntdll.dll sono le cosiddette Api native di windows (spesso poco o per niente documentate) e servono da wrapper verso le syscall del sistema operativo. Se prendi ntdll.dll e la dai in pasto ad un disassemblatore tipo IDAPro, ti leggerai una sfilza di codice del tipo:

arg_0 = byte ptr 4
mov eax, 4Ch ; NtOpenDirectoryObject
lea edx, [esp+arg_0]
int 2Eh
retn 0Ch
_NtOpenDirectoryObject@12 endp

_NtOpenEvent@12 proc near
arg_0 = byte ptr 4
mov eax, 4Dh ; NtOpenEvent
lea edx, [esp+arg_0]
int 2Eh
retn 0Ch
_NtOpenEvent@12 endp

...


...e così via. NtCreateFile() e ZwCreateFile(), ad esempio, chiamano sempre l'interrupt 2Eh con 17h in eax.
In pratica, l'interrupt 2Eh fa' da wrapper tra le routine a ring0 e quelle a ring3. Scommetto che vi ricorda qualcosa... :p (int 21h in MS-DOS...)

In Windows NT, però non l'interrupt 2Eh non è l'unico interrupt fornito dal kernel:
002A IntG32 0008:8013E756 DPL=3 P _KiGetTickCount
002B IntG32 0008:8013E840 DPL=3 P _KiCallbackReturn
002C IntG32 0008:8013E950 DPL=3 P _KiSetLowWaitHighThread
002D IntG32 0008:8013F50C DPL=3 P _KiDebugService
002E IntG32 0008:8013E2D0 DPL=3 P _KiSystemService
002F IntG32 0008:801416F8 DPL=0 P _KiTrap0F

Questo dump è ottenuto con un dump dell'IDT ottenuto con SoftICE (preso da un articolo scritto da un mio amico, l'OS mi pare fosse Win2000). Comunque se avete SoftICE attivo, il comando è IDT. :p

In pratica, le funzioni di ntdll.dll chiamano semplicemente _KiSystemService con diversi valori di eax in base alla sottofunzione (di _KiSystemService) da chiamare. Il DPL indica il livello di privilegio richiesto per poter chiamare quella determinata routine, se è =3 la possiamo chiamare, se è =0 la può utilizzare solo il kernel. ...(in realtà qui ci sarebbero un mare di cose da dire, diciamo che ho semplificato con la mannaia)....

Per ravanare più a fondo bisogna scriversi un driver... Si inizia con
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath )
{
....

...e poi si va avanti... :p

GordonFreeman
06-10-2005, 21:03
Se devi semplicemente monitorare la creazione e/o l'apertura dei file, ti consiglio di scaricare gratuitamente l'utility fiemon da sysinternals.com

Se vuoi invece metterti a studiare le API native di Windows, allora ti consiglio di cercare gli articoli di Matt Pietrek su MSDN. Una volta teneva una rubrica chiamata "under the hood" che era molto interessante, comunque qualcosa si può già accennare anche qui.

In pratica le funzioni di tipo NtBlablabla() e ZwBlblabla() ecc... di ntdll.dll sono le cosiddette Api native di windows (spesso poco o per niente documentate) e servono da wrapper verso le syscall del sistema operativo. Se prendi ntdll.dll e la dai in pasto ad un disassemblatore tipo IDAPro, ti leggerai una sfilza di codice del tipo:

arg_0 = byte ptr 4
mov eax, 4Ch ; NtOpenDirectoryObject
lea edx, [esp+arg_0]
int 2Eh
retn 0Ch
_NtOpenDirectoryObject@12 endp

_NtOpenEvent@12 proc near
arg_0 = byte ptr 4
mov eax, 4Dh ; NtOpenEvent
lea edx, [esp+arg_0]
int 2Eh
retn 0Ch
_NtOpenEvent@12 endp

...


...e così via. NtCreateFile() e ZwCreateFile(), ad esempio, chiamano sempre l'interrupt 2Eh con 17h in eax.
In pratica, l'interrupt 2Eh fa' da wrapper tra le routine a ring0 e quelle a ring3. Scommetto che vi ricorda qualcosa... :p (int 21h in MS-DOS...)

In Windows NT, però non l'interrupt 2Eh non è l'unico interrupt fornito dal kernel:
002A IntG32 0008:8013E756 DPL=3 P _KiGetTickCount
002B IntG32 0008:8013E840 DPL=3 P _KiCallbackReturn
002C IntG32 0008:8013E950 DPL=3 P _KiSetLowWaitHighThread
002D IntG32 0008:8013F50C DPL=3 P _KiDebugService
002E IntG32 0008:8013E2D0 DPL=3 P _KiSystemService
002F IntG32 0008:801416F8 DPL=0 P _KiTrap0F

Questo dump è ottenuto con un dump dell'IDT ottenuto con SoftICE (preso da un articolo scritto da un mio amico, l'OS mi pare fosse Win2000). Comunque se avete SoftICE attivo, il comando è IDT. :p

In pratica, le funzioni di ntdll.dll chiamano semplicemente _KiSystemService con diversi valori di eax in base alla sottofunzione (di _KiSystemService) da chiamare. Il DPL indica il livello di privilegio richiesto per poter chiamare quella determinata routine, se è =3 la possiamo chiamare, se è =0 la può utilizzare solo il kernel. ...(in realtà qui ci sarebbero un mare di cose da dire, diciamo che ho semplificato con la mannaia)....

Per ravanare più a fondo bisogna scriversi un driver... Si inizia con
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath )
{
....

...e poi si va avanti... :p

heehhe beh se sapessi scrivere un filter driver del file system non avrei nemmeno scritto questo post,il problema non si sarebbe posto

ok,quindi la mia funzione hook , quando deve chiamare la funzione originale , invece di chiamarla deve fare semplicemente l'interrupt 0x2E con un certo valore in EAX,tutto qua?? e questo è come aver chiamato la funzione originale? se è così è perfetto,quindi NtCreateFile() potrei hookarla così,ad esempio



NTSTATUS NtCreateFileHook(...){

NTSTATUS iReturn;

//... esamina i parametri,etc.

_asm {
// PUSHa i parametri sullo stack
MOV EAX,0x17
INT 0x2E
MOV iReturn,EAX
};

return iReturn;

}



giusto??

fantoibed
06-10-2005, 21:20
giusto??
No. E' più complesso. :boh: Inizia a leggere il funzionamento di filemon:
http://www.sysinternals.com/Utilities/Filemon.html

Ci sono diversi articoli in coda. :p

GordonFreeman
06-10-2005, 21:36
No. E' più complesso. :boh: Inizia a leggere il funzionamento di filemon:
http://www.sysinternals.com/Utilities/Filemon.html

Ci sono diversi articoli in coda. :p

ho visto,gli articoli non danno alcuna informazione in merito
comunque per capire cosa devo fare devo imparare a scrivere un driver??
se è così tanto vale che faccio un filter driver :(

oppure potrei fare un'altra cosa: carico una nuova dll che è identica a ntdll,ma cambia il nome del file,e chiamo NtCreateFile() di quella nuova dll...può funzionare?

fantoibed
06-10-2005, 21:58
Sinceramente soffro un po' di carenze di tempo libero e non riesco a farti degli esempi. Su sysinternals trovi i sorgenti di programmi simili a filemon (anche se non proprio di quello, purtroppo) e varie info. :p
http://www.sysinternals.com/Information/NativeApi.html
http://www.sysinternals.com/SourceCode.html
http://www.sysinternals.com/SourceCode/VcMon.html

:p

GordonFreeman
06-10-2005, 22:09
Sinceramente soffro un po' di carenze di tempo libero e non riesco a farti degli esempi. Su sysinternals trovi i sorgenti di programmi simili a filemon (anche se non proprio di quello, purtroppo) e varie info. :p
http://www.sysinternals.com/Information/NativeApi.html
http://www.sysinternals.com/SourceCode.html
http://www.sysinternals.com/SourceCode/VcMon.html

:p

ma no,inutile che cerchi di insegnarmi a scrivere un driver,non lo voglio imparare :) vorrebbe dire buttare via tutto il mio progetto e ricominciare da zero,non esiste...e poi non serve che mi fai esempi,ho già un libro sul WDM,che non ho ancora aperto,lo leggerò molto + avanti

ci devono essere soluzioni + semplici cavolo...ad esempio quella della dll che ho detto non potrebbe funzionare?

oppure avrei un'altra idea:inietto una dll,e in DLL_PROCESS_ATTACH e DLL_THREAD_ATTACH salvo i thread identifiers (TID) da qualche parte usando GetCurrentThreadId(),poi nelle mie funzioni hook uso SuspendThread() passandogli gli handle a tutti i thread del processo forchè quello corrente,faccio il mio lavoro e poi chiamo ResumeThread()

tutto questo in una sezione critica,ovvero compreso fra le due chiamate EnterCriticalSection() e LeaveCriticalSection()

che ne dici? c'è pericolo di deadlock o altri problemi?

71104
06-10-2005, 22:14
oppure potrei fare un'altra cosa: carico una nuova dll che è identica a ntdll,ma cambia il nome del file,e chiamo NtCreateFile() di quella nuova dll...può funzionare?si, oppure soluzione alternativa quando sei entrato in un processo da hookare modifica la IAT.
per sincronizzare l'accesso alla funzione da hookare avevo scritto un articolo su Usenet tempo fa in cui esponevo un sistema di mia invenzione che si basa sull'atomicità delle operazioni di trasferimento di un numero massimo di 32 bit (4 bytes).
in sintesi funziona così: prima di scrivere il JMP scrivi un'istruzione RET N (che occupa 3 bytes se ben ricordo, quindi sarebbe un'operazione atomica), poi scrivi gli ultimi 2 bytes del JMP, e poi scrivi i primi 3 bytes del JMP (sono tutte operazioni atomiche); quello che accade è che semplicemente per un piccolissimo istante di tempo la funzione se chiamata semplicemente non farà nulla, ritornerà immediatamente con un valore indefinito in EAX; rarissimi i casi in cui una simile condizione provoca un crash del programma.
altrimenti, soluzione decisamente migliore, implementi il sistema dei trampolines come quello delle Detours (scaricatele dal sito di Microsoft Research: sono molto istruttive ;)).

71104
06-10-2005, 22:16
oppure avrei un'altra idea:inietto una dll,e in DLL_PROCESS_ATTACH e DLL_THREAD_ATTACH salvo i thread identifiers (TID) da qualche parte usando GetCurrentThreadId(),poi nelle mie funzioni hook uso SuspendThread() passandogli gli handle a tutti i thread del processo forchè quello corrente,faccio il mio lavoro e poi chiamo ResumeThread()

tutto questo in una sezione critica,ovvero compreso fra le due chiamate EnterCriticalSection() e LeaveCriticalSection()

che ne dici? c'è pericolo di deadlock o altri problemi?
se lo implementi bene funzionerà di sicuro, ma è inutilmente complesso, senza contare che un thread potrebbe venir creato **dopo** che hai preso lo snapshot, e quindi non lo vedi; leggi piuttosto la mia risposta precedente o studiati il codice delle Detours :)

EDIT: considera anche l'ipotesi di usare la soluzione della IAT: modificare uno slot nella IAT è un'operazione atomica, quindi non causerebbe nessun problema di sincronizzazione!! ;)

GordonFreeman
06-10-2005, 23:35
se lo implementi bene funzionerà di sicuro, ma è inutilmente complesso, senza contare che un thread potrebbe venir creato **dopo** che hai preso lo snapshot, e quindi non lo vedi; leggi piuttosto la mia risposta precedente o studiati il codice delle Detours :)

EDIT: considera anche l'ipotesi di usare la soluzione della IAT: modificare uno slot nella IAT è un'operazione atomica, quindi non causerebbe nessun problema di sincronizzazione!! ;)

allora,modificare la PE ExportDirectory come dici tu lo so fare,ma non mi va bene,perchè devo scrivere un anti RootKit modo utente,e un processo RootKit se ne frega della IAT,perchè chiama direttamente le funzioni native di windows nt con salti assoluti,oppure mi frega persino usando LoadLibrary()

e lui stesso l'API hooking non lo fa scrivendo sull'export directory,ma scrivendo il salto sui primi byte delle funzioni native

se non ci credi prova a vedere il sorgente di ntillusion o qualche guida al file hiding e process hiding in windows nt
www.syshell.org
www.rootkit.com

quindi direi che devo usare anch'io lo stesso metodo se voglio spiare un processo rootkit e scoprire che esso usa file nascosti

poi per il fatto del thread che viene creato dopo...scusa se io catturo DLL_THREAD_ATTACH la mia dll non viene notificata che il thread è stato creato? se no a cosa serve il messaggio

il metodo che dici tu non l'ho capito da come me l'hai spiegato,quando ho tempo leggerò quell'articolo ;)

71104
06-10-2005, 23:59
che ne so io di quello che devi fare, per quello che chiedevi nel post originale la manipolazione della IAT andava benissimo :D
e comunque non credere che sia una tecnica tanto inusitata, anzi... ;)
per quanto riguarda i thread prima avevo capito male, pensavo che volessi enumerarli e sospenderli tutti; rileggendo bene però penso che comunque fai hai sempre e comunque un piccolo momento di tempo in cui potrebbe venir creato un thread senza che te ne accorgi (o meglio, te ne accorgi dopo): che succede se un thread viene creato proprio mentre stai ciclando sulla tua lista di TID per sospenderli tutti? be', oddio a pensarci bene in effetti si può risolvere sincronizzando la risorsa su cui lavori (cioè la lista dei TID), sempre che la notifca di DLL_THREAD_ATTACH avvenga nel contesto del nuovo thread creato (e non ne sono sicuro), ma ripeto che è inutilmente complicato: penso sia più semplice risolvere il problema di sincronizzazione applicando un trampoline utilizzando la mia tecnica: in tal modo applichi il JMP in maniera atomica, quasi sicuramente indolore, e soprattutto lo fai una volta sola (al massimo due), senza bisogno di togliere e rimettere il JMP tutte le volte.

GordonFreeman
07-10-2005, 00:47
che ne so io di quello che devi fare, per quello che chiedevi nel post originale la manipolazione della IAT andava benissimo :D
e comunque non credere che sia una tecnica tanto inusitata, anzi... ;)
per quanto riguarda i thread prima avevo capito male, pensavo che volessi enumerarli e sospenderli tutti; rileggendo bene però penso che comunque fai hai sempre e comunque un piccolo momento di tempo in cui potrebbe venir creato un thread senza che te ne accorgi (o meglio, te ne accorgi dopo): che succede se un thread viene creato proprio mentre stai ciclando sulla tua lista di TID per sospenderli tutti? be', oddio a pensarci bene in effetti si può risolvere sincronizzando la risorsa su cui lavori (cioè la lista dei TID), sempre che la notifca di DLL_THREAD_ATTACH avvenga nel contesto del nuovo thread creato (e non ne sono sicuro), ma ripeto che è inutilmente complicato: penso sia più semplice risolvere il problema di sincronizzazione applicando un trampoline utilizzando la mia tecnica: in tal modo applichi il JMP in maniera atomica, quasi sicuramente indolore, e soprattutto lo fai una volta sola (al massimo due), senza bisogno di togliere e rimettere il JMP tutte le volte.

allora,per la sincronizzazione è una cavolata...in DLL_PROCESS_ATTACH chiamo InitializeCriticalSection(),poi uso la stessa sezione critica sia prima di sospendere i thread (nella funzione hook) sia prima di aggiungere un nuovo thread alla lista dei TID (in DLL_THREAD_ATTACH),quindi è impossibile aggiungere un nuovo TID mentre un altro thread sta accedendo alla lista etc.

e il contesto è quello del thread corrente se no GetCurrentThreadId() non funzionerebbe , mi ritornerebbe un TID sbagliato , che mi sembra una cosa assurda,non credo che MS abbia fatto un errore così grossolano ;)

poi il fatto della dll "copia" caricata con LoadLibrary() per chiamare la funzione originale...ha un problema: il rootkit ha hookato LdrLoadDll() in ntdll.dll,quindi potrebbe fare una bastardata,lui prima di caricare la mia dll controlla le informazioni PE e si accorge che la dll è in realtà un ntdll.dll rinominato...e poi quello che fà lo lascio alla fantasia,magari carica la dll che voglio ma poi la hooka,oppure me ne carica un'altra,...che ne so

allora pensando a questa cosa ho provato a fare questo : mi son caricato la dll a mano mappando il file in memoria,poi usando
VirtualAlloc(...,PAGE_EXECUTE_READWRITE,...) per allocare tanta memoria quanta ne occupa l'immagine del modulo (quando è caricata in memoria)
e poi copiando tutto il contenuto delle varie sezioni del file dentro questa immagine in memoria...poi infine ho chiamato la DllEntryPoint() passandole i parametri HINSTANCE = indirizzo ritornato da virtualalloc, DWORD = DLL_PROCESS_ATTACH e LPVOID=NULL per inizializzare la dll

poi quando devo scrivere i byte di salto,se uso gli originali NtProtectVirtualMemory() (o VirtualProtect() ),il rootkit mi intercetta e vede che c'è qualcosa di sospetto,e anche qui potrebbe fare qualsiasi cosa,tipo far fallire la chiamata,oppure "scappare via" terminando il suo processo o non usando + i file nascosti etc. in modo che nessuno possa scoprire che è lui che li usa..e quant'altro...quindi devo usare NtQueryVirtualMemory() etc. della dll caricata a mano

ho provato a caricare a mano ntdll , kernel32 e psapi...e con quest'ultima va in crash il programma quando chiamo l'entrypoint...mah non ho idea del perchè

ecco,tornando a noi...nella funzione hook io chiamavo la funzione originale in questa dll caricata a mano...e funzionava tutto apparentemente bene con varie funzioni che ho provato...eccetto NtCreateFile() e
NtOpenFile()...quando ho hookato notepad.exe , winamp.exe , iexplore.exe , vlc.exe e zonealarm.exe per fare una prova...tutti i processi forchè zonealarm non riuscivano a leggere i file,dava un MessageBox di errore tipo "ID di periferica oltre al valore limite imposto da windows" , o una roba del genere , oppure semplicemente diceva che il file non esiste o è impossibile aprirlo

ecco,ho raccontato la mia vita morte e miracoli,e adesso hai capito perchè voglio adottare il metodo "chiamare le vere funzioni originali"

i trampolini che dici tu proverò a vederli,ma mi puzza un po',dimmi se nel mio caso può funzionare

71104
07-10-2005, 11:30
[...] ho provato a caricare a mano ntdll , kernel32 e psapi...e con quest'ultima va in crash il programma quando chiamo l'entrypoint...mah non ho idea del perchè fa' vedere il codice di binding e rilocamento... comunque guarda che potevi mappare il file in modo molto semplice creando un file mapping senza chiamare VirtualAlloc 30.000 volte :) (oltrettutto col pericolo che le pagine allocate non siano contigue)
se vuoi ti faccio vedere il codice di un mio virus che lo fa (attualmente quel virus ha qualche problema che sto cercando di risolvere).

[...] ecco,ho raccontato la mia vita morte e miracoli,e adesso hai capito perchè voglio adottare il metodo "chiamare le vere funzioni originali" se il tuo problema è solo che il RootKit potrebbe averti già hookato funzioni chiave allora puoi risolvere da subito nel thread remoto che crei inizialmente per caricare la tua DLL nel processo target: in quel thread devi fare dei controlli per vedere se ci sono API native col JMP iniziale.
l'entry point di questo thread in teoria dovresti anche scriverlo in assembly puro e con anche qualche __emit dal momento che quando lo allochi nell'altro processo non è rilocato e quindi deve essere tutto completamente "position independent", ma di do' una dritta: in Visual C++ 6 puoi scriverlo in C alla condizione che non usi lo statement swicth ;)

i trampolini che dici tu proverò a vederli,ma mi puzza un po',dimmi se nel mio caso può funzionare e perché no... ti dico esattamente cosa ti risolve questo sistema: non hai bisogno di togliere e rimettere il JMP all'inizio della funzione originale ogni volta che la tua routine di intercettazione viene chiamata; invece facendo come fai tu (cioè appunto togliendolo e rimettendolo ogni volta) hai i problemi di sincronizzazione ormai ben noti.
con i trampolines il JMP lo metti solo una volta in fase di inizializzazione e lo togli in fase di finalizzazione; anzi, quasi quasi ti consiglio proprio di usare le Detours :) sono efficientissime e nonostante i sorgenti sembrino molto grossi l'overhead di codice che causano nel tuo programma nel 90% dei casi è molto piccolo ;)

PS: girando qua e là per Internet ho visto una volta un PDF che descriveva un sistema uguale a quello dei trampolines, solo che li chiamava semplicemente "stubs".

71104
07-10-2005, 11:40
mi viene in mente una cosa: per applicare la tecnica dei trampolines è necessario un disassemblatore integrato nel tuo programma; chiaramente non ne puoi realizzare uno, ma è altrettanto assurdo sospendere tutti i threads ogni volta che togli e rimetti un JMP, quindi il mio consiglio è di utilizzare il disassemblatore delle Detours (sempre che non usi le Detours stesse), che è ottimo: è leggero e sicuro (fa anche dei "sanity checks").

GordonFreeman
07-10-2005, 15:42
fa' vedere il codice di binding e rilocamento... comunque guarda che potevi mappare il file in modo molto semplice creando un file mapping senza chiamare VirtualAlloc 30.000 volte :) (oltrettutto col pericolo che le pagine allocate non siano contigue)
se vuoi ti faccio vedere il codice di un mio virus che lo fa (attualmente quel virus ha qualche problema che sto cercando di risolvere).

il codice è un casino,comunque il rilocamento non l'ho fatto :( forse è quella la causa?? ho semplicemente copiato il contenuto delle varie sezioni (".text",".rdata",".idata",".reloc",eccetera) nell'immagine di memoria,e poi ho chiamato la funzione di entry point...non basta?

poi per le pagine di memoria fisica non contigue chi se ne frega, quello che conta è che le pagine di memoria VIRTUALE siano contigue,e non potrebbe essere altrimenti,scusa,se no andrebbe in crash il programma se solo faccio un memcpy() o memset() per scrivere su tutta quella memoria...ma questo non succede,quindi sono contigue

p.s. se vuoi il codice te lo mando,ma è lungo...e il binding cosa sarebbe ? forse serve a trovare l'indirizzo di una funzione partendo dal suo nome? non serve,io guardo direttamente nella ExportDirectory


se il tuo problema è solo che il RootKit potrebbe averti già hookato funzioni chiave allora puoi risolvere da subito nel thread remoto che crei inizialmente per caricare la tua DLL nel processo target: in quel thread devi fare dei controlli per vedere se ci sono API native col JMP iniziale.
l'entry point di questo thread in teoria dovresti anche scriverlo in assembly puro e con anche qualche __emit dal momento che quando lo allochi nell'altro processo non è rilocato e quindi deve essere tutto completamente "position independent", ma di do' una dritta: in Visual C++ 6 puoi scriverlo in C alla condizione che non usi lo statement swicth ;)

allora,il rootkit fa hooking a tutti i processi,anche al mio anti-rootkit,quindi è già scontato che le funzioni sono state sovrascritte col jmp , e io non posso usare semplicemente CreateRemoteThread() o NtCreateThread() perchè l'anti rootkit me la cattura e non mi lascia fare injection se l'handle che gli passo è quello del suo processo,magari nascosto,...oppure magari mi fà fallire la chiamata in ogni caso,o quant'altro...insomma devo creare il thread senza usare le funzioni hookate dal rootkit...e quindi mappo una nuova ntdll a mano e chiamo NtCreateThread() di quella dll....poi come ti ho già detto non posso usare VirtualProtect() originale e company per scrivere sopra le funzioni,perchè il rootkit me le ha hookate e non mi lascia,quindi chiamo quella caricata a mano



e perché no... ti dico esattamente cosa ti risolve questo sistema: non hai bisogno di togliere e rimettere il JMP all'inizio della funzione originale ogni volta che la tua routine di intercettazione viene chiamata; invece facendo come fai tu (cioè appunto togliendolo e rimettendolo ogni volta) hai i problemi di sincronizzazione ormai ben noti.
con i trampolines il JMP lo metti solo una volta in fase di inizializzazione e lo togli in fase di finalizzazione; anzi, quasi quasi ti consiglio proprio di usare le Detours :) sono efficientissime e nonostante i sorgenti sembrino molto grossi l'overhead di codice che causano nel tuo programma nel 90% dei casi è molto piccolo ;)

PS: girando qua e là per Internet ho visto una volta un PDF che descriveva un sistema uguale a quello dei trampolines, solo che li chiamava semplicemente "stubs".

ma che strano,e come fanno a emulare le funzioni originali? sicuramente non le chiamano,perchè tu dici che il salto resta sempre,quindi in teoria ne chiamano una copiata a mano dall'originale prima di mettere il salto...boh leggerò l'articolo,comunque spiegami + o meno come emulano la funzione originale

71104
07-10-2005, 17:56
il codice è un casino,comunque il rilocamento non l'ho fatto :( forse è quella la causa?? ho semplicemente copiato il contenuto delle varie sezioni (".text",".rdata",".idata",".reloc",eccetera) nell'immagine di memoria,e poi ho chiamato la funzione di entry point...non basta? se non hai rilocato allora la causa del crash è sicuramente quella... devi rilocare per forza: se non l'hai fatto probabilmente dopo 1 po' vanno in crash anche tutti gli altri moduli (compresa la tua DLL); il motivo percui non crashano subito è che evidentemente nell'entry point non usano nessuno statement switch, nessun puntatore a variabili globali, e nessun altra causa di fixup.
leggiti bene il doc di Microsoft sul formato PE ;) è molto dettagliato (è completo tranne che per alcuni tipi di fixup praticamente inutilizzati, roba del Mac, e per un formato proprietario di informazioni di debug che però a te non interessa).

poi per le pagine di memoria fisica non contigue chi se ne frega, quello che conta è che le pagine di memoria VIRTUALE siano contigue,e non potrebbe essere altrimenti,scusa,se no andrebbe in crash il programma se solo faccio un memcpy() o memset() per scrivere su tutta quella memoria...ma questo non succede,quindi sono contigue io infatti non parlavo di indirizzi fisici, parlavo proprio di indirizzi virtuali... che succede se dopo aver allocato la prima pagina, durante l'allocazione della seconda il sistema operativo si accorge che il programma ne ha già allocata una in quella posizione? deve saltare di qualche kilobyte e allocare dopo, e quindi le tue sezioni non sono più contigue (e non sto parlando di indirizzi fisici); è molto meglio lasciar fare al sistema operativo utilizzando un oggetto file mapping (che oltrettutto puoi anche condividere tra più processi risparmiando memoria quando non devi rilocare).
qui sotto ti ho messo il codice del mio virus, che fa esattamente questa cosa; occhio però che ha un problema (quasi) noto che devo ancora risolvere :(
mi funziona perfettamente se lo metto in un paio di processi, ma provoca schermata blu ( :eek: ) se lo metto in tutti...

<il codice è stato tempestivamente rimosso>

questa è la versione base del virus che mostra una semplice MessageBox ogni 5 sec. proveniente da tutti i processi... cioè da ciascun processo ogni 5 sec arriva la MessageBox (puoi immaginarti che cosa fastidiosa :p).
avevo anche scritto un piccolo txt contenente la descrizione delle caratteristiche e delle tecniche usate nel virus, che ti copio di seguito (ce n'è una che non ho ancora applicato: l'invisibilità nel call stack).

1. dimensioni ridotte
2. invisibile
3. autorilocante
4. autobindante
5. entry point mascherato
6. anti disassemblatore (solo in alcuni punti)
7. anti API tracer
8. invisibile nel call stack
9. intercetta CreateProcessW e si rimappa in tutti i nuovi processi


1. Dimensioni ridotte

Nella versione compilata in modalità Release è stato rimosso il runtime di Visual C++: il file risulta più piccolo di circa 16 kb ma deve usare solo chiamate API: no problem, visto che nel suo runtime VC++ traduce tutte le funzioni standard del C in chiamate API.


2. Invisibilità

La DLL (che può essere caricata solo in runtime con LoadLibrary) resituisce FALSE dalla DllMain, fingendo di fallire il caricamento; dunque non può essere caricata una sua copia visibile, ma prima di ritornare con FALSE la DllMain chiama una routine interna che crea una copia invisibile della DLL stessa scrivendo il contenuto del suo stesso file in un file mapping object che potrà anche essere condiviso negli altri processi del sistema.


3-4. Autorilocamento e autobinding

La copia invisibile inizia la sua esecuzione da uno speciale entry point che per prima cosa riloca la nuova copia (il codice di rilocamento non contiene fixup); successivamente la binda e crea un nuovo thread (il thread principale della DLL); infine ritorna.


5. Entry point mascherato

Lo speciale entry point dal quale inizia l'esecuzione della copia invisibile si chiamava inizialmente "_unknown" (è stato necessario esportarlo per conoscerne l'indirizzo in runtime), ma con un piccolo programma ho effettuato delle modifiche al file finale e adesso l'entry point si chiama letteralmente "(Not Found)": ottimo depistaggio per chi usa programmi come PEDUMP o il Dependency Walker.


6. Anti disassemblatore

Alcune parti importanti del codice sono offuscate ai disassemblatori: è presente un finto opcode che disallinea il codice che segue; il finto opcode non viene eseguito perché viene saltato da un JMP short relativo che salta di un solo byte.

Codice assembly:

JMP $+1; JMP short relativo che salta un byte
DB 0xB8 ; primo byte di un'istruzione MOV EAX,0xXXXXXXXX (mancano altri 4 bytes)
; altri opcodes...

un disassemblatore leggerà:

JMP $+1
MOV EAX,0xXXXXXXXX
; codice disallineato...

Spesso accade che dopo qualche byte il codice si riallinea per caso, quindi ne viene offuscata solo una piccola parte.
Se questa tecnica viene congiunta all'uso di SMC (Self-Modifying Code) il programma diventa anche abbastanza difficile da debuggare.


7. Anti API tracer

Questo è il pezzo forte :-)
Il tracing delle chiamate API (solo quelle da parte della DLL) è reso impossibile per due motivi:

1) la Import Address Table in runtime viene scambiata con la Import Lookup Table: il codice della DLL continua a fare riferimento alle entries corrette (quelle della Import Address Table), ma negli headers l'RVA della IAT è scambiato con quello della ILT; di conseguenza non è possibile rintracciare la IAT per modificarla a scopo di intercettazione (un ostacolo non impossibile da superare, ma una bella noia per un ipotetico cracker ;-))

2) il binding della copia invisibile viene fatto in maniera speciale: nella Import Address Table in ciascuno slot non viene messo l'indirizzo effettivo della rispettiva funzione API, ma un indirizzo che punta a un thunk che esegue i primi byte di codice della API e salta al suo indirizzo più alcuni byte; in tal modo i primi byte del codice della funzione API non vengono eseguiti (viene eseguita una copia di essi), e non c'è pericolo di eseguire JMP messi all'inizio della funzione come breakpoint (tipico sistema dei trampolines).
Generalizzando si può dire che non è possibile intercettare le chiamate da parte della DLL a nessuna delle funzioni importate, siano esse API di Windows o altro.

Nota bene: alcune funzioni API iniziano col seguente codice:

PUSH EBP
MOV EBP,ESP

che occupa 3 bytes, mentre altre iniziano con quest'altro:

MOV EAX,EAX
PUSH EBP
MOV EBP,ESP

che ne occupa 5; dal momento che l'istruzione

MOV EAX,EAX

è inutile, lo stub prima di saltare al codice vero della funzione esegue sempre il codice da 3 bytes (quello senza il mov), ma quando deve saltare all'indirizzo della funzione salta 3 bytes più avanti nel primo caso, e 5 nel secondo; questo significa che nel secondo caso un JMP messo come breakpoint (che occupa 5 bytes) viene completamente saltato e l'esecuzione della funzione prosegue senza problemi, mentre nel primo caso il JMP provoca quasi sicuramente un crash (disallinea il codice); nella migliore delle ipotesi i due bytes rimanenti del JMP non rappresentano nessun opcode, quindi la CPU li salta ed esegue il resto della funzione senza nessun crash.


8. Invisibilità nel call stack

Rompendo la catena dei frame pointers (i push ebp/mov ebp,esp che avvengono all'inizio di ogni chiamata) è possibile far "scomparire" del tutto le funzioni della DLL dal call stack: all'inizio di ogni funzione è sufficiente spingere nello stack un frame pointer di qualche frame più su anziché quello attuale per far credere che la chiamata provenga ad esempio da kernel32.dll anziché dalla DLL. In tal modo si possono anche ingannare alcuni tipi di firewall software che in generale, quando intercettano una richiesta di comunicazione, oltre al processo che l'ha fatta cercano di individuare anche il particolare modulo (e lo fanno analizzando il call stack).


9. Intercettazione della creazione di nuovi processi

La DLL setta un breakpoint alla funzione API CreateProcessW mettendo un JMP all'inizio; in tal modo può intercettare la creazione di nuovi processi e rimapparsi all'interno di essi.

NOTA BENE: una chiamata a CreateProcessW da parte della DLL stessa causa un crash perché il JMP messo come breakpoint viene saltato solo in parte, e il rimanente codice che viene eseguito è parzialmente disallineato.
Se la DLL ha la necessità di chiamare CreateProcessW deve chiamare MyCreateProcessW al suo posto, che sarebbe la routine di handling dell'intercettazione dei nuovi processi; in alternativa può chiamare la CreateProcessW, ma prima deve togliere temporaneamente il JMP chiamando SetJmp(FALSE) e poi lo deve rimettere con SetJmp(TRUE).


p.s. se vuoi il codice te lo mando,ma è lungo...e il binding cosa sarebbe ? il binding è la compilazione della Import Address Table (per compilazione ovviamente qui non intendo l'operazione di un compilatore, intendo semplicemente mettere i valori, riempire la tabella insomma).

allora,il rootkit fa hooking a tutti i processi,anche al mio anti-rootkit,quindi è già scontato che le funzioni sono state sovrascritte col jmp , e io non posso usare semplicemente CreateRemoteThread() o NtCreateThread() perchè l'anti rootkit me la cattura e non mi lascia fare injection se l'handle che gli passo è quello del suo processo,magari nascosto,...oppure magari mi fà fallire la chiamata in ogni caso,o quant'altro...insomma devo creare il thread senza usare le funzioni hookate dal rootkit... e quindi mappo una nuova ntdll a mano e chiamo NtCreateThread() di quella dll....poi come ti ho già detto non posso usare VirtualProtect() originale e company per scrivere sopra le funzioni,perchè il rootkit me le ha hookate e non mi lascia,quindi chiamo quella caricata a mano be' a parte che un malware così sofisticato da impedire la DLL injection lo devo ancora vedere :) in genere i programmatori di malware non si fanno troppe seghe mentali :) molti virus esistenti sono mooolto più niubbi di quelli che potresti creare tu o io ;)
comunque se hai paura che nel tuo processo originale CreateRemoteThread sia stata intercetta, semplicemente anche lì fai un controllo su tutte le funzioni API che importi per vedere se hanno dei JMP (e casomai controlla anche l'integrità della IAT ;)).

ma che strano,e come fanno a emulare le funzioni originali? sicuramente non le chiamano,perchè tu dici che il salto resta sempre,quindi in teoria ne chiamano una copiata a mano dall'originale prima di mettere il salto...boh leggerò l'articolo,comunque spiegami + o meno come emulano la funzione originale il salto nella funzione originale rimane sempre: per chiamarla la funzione di intercettazione non la deve chiamare direttamente (altrimenti è chiaro che provoca uno stack overflow dopo un breve loop), ma deve chiamare per l'appunto il trampolino che altro non è che uno stub dove sono stati salvati i primi bytes della funzione originale più un JMP finale che manda l'esecuzione al codice della funzione originale saltando però il JMP messo all'inizio (è un po' complicato :p)

71104
07-10-2005, 17:58
ehm, piccolo problema :stordita:
il txt si vede male perché HardwareUpgrade non fa il wordwrap automatico... :stordita:

edit: sistemato :cool:

cionci
07-10-2005, 18:00
il txt si vede male perché HardwareUpgrade non fa il wordwrap automatico... :stordita:
Certo che lo fa, basta che tu non lo metta fra i tag code...

71104: potresti rimuovere il codice del virus... Capisco che sia interessante, ma altra gente potrebbe sfruttarlo...

71104
07-10-2005, 18:12
Certo che lo fa, basta che tu non lo metta fra i tag code... me n'ero accorto, tutto sistemato :p :p :p

71104: potresti rimuovere il codice del virus... Capisco che sia interessante, ma altra gente potrebbe sfruttarlo... doh!!! :(
è proprio necessario? tanto non funziona, e probabilmente sono l'unico essere della terra in grado di correggere il problema in mezzo a quel "Coding Horror"... :D

cionci
07-10-2005, 18:15
è proprio necessario? tanto non funziona, e probabilmente sono l'unico essere della terra in grado di correggere il problema in mezzo a quel "Coding Horror"... :D
Nella storia dei virus ce ne sono stati tantissimi che non funzionavano a dovere ed hanno fatto più danni della grandine...

Magari invialo in PVT a GordonFreeman...

71104
07-10-2005, 18:17
Nella storia dei virus ce ne sono stati tantissimi che non funzionavano a dovere ed hanno fatto più danni della grandine...

Magari invialo in PVT a GordonFreeman...
vabbuo'...

GordonFreeman
07-10-2005, 22:35
io infatti non parlavo di indirizzi fisici, parlavo proprio di indirizzi virtuali... che succede se dopo aver allocato la prima pagina, durante l'allocazione della seconda il sistema operativo si accorge che il programma ne ha già allocata una in quella posizione? deve saltare di qualche kilobyte e allocare dopo, e quindi le tue sezioni non sono più contigue (e non sto parlando di indirizzi fisici); è molto meglio lasciar fare al sistema operativo utilizzando un oggetto file mapping (che oltrettutto puoi anche condividere tra più processi risparmiando memoria quando non devi rilocare).

ma questo è assurdo,scusa,allora uno alloca memoria inutilmente,perchè non può nemmeno fare un memset o memcpy su tutta la memoria perchè va a scrivere byte contigui mentre la memoria allocata non lo è,quindi andando a scrivere sulle pagine che stanno in mezzo e provocando un disastro...non ci credo,che porcheria è??? poi tu dici che dopo aver allocato la prima pagina windows si accorge che la seconda è occupata etc.. ma lol! scusa tu a virtualalloc o malloc gli dici quanta memoria vuoi allocare,e windows calcola quante pagine sono,poi quando va a vedere le pagine libere sceglie un blocco di pagine CONTIGUE per allocarti la memoria,mica alloca una pagina alla volta eccetera?? boh magari me lo leggerò sul libro "Inside Windows 2000" se è come dici tu o meno,ma è ridicolo accidenti


qui sotto ti ho messo il codice del mio virus, che fa esattamente questa cosa; occhio però che ha un problema (quasi) noto che devo ancora risolvere :(
mi funziona perfettamente se lo metto in un paio di processi, ma provoca schermata blu ( :eek: ) se lo metto in tutti...

<il codice è stato tempestivamente rimosso>

questa è la versione base del virus che mostra una semplice MessageBox ogni 5 sec. proveniente da tutti i processi... cioè da ciascun processo ogni 5 sec arriva la MessageBox (puoi immaginarti che cosa fastidiosa :p).
avevo anche scritto un piccolo txt contenente la descrizione delle caratteristiche e delle tecniche usate nel virus, che ti copio di seguito (ce n'è una che non ho ancora applicato: l'invisibilità nel call stack).

va in crash perchè alcune appllicazioni native non possono fare messagebox,ho provato anch'io quando iniettavo la dll in tutti i processi caricando anche user32.dll,facevo un messagebox ma non funzionava mi sembra su smss.exe e lsass.exe e altri...ma non ricordo se addirittura mi mandava la schermata blu :rolleyes: comunque prova ad evitare quei processi e vedi se fa il crash


il binding è la compilazione della Import Address Table (per compilazione ovviamente qui non intendo l'operazione di un compilatore, intendo semplicemente mettere i valori, riempire la tabella insomma).
ah sì,lo sapevo,il loader dinamico (LdrLoadDll()) và a scrivere gli indirizzi delle funzioni importate sull'array FirstThunk della import address table...ok ma è inutile,tanto sia io che il rootkit non la usiamo...


be' a parte che un malware così sofisticato da impedire la DLL injection lo devo ancora vedere :) in genere i programmatori di malware non si fanno troppe seghe mentali :) molti virus esistenti sono mooolto più niubbi di quelli che potresti creare tu o io ;)
comunque se hai paura che nel tuo processo originale CreateRemoteThread sia stata intercetta, semplicemente anche lì fai un controllo su tutte le funzioni API che importi per vedere se hanno dei JMP (e casomai controlla anche l'integrità della IAT ;)).

heheh ma non si può leggere dentro le funzioni,perchè la pagina è solo PAGE_EXECUTE , sia senza l'hooking del rootkit che dopo , perchè poi il rootkit dopo aver messo il salto,ripristina la PAGE_EXECUTE e quindi i byte delle funzioni non sono + leggibili,ma solo eseguibili,se vado a leggere mi termina il programma....prima devo usare semmai ntvirtualquery e virtualprotect,ma a quel punto il rootkit si "insospettisce" e "scappa" :) non deve accorgersi che qualcuno fa robe sospette...e quindi siamo sempre lì,devo usare una funzione trusted...o un modulo caricato a mano oppure un "jmp ritardato" come dici tu,ma è rischioso perchè magari il rootkit non ha scritto il jmp all'inizio ma dopo,e magari ha sovrascritto anche i byte successivi...mah non mi fido hihi :D


il salto nella funzione originale rimane sempre: per chiamarla la funzione di intercettazione non la deve chiamare direttamente (altrimenti è chiaro che provoca uno stack overflow dopo un breve loop), ma deve chiamare per l'appunto il trampolino che altro non è che uno stub dove sono stati salvati i primi bytes della funzione originale più un JMP finale che manda l'esecuzione al codice della funzione originale saltando però il JMP messo all'inizio (è un po' complicato :p) sisi,ho capito,ma non mi fido,vedi sopra


un'ultima cosa: se faccio il file mapping ho una mappatura DEL FILE,ma come sai l'immagine in memoria di una dll non è identica al file,perchè la dimensione di allineamento delle sezioni del file è diversa dall'allineamento di sezioni in memoria,di solito è minore nel file,ad esempio in kernel32.dll le sezioni sono allineate a 0x400 bytes,e in memoria a 0x1000 bytes...e gli RVA si riferiscono all' "indirizzo relativo all'inizio del MODULO IN MEMORIA", e quindi non è un offset all'interno del file

ecco,allora fai conto che la funzione DllEntryPoint() che tu chiami nel file mapping object si riferisca a una variabile in ".idata"...il processo crasherà perchè l'indirizzo che DllEntryPoint ha usato è un RVA, che non corrisponde all'offset della variabile DENTRO IL FILE...spero di aver esposto bene il problema....

per quello ho allocato memoria allineando correttamente le sezioni e poi riempiendole e lasciando inalterati i byte di padding (di allineamento della sezione).... mamma mia che casino :p

71104
07-10-2005, 22:57
ma questo è assurdo,scusa,allora uno alloca memoria inutilmente,perchè non può nemmeno fare un memset o memcpy su tutta la memoria perchè va a scrivere byte contigui mentre la memoria allocata non lo è,quindi andando a scrivere sulle pagine che stanno in mezzo e provocando un disastro...non ci credo,che porcheria è??? poi tu dici che dopo aver allocato la prima pagina windows si accorge che la seconda è occupata etc.. ma lol! scusa tu a virtualalloc o malloc gli dici quanta memoria vuoi allocare,e windows calcola quante pagine sono,poi quando va a vedere le pagine libere sceglie un blocco di pagine CONTIGUE per allocarti la memoria,mica alloca una pagina alla volta eccetera?? boh magari me lo leggerò sul libro "Inside Windows 2000" se è come dici tu o meno,ma è ridicolo accidenti AAAAAAAAAAAAAHHHHFERMAFERMAFERMAFERMAFERMAFERMA!!!!!!!
stiamo dicendo cose diverse O_o'
dunque: io (che chissa perché quando leggo su HWU capisco sempre male, sarà che leggo di fretta... O_o') avevo capito che tu per ogni sezione chiamavi VirtualAlloc, o cmq che la chiamavi più volte; da quest'ultimo post invece mi sembra che la chiami una volta sola per allocare TUTTO, e in tal caso non c'è NESSUNISSIMO problema, se non quello che devi stare attento a calcolare la reale dimensione dell'immagine PE una volta caricata in memoria.

va in crash perchè alcune appllicazioni native non possono fare messagebox,ho provato anch'io quando iniettavo la dll in tutti i processi caricando anche user32.dll,facevo un messagebox ma non funzionava mi sembra su smss.exe e lsass.exe e altri...ma non ricordo se addirittura mi mandava la schermata blu :rolleyes: comunque prova ad evitare quei processi e vedi se fa il crash veramente non c'entra, se alcuni processi non possono chiamare MessageBoxA (o W) sarà perché non hanno user32.dll, ma:
1) il mio bel loader invisibile carica la mia DLL in modalità invisibile, ma usa anche la normale LoadLibraryA per caricare tutti i moduli da cui dipende
2) non ho mai provato ad entrare in processi del sistema operativo (cioè in processi avviati da SYSTEM), ho provato solo con notepad, mspaint, explorer, e cazzate simili.
il problema è un problema di istanze e di protezione Copy-On-Write (come ti dicevo, il problema è (quasi) noto :))

heheh ma non si può leggere dentro le funzioni,perchè la pagina è solo PAGE_EXECUTE , la IA32 non fa distinzione tra permessi di esecuzione e permessi di lettura :)
se hai i permessi di esecuzione hai anche quelli di lettura e viceversa, vai tranquillo ;)

un'ultima cosa: se faccio il file mapping ho una mappatura DEL FILE,ma come sai l'immagine in memoria di una dll non è identica al file,perchè la dimensione di allineamento delle sezioni del file è diversa dall'allineamento di sezioni in memoria,di solito è minore nel file,ad esempio in kernel32.dll le sezioni sono allineate a 0x400 bytes,e in memoria a 0x1000 bytes...e gli RVA si riferiscono all' "indirizzo relativo all'inizio del MODULO IN MEMORIA", e quindi non è un offset all'interno del file

ecco,allora fai conto che la funzione DllEntryPoint() che tu chiami nel file mapping object si riferisca a una variabile in ".idata"...il processo crasherà perchè l'indirizzo che DllEntryPoint ha usato è un RVA, che non corrisponde all'offset della variabile DENTRO IL FILE...spero di aver esposto bene il problema....

per quello ho allocato memoria allineando correttamente le sezioni e poi riempiendole e lasciando inalterati i byte di padding (di allineamento della sezione).... mamma mia che casino :p capisco perfettamente il problema, ci sono passato anch'io: io inizialmente credevo che l'immagine in memoria fosse diversa solo per i files generati dai compilatori "sfigati" (:D) e che in realtà i compilatori "tosti" (:D) generassero immagini "comode" da mappare direttamente...
dopodiché un bel giorno di punto in bianco il virus mi inizia a crashare senza motivo e analizzandolo col PEView scopro che gli RVA sono *COMPLETAMENTE DIVERSI* dagli offset... :|
ed era il compilatore Intel :sofico:
quindi ho continuato ad usare sempre la tecnica dei file mapping, ma in maniera un po' diversa: premesso che non mi fido del flag SEC_IMAGE che si può passare alla CreateFileMapping, io procedo come segue:
1) leggo gli headers direttamente dal file e calcolo la dimensione complessiva dell'immagine virtuale una volta caricata in memoria
2) creo un file mapping object nel file di paging con la dimensione calcolata
3) ci ricopio manualmente gli headers e le sezioni
4) riloco e bindo :)
in pratica potrei anche usare il semplice VirtualAlloc, vero, ma col file mapping object risparmio memoria :)

GordonFreeman
07-10-2005, 23:29
ok dai allora il casino che avevo è il rilocamento,speriamo che facendolo possa avere delle API trusted funzionanti :)
per ora provo un normale virtualalloc e poi le ottimizzazioni vengono alla fine

ma è vero che non si può creare thread remoti nel processo SYSTEM e alcune applicazioni native?

e un'ultima domanda,considera questo dump di NtCreateFile()

NtCreateFile:
mov eax, 0x0000001A
lea edx, [esp+04]
int 0x2E
ret 0x2C

se io , per chiamare la funzione originale , semplicemente eseguissi quelle istruzioni invece di chiamare la funzione??

fantoibed all'inizio mi ha detto che non funziona,che non è la stessa cosa...ma allora non lo sarebbe nemmeno il chiamare le api trusted che carichiamo a mano tu e io... spiegami cosa intendeva

71104
07-10-2005, 23:46
ok dai allora il casino che avevo è il rilocamento,speriamo che facendolo possa avere delle API trusted funzionanti :)
per ora provo un normale virtualalloc e poi le ottimizzazioni vengono alla fine

ma è vero che non si può creare thread remoti nel processo SYSTEM e alcune applicazioni native? in generale credo che sia possibile chiamare OpenProcess su processi creati sotto altri utenti solo se si ha SE_DEBUG_NAME, quindi te lo deve permettere la policy e poi devi anche abilitare il privilegio (che è disabilitato di default). credo che solo gli amministratori di default abbiano SE_DEBUG_NAME. cmq di tutto questo nn sono sicuro...

e un'ultima domanda,considera questo dump di NtCreateFile()

NtCreateFile:
mov eax, 0x0000001A
lea edx, [esp+04]
int 0x2E
ret 0x2C

se io , per chiamare la funzione originale , semplicemente eseguissi quelle istruzioni invece di chiamare la funzione??

fantoibed all'inizio mi ha detto che non funziona,che non è la stessa cosa...ma allora non lo sarebbe nemmeno il chiamare le api trusted che carichiamo a mano tu e io... spiegami cosa intendeva mah, veramente non vedo perché non dovrebbe funzionare: nessuna di quelle istruzioni è privilegiata... guai se lo fosse... tu prova, in effetti chiamare direttamente l'interrupt anziché lo stub dell'API nativa mi sembra un'ottima idea! :)
secondo me funziona...

GordonFreeman
08-10-2005, 00:10
mah, veramente non vedo perché non dovrebbe funzionare: nessuna di quelle istruzioni è privilegiata... guai se lo fosse... tu prova, in effetti chiamare direttamente l'interrupt anziché lo stub dell'API nativa mi sembra un'ottima idea! :)
secondo me funziona...

si beh,ma non si può sperare che siano così tutte le funzioni che ci interessano,sicuramente qualcuna di quelle userà le strutture dati interne alla ntdll.dll...e in quel caso bisogna chiamare la funzione originale comunque (o in una dll copia)...però si può sempre provare a vedersi i dump , magari lo farò,e in tal caso si potrebbe fare a meno di sbattersi la testa con virtualalloc,file mapping,sezioni,rilocamento etc. e sarebbe una buona cosa...

vabbeh...tutto questo lo devo fare per la tesi di laurea...solo a me potevano dare una mazzata del genere da fare , che sfiga :(

consideriamo un'altra cosa: se il rootkit è furbo non chiama ntcreatefile,ma per usare un file esegue lo stub direttamente,quindi io anti-rootkit illuso non lo beccherò mai,non saprò mai che usa i file nascosti...da qui la necessità di scrivere un filter driver che catturi gli IORP e che lo becchi per forza,da lì non scappa...heheh ma tanto i rootkit modo utente con cui devo avere a che fare sono NtIllusion e pochi altri rootkit modo utente poco furbi,che non lo fanno ma chiamano la funzione...

cioè mi è stato assegnato di fare tutto ciò che è possibile in modo utente...ed è questo secondo me :) ...più di così bisogna scrivere un filter driver , nel caso che il rootkit sia "furbo" come ho detto sopra, o no?

71104
08-10-2005, 00:33
cioè mi è stato assegnato di fare tutto ciò che è possibile in modo utente...ed è questo secondo me :) ...più di così bisogna scrivere un filter driver , nel caso che il rootkit sia "furbo" come ho detto sopra, o no? più che catturare gli IRP dovresti sostituire l'int 2E (occhio che rischi di mandare veramente tutto a gentil donzelle :D è meglio che fai prima CLI, poi sostituisci, e poi fai STI ;)).
comunque penso di no, più di così non puoi fare: se il rootkit chiama direttamente int 2E senza passare per gli stub di ntdll.dll amen...

GordonFreeman
08-10-2005, 01:08
più che catturare gli IRP dovresti sostituire l'int 2E (occhio che rischi di mandare veramente tutto a gentil donzelle :D è meglio che fai prima CLI, poi sostituisci, e poi fai STI ;)).
comunque penso di no, più di così non puoi fare: se il rootkit chiama direttamente int 2E senza passare per gli stub di ntdll.dll amen...

e invece no! heheh senti questa:

allora,considera la funzione NtQuerySystemInformation , con il parametro SystemHandleInformation...ecco io nell'anti-rootkit posso ottenere tutti gli handle del sistema , duplicarli per il mio processo , e usare NtQueryObject() , come ho detto in questo post
http://www.hwupgrade.it/forum/showthread.php?t=1031131

allora,lì c'è il problema delle pipe che non posso interrogarle per sapere il loro nome se no il mio thread si blocca e bisogna riavviare il sistema fisicamente,col tasto per resettare :D

quindi direi che invece che usare NtQueryObject() posso usare questa funzione presente in un MSDN Example, che si chiama
GetFilenameFromHandle()
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/obtaining_a_file_name_from_a_file_handle.asp

bene ,quest'approccio implica non usare l'altro,che sarebbe inutile a questo punto heheh,cioè si può fare tutto in locale,non serve fare dll injection


ora i problemi sono 2:

1. siamo sicuri che CreateFileMapping() non si blocchi sulle pipe?? spero che ritorni solo un errore

2. per usare un CreateFileMapping() trusted,forse combino a caricare un ntdll a mano e chiamare NtCreateSection() , ma per GetMappedFileName() come farei?? questo è un po' un casino,perchè io non so quali funzioni di ntdll esso chiama e più o meno com'è implementata...allora direi di fare una furbata:

- sospendo tutti i processi forchè il mio chiamando NtSuspendProcess() che è disponibile da windows xp
- chiamo NtCreateProcess() dalla mia dll trusted e creo un "processino" che faccia questo lavoro,senza alcun pericolo di hooking perchè nessuno può aver intercettato la chiamata,e poi se ipotizziamo un thread nel processo rootkit che periodicamente chiama NtQuerySystemInformation per ottenere i processi sul sistema...beh non può vederlo perchè il rootkit viene sospeso :)
- chiamo NtResumeProcess() per far ripartire tutti i processi

hihihih ma tutto questo và fatto periodicamente,e spero che non rallenti vistosamente il sistema...ma chissenefrega,sicuramente sarà + veloce di zonealarm :fagiano:

poi c'è un pericolo che il rootkit metta questo thread "demone" in tutti i processi,anche nel mio,ma anche lì posso controllare i miei thread e vedere se ce n'è qualcuno che so di non aver creato io :cool: