[原創]一次美麗的誤會引發對函式呼叫保護的思考
各位大佬請指正,一時思考,並未做更多詳細研究。
很久沒碰wx了,最近想寫個東西,就重新拿了起來,最新版本2.6.8.65(此時已經2.6.8.68)。
找到以前分析過的傳送文字訊息介面,發現函式大變樣,很明顯的vm痕跡。
.vmp0:1131CE33 000 push 2493AC03h .vmp0:1131CE38 004 call sub_1134AEB3 .vmp0:1131CE3D 000 mov cx, [ebp+0] .vmp0:1131CE42 000 test bp, 373Dh .vmp0:1131CE47 000 shl ah, cl .vmp0:1131CE49 000 mov dx, [ebp+2] .vmp0:1131CE4E 000 cmovnb eax, edi .vmp0:1131CE51 000 lea ebp, [ebp-2] ... .vmp0:1131CE9C bswap eax .vmp0:1131CE9E inc eax
當時也沒在意,仔細看介面引數並沒有變化,就直接拿來用了。
結果發現介面不能用了,並沒有成功傳送文字資訊。
擦,難道vm裡面藏了什麼玄機,做了防止函式呼叫的保護??
...
正整備大幹一場的時候,重新測試給別人傳送訊息是ok的。
這是一次美麗的誤會,測試時是給自己的微信傳送訊息,結果證明該介面是不能給自己發的,所以沒成功。
...
然後就繼續說說先前自以為的wx在函式中可能做的防止呼叫的保護吧。
防
按照自己思考的防止別人呼叫函式的思路,其實就是檢查呼叫源,那麼肯定是從呼叫棧入手:
- 在函式內部回溯呼叫堆疊,檢查返回地址
- 返回地址為微信模組則正常呼叫,否則拒絕執行
- 可能檢查一層(wechatwin.dll),或者多層
- 可能檢測返回地址在模組範圍,或者是準確的返回地址
- vm相關邏輯,增加分析難度
大概實現程式碼就是:
void TestAntiCall(DWORD a1) { //vmstart DWORD retAddr = *((DWORD*)((char*)&a1 - 4));// if(retAddr > wxModuleBase && retAddr < wxModuleEnd) { //do things } else { //anti //do nothing } //vmend }
攻
所以能夠想到的對抗方式就是在呼叫TestAntiCall的時候,修改呼叫棧返回地址,讓TestAntiCall誤以為確實是正常呼叫。
這裡分析只考慮檢查一層返回地址。
比如如下正常呼叫程式碼,00003就是返回地址,在合法模組內,即可正常呼叫。
//正常呼叫程式碼 void Right_TestAntiCall() { 00001 push a1 00002 call TestAntiCall 00003 add esp, 4 }
而我的呼叫TestAntiCall函式(在我的模組內)如下,add esp, 4;
為TestAntiCall拿到的返回地址,這個地址肯定在我的模組內,呼叫失敗。
pfnTestAntiCall = 原始TestAntiCall地址; pfnTestAntiCall_RetAddr = 000003;//呼叫TestAntiCall返回地址 //這個會失敗 void MyTestAntiCall(DWORD a1) { __asm { push a1; call pfnTestAntiCall; add esp, 4; //返回地址 } }
然後嘗試欺騙TestAntiCall
,我們修改一下呼叫棧的返回地址(本來應該是MyRetAddr)。
透過push+jmp
來替換通常的call
,這樣返回地址由我們自己壓入,這裡壓入正常呼叫的返回地址g_SendTextMsgRetAddr
。
//這個會成功 void MyTestAntiCall(DWORD a1) { __asm { push a1; push g_SendTextMsgRetAddr;//壓入原始retaddr jmp pfnWxSendTextMsg; //呼叫函式,這樣函式內部檢測就是正常的 add esp, 4; //MyRetAddr } }
當然,就這麼簡單的呼叫,肯定會出問題的,因為jmp pfnWxSendTextMsg
之後,就會返回到Right_TestAntiCall
的00003
,如此顯然導致棧破壞,會出現崩潰。
所以為了讓程式正常執行,還需要多兩個處理步驟。
Right_TestAntiCall
的00003處修改指令為jmp MyRetAddr。讓執行流返回到MyTestAntiCall1- 恢復00003處原始指令。
//1. `Right_TestAntiCall`的00003處修改指令為jmp MyRetAddr。讓執行流返回到MyTestAntiCall1 void fakeAntiTestCall(DWORD retaddr1, DWORD retaddr2, char OrigCode[5]) { DWORD MyRetAddr = retaddr1 - 24; DWORD ShellCode[5] = { 0xe9, 0x00, 0x00, 0x00, 0x00 }; *((DWORD*)(&ShellCode[1])) = MyRetAddr; memcpy(OrigCode, (char*)retaddr2, 5); Patch((PVOID)retaddr2, 5, ShellCode); } //2. 恢復00003處原始指令。 void fakeAntiTestCall1(DWORD retaddr2, char OrigCode[5]) { Patch((PVOID)retaddr2, 5, OrigCode); } //這個會成功 void MyTestAntiCall(DWORD a1) { DWORD MyRetAddr = 0; char OrigCode[5] = { 0 }; __asm { jmp RET1; INIT: pop eax;//retAddr mov MyRetAddr, eax; lea eax, OrigCode; push eax; push g_SendTextMsgRetAddr; push MyRetAddr; call fakeAntiTestCall; //在原始g_SendTextMsgRetAddr處跳入MyTestAntiCall1的MyRetAddr push a1; push g_SendTextMsgRetAddr;//壓入原始retaddr jmp pfnWxSendTextMsg; //呼叫函式,這樣函式內部檢測就是正常的 add esp, 4; //MyRetAddr lea eax, OrigCode; push eax; push g_SendTextMsgRetAddr; call fakeAntiTestCall1;//恢復g_SendTextMsgRetAddr資料 ret; RET1: call INIT; nop; } }
為了拿到MyRetAddr的地址,透過call+pop的方法完成,如下:
__asm { jmp RET1: WORK: pop eax; //eax = retaddr mov retaddr, eax; //do thing add esp, 4;//MyRetAddr RET1: call WORK;//push retaddr; jmp WORK; nop;//retaddr }
上面拿到retaddr和MyRetAddr明顯不是同一個,所以在fakeAntiTestCall
中減去一個偏移24拿到MyRetAddr
。
偏移值透過下面的位元組碼可以計算出來10024E1E
- 10024E06
= 24。
.text:10024DDF EB 37 jmp short RET1 .text:10024DE1 INIT: .text:10024DE1 58 pop eax .text:10024DE2 89 45 F4 mov MyRetAddr, eax .text:10024DE5 8D 45 F8 lea eax, OrigCode .text:10024DE8 50 push eax .text:10024DE9 FF 35 00 D0 25 10 push pfnTestAntiCall_RetAddr .text:10024DEF FF 75 F4 push MyRetAddr .text:10024DF2 E8 C9 00 00 00 call fakeAntiTestCall; .text:10024DF7 FF 75 E0 push a1 .text:10024DFA FF 35 00 D0 25 10 push pfnTestAntiCall_RetAddr .text:10024E00 FF 25 D4 A4 28 10 jmp pfnTestAntiCall; .text:10024E06 83 C4 04 add esp, 4 .text:10024E09 8D 45 F8 lea eax, OrigCode .text:10024E0C 50 push eax .text:10024E0D FF 35 00 D0 25 10 push MyRetAddr .text:10024E13 E8 88 00 00 00 call fakeAntiTestCall1; .text:10024E14 C3 ret; .text:10024E19 .text:10024E19 RET1: .text:10024E19 E8 C4 FF FF FF call INIT .text:10024E1E 90 nop
如此可以正常完成一次呼叫,但是還有問題,因為會反覆修改Right_TestAntiCall
的指令,可能在多執行緒中執行時出現問題。
所以更好的方法時在Right_TestAntiCall
的模組中找一個不用(零值)的記憶體,用來保護臨時指令,不細講了,大家自行探索吧。
(完)
歡迎關注:漢客兒
相關文章
- 一次composer錯誤使用引發的思考2019-03-05
- 從函式式元件引發的效能思考2021-07-17函式元件
- 函式呼叫時用const保護指標2022-12-01函式指標
- 一次定時任務配置錯誤引發的思考2019-10-16
- 函式的呼叫方式和引數2024-04-12函式
- 一次Toast元件引發的思考2019-12-07AST元件
- 關於一篇文章引發的匿名函式的思考2019-06-12函式
- 一次打包引發的思考,原來maven還能這麼玩?2020-11-09Maven
- 【C語言】函式的概念和函式的呼叫(引數傳遞)2020-10-04C語言函式
- 一次內聯元素錯位引發對line-height的思考2018-11-08
- 由mv命令引發的對inode的思考2020-09-01
- 由節流函式引發出我對event-loop的思考,順便刷刷爆款題2019-04-01函式OOP
- 美麗的閉包,在js中實現函式過載2018-06-16JS函式
- 一次筆試引發的關於setTimeout的this的思考2018-12-21筆試
- 從一次react非同步setState引發的思考2018-12-04React非同步
- Lua中呼叫ref和out修飾引數的函式/過載函式2024-05-23函式
- 函式呼叫引數變數傳值的問題2020-04-04函式變數
- Swift 呼叫 Objective-C 的可變引數函式2018-03-24SwiftObject函式
- 一次聊天引發的思考--java併發包實戰2019-03-20Java
- 關於共享資源保護的思考2022-12-17
- 一個 Vue 地圖元件錯誤引發的思考2019-04-02Vue地圖元件
- 外部函式的呼叫2018-11-06函式
- 學習javaScript必知必會(1)~js介紹、函式、匿名函式、自呼叫函式、不定長引數2022-01-14JavaScriptJS函式
- 一次線上問題排查所引發的思考2018-07-09
- 反射破壞單例的私有建構函式保護2019-01-07反射單例函式
- python不定長引數如何呼叫函式?2021-09-11Python函式
- for...in引發的思考2019-05-03
- 如何使用「原本」認證原創文章,保護自己的版權2018-04-20
- Python中將函式作為另一個函式的引數傳入並呼叫2019-02-16Python函式
- 一個map函式引發的血案2019-04-16函式
- 星級評價元件--引發對React元件的思考2018-03-18元件React
- 電梯引發的思考2018-07-18
- 函式呼叫棧的問題2018-08-10函式
- 虛擬函式的呼叫原理2020-10-10函式
- 記一次.net使用httpclient中程式碼中使用response.EnsureSuccessStatusCode()引發的誤會2024-09-07HTTPclient
- 原創:oracle聚合函式介紹2020-04-06Oracle函式
- 從app.route裝飾器引發對endpoint的思考2019-02-16APP
- 保護創新、促進仿製:對中國建立藥品專利保護體系的政策建議2020-07-06