Calling Convention, queste sconosciute..

Data

By "Lonely Wolf"

 

30/07/2004

UIC's Home Page

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!!
- Ne hai mai dubitato Que? Questo è solo l'inizio...

Prima di correre, bisogna imparare a camminare...

....

E-mail: [email protected]
Su azzurra, #crack-it #cryptorev #pmode #olografix #gnu ..

....

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.... ;)


Calling Convention, queste sconosciute..

Written by "Lonely Wolf"

Essay

Calling e naming Convention...queste sconosciute

Le calling convention, come dice il nome, definiscono come un linguaggio implementi una chiamata a una funzione e come la funzione debba ritornare al chiamante. Le naming convention specificano come e se il compilatore o l'assemblatore alterano il nome di un identificatore o il modo in cui questo debba essere salvato prima di piazzarlo in file object.
Quando viene chiamata una funzione, gli argomenti gli sono tipicamente passati ed eventualmente viene ritornato un valore. Quindi una calling convention stabilisce COME gli argomenti sono passati e i valori ritornati alle funzioni, specifica anche come i nomi delle funzioni vengono "decorati".

Inoltre osservando del codice (con un disassemblatore o un debugger) e tenendo d'occhio alcuni dettagli possiamo riconoscere quale convenzione è stata usata e riconoscere i codici di prologo relativi ad esempio.
Diventa molto importante quando ad esempio vogliamo linkare dei moduli C/C++ con del codice asm (pensateci un attimo, se un modulo dovesse chiamare una funzione di un altro modulo linkato in questo modo, e fossero stati prodotti da compilatori diversi con modi diversi di passarsi parametri, è evidente che ci sarebbero dei problemucci) (sì, guarda che i problemi nascono anche per molto meno metti che una dll scritta con vc++ abbia funzioni cdecl e io le chiamo dal mio prog vc++ come stdcall, non c'è bisogno di andare a cercare l'asm, ndnt). Indipentemente dalla calling convention scelta, succedono le seguenti cose:

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

L'assemblatore genera automaticamente il codice di prologo quando incontra la prima istruzione o label dopo la direttiva PROC (che deve essere dichiarata prima di una INVOKE) e genera il codice di epilogo quando trova ret o iret. Usando questo codice generato si risparmia tempo e diminuisce il numero di righe di codice che devono essere scritte. La generazione del codice di prologo o di epilogo dipende:

- Variabili locali definite
- Argomenti passati alla procedura
- Processore corrente selezionato (solo epilogo)
- La calling convention corrente
- Le opzioni passate in prologuearg della direttiva PROC - I registri che devono essere salvati

Il codice standard per il prologo e l'epilogo, come vedremo, gestisce i parametri e lo spazio per le variabili locali. Se una procedura non dovesse avere alcun parametro e variabile locale verrebbero omessi, a meno che FORCEFRAME non venga specificato in prologuearg. Il codice di prologo consiste in 3 passi:

- Puntare BP alla cima dello stack (preparazione dello stackframe)
- Settare lo spazio sullo stack per le variabili locali
- Salvare i registri che le procedure/funzioni devono preservare

Il codice di epilogo cancella questi 3 passi in ordine inverso.

Codice di prologo ed epilogo user-defined

Ganzo, ragazzi sentite che roba, lo sapevate? (non su rieducational channel :D) bada te quante cose sto scoprendo a questo giro...
Se per qualche motivo (ho quasi paura a indagare :P) volessimo usare un set di istruzioni diverso per il codice di prologo e di epilogo nelle procedure, è possibile scrivere macros che vengono eseguite al posto del prologo/epilogo standard. Queste macros user-defined rispondono correttamente se è stato specificato FORCEFRAME.
Per scrivere un proprio codice di prologo/epilogo la direttiva OPTION deve apparire nel programma. Essa disabilita automaticamente la generazione del codice prologo/epilogo. Quando si specifica
OPTION PROLOGUE:nomemacro
OPTION EPILOGUE:nomemacro

l'assemblatore chiama la macro specificata e si aspetta che sia nella forma:


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 ;)

Dicevamo, ah si, Le principali calling convention sono: __cdecl, __stdcall, __fastcall, thiscall (Ci sarebbero anche altre convenzioni più vecchie come PASCAL (mammon_ ci spiega che veniva usata in Win16 e pusha i parametri da sx verso dx e lo stack veniva ripulito dal callee, al contrario della convenzione C), BASIC, FORTRAN (che per l'assemblatore sono sinonimi ma mette i nomi in uppercase) e SYSCALL - che lascia i nomi inalterati)

Leggi articolo originale
Vediamo una tabella riassuntiva presa dal sito M$ (si riferisce a Visual C++ ma per i nostri scopi dovrebbe essere più che sufficiente):

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
 

__cdecl

Oltre a quanto riportato in tabella, __cdecl è la convenzione default per i programmi scritti in C/C++. Se un nostro programma usa un'altra convenzione ma per qualche motivo una nostra funzione deve usare per forza __cdecl possiamo usare la seguente sintassi (questo discorso vale anche per le altre..mah veramente dipende soprattutto dalle impostazioni del compilatore, ndnt) :

int __cdecl sumExample (int a, int b);


...
int __cdecl sumExample (int a, int b)
{
  return a+b;
}
...

int c = sumExample (2, 3);


Vediamo una chiamata con questa convenzione:

; 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)

__stdcall

Questa è la convenzione usata di solito per chiamare funzioni Win32 API. Infatti WINAPI non è altro che

#define WINAPI __stdcall

Prendiamo sempre in considerazione la funzione di esempio di prima ma con lo specificatore __stdcall ovviamente. Con questa convenzione i nomi delle funzioni sono decorate mettendo davanti il solito underscore e appendendo '@' e il numero di byte che rappresentano lo spazio richiesto sullo stack (disassemblando un programma o cmq dandoci un'occhiata dentro con qualche tool non avete mai visto niente del genere? Io si, con PE Explorer ad esempio e infatti mi chiedevo cosa fosse quella roba...mumble mumble).


; <- 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


E questo è invece il codice della funzione relativa:


; <- 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


Poichè lo stack è ripulito dalla funzione chiamata, __stdcall crea eseguibili più piccoli della __cdecl nella quale il codice di cleanup deve essere generato per ogni chiamata di funzione. D'altro canto, funzioni con numero variabili di argomenti devono usare __cdecl visto che solo il caller conosce il numero di argomenti passati a ogni call perciò solo il caller può fare il cleanup, ripeto, solo lui sa di QUANTO ripulirlo, per dirlo alla io rozzo :P

__fastcall

Questa convenzione implica che gli argomenti devono essere piazzati nei registri invece che sullo stack laddove possibile. Poichè si lavora sui registri è implicito che si vada più veloce (fast) :)
Prendiamo la solita funzione con lo specificatore __fastcall.

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.


Ah, questa non la sapevo, leggo che M$ si riserva il diritto di cambiare i registri per il passaggio degli argomenti (NO COMMENT... boh ;P)

; <- piazza gli argomenti in EDX e ECX
  mov         edx,3
  mov         ecx,2

  call        @fastcallSum@8

  mov         dword ptr [c],eax


E ora il codice della funzione:

; <-  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




thiscall

Thiscall è la calling convention default per le funzioni membro di classe C++ (eccetto per quelle con numero variabile di argomenti (per le quali si ritorna alla cdecl, ndnt)).

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.

Vediamo un esempio:



struct CSum
{
	int sum ( int a, int b) {return a+b;}
};


Il codice asm relativo è:

  push        3
  push        2
  lea         ecx,[sumObj] 

  call        ?sum@CSum@@QAEHHH@Z	; CSum::sum
  mov         dword ptr [s4],eax


Mentre il codice della call sarà:


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         8
Uhhh 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);


Vi posterei il link della M$, ma quelli ogni tanto potano le pagine...boh cmq http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_core_results_of_calling_example.asp Ma siii, dai di ascii art ;)

Allora, per questo esempio con la convenzione __cdecl il nome della funzione diventa _MyFunc e


-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

-stack-     -locazione-
2.7183      ESP+0x14
            ESP+0x10
8192        ESP+0x0C
12          ESP+0x08
x           ESP+0x04
ret.addr.   ESP

registri

This(solo thiscall) ECX
non usato           EDX

__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?