1、軟件的逆向、外掛、破解等,本質上是想辦法改變原有代碼的執行路徑,主要的方式有兩種:
- 改變某些關鍵數據,比如函數參數(android下有xpose、frida等現成的hook框架,逆向人員只需要找准hook的點就完成了90%的逆向工作)、某些全局變量(比如VX防止多開的mutex);改變這些數據后,會導致原來的if條件走另一個分支,達到改變執行流程的目的;
- 改變某些關鍵代碼,尤其是涉及到代碼跳轉的指令,最常見的就是jmp、call,比如改成NOP;或把JE改成JNE等相反的邏輯;
外掛、軟件破解等無不是從這方面入口去尋找關鍵的call、jmp或數據。為了保護自己的關鍵代碼不被輕易找到並更改,衍生出了很多的防護措施,常見的反調試、代碼加密(異或代碼,等到執行前才解密,windows下的PG保護就是這么干的)、加殼干擾等;
先看一段簡單的代碼:用戶輸入字符串,然后和內置的字符串做對比,一樣就輸出ok,不一樣就輸出fail;這種比對的邏輯一般都用來做密碼、license等的判斷。稍微懂點底層安全的人都能看出這段代碼的兩個問題:
(1)字符串比對這里:從左向右逐個比對,一旦發現有一個不匹配立即返回false;側信道攻擊就可以根據返回時間判斷前面的字符是不是正確的。防御的方式也不復雜,比如兩個字符串統一用MD5轉換后再比對。或則不要單個字節對比,而是把字符串按照4個字節均分成N等份,每份32bit,轉換成int型后比較整數的大小,看看是不是一樣的;
(2)main函數里面的if判斷邏輯;
反匯編代碼如下:00381A9A處有個je,很明顯這里的正常邏輯是:eax等於0嗎?如果是就跳轉到00381AAB這里打印fail,否則繼續執行打印ok的代碼;
整個過程的代碼邏輯清晰明了,但這也產生了另一個問題:這種代碼隨便用OD\IDA\X32DBG就能反匯編,屆時改一下關鍵處的代碼(比如00381A9A的je改成jne),或則把對比函數的返回值從0改成1(返回值默認保存在eax寄存器),這種密碼、license校驗就完全失效了。那么問題來了,該怎么保護了這些關鍵代碼了?
while (1) 00381A68 B8 01 00 00 00 mov eax,1 00381A6D 85 C0 test eax,eax 00381A6F 74 49 je main+7Ah (0381ABAh) { scanf("%s", buf); 00381A71 68 38 A1 38 00 push offset buf (038A138h) 00381A76 68 30 7B 38 00 push offset string "%s" (0387B30h) 00381A7B E8 B7 F5 FF FF call _scanf (0381037h) 00381A80 83 C4 08 add esp,8 if (mystrcmp(buf, "123")) 00381A83 68 34 7B 38 00 push offset string "123" (0387B34h) 00381A88 68 38 A1 38 00 push offset buf (038A138h) 00381A8D E8 AE F6 FF FF call mystrcmp (0381140h) 00381A92 83 C4 08 add esp,8 00381A95 0F B6 C0 movzx eax,al 00381A98 85 C0 test eax,eax 00381A9A 74 0F je main+6Bh (0381AABh) printf("ok\n"); 00381A9C 68 38 7B 38 00 push offset string "ok\n" (0387B38h) 00381AA1 E8 31 F6 FF FF call _printf (03810D7h) 00381AA6 83 C4 04 add esp,4 00381AA9 EB 0D jmp main+78h (0381AB8h) else printf("fail\n"); 00381AAB 68 3C 7B 38 00 push offset string "fail\n" (0387B3Ch) 00381AB0 E8 22 F6 FF FF call _printf (03810D7h) 00381AB5 83 C4 04 add esp,4 } 00381AB8 EB AE jmp main+28h (0381A68h) system("pause"); 00381ABA 8B F4 mov esi,esp 00381ABC 68 44 7B 38 00 push offset string "pause" (0387B44h) 00381AC1 FF 15 70 B1 38 00 call dword ptr [__imp__system (038B170h)] 00381AC7 83 C4 04 add esp,4 00381ACA 3B F4 cmp esi,esp 00381ACC E8 7D F7 FF FF call __RTC_CheckEsp (038124Eh) } 00381AD1 5F pop edi 00381AD2 5E pop esi
2、保護方式原理也不復雜:見招拆招,既然逆向人員大都通過IDA\OD\X32DBG等工具反編譯后調試匯編代碼,那防御人員是不是可以在匯編代碼層面考慮干擾方式了?現在來看看另一種比對方式:
#include <iostream> #include <stdlib.h> #include <Windows.h> #define IS_ZERO(x) (1-((x)|(-x))>>31) typedef void (*CALL)(); void f1() { printf("ok\n"); } void f2() { printf("fail\n"); } /* 如果a==b調用f1,否則調用f2,不能出現jcc指令 */ void nojcc(int a, int b, CALL f1, CALL f2) { _asm { mov eax, a sub eax, b; 如果結果是0,ZF = 1,否則相反;這里也可以用CMP,只不過不把結果寫回eax pushfd pop eax; eflags存入eax and eax, 0x40; 0x40 = 1000000取eflags的ZF位 shr eax, 6; ZF位放末尾 mov ecx, 1 sub ecx, eax; 如果ZF = 1,那么ecx = 0,否則ecx = 1.總之ecx和ZF剛好相反 ; 注意:這里的兩個數相減,會繼續影響ZF。比如ecx=0,會讓ZF=1,這剛好和開始的ZF保持一致;同時這里的ZF已經不用了,不影響最后的結果 neg eax; 按位取反后加1.如果ZF = 1,那么eax = 0xFFFFFFFF;如果ZF = 0,那么eax = 0x100000000(注意最高位溢出了,實際上eax = 0) neg ecx; 按位取反后加1.如果ZF = 1,那么ecx = 0xFFFFFFFF;如果ZF = 0,那么ecx = 0x100000000(注意最高位溢出了,實際上ecx = 0) and eax, f1; 如果eax = 0,那么f1就不保留,執行f2 and ecx, f2; 如果ecx = 0,那么f2就不保留,執行f1 or eax, ecx; 上面在f1和f2已經二選一了(只保留1個,另一個是0),這里綜合一下結果 call eax } } int main() { nojcc(1,2,f1,f2); system("pause"); }
先看看效果:nojcc函數前面的兩個參數不等,那么打印fail,否則打印ok;
老規矩:重要的邏輯都在注釋了。這里大致解釋一下邏輯:
- 既然兩個數要比較,常見的就是CMP了。但這個很容易被逆向人員發現,這里用sub替代,隱蔽性更好;
- 兩個數相減,怎么知道結果了?看eflgas的ZF位唄;
- 最后有f1和f2兩個函數,也就是有2個分支,應該選哪一個了?因為sub ecx,eax,所以ecx和eax有一個必定是0,這就是個很好的二選一“開關”,所以先neg一下,讓兩個數分別是0xFFFFFFFF和0x00000000,再分別和兩個地址and,這時只有一個分支的地址不為0了,最后or一下,得到最終的跳轉地址,完美撒花!
整個過程完全沒用到CMP、JE、JNE等跳轉指令,並且用了and、or、shr等操作,給人第一感覺是用來加解密的!整個過程需要逆向人員逐步調試每行指令才有可能理解背后的邏輯,安全性大幅提升!
3、接着用專業的加殼工具VMP加殼;這里把第一個原始的exe用VMP針對entryPoint加殼,結果對比如下:
(1)體積:從40K膨脹到了584K,翻了14倍;
增加的部分集中在這了:vmp的段,一共有89*4KB=356KB;
(2)再看看入口,已經面目全非;第一行代碼就是跳轉;
跟着剛才的jmp語句,追蹤到這里:先是push一個magic number,再通過call繼續跳轉,這是典型進VM的標志特征!
繼續追蹤:發現很多“莫名其妙”的代碼,函數的風格和VS完全不同(32位最常見的風格就是push ebp,mov ebp,esp開頭),VM加殼后完全變了
注意事項:
1、這里用了scanf函數接受用戶的輸入,這個函數比較久遠了,貌似沒有邊界檢查,容易導致棧溢出攻擊,不安全,所以VS2019默認是編譯不通過的,這里要做如下設置才行;
2、這次知道為啥VX這種國民級別的APP為啥至今不加殼了,加殼的“壞處”:
(1)殼的特征也很明顯,比如“內存布局”中能找到vmp的段;
(2)exe的體積增加幾十倍
(3)增加了大量的混淆指令,降低了運行效率;增加的這些混淆指令還有可能導致原APP邏輯出錯!
(4)VMP會不會在加殼的同時留后門了? VX這種裝機量幾十億的APP,一旦官方發布的exe被裝上了病毒/木馬,后果不堪設想!
參考:1、https://www.bilibili.com/video/BV1oT4y1K7Vi VM保護攻防
VMP版本:3.5.0,在吾愛破解的網盤上下載的;