Calling Convention, queste sconosciute.. | ||
Data |
By "Lonely Wolf" |
|
30/07/2004 |
Published by Quequero | |
Se son matto pazienza.
Preferisco la mia follia alla saggezza degli altri - Van Gogh |
Complimenti Lone, quando ti
metti le fai per bene le cose!! |
Prima di correre, bisogna imparare a camminare... |
.... |
|
.... |
Difficoltà |
( )NewBies(X)Intermedio( )Avanzato( )Master |
Oggi niente cracking o reversing...oggi
architetturing, segmenti & co. :D che male non fa :P
Ma cosa c'entra sta roba col reversing???
C'entra, C'entra.... ;)
Essay |
1. Tutti gli argomenti sono estesi a 4 bytes (su win32) e messi nelle appropriate locazioni di memoria. Di solito queste locazioni sono lo stack ma potrebbero essere anche registri, dipende dalla convention scelta. 2. L'esecuzione del programma salta all'indirizzo della funzione chiamata. 3. All'interno della funzione i registri ESI, EDI, EBX, and EBP vengono salvati sullo stack (non ci avete mai fatto caso, quando ad esempio steppate con softice ed entrate in una call di qualche programma, all'inizio c'è tutta la serie di push e seghe varie per preparare lo stackframe?). La parte di codice che si preoccupa di fare questo viene chiamato "prologo e di solito viene generato dal compilatore. (a meno che non spefichi in vc++ che la funzione sia naked per esempio, ndnt) 4. Viene eseguito il codice opportuno della funzione e il valore di ritorno viene messo nel registro EAX. 5. I registri ESI, EDI, EBX, and EBP sono ripristinati. Il pezzo di codice che fa questo viene chiamato "epilogo" ecome il prologo,nella maggioranza dei casi viene generato dal compilatore. 6. Gli argomenti vengono rimossi dallo stack. Questa operazione viene chiamata "stack cleanup" e potrebbe essere performato sia all'interno della funzione chiamata (callee) che dal chiamante (caller) a seconda della calling convention usata.Uso di codice automatico per Prologo ed epilogo
nomemacro MACRO nomeproc, \ flag, \ parmbytes, \ localbytes, \ -reglist-, \ userparamsPer ulteriori dettagli Masm Programmer's Guide pag.204. Si trova anche in pdf da scaricare...anzi, se proprio non avete niente di meglio da fare è una lettura che vi consiglio ;)
Keyword | Stack cleanup | Passaggio dei Parametri |
---|---|---|
__cdecl | Caller | Pusha i parametri sullo stack in ordine inverso (da dx a sx) |
__stdcall | Callee | Pusha i parametri sullo stack in ordine inverso (da dx a sx) |
__fastcall | Callee | Parametri passati nei registri, poi pushati sullo stack |
thiscall | Callee | Parametri pushati sullo stack; il puntatore this salvato in ECX |
... int __cdecl sumExample (int a, int b) { return a+b; } ... int c = sumExample (2, 3);
; pusha gli argomenti da dx a sx push 3 push 2 ; <- chiama la funzione definita sopra call _sumExample ; <- il nome della funzione viene decorata con il prefisso underscore _ ; <- cleanup the stack aggiungendo la grandezza degli argomenti al registro ESP add esp,8 ; <- copia il valore di ritorno da EAX in una variabile locale (int c) mov dword ptr [c],eax Questa invece è la funziona chiamata: ; <- prologo push ebp mov ebp,esp sub esp,0C0h ; <- Qui completa la preparazione dello stackframe (chi? il compilatore!) AndreaGeddon> se lo fa in vc in debug mode contemplando i param passati e spazio per var locali push ebx push esi push edi lea edi,[ebp-0C0h] mov ecx,30h mov eax,0CCCCCCCCh ; <- Ancora 'robaccia' messa dal compilatore per assicurarsi che la funzione ritorni correttamente rep stos dword ptr [edi] ; <- return a + b; mov eax,dword ptr [a] add eax,dword ptr [b] ; <- epilogo pop edi pop esi pop ebx mov esp,ebp pop ebp ret(la cdecl è usata al 100% se il numero di argomenti passato è imprecisato, questo è importante, ndnt)
; <- al solito pusha gli argumenti da dx a sx push 3 push 2 call _sumExample@8 <- Per la cronaca, lo spazio richiesto è 8 perchè i parametri pushati sullo stack sono 2 DWORD, 4byte+4byte mov dword ptr [c],eax
; <- prologo (identico alla __cdecl) ; <- return a + b; mov eax,dword ptr [a] add eax,dword ptr [b] ; <-epilogo (lo stesso codice di __cdecl) ... ; <- cleanup the stack e ritorno ret 8
1. I primi 2 argomenti della funzione che richiedono 32 bit o meno vengono piazzati nei registri ECX ed EDX. I restanti sono pushati sullo stack da dx a sx. 2. Gli argomenti vengono poppati dallo stack dalla funzione chiamata 3. Il nome della funzione viene decorato anteponendo @ e appendendo un'altra @ e il numero di bytes (decimale) di spazio richiesto per gli argomenti.
; <- piazza gli argomenti in EDX e ECX mov edx,3 mov ecx,2 call @fastcallSum@8 mov dword ptr [c],eax
; <- prologo push ebp mov ebp,esp sub esp,0D8h push ebx push esi push edi push ecx lea edi,[ebp-0D8h] mov ecx,36h mov eax,0CCCCCCCCh rep stos dword ptr [edi] pop ecx mov dword ptr [ebp-14h],edx mov dword ptr [ebp-8],ecx ; <- return a + b; mov eax,dword ptr [a] add eax,dword ptr [b] ;<- epilogo pop edi pop esi pop ebx mov esp,ebp pop ebp ret
1. Gli argomenti sono passati da dx a sx e messi nello stack. this è piazzato in ECX. 2. Lo Stack cleanup viene fatto dalla funzione chiamata.
struct CSum { int sum ( int a, int b) {return a+b;} };
push 3 push 2 lea ecx,[sumObj] call ?sum@CSum@@QAEHHH@Z ; CSum::sum mov dword ptr [s4],eax
push ebp mov ebp,esp sub esp,0CCh push ebx push esi push edi push ecx lea edi,[ebp-0CCh] mov ecx,33h mov eax,0CCCCCCCCh rep stos dword ptr [edi] pop ecx mov dword ptr [ebp-8],ecx mov eax,dword ptr [a] add eax,dword ptr [b] pop edi pop esi pop ebx mov esp,ebp pop ebp ret 8Uhhh che carini questi disegnini della M$, perdonatemi non voglio essere prolisso però credo non faccia male aggiungere anche questo esempio
void calltype MyFunc( char c, short s, int i, double f ); . . void MyFunc( char c, short s, int i, double f ) { . . } . . MyFunc ('x', 12, 8192, 2.7183);
-stack- -locazione- 2.7183 ESP+0x14 ESP+0x10 <- Non è che è vuoto, ma essendo un double occupa 2 dword per la sua rappresentazione interna 8192 ESP+0x0C 12 ESP+0x08 x ESP+0x04 <- ma ESP+4 non dovrebbe essere Address of Caller? M$ dogma ret.addr. ESP registri non usato ECX non usato EDX__stdcall and thiscall, il nome della funzione decorato diventa _MyFunc@20
__fastcall il nome della funzione decorata diventa @MyFunc@20
-stack- -locazione- 2.7183 ESP+0x0C ESP+0x08 8192 ESP+0x04 ret.addr. ESP registri x ECX 12 EDXCarino questo, guardate quest'altro pezzo di codice che ho trovato (in un angolo remoto del mio hd):
This C code with its interspersed assembly code demonstrates the various calling conventions. // The strings passed to each function. static char * g_szStdCall = "__stdcall" ; static char * g_szCdeclCall = "__cdecl" ; static char * g_szFastCall = "__fastcall" ; static char * g_szNakedCall = "__naked" ; // The extern "C" turns off all C++ name decoration. extern "C" { // The __cdecl function. void CDeclFunction ( char * szString , unsigned long ulLong , char chChar ) ; // The __stdcall function. void __stdcall StdCallFunction ( char * szString , unsigned long ulLong , char chChar ) ; // The __fastcall function. void __fastcall FastCallFunction ( char * szString , unsigned long ulLong , char chChar ) ; // The naked function. The declspec goes on the definition, not the // declaration. int NakedCallFunction ( char * szString , unsigned long ulLong , char chChar ) ; } void main ( void ) { 00401000 55 push ebp 00401001 8B EC mov ebp,esp 00401003 53 push ebx 00401004 56 push esi 00401005 57 push edi // Call each function to generate the code. CDeclFunction ( g_szCdeclCall , 1 , 'a' ) ; 00401008 6A 61 push 61h 0040100A 6A 01 push 1 0040100C A1 14 30 40 00 mov eax,[00403014] 00401011 50 push eax 00401012 E8 45 00 00 00 call 0040105C 00401017 83 C4 0C add esp,0Ch StdCallFunction ( g_szStdCall , 2 , 'b' ) ; 0040101C 6A 62 push 62h 0040101E 6A 02 push 2 00401020 8B 0D 10 30 40 00 mov ecx,dword ptr ds:[00403010h] 00401026 51 push ecx 00401027 E8 3D 00 00 00 call 00401069 FastCallFunction ( g_szFastCall , 3 , 'c' ) ; 0040102E 6A 63 push 63h 00401030 BA 03 00 00 00 mov edx,3 00401035 8B 0D 18 30 40 00 mov ecx,dword ptr ds:[00403018h] 0040103B E8 38 00 00 00 call 00401078 NakedCallFunction ( g_szNakedCall , 4 , 'd' ) ; 00401042 6A 64 push 64h 00401044 6A 04 push 4 00401046 8B 15 1C 30 40 00 mov edx,dword ptr ds:[0040301Ch] 0040104C 52 push edx 0040104D E8 40 00 00 00 call 00401092 00401052 83 C4 0C add esp,0Ch } 00401057 5F pop edi 00401058 5E pop esi 00401059 5B pop ebx 0040105A 5D pop ebp 0040105B C3 ret void CDeclFunction ( char * szString , unsigned long ulLong , char chChar ) { 0040105C 55 push ebp 0040105D 8B EC mov ebp,esp 0040105F 53 push ebx 00401060 56 push esi 00401061 57 push edi __asm NOP __asm NOP // NOPs stand for the function body here 00401062 90 nop 00401063 90 nop } 00401064 5F pop edi 00401065 5E pop esi 00401066 5B pop ebx 00401067 5D pop ebp 00401068 C3 ret void __stdcall StdCallFunction ( char * szString , unsigned long ulLong , char chChar ) { 00401069 55 push ebp 0040106A 8B EC mov ebp,esp 0040106C 53 push ebx 0040106D 56 push esi 0040106E 57 push edi __asm NOP __asm NOP 0040106F 90 nop 00401070 90 nop } 00401071 5F pop edi 00401072 5E pop esi 00401073 5B pop ebx 00401074 5D pop ebp 00401075 C2 0C 00 ret 0Ch void __fastcall FastCallFunction ( char * szString , unsigned long ulLong , char chChar ) { 00401078 55 push ebp 00401079 8B EC mov ebp,esp 0040107B 83 EC 08 sub esp,8 0040107E 53 push ebx 0040107F 56 push esi 00401080 57 push edi 00401081 89 55 F8 mov dword ptr [ebp-8],edx 00401084 89 4D FC mov dword ptr [ebp-4],ecx __asm NOP __asm NOP 00401087 90 nop 00401088 90 nop } 00401089 5F pop edi 0040108A 5E pop esi 0040108B 5B pop ebx 0040108C 8B E5 mov esp,ebp 0040108E 5D pop ebp 0040108F C2 04 00 ret 4 78: __declspec(naked) int NakedCallFunction ( char * szString , unsigned long ulLong , char chChar ) { __asm NOP __asm NOP 00401092 90 nop 00401093 90 nop // Naked functions must EXPLICITLY do a return. __asm RET 00401094 C3 ret__declspec ... facciamoci un'idea di cosa si tratti, visto che è stata rammentata (..INPUT, INPUT, INPUT...:D). Non è che sia una calling convention, cmq MSDN mi dice che "L'edizione a 32 bit di VC++ usa uses __declspec(dllimport) and __declspec(dllexport) per sostituire __export nella precedente versione a 16 bit. (...se devi esportare una funzione dice che è da esportare e la mette nella Export table, se la importa la mette nella IT, è fondamentale eccome, ndnt). Quindi si usa anche per importare variabili usati in una dll o funzioni (ma anche per l'export). Il formato PE è progettato per minimizzare il numero di pagine che devono essere "toccate" per risolvere imports. Per fare questo sappiamo che tutti gli import addresses per qualsiasi programma stanno nella IAT (Import Address Table). Una nota su Thunk. Da un thread apparso su RCE intitolato guardacaso: What is a thunk?
...Wow!!! Now all the loader has to do, it simply put the value in memory from your computer or my computer in one single place,instead of the 60 places. So, the exe on my computer would look like: ---Begin Exe---- 0001:00000001 XXX 0001:00000003 XXX 0001:00000005 XXX 0001:00000007 jmp 0001:00000090 0001:00000009 XXX 0001:00000011 XXX 0001:00000013 jmp 0001:00000090 0001:00000015 XXX 0001:00000017 XXX 0001:00000019 XXX 0001:00000021 jmp 0001:00000090 : : 0001:00000090 Call 700999F (in memory, which is MessageBoxA) ----End Exe----- Simple to understand, is it not? Here, all jumps are called, well JUMPS. But the call at 0001:00000090? Its called as THUNKS. Get it? Simple is it not?
(Per ulteriori approfondimenti vi rimando al...MESSAGGIO PROMOZIONALE...Tutorial di Ntoskrnl sui PE
Note finali |
Saluto Ntoskrnl (ovviamente :P grazie davvero per le tue note e per aver corretto qualche mio strafalcione, attualmente 02/08/2004 in Norvegia, sulle isole Lofoten, mi ha appena mandato sms), albe, Quequero, andreageddon, evilcry (...), Ironspark, i satelliti di marte :D , ZaiRoN, MrCode, la nostra Giulia (giù io aspetto sempre il tuo Tasm vs Masm :P), e tutti quelli che hanno e hanno avuto la sfortuna di conoscermi :P
Disclaimer |
Sono contento di averlo fatto, ho imparato molto. Come sono andato?