本文為看雪論壇精華文章
看雪論壇作者ID:TkBinary
反彙編代碼還原之優化方式
反彙編代碼還原之除數為2的冪
反彙編代碼還原之加減乘
* 點擊文字即可跳轉文章
分數乘法
**分數*分數 是分子 * 分子 分母 * 分母**如下:
口訣: 乘分數,沒難度,上乘上,下乘下,再約分。
整數相乘
如果一個整數 * 一個分數,那麼這個分數就可以看做是分子,而分母就是1。
例子:
至於約分,可以尋找最小公因數,跟公倍數相反。
公倍數是找出相乘,公因數就是找出可以分子/分母的數,都去除同一個數。
2.1 高級代碼與反彙編int main(int argc, char* argv[]){ /* 除法 */ int NumberOne = 0; int NumberTwo = 0;
scanf("%d",&NumberOne); scanf("%d",&NumberTwo);
int Count1 = NumberOne / 3;
int Count2 = NumberTwo / 5;
int Count3 = NumberTwo / 6;
int Count4 = NumberTwo / 9;
printf("%d%d%d%d%d",Count4,Count3,Count2,Count1); system("pause");
return 0;}彙編對應代碼:
.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp).text:00401000 _main proc near ; CODE XREF: start+AF↓p.text:00401000.text:00401000 var_8 = dword ptr -8.text:00401000 var_4 = dword ptr -4.text:00401000 argc = dword ptr 4.text:00401000 argv = dword ptr 8.text:00401000 envp = dword ptr 0Ch.text:00401000.text:00401000 sub esp, 8.text:00401003 xor eax, eax.text:00401005 mov [esp+8+var_8], eax.text:00401009 mov [esp+8+var_4], eax.text:0040100D lea eax, [esp+8+var_8].text:00401011 push eax.text:00401012 push offset aD ; "%d".text:00401017 call _scanf.text:0040101C lea ecx, [esp+10h+var_4].text:00401020 push ecx.text:00401021 push offset aD ; "%d".text:00401026 call _scanf
//第一段.text:0040102B mov ecx, [esp+18h+var_8].text:0040102F mov eax, 55555556h.text:00401034 imul ecx.text:0040103A mov eax, edx.text:0040103C shr eax, 1Fh.text:0040103F add edx, eax
//第二段.text:00401036 mov ecx, [esp+18h+var_4].text:00401041 mov eax, 66666667h.text:00401047 imul ecx.text:00401049 sar edx, 1.text:0040104B mov eax, edx.text:0040104D shr eax, 1Fh.text:00401050 add edx, eax.text:00401057 push edx //這裡原先是流水線優化給提到上邊來了//第三段.text:00401052 mov eax, 2AAAAAABh.text:00401058 imul ecx.text:0040105A mov eax, edx.text:0040105C shr eax, 1Fh.text:0040105F add edx, eax.text:00401066 push edx //同上流水線優化
//第四段.text:00401061 mov eax, 38E38E39h.text:00401067 imul ecx.text:00401069 sar edx, 1.text:0040106B mov ecx, edx.text:0040106D shr ecx, 1Fh.text:00401070 add edx, ecx.text:00401072 push edx
.text:00401073 push offset aDDDDD ; "%d%d%d%d%d".text:00401078 call _printf.text:0040107D push offset aPause ; "pause".text:00401082 call _system.text:00401087 xor eax, eax.text:00401089 add esp, 30h.text:0040108C retn.text:0040108C _main endp2.2 除法非2的冪還原乍一看上面彙編代碼,除法怎麼變為了這樣,為什麼又有乘法又有除法,還有調整等,那麼這裡就著重講解一下。
如果想要直接還原,那麼我們把代碼定式提取出來,直接進行還原。
代碼定式:
mov ecx, 被除數mov eax, M值imul ecxsar edx, nmov eax, edxshr eax, 1Fhadd edx, eaxpush edx根據彙編我們只需要看三個點即可,並且得出三個點的公式得原理:
其中M是編譯器計算出來了, ecx是被除數,這裡sar n值,直接操作的是edx。這裡其實是除法轉變為了乘法,而如果除法轉變為乘法,那麼在32位年代下,兩個數相乘32的值是不會放下的。所以我們這裡其實使用的是 edx,eax 來代表乘法的結果,然後我們直接操作了乘積的高位,這裡右移1,等價於是除以2^n+1。
那麼我們還原的時候直接記住死公式就可以,2^32+1/M 。
直接統計n的取值,然後用 2的32次方 + n即可。因為乘積的低位時代表2^32次方,這裡直接是對乘積高位做操作,所以我們需要加上低位的32次方的值。
例子還原:
.text:0040102B mov ecx, [esp+18h+var_8].text:0040102F mov eax, 55555556h.text:00401034 imul ecx.text:0040103A mov eax, edx //這裡直接使用的是edx.而沒有對edx做修改.所以我們的n值就是0.text:0040103C shr eax, 1Fh.text:0040103F add edx, eax我們套用公式:
2.99直接向上取整 = 3,所以這裡我們就尋得被除數為3。
2.3 除數為非2的冪的優化與原理.text:00401061 mov eax, 38E38E39h.text:00401067 imul ecx.text:00401069 sar edx, 1.text:0040106B mov ecx, edx
.text:0040106D shr ecx, 1Fh.text:00401070 add edx, ecx.text:00401072 push edx這裡我們彙編分為兩部分。上邊是可以直接套用公式還原,而下方其實是獲取符號位,調整一下符號位。shr 邏輯右移最高位以0填充,右移31(1F)位 取得符號位,然後這裡有一個加法。其實這個加法也是套路,跟我們之前講解的無分支優化差不多。如果結果是正數那麼 add edx,eax 就是加0,等於什麼都不幹;如果是結果是負數那麼我們就需要除法的商做調整,做加1調整。如果想要了解為什麼非2的冪代碼會變成上面的代碼,那麼我們就要理解下原理。其中 2n/b這個是可以在編譯期中計算出來的,VC++6.0 或者VS2019 在編譯 期間n的取值是大於32的,所以是可以算出來的。所以我們的公式可以變為如下,在這裡我們把編譯器可以計算出來的值記做C。那麼可以得出以下公式:最終簡化為就是(a * c) >> n,而反過頭來看我們的彙編代碼:.text:00401061 mov eax, 38E38E39h.text:00401067 imul ecx.text:00401069 sar edx, 1ecx eax / 2^33 這條公式就正好對應著我們除法轉變為乘法的原理,和我們上面的公式一樣,(a c) >> n。所以我們解方程即可,2^n / b = M值,那麼 2^n / M = b ,b就是我們的要求的除數,所以得出我們的除數。
2.4 總結除法還原公式這裡的C其實就是2^n/b,在彙編中我們也設為M,所以也可以寫為如下:對於上面我們記住代碼定式也可以進行還原,當然熟悉除法轉變為乘法的原理更好。高級代碼看其特徵我們發現其實是一樣的,都可以使用除法轉乘法的公式來進行還原。除法還原公式其實就是解方程了,解開就可以得到被除數。int main(int argc, char* argv[]){ /* 除法 */ unsigned int NumberOne = 0; unsigned int NumberTwo = 0;
scanf("%u",&NumberOne); scanf("%u",&NumberTwo);
unsigned int Count1 = NumberOne / 3;
unsigned int Count2 = NumberTwo / 5;
unsigned int Count3 = NumberTwo / 6;
unsigned int Count4 = NumberTwo / 9;
printf("%d%d%d%d%d",Count4,Count3,Count2,Count1); system("pause");
return 0;}.text:00401005 mov [esp+8+var_8], eax.text:00401009 mov [esp+8+var_4], eax
.text:0040102B mov eax, 2863311531.text:00401030 mov ecx, [esp+18h+var_4].text:00401034 mul [esp+18h+var_8].text:00401038 shr edx, 1
.text:0040103A mov eax, 3435973837.text:0040103F push edx.text:00401040 mul ecx.text:00401042 shr edx, 2
.text:00401045 mov eax, 0AAAAAAABh.text:0040104A push edx.text:0040104B mul ecx.text:0040104D shr edx, 2
.text:00401050 mov eax, 38E38E39h.text:00401055 push edx.text:00401056 mul ecx.text:00401058 shr edx, 1.text:0040105A push edx
3.1 無符號非2的冪除數為正代碼反彙編還原看如上代碼,根據除法轉為乘法公式 (a * b) >> n,我們可以求出:2^n / b = M 2^n/M = b(除數常量).text:0040102B mov eax, 2863311531.text:00401030 mov ecx, [esp+18h+var_4].text:00401034 mul [esp+18h+var_8].text:00401038 shr edx, 1取N 值 = 1,取M值 = 2863311531(注意這裡IDA我按了下H轉為10進位來查看.這樣方便我們用科學計算器來進行運算)。被除數 = 2^33除數 = M求除數公式 = 2^n/M = b代入公式2^33 / 2863311531 = 2.999999999 向上取整 = 3 所以得出這裡的除數為3. ecx = var4所以這裡反彙編為高級代碼為 var_4 / 3
3.2 無符號非2的冪除數為負數反彙編代碼還原int main(int argc, char* argv[]){ /* 除法 */ unsigned int NumberOne = 0; unsigned int NumberTwo = 0;
scanf("%u",&NumberOne); scanf("%u",&NumberTwo);
unsigned int Count1 = NumberOne / -3;
unsigned int Count2 = NumberTwo / -5;
unsigned int Count3 = NumberTwo / -6;
unsigned int Count4 = NumberTwo / -9;
printf("%d%d%d%d%d",Count4,Count3,Count2,Count1); system("pause");
return 0;}.text:00401005 mov [esp+8+var_8], eax.text:00401009 mov [esp+8+var_4], eax
/-3.text:0040102B mov eax, 40000001h.text:00401030 mov ecx, [esp+18h+var_4].text:00401034 mul [esp+18h+var_8].text:00401038 shr edx, 1Eh.text:00401040 push edx
/-5.text:0040103B mov eax, 80000003h.text:00401041 mul ecx.text:00401043 shr edx, 1Fh.text:0040104B push edx
/-6.text:00401046 mov eax, 7.text:0040104C mul ecx.text:0040104E mov eax, ecx.text:00401050 sub eax, edx.text:00401052 shr eax, 1.text:00401054 add eax, edx.text:00401056 shr eax, 1Fh.text:00401059 push eax/-9.text:0040105A mov eax, 80000005h.text:0040105F mul ecx.text:00401061 shr edx, 1Fh.text:00401064 push edx根據上面反彙編代碼,我們看到三個地方有我們的疑似 (a * c) >> n的代碼,也就是除法轉變為乘法的公式。設 M = 40000001h =10進位的 1073741825以計算器的科學計算計算出的數值是4294967292(10進位)。那麼我們將這個數複製到以程式設計師計算的計算器中(複製到Dec 10進位輸入)可以看到是一個負數。注意輸入的時候以QWORD輸入,然後再點擊一下變成DWORD則可以看到表達為-4。此時直接對其NOT取反則可以得到原始的被除數,但是這個被除數常量是負數。你也可以讓此值做+1調整變為4294967293,那麼就得出了數直接就是-3,可以很明確的知道我們的被除數是-3。
3.3 特殊彙編看到上面有一段代碼,我們特別不理解為什麼/-6彙編變了。2^64 / 4,294,967,302 = 0x100000006,最高位為符號位,代表這是個負數也就是-6。如果根據定式還原得出的結果為0xFFFFFFF9,然後對其取反得到6。mov regA,[ebp - xxx]mov reg,Mmul regAsub regA,edxshr regA,nadd regA,edxshr regA,(n-1)這裡取出兩個n值。1+31= 32,所以n值為32。
代入公式得:
2^64 / 4,294,967,303 = 0XFFFFFFF9
關於特殊彙編在下一篇應該會詳細講解。
4.1 為什麼學習除法優化
還是那句話為什麼學習除法優化,好就以我們上面的例子為例,那麼使用IDA F5查看。
請問看到這種代碼你怕了嗎?直接反彙編可以得出這一段代碼我是幹啥,用IDA反彙編就會得出右移等等,在我們看來就是硬彙編。
也就是硬著頭皮還原的,所以說這就是我們學習的意義所在。當然F5還是很強大的,但是我們學會了能更好的幫助我們進行反彙編,更好的通過反彙編從而還原為高級代碼。
其實還是老生常談,拿高版本與低版本做個對比,看看優化方式是否發生了改變。
5.1 vs2019 x86 有符號非2的冪優化高級代碼:
int main(int argc, char* argv[]){ /* 除法 */ int NumberOne = 0; int NumberTwo = 0; scanf("%d",&NumberOne); scanf("%d",&NumberTwo); int Count1 = NumberOne / 3; int Count2 = NumberTwo / 5; int Count3 = NumberTwo / 6; int Count4 = NumberTwo / 9; printf("%d%d%d%d%d",Count4,Count3,Count2,Count1); system("pause"); return 0;}彙編:
.text:00401080 push ebp.text:00401081 mov ebp, esp.text:00401083 sub esp, 8.text:00401086 lea eax, [ebp+var_4].text:00401089 mov [ebp+var_4], 0.text:00401090 push eax.text:00401091 push offset unk_41ECDC.text:00401096 mov [ebp+var_8], 0.text:0040109D call sub_401050.text:004010A2 lea eax, [ebp+var_8].text:004010A5 push eax.text:004010A6 push offset unk_41ECDC.text:004010AB call sub_401050 .text:004010B0 mov ecx, [ebp+var_8].text:004010B3 mov eax, 55555556h.text:004010B8 imul [ebp+var_4].text:004010BB mov eax, edx.text:004010BD shr eax, 1Fh.text:004010C0 add eax, edx.text:004010C2 push eax .text:004010C3 mov eax, 66666667h.text:004010C8 imul ecx.text:004010CA sar edx, 1.text:004010CC mov eax, edx.text:004010CE shr eax, 1Fh.text:004010D1 add eax, edx.text:004010D3 push eax .text:004010D4 mov eax, 2AAAAAABh.text:004010D9 imul ecx.text:004010DB mov eax, edx.text:004010DD shr eax, 1Fh.text:004010E0 add eax, edx.text:004010E2 push eax .text:004010E3 mov eax, 38E38E39h.text:004010E8 imul ecx.text:004010EA sar edx, 1.text:004010EC mov eax, edx.text:004010EE shr eax, 1Fh.text:004010F1 add eax, edx.text:004010F3 push eax.text:004010F4 push offset aDDDDD ; "%d%d%d%d%d".text:004010F9 call sub_401020.text:004010FE push offset aPause ; "pause".text:00401103 call sub_4048E7.text:00401108 add esp, 28h.text:0040110B xor eax, eax.text:0040110D mov esp, ebp.text:0040110F pop ebp這裡流水線優化等也不去掉了,可以發現跟VC6.0無任何區別。
5.2 vs2019 x64有符號非2的冪優化高級代碼:
int main(int argc, char* argv[]){ /* 除法 */ __int64 NumberOne = 0; __int64 NumberTwo = 0; scanf("%I64d", &NumberOne); scanf("%I64d", &NumberTwo); __int64 Count1 = NumberOne / 3; __int64 Count2 = NumberTwo / 5; __int64 Count3 = NumberTwo / 6; __int64 Count4 = NumberTwo / 9; printf("%I64d%I64d%lld%lld", Count4, Count3, Count2, Count1); system("pause"); return 0;}反彙編:
text:00000001400010D0 sub rsp, 38h.text:00000001400010D4 xor eax, eax.text:00000001400010D6 lea rdx, [rsp+38h+arg_10].text:00000001400010DB lea rcx, aI64d ; "%I64d".text:00000001400010E2 mov [rsp+38h+arg_10], rax.text:00000001400010E7 mov [rsp+38h+arg_18], rax.text:00000001400010EC call sub_140001080.text:00000001400010F1 lea rdx, [rsp+38h+arg_18].text:00000001400010F6 lea rcx, aI64d ; "%I64d".text:00000001400010FD call sub_140001080.text:0000000140001102 mov rcx, [rsp+38h+arg_18] .text:0000000140001107 mov rax, 5555555555555556h.text:0000000140001111 imul [rsp+38h+arg_10].text:0000000140001116 mov rax, 6666666666666667h.text:0000000140001120 mov r10, rdx.text:0000000140001123 shr r10, 3Fh.text:0000000140001127 add r10, rdx.text:000000014000112A imul rcx.text:000000014000112D mov [rsp+38h+var_18], r10.text:0000000140001132 mov r9, rdx.text:0000000140001135 sar r9, 1.text:0000000140001138 mov rax, r9.text:000000014000113B shr rax, 3Fh.text:000000014000113F add r9, rax .text:0000000140001142 mov rax, 2AAAAAAAAAAAAAABh.text:000000014000114C imul rcx.text:000000014000114F mov rax, 1C71C71C71C71C72h.text:0000000140001159 mov r8, rdx.text:000000014000115C shr r8, 3Fh.text:0000000140001160 add r8, rdx.text:0000000140001163 imul rcx.text:0000000140001166 lea rcx, aI64dI64dLldLld ; "%I64d%I64d%lld%lld".text:000000014000116D mov rax, rdx.text:0000000140001170 shr rax, 3Fh.text:0000000140001174 add rdx, rax.text:0000000140001177 call sub_140001020.text:000000014000117C lea rcx, aPause ; "pause".text:0000000140001183 call sub_1400045C4.text:0000000140001188 xor eax, eax.text:000000014000118A add rsp, 38h.text:000000014000118E retn觀看反彙編其實跟x86一致。
這裡還是去掉流水線優化,提取核心代碼位置進行反彙編。
M數值變大,還是代入公式即可。這裡使用的mov r10,rdx,說明直接使用的rdx, rdx是一個64位數,所以 2^64 / M = 2.9999999 向上取整 = 3。
5.3 linux gcc x86 有符號非2的冪高級代碼:
int main(){ int NumberOne = 0; int NumberTwo = 0; scanf("%d", &NumberOne); scanf("%d", &NumberTwo); int Count1 = NumberOne / 3; int Count2 = NumberTwo / 5; int Count3 = NumberTwo / 6; int Count4 = NumberTwo / 9; printf("%d%d%d%d", Count4, Count3, Count2, Count1); scanf("%d", &Count4); scanf("%d", &Count3); scanf("%d", &Count2); scanf("%d", &Count1); printf("%d%d%d%d", Count4, Count3, Count2, Count1); return 0;}如果你是64位系統,那麼你使用gcc命令如下:
編譯出來即可,其實下面反彙編跟x86 vc++6.0 vs2019 一樣,可以不用看了。
反彙編:
.text:080484BB ; int __cdecl main(int argc, const char **argv, const char **envp).text:080484BB public main.text:080484BB main proc near ; DATA XREF: _start+17↑o.text:080484BB.text:080484BB var_24 = dword ptr -24h.text:080484BB var_20 = dword ptr -20h.text:080484BB var_1C = dword ptr -1Ch.text:080484BB var_18 = dword ptr -18h.text:080484BB var_14 = dword ptr -14h.text:080484BB var_10 = dword ptr -10h.text:080484BB var_C = dword ptr -0Ch.text:080484BB argc = dword ptr 8.text:080484BB argv = dword ptr 0Ch.text:080484BB envp = dword ptr 10h.text:080484BB.text:080484BB ; __unwind {.text:080484BB lea ecx, [esp+4].text:080484BF and esp, 0FFFFFFF0h.text:080484C2 push dword ptr [ecx-4].text:080484C5 push ebp.text:080484C6 mov ebp, esp.text:080484C8 push ebx.text:080484C9 push ecx.text:080484CA sub esp, 20h.text:080484CD mov eax, large gs:14h.text:080484D3 mov [ebp+var_C], eax.text:080484D6 xor eax, eax.text:080484D8 mov [ebp+var_24], 0.text:080484DF mov [ebp+var_20], 0.text:080484E6 sub esp, 8.text:080484E9 lea eax, [ebp+var_24].text:080484EC push eax.text:080484ED push offset unk_80486B0.text:080484F2 call ___isoc99_scanf.text:080484F7 add esp, 10h.text:080484FA sub esp, 8.text:080484FD lea eax, [ebp+var_20].text:08048500 push eax.text:08048501 push offset unk_80486B0.text:08048506 call ___isoc99_scanf.text:0804850B add esp, 10h.text:0804850E mov ecx, [ebp+var_24] .text:08048511 mov edx, 55555556h.text:08048516 mov eax, ecx.text:08048518 imul edx.text:0804851A mov eax, ecx.text:0804851C sar eax, 1Fh.text:0804851F sub edx, eax.text:08048521 mov eax, edx.text:08048523 mov [ebp+var_1C], eax.text:08048526 mov ecx, [ebp+var_20] .text:08048529 mov edx, 66666667h.text:0804852E mov eax, ecx.text:08048530 imul edx.text:08048532 sar edx, 1.text:08048534 mov eax, ecx.text:08048536 sar eax, 1Fh.text:08048539 sub edx, eax.text:0804853B mov eax, edx.text:0804853D mov [ebp+var_18], eax.text:08048540 mov ecx, [ebp+var_20] .text:08048543 mov edx, 2AAAAAABh.text:08048548 mov eax, ecx.text:0804854A imul edx.text:0804854C mov eax, ecx.text:0804854E sar eax, 1Fh.text:08048551 sub edx, eax.text:08048553 mov eax, edx.text:08048555 mov [ebp+var_14], eax.text:08048558 mov ecx, [ebp+var_20] .text:0804855B mov edx, 38E38E39h.text:08048560 mov eax, ecx.text:08048562 imul edx.text:08048564 sar edx, 1.text:08048566 mov eax, ecx.text:08048568 sar eax, 1Fh.text:0804856B sub edx, eax.text:0804856D mov eax, edx5.4 其它同理自己建立項目研究自己建立項目研究,高手複習,新手學習,謝謝觀看。
看雪ID:TkBinary
https://bbs.pediy.com/user-home-723188.htm
*本文由看雪論壇 TkBinary 原創,轉載請註明來自看雪社區。