160个Crackme038之P-Code初窥门径
文章目錄
- VB的變量類型
- 實(shí)戰(zhàn)分析
- 第一部分 基礎(chǔ)校驗(yàn)
- 第二部分 根據(jù)用戶名計(jì)算結(jié)果
- 第三部分 除以圓周率
- 第四部分 干擾代碼
- 第五部分 關(guān)鍵比較
- 寫出注冊機(jī)
- 總結(jié)
- 附上分析過程
【軟件名稱】:CyberBlade.2.exe
【軟件大小】:61.0 KB
【下載地址】:https://github.com/TonyChen56/160-Crackme
【加殼方式】:未加殼
【保護(hù)方式】:Name/Serial
【編譯語言】:VB P-Code
【調(diào)試環(huán)境】:W10 x64
【使用工具】 OD,VBExplorer,VB Decompiler
【破解日期】:2019-5-1
【破解目的】:學(xué)習(xí)分析P-Code類型的程序,理解P-Code虛擬機(jī)的解釋過程
【目標(biāo)程序】:
這一次的目標(biāo)程序的這個(gè)Crackme,是160個(gè)Crackme里面的第38個(gè)。運(yùn)行時(shí)需要 Visual Basic 5.0 運(yùn)行庫支持。這個(gè)Crackme的分析,用到了三個(gè)工具,每個(gè)工具都有各自的用途:
- OD:用于跟蹤P-Code偽指令的具體細(xì)節(jié)及在靜態(tài)分析過程中無法查看的數(shù)據(jù)
- VBExplorer 用于查看P-Code偽指令及注釋
- VB Decompiler 用于靜態(tài)查看反匯編的偽代碼,減少OD跟蹤偽指令的工作量
VB的變量類型
想要分析這個(gè)Crackme,首先需要了解VB變量類型在內(nèi)存中的存儲(chǔ)方式。
VB中的variant類型屬于一種結(jié)構(gòu)體,該結(jié)構(gòu)體的前兩個(gè)字節(jié)表示變量的類型,后面有3個(gè)WORD是保留的,接下來才是其真正的值,如下圖:
也就是說VB變量中真正的數(shù)據(jù)是存儲(chǔ)在首地址+8的位置處,下圖顯示了VB的所有的變量類型及含義
實(shí)戰(zhàn)分析
首先用 VB Decompiler反編譯目標(biāo)程序,找到Check按鈕的點(diǎn)擊事件,分析整個(gè)點(diǎn)擊事件的校驗(yàn)過程
這個(gè)程序的校驗(yàn)過程分為五個(gè)部分,下面講解每一個(gè)部分的校驗(yàn)過程
第一部分 基礎(chǔ)校驗(yàn)
首先根據(jù)靜態(tài)分析的結(jié)果可以看到,該程序首先會(huì)校驗(yàn)用戶名和序列號(hào)是否為空,然后判斷序列號(hào)長度是否小于5個(gè)字節(jié),否則提示錯(cuò)誤。即使看不懂反匯編后的VB代碼,也可以通過字符串知道整個(gè)過程。
第二部分 根據(jù)用戶名計(jì)算結(jié)果
第二部分的校驗(yàn)過程看的就不那么清晰了,需要利用OD動(dòng)態(tài)跟蹤每一個(gè)偽指令的具體操作流程。
用VBExplorer對目標(biāo)程序進(jìn)行反編譯,一直往下拉,根據(jù)字符串直接忽略第一部分的基礎(chǔ)校驗(yàn),來到0040E380的位置
我們可以看到第一個(gè)被執(zhí)行的偽指令是0040E380處的0D,接下來將程序載入OD,數(shù)據(jù)窗口跟隨->0040E380
然后給第一個(gè)字節(jié)0D下內(nèi)存訪問斷點(diǎn),F9運(yùn)行
然后隨便輸入一個(gè)用戶名和序列號(hào),點(diǎn)擊Check
程序首先會(huì)讀取一個(gè)字節(jié)的操作碼到AL,
來看下VB Explorer中顯示的操作碼,后面的注釋提示這是在調(diào)用一個(gè)函數(shù),0D后面的是操作碼的參數(shù),接著esi自增1,指向操作碼的參數(shù)
接著通過一個(gè)jmp跳轉(zhuǎn)去執(zhí)行操作碼,0x741BED94是地址跳轉(zhuǎn)表的首地址,eax保存下一條指令的操作碼,由于每一個(gè)跳轉(zhuǎn)地址是一個(gè)DWORD,所以用eax乘以4的值加上跳轉(zhuǎn)表的基地址來索引下一條偽指令的解釋單元,我們跟隨這個(gè)jmp
首先把[ebp-0x4C]賦值給eax,然后將eax壓棧。我們需要知道eax的含義。數(shù)據(jù)窗口跟隨之后,發(fā)現(xiàn)是一個(gè)指針,再次選中前四個(gè)字節(jié),數(shù)據(jù)窗口跟隨DWORD,然后將數(shù)據(jù)顯示方式切換為長型->地址
這其實(shí)是一個(gè)函數(shù)的跳轉(zhuǎn)表,再接著把esi的內(nèi)容A0賦值給edi,而esi始終執(zhí)行的是操作碼
可以看到這一步實(shí)際上是在取操作碼的參數(shù)A0了
接著取出eax的內(nèi)容,然后將eax加上edi,eax實(shí)際是跳轉(zhuǎn)表的首地址,那么參數(shù)A0,就是跳轉(zhuǎn)表的偏移
接著call eax,我們不需要跟進(jìn)這個(gè)函數(shù),只需要關(guān)注棧中的第二個(gè)參數(shù)0x19F134即可。直接步過這個(gè)函數(shù)
可以看到棧中的參數(shù)顯示出了我們剛才輸入的用戶名,再接著單步到xor eax,eax的地址
這里把eax的值清零了。也就是說第一條偽指令已經(jīng)執(zhí)行完成了。
這個(gè)就是P-Code虛擬機(jī)的解釋過程,其中esi始終指向要解釋的偽指令,eax保存的是將要解釋的偽指令。
接著看下一條偽指令,6C是操作碼,64FF是參數(shù)。后面的注釋告訴我們這條偽指令是在將某個(gè)DWORD值入棧。繼續(xù)跟蹤
首先取出操作碼6C,然后esi+5執(zhí)向偽指令參數(shù),接著跳轉(zhuǎn)執(zhí)行這條偽指令,直接跟進(jìn)jmp
首先取出參數(shù)FF64放到eax中,FF64是一個(gè)負(fù)數(shù),
也就是十進(jìn)制的9C,這也是為什么VBExplorer里會(huì)顯示LOCAL_009C的原因,這個(gè)9C代表[ebp-9C],是個(gè)局部變量
接著將[eax+ebp]入棧,壓棧的是剛剛輸入的用戶名[eax]的值是-98,這里其實(shí)是將[ebp-98]局部變量壓入棧,然后eax清零,表示這條偽指令結(jié)束。
繼續(xù)看下一條,偽代碼解釋的求長度
首先取出偽指令,然后直接跟進(jìn)jmp
這里調(diào)用vbaLenBster,參數(shù)是之前輸入的用戶名,接著將用戶名長度入棧后,eax清零,接著看下一條FD6934EF
這條偽指令后面并沒有給出解釋,但是沒有關(guān)系,我們可以根據(jù)偽指令的執(zhí)行過程猜測指令含義,這里其實(shí)是一個(gè)雙操作碼的指令,
首先取出操作碼FD,直接跟進(jìn)jmp,什么都沒有做,直接將eax清零。之后再次取出操作碼69,繼續(xù)跟進(jìn)jmp
這里將bx賦值為0x3,然后跳轉(zhuǎn),繼續(xù)跟進(jìn)
接著取出參數(shù)FF34,FF34也是個(gè)負(fù)數(shù),然后再將FF34加上ebp,代表這是一個(gè)局部變量
接下來將ecx賦值給[eax+0x8]的地址處,我們數(shù)據(jù)窗口跟隨eax,然后將bx賦值給eax,賦值完成后eax值如下:
還記得VB的變量類型嗎?前兩個(gè)字節(jié)是變量類型,03代表是Long,中間是6個(gè)字節(jié)的保留位,首地址+8的位置才是真正的數(shù)值。
這個(gè)07是之前通過vbaLenBstr獲取到的用戶名長度,在這里轉(zhuǎn)成了變量,并將變量首地址壓棧。
現(xiàn)在我們就能通過實(shí)際的跟蹤結(jié)果來得出這條指令的含義了。就是將int值轉(zhuǎn)成變量類型。由于整個(gè)跟蹤過程實(shí)在是復(fù)雜,我這里只貼出算法的關(guān)鍵部分
在40E3C1處截取用戶名的第一個(gè)字符串
接著在40E3CD處將截取的用戶名每一位轉(zhuǎn)成ASCII值
接著將用戶名每一位的ASCII值轉(zhuǎn)為十進(jìn)制后進(jìn)行字符串拼接
整個(gè)過程循環(huán),循環(huán)次數(shù)為用戶名的長度,可以直接在這個(gè)地方下斷點(diǎn)看到最后的結(jié)果
即拼接用戶名的ASCII十進(jìn)制字符串,第二部分的算法就完成
在VB偽代碼中,var_94就是最后拼接的結(jié)果
第三部分 除以圓周率
接下來是第三部分,直接來看VB Decompiler中的偽代碼
這一部分的邏輯也很清晰,如果用戶名拼接的字符串長度大于9的話,就將這個(gè)結(jié)果轉(zhuǎn)為浮點(diǎn)數(shù)除以圓周率,一直除到結(jié)果的長度小于9。這個(gè)部分我也用OD詳細(xì)跟過每一條偽指令,確實(shí)和靜態(tài)反匯編的邏輯是一樣的
但是在loc_40E449的位置,將var_94和一個(gè)值進(jìn)行了異或,并且還減去了另外一個(gè)值。這兩個(gè)數(shù)值我們無從得知,只能跟蹤OD
根據(jù)偽指令的助記符XorVar和SubVar快速定位到這兩個(gè)地址,下內(nèi)存訪問斷點(diǎn),很快就能找到這兩個(gè)值
可以看到這里實(shí)際上是將0x30F85678和用戶名的結(jié)果進(jìn)行異或
然后減去0xD8B3,找到了這兩個(gè)數(shù),第三部分也就結(jié)束了
第四部分 干擾代碼
這個(gè)是最有意思的,你會(huì)發(fā)現(xiàn)代碼初始化了10次循環(huán),循環(huán)將一個(gè)變量和Key值進(jìn)行比較,但問題在于Then分支沒有任何代碼。這也就是說不管這個(gè)循環(huán)中的比較成立與否 都對我們沒有任何影響
第五部分 關(guān)鍵比較
最后一部分,比較序列號(hào)減去var_94是否等于用戶名長度,也就是說用戶名計(jì)算的結(jié)果再加上用戶名的長度就是真正的序列號(hào)。最后對這個(gè)程序的校驗(yàn)過程做一個(gè)總結(jié)
總結(jié):
寫出注冊機(jī)
接著我們根據(jù)已經(jīng)分析的算法寫出這個(gè)程序的注冊機(jī),這個(gè)注冊機(jī)用C++寫還是太費(fèi)勁了 直接用python快
Name = "GuiShou" s = int(''.join([str(ord(i)) for i in Name]))while len(str(s))>9:s = int(s // 3.141592654)Serial = (s ^ 0x30F85678) - 55475 + len(Name) print(Serial)總結(jié)
P-Code的程序并非如傳言一樣不可戰(zhàn)勝,只要你有足夠的耐心,配合VB Decompiler+VBExplorer+OD的黃金組合,剩下的就是純體力活了。
P-Code類的程序用OD跟蹤偽指令雖然能看到每一處實(shí)現(xiàn)細(xì)節(jié),但是畢竟還是太費(fèi)力了。這個(gè)時(shí)候如果能總結(jié)出一套相對比較完整的P-Code的偽指令及每個(gè)參數(shù)的具體含義再配合WKTVBDebugger,調(diào)試P-Code就顯得游刃有余了。如果有大佬總結(jié)出來了還請發(fā)我一份 哈哈。
附上分析過程
最后附上分析過程和相關(guān)文件
:0040E380 0DA0000300 VCallHresult ;Call ptr_0040342C 獲取輸入的用戶名 :0040E385 6C64FF ILdRf ;Push DWORD [LOCAL_009C] 將用戶名壓入堆棧 :0040E388 4A FnLenStr ;vbaLenBstr 求用戶名長度 :0040E389 FD6934FF CVarI4 ; 將用戶名長度轉(zhuǎn)為變量 :0040E38D 2F64FF FFree1Str ;SysFreeString [LOCAL_009C]; [LOCAL_009C]=0 釋放用戶名內(nèi)存 :0040E390 1A68FF FFree1Ad ;Push [LOCAL_0098]; Call [[[LOCAL_0098]]+8]; [[LOCAL_0098]]=0 釋放局部變量 :0040E393 FE68B4FE8901 ForVar ; 初始化循環(huán)次數(shù) 開始循環(huán) :0040E399 0464FF FLdRfVar ;Push LOCAL_009C 將局部變量0壓入堆棧 :0040E39C 21 FLdPrThis ;[SR]=[stack2] :0040E39D 0F0003 VCallAd ;Return the control index 02 :0040E3A0 1968FF FStAdFunc ; :0040E3A3 0868FF FLdPr ;[SR]=[LOCAL_0098] ***********Reference To:[propget]TextBox.Text| :0040E3A6 0DA0000300 VCallHresult ;Call ptr_0040342C 獲取輸入的用戶名 :0040E3AB 046CFF FLdRfVar ;Push LOCAL_0094 將局部變量0壓入堆棧 :0040E3AE 2824FF0100 LitVarI2 ;PushVarInteger 0001 將局部變量1壓入堆棧 :0040E3B3 04D4FE FLdRfVar ;Push LOCAL_012C 將局部變量0壓入堆棧 :0040E3B6 FC22 CI4Var ;vbaI4Var 將變量1轉(zhuǎn)為數(shù)字 :0040E3B8 3E64FF FLdZeroAd ;Push DWORD [LOCAL_009C]; [LOCAL_009C]=0 將用戶名壓入堆棧 :0040E3BB 4644FF CVarStr ; 將用戶名字符串轉(zhuǎn)為變量 :0040E3BE 0404FF FLdRfVar ;Push LOCAL_00FC 將局部變量0壓入堆棧 **********Reference To->msvbvm50.rtcMidCharVar | :0040E3C1 0A0A001000 ImpAdCallFPR4 ;Call ptr_00401006; check stack 0010; Push EAX 截取用戶名的第一個(gè)字符 :0040E3C6 0404FF FLdRfVar ;Push LOCAL_00FC 將用戶名的第一個(gè)字符壓入堆棧 :0040E3C9 FDFEB0FE CStrVarVal ; 將用戶名的第一個(gè)字符從變量轉(zhuǎn)為字符串 **********Reference To->msvbvm50.rtcAnsiValueBstr| :0040E3CD 0B0B000400 ImpAdCallI2 ;Call ptr_0040100C; check stack 0004; Push EAX 將用戶名的第一個(gè)字符轉(zhuǎn)為ASCII值 :0040E3D2 4434FF CVarI2 ; 將用戶名的第一個(gè)字符的ASCII值轉(zhuǎn)為變量 :0040E3D5 FBEFE4FE ConcatVar ; 將ASCII值的十進(jìn)制進(jìn)行字符串拼接 :0040E3D9 FCF66CFF FStVar ; 將拼接的字符串轉(zhuǎn)為變量 :0040E3DD 2FB0FE FFree1Str ;SysFreeString [LOCAL_0150]; [LOCAL_0150]=0 釋放字符串 :0040E3E0 1A68FF FFree1Ad ;Push [LOCAL_0098]; Call [[[LOCAL_0098]]+8]; [[LOCAL_0098]]=0 :0040E3E3 36060044FF24FF04 FFreeVar ;Free 0006/2 variants 釋放變量 :0040E3EC 04D4FE FLdRfVar ;Push LOCAL_012C :0040E3EF FE7EB4FE2D01 NextStepVar ; 開始下一輪循環(huán) :0040E3F5 046CFF FLdRfVar ;Push LOCAL_0094 將用戶名拼接的字符串壓入堆棧——7111710583104111117(0x13) :0040E3F8 FBEB44FF FnLenVar ;vbaLenVar 求用戶名拼接的字符串長度 :0040E3FC 2854FF0900 LitVarI2 ;PushVarInteger 0009 將整形變量9壓入堆棧 :0040E401 5D HardType ; 修改變量9的類型 :0040E402 FB74 GtVarBool ;Push (Pop1 >= Pop2) 比較長度是否大于9 :0040E404 1CB901 BranchF ;If Pop=0 then ESI=0040E425 如果不大于9則跳轉(zhuǎn)到0040E425 :0040E407 046CFF FLdRfVar ;Push LOCAL_0094 將用戶名拼接的字符串壓入堆棧 :0040E40A FEC454FF50455254 LitVarR8 ; 將參數(shù)一(圓周率)的類型修改為浮點(diǎn)數(shù) :0040E416 FBBC44FF DivVar ; 將用戶名拼接的字符串除以圓周率 :0040E41A FBE124FF FnFixVar ; 相當(dāng)于字符串拷貝 :0040E41E FCF66CFF FStVar ; 將用戶名拼接的字符串轉(zhuǎn)為浮點(diǎn)數(shù) :0040E422 1E8901 Branch ;ESI=0040E3F5 如果長度大于9則跳轉(zhuǎn)至0040E3F5 :0040E425 046CFF FLdRfVar ;Push LOCAL_0094 將浮點(diǎn)數(shù)結(jié)果壓入堆棧 :0040E428 FEC154FF7856F830 LitVarI4 ; 將浮點(diǎn)數(shù)轉(zhuǎn)為整形 :0040E430 FB1744FF XorVar ; 將結(jié)果和30F85678進(jìn)行異或 :0040E434 FCF66CFF FStVar ; 保存結(jié)果 :0040E438 046CFF FLdRfVar ;Push LOCAL_0094 將異或后的結(jié)果壓棧 :0040E43B 080800 FLdPr ;[SR]=[STACK_0008] :0040E43E 8A4C00 MemLdStr ;Push DWORD [[SR]+004C] 將0xDBD3壓棧 :0040E441 FD6954FF CVarI4 ; 將0xDBD3轉(zhuǎn)為變量 :0040E445 FB9C44FF SubVar ; 用異或后的結(jié)果減去0xDBD3 :0040E449 FCF66CFF FStVar ; 保存結(jié)果需要分析記錄和相關(guān)文件可以到我的Github下載:https://github.com/TonyChen56/160-Crackme
總結(jié)
以上是生活随笔為你收集整理的160个Crackme038之P-Code初窥门径的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 脚本类恶意程序分析技巧汇总
- 下一篇: 160个Crackme039