|
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-, \
userparams
Per 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 EDX
Carino 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?