<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Frida inlineHook原理分析及簡單設計一款AArch64 inlineHook工具

    VSole2022-07-07 16:23:32

    近期突然發現64位APP分析需求激增,然而手邊好用的 inlineHook 只有 Frida 一款,所以打算稍微研究下 Frida 的思路,以作借鑒,然后寫一款滿足簡單自用需求的 AArch64 inlineHook 工具。

    Frida inlineHook 思路分析

    根據之前開發 AArch32 inlineHook 框架的經驗,總結 inlineHook 框架開發的幾個關鍵點大抵如下:

    1. 動態替換需要 Hook 的指令片段為一段經過設計的跳板指令,即 trampoline ,目標為我們設計好的一段 shellCode
    2. 在內存中設計并生成一段 shellCode ,這是我們的可控 shellCode ,在該 shellCode 中需要實現 Hook 的功能函數(即打印/替換-參數/結果)
    3. shellCode 的設計原則是保持 Hook 前后的棧平衡,并保護寄存器狀態(即Hook結束后,保持與Hook開始前一致的棧布局與寄存器狀態)
    4. 在 shellCode 中完成原函數的執行工作,被替換的掉的指令中若包含計算 PC-relative address ( 如 Branch 指令 ),需要對其正確解析執行

    對我來說一個簡單的工具只要滿足前3點就足夠了,第4點待后續優化的時候再行完善,所以我們接下來看看 Frida 是如何完成以上這幾點的。

    Step1:

    首先我們簡單編寫一個 com.example.x64 應用作為目標 APP,且在 libx64.so 中放置一個 native 函數: Java_com_example_x64_JNI_aal ,馬上使用 Frida Hook 

    存在以下兩種情況:

    1、Frida Hook 函數開頭指令(即直接 Hook 導出函數)

    2、Hook 函數中間指定位置的指令

    Frida 代碼如下:

    //## hookTest1: Hook 導出函數->Java_com_example_x64_JNI_aalfunction hookTest1() {    var helloAddr = Module.findExportByName("libx64.so", "Java_com_example_x64_JNI_aal");    console.log(helloAddr);    if(helloAddr != null){        Interceptor.attach(helloAddr,{            onEnter: function(args){                console.log("hook1 on enter");            },            onLeave: function(retval){                console.log("hook1 on leave");            }        });    }}//## hookTest2: Hook 指定位置->0x000000000000BBA0function hookTest2() {    var libutilityAddr = Module.findBaseAddress("libx64.so");    var getOriginalStringAddr = libutilityAddr.add(0x000000000000BBA0);    console.log(getOriginalStringAddr);    if(getOriginalStringAddr != null){        Interceptor.attach(getOriginalStringAddr,{            onEnter: function(args){                console.log("hook2 on enter");            },            onLeave: function(retval){                console.log("hook2 on leave");            }        });    }}
    

    Hook 完畢,執行結果如下:

    不知道為什么打出來了兩次 hook1 on leave ,之后我使勁檢查代碼,確定并沒有寫錯。猜測原因或許是Hook點2在最終返回值的設置上出現了什么問題吧。我們暫時忽略上面的問題,接下來分析這兩個地址上的指令發生了甚么變化。

    Step2:

    掛上我們的調試器,首先對 Hook1 進行分析:Hook1 對應 Java_com_example_x64_JNI_aal 函數的入口位置( 0x7fac430b70 ),可以看到前16字節已經被替換掉,新指令為利用 x16 寄存器制作的一個跳板(trampoline),其目標為 0x7face7c600:

    Hook2 情況與 Hook1 類似,也是生成了16字節的跳板指令(依然使用 x16 寄存器)來替換掉 0xBBA0 位置的16字節原始指令,在此不做展示。

    P.S. > 在后續多次測試中發現,偶爾也會出現使用單條 Branch 指令(4字節)來替換掉被 Hook 地址的單條指令(4字節)的情況發生,如下圖所示。

    因為 Branch 指令存在跳轉范圍(+-128MB),所以 Frida 使用這種形式的 trampoline 需要對被 Hook 地址前后 128MB 范圍進行檢測,尋找空閑地址,不過這對本文實現一個簡單的 inlineHook 模型并無太大影響,故不做深入討論。 

    其實 Frida 還有一種跳轉范圍擴大至 +-4GB 大小的 trampoline 生成規則,在此也不做討論了,因為在原理上大同小異,單純屬于細節優化問題。

    另外還有不得不提的一點,當 trampoline 使用 x16 寄存器作為跳板寄存器時,Hook 結束后 x16 寄存器無疑會被污染,然而事實上 Frida 同時使用了 x16 與 x17 寄存器,那么關于這兩個寄存器有什么說法呢?官方對這兩個寄存器作用的描述如下:

    描述中提到 x16、x17 寄存器作為內部過程調用中的臨時寄存器,結合下圖便能更好的理解官方的定義。

    關于 trampoline 的研究就到此為止了,接下來我們看他生成的 shellCode。

    Step3:

    接下來我們開始分析 shellcode 部分,以 Hook1 為例。

    Java_com_example_x64_JNI_aal 函數入口: 0x7fac430b70

    入口處 trampoline 匯編代碼如下:

    進入 0x7face7c600 位置,分析如下圖:

    首先 mmap 了一段匿名內存( 7face7c000-7face83000 rwxp ),在 0x7face7c600 位置放置了以下幾條匯編指令構成第二段跳板。

    > ldr x17, =0x7facec12e0

    > ldr x16, =0x7face7c000

    > br x16

    其中 x17 寄存器裝載了一個地址( 0x7facec12e0 ),這個地址內部保存著 0x7fac430b70 ,正是 Java_com_example_x64_JNI_aal 函數入口地址。

    而 x16 寄存器裝載了此番生成的 shellCode 的地址( 0x7face7c000 ),將該段內存 dump 下來,拖入 ida 進行分析:

    綠色、藍色部分合并完成了棧平衡、寄存器保護與恢復工作。

    我們在外部用 JS 編寫的 Hook 功能代碼( onEnter 部分 ),由 BLR X4 ( 0x7F7D8D8360 ) 跳轉至 frida-agent-64.so (見下圖)來完成。

    在 JS 中可以打印,甚至修改函數入參的原因是因為入參(前8個在 X0-X7 寄存器上,后面的在棧上)已全部由綠色塊指令壓入棧中保存,所以在 BLR X4 進行函數調用時,合理設置 X0-X3 寄存器,使其正確的指向棧上某位置尤為關鍵。

    我們接下來在 shellcode 最后一條 BR X16 指令上插入斷點,分析函數的運行情況。

    當斷點觸發時 BR X16 欲跳轉至內存 0x7face7c630,其對應的匯編代碼如上圖所見,其中包含 Java_com_example_x64_JNI_aal 函數開頭被替換的4條原始指令。

    之后再次使用 x16 寄存器跳轉至 0x7fac430b80,即函數 Java_com_example_x64_JNI_aal 開頭偏移 0x10 的位置,以完成原函數的執行動作。

    此時 hook1 on enter 打印完畢,但 hook1 on leave 還未打印,所以注意到 x30 寄存器中保存的返回地址是 0x7face7c60c,即前文中暫未分析的第三段跳板指令,匯編代碼如下:

    > ldr x17, =0x7facec12e0

    > ldr x16, =0x7face7c100

    > br x16

    x17 寄存器行為與之前一致,x16 寄存器裝載了第二段 shellCode 的地址( 0x7face7c100 ),剛才已經一起 dump 下來了,直接在 ida 分析。

    綠色、藍色部分代碼作用不變,由 BLR X3 ( 0x7F7D8D86C8 ) 跳轉至 frida-agent-64.so 來完成外部 JS 寫的 Hook 功能代碼中 onLeave 的部分。

    最后由 BR X16 返回 Java_com_example_x64_JNI_aal 函數被調用時真正的 LR。

    至此 shellcode 部分也大體分析完畢了,此時我們應該能夠寫出一款簡單的 AArch64 inlineHook 工具模型了。

    AArch64 inlineHook 開發

    結合前文的分析,我們的 inlineHook 應該具備以下這幾點功能:

    1. Hook 導出函數:即在函數開頭進行 Hook ,能夠執行原函數,并提供 onEnter 以及 onLeave 兩層代碼注入點,達到類似 Frida 那種 "代碼托管" 一樣的效果
    2. Hook 函數內指定地址:Hook 指定位置的匯編指令,僅提供 onEnter 一層代碼注入點,因為考慮到在指定位置上 X30( LR ) 寄存器可能已經發生變化,此時用該寄存器做返回判斷并不準確,故放棄 onLeave
    3. 在 onEnter 中提供入參的打印/修改操作 ( 本質是寄存器/堆棧內存打印/修改操作 )
    4. 在 onLeave 中提供返回值的打印/修改操作 ( 本質是寄存器/堆棧內存打印/修改操作 )

    有了以上幾點需求,我們現在可以開始開發了 ( 源碼下載見文章末尾 )

    Step1:

    我們首先來設計 shellcode 部分,在本簡易版工具中,我們的跳板指令選擇使用 x16 寄存器的 16 字節 trampoline ,代碼如下:

    _trampoline_:    LDR                 X16, x64code0    BR                  X16x64code0:_jmp_addr_:    .dword 0x1111111111111111
    

    接下需要做參數和返回地址入棧工作,以及全寄存器狀態保護,代碼如下:

    接下來調用 Hook 功能函數的 onEnter 部分,并恢復寄存器及棧狀態,最后取出返回地址并返回原函數執行。

    對于 onLeave 部分的 shellcode 與之大體類似,就不貼圖展示了。

    Step2:

    接下來開始編寫函數完成 inlineHook 的插入

    //## Hook目標函數extern "C" JNIEXPORT jstring JNICALLJava_com_cs_inline_MainActivity_stringFromJNI(        JNIEnv* env,        jobject /* thisobj */,        jstring jstr) {    std::string hello = "Hello from C++: ";    hello.append(env->GetStringUTFChars(jstr, nullptr));    return env->NewStringUTF(hello.c_str());} //## 該函數內部完成了對Java_com_cs_inline_MainActivity_stringFromJNI函數的inlineHookextern "C" JNIEXPORT void JNICALLJava_com_cs_inline_MainActivity_inlineHook1(JNIEnv* env,                                            jobject /* thisobj */){    //## Hook target函數為:Java_com_cs_inline_MainActivity_stringFromJNI    u_long func_addr = (u_long)Java_com_cs_inline_MainActivity_stringFromJNI;    extern u_long _shellcode_start_, _the_func_addr_, _end_func_addr_, _ori_ins_set1_, _retback_addr_, _shellcode_end_, _trampoline_, _jmp_addr_, _shellcode_part2_;    //## 計算shellcode整體長度    u_long total_len = (u_long)&_shellcode_end_ - (u_long)&_shellcode_start_;    LOGD(ANDROID_LOG_DEBUG, "[+] ShellCode len: %d, target func: %p", total_len, func_addr);     //## 使用mmap分配匿名內存存放shellcode    u_long page_size = getpagesize();    u_long shellcode_mem_start = (u_long)mmap(0, page_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);    memset((void *)shellcode_mem_start, 0, page_size);    memcpy((void *)shellcode_mem_start, (void *)&_shellcode_start_, total_len);    LOGD(ANDROID_LOG_DEBUG, "[+] shellcode_mem_start: %p", shellcode_mem_start);     //## 設置trampoline跳轉的目標地址    *(u_long*)&_jmp_addr_ = shellcode_mem_start;     u_long mem_the_func_addr_ = (u_long)&_the_func_addr_ - (u_long)&_shellcode_start_ + shellcode_mem_start;    u_long mem_end_func_addr_ = (u_long)&_end_func_addr_ - (u_long)&_shellcode_start_ + shellcode_mem_start;    u_long mem_ori_ins_set1_ = (u_long)&_ori_ins_set1_ - (u_long)&_shellcode_start_ + shellcode_mem_start;    u_long mem_retback_addr_ = (u_long)&_retback_addr_ - (u_long)&_shellcode_start_ + shellcode_mem_start;    if(!off_shellcode_part2_)        off_shellcode_part2_ = (u_long)&_shellcode_part2_ - (u_long)&_shellcode_start_;     //## 設置onEnter及onLeave函數    *(u_long*)mem_the_func_addr_ = (u_long)on_enter_1;    *(u_long*)mem_end_func_addr_ = (u_long)on_leave_1;    //## 設置返回地址為距離Hook點0x10長度的指令地址,即偏移為trampoline的長度    *(u_long*)mem_retback_addr_ = (u_long)func_addr + 0x10;     //## 原指令保存,并未做任何解析,PC-relative address相關指令暫不支持    *(u_long*)mem_ori_ins_set1_ = *(u_long*)func_addr;    *(u_long*)(mem_ori_ins_set1_ + 8) = *(u_long*)(func_addr + 8);     //## 頁權限修改并完成inlineHook    u_long entry_page_start = (u_long)(func_addr) & (~(page_size-1));    mprotect((u_long*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);    *(u_long*)func_addr = *(u_long*)&_trampoline_;    *(u_long*)(func_addr + 8) = *(u_long*)(((u_long)&_trampoline_) + 8);
    

    inlineHook1 函數主要作用是分配 shellcode 的內存及設置其中的關鍵數據,并使用 trampoline 替換原指令完成 Hook,函數內注釋較為詳細,就不做過多解釋了。

    最后我們來編寫 onEnter 及 onLeave 函數。

    //## 使用線程局部存儲保存原始返回地址LR(X30)u_long thread_local ori_lr = 0;u_long off_shellcode_part2_ = 0; void on_enter_1(u_long sp){    //## sp回到初始位置,取出返回地址LR    sp = sp + 0x60;    u_long lr = *(u_long*)(sp - 8);    u_long lr_ptr = sp - 8;    u_long pc = *(u_long*)(sp - 0x20);    pc -= 0x20;    //## 使用TLS保存LR    ori_lr = lr;    //## 一般來說8個參數頂天了    u_long arg1 = *(u_long*)(sp - 0x28);    u_long arg2 = *(u_long*)(sp - 0x30);    u_long arg3 = *(u_long*)(sp - 0x38);    u_long* arg3_ptr = (u_long*)(sp - 0x38);    u_long arg4 = *(u_long*)(sp - 0x40);    u_long arg5 = *(u_long*)(sp - 0x48);    u_long arg6 = *(u_long*)(sp - 0x50);    u_long arg7 = *(u_long*)(sp - 0x58);    u_long arg8 = *(u_long*)(sp - 0x60);    //## sp上還有參數的話照下面這么寫    u_long arg9 = *(u_long*)(sp);    u_long arg10 = *(u_long*)(sp + 0x8);     //## 打印String參數    JNIEnv* env = reinterpret_cast(arg1);    jstring jstr = reinterpret_cast(arg3);    LOGD(ANDROID_LOG_INFO, "[+] arg3: %s", env->GetStringUTFChars(jstr, nullptr));    //## 替換String參數    jstring jstr_new = env->NewStringUTF("--This is on_enter_1 !");    *arg3_ptr = reinterpret_cast(jstr_new);     //## 修改LR寄存器,保證原始函數執行完畢會回到on_leave_1函數    *(u_long*)lr_ptr = pc + off_shellcode_part2_;    LOGD(ANDROID_LOG_WARN, "[+] on_enter_1: %p", on_enter_1);} void on_leave_1(u_long sp){    //## sp回到初始位置    sp = sp + 0x10;    u_long x0 = *(u_long*)(sp - 8);    u_long* x0_ptr = (u_long*)(sp - 8);    u_long lr = *(u_long*)(sp - 0x10);    u_long* lr_ptr = (u_long*)(sp - 0x10);     //## do_something ...    LOGD(ANDROID_LOG_DEBUG, "[+] on_leave_1: %p", on_leave_1);     //## 取回LR并返回    *(u_long*)lr_ptr = ori_lr;}
    

    在 onEnter 函數中需要保存原始函數的返回地址 LR 寄存器值至 TLS 中,并在最后設置臨時返回地址為 onLeave 函數對應的 shellcode,最后再 onLeave 中再取回真實的 LR 并返回實際的函數調用鏈中,完成整個 inlineHook 流程。

    另外 Hook 指定位置匯編指令的代碼并未貼出,因為原理是一致的,僅僅在 onEnter 函數中不設置臨時返回地址即可。

    效果展示及總結

    僅開啟 Hook1 時的效果如下圖所示:

    總結:借鑒 Frida 的 inlineHook 原理設計了一款簡單的 inlineHook 框架,滿足了部分常用需求;關于框架的 trampoline 優化,PC-relative address 相關指令解析執行等工作,待后續繼續開發優化。

    代碼已上傳:

    Gitee鏈接: https://gitee.com/zzy_cs/inline-hook

    Git鏈接: https://github.com/zzyccs/inlineHook

    數據寄存器狀態寄存器
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    不可中斷狀態實際上是系統對進程和硬件設備的一種保護機制。當負載存在明顯升高趨勢時,及時進行分析和調查。系統調用過程中并不會涉及虛擬內存等進程用戶態資源,也不會切換進程。因此系統調用通常稱為特權模式切換。進程是由內核管理和調度的,進程上下文切換只能發生在內核態。因此相比系統調用來說,在保存當前進程的內核狀態和CPU寄存器之前,需要先把該進程的虛擬內存,棧保存下來。
    匯編語言是一種用于電子計算機、微處理器、微控制器或其他可編程器件的低級語言,亦稱為符號語言。Smali匯編基礎Smali語言最早是由JesusFreke發布在Google Code上的一個開源項目,并不是擁有官方標準的語言。因此也將Smali語言稱作Android虛擬機的反匯編語言。基本類型Smali基本數據類型中包含兩種類型,原始類型和引用類型。而在Smali中則是以LpackageName/objectName的形式表示對象類型。
    IT之家注:這里提到的 FLAGS 寄存器一般被稱為“包含 x86 CPU 當前狀態狀態寄存器”,而 JCC 是基于 EFLAGS 寄存器內容的“允許條件分支的 CPU 指令”。簡單來說,要想利用這個漏洞實現攻擊,首先應該通過 EFLAGS 寄存器觸發編碼的瞬態執行,然后測量 JCC 指令的執行時間來獲取該編碼數據的內容。不過他們目前還不清楚是什么原因導致了這個 Bug。
    關于堆棧ShellCode操作:基礎理論002-利用fs寄存器尋找當前程序dll的入口:從動態運行的程序中定位所需dll003-尋找大兵LoadLibraryA:從定位到的dll中尋找所需函數地址004-被截斷的shellCode:加解密,解決shellCode的零字截斷問題
    近期突然發現64位APP分析需求激增,然而手邊好用的 inlineHook 只有 Frida 一款,所以打算稍微研究下 Frida 的思路,以作借鑒,然后寫一款滿足簡單自用需求的 AArch64 inlineHook 工具。Step1:首先我們簡單編寫一個 com.example.x64 應用作為目標 APP,且在 libx64.so 中放置一個 native 函數:?
    源碼分析1、LLVM編譯器簡介LLVM 命名最早源自于底層虛擬機的縮寫,由于命名帶來的混亂,LLVM就是該項目的全稱。LLVM 核心庫提供了與編譯器相關的支持,可以作為多種語言編譯器的后臺來使用。自那時以來,已經成長為LLVM的主干項目,由不同的子項目組成,其中許多是正在生產中使用的各種 商業和開源的項目,以及被廣泛用于學術研究。
    這樣一旦運行的服務器宕機,就把備份的服務器運行起來。冷備的方案比較容易實現,但冷備的缺點是主機出現故障時備機不會自動接管,需要主動切換服務。當一臺服務器宕機后,自動切換到另一臺備用機使用。
    Python人工智能第10篇介紹TF實現CNN圖像分類任務
    因此,探尋新的應對新型安全威脅的方法成為當前各機構的研究熱點。為應對新型病毒和木馬的安全威脅,行業內通常采用安全模塊擴展技術。的主要安全目標是防止敏感數據的完整性和機密性遭到破壞。當 REE 執行時,CPU 狀態寄存器以及總線信號中的對應位會置 1,安全內存和安全設備不再接受 CPU 的訪問請求。TEE 使用快速中斷請求,REE 使用中斷請求。
    虛擬機檢測技術整理
    2023-05-11 09:15:35
    第一次嘗試惡意代碼分析就遇到了虛擬機檢測,于是就想著先學習一下檢測的技術然后再嘗試繞過。學習后最終發現,似乎最好的方法不應該是去patch所有檢測方法,而是直接調試并定位檢測函數再繞過。但既然已經研究了兩天,索性將收集到的資料整理一下,方便后人查找。惡意軟件可以搜索這些文件、目錄或進程的存在。VMware 虛擬機中可能會有如下的文件列表:C:\Program Files\VMware\
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类