<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工作原理學習

    VSole2022-07-12 16:28:29

    一、frida介紹

    frida是一款便攜的、自由的、支持全平臺的hook框架,可以通過編寫JavaScript、Python代碼來和frida_server端進行交互,還記得當年用xposed時那種寫了一大堆代碼每次修改都要重新打包安裝重啟手機、那種調試調到頭皮發麻的痛苦,百分之30的時間都是在那里安裝重啟安裝重啟。

    二、frida的代碼結構

    frida-core: Frida 核心庫frida-gum: inline-hook 框架bindings:frida-python: pythonfrida-node: Node.jsfrida-qml: Qmlfrida-swift: Swiftfrida-tools: CLI toolscapstone: instruction disammbler
    

    Frida的核心是c編寫的有多種語言綁定例如 Node.js、 Python、 Swift、 .NET、 Qml。

    一般我們都使用js去編寫frida腳本因為js的異常處理機制非常棒相比于其他語言更高效好用。

    frida-core

    frida-core的功能有進程注入、進程間通信、會話管理、腳本生命周期管理等功能,屏蔽部分底層的實現細節并給最終用戶提供開箱即用的操作接口。而這一切的實現都在 frida-core 之中。

    正如名字所言,這其中包含了 frida 相關的大部分關鍵模塊和組件,比如 frida-server、frida-gadget、frida-agent、frida-helper、frida-inject 以及之間的互相通信底座。

    frida-gum

    frida-gum是基于inline-hook實現的他還有很多豐富的功能比如用于代碼跟蹤 Stalker、用于內存訪問監控的MemoryAccessMonitor,以及符號查找、棧回溯實現、內存掃描、動態代碼生成和重定位等。

    Interceptor

    Interceptor 是 inline-hook 的封裝。

    GumInterceptor * interceptor;GumInvocationListener * listener;gum_init ();interceptor = gum_interceptor_obtain ();//GumInvocationListener*的接口listener = g_object_new (EXAMPLE_TYPE_LISTENER, NULL);
    // 開始 hook `open` 函數gum_interceptor_begin_transaction (interceptor);gum_interceptor_attach_listener (interceptor,      GSIZE_TO_POINTER (gum_module_find_export_by_name (NULL, "open")),      listener,      GSIZE_TO_POINTER (EXAMPLE_HOOK_OPEN));gum_interceptor_end_transaction (interceptor);
    // 測試 hook 效果close (open ("/etc/hosts", O_RDONLY));
    // 結束 hookgum_interceptor_detach_listener (interceptor, listener);g_object_unref (listener);g_object_unref (interceptor);
    

    Stalker

    潛行者又稱為尾行癡漢,可以實現指定線程中所有函數、所有基本塊、甚至所有指令的跟蹤但是有很大的缺點比如在32位或者thumb下問題很大,一般想使用指令跟蹤都是使用內存斷點或者unidbg模擬執行so但是有很多問題,內存斷點的反調試倒是很容易解決但是性能是一個很大的缺陷代碼觸發斷點后會先中斷到內核態,然后再返回到用戶態(調試器)執行跟蹤回調,處理完后再返回內核態,然后再回到用戶態繼續執行,這來來回回的黃花菜都涼了。

    但Unidbg的使用門檻動不動就補環境,龍哥說樣本和Unidbg之間摩擦出的火花才是最迷人的。或者說人話——“Unidbg怎么又報錯了,我該怎么辦?”

    Stalker的簡單使用

    Interceptor.attach(addr, {       onEnter: function (args) {           this.args0 = args[0];           this.tid = Process.getCurrentThreadId();           //跟隨           Stalker.follow(this.tid, {               events: {//事件                   call: true,//呼叫                   ret: false,//返回                   exec: true,//執行                   block: false,//塊                   compile: false//編譯               },               //接收               onReceive(events){                   for (const [index,value] of Stalker.parse(events)) {                       console.log(index,value);                       //findModuleByAddress    {"name":"libc.so","base":"0x7d1f0af000","size":3178496,"path":"/apex/com.android.runtime/lib64/bionic/libc.so"}                       //console.log("tuzi",Process.findModuleByAddress(0x7d1f13adb8));
                       }               }               // onCallSummary(summay){               //console.log("onCallSummary"+JSON.stringify(summay));               // },           });       }, onLeave: function (retval) {           Stalker.unfollow(this.tid);       }   });
    

    Stalker也可以用來還原ollvm混淆 記錄函數的真實執行地址結合ida反匯編沒執行的代碼都nop掉可以很大程度上幫助輔助混淆算法分析當然可能不太準確但也是一種非常棒的思路。

    Stalker的功能實現,在線程即將執行下一條指令前,先將目標指令拷貝一份到新建的內存中,然后在新的內存中對代碼進行插樁,如下圖所示:

    這其中使用到了代碼動態重編譯的方法,好處是原本的代碼沒有被修改,因此即便代碼有完整性校驗也不影響,另外由于執行過程都在用戶態,省去了多次中斷內核切換,性能損耗也達到了可以接受的水平。由于代碼的位置發生了改變,如前文 Interceptor 一樣,同樣要對代碼進行重定位的修復。

    內存監控

    MemoryAccessMonitor可以實現對指定內存區間的訪問監控,在目標內存區間發生讀寫行為時可以觸發用戶指定的回調函數。

    通過閱讀源碼發現這個功能的實現方法非常簡潔,本質上是將目標內存頁設置為不可讀寫,這樣在發生讀寫行為時會觸發事先注冊好的中斷處理函數,其中會調用到用戶使用 gum_memory_access_monitor_new 注冊的回調方法中。

    //C 代碼gbooleangum_memory_access_monitor_enable (GumMemoryAccessMonitor * self,                                  GError ** error){  if (self->enabled)    return TRUE;  // ...  self->exceptor = gum_exceptor_obtain ();  gum_exceptor_add (self->exceptor, gum_memory_access_monitor_on_exception,      self);  // ...}
    //js代碼function read_write_break(){    function hook_dlopen(addr, soName, callback) {        Interceptor.attach(addr, {            onEnter: function (args) {                var soPath = args[0].readCString();                if(soPath.indexOf(soName) != -1) hook_call_constructors();            }, onLeave: function (retval) {            }        });    }    var dlopen = Module.findExportByName("libdl.so", "dlopen");    var android_dlopen_ext = Module.findExportByName("libdl.so", "android_dlopen_ext");    hook_dlopen(dlopen, "libaes.so", set_read_write_break);    hook_dlopen(android_dlopen_ext, "libaes.so", set_read_write_break);
        function set_read_write_break(){        //實現一個異常回調   處理好這個異常就可以正常返回        Process.setExceptionHandler(function(details) {            console.log(JSON.stringify(details, null, 2));            console.log("lr", DebugSymbol.fromAddress(details.context.lr));            console.log("pc", DebugSymbol.fromAddress(details.context.pc));            Memory.protect(details.memory.address, Process.pointerSize, 'rwx');            console.log(Thread.backtrace(details.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('') + '');            return true;        });        var addr = Module.findBaseAddress("libaes.so").add(0x6666);        Memory.protect(addr, 8, '---'); //修改內存頁的權限        /**         * 比如有一個地址是0x12345678  我想看一下是那個代碼去訪問了這個地址         * 我只需要把這個內存地址置空 有函數去訪問這個地址時 就會觸發非法訪問異常         * 比較雞肋這種方法 這種方法會一次修改一個內存頁  并且觸發一次就無效了         */    }}
    

    hook原理

    1.注入進程ptracedlopen2.hook 目標函數2.1 Java HookStatic Field Hook:靜態成員hookMethod Hook:函數hook2.2 Native So HookGOT Hook:全局偏移表hookSYM Hook:符號表hookInline Hook:函數內聯hook執行自身代碼獲取敏感信息修改返回值etc.
    

    frida注入的主要思路就是找到目標進程,使用ptrace跟蹤目標進程獲取mmap,dlpoen,dlsym等函數庫的便宜獲取mmap在目標進程申請一段內存空間將在目標進程中找到存放[frida-agent-32/64.so]的空間啟動執行各種操作由agent去實現。

    補充:frida注入之后會在遠端進程分配一段內存將agent拷貝過去并在目標進程中執行代碼,執行完成后會 detach 目標進程,這也是為什么在 frida 先連接上目標進程后還可以用gdb/ida等調試器連接,而先gdb連接進程后 frida 就無法再次連上的原因(frida在注入時只會ptrace一下下注入完畢后就會結束ptrace所以ptrace占坑這種反調試使用spawn方式啟動即可)。

    frida-agent 注入到目標進程并啟動后會啟動一個新進程與 host 進行通信,從而 host 可以給目標進行發送命令,比如執行代碼,激活/關閉 hook,同時也能接收到目標進程的執行返回以及異步事件信息等。

    hook java層

    frida 的 hook 區分了 art 模式和 dalvik 模式。

    dalvik 模式

    把 java 函數變成 native 函數,然后修改入口信息為自定義函數信息。

    struct Method {      ClassObject*    clazz;   /* method所屬的類 public、native等*/    u4              accessFlags; /* 訪問標記 */    u2             methodIndex; //method索引    //三個size為邊界值,對于native函數,這3個size均等于參數列表的size    u2              registersSize;  /* ins + locals */    u2              outsSize;    u2              insSize;    const char*     name;//函數名稱    /*     * Method prototype descriptor string (return and argument types)     */    DexProto        prototype;    /* short-form method descriptor string */    const char*     shorty;    /*     * The remaining items are not used for abstract or native methods.     * (JNI is currently hijacking "insns" as a function pointer, set     * after the first call.  For internal-native this stays null.)     */    /* the actual code */    const u2*       insns;          /* instructions, in memory-mapped .dex */    /* cached JNI argument and return-type hints */    int             jniArgInfo;    /*     * Native method ptr; could be actual function or a JNI bridge.  We     * don't currently discriminate between DalvikBridgeFunc and     * DalvikNativeFunc; the former takes an argument superset (i.e. two     * extra args) which will be ignored.  If necessary we can use     * insns==NULL to detect JNI bridge vs. internal native.     */    DalvikBridgeFunc nativeFunc;    /*     * Register map data, if available.  This will point into the DEX file     * if the data was computed during pre-verification, or into the     * linear alloc area if not.     */    const RegisterMap* registerMap;
    };
    ………
    function replaceDalvikImplementation (fn) {  if (fn === null && dalvikOriginalMethod === null) {    return;  }//備份原來的method,  if (dalvikOriginalMethod === null) {    dalvikOriginalMethod = Memory.dup(methodId, DVM_METHOD_SIZE);    dalvikTargetMethodId = Memory.dup(methodId, DVM_METHOD_SIZE);  }
      if (fn !== null) {   //自定的代碼    implementation = implement(f, fn);
        let argsSize = argTypes.reduce((acc, t) => (acc + t.size), 0);    if (type === INSTANCE_METHOD) {      argsSize++;    }    // 把method變成native函數    /*     * make method native (with kAccNative)     * insSize and registersSize are set to arguments size     */    const accessFlags = (Memory.readU32(methodId.add(DVM_METHOD_OFFSET_ACCESS_FLAGS)) | kAccNative) >>> 0;    const registersSize = argsSize;    const outsSize = 0;    const insSize = argsSize;
        Memory.writeU32(methodId.add(DVM_METHOD_OFFSET_ACCESS_FLAGS), accessFlags);    Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_REGISTERS_SIZE), registersSize);    Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_OUTS_SIZE), outsSize);    Memory.writeU16(methodId.add(DVM_METHOD_OFFSET_INS_SIZE), insSize);    Memory.writeU32(methodId.add(DVM_METHOD_OFFSET_JNI_ARG_INFO), computeDalvikJniArgInfo(methodId));    //調用dvmUseJNIBridge為這個Method設置一個Bridge,本質上是修改結構體中的nativeFunc為自定義的implementation函數    api.dvmUseJNIBridge(methodId, implementation);
        patchedMethods.add(f);  } else {    patchedMethods.delete(f);
        Memory.copy(methodId, dalvikOriginalMethod, DVM_METHOD_SIZE);    implementation = null;  }}
    

    art 模式

    art模式也是需要將java 函數變成 native 函數但是不同于dalvik,art下有兩種解釋器一種匯編解釋器一種smali解釋器。

    quick code 模式:執行 arm 匯編指令Interpreter 模式:由解釋器解釋執行 Dalvik 字節碼
    

    1.如果函數已經存在quick code, 則指向這個函數對應的 quick code的起始地址,而當quick code不存在時,它的值則會代表其他的意義。

    2.當一個 java 函數不存在 quick code時,它的值是函數artQuickToInterpreterBridge 的地址,用以從 quick 模式切換到 Interpreter 模式來解釋執行 java 函數代碼。

    3.當一個 java native(JNI)函數不存在 quick code時,它的值是函數 art_quick_generic_jni_trampoline 的地址,用以執行沒有quick code的 jni 函數。

    所以 frida 要將 java method 轉為 native method,需要將ARTMethod 結構進行如下修改:

    patchMethod(methodId, {  //jnicode入口entry_point_from_jni_改為自定義的代碼  'jniCode': implementation,  //修改為access_flags_為native  'accessFlags': (Memory.readU32(methodId.add(artMethodOffset.accessFlags)) | kAccNative | kAccFastNative) >>> 0,  //art_quick_generic_jni_trampoline函數的地址  'quickCode': api.artQuickGenericJniTrampoline,  //artInterpreterToCompiledCodeBridge函數地址  'interpreterCode': api.artInterpreterToCompiledCodeBridge});
    
    進程間通信jni
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Frida工作原理學習
    2022-07-12 16:28:29
    frida是一款便攜的、自由的、支持全平臺的hook框架,可以通過編寫JavaScript、Python代碼來和frida_server端進行交互,還記得當年用xposed時那種寫了一大堆代碼每次修改都要重新打包安裝重啟手機、那種調試調到頭皮發麻的痛苦,百分之30的時間都是在那里安裝重啟安裝重啟。
    也防止有人通過inlinehook 直接hook recv ,recvform,recvmsg 直接在收到數據包的時候被攔截和替換掉。
    本文對如何利用 DCOM 進行橫向移動的手法進行了總結,希望可以對大家的學習提供一些幫助。
    一個廣泛應用于 Windows、 Linux 和 Mac 環境的開源模塊的維護者最近破壞了它的功能,以抗議烏克蘭的戰爭,大眾再次將注意力集中在與軟件代碼依賴相關的潛在的嚴重安全問題上。
    1.IPC橫向 IPC(Internet Process Connection)共享命名管道的資源,是為了實現進程間通信而開放的命名管道。IPC可以通過驗證用戶名和密碼獲得相應的權限,使用139、445端口。 1.1 利用條件
    長久以來,網絡攻擊者一直都在積極探索和實現針對Linux操作系統的定向攻擊,而LaZagne(一種流行的開源密碼恢復工具)等實用工具的易訪問性,使得威脅行為者在惡意軟件攻擊鏈中使用它們來轉儲密碼時變得越來越方便了。
    COM-Hunter是一款針對持久化COM劫持漏洞的安全檢測工具,該工具基于C#語言開發,可以幫助廣大研究人員通過持久化COM劫持技術來檢測目標應用程序的安全性。COM對象的濫用使安防團隊能夠代表受信任的進程執行任意代碼。執行COM劫持不需要管理員權限,因為HKCU注冊表配置單元中的類在HKLM中的類之前執行。唯一影響高完整性進程(提升)的例外情況是,僅從HKLM位置加載對象,以防止特權提升。
    vsomeip 是 GENIVI 實現的開源 SOME/IP 庫,由 C++ 編寫,目前主要實現了 SOME/IP 的通信和服務發現功能,并在此基礎上增加了少許的安全機制。 GENIVI是一個聯盟組織,由 BMW 倡導,是汽車信息娛樂領域系統軟件標準的倡導者,創建基于linux 系統的 IVI 軟件平臺和操作系統。GENIVI 倡導了很多開源軟件項目,比如:DLT、CommonAPI C++、v
    前言 由于傳播、利用此文所提供的信息而造成的任何直接或者間接的后果及損失,均由使用者本人負責,文章作者不為此承擔任何責任。 如果文章中的漏洞出現敏感內容產生了部分影響,請及時聯系作者,望諒解。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类