[原創]一次美麗的誤會引發對函式呼叫保護的思考
各位大佬請指正,一時思考,並未做更多詳細研究。
很久沒碰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
的模組中找一個不用(零值)的記憶體,用來保護臨時指令,不細講了,大家自行探索吧。
(完)
歡迎關注:漢客兒
相關文章
- 一次快速排序錯誤引發的思考2015-10-30排序
- 從函式式元件引發的效能思考2021-07-17函式元件
- 一次composer錯誤使用引發的思考2019-03-05
- 函式呼叫時用const保護指標2022-12-01函式指標
- 一次定時任務配置錯誤引發的思考2019-10-16
- ASP中函式呼叫對引數的影響 (轉)2007-10-17函式
- 【原創】一對雙引號引發的goldengate血案2011-09-05Go
- 函式的呼叫方式和引數2024-04-12函式
- 一次Toast元件引發的思考2019-12-07AST元件
- 美麗的閉包,在js中實現函式過載2018-06-16JS函式
- 避免對mod函式的呼叫2014-01-17函式
- 關於Python中函式過載問題的思考(原創)2010-05-18Python函式
- 一次打包引發的思考,原來maven還能這麼玩?2020-11-09Maven
- 關於一篇文章引發的匿名函式的思考2019-06-12函式
- 和Android的第一次美麗邂逅2017-08-27Android
- 【原創】cast() 函式的用處2008-06-01AST函式
- 【C語言】函式的概念和函式的呼叫(引數傳遞)2020-10-04C語言函式
- 函式呼叫的引數規則與解包2016-04-11函式
- 由mv命令引發的對inode的思考2020-09-01
- 美麗or智慧的思考,女孩必須懂得的25個幸福祕籍。2012-10-13
- 開拓創想【大牛面對面】系列之“美麗說”移動開發實踐的祕密2015-04-16移動開發
- 由節流函式引發出我對event-loop的思考,順便刷刷爆款題2019-04-01函式OOP
- 關於共享資源保護的思考2022-12-17
- 從一次react非同步setState引發的思考2018-12-04React非同步
- 一次線上問題排查所引發的思考2018-07-09
- 類的對過載函式的呼叫不明確2013-07-28函式
- 美麗天天秒模式開發_美麗天天秒商城系統搭建2021-12-13模式
- 一次內聯元素錯位引發對line-height的思考2018-11-08
- 一次聊天引發的思考--java併發包實戰2019-03-20Java
- 【原】關於員工離職引發的思考2008-04-24
- 一次筆試引發的關於setTimeout的this的思考2018-12-21筆試
- 【原創】SYS_CONTEXT函式的用法2008-05-07Context函式
- 一個外行對ERP的問題的思考方式=原創2007-12-05
- 反射破壞單例的私有建構函式保護2019-01-07反射單例函式
- Lua中呼叫ref和out修飾引數的函式/過載函式2024-05-23函式
- 函式呼叫引數變數傳值的問題2020-04-04函式變數
- Swift 呼叫 Objective-C 的可變引數函式2018-03-24SwiftObject函式
- _nop_()函式引發的血案2017-01-30函式