吾愛破解 - LCG - LSG |安卓破解|病毒分析|huihengkj.com

 找回密碼
 注冊[Register]

QQ登錄

只需一步,快速開始

搜索
查看: 3291|回復: 24

[原創] PESpin 0.3x兼容脫殼分析

  [復制鏈接]
樓主
鎮北看雪 發表于 2020-9-12 23:55 回帖獎勵
本帖最后由 鎮北看雪 于 2020-9-13 10:08 編輯

寫在前面


為什么說是兼容脫殼分析呢,因為此殼涉及到IAT的操作。如果不注意的話,看著好像是脫殼完成了再脫殼的機器上也能正常運行,但是一旦放到另一個機器上就無法運行。我在一開始脫此殼時就是犯了這樣的錯誤,下面我們就來一起探索這個殼。

脫殼工具


脫殼分析過程


尋找OEP

用OD加載被加殼軟件后運行發現存在很多異常,那我們就用第一次異常法來尋找OEP。
配置好OD后我們運行程序來到最后一次異常處,我們發現這里是都是一些非法指令。

500彩票邀请码那我就來到此異常的異常處理處,從堆棧視圖中我們可以得知異常處理函數的地址為0x46D7B4。我們再次地址處下斷點然后Shif + F9運行程序,程序會停在此處。

然后我們打開內存窗口對代碼段設置內存訪問斷點,運行程序后程序會停在OEP處。

500彩票邀请码我們觀察OEP處代碼發現和正常的入口點并不同,其應該存在Stolen bytes。

解決stolen bytes

我們重新加程序發現在EP時pushad后esp為0x12FFA4,那么在執行Stolen bytes前肯定會popad。

500彩票邀请码我們先運行到最后一次異常的異常處理程序后,對0x12FFA4下硬件訪問斷點。

500彩票邀请码然后我們運行程序后,程序會在執行完popad指令后暫停。我們向下單步跟蹤發現Stolen Bytes代碼,此代碼存在花指令,所以我么需要單步跟蹤并將Stolen bytes代碼的機器碼記錄。

最后我們得到了Stolen的機器碼

55 8B EC 6A FF 68 60 0E 45 00 68 C8 92 42 00 64 A1 00 00 00 00 50 64 89 25 00 00 00 00 83 C4 A8
53 56 57 89 65 E8 FF 15 EC F5 46 00 33 D2 8A D4 89 15 34 E6 45 00 8B C8 81 E1 FF 00 00 00 89 0D
30 E6 45 00 C1 E1 08

500彩票邀请码接著我們將此代碼粘貼到假的OEP前,最后得到真正的OEP的RVA為0x271B0。

DUMP程序

正常的步驟我們就應該在尋找完OEP后dump程序,但此殼如果在此dump就無法脫去。我們可以先用LordPR工具dump程序,后面再分析為什么不能在此dump程序。

重建輸入表

500彩票邀请码重建輸入表最為麻煩,我們隨便找到一處API調用。我們發現并沒有看見直接的API調用提示,但是發現了類似于API調用的語句,call Xdword ptr ds:[0x46FA0C]。我們來到0x46FA0C地址處發現此處類似于一個函數的頭部并且存在花指令。

我們F8單步向下執行發現其會jmp 到一個高地址處,此地址應該是某個dll的輸出函數所在的空間。

我們來到此地址處發現此地址處為某個API的指令,而且我們發現此API此地址前的指令和剛剛在跳轉到此地址前執行的指令相同。我們得知此殼應該是對程序調用的API頭部進行了HOOK,將API頭部的代碼拷貝到自己申請的內存中自己執行后,又會跳到對應API地址處執行HOOK的API剩余的指令。

500彩票邀请码由 call Xdword ptr ds:[0x46FA0C] 指令可得0x46FA0C內存中保存的是殼代碼動態申請內存的地址,我們需要將此地址處的內存的值修改為真正API的地址。我們重新加載程序然后來到最后一次異常處理程序地址處,我們對0x46FA0C地址下內存寫入斷點。運行程序后程序停止,我們發現此處其會將殼代碼動態申請的內存地址存到此內存處。

如果把eax變為API真正的地址就可以使繞過殼對API的hook了。我們需要得到殼代碼什么時候獲得API真正的地址要想得到API真正的地址殼代碼一定會調用GetProcAddress(),重新加載程序還是來到最后一次異常處理程序處,然后我們對GetProcAddress()API下斷點(最好在API返回處下斷點,因為測試發現殼代碼會對API頭部進行檢測是否有斷點)。運行程序發現程序斷在GetProcAddress()函數處,我們Alt + F9返回到用戶代碼,可以看到eax為對應API的代碼。

我們就可以用此關鍵點結合上一個可以繞過API hook的關鍵點進行打補丁。思路是:因為此關鍵處可以得到API真正的地址,我們在此處將API地址保存,另一處關鍵點如果eax為真正的API地址就可以將繞過API HOOK,那我們就在那打補丁讓剛剛保存的API地址賦值給eax。

我們先在能獲得eax地址的地方跳到一個空白地址處打補丁將API保存起來。

補丁代碼為下,將API地址按雙字保存到地址0x0045C700處。(0x477389內存處值為0x0045C700)

00477321    60              pushad
00477322    8B1D 89734700   mov ebx,dword ptr ds:[0x477389]
00477328    8903            mov dword ptr ds:[ebx],eax
0047732A    83C3 04         add ebx,0x4
0047732D    891D 89734700   mov dword ptr ds:[0x477389],ebx
00477333    61              popad
00477334    894424 1C       mov dword ptr ss:[esp+0x1C],eax
00477338    61              popad
00477339  ^ E9 404BFFFF     jmp 0046BE7E                           
0047733E    90              nop

然后我們在第一個關鍵處跳到一個空白地址處,打補丁將eax變為API真正的地址。

500彩票邀请码補丁代碼為下,將剛剛保存的API地址取出來并賦給eax替換殼代碼動態申請的地址,從而繞過API HOOK

00477349    60              pushad
0047734A    8B1D 89734700   mov ebx,dword ptr ds:[0x477389]
00477350    83EB 04         sub ebx,0x4
00477353    8B03            mov eax,dword ptr ds:[ebx]
00477355    8907            mov dword ptr ds:[edi],eax
00477357    61              popad
00477358  ^ E9 B94CFFFF     jmp 0046C016                           

500彩票邀请码然后我們在代碼塊設置內存訪問斷點,運行程序后程序會停在假的OEP處。這時我們在看 call Xdword ptr ds:[0x46FA0C] API調用指令時其已經將API HOOK繞過直接調用API了。

按正常的規律0x46FA0C應該就是IAT所在處了,但是我么你在內存中查看發現實際API地址會 保存在一個以地址0x46F42A開頭的表中,且API地址以一個字節0間隔,有的還以兩個字節0間隔。正常的IAT是相同的dll函數地址在一起,不同的API地址以雙字節0間隔。所以ImportRE是無法識別此表得,也就是無法重建輸入表。我們需要形成一張規則的IAT表,我們剛剛打補丁將API地址都保存在了0x45C700處了。我們查看此地址發現此表基本符合IAT表的特征,但是其kernel32.dll的函數和ntdll.dll的函數混在了一起,而且各個不同的dll函數沒有用雙字節0間隔,

500彩票邀请码所以我們用OD腳本稍微修正一下,讓其符合標準的IAT的特征。

var        var_begin
var        var_end
var        var_address
var        var_num
var        var_value
var        var_value1

mov        var_begin,0045c704                          //我們存放規則IAT表的地址為0x0045C700
mov        var_end,0045c930                            //kernel32.dll和ntdll.dll沒修正前的結束地址
mov        var_address,0045c704                        //將修正后的kernel32.dll與ntdll.dll存在此地址后
mov        var_num,0

Start:                                                 //Start將kernel32.dll與ntdll.dll分開
cmp        [var_begin + var_num],7c920000
jb         e0
mov        var_value,[var_address]
mov        [var_address],[var_begin + var_num]
mov        [var_begin + var_num],var_value               
add        var_address,4

e0:
add        var_num,4
mov        var_value1,var_begin
add        var_value1,var_num
cmp        var_value1,var_end
ja         Start0                                                
jmp        Start

Start0:                                                //Start0將連續的IAT表不同的DLL函數用雙字節0分開。
mov        var_begin,0045cde0                          //IAT表最后一個API地址所在的IAT地址

Start1:                                                //判斷是否到達不同DLL的交界處
mov        var_num,C        
cmp        var_begin,0045cda4
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045cd64        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045cd60        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045cd58        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045cd4c        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045cd38        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045cd18        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045ccf4        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045cbc0        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045c934        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045c72c        
je         ee0
ja         ee1
dec        var_num
cmp        var_begin,0045c704        
ja         ee1
dec        var_num

ee0:                                                    //如果到達交界處就將交界上一個雙字節寫入0 
mul        var_num,4
mov        [var_begin + var_num  - 4],0
jmp        ee2

ee1:
mul        var_num,4
ee2:
mov        [var_begin + var_num],[var_begin]            //將IAT表項下移
sub        var_begin,4
cmp        var_begin,0045c700                           //判斷是否達到IAT表頭,到達則結束腳本運行
jne        Start1
mov        [var_begin + 4],0

End:
ret

500彩票邀请码修正后的API表如下圖,且符合IAT表的特征。

我們接下來需要將原來call API的指令即call不規則API地址表,變為call我們這個規則IAT表。借助下面腳本實現此功能。

var        var_begin
var        var_end
var        var_IAT
var        var_address1
var        var_address2
var        var_value

mov        var_begin,401000                     //代碼段的起始地址
mov        var_end,44B000                       //代碼段的結束地址
mov        var_IAT,45C700                       //我們修正后的IAT表所在的起始地址

Start:
findop     var_begin,#FF15??#                   //查詢CALL指令的機器碼
mov        var_address1,$RESULT                 //判斷call指令所在地址是否超過代碼段范圍
cmp        var_address1,var_end                 //超出就直接結束腳本運行
ja         End

add        var_address1,2                       //獲取call指令后面的[]中的值
mov        var_value,[var_address1]
mov        var_value,[var_value]
mov        var_begin,var_address1

cmp        var_value,50000000                   //判斷值是否大于0x50000000
ja         e0                                   //大于說明就是API調用,否則就是正常的函數調用
jmp        Start

e0:
mov        var_address2,var_IAT                  
e1:
cmp        [var_address2],var_value             //查詢并判斷IAT表中與對應call調用的API地址相等的值
je         e2
add        var_address2,4
jmp        e1
e2:
mov        [var_address1],var_address2          //查詢到后將call不規則API地址表換為 CALL我們修正后的IAT表
jmp        Start

End:        
ret

查看call API的指令發現,其API地址為我們修正的IAT表中的值。這樣我們就可以用ImportRE工具來獲取IAT并重建輸入表了。

500彩票邀请码接著我們將入口點代碼恢復,然后用LordPE工具dump程序。先修正鏡像大小,然后在完整轉存。

500彩票邀请码然后用ImportRE重建輸入表,輸入OEP的RVA為0x271b0, IAT的RVA為0x5c700。點擊獲取輸入表信息,并截切掉無效的數據后得到完整的IAT,接著修復dump文件。

我們運行修復后的文件發現崩潰,用OD載入程序發現程序會調用0x400178。后面也會有一些call 401000前地址的指令,這是因為殼程序為了防止dump程序,將一部分代碼寫到了PE文件頭的映射處,這樣dump時就無法dump這段程序。

解決Anti dump

我們可以將未脫殼程序的0x400000PE文件頭映射處的0x1000大小的數據拷貝到程序其他地方,然后在程序運行到達入口點前將這部分數據復制到PE文件頭中,這樣還需要將程序的入口點改為實現此功能的代碼的開始處。

在0x44A1AF處打補丁,下面是補丁程序:

0044A191   .  56 69 72 74 75 61 6C 50 72 6F 74 65 63 74 00      ascii "VirtualProtect",0
0044A1A0      00                                                db 00
0044A1A1   .  6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00            ascii "kernel32.dll",0
0044A1AE      00                                                db 00
0044A1AF > $  60                                                pushad
0044A1B0   .  B8 A1A14400                                       mov eax,0x44A1A1                         ;  ASCII "kernel32.dll"
0044A1B5   .  50                                                push eax                                 ; /FileName => "kernel32.dll"
0044A1B6   .  FF15 C8C84500                                     call Xdword ptr ds:[0x45C8C8]            ; \LoadLibraryA
0044A1BC   .  68 91A14400                                       push 0x44A191                            ; /ProcNameOrOrdinal = "VirtualProtect"
0044A1C1   .  50                                                push eax                                 ; |hModule
0044A1C2   .  FF15 CCC84500                                     call Xdword ptr ds:[0x45C8CC]            ; \GetProcAddress
0044A1C8   .  68 81A14400                                       push 0x44A181
0044A1CD   .  6A 40                                             push 0x40
0044A1CF   .  68 00100000                                       push 0x1000
0044A1D4   .  68 00004000                                       push 0x400000
0044A1D9   .  FFD0                                              call Xeax
0044A1DB   .  BB 00000000                                       mov ebx,0x0
0044A1E0   >  8A8B 00D04500                                     mov cl,byte ptr ds:[ebx+0x45D000]
0044A1E6   .  888B 00004000                                     mov byte ptr ds:[ebx+0x400000],cl
0044A1EC   .  43                                                inc ebx
0044A1ED   .  81FB 00100000                                     cmp ebx,0x1000
0044A1F3   .^ 72 EB                                             jb X0044A1E0                             ;  dumped_3.0044A1E0
0044A1F5   .  B8 A1A14400                                       mov eax,0x44A1A1                         ;  ASCII "kernel32.dll"
0044A1FA   .  50                                                push eax                                 ; /FileName => "kernel32.dll"
0044A1FB   .  FF15 C8C84500                                     call Xdword ptr ds:[0x45C8C8]            ; \LoadLibraryA
0044A201   .  68 91A14400                                       push 0x44A191                            ; /ProcNameOrOrdinal = "VirtualProtect"
0044A206   .  50                                                push eax                                 ; |hModule
0044A207   .  FF15 CCC84500                                     call Xdword ptr ds:[0x45C8CC]            ; \GetProcAddress
0044A20D   .  8B1D 81A14400                                     mov ebx,dword ptr ds:[0x44A181]
0044A213   .  68 81A14400                                       push 0x44A181
0044A218   .  53                                                push ebx
0044A219   .  68 00100000                                       push 0x1000
0044A21E   .  68 00004000                                       push 0x400000
0044A223   .  FFD0                                              call Xeax
0044A225      61                                                popad
0044A226    ^ E9 5CCFFDFF                                       jmp 00427187                             ;  dumped_3.00427187

然后我們重新dump并重建輸入表,并且需要將入口點的RVA改為0x4A1AF。得到最終的程序,我們運行發現運行成功。

如果你認為我們已經脫殼成功那么你就錯了,我們剛剛是不兼容的脫殼方式,雖然在脫殼的機器上可以運行,但是放到另外一個機器上就無法運行。在另一個機器上我們用OD分析一下。我們發現其會從那個不規則的函數地址表里獲取API的地址,并調用它。因為我們在dump程序時已經將此表寫死,所以此API地址如果在脫殼機器上運行因其dll每次加載的基地址都相同,所以API的地址也相同因此能正常運行。但是放到另外一個機器上因為dll加載基地址不同了,所以其API的地址也不相同了。導致其call了一個無效的地址。

這是因為我們在脫殼的時候只考慮到了其程序會通過CALL [  不規則地址表 ]的形式調用API,而忽略了其還會通過mov eax,[不規則地址表]    call  eax   。   jmp  [不規則地址表]的形式調用API,導致會存在不兼容的問題。

解決兼容

為了解決兼容性的問題我們需要將不規則表的地址換成我們自己補丁程序的值,然后通過補丁程序來進一步調用我們自己修正的規則IAT表。

我們在將補丁程序放到0x45D000地址處,通過下面的腳本打補丁。

var        var_begin
var        var_address1_1
var        var_address1_2
var        var_address2_1
var        var_num1
var        var_num2
var        var_num3
var        var_value1

mov        var_begin,0045d000                                   //補丁起始地址
mov        var_address1_1,0046f42a                              //不規則API表起始地址
mov        var_address1_2,0046fba8                              //不規則API表結束地址
mov        var_address2_1,0045c700                              //我們修正的規則IAT表起始地址                            
mov        var_num1,0
mov        var_num3,0

Start:                                                            
mov        var_value1,[var_address1_1 + var_num1],1             //因為是不規則API地址表,我們需要定位一個正確的API地址項
cmp        var_value1,0
jne        e
mov        var_value1,[var_address1_1 + var_num1 + 1]
cmp        var_value1,70000000
jb         e
inc        var_num1

e:
mov        var_value1,[var_address1_1 + var_num1]               //獲得不規則API地址表的表項的API地址
mov        var_num2,0
e0:
cmp        [var_address2_1 + var_num2],var_value1               //通過我們修正的IAT中查找相等的API地址,然后得到其IAT表地址
je         e1
add        var_num2,4
jmp        e0

e1:
mov        [var_begin + var_num3],25FF                          //寫入JMP的機器碼25FF
mov        [var_begin + var_num3 + 2],var_address2_1 + var_num2 //寫入對應規則IAT表的對應項
mov        [var_address1_1 + var_num1],var_begin + var_num3     //在不規則API表中寫入我們補丁對應的地址
add        var_num3,6                                           //定位到下一個補丁位置
add        var_num1,5                                           //定位到下一個不規則API地址表項
cmp        var_address1_1 + var_num1,var_address1_2             //判斷是否達到不規則地址表最后一個表項
jne        Start

mov        var_address1_1,00460818
mov        var_address1_2,00460f28
mov        var_num1,0

Start1:                                                         //其除了存在一個不規則的API地址表,實際還存在一個規則的API地址項
mov        var_value1,[var_address1_1 + var_num1]               //下面邏輯和上面相似
cmp        var_value1,01000000
ja         ee
add        var_num1,4
jmp        Start1
ee:
mov        var_num2,0
ee0:
cmp        var_value1,[var_address2_1 + var_num2]
je         ee1
add        var_num2,4
jmp        ee0
ee1:
mov        [var_begin + var_num3],25FF
mov        [var_begin + var_num3 + 2],var_address2_1 + var_num2
mov        [var_address1_1 + var_num1],var_begin + var_num3
add        var_num3,6
add        var_num1,4
cmp        var_address1_1 + var_num1,var_address1_2
jne        Start1        

END:
ret

打完補丁后我們可以看到0x45D000處都變為了jmp[ ]的API調用指令。這樣無論是call [ ]形式的調用,還是mov eax , [ ]    call   eax的形式,還是jmp [ ]的形式都會來到此補丁處,然后通過此補丁調用對應的API。

因為我們把補丁程序放到了這,而我們前面將0x400000PE文件頭防Anti Dump的數據也放到了這,所以我們需要尋找放到其他位置,我放到了0x45F000處。相應的我們前面在代碼入口點打的補丁程序(為了將PE文件頭映射地址數據恢復)也要稍微修改一下。
這樣就可以實現兼容脫殼,現在我們將程序放到新機器上可以正常運行。

總結


這個殼難點在如何解決兼容,也就是容易忽略API的多種調用形式。有時候我們以為脫殼了,而程序放到其他機器無法運行很有可能就是因為這種情況。

加殼程序與脫殼程序.zip

453.66 KB, 下載次數: 20, 下載積分: 吾愛幣 -1 CB

免費評分

參與人數 16威望 +2 吾愛幣 +122 熱心值 +14 收起 理由
tocabd + 1 + 1 熱心回復!
小哥9527 + 1 熱心回復!
晚安說給自己聽 + 1 + 1 我很贊同!
gaosld + 1 + 1 熱心回復!
小朋友呢 + 2 + 1 熱心回復!
fengbolee + 1 + 1 用心討論,共獲提升!
Hmily + 2 + 100 + 1 感謝發布原創作品,吾愛破解論壇因你更精彩!
二娃 + 2 + 1 謝謝@Thanks!
CrazyNut + 3 + 1 用心討論,共獲提升!
antiol + 3 + 1 我很贊同!
wyb1109_2008 + 1 謝謝@Thanks!
笙若 + 1 + 1 謝謝@Thanks!
pack39 + 1 + 1 雖然win殼已日落西山,但你這水平真不是一般的高
FleTime + 3 + 1 歡迎分析討論交流,吾愛破解論壇有你更精彩!
朱朱你墮落了 + 1 + 1 人菜看不懂,純支持一個。
董督秀 + 1 用心討論,共獲提升!

查看全部評分

發帖前要善用論壇搜索功能,那里可能會有你要找的答案或者已經有人發布過相同內容了,請勿重復發帖。

推薦
二娃 發表于 2020-9-14 01:10
首先感謝樓主的分享,在這里提一個小小的疑問,
為啥在修復API調用的時候是正著存GetProcAddress得到的地址然后反著寫回到調用表中?是因為這個殼就是這樣干的嗎?
推薦
 樓主| 鎮北看雪 發表于 2020-9-14 07:03 |樓主
二娃 發表于 2020-9-14 01:10
首先感謝樓主的分享,在這里提一個小小的疑問,
為啥在修復API調用的時候是正著存GetProcAddress得到的地 ...

對,一般都是這么干的
沙發
chen4321 發表于 2020-9-13 06:53
3#
pack39 發表于 2020-9-13 07:13
你這水平,比之前的脫神有過之而無不及。
4#
ekanshao 發表于 2020-9-13 10:00
謝謝樓主分享!好貨!
5#
wyb1109_2008 發表于 2020-9-13 12:34
樓主辛苦了。寫這么多。學習觀摩中
6#
singleboy990624 發表于 2020-9-13 15:56

謝謝樓主分享!好貨
7#
liltn 發表于 2020-9-13 17:42
感謝樓主分享!!
8#
我乃常山趙子龍 發表于 2020-9-13 20:37

樓主辛苦了。寫這么多
9#
InMenory 發表于 2020-9-13 23:34
謝謝樓主分享!好貨
您需要登錄后才可以回帖 登錄 | 注冊[Register]

本版積分規則 警告:本版塊禁止灌水或回復與主題無關內容,違者重罰!

快速回復 收藏帖子 返回列表 搜索

RSS訂閱|小黑屋|處罰記錄|聯系我們|吾愛破解 - LCG - LSG ( )

GMT+8, 2020-10-24 20:20

Powered by Discuz!

500彩票邀请码Copyright © 2001-2020, Tencent Cloud.

快速回復 返回頂部 返回列表

500彩票邀請碼-彩經網