unidbg逆向工程(原理與實踐)_第1頁
unidbg逆向工程(原理與實踐)_第2頁
unidbg逆向工程(原理與實踐)_第3頁
unidbg逆向工程(原理與實踐)_第4頁
unidbg逆向工程(原理與實踐)_第5頁
已閱讀5頁,還剩646頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

Unidbg逆向工程原理與實踐目錄第一部分進(jìn)入unidbg的世界第1章unidbg環(huán)境準(zhǔn)備與快速上手1.1r0env環(huán)境介紹與集成1.2IDEA安裝及配置1.3第一個unidbg項目1.4本章小結(jié)第2章unidbg模擬執(zhí)行初探2.1第一個NDK項目2.2unidbg的符號調(diào)用與地址調(diào)用2.3本章小結(jié)第3章unidbg補(bǔ)環(huán)境、Hook與Patch3.1為so添加交互:使用JNI接口編寫md5方法3.2使用unidbg修補(bǔ)執(zhí)行環(huán)境并模擬執(zhí)行3.3脫離編譯器,使用命令行編譯so3.4unidbg的Hook3.5unidbg的Patch3.6本章小結(jié)第二部分unidbg原理第4章ELF文件執(zhí)行視圖解析4.1ELF文件結(jié)構(gòu)4.2深入jelf代碼細(xì)節(jié),探究ELF解析4.3本章小結(jié)第5章Unicorn的初級使用與初探Linker5.1Unicorn的初級使用:模擬執(zhí)行與Hook5.2初探Android系統(tǒng)源碼5.3本章小結(jié)第6章深入Linker:so的加載、鏈接、初始化6.1so的加載過程6.2so的鏈接過程6.3so的初始化操作6.4本章小結(jié)第7章使用Unicorn模擬Linker:so的加載過程7.1模擬Linker:環(huán)境準(zhǔn)備7.2模擬Linker:so的加載7.3動態(tài)調(diào)試Linker,探究so的內(nèi)存布局圖7.4本章小結(jié)第8章使用Unicorn模擬Linker:so的鏈接過程8.1so的依賴庫加載過程8.2so的動態(tài)鏈接8.3初嘗試:使用unidbg模擬執(zhí)行簡單so文件8.4探究unidbg的Linker代碼細(xì)節(jié)8.5本章小結(jié)第9章R0dbg實戰(zhàn)與Unidbg_FindKey9.1模擬Linker:so的初始化過程9.2指令追蹤與排錯9.3使用R0dbg模擬執(zhí)行so9.4Unidbg_FindKey牛刀小試9.5本章小結(jié)第10章unidbg源碼解析:AndroidEmulator10.1創(chuàng)建AndroidEmulator10.2創(chuàng)建FileSystem10.3創(chuàng)建Backend10.4創(chuàng)建SvcMemory10.5本章小結(jié)第11章unidbg源碼解析:DalvikVM11.1分析createDalvikVM()11.2Dvm相關(guān)類介紹11.3本章小結(jié)第12章unidbg源碼解析:模擬執(zhí)行流程追蹤12.1編寫含JNI交互的MD5算法并模擬執(zhí)行12.2模擬執(zhí)行流程追蹤:尋找函數(shù)12.3模擬執(zhí)行流程追蹤:處理參數(shù)并模擬執(zhí)行12.4本章小結(jié)第13章unidbg源碼解析:JNI交互流程追蹤13.1JNI注冊13.2JNI指令執(zhí)行13.3本章小結(jié)第14章unidbg源碼解析:Memory14.1Memory模塊的創(chuàng)建14.2AndroidElfLoader的方法實現(xiàn)14.3加載so的loader功能14.4本章小結(jié)第15章unidbg源碼解析:Hook15.1unidbg的Hook框架15.2Debugger模塊解析15.3本章小結(jié)第三部分模擬執(zhí)行與補(bǔ)環(huán)境實戰(zhàn)第16章unidbg實戰(zhàn):I/O重定向16.1分析App的內(nèi)部邏輯16.2unidbg模擬執(zhí)行分析16.3本章小結(jié)第17章unidbg實戰(zhàn):Debugger自吐17.1分析App的內(nèi)部邏輯17.2使用unidbg工具進(jìn)行分析17.3本章小結(jié)第18章unidbg實戰(zhàn):指針參數(shù)與Debugger18.1指針參數(shù)的使用18.2快速識別AES算法18.3本章小結(jié)第19章unidbg實戰(zhàn):魔改Base64還原19.1逆向環(huán)境搭建19.2APK分析19.3so文件詳細(xì)分析19.4使用unidbg輔助分析so19.5本章小結(jié)第20章unidbg實戰(zhàn):使用unidbg動態(tài)分析內(nèi)存中的數(shù)據(jù)20.1環(huán)境搭建20.2APK基本分析20.3使用IDA靜態(tài)分析so并使用unidbg動態(tài)驗證20.4本章小結(jié)第21章unidbg實戰(zhàn):使用unidbg主動調(diào)用fork進(jìn)程21.1樣本情景復(fù)現(xiàn)21.2樣本反編譯分析21.3so中代碼的分析21.4使用unidbg對fork進(jìn)程中的函數(shù)做處理21.5本章小結(jié)第22章unidbg補(bǔ)環(huán)境實戰(zhàn):補(bǔ)環(huán)境入門22.1為什么要補(bǔ)環(huán)境22.2unidbg補(bǔ)環(huán)境的案例情景復(fù)現(xiàn)22.3模擬執(zhí)行so22.4本章小結(jié)第23章unidbg補(bǔ)環(huán)境實戰(zhàn):標(biāo)識記錄23.1樣本一:如何補(bǔ)JNI_OnLoad環(huán)境23.2樣本二:文件標(biāo)識的補(bǔ)環(huán)境策略23.3樣本總結(jié)23.4本章小結(jié)第24章unidbg補(bǔ)環(huán)境實戰(zhàn):設(shè)備風(fēng)控24.1Android系統(tǒng)API補(bǔ)全策略24.2目錄獲取24.3樣本最后一個函數(shù)的調(diào)用24.4本章小結(jié)第25章unidbg補(bǔ)環(huán)境實戰(zhàn):補(bǔ)環(huán)境加強(qiáng)25.1上文回顧25.2樣本的框架搭建25.3補(bǔ)環(huán)境實操25.4本章小結(jié)第26章unidbg補(bǔ)環(huán)境實戰(zhàn):總結(jié)26.1補(bǔ)環(huán)境初始化26.2補(bǔ)環(huán)境適用場景26.3補(bǔ)環(huán)境的規(guī)范26.4本章小結(jié)第四部分反制與生產(chǎn)環(huán)境部署第27章Anti-unidbg系列:環(huán)境變量檢測27.1Linux中的環(huán)境變量27.2Android中的環(huán)境變量27.3環(huán)境變量源碼解析27.4unidbg如何設(shè)置環(huán)境變量27.5本章小結(jié)第28章Anti-unidbg系列:xHook檢測28.1xHook的基本使用28.2xHook的原理闡述28.3xHook檢測實現(xiàn)28.4unidbgxHook檢測28.5補(bǔ)充InlineHook檢測28.6本章小結(jié)第29章Anti-unidbg系列:JNI層常見函數(shù)處理29.1FindClass反制策略29.2methodID反制策略29.3本章小結(jié)第30章Anti-unidbg系列:unidbg常規(guī)檢測總結(jié)30.1檢測說明30.2基地址檢測30.3JNI環(huán)境之JNI調(diào)用30.4JNI環(huán)境之類檢測30.5文件描述符30.6uname30.7運行時間檢測30.8檢測Unicorn30.9本章小結(jié)第31章unidbg生產(chǎn)環(huán)境部署31.1SpringBoot框架的基本使用方法31.2SpringBoot和unidbg結(jié)合31.3unidbg-boot-server簡介31.4unidbg-boot-server項目實例31.5本章小結(jié)

第一部分Part1進(jìn)入unidbg的世界■第1章unidbg環(huán)境準(zhǔn)備與快速上手■第2章unidbg模擬執(zhí)行初探■第3章unidbg補(bǔ)環(huán)境、Hook與Patch

Chapter1第1章unidbg環(huán)境準(zhǔn)備與快速上手“工欲善其事,必先利其器?!北菊聦⒔榻B筆者在使用unidbg時的一些環(huán)境配置,包括主機(jī)和測試機(jī)的一些基礎(chǔ)環(huán)境。一個良好的工作系統(tǒng)體系能給工作帶來很多便利,讓大家不必因為環(huán)境問題而焦頭爛額。

1.1r0env環(huán)境介紹與集成r0env是筆者專門為初學(xué)者打造的一個Android逆向工程環(huán)境,使用該環(huán)境可以免去枯燥的、充滿重復(fù)勞動的、高度依賴科學(xué)上網(wǎng)的環(huán)境準(zhǔn)備過程,讓初學(xué)者的逆向工程學(xué)習(xí)變得無比順暢、事半功倍。同時,擁有一個統(tǒng)一的環(huán)境,可以降低溝通成本。在本書中,我們統(tǒng)一使用該環(huán)境。1.1.1r0env各組件介紹對于PC環(huán)境,筆者基于KaliLinux制作了相關(guān)虛擬機(jī)鏡像,并集成了開發(fā)環(huán)境及常用的逆向工具。使用虛擬機(jī)而不是真機(jī),有以下兩個主要原因。其一,虛擬機(jī)自帶“時光機(jī)”功能,可以“時光倒流”。這樣在配置失誤導(dǎo)致環(huán)境崩潰時,可以極為方便地通過歷史快照來恢復(fù)環(huán)境。圖1—1為筆者在日常工作中創(chuàng)建的一些虛擬機(jī)快照。其二,虛擬機(jī)在工作環(huán)境中具有良好的隔離特性,在實驗的過程中不會“污染”真機(jī),是測試全新功能的天然“沙盤”。如VMware具有良好的跨平臺特性,完美支持Windows、macOS和Linux三大主流操作系統(tǒng),可以隨時將學(xué)習(xí)和工作環(huán)境整體打包,在各種環(huán)境中進(jìn)行部署和遷移?,F(xiàn)在筆者就是在將環(huán)境打包分享給大家。圖1—1虛擬機(jī)快照而KaliLinux是基于Debian的Linux發(fā)行版,與Ubuntu師出同門,用于數(shù)字取證操作系統(tǒng)。KaliLinux預(yù)裝了許多滲透測試軟件,包括Metasploit、BurpSuite、sqlmap、Nmap以及CobaltStrike等,是一套開箱即用的專業(yè)滲透測試工具箱。KaliLinux自帶VMware鏡像版本,下載并解壓后雙擊打開.vmx文件即可開機(jī)。后續(xù)不管是Frida工作開發(fā)環(huán)境配置、AndroidStudio安裝、NDK開發(fā)、抓包環(huán)境配置、自制路由器抓包、GDB和LLDB調(diào)試,還是更加復(fù)雜的OLLVM開發(fā)、ART虛擬機(jī)定制開發(fā)、AOSP源碼閱讀或編譯等,都將在KaliLinux中完成。如果使用Windows、macOS系統(tǒng),則將在莫名Bug的定位和消除上耗費大量的時間,而時間成本是很高的。筆者分享的鏡像已經(jīng)做了以下的環(huán)境配置。1.用戶選擇新款KaliLinux虛擬機(jī)的默認(rèn)用戶是Kali。對于專業(yè)的Android逆向人員來說,用root用戶可以更加心無旁騖地專注于工作本身。筆者已經(jīng)重新將root用戶開啟,大家在開機(jī)后在用戶處填寫root,在密碼處填寫toor即可進(jìn)入系統(tǒng)。同時原裝KaliLinux開機(jī)時的時區(qū)是不對的,會影響抓包。筆者已經(jīng)將時區(qū)調(diào)整到東八區(qū),即在聯(lián)網(wǎng)時當(dāng)前時區(qū)會與授時服務(wù)器自動同步,后續(xù)免維護(hù)。2.終端選擇KaliLinux最新版本的默認(rèn)Shell是Zsh,Zsh雖然方便,但它并不兼容且不能運行諸多編譯系統(tǒng)。因此,筆者已經(jīng)將Shell回退到Bash。進(jìn)入虛擬機(jī)后,使用<Ctrl+Shift+T>組合鍵即可打開Bash。3.文件傳輸雖然KaliLinux支持直接將文件拖曳進(jìn)虛擬機(jī),但是VMwaretools默認(rèn)會在/root/.cache/vmware/drag_and_drop目錄下緩存文件,久而久之,緩存文件會非常占空間。如果習(xí)慣使用拖曳的方式傳文件,一定要記得多清理這個目錄。筆者建議使用SSH服務(wù)來傳輸文件。開啟SSH服務(wù)后,可以在虛擬機(jī)外使用FileZilla將文件,比如動輒數(shù)十GB的AOSP源碼文件傳進(jìn)虛擬機(jī)內(nèi),而不會產(chǎn)生任何緩存,輕松高效。使用如下命令開啟SSH服務(wù)(已默認(rèn)開啟):/etc/init.d/sshstart4.科學(xué)工具推薦將科學(xué)上網(wǎng)的工具運行在宿主機(jī)上,打開SOCKS5服務(wù)并允許來自局域網(wǎng)的連接。對于proxychains,需編輯/etc/proxychains4.conf文件,在文件末尾按照如下格式添加宿主機(jī)的IP地址和科學(xué)端口即可:socks51080而對于redsocks,則需編輯/etc/redsocks.conf文件,將ip和port修改為宿主機(jī)的IP地址和科學(xué)上網(wǎng)的端口。也要編輯iptables.sh文件,將不重定向的地址添加為宿主機(jī)的IP地址。ip=;port=1080;proxychains只對單條命令生效。redsocks+iptables對全局有效,重啟后失效。5.Frida開發(fā)環(huán)境對于Frida的版本管理,筆者已經(jīng)安裝好pyenv,在任意目錄下,如果切換Python環(huán)境,則切換了Frida的版本。同時筆者也配置好了Frida的開發(fā)環(huán)境,如支持在任意目錄下編寫JavaScript都有Frida的代碼提示,按住Ctrl后鼠標(biāo)單擊API即可閱讀相應(yīng)源碼,以及按回車鍵補(bǔ)全(代碼塊)等方便的功能。6.開發(fā)、逆向工具等集成筆者也集成了常用的分析工具,運行相關(guān)命令即可打開。r0env中已經(jīng)集成的工具如表1—1所示。表1—1r0env中已經(jīng)集成的工具至于相關(guān)工具的介紹與使用,筆者將會在實戰(zhàn)操作中慢慢講解。1.1.2r0env下載及安裝r0env的虛擬機(jī)的下載地址如下,讀者可任選其一進(jìn)行下載。百度網(wǎng)盤:/s/1anvG0Ol_qICt8u7q5_eQJw;提取碼:3x2a阿里云盤:25:8080/r0env登錄時用戶名:root;密碼:toor下載完所有文件后將其放在同一目錄中,先查看解壓指南.txt,驗證文件的MD5,保證文件在傳輸過程中沒有損壞。在Windows系統(tǒng)下打開cmd命令窗口,驗證命令示例如下:E:\VMware\VirtualMachines>CertUtil-hashfiler0envKaliLinux2021.7z.001MD5MD5的r0envKaliLinux2021.7z.001哈希:e149ad96605844dd307b2e2699df4c3fCertUtil:-hashfile命令成功完成。之后雙擊r0envKaliLinux2021.7z.001文件,使用壓縮工具進(jìn)行解壓。解壓完成后導(dǎo)入VMware:在VMware菜單中依次選擇“文件”→“打開”(快捷鍵<Ctrl+O>),打開文件夾中的vmx文件即可,如圖1—2所示。圖1—2VMware的選擇虛擬機(jī)鏡像界面之后選擇“開啟虛擬機(jī)”來啟動Kali虛擬機(jī)。第一次啟動時,VMware會彈出如圖1—3所示的對話框,這里有移動和復(fù)制兩個選項。如果沒有特殊需要,推薦選擇“我已復(fù)制該虛擬機(jī)(P)”。正常開機(jī)后,在用戶名處輸入root,在密碼處輸入toor,即可進(jìn)入r0envKali。至此,初始開發(fā)環(huán)境配置完成。圖1—3VMware對于配置網(wǎng)絡(luò)功能的提示1.2IDEA安裝及配置IDEA是一個Java編輯器,對Java的支持很好,提供很多Java相關(guān)的插件。由于unidbg是IDEA項目,所以需要下載IDEA工具來進(jìn)行相關(guān)的開發(fā)。首先打開IDEA官網(wǎng)(/zh-cn/idea/download/#section=linux),選擇LinuxUltimate版本進(jìn)行下載。點擊后,會跳轉(zhuǎn)到新的頁面,右擊“直接鏈接”并選擇“復(fù)制鏈接”。在虛擬機(jī)中使用wget命令下載,示例如下:wget/idea/ideaIU-2021.3.3.tar.gz?_gl=1*b2rtzl*_ga*NjM4ODM5MDk5LjE2NDg5Njk5MzM.*_ga_V0XZL7QHEB*MTY0OTMyMjAyNy4xLjEuMTY0OTMyMjQ4My4w&_ga=2.115551046.1167729747.1649322028-638839099.1648969933等待下載完成,過程如圖1—4所示。圖1—4下載IDEA下載的文件名后有一部分網(wǎng)址參數(shù),使用以下命令對文件進(jìn)行重命名并校驗SHA256值。mvideaIU-2021.3.3.tar.gz\?_gl\=1\*b2rtzl\*_ga\*NjM4ODM5MDk5LjE2NDg5Njk5MzM.\*_ga_V0XZL7QHEB\*MTY0OTMyMjAyNy4xLjEuMTY0OTMyMjQ4My4wideaIU-2021.3.3.tar.gzsha256sumideaIU-2021.3.3.tar.gz至此,IDEA下載完成。接著對下載的IDEA文件進(jìn)行解壓,命令如下:tar-zxvfideaIU-2021.3.3.tar.gz解壓后我們就安裝好了IDEA。然后啟動IDEA,命令如下:./idea-IU-213.7172.25/bin/idea.sh進(jìn)入IDEA初始設(shè)置界面。閱讀并同意用戶協(xié)議,登錄JetBrainsAccount賬戶開始試用。IDEA歡迎界面如圖1—5所示。圖1—5IDEA歡迎界面1.3第一個unidbg項目接下來,我們一起運行第一個unidbg項目。1.3.1unidbg介紹unidbg是一個基于Unicorn的逆向工具,可以黑盒調(diào)用Android和iOS中的so文件。這使逆向人員無須了解so內(nèi)部算法原理,只需主動調(diào)用so中的函數(shù),傳入所需的參數(shù),補(bǔ)全運行所需的環(huán)境,即可得到需要的結(jié)果。對于Android逆向來說,unidbg有以下幾個特點:?模擬JNI調(diào)用的API,因而可以調(diào)用JNI_OnLoad函數(shù)。?支持JavaVM和JNIEnv。?支持模擬系統(tǒng)調(diào)用指令。?支持ARM32和ARM64。?支持基于Dobby的InlineHook。?支持基于xHook的GOTHook。?Unicorn后端支持簡單的控制臺調(diào)試器、GDBStub、指令追蹤和內(nèi)存讀寫追蹤。?支持Dynarmic。筆者將會在接下來的內(nèi)容中逐步講解unidbg的特點。1.3.2unidbg下載與運行示例unidbg的下載地址為/zhkl0228/Unidbg,直接使用以下命令即可將它下載到本地。gitclone/zhkl0228/Unidbg.gitCloninginto'Unidbg'...remote:Enumeratingobjects:33604,done.remote:Countingobjects:100%(533/533),done.remote:Compressingobjects:100%(317/317),done.remote:Total33604(delta158),reused394(delta107),pack-reused33071Receivingobjects:100%(33604/33604),552.55MiB|5.82MiB/s,done.Resolvingdeltas:100%(16642/16642),done.Updatingfiles:100%(1334/1334),done.unidbg是一個標(biāo)準(zhǔn)的Java項目,下載完成后,使用IDEA打開unidbg項目文件夾,并在彈出的窗口中選擇相應(yīng)的項目,如圖1—6所示。圖1—6使用IDEA打開unidbg項目當(dāng)?shù)谝淮未蜷_項目時,IDEA會下載一些依賴,慢慢等待下載完成即可。可以通過jnettop命令查看IDEA后臺下載情況,執(zhí)行命令后的結(jié)果如圖1—7所示。圖1—7通過jnettop命令查看IDEA后臺下載情況下載完成后,打開項目根目錄下的unidbg-android/src/test/java/com/kanxue/test2/MainAc-tivity.java文件,單擊main方法左側(cè)的綠三角(快捷鍵<Ctrl+Shift+F10>)來運行該代碼。如果運行成功,則證明unidbg環(huán)境搭建完成,如圖1—8所示。圖1—8unidbg環(huán)境搭建完成1.3.3unidbg示例講解在上一小節(jié)我們成功運行了unidbg的示例代碼,接下來我們根據(jù)代碼的執(zhí)行流程對示例代碼進(jìn)行講解。publicstaticvoidmain(String[]args){//獲取系統(tǒng)當(dāng)前時間longstart=System.currentTimeMillis();//實例化一個MainActivity(即當(dāng)前類)對象MainActivitymainActivity=newMainActivity();//打印當(dāng)前時間與開始時間差值,即實例化MainActivity對象的時間System.out.println("loadoffset="+(System.currentTimeMillis()-start)+"ms");//執(zhí)行MainAcitivty的crack()實例方法mainActivity.crack();}首先看main()方法。其主要工作為實例化MainActivity對象,調(diào)用它的crack()方法,并記錄和打印實例化對象的時間。由上一小節(jié)運行的結(jié)果來看,實例化對象的過程是比較耗時的,但對于逆向安全人員來講,達(dá)成最終目的更重要,這點性能損耗可以忽略。其次看MainActivity的構(gòu)造方法MainActivity()。該構(gòu)造方法通過AndroidEmulatorBuilder構(gòu)建了一個AndroidEmulator實例。其中調(diào)用的for32Bit()方法指定模擬器為32位。addBa-ckendFactory()方法則為模擬器添加一個后端工廠,此處添加的后端工廠為DynarmicFactory。雖然犧牲了一定的特性,但它的執(zhí)行速度比較快,后續(xù)可以根據(jù)情況切換成相應(yīng)的后端工廠。這里傳入的參數(shù)true的含義為“當(dāng)出現(xiàn)問題時,回退到Unicorn后端工廠”。最后調(diào)用build()方法來創(chuàng)建對象。除了DynarmicFactory,unidbg還支持hypervisor、KVM、Unicorn2以及默認(rèn)的Unicorn。后端工廠通常位于項目根目錄下的backend文件夾中,如圖1—9所示。默認(rèn)的Unicorn則位于項目根目錄下的unidbg-api/pom.xml文件中以Maven的方式被引用。然后emulator通過調(diào)用getMemory()方法獲得了Memory對象。Memory接口可以讓我們進(jìn)行一些內(nèi)存相關(guān)的操作。之后,實例化一個Android庫解析器,將SDK版本設(shè)置為23。unidbg提供了兩個SDK版本,分別是19和23,位于項目根目錄下的unidbg-android/src/main/resources/android目錄下。SDK的目錄下有一些運行時依賴庫,用于模擬相關(guān)的環(huán)境,如圖1—10所示。圖1—9unidbg支持的后端工廠圖1—10unidbg支持的SDK同時給Memory接口的對象設(shè)置這個Android庫解析器。通過emulator模擬器對象的createDalvikVM()方法創(chuàng)建了一個vm虛擬機(jī),并通過setVerbose()方法禁止輸出詳細(xì)日志。vm對象的其他個性化配置將會在后文中一一講解。之后使用vm.loadLibrary()方法將so文件加載到內(nèi)存中。該方法需傳入兩個參數(shù):第一個參數(shù)是一個File對象,傳入的地址是基于項目根目錄的路徑;第二個參數(shù)控制是否自動執(zhí)行so文件中的init函數(shù),此處設(shè)置為false,表示不自動執(zhí)行(如果設(shè)置為true,則unidbg會在加載時自動調(diào)用相應(yīng)的init函數(shù))。so文件加載成功后會返回一個DalvikModule對象,可以使用該對象來執(zhí)行callJNI_OnLoad()方法,從而使unidbg執(zhí)行JNI_OnLoad函數(shù)。其中傳入的參數(shù)為模擬器對象。這樣,通過MainActivity的構(gòu)造方法,我們完成了模擬器及so文件運行所需的一些環(huán)境的初始化。接下來便是調(diào)用MainActivity對象的crack()方法了。在crack()方法的第一行,ProxyDvmObject.createObject(vm,this)通過代理Dvm對象創(chuàng)建了一個DvmObject對象,其中:第一個參數(shù)為vm虛擬機(jī);第二個參數(shù)為傳入的this指針,在運行過程中,傳入的是com.kanxue.test2.MainActivity實例對象。這一行得到的obj是一個DvmObject對象,該對象可以通過callJniMethodBoolean()等方法來調(diào)用so文件中JNI的方法,調(diào)用方法的返回值為Boolean類型。這是unidbg中調(diào)用JNI方法的操作。再下面是一個三層循環(huán),使用LETTERS字符表遍歷組成三個字符的字符串。通過obj對象調(diào)用callJniMethodBoolean()方法來執(zhí)行so文件中的jnitest(Ljava/lang/String;)Z函數(shù),通過返回值來判斷是否成功。如果返回值為真,則打印傳入的參數(shù)與所耗時間并退出函數(shù)。callJniMethodBoolean()方法有三個參數(shù):第一個參數(shù)為模擬器實例對象;第二個參數(shù)為so文件中的JNI方法簽名;第三個參數(shù)為函數(shù)的參數(shù)列表,此處為JNI方法所需的參數(shù)字符串。接下來我們將相應(yīng)路徑中的so提取出來,使用IDA打開,在函數(shù)列表中使用組合鍵<Ctrl+F>搜索jnitest命令來找到相應(yīng)的函數(shù),使用快捷鍵F5查看相應(yīng)的偽代碼,如圖1—11所示。不難看出,這個JNI方法添加了OLLVM混淆,會增加逆向人員的分析難度。unidbg允許我們在不需要知道函數(shù)內(nèi)部邏輯及算法細(xì)節(jié)的情況下,對函數(shù)進(jìn)行主動調(diào)用,只需傳入相應(yīng)的參數(shù)即可獲得運行結(jié)果,使函數(shù)可以“為我所用”,從而極大地提高逆向人員的效率。這也是unidbg最重要的應(yīng)用。圖1—11IDA關(guān)于jnitest函數(shù)的偽代碼與此同時,我們可以看到,實際上調(diào)用的JNI方法聲明為int__fastcallJava_com_kanxue_test2_MainActivity_jnitest(JNIEnv*env,jobjectthiz,jstringstr);。在上述實際調(diào)用過程中,我們的方法名只寫了jnitest,且只傳入了最后一個參數(shù)。對于缺失的前兩個參數(shù),unidbg會自動幫助我們完成補(bǔ)全操作。對于JNI方法的函數(shù)名,unidbg也會根據(jù)ProxyDvmObject.createObject(vm,this)這個代理對象傳入?yún)?shù)中的this參數(shù),來解析對應(yīng)的類的全類名,并對JNI方法名進(jìn)行補(bǔ)全操作。這也是為什么MainActivity的包名為com.kanxue.test2,它是與so中JNI方法的方法名相對應(yīng)的。只有相應(yīng)的包名與類名配置正確,unidbg才能正確地執(zhí)行JNI方法。unidbg是一個非常強(qiáng)大的框架。Unicorn只是模擬了一個CPU來執(zhí)行一些匯編指令。而unidbg在Unicorn的基礎(chǔ)上添加了一些so運行所依賴的環(huán)境,會自動幫助我們完成so所需的加載、Linker鏈接等一系列復(fù)雜操作,使我們可以非常方便地調(diào)用so中的JNI方法,獲得我們想要的結(jié)果。1.4本章小結(jié)在本章中,筆者對unidbg的環(huán)境配置、下載安裝,以及第一個unidbg示例的運行與代碼進(jìn)行了細(xì)致的講解,相信大家對unidbg已經(jīng)有了一個簡單的認(rèn)識。在接下來的章節(jié)中,筆者將會繼續(xù)對unidbg的初步使用進(jìn)行講解,帶領(lǐng)大家逐步走進(jìn)unidbg的世界。

Chapter2第2章unidbg模擬執(zhí)行初探由于各個App廠商的安全意識逐漸提高,目前已經(jīng)很少有App廠商將App的關(guān)鍵算法放在Java層面,大部分廠商開始嘗試使用NDK開發(fā),將關(guān)鍵算法放在so層中,并配置一些防護(hù)手段來保護(hù)App不被逆向分析和破解。本章將帶領(lǐng)大家創(chuàng)建一個NDKAndroid項目,通過使用unidbg模擬執(zhí)行自己編寫的so函數(shù)實例,來對unidbg調(diào)用函數(shù)的兩種方式和參數(shù)問題進(jìn)行講解。

2.1第一個NDK項目接下來我們創(chuàng)建第一個NDK項目來完成案例的演示。2.1.1使用AndroidStudio創(chuàng)建NDK項目首先打開AndroidStudio,在菜單欄中依次選擇File→New→NewProject...來打開新建項目向?qū)?,選擇相應(yīng)的項目模板創(chuàng)建NDK項目,如圖2—1所示。之后配置項目屬性,如圖2—2所示,單擊Next按鈕進(jìn)入下一步。在Activity界面選擇默認(rèn)配置即可,直接單擊Finish按鈕,如圖2—3所示。接下來耐心等待Gradle配置完成,界面如圖2—4所示。圖2—1AndroidStudio新建項目選擇模板圖2—2AndroidStudio配置項目屬性圖2—3AndroidStudio配置Activity界面圖2—4AndroidStudioNDK項目界面2.1.2編寫自己的so業(yè)務(wù)代碼首先打開activity_main.xml,添加一個Button控件,如圖2—5所示。編寫MainActivity.java代碼,添加md5()方法并編寫B(tài)utton按鈕的單擊事件來調(diào)用該方法,如圖2—6所示。圖2—5打開activity_main.xml,添加Button控件圖2—6MainActivity.java代碼可以發(fā)現(xiàn)md5()方法名報紅,這是因為編譯器沒有找到方法相應(yīng)的實現(xiàn)。單擊報紅的函數(shù)名,然后使用組合鍵<Alt+Enter>選擇CreateJNIfunctionformd5,如圖2—7所示。AndroidStudio會自動在native-lib.cpp中生成一個md5()方法的空實現(xiàn),如圖2—8所示。圖2—7自動創(chuàng)建JNI函數(shù)圖2—8自動生成的md5()方法空實現(xiàn)在GitHub上找一份md5()方法的相應(yīng)實現(xiàn),代碼鏈接為/pod32g/MD5,將代碼粘貼到上述的空實現(xiàn)處,如圖2—9所示。連接模擬器或者真機(jī)并運行,APK會自動編譯并安裝到目標(biāo)計算機(jī)上。單擊MD5按鈕,TextView的內(nèi)容會改變,如圖2—10所示。經(jīng)過CyberChef的驗證,程序的計算結(jié)果是正確的。接下來我們開始通過unidbg來模擬執(zhí)行自己編寫的so文件中的md5()方法。圖2—9native-lib.cpp代碼圖2—10自編寫App運行界面

2.2unidbg的符號調(diào)用與地址調(diào)用這一節(jié),我們來學(xué)習(xí)unidbg提供的兩種模擬調(diào)用方式:符號調(diào)用和地址調(diào)用。2.2.1unidbg主動調(diào)用前置準(zhǔn)備首先重新編譯一下APK,使它生成全架構(gòu)的so文件,如圖2—11所示。將AndroidStudio左側(cè)項目結(jié)構(gòu)圖切換為Project視圖,然后找到項目中編譯出來的app-debug.apk,如圖2—12所示。圖2—11編譯APK操作圖2—12app-debug.apk目錄接著使用終端進(jìn)入相應(yīng)目錄,使用以下命令解壓APK文件。unzipapp-debug.apk使用IDEA打開之前的unidbg項目,在unidbg-android/src/test/java下依照引用so文件的全類名來編寫相同的類。然后將上面解壓的APK中的lib/arm64-v8a中的so及APK文件都復(fù)制到該目錄下,最終效果如圖2—13所示。圖2—13代碼編寫準(zhǔn)備界面2.2.2unidbg主動調(diào)用so函數(shù)首先編寫MainActivity構(gòu)造方法來模擬so執(zhí)行所需的環(huán)境。由代碼可知,前4行將常用的變量存儲為成員變量,以便在不同的成員函數(shù)間使用。初始化環(huán)境的代碼與1.3.3節(jié)的代碼大致相同。只是在創(chuàng)建vm虛擬機(jī)時,傳入了原本的APK文件,使unidbg可以依據(jù)APK為模擬環(huán)境做一些初始化工作。同時通過加載so文件得到的dalvikModule對象的getModule()方法,得到操作so的對象并將其保存到成員變量中備用。unidbg支持兩種調(diào)用so中函數(shù)的方式:符號調(diào)用和地址調(diào)用。符號調(diào)用方式的代碼示例如下:publicvoidcallMd5(){DvmObjectobj=ProxyDvmObject.createObject(vm,this);Stringdata="dta";DvmObjectdvmObject=obj.callJniMethodObject(emulator,"md5(Ljava/lang/String;)Ljava/lang/String;",data);Stringresult=(String)dvmObject.getValue();System.out.println("[symble]Callthesomd5functionresultis==>"+result);}相關(guān)代碼與1.3.3節(jié)的示例代碼區(qū)別不大。先根據(jù)傳入的this參數(shù)生成一個調(diào)用so的代理對象obj,該處傳入的this參數(shù)即com.dta.lesson2.MainActivity對象,如果全類名無法與原APK中調(diào)用so的java類對應(yīng),則unidbg將無法補(bǔ)全相應(yīng)的函數(shù)名。由于md5()方法的返回值為String類型,所以應(yīng)當(dāng)使用callJniMethodObject()方法來調(diào)用md5()方法,同時使用DvmObject對象來接收該返回值。除了基本類型之外,其余的類型都要返回Object對象,相關(guān)的API如圖2—14所示。圖2—14unidbg支持的callJniMethod相關(guān)的API對于得到的md5()方法的結(jié)果DvmObject對象,我們可以通過調(diào)用它的getValue()方法來獲得相應(yīng)的值。由于我們已知函數(shù)的返回值為String類型,因此可以將其強(qiáng)制轉(zhuǎn)換為String類型,之后將獲得的值輸出。unidbg中另一種常用的函數(shù)調(diào)用方式是地址調(diào)用。一般情況下,我們所需的函數(shù)可能沒有導(dǎo)出,所以無法進(jìn)行符號調(diào)用。地址調(diào)用方式的代碼示例如下:在使用符號調(diào)用方式時,unidbg幫助我們完成了很多操作,例如拼接函數(shù)名、填充參數(shù)等。但在使用地址調(diào)用方式時,這些操作需要我們自己來完成。根據(jù)函數(shù)的原型md5(_JNIEnv*env,jobjectthiz,jstringstr),我們需要手動構(gòu)造參數(shù)列表,補(bǔ)全相應(yīng)參數(shù),才能對相關(guān)函數(shù)進(jìn)行調(diào)用。首先通過getJNIEnv()方法獲取JNIEnv,并使用指針類型來存儲。然后使用與符號調(diào)用相同的方式創(chuàng)建一個調(diào)用so函數(shù)的代理對象,并將其作為所需的第二個參數(shù)jobject。對于第三個參數(shù)需要注意的是,在unidbg中,當(dāng)傳入?yún)?shù)為非指針和Number類型時,都需要先將其定義為DvmObject對象并添加到VM中,才能夠使用該參數(shù)。而對于常用的String類,unidbg對其做了相應(yīng)的封裝,這里新建了一個StringObject對象來作為第三個參數(shù)。之后定義一個List<Object>參數(shù)列表,并將構(gòu)造的三個參數(shù)添加到其中。然后使用module.callFunction()方法來對函數(shù)進(jìn)行調(diào)用,該方法需要三個參數(shù),分別為模擬器對象、函數(shù)的相對偏移地址和參數(shù)數(shù)組。對于函數(shù)偏移的獲取,我們需要使用IDA載入該so函數(shù)以找到相應(yīng)的獲取函數(shù),如圖2—15所示??梢钥吹絤d5()方法的偏移為0x8E80,但由于該匯編指令的格式為thumb,所以需要對函數(shù)地址進(jìn)行加1操作,最終此處填入的參數(shù)的值為0x8E81。對于arm指令與thumb指令,可以通過指令長度來區(qū)分:thumb的指令長度為2字節(jié),而arm指令長度為4字節(jié)。此外,arm指令中是沒有PUSH指令的,這也可以作為區(qū)分的依據(jù)。調(diào)用函數(shù)后獲得的返回值為Number類型,這與函數(shù)的實際返回值String類型是不相符的。不過不用著急,我們可以用兩種方案來處理:對于基本數(shù)值類型或者布爾類型,可以直接通過Number獲取相應(yīng)的值;而對于Object對象來說,Number中實際存儲的是Object對象的hash值,因此需要先通過vm.getObject()方法來獲取DvmObject對象(傳入的參數(shù)為Value(),即Object對象的hash值),之后即可通過getValue()方法來獲取對象中存儲的值,并將其強(qiáng)制轉(zhuǎn)換為String類型后輸出。圖2—15使用IDA獲取函數(shù)偏移main()方法就比較簡單了,僅僅是實例化MainActivity對象后使用了符號調(diào)用和地址調(diào)用這兩個方式來完成so中函數(shù)的調(diào)用。publicstaticvoidmain(String[]args){longstart=System.currentTimeMillis();MainActivitymainActivity=newMainActivity();System.out.println("loadthevm"+(System.currentTimeMillis()-start)+"ms");mainActivity.callMd5();mainActivity.call_address();}輸出如下,可以發(fā)現(xiàn)函數(shù)的輸出是正確計算出來的。Pickedup_JAVA_OPTIONS:-Dawt.useSystemAAFontSettings=on-Dswing.aatext=trueloadthevm16020ms[symble]Callthesomd5functionresultis==>36072180305f072a2e2c7ea96eedf034[addr]Callthesomd5functionresultis==>36072180305f072a2e2c7ea96eedf034Processfinishedwithexitcode02.2.3unidbg部分API簡單講解在上述代碼中,我們接觸到幾個常用的接口,如AndroidEmulator、Memory、VM等。AndroidEmulator是一個抽象的Android模擬器接口。它作為一個樞紐,協(xié)調(diào)unidbg中大多數(shù)模塊的工作,至關(guān)重要。我們的大多數(shù)操作需要借助它完成。而AndroidEmulatorBuilder可以幫助我們快速創(chuàng)建AndroidEmulator實例。例如:AndroidEmulatoremulator=AndroidEmulatorBuilder//指定32位處理器.for32Bit()//指定64位處理器//.for64bit()//添加后端工廠.addBackendFactory(newDynarmicFactory(true))//指定進(jìn)程名,推薦以Android包名作為進(jìn)程名.setProcessName("com.github.unidbg")//設(shè)置根路徑,此路徑相當(dāng)于Android中的根目錄.setRootDir(newFile("target/rootfs/default"))//生成AndroidEmulator實例.build();我們也可以通過AndroidEmulator實例來獲取內(nèi)存操作接口Memory實例:Memorymemory=emulator.getMemory();創(chuàng)建DalvikVM虛擬機(jī)://創(chuàng)建虛擬機(jī)VMdalvikVM=emulator.createDalvikVM();//創(chuàng)建虛擬機(jī)并指定APK文件VMdalvikVM=emulator.createDalvikVM(newFile("apkfilepath"));Memory內(nèi)存接口主要提供內(nèi)存管理和ELF文件加載兩個功能。對于ELF文件的加載,我們一般用加載APK的形式實現(xiàn),因為unidbg會幫助我們進(jìn)行一些解析處理,可以節(jié)省很多時間。Modulemodule=emulator.loadLibrary(newFile("filepath"),true);而VM對象的主要作用就是代理一套JNI,幫助用戶借助JNI來操作Java層。VM的實際功能是調(diào)用JNI_OnLoad函數(shù)。某些JNI方法在JNI_OnLoad函數(shù)進(jìn)行動態(tài)綁定時需調(diào)用此函數(shù)才能夠調(diào)用JNI方法,所以這里推薦在加載模塊后再執(zhí)行此方法。//參數(shù)一:模擬器實例//參數(shù)二:要執(zhí)行JNI_OnLoad函數(shù)的模塊dalvikVM.callJNI_OnLoad(emulator,module);或者控制JNI交互的詳細(xì)日志輸出://設(shè)置是否輸出JNI運行日志vm.setVerbose(true);對于CallMethod系列的方法,第二個參數(shù)是方法簽名,我們只需傳入部分名稱即可,這是因為unidbg在底層邏輯中進(jìn)行了相應(yīng)的處理。對于上面的項目,按住Ctrl后單擊callJniMethodObject()方法,可以跟進(jìn)相應(yīng)的實現(xiàn),之后跟進(jìn)其內(nèi)部的callJniMethod()方法,再跟進(jìn)findNativeFunction()方法,可以查看unidbg找到相應(yīng)函數(shù)的過程,如圖2—16所示。圖2—16unidbgfindNativeFunction()實現(xiàn)可以看到,在第239行unidbg根據(jù)對傳入對象的全類名與函數(shù)名進(jìn)行拼接來得到最終的函數(shù)名。而使用callFunction()方法時需要傳入?yún)?shù),除了基本數(shù)據(jù)類型之外,其余數(shù)據(jù)類型都要封裝為DvmObject并傳入VM虛擬機(jī)后才能夠使用,具體后文會有詳細(xì)的案例。2.3本章小結(jié)在本章中,筆者首先帶領(lǐng)大家編寫了一個自己的NDK項目,并使用unidbg提供的兩種模擬調(diào)用方式(符號調(diào)用和地址調(diào)用)對自編寫的so進(jìn)行了模擬執(zhí)行,最后對實驗中涉及的API進(jìn)行了簡要講解。相信學(xué)習(xí)完本章后,大家已經(jīng)對unidbg的使用方式有了簡單了解,至于更多的用法,會在后續(xù)章節(jié)中慢慢探討。

Chapter3第3章unidbg補(bǔ)環(huán)境、Hook與Patch在上一章中,我們對unidbg的模擬執(zhí)行進(jìn)行了簡單的介紹,但自編寫的so過于簡單,并沒有太多地體現(xiàn)出真實環(huán)境中復(fù)雜的情況。在本章中,我們將為自編寫的so添加與JNI交互的功能,使用unidbg為它補(bǔ)全JNI環(huán)境并模擬執(zhí)行,還會對其中遇到的問題和解決思路進(jìn)行探討。在模擬執(zhí)行的基礎(chǔ)上,為了應(yīng)對真實環(huán)境中的校驗等程序邏輯,本章對Hook框架的使用和如何進(jìn)行內(nèi)存Patch(打補(bǔ)?。┮策M(jìn)行了初步講解。

3.1為so添加交互:使用JNI接口編寫md5方法在編寫調(diào)用JNI接口實現(xiàn)md5方法之前,我們先用Java實現(xiàn)md5方法,以便作為示例來指導(dǎo)編寫。相關(guān)代碼如下所示。編譯好后運行,運行結(jié)果無誤。之后我們依靠Java版示例,定義本地方法md52(),并使用<Alt+Enter>快捷鍵讓AndroidStudio幫我們創(chuàng)建相應(yīng)函數(shù)的空實現(xiàn)。依照J(rèn)ava相關(guān)代碼,使用JNI接口調(diào)用Java層的MessageDigest等類的方法完成相關(guān)方法的調(diào)用,最后代碼如下所示。這是根據(jù)Java代碼的流程使用JNI接口仿寫了一遍。JNI接口代碼的編寫流程與Java的反射類似,通過FindClass()方法找到類,通過GetStaticMethodID()等方法獲得方法的jmethodID,然后通過CallStaticObjectMethod()等系列方法來執(zhí)行方法得到結(jié)果。3.2使用unidbg修補(bǔ)執(zhí)行環(huán)境并模擬執(zhí)行首先將編寫的unidbg代碼MainActivity.java復(fù)制到同包名目錄下,改名為MainJni.java,我們將在此文件中編寫響應(yīng)代碼。同時,將上述Android項目編譯好的APK解壓,取得armeabi-v7a下的so文件,復(fù)制到同目錄中,并重命名為libjni.so。接下來簡單修改一下源代碼,如下所示:這里僅僅是修改了加載so的名稱,模擬執(zhí)行函數(shù)的函數(shù)名、函數(shù)簽名以及傳入的參數(shù)。修改完后嘗試運行,收到如圖3—1所示的報錯消息。圖3—1尋找md52()方法失敗報錯這是因為我們修改了類文件的文件名,而上述代碼傳入了該類實例來當(dāng)作執(zhí)行so中函數(shù)的java對象,導(dǎo)致unidbg根據(jù)傳入的類實例處理后的函數(shù)名無法找到需要調(diào)用的Java_com_dta_lesson2_MainActivity_md52()方法。可以找到2.2.3節(jié)中API的內(nèi)部實現(xiàn),下斷點進(jìn)行調(diào)試,查看處理后得到的最終的函數(shù)名,如圖3—2所示。圖3—2調(diào)試查看symbolName由于我們修改了執(zhí)行類的類名,導(dǎo)致傳入該實例后,拼接后的函數(shù)名為Java_com_dta_lesson2_MainJni_md52,這與我們想要調(diào)用的函數(shù)名是不相符的,自然找不到對應(yīng)的函數(shù)。所以需要將上述代碼修改為如下代碼:DvmObjectobj=vm.resolveClass("com/dta/lesson2/MainActivity").newObject(null);首先使用vm.resolveClass()方法,通過傳入一個類名字符串的方式來構(gòu)建一個class,然后使用newObject()方法來實例化對象,這樣unidbg便可以通過該對象來獲得正確的函數(shù)名。再次運行,仍然報錯,如圖3—3所示。圖3—3未設(shè)置setJni()方法報錯由于模擬執(zhí)行的函數(shù)中有JNI操作,而不同的so調(diào)用的JNI方法有所不同,unidbg無法為其一一實現(xiàn),只實現(xiàn)了部分常用的JNI接口,缺失的部分則需要讀者自己來實現(xiàn)。因此這里提示我們需要設(shè)置vm.setJni()來實現(xiàn)缺失的JNI接口部分。具體做法是:我們需要調(diào)用setJni()方法,讓MainJni繼承AbstractJni類,并將MainJni類實例當(dāng)作參數(shù)傳入setJni()方法中。當(dāng)我們進(jìn)入AbstractJni類時,可以發(fā)現(xiàn)unidbg已經(jīng)實現(xiàn)了常用的JNI操作,如圖3—4所示。圖3—4AbstractJni相關(guān)實現(xiàn)修改代碼,如圖3—5所示。圖圖圖3—5修改代碼修改后運行,發(fā)現(xiàn)還是報錯,如圖3—6所示。圖3—6沒有MessageDigest.update()實現(xiàn)雖然我們繼承了AbstractJni類,并為VM虛擬機(jī)設(shè)置了JNI接口,但由于AbstractJni并沒有相應(yīng)MessageDigest.update()的實現(xiàn),所以報錯。MessageDigest.update()是代碼中調(diào)用的第二個JNI接口,第一個調(diào)用JNI接口的方法為MessageDigest.getInstance(),為什么它沒有報錯呢?我們嘗試在AbstractJni中搜索MessageDigest->getInstance,發(fā)現(xiàn)unidbg已經(jīng)對其做了實現(xiàn),如圖3—7所示。而unidbg對于update()方法則沒有做實現(xiàn),因此會報java.lang.UnsupportedOperationException錯誤,如圖3—8所示。我們需要重寫報錯的callVoidMethodV()方法,為unidbg補(bǔ)全缺失的update()方法的具體實現(xiàn),使之可以繼續(xù)執(zhí)行。這個補(bǔ)齊JNI的過程叫作“補(bǔ)環(huán)境”。在MainJni中編寫以下代碼:圖3—7AbstractJni實現(xiàn)MessageDigest->getInstance3—8缺失update()方法報錯publicvoidcallVoidMethodV(BaseVMvm,DvmObject<?>dvmObject,Stringsignature,VaListvaList){if(signature.equals("java/security/MessageDigest->update([B)V")){MessageDigestmessageDigest=(MessageDigest)dvmObject.getValue();intintArg=vaList.getIntArg(0);Objectobject=vm.getObject(intArg).getValue();messageDigest.update((byte[])object);return;}super.callVoidMethodV(vm,dvmObject,signature,vaList);}當(dāng)執(zhí)行到該方法時,參數(shù)vm為虛擬機(jī),dvmObject為調(diào)用該函數(shù)的對象,signature為函數(shù)的簽名,vaList為函數(shù)的參數(shù)列表。在調(diào)用callVoidMethodV函數(shù)前先判斷函數(shù)的簽名是否與update()方法相匹配,該處填寫的簽名為報錯信息給出的簽名,如果匹配則執(zhí)行補(bǔ)齊的自實現(xiàn)代碼。在執(zhí)行messageDigest.update()方法時,我們首先要取得MessageDigest對象,如我們已知的<?dvmObject就是調(diào)用方法的對象,因此可以使用dvmObject.getValue()方法來獲得MessageDigest對象,由于我們已經(jīng)知道它的類型,所以可以將它強(qiáng)制類型轉(zhuǎn)換為MessageDigest類型。接下來便是取參數(shù)了。通過vaList.getIntArg()方法,傳入下標(biāo),我們便能取得相應(yīng)的參數(shù)。需要注意的是,由于update()方法的參數(shù)類型為byte[],而在JNI中沒有對象的概念,因此unidbg為非基本類型維護(hù)了一個Map引用,如圖3—9所示。通過getIntArg()方法取得的只是Map中的key值,還需要通過vm.getObject()方法從VM虛擬機(jī)中取得該對象,再通過.getValue()方法取得實際對象的值。3—9unidbg維護(hù)的Map引用之后通過調(diào)用messageDigest.update()方法,完成對JNI環(huán)境的修補(bǔ)。修補(bǔ)完update()方法后繼續(xù)運行,收到缺失digest()方法的報錯提示,如圖3—10所示。圖3—10缺失digest()方法報錯根據(jù)報錯的調(diào)用棧,重寫callObjectMethodV()方法,代碼如下:publicDvmObject<?>callObjectMethodV(BaseVMvm,DvmObject<?>dvmObject,Stringsignature,VaListvaList){if(signature.equals("java/security/MessageDigest->digest()[B")){MessageDigestmessageDigest=(MessageDigest)dvmObject.getValue();byte[]digest=messageDigest.digest();DvmObject<?>object=ProxyDvmObject.createObject(vm,digest);vm.addLocalObject(object);returnobject;}returnsuper.callObjectMethodV(vm,dvmObject,signature,vaList);}步驟與之前大致相同,只是多了一個將運算得到的結(jié)果digest先代理創(chuàng)建為DvmObject對象,再添加到VM虛擬機(jī)的操作。所有的非基本類型、包裝類型都需要添加到VM虛擬機(jī)的Map映射中,否則在JNI中無法找到該引用。最后將運算得到的結(jié)果當(dāng)作函數(shù)返回值返回。再次運行,繼續(xù)報錯,提示找不到自編寫的byte2Hex()方法,如圖3—11所示。繼續(xù)根據(jù)報錯信息來補(bǔ)全JNI環(huán)境。圖3—11缺失byte2Hex()方法報錯補(bǔ)全后的byte2Hex()方法代碼如圖3—12所示,這里只是直接調(diào)用了編寫好的byte2Hex()方法,并將結(jié)果轉(zhuǎn)換為StringObject對象并添加到VM虛擬機(jī)中。圖3—12補(bǔ)全后的byte2Hex()方法代碼再次運行,發(fā)現(xiàn)成功得到正確的結(jié)果,如圖3—13所示。圖3—13成功模擬執(zhí)行JNI函數(shù)雖然修補(bǔ)環(huán)境的工作較為煩瑣,但相較于復(fù)雜的so中的算法流程而言,使用unidbg的好處不言而喻。

3.3脫離編譯器,使用命令行編譯so由于在unidbg的學(xué)習(xí)過程中,我們會頻繁地編譯so文件來進(jìn)行測試。如果每次都使用AndroidStudio來編譯,那么需要編譯器做一些除編譯so以外的編譯與打包APK的操作,也需要我們手動提取出APK中的so文件。為了提高效率,我們學(xué)習(xí)一下使用命令行編譯so文件的方法。首先打開IDEA,在lesson2包的同級目錄中創(chuàng)建lesson5包,并將MainActivity.java代碼復(fù)制進(jìn)去。在lesson5下創(chuàng)建build文件夾,用于存放編譯so的源代碼和配置文件。打開AndroidStudio,將src/main/cpp目錄下的native-lib.cpp與CMakeLists.txt文件放到lesson5/build目錄下,如圖3—14所示。圖3—14lesson5項目結(jié)構(gòu)由于使用cmake進(jìn)行編譯,因此我們還需要將AndroidStudio中的cmake添加到環(huán)境變量中。AndroidStudioSDK中的cmake路徑為/root/Android/Sdk/cmake/988404/bin,其中的版本號需要根據(jù)實際情況進(jìn)行修改。在~/.bashrc文件末尾添加如下路徑:PATH=$PATH:/root/Android/Sdk/cmake/988404/bin;exportPATH;重啟終端,如果發(fā)現(xiàn)cmake命令已經(jīng)可以在任意目錄中使用,則配置成功。對于編譯腳本的配置,Google官方已在文檔中編寫了示例,地址為https:///studio/projects/configure-cmake#call-cmake-cli,讀者可自行參考。在build目錄下創(chuàng)建build.sh文件,將示例復(fù)制到文件中,并修改如下路徑:還需修改CMakeLists.txt文件,添加如下配置:#配置生成so文件目標(biāo)目錄為項目根目錄的上一層(即build目錄的上級目錄)set(CMAKE_LIBRARY_OUTPUT_DIRECTORY${PROJECT_SOURCE_DIR}/../)#配置構(gòu)建類型為Releaseset(CMAKE_BUILD_TYPE"Release")#指定C和C++不輸出調(diào)試信息set(CMAKE_C_FLAGS_RELEASE"${CMAKE_C_FLAGS_RELEASE}-s")set(CMAKE_CXX_FLAGS_RELEASE"${CMAKE_CXX_FLAGS_RELEASE}-s")個性化修改并刪除所有注釋后,so編譯相關(guān)配置如圖3—15所示。圖3—15so編譯相關(guān)配置運行build.sh腳本,如果在build的同級目錄下生成libnative-lib.so,則命令行編譯成功,如圖3—16所示。圖3—16命令行編譯成功3.4unidbg的Hookunidbg支持多種Hook框架,如HookZz、Dobby、xHook、whale等。此處我們使用HookZz來完成對32位so程序的Hook。首先修改build/native-lib.cpp文件,如下所示,添加add函數(shù)來完成絕對值相加的操作。接著重新編寫MainActivity.java代碼,并模擬執(zhí)行add()方法,如圖3—17所示。圖3—17模擬執(zhí)行add()方法成功模擬執(zhí)行add()方法后,接下來在MainActivity.java中編寫hook()方法,如圖3—18所示。圖3—18hook()方法首先通過HookZz.getInstance()方法獲取到HookZz實例,然后調(diào)用它的replace()方法。replace()方法的第一個參數(shù)是需要被Hook的函數(shù)的首地址,可以通過IDA來查看,如圖3—19所示。由于這里使用thumb指令集,因此需要對地址進(jìn)行+1操作。圖3—19IDA查看add函數(shù)地址第二個參數(shù)是ReplaceCallback回調(diào)類,通過新建一個匿名內(nèi)部類來傳入。ReplaceCallback類有三個方法可供重寫,其中兩個onCall()方法是在函數(shù)執(zhí)行之前執(zhí)行,postCall()方法是在函數(shù)執(zhí)行結(jié)束之后執(zhí)行。postCall()方法默認(rèn)是不執(zhí)行的,需要配置第三個參數(shù)enablePostCall為true來啟用。onCall()有兩個可重寫的方法,區(qū)別在于是否帶有參數(shù)context,該參數(shù)可用于讀寫寄存器相關(guān)內(nèi)容。在main()方法的“mainActivity.callAdd();”前調(diào)用該hook方法進(jìn)行Hook,運行,可以觀察到相應(yīng)函數(shù)的Hook的執(zhí)行流程,如圖3—20所示。圖3—20Hook的執(zhí)行流程對于onCall()方法,可以在函數(shù)執(zhí)行前獲取和修改函數(shù)的參數(shù),如圖3—21所示。對于postCall()方法,可以借助后端工廠來進(jìn)行寄存器的寫入操作,以達(dá)到修改返回值的目的,如圖3—22所示。圖3—21調(diào)用onCall()方法獲取和修改函數(shù)的參數(shù)圖3—22調(diào)用postCall()方法修改返回值

3.5unidbg的Patch如果想要在unidbg中修改程序的邏輯代碼,那么可以利用Patch技術(shù)。接下來將圖3—19中地址為0x3E8的ADDS指令修改為SUBS指令,也就是將原函數(shù)的絕對值相加功能改為絕對值相減功能??梢酝ㄟ^/來將匯編指令轉(zhuǎn)換為機(jī)器碼,得到D01A,如圖3—23所示。在MainActivity.java中添加patch()方法。圖3—23將匯編指令轉(zhuǎn)換為機(jī)器碼publicvoidpatch(){//module.base+0x3E8得到內(nèi)存中相應(yīng)的地址,獲得指向該地址的指針UnidbgPointerpointer=UnidbgPointer.pointer(emulator,module.base+0x3E8);byte[]code=newbyte[]{(byte)0xd0,0x1a};pointer.write(code);}在unidbg中,可以借助UnidbgPointer封裝的API來讀寫內(nèi)存,也可以通過傳入地址來獲得一個指針,進(jìn)而對該內(nèi)存進(jìn)行讀寫操作。此處便通過指針來將相應(yīng)的機(jī)器碼寫入內(nèi)存,達(dá)到Patch的效果。通過UnidbgPointer.write()寫入兩個指令字節(jié),將ADDS指令修改為SUBS指令。在main()方法中添加mainActivity.patch()方法調(diào)用,運行成功后的結(jié)果如圖3—24所示,值變?yōu)榱?1。圖3—24通過機(jī)器碼Patch運行結(jié)果但是,每次手動將匯編指令轉(zhuǎn)換為機(jī)器碼比較麻煩,可以借助Keystone來將匯編指令自動轉(zhuǎn)換為機(jī)器碼,再進(jìn)行Patch。編寫patch2()方法,代碼如下:publicvoidpatch2(){UnidbgPointerpointer=UnidbgPointer.pointer(emulator,module.base+0x3E8);//指定架構(gòu)和模式實例化一個Keystone對象Keystonekeystone=newKeystone(KeystoneArchitecture.Arm,KeystoneMode.ArmThumb);//要修改的匯編指令Strings="subsr0,r2,r3";//獲得機(jī)器碼字節(jié)數(shù)組byte[]machineCode=keystone.assemble(s).getMachineCode();pointer.write(machineCode);}該代碼只是將人工轉(zhuǎn)換機(jī)器碼的工作交給了Keystone來做。首先實例化一個keystone對象,傳入架構(gòu)為Arm、模式為ArmThumb。然后使用keystone.assemble(s).getMachineCode()將匯編指令轉(zhuǎn)換為機(jī)器碼字節(jié)數(shù)組來進(jìn)行Patch操作。在main()方法中調(diào)用上述方法,運行結(jié)果如圖3—25所示。圖3—25通過匯編指令Patch運行結(jié)果3.6本章小結(jié)本章首先對自編寫的so文件添加了JNI方法,然后使用unidbg補(bǔ)全缺失的JNI環(huán)境,并成功模擬執(zhí)行。對于補(bǔ)環(huán)境,核心思想就是“缺哪補(bǔ)哪”,反復(fù)運行查看報錯信息,并根據(jù)報錯信息來補(bǔ)充相應(yīng)的缺失JNI函數(shù)的實現(xiàn)代碼,直到程序可以成功運行為止。接著為了學(xué)習(xí)更加方便,探討了如何脫離編譯器來手動編譯so文件。再然后對unidbg支持的幾種Hook框架進(jìn)行了簡單介紹,并使用HookZz框架來對so文件進(jìn)行Hook操作。為了滿足對程序的執(zhí)行邏輯進(jìn)行修改的需求,本章演示了如何使用UnidbgPointer來對內(nèi)存中的代碼進(jìn)行Patch操作。至此,unidbg的基礎(chǔ)操作已經(jīng)介紹完成,在接下來的章節(jié)中,我們會對unidbg的原理以及高級應(yīng)用進(jìn)行深入講解。

第二部分Part2unidbg原理■第4章ELF文件執(zhí)行視圖解析■第5章Unicorn的初級使用與初探Linker■第6章深入Linker:so的加載、鏈接、初始化■第7章使用Unicorn模擬Linker:so的加載過程■第8章使用Unicorn模擬Linker:so的鏈接過程■第9章R0dbg實戰(zhàn)與Unidbg_FindKey■第10章unidbg源碼解析:AndroidEmulator■第11章unidbg源碼解析:DalvikVM■第12章unidbg源碼解析:模擬執(zhí)行流程追蹤■第13章unidbg源碼解析:JNI交互流程追蹤■第14章unidbg源碼解析:Memory■第15章unidbg源碼解析:HookChapter4第4章ELF文件執(zhí)行視圖解析在之前的學(xué)習(xí)中,大家已經(jīng)掌握了unidbg的初級使用。在接下來的章節(jié)中,我們將重點學(xué)習(xí)關(guān)于unidbg原理的內(nèi)容。本章將以執(zhí)行視圖來介紹ELF的文件結(jié)構(gòu),并深入代碼來分析ELF的解析過程,從而學(xué)習(xí)unidbg是如何加載并運行so文件的前置文件結(jié)構(gòu)知識。

4.1ELF文件結(jié)構(gòu)ELF全稱為ExecutableandLinkingFormat(可執(zhí)行鏈接格式)。在Linux系統(tǒng)中,ELF文件有三種類型,不同類型文件的格式不同,可以通過file命令來進(jìn)行查看。?可重定位文件(RelocatableFile):后綴名一般為.o,包含代碼和數(shù)據(jù),可以與其他可重定位目標(biāo)文件合并起來創(chuàng)建一個可執(zhí)行目標(biāo)文件。?可執(zhí)行文件(ExecutableFile):后綴名一般為.out,是靜態(tài)鏈接的可執(zhí)行文件,包含二進(jìn)制代碼和數(shù)據(jù),可直接復(fù)制到內(nèi)存中執(zhí)行。?共享目標(biāo)文件(SharedObjectFile):后綴名一般為.so,包含可在兩種上下文中鏈接的代碼和數(shù)據(jù),鏈接編輯器可以將它和其他可重定位文件和共享目標(biāo)文件一起處理,生成另一個目標(biāo)文件;此外,在加載或者運行時動態(tài)鏈接器(DynamicLinker)可以將它動態(tài)載入內(nèi)存并鏈接,與某個可執(zhí)行文件以及其他共享目標(biāo)一起組合創(chuàng)建進(jìn)程映像。在Linux中并不以后綴名作為區(qū)分文件格式的絕對標(biāo)準(zhǔn),所以后綴名可有可無,具體文件格式可使用file命令進(jìn)行查看,如圖4—1所示。圖4—1使用file命令查看libnative-lib.so的文件格式ELF文件有兩種視圖:執(zhí)行視圖和鏈接視圖。對于執(zhí)行視圖,主要使用程序頭部表來將它載入內(nèi)存;對于鏈接視圖,則需要根據(jù)節(jié)區(qū)頭部表中的信息來完成鏈接操作。ELF文件的兩種視圖如圖4—2所示。ELF文件開始處是ELF頭部(ELFHeader),用于描述整個文件的組織。?程序頭部表(ProgramHeaderTable)用于告訴系統(tǒng)如何創(chuàng)建進(jìn)程映像。圖4—2ELF文件的兩種視圖用于構(gòu)造目標(biāo)映像的目標(biāo)文件必須具有程序頭部表,可重定位文件并不需要該表。?節(jié)區(qū)頭部表(SectionHeaderTable)包含了描述文件節(jié)區(qū)的信息,用于鏈接的目標(biāo)文件必須包含節(jié)區(qū)頭部表,其他目標(biāo)文件則不做要求。對于共享目標(biāo)文件,兩種視圖都必須存在。因為鏈接編輯器在鏈接的時候需要節(jié)區(qū)頭部表來查看信息從而對各個目標(biāo)文件進(jìn)行鏈接,而加載器需要根據(jù)程序頭部表把相應(yīng)的段加載到進(jìn)程的虛擬內(nèi)存中。本章更注重以執(zhí)行視圖的角度分析ELF頭部以及程序頭部表,節(jié)區(qū)頭部表在之后會繼續(xù)探討。4.1.1ELF頭部結(jié)構(gòu)查看Linux系統(tǒng)中的/usr/include/elf.h文件,可以找到ELF頭部的結(jié)構(gòu)定義。使用010Editor打開libnative-lib.so,在TemplateRepository中安裝ELF.bt模板,查看ELF頭部,如圖4—3所示。圖4—3使用010Editor查看ELF頭部也可以使用readelf相關(guān)命令來查看ELF頭部,如圖4—4所示。圖4—4使用readelf相關(guān)命令來查看ELF頭部其中,e_ident數(shù)組給出了ELF的一些標(biāo)識信息。?前4字節(jié)是魔數(shù)(Magicnumber),固定為7F“ELF”。?ei_class:標(biāo)識文件的類別,此處為ELFCLASS32,即32位目標(biāo)。?di_data:指明數(shù)據(jù)的編碼格式,此處LSB為小端序。?ei_version:版本號碼,一般為0x1,表示E_CURRENT當(dāng)前版本。解析完e_ident數(shù)組,繼續(xù)查看其他字段。?e_type:標(biāo)識目標(biāo)文件的類型,此處為ET_DYN(3),即共享目標(biāo)文件。?e_machine:標(biāo)識文件的目標(biāo)體系結(jié)構(gòu)類型,表明可以在哪種平臺上運行,此處為EM_ARM(40),表示它為ARM架構(gòu)。?e_version:標(biāo)識目標(biāo)文件版本,一般為EV_CURRENT(1)。?e_entry:標(biāo)識程序入口的虛擬地址,類似C語言中的main函數(shù)入口地址,但so文件是共享目標(biāo)文件,沒有程序入口點,此值可忽略。?e_phoff:程序頭部表格在文件中的字節(jié)偏移量,可根據(jù)該偏移量找到相應(yīng)的程序頭部表。?e_shoff:節(jié)區(qū)頭部表在文件中的字節(jié)偏移量,可根據(jù)該偏移量找到相應(yīng)的節(jié)區(qū)頭部表。?e_flags:保存與文件相關(guān)的,特定于處理器的標(biāo)志。?e_ehsize:ELF頭部的大小,此處為52,而程序頭部表在文件中的字節(jié)偏移量也為52,說明ELF頭部后緊挨著程序頭部表。?e_phentsize:程序頭部表中每個表項的大小,此處為32字節(jié)。?e_phnum:程序頭部表中表項的數(shù)目,此處為8。?e_shentsize:節(jié)區(qū)頭部表中每個表項的大小。?e_shnum:節(jié)區(qū)頭部表中表項的數(shù)目。?e_shstrndx:節(jié)區(qū)頭部表中與節(jié)區(qū)名稱字符串表相關(guān)的表項的索引。ELF頭部的結(jié)構(gòu)比較簡單,包含ELF的一些簡單信息。接下來重點分析程序頭部表部分。4.1.2程序頭部表可執(zhí)行文件或者共享目標(biāo)文件的程序頭部是一個結(jié)構(gòu)數(shù)組,每個結(jié)構(gòu)描述了一個段,或者系統(tǒng)準(zhǔn)備程序執(zhí)行所必需的其他信息。程序頭部僅對于可執(zhí)行文件和共享

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論