注:本文只討論如何調(diào)試被加殼的ELF文件,包括調(diào)試中的技巧運(yùn)用及調(diào)試過(guò)程中可能遇到的問(wèn)題的解決方法,不包含如何還原加固的DEX。本文將以某加殼程序和某加固為目標(biāo)
ELF全稱:Executable and Linkable Format,是Linux下的一種可執(zhí)行文件格式。 此種文件格式和WINDOWS一樣,常見(jiàn)分為兩種類型:
所有的上述加載流程代碼全部包含在linker.so里,可參看安卓源碼或逆向linker.so
ARMCPU采用RISC(精簡(jiǎn)指令集)架構(gòu)(X86是CISC,復(fù)雜指令集),指令等長(zhǎng),相對(duì)CISC架構(gòu)更加省電,執(zhí)行效率更高
ARM(4字節(jié)等長(zhǎng)),THUMB(2字節(jié)等長(zhǎng)),THUMB2(4字節(jié)等長(zhǎng))。這三種指令集可以在同一執(zhí)行程序中切換,切換原則為:
ARM <–>THUMB,THUMB<–>ARM(PC的最高位確定指令集類型:1為THUMB;0為ARM)
THUMB<–>THUMB2(由27-31位決定)
thumb2其實(shí)就是thumb的擴(kuò)展,其目的是為了一條4字節(jié)指令完成多條2字節(jié)指令
通用寄存器:r0-r15
特殊寄存器:r13 = SP(棧地址), r14 = LR(函數(shù)返回地址), r15 = PC(當(dāng)前指令流地址)
還包括CPSR,APSR,浮點(diǎn)等寄存器,具體可參照ARM指令集手冊(cè)
上面介紹了,加殼的SO的殼代碼都在INIT_ARRAY段和INIT段
那我們先看下一個(gè)被加殼以后的SO,在linux下面用readelf -a命令查看ELF信息
在ELF-HEADER里我們看到有Entry,地址為0x22a8
在動(dòng)態(tài)段里看到了INIT_ARRAY數(shù)組,數(shù)組地址為0x21000,大小是12 BYTES
用IDA看看數(shù)組的內(nèi)容
上面說(shuō)了,-1為無(wú)效,0代表結(jié)束,那INIT有效地址僅是0x2418這個(gè)地址
也就是說(shuō),這個(gè)SO加載起來(lái)以后,會(huì)先從0x2418這個(gè)地址開(kāi)始執(zhí)行,執(zhí)行完成后再去執(zhí)行Entry
我們?cè)賮?lái)看下某加固的ELF信息
在ELF-HEADER里我們看到有Entry,地址為0x3860
DA打開(kāi)某加固的文件時(shí),會(huì)提示錯(cuò)誤,不能打開(kāi),后面在Anti Anti Debugger中會(huì)講到為什么
在動(dòng)態(tài)段里看到了INIT_ARRAY數(shù)組,數(shù)組地址為0x28CA4,大小是8 BYTES
我們還看到了INIT段,地址是0x11401
我們?cè)谶@里,總結(jié)下執(zhí)行的順序:
根據(jù)linker的代碼,當(dāng)INIT段和INIT_ARRAY段都存在的情況下,先運(yùn)行INIT段,再運(yùn)行INIT_ARRAY段,否則單獨(dú)運(yùn)行對(duì)應(yīng)指向的函數(shù),最后執(zhí)行ENTRY
調(diào)試SO和PE_DLL其實(shí)道理是一樣的,都需要一個(gè)宿主進(jìn)程,這里要寫一個(gè)SO_LOADER,參看代碼如下,可通過(guò)NDK編譯。(代碼是王晨同學(xué)早期提供的)
這里選用IDA6.6做為調(diào)試器。
第一步:拷貝調(diào)試器到安卓手機(jī)上
命令:adb push android_server /data/local/tmp/and
這里為什么把a(bǔ)ndroid_server 改名成and呢~~~,其實(shí)就是為了避免被檢測(cè)出調(diào)試器,后面我在Anti Anti Debugger中會(huì)詳細(xì)說(shuō)一下關(guān)于這部分的內(nèi)容
第二步:?jiǎn)?dòng)調(diào)試器
adb shell回車,進(jìn)入/data/local/tmp/目錄,啟動(dòng)調(diào)試器,啟動(dòng)后畫面
第三步:重定向調(diào)試端口
adb forward tcp:23946 tcp:23946
至此手機(jī)端設(shè)置完畢,下面來(lái)看看IDA里如何設(shè)置
IDA加載我們自己寫的so_loader,在854C處,按F2下斷點(diǎn)
選擇菜單欄里面的Debuger-Select Debugger,選擇Remote Arm linux/Andoid debugger
點(diǎn)擊OK,然后F9運(yùn)行
在配置里面,Hostname里面填入127.0.0.1,點(diǎn)擊OK
如果你的手機(jī)里面沒(méi)有這個(gè)文件,會(huì)提示你COPY,點(diǎn)擊確定即可,如果有這個(gè)文件,會(huì)出現(xiàn)下面的選擇,一般選擇USE FOUND就可以了,如果你要調(diào)試的程序有修改,選擇COPY NEW覆蓋一個(gè)新的進(jìn)去
然后一路OK,就出現(xiàn)調(diào)試狀態(tài)了~~,當(dāng)前PC就是剛才我們F2設(shè)置的斷點(diǎn)
上面說(shuō)了,SO的加載在linker.so里完成,我們要做的,就是把斷點(diǎn)設(shè)置在linker.so里面
先找代碼,IDA打開(kāi)linker.so,在string窗口里找
call_constructors_recursive,雙擊并查看引用
雙擊第二個(gè)引用處,然后往上找blx r3(init段的調(diào)用),b.w xxxxxxxx(init_array段的調(diào)用)
好了,現(xiàn)在我們找到了地址0x54d0, 0x3af0這兩個(gè)地方,回到剛才調(diào)試的IDA里面,
選擇菜單欄debugger-Debugger windows-module list打開(kāi)進(jìn)程模塊列表
linker.so的base是40002000,分別對(duì)應(yīng)的兩個(gè)地址就是
0x40002000+0x54d0 = 0x400074d0 0x40002000+0x3af0 = 0x40005af0
我們?cè)贗DA View-PC窗口GO 過(guò)去
在0x74d0處,按C鍵,變成代碼.奇怪,為什么沒(méi)有反應(yīng)!!而且在最下面的output window有如下提示
這里就是我說(shuō)的很重要的問(wèn)題了,上面我提到了,被調(diào)試的程序可以在3種指令集之間切換,這時(shí)的IDA并不知道當(dāng)前要變成代碼的地址是ARM還是THUMB,這時(shí)我們需要對(duì)照靜態(tài)的來(lái)看,或者你對(duì)指令集絕對(duì)的熟悉,看到BYTE CODE就知道是哪種指令集
靜態(tài)中,顯然IDA給的是2字節(jié)指令,那必然是THUMB,我們需要把當(dāng)前地址改成THUMB
方法:按鍵盤的ALT+G,呼喚出窗口
T,DS不用管,我們只需要把VALUE改成1就是THUMB指令集了,變成1點(diǎn)擊OK以后
在原來(lái)的地址上出現(xiàn)了CODE16,這時(shí)我們?cè)偃一次
C完以后,就出現(xiàn)代碼了!!!ARM和THUMB就是這么切換的,切記,切記
再看另外一個(gè)地址,0x400a5af0,按照同樣的方法再來(lái)一次
問(wèn)題又來(lái)了,奇怪了,為什么下面不是指令?!這個(gè)是IDA的BUG,6.6版本對(duì)THUMB2指令在調(diào)試狀態(tài)的解析就是有問(wèn)題。。。。,不過(guò)沒(méi)關(guān)系,我們往下看
這段代碼才是最重要的,執(zhí)行每一個(gè)init_array中地址的函數(shù),就在blx r2這句。 至此,如何斷住INIT段和INIT_ARRAY段,就講完了,剩下的大家就自己調(diào)試吧
其實(shí)這種問(wèn)題,是IDA解析ELF和linker解析ELF不一致造成的,IDA更加嚴(yán)格
用ida打開(kāi)某加固的so,提示
這個(gè)提示就是說(shuō),有個(gè)數(shù)據(jù)描述是無(wú)效的,我們來(lái)看看,是哪個(gè)。
SHT說(shuō)的就是Section Table,來(lái)看看ELF頭部數(shù)據(jù)如下
shoff就是這個(gè)值,我們用16進(jìn)制編輯器過(guò)去看看
全是0,顯然這里有問(wèn)題,我們首先要把這個(gè)值清0,保存文件。
再次加載,還有問(wèn)題,提示如下:
這次通過(guò)調(diào)試IDA的ELF插件,發(fā)現(xiàn)當(dāng)PROGRAM HEADER中的物理偏移大于文件大小時(shí),就會(huì)出現(xiàn)該錯(cuò)誤。
顯然,Program Header中的第一組數(shù)據(jù),p_offset超過(guò)了文件大小,根據(jù)ELF結(jié)構(gòu),定位到該數(shù)據(jù)偏移,改成0,IDA加載成功
通過(guò)調(diào)試該加殼程序,總結(jié)他用到的方法:
方法1:檢測(cè)父進(jìn)程的文件名
調(diào)用getppid,獲取父進(jìn)程的id, open(“/proc/ppid/cmdline”)獲取父進(jìn)程名稱,檢測(cè)常用調(diào)試器的名字,這就是上面我COPY文件時(shí),為啥要把a(bǔ)ndroid_server變成隨意一個(gè)文件名的原因了
對(duì)策:修改getppid的返回值,隨便給一個(gè)可以用的就行了
其實(shí)還有其他方法可以獲取ppid,比如open(“/proc/pid/status”),read這個(gè)handle的內(nèi)容,在里面尋找ppid也行
方法2:異常陷阱
和WINDOWS的方法類似,設(shè)置一個(gè)trap,檢測(cè)調(diào)試器
對(duì)策:IDA默認(rèn)所有的trap都交給調(diào)試器處理,所以我們需要修改對(duì)應(yīng)的設(shè)置。菜單選擇debugger-debugger options,點(diǎn)擊edit exceptions按鈕
在trap上,右鍵編輯改成如下即可
當(dāng)然,檢測(cè)調(diào)試器,還有很多方法,見(jiàn)招拆招就可以,這里就不詳述了