逆向-加減乘運算

後面打算記錄一下逆向識別的學習過程,也就是把每個C語言中的基礎關鍵點都反彙編,然後對比觀察。雖然說這裏的每一步都是很簡單,但是就算簡單也還是得看,畢竟每個程序都是由這些一個個簡單的點組合而成,當進行反彙編還原時,也就是將反彙編一點點拆分成這若干塊的小知識點。

好了,首先看最基礎的四則運算,也就是加減乘除,當然這篇博客並不包括除,除法的話下面博客再記錄吧(因爲優化有些複雜了,怕篇幅過長)。

首先對於最簡單的運算來說,就是不包括一些複雜混合運算,大體上分應該就以下幾種

變量?常量
常量?常量
變量?變量

首先來看一下加的情況

int main(int argc, char* argv[])
{
    int nValueOne = 5;
    int nValueTwo = 6;
    nValueOne + nValueTwo; //無效語句
    
    nValueOne = nValueTwo + 2; //變量+常量
    nValueOne = 5 + 6; //常量+常量
    nValueTwo = argc + nValueTwo; //變量+變量
    printf("%d %d",nValueOne,nValueTwo);
    return 0;
}

那麼首先來觀察一下Debug的情況

mov dword ptr [ebp-4],5
mov dword ptr [ebp-8],6
//nValueOne = nValueTwo + 2;
mov eax,dword ptr [ebp-8]
add eax,2
mov dword ptr [ebp-4],eax
//nValueOne = 5 + 6;
mov dword ptr [ebp-4],0Bh
//nValueTwo = argc + nValueTwo;
mov ecx,dword ptr [ebp+8]
add ecx,dword ptr [ebp-8]
mov dword ptr [ebp-8],ecx

mov edx,dword ptr [ebp-8]
push edx
mov eax,dword ptr [ebp-4]
push eax

可以看出來,在Debug中,常量+常量的方式編譯器直接給出了結果(編譯器的優化-常量摺疊),其餘的都按照彙編語句還原即可。對於編譯器的一些優化,我們下面先來看完Release後再談

mov eax,[esp+argc]
add eax,6
push eax
push 0Bh  //常量摺疊+常量傳播

使用了Release後,這代碼瞬間變得精簡了好多,下面那就先來說一說編譯器的一些優化

1. 常量摺疊

x = 3 + 4

此時3和4都是常量,其結果可預見,必定爲3,所以沒有必要產生指令。

2. 常量傳播

x = 3 + 5
y = x

首先x的值是可以直接確定的,也就是8,那麼所以下一行代碼y的賦值其結果也是可預見的,所以直接生成y=8即可。

3. 減少變量

x = i * 3;
y = j * 3;
if(x > y)  //此後x和y沒有再引用
    //...

由於後面的代碼中不會再引用x和y,所以可以直接去掉x和y,直接生成 if (i > j)即可

4.複寫傳播

x = a;
//...  此處未改變x值
y = x + b;

與常量傳播很像,只是目標變成了變量,由於中間未改變x的值,所以可直接用變量a代替x,即 y = a + b。

除了以上一些優化外,還有一些其他的優化,到時候就遇到了再分析吧。在Relase中,可能還會將一些減法轉化爲加法(加-x),還有當出現重複賦值的時候,上句賦值會被刪除,如上面代碼中的nValueOne這個變量,連續中間未修改的進行賦值操作,那麼上一次的賦值操作肯定是無用的語句。

好了,再來看上面Relase中的結果應該就比較清楚了,首先,由於第一處的變量+常量的語句是無效語句,所以都沒有生成反彙編代碼。而第二處使用了常量摺疊,直接計算出了0xB。而後因爲需要打印這個結果,有使用了常量傳播,使得最後直接push結果即可。

看完加的情況再來看看減的情況,其實減的情況和加很像,因爲上面的優化規則都是通用的

int main(int argc, char* argv[])
{
    int a = 10;
    int b = 5;
    scanf("%d",&b);
    int c = b - 9;
    argc = 10 - b;
    b = argc - c;
    printf("%d %d %d",b,c,argc);
    return 0;
}

下面分析對比Debug和Release情況

Debug下

//int c = b - 9;    
mov ecx,dword ptr [ebp-8]
sub ecx,9
mov dword ptr [ebp-0ch],ecx
//argc = 10 - b;
mov edx,0ah
sub edx,dword ptr [ebp-8]
mov dword ptr [ebp+8],edx
//b = argc - c;
mov eax,dword ptr [ebp+8]
sub edx,dword ptr [ebp-0ch]
mov dword ptr [ebp-8],eax


Relsese下

mov edx,[esp+0ch+var_4]
mov eax,0Ah
sub eax,edx       //argc = 10 - b;  這裏使用了兩寄存器化的情況,一般debug只會一寄存器化
lea ecx,[edx-9]   //int c = b - 9;  使用lea指令,效率更高
mov edx,eax
sub edx,ecx       //b = argc - c;

這裏的話主要看分析一下lea指令吧,lea其本意是對其取地址,但是由於效率有些高,所以有時候會用於做一些基本運算,你想想看,[0x12345678]是用於取0x12345678中的內容的,那麼對其取地址是不是就是等於0x12345678。那麼對於上面的指令其實就是 ecx = edx - 9。注意lea也不是萬能的,因爲其lea是有一定的格式的,所以當運算不符合其格式時,當然是用不了的。

OK,下面再來分析一下乘的情況

乘法運算對應的彙編指令有有符號IMUL和無符號MUL兩種。不過就單獨對於乘法來說,在vs系列上我發現就算定義的都是無符號,不過最終生成的彙編指令都是IMUL,這裏因爲IMUL可以支持多操作數(結果32位),而mul只有單操作數。

這裏對於乘來說,我們需要細分一下乘以常量的情況,因爲此時編譯器有不同的優化,分別是乘以2的冪和乘以非2的冪

int main(int argc, char* argv[])
{
    int nVarOne = argc;
    int nVarTwo = nVarOne;
    
    printf("%d",nVarOne * 29); //乘以非2的冪
    printf("%d",nVarOne * 16); //乘以2的冪
    printf("%d", 5 * 6);       //常量*常量
    printf("%d",nVarOne * nVarTwo); //變量乘變量
    printf("%d",nVarOne * nVarTwo + 9); //混合
    return 0;
}

看完源碼,其實對於Debug下來說,我們基本上可以猜出來,首先第三個printf結果常量摺疊肯定是結果,而第二個我們可以使用左移來代替乘法。

//nVarOne * 29
mov edx,dword ptr [ebp-4]
imul edx,edx,1Dh

//nVarOne * 16
mov eax,dword ptr [ebp-4]
shl eax,4

// 5 * 6
push 1Eh

//nVarOne * nVarTwo
mov ecx,dword ptr [ebp-4]
imul ecx,dword ptr [ebp-8]

//nVarOne * nVarTwo + 9
mov edx,dword ptr [ebp-4]
imul edx,dword ptr [ebp-8]
add edx,9

下面再來看一下Release下的版本

//nVarOne * 29
mov esi,[esp+4+argc]
lea eax,ds:0[esi*8] //esi = esi * 8
sub eax,esi //eax = esi * 7
lea ecx,[esi+eax*4] //ecx = esi + esi * 7 * 4 = esi * 29

//nVarOne * 16
mov edx,esi
shl edx,4

//5 * 16
push 1Eh  

//nVarOne * nVarTwo  編譯器發現都是變量 argc
imul esi,esi  

//nVarOne * nVarTwo + 9  上面的結果+9
add esi,9  

對比看來,release下優化的還是有點多的,這裏着重注意下第一個,release會把非乘以2的冪也給優化了,使用了多條lea指令,當然當這個數過大時,可能lea指令就需要多條了,這時當划不來時就還是會上imul指令的。

OK,這裏加減乘都講完了,下面就來簡單說下編譯器的窺孔優化,這是一種局部的優化方式

掃描源碼,嘗試使用優化手段,如常量摺疊,傳播等等
    掃描完代碼如果沒有優化則退出,優化結束
    如果有優化則代碼優化(發生修改)後,循環重新回到掃描源碼階段

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章