逆向知識十一講,識別函數的調用約定,函數參數,函數返回值.


 

      逆向知識十一講,識別函數的調用約定,函數參數,函數返回值.

在反匯編中,我們常常的會看到各種的函數調用,或者通過逆向的手段,單獨的使用這個函數,那么此時,我們就需要認識一下怎么識別函數了.

一丶識別__cdecl 函數(俗稱C Call),函數參數,函數返回值

首先寫一個C Call的函數

1.返回值 int類型, 參數int 類型

高級代碼:

int __cdecl MyAdd(int a,int b)
{
    return a + b;
}
int main(int argc, char* argv[])
{
    MyAdd(3,4);
    return 0;
}

main函數調用我們的自己寫的

 

Debug下的匯編代碼

在Debug版本下的調用處,我們會看到這種代碼,沒有流水線優化,沒有任何優化

看到了,兩個push,緊接着一個Call,然后平棧在外面

識別參數

  有經驗的可能會說兩個push 就是兩個參數,其實不然,我們要進入函數內部,看內部的代碼用了幾個參數,要通過這個來識別.

  

  有兩處使用,所以是兩個參數. 而且直接給eax反回了,此時我們就可以在main函數位置,調用此函數的位置往上數幾個push了,這些push才是屬於自己這個函數的.

識別參數類型:

  參數類型還是很好識別了,使用參數的地方用的直接是4個字節的寄存器,那么我們可以暫定為int類型

識別調用約定

  如果在函數外面平棧,那么就是C調用約定,從識別參數來看,函數內部的 retn並沒有平棧.

 識別返回值

  從上面的識別參數我們看到,eax重新寫入了,那么返回值就是int類型

Release版本下的匯編

Release版本和Debug版本差不多一樣,優化了少許代碼,但是核心代碼不變

總結:

  1.識別參數,看其函數內部使用了幾個參數,然后在函數調用的地方往上數幾個push這些是屬於自己函數的.

  2.識別參數類型,看其參數是怎么使用.

  3.識別調用約定,看其函數內部是否平棧

  4.識別返回值類型,看其eax是否是被重寫,如果被重寫,則是返回值是int類型

2.返回值 __int64 C調用約定,參數是浮點和double的情況下

高級代碼:

  

__int64 __cdecl MyAdd(float a,double b)
{
    return a + b;
}
int main(int argc, char* argv[])
{
    MyAdd(3,4);
    return 0;
}

Debug下的匯編代碼

1.main 函數調用處

2.函數內部

3.函數內部調用的__ftol

講解:

  1.識別C約定和上面一樣,外面平棧

  2.識別參數,看其我們的的函數調用處,發現有三個push,如果不知道,則會陷入坑,直接認為是三個參數.,但是跟隨到函數內部,我們發現只有兩個參數,而第二個參數是double,所以在32位下要push 兩個四字節,其中高位是0,低位是常量(4)的浮點編碼.

  3.識別參數類型,在MyAdd內部,發現了兩處使用參數的地方,用的指令分別是 fld 和fadd指令,這些都是浮點相關的.

識別技巧.

  fld指令 將實數壓入浮點協處理器,那么此時我們看下匯編指令,(使用IDA的K命令,可以不是符號顯示,也就是下方貼出的匯編指令)

    

FLD 第一個是一個Dword 那么可以確定為是一個32位浮點

Fadd指令,使用Fadd指令的時候,發現是第二個QWORD,難么可以確定是一個double類型的浮點.功能的還原和匯編逆向前10講一樣,里面都是各種流程和指令

  4.識別返回值,在識別返回值的時候,我們發現調用了一個_ftol函數,看到這個函數可以確定返回的是一個__int64,當然我們進入函數內部看到了

下方使用eax 和edx了,而且直接反會了,那么我們知道,在32位系統下,返回一個64位數字,在匯編中的表現形式就是edx.eax的存儲方式.

Release版本下的匯編

 

熟悉總結的四句話,以不變應萬變即可,因為類型都不一樣.

二丶識別stdcall  函數參數,返回值,參數類型

stdcall比較簡單.但是和fastcall還是有區別的.因為fastcall會有寄存器傳參,所以把兩個的區別搞明白就可以了.

高級代碼:

  

__int64 __stdcall MyAdd(float a,double b,int c)
{
    return a + b + c;
}
int main(int argc, char* argv[])
{
    MyAdd(3.0f,4.0f,6);
    return 0;
}

直接一次性的把各種參數類型,以及返回值設置不一樣.觀看匯編

Debug下的匯編代碼

1.函數調用處

  

2.MyAdd函數內部

1.識別函數的參數,發現了push 4個進去,但是不要被騙了,在MyAdd內部 分別得出了使用三個參數的位置,所以得出第一個參數為  float 第二個參數類型是 double,第三個參數 是int,又因為其中有一個double參數,所以在調用外面可以看到4個push,因為double是8個字節

2.識別參數個數,stdcall最好的就是它是內部平棧,也就是retn 10h,當然也可以通過這個來判斷函數參數的個數

3.識別函數返回值類型

  函數返回值類型,在MyAdd中調用了_ftol函數,其內部則是返回__int64,返回值是 edx.eax

Release下的匯編

  

和Debug匯編一樣,有少許優化.

總結:

  1.識別參數類型: 識別參數類型可以通過函數內部使用參數的時候用的指令,比如第一個 float,使用的是fld指令,fld系列指令就是操作浮點的,而又因為它是一個dword,所以判斷是float,第三個參數是一個int,使用的是fixxx指令,fixxx指令就是操作的整數,因為它也是一個dword所以判斷是int(當然可以看函數參數使用過程中其指令使用的時候表明這個參數是什么類型的)

  2.識別參數個數, 識別參數個數在stdcall中有兩種方式,第一種,直接看內部指令使用參數的地方,第二種,看平棧的時候平了多少.比如上面的例子, retn 10h(16),也就是4個參數的大小,但因為double是8字節,所以判斷是三個參數

  3.識別返回值,識別返回值 如果是int指令,那么返回值則放在eax中,如果是__int64指令,返回值則是在 edx.eax中,如果是浮點返回值,返回值則是在浮點協處理器中.

  4.識別調用約定,函數內部平棧,如果沒有寄存器傳參則是stdcall,如果有寄存器傳參,則是fastcall

三丶識別 fastcall 函數,參數個數,參數類型,返回值

高級代碼:

  

double __fastcall MyAdd(float a,double b,int c)
{
    return a + b + c;
}

float __fastcall MySub(__int64 a,int b)
{
    return a - b;
}
int main(int argc, char* argv[])
{
    MyAdd(3.0f,4.0f,6);
    MySub(4,3);
    return 0;
}

 

Debug下的匯編代碼

  1.main函數調用的時候的匯編代碼

    

  2. MyAdd函數內部

    

  3.MySub函數內部

    

    1.識別調用約定, 我們看MyAdd內部,還是MySub內部,里面都是用了外面傳入的ecx,並且沒有保存.那么fastcall就是ecx傳參了.平棧和stdcall一樣,函數內部平棧

    2.識別函數的個數,識別函數的個數也有兩種方法,第一種,看retn的時候,然后加上寄存器, 我們看myadd內部,retn 0ch,平了3個參數,但外面更改了ecx,里面使用了ecx,那么就是4個參數,但因為其中一個參數類型是double,所以還是三個參數.

    3.識別參數類型,看指令來判斷是什么類型,fld指令是浮點,fixxx指令則是使用的int,如果看edx.eax並且符號擴展了,則是__int64

    4.上面返回值類型么有更改為doubLe和float,可以看出,在main函數下面是用浮點的出棧指令 fstp指令,從浮點協處理器出棧,浮點協處理器是64位的,所以返回double

總結:

  1.調用約定,如果是c call那么外面平棧,stdcall函數內部平棧,fastcall函數內部平棧,但是會使用外面的寄存器.

  2.識別參數個數,類型,同上

  3.識別返回值,同上.

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM