{"id":31827507,"url":"https://github.com/cyrus-studio/frida_stalker","last_synced_at":"2025-10-11T18:55:30.827Z","repository":{"id":285149321,"uuid":"957211323","full_name":"CYRUS-STUDIO/frida_stalker","owner":"CYRUS-STUDIO","description":"使用 Frida Stalker 反 OLLVM 算法还原（函数调用分析、参数分析、打印调用堆栈、指令\u0026寄存器Trace）。  Using Frida Stalker to Bypass OLLVM and Restore (Function Call Analysis, Parameter Analysis, Stack Trace Printing, Instruction \u0026 Registers Trace).","archived":false,"fork":false,"pushed_at":"2025-09-16T19:42:36.000Z","size":29,"stargazers_count":50,"open_issues_count":0,"forks_count":20,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-16T22:11:29.232Z","etag":null,"topics":["frida","frida-stalker","ollvm","trace"],"latest_commit_sha":null,"homepage":"https://cyrus-studio.github.io/blog/posts/%E4%BD%BF%E7%94%A8-frida-stalker-%E5%8F%8D-ollvm-%E7%AE%97%E6%B3%95%E8%BF%98%E5%8E%9F/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CYRUS-STUDIO.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-29T20:18:04.000Z","updated_at":"2025-09-16T19:42:39.000Z","dependencies_parsed_at":"2025-03-29T21:25:31.474Z","dependency_job_id":"82cf0cc3-b9f2-48ed-8c03-47bf6965dcad","html_url":"https://github.com/CYRUS-STUDIO/frida_stalker","commit_stats":null,"previous_names":["cyrus-studio/fridastalker","cyrus-studio/frida_stalker"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/CYRUS-STUDIO/frida_stalker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_stalker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_stalker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_stalker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_stalker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CYRUS-STUDIO","download_url":"https://codeload.github.com/CYRUS-STUDIO/frida_stalker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_stalker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279008295,"owners_count":26084431,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["frida","frida-stalker","ollvm","trace"],"created_at":"2025-10-11T18:55:28.294Z","updated_at":"2025-10-11T18:55:30.818Z","avatar_url":"https://github.com/CYRUS-STUDIO.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e 版权归作者所有，如有转发，请注明文章出处：\u003chttps://cyrus-studio.github.io/blog/\u003e\n\n# 前言\n\n\n\n在移动应用的安全加固中，**OLLVM（Obfuscator-LLVM）**  是一种常见的代码混淆与保护手段。它通过控制流平坦化、虚假控制流、指令替换等方式，使逆向分析者很难直接还原出原始算法逻辑。\n\n\n\n然而，通过 Frida 提供的 **Stalker**  模块去动态分析让我们有机会对 OLLVM 的“黑盒逻辑”进行还原。\n\n\n\n通过 Stalker 的指令级追踪能力，我们不仅能捕获 **函数调用** 、记录 **调用参数** ，还可以打印 **调用堆栈** ，最终逐步揭开 OLLVM 加固算法的真实运行流程。\n\n\n\n比如，分析某个 so 中偏移为  0x23AD0 的加密函数。使用 IDA 反汇编 so，可以看到 so 中该函数做了混淆\n\n\n\n![word/media/image1.png](https://gitee.com/cyrus-studio/images/raw/master/67a623e949b405628e3a6988a4a4f38a.png)\n\n\n使用了控制流平坦化混淆\n\n\n\n![word/media/image2.png](https://gitee.com/cyrus-studio/images/raw/master/2833883961c7d1298e9bc068228714c4.png)\n\n\n# Frida Stalker\n\n\n\n**Frida Stalker**  是 Frida 提供的一个强大的指令级追踪引擎，它能够在目标进程运行时，动态捕获每一条指令的执行情况。与传统的函数级 hook 不同，Stalker 可以深入到 **原生汇编层面** ，追踪寄存器变化、内存访问、函数调用关系等底层细节。\n\n\n\n相关链接：\n\n- Stalker 介绍：[https://frida.re/docs/stalker/](https://frida.re/docs/stalker/)\n\n- api 文档：[https://frida.re/docs/javascript-api/#stalker](https://frida.re/docs/javascript-api/#stalker)\n\n\n\n目前 Stalker 对于 arm64 支持比较好，但是 arm32 并不是很完善。\n\n\n\n![word/media/image3.png](https://gitee.com/cyrus-studio/images/raw/master/db534ee780918e2346c42cf76bcedfed.png)\n\n\n# onCallSummary（函数调用摘要)\n\n\n\n**onCallSummary**  是 Frida Stalker 提供的一个回调方法，用于在 **函数调用层面**  对收集到的执行数据进行归纳和统计。\n\n\n\n它会将某一段追踪区间内的 **调用信息进行汇总** ，例如：\n\n- 哪些函数被调用了\n\n- 每个函数被调用了多少次\n\n- 调用分布和频率\n\n\n\n简而言之，onCallSummary 像是 **函数调用的统计报表** ，让你能在混淆代码的“噪音”中看清主干逻辑。\n\n\n\n返回数据的结构（summary）通常类似于以下格式：\n\n```\n{\n  \"函数地址\": 调用次数\n}\n```\n\n\nhook 目标函数，跟踪  call 事件并打印 call summary\n\n```\nfunction onCallSummary() {\n    // 目标 so\n    var soName = \"libaes.so\"\n    // 目标 so 基址\n    var baseAddress = Module.findBaseAddress(soName);\n    // 目标函数地址 = 基址 + 偏移\n    var targetAddr = baseAddress.add(0x23AD0);\n\n    console.log('Target function found at:', targetAddr);\n\n    Interceptor.attach(targetAddr, {\n        onEnter: function (args) {\n            console.log('Entering target function');\n            Stalker.follow(Process.getCurrentThreadId(), {\n                events: {\n                    call: true,      // 捕获函数调用\n                    ret: false,       // 捕获函数返回\n                    exec: false,      // 捕获指令执行\n                    block: false,    // 捕获基本块\n                    compile: false   // 捕获编译事件\n                },\n                onCallSummary: function (summary) {\n                    console.log('Call Summary:');\n                    Object.keys(summary).forEach(function (addr) {\n                        var module = Process.getModuleByAddress(ptr(addr));\n                        // 判断地址是否属于目标 so\n                        if (module \u0026\u0026 module.name === soName) {\n                            var offset = ptr(addr).sub(module.base);\n                            console.log(`调用函数地址: ${ptr(addr)} | 模块: ${module.name} | 偏移: ${offset} | 次数: ${summary[addr]}`);\n                        }\n                    });\n                }\n            });\n        },\n\n        onLeave: function (retval) {\n            console.log('Leaving target function');\n            Stalker.unfollow(Process.getCurrentThreadId());\n        }\n    });\n}\n\n// setImmediate()：确保代码在 Frida 环境准备好后执行。\nsetImmediate(onCallSummary)\n```\n\n\n启动 frida-server，附加到当前 app 并执行脚本\n\n```\nfrida -H 127.0.0.1:1234 -F -l onCallSummary.js\n```\n\n\n输出如下：\n\n```\nTarget function found at: 0x77fdf2ead0\n[Remote::AndroidExample]-\u003e Entering target function\nLeaving target function\nCall Summary:\n调用函数地址: 0x77fdf62d50 | 模块: libaes.so | 偏移: 0x57d50 | 次数: 4\n调用函数地址: 0x77fdf62d00 | 模块: libaes.so | 偏移: 0x57d00 | 次数: 1\n调用函数地址: 0x77fdf62dd0 | 模块: libaes.so | 偏移: 0x57dd0 | 次数: 1\n调用函数地址: 0x77fdf62f70 | 模块: libaes.so | 偏移: 0x57f70 | 次数: 3\n调用函数地址: 0x77fdf62d80 | 模块: libaes.so | 偏移: 0x57d80 | 次数: 1\n调用函数地址: 0x77fdf30f60 | 模块: libaes.so | 偏移: 0x25f60 | 次数: 1\n调用函数地址: 0x77fdf62d30 | 模块: libaes.so | 偏移: 0x57d30 | 次数: 1\n调用函数地址: 0x77fdf62e00 | 模块: libaes.so | 偏移: 0x57e00 | 次数: 1\n调用函数地址: 0x77fdf30b8c | 模块: libaes.so | 偏移: 0x25b8c | 次数: 1\n调用函数地址: 0x77fdf34580 | 模块: libaes.so | 偏移: 0x29580 | 次数: 10\n调用函数地址: 0x77fdf62ce0 | 模块: libaes.so | 偏移: 0x57ce0 | 次数: 1\n调用函数地址: 0x77fdf62db0 | 模块: libaes.so | 偏移: 0x57db0 | 次数: 1\n调用函数地址: 0x77fdf62d60 | 模块: libaes.so | 偏移: 0x57d60 | 次数: 13\n调用函数地址: 0x77fdf303cc | 模块: libaes.so | 偏移: 0x253cc | 次数: 1\n调用函数地址: 0x77fdf62d10 | 模块: libaes.so | 偏移: 0x57d10 | 次数: 1\n调用函数地址: 0x77fdf62de0 | 模块: libaes.so | 偏移: 0x57de0 | 次数: 1\n调用函数地址: 0x77fdf30bc8 | 模块: libaes.so | 偏移: 0x25bc8 | 次数: 1\n调用函数地址: 0x77fdf62eb0 | 模块: libaes.so | 偏移: 0x57eb0 | 次数: 1\n调用函数地址: 0x77fdf62d40 | 模块: libaes.so | 偏移: 0x57d40 | 次数: 1\n调用函数地址: 0x77fdf62cf0 | 模块: libaes.so | 偏移: 0x57cf0 | 次数: 2\n调用函数地址: 0x77fdf62dc0 | 模块: libaes.so | 偏移: 0x57dc0 | 次数: 1\n调用函数地址: 0x77fdf62d70 | 模块: libaes.so | 偏移: 0x57d70 | 次数: 1\n调用函数地址: 0x77fdf62df0 | 模块: libaes.so | 偏移: 0x57df0 | 次数: 2\n调用函数地址: 0x77fdf62ec0 | 模块: libaes.so | 偏移: 0x57ec0 | 次数: 1\n调用函数地址: 0x77fdf62da0 | 模块: libaes.so | 偏移: 0x57da0 | 次数: 2\n```\n\n\n# onReceive（接收捕获的事件）\n\n\n\n**onReceive**  是 Frida Stalker 的另一个重要回调方法，用于 **逐条接收捕获到的事件** 。与 onCallSummary 不同，它不会进行统计汇总，而是将底层指令级别的执行轨迹实时发送到回调中。\n\n\n\n在 onReceive 中，你能拿到最原始的 **执行事件数据** ，例如：\n\n- 每条指令的执行地址\n\n- 寄存器变化\n\n- 内存读写行为\n\n- 调用的目标函数地址\n\n\n\n简而言之，onReceive 就像一台 **显微镜** ，能把程序的执行过程逐步展现出来，配合 \n\nonCallSummary 的宏观视角，二者结合能更高效地对抗复杂的代码混淆与保护机制。\n\n\n\nonReceive 传递的 events 数据需要用 Stalker.parse() 解析，解析后的数据是数组类型，格式如下：\n\n```\ncall,0x789143a16c,0x77a1addf1c,0\nret,0x7890000e34,0x788ff64e68,2\n\n[事件类型], [调用方地址], [目标地址], [附加信息]\n```\n\n\n比如，跟踪 call 和 ret 事件 并打印日志：\n\n```\nfunction getModuleByAddressSafe(address) {\n    try {\n        // 尝试获取模块\n        var module = Process.getModuleByAddress(address);\n\n        // 如果模块存在，返回模块\n        if (module) {\n            return module;\n        } else {\n            // 如果没有找到模块，返回 null\n            return null;\n        }\n    } catch (e) {\n        // 捕获异常，返回 null\n        return null;\n    }\n}\n\nfunction onReceive() {\n\n    var soName = \"libaes.so\"\n\n    // 目标 so 基址\n    var baseAddress = Module.findBaseAddress(soName);\n    // 目标函数地址 = 基址 + 偏移\n    var targetAddr = baseAddress.add(0x23AD0);\n\n    console.log('Target function found at:', targetAddr);\n\n    Interceptor.attach(targetAddr, {\n        onEnter: function (args) {\n            console.log('Entering target function');\n            Stalker.follow(Process.getCurrentThreadId(), {\n                events: {\n                    call: true,      // 捕获函数调用\n                    ret: true,       // 捕获函数返回\n                    exec: false,      // 捕获指令执行\n                    block: false,    // 捕获基本块\n                    compile: false   // 捕获编译事件\n                },\n                // 实时接收事件数据\n                onReceive: function (events) {\n                    var parsedEvents = Stalker.parse(events);\n\n                    console.log(`onReceive 事件数量: ${parsedEvents.length}`);\n\n                    parsedEvents.forEach(function (event) {\n                        // console.log(`收到事件: ${event}`);\n                        var caller = getModuleByAddressSafe(event[1]);\n                        var target = getModuleByAddressSafe(event[2]);\n\n                        // 判断地址是否属于目标 so\n                        if (caller \u0026\u0026 caller.name === soName) {\n\n                            var callerName = caller ? caller.name : \"Unknown\"\n                            var targetName = target ? target.name : \"Unknown\"\n\n                            var callerOffset = caller ? ptr(event[1]).sub(caller.base) : \"Unknown\";\n                            var targetOffset = target ? ptr(event[2]).sub(target.base) : \"Unknown\";\n\n                            console.log(`[${event[0]}] from: ${event[1]} | ${callerName} | ${callerOffset} -\u003e to: ${event[2]} | ${targetName} | ${targetOffset}`);\n                        }\n                    });\n                }\n            });\n        },\n\n        onLeave: function (retval) {\n            console.log('Leaving target function');\n            Stalker.unfollow(Process.getCurrentThreadId());\n        }\n    });\n}\n\n// setImmediate()：确保代码在 Frida 环境准备好后执行。\nsetImmediate(onReceive)\n```\n\n\n附加到当前 app 并执行脚本\n\n```\nfrida -H 127.0.0.1:1234 -F -l onReceive.js\n```\n\n\n输出如下：\n\n```\nTarget function found at: 0x77fdf2ead0\n[Remote::AndroidExample]-\u003e Entering target function\nLeaving target function\nonReceive 事件数量: 390\n[call] from: 0x77fdf2eb08 | libaes.so | 0x23b08 -\u003e to: 0x77fdf62d30 | libaes.so | 0x57d30\n[call] from: 0x77fdf2ef48 | libaes.so | 0x23f48 -\u003e to: 0x780d775348 | libart.so | 0x360348\n[ret] from: 0x77fdf2ef54 | libaes.so | 0x23f54 -\u003e to: 0x77fdf2eb0c | libaes.so | 0x23b0c\n[call] from: 0x77fdf2eb18 | libaes.so | 0x23b18 -\u003e to: 0x77fdf62d40 | libaes.so | 0x57d40\n[call] from: 0x77fdf2ef7c | libaes.so | 0x23f7c -\u003e to: 0x780d773378 | libart.so | 0x35e378\n[ret] from: 0x77fdf2ef88 | libaes.so | 0x23f88 -\u003e to: 0x77fdf2eb1c | libaes.so | 0x23b1c\n[call] from: 0x77fdf2eb34 | libaes.so | 0x23b34 -\u003e to: 0x77fdf303cc | libaes.so | 0x253cc\n[ret] from: 0x77fdf3048c | libaes.so | 0x2548c -\u003e to: 0x77fdf2eb38 | libaes.so | 0x23b38\n[call] from: 0x77fdf2eb3c | libaes.so | 0x23b3c -\u003e to: 0x77fdf62d00 | libaes.so | 0x57d00\n[call] from: 0x77fdf2e87c | libaes.so | 0x2387c -\u003e to: 0x77fdf62cf0 | libaes.so | 0x57cf0\n[call] from: 0x77fdf38f98 | libaes.so | 0x2df98 -\u003e to: 0x77fdf62d50 | libaes.so | 0x57d50\n[ret] from: 0x77fdf38fbc | libaes.so | 0x2dfbc -\u003e to: 0x77fdf2e880 | libaes.so | 0x23880\n[ret] from: 0x77fdf2e8ec | libaes.so | 0x238ec -\u003e to: 0x77fdf2eb40 | libaes.so | 0x23b40\n[call] from: 0x77fdf2eb4c | libaes.so | 0x23b4c -\u003e to: 0x77fdf62ce0 | libaes.so | 0x57ce0\n[call] from: 0x77fdf2e7f4 | libaes.so | 0x237f4 -\u003e to: 0x77fdf62cf0 | libaes.so | 0x57cf0\n[call] from: 0x77fdf38f98 | libaes.so | 0x2df98 -\u003e to: 0x77fdf62d50 | libaes.so | 0x57d50\n[ret] from: 0x77fdf38fbc | libaes.so | 0x2dfbc -\u003e to: 0x77fdf2e7f8 | libaes.so | 0x237f8\n[ret] from: 0x77fdf2e864 | libaes.so | 0x23864 -\u003e to: 0x77fdf2eb50 | libaes.so | 0x23b50\n[call] from: 0x77fdf2eb80 | libaes.so | 0x23b80 -\u003e to: 0x77fdf62d50 | libaes.so | 0x57d50\n[call] from: 0x77fdf2ebb8 | libaes.so | 0x23bb8 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf2ebd0 | libaes.so | 0x23bd0 -\u003e to: 0x77fdf62d10 | libaes.so | 0x57d10\n[ret] from: 0x77fdf2e9bc | libaes.so | 0x239bc -\u003e to: 0x77fdf2ebd4 | libaes.so | 0x23bd4\n[call] from: 0x77fdf2ebdc | libaes.so | 0x23bdc -\u003e to: 0x77fdf62d70 | libaes.so | 0x57d70\n[ret] from: 0x77fdf35128 | libaes.so | 0x2a128 -\u003e to: 0x77fdf2ebe0 | libaes.so | 0x23be0\n[call] from: 0x77fdf2ebfc | libaes.so | 0x23bfc -\u003e to: 0x77fdf62d80 | libaes.so | 0x57d80\n[call] from: 0x77fdf36664 | libaes.so | 0x2b664 -\u003e to: 0x77fdf62f70 | libaes.so | 0x57f70\n[ret] from: 0x77fdf35c4c | libaes.so | 0x2ac4c -\u003e to: 0x77fdf36668 | libaes.so | 0x2b668\n[call] from: 0x77fdf366b0 | libaes.so | 0x2b6b0 -\u003e to: 0x77fdf30b8c | libaes.so | 0x25b8c\n[call] from: 0x77fdf30bb8 | libaes.so | 0x25bb8 -\u003e to: 0x77fdf62eb0 | libaes.so | 0x57eb0\n[call] from: 0x77fdf31744 | libaes.so | 0x26744 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf3179c | libaes.so | 0x2679c -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf317f4 | libaes.so | 0x267f4 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf3184c | libaes.so | 0x2684c -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[call] from: 0x77fdf31898 | libaes.so | 0x26898 -\u003e to: 0x77fdf34580 | libaes.so | 0x29580\n[ret] from: 0x77fdf34640 | libaes.so | 0x29640 -\u003e to: 0x77fdf3189c | libaes.so | 0x2689c\n[ret] from: 0x77fdf324d8 | libaes.so | 0x274d8 -\u003e to: 0x77fdf30bbc | libaes.so | 0x25bbc\n[ret] from: 0x77fdf30bc4 | libaes.so | 0x25bc4 -\u003e to: 0x77fdf366b4 | libaes.so | 0x2b6b4\n[ret] from: 0x77fdf36764 | libaes.so | 0x2b764 -\u003e to: 0x77fdf2ec00 | libaes.so | 0x23c00\n[call] from: 0x77fdf2ed68 | libaes.so | 0x23d68 -\u003e to: 0x77fdf62d50 | libaes.so | 0x57d50\n[call] from: 0x77fdf2ed80 | libaes.so | 0x23d80 -\u003e to: 0x77fdf62db0 | libaes.so | 0x57db0\n[call] from: 0x77fdf35fcc | libaes.so | 0x2afcc -\u003e to: 0x77fdf62f70 | libaes.so | 0x57f70\n[ret] from: 0x77fdf35c4c | libaes.so | 0x2ac4c -\u003e to: 0x77fdf35fd0 | libaes.so | 0x2afd0\n[call] from: 0x77fdf36190 | libaes.so | 0x2b190 -\u003e to: 0x77fdf30bc8 | libaes.so | 0x25bc8\n[call] from: 0x77fdf30bec | libaes.so | 0x25bec -\u003e to: 0x77fdf62ec0 | libaes.so | 0x57ec0\n[call] from: 0x77fdf326c8 | libaes.so | 0x276c8 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf3274c | libaes.so | 0x2774c -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf327d0 | libaes.so | 0x277d0 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf32834 | libaes.so | 0x27834 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf32fa8 | libaes.so | 0x27fa8 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf330e8 | libaes.so | 0x280e8 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf33208 | libaes.so | 0x28208 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[call] from: 0x77fdf332f0 | libaes.so | 0x282f0 -\u003e to: 0x77fdf62d60 | libaes.so | 0x57d60\n[ret] from: 0x77fdf33334 | libaes.so | 0x28334 -\u003e to: 0x77fdf30bf0 | libaes.so | 0x25bf0\n[ret] from: 0x77fdf30bf8 | libaes.so | 0x25bf8 -\u003e to: 0x77fdf36194 | libaes.so | 0x2b194\n[ret] from: 0x77fdf3626c | libaes.so | 0x2b26c -\u003e to: 0x77fdf2ed84 | libaes.so | 0x23d84\n[call] from: 0x77fdf2ee08 | libaes.so | 0x23e08 -\u003e to: 0x77fdf62dc0 | libaes.so | 0x57dc0\n[call] from: 0x77fdf35cc4 | libaes.so | 0x2acc4 -\u003e to: 0x77fdf62f70 | libaes.so | 0x57f70\n[ret] from: 0x77fdf35c4c | libaes.so | 0x2ac4c -\u003e to: 0x77fdf35cc8 | libaes.so | 0x2acc8\n[call] from: 0x77fdf35d08 | libaes.so | 0x2ad08 -\u003e to: 0x77fdf30f60 | libaes.so | 0x25f60\n[ret] from: 0x77fdf30f6c | libaes.so | 0x25f6c -\u003e to: 0x77fdf35d0c | libaes.so | 0x2ad0c\n[ret] from: 0x77fdf35d20 | libaes.so | 0x2ad20 -\u003e to: 0x77fdf2ee0c | libaes.so | 0x23e0c\n[call] from: 0x77fdf2ee14 | libaes.so | 0x23e14 -\u003e to: 0x77fdf62dd0 | libaes.so | 0x57dd0\n[call] from: 0x77fdf2efb0 | libaes.so | 0x23fb0 -\u003e to: 0x780d775280 | libart.so | 0x360280\n[ret] from: 0x77fdf2efbc | libaes.so | 0x23fbc -\u003e to: 0x77fdf2ee18 | libaes.so | 0x23e18\n[call] from: 0x77fdf2ee30 | libaes.so | 0x23e30 -\u003e to: 0x77fdf62de0 | libaes.so | 0x57de0\n[call] from: 0x77fdf2effc | libaes.so | 0x23ffc -\u003e to: 0x780d775690 | libart.so | 0x360690\n[ret] from: 0x77fdf2f008 | libaes.so | 0x24008 -\u003e to: 0x77fdf2ee34 | libaes.so | 0x23e34\n[call] from: 0x77fdf2ee5c | libaes.so | 0x23e5c -\u003e to: 0x77fdf62df0 | libaes.so | 0x57df0\n[call] from: 0x77fdf2ee94 | libaes.so | 0x23e94 -\u003e to: 0x77fdf62df0 | libaes.so | 0x57df0\n[call] from: 0x77fdf2eeb4 | libaes.so | 0x23eb4 -\u003e to: 0x77fdf62e00 | libaes.so | 0x57e00\n[call] from: 0x77fdf2f040 | libaes.so | 0x24040 -\u003e to: 0x780d775448 | libart.so | 0x360448\n[ret] from: 0x77fdf2f04c | libaes.so | 0x2404c -\u003e to: 0x77fdf2eeb8 | libaes.so | 0x23eb8\n[call] from: 0x77fdf2eebc | libaes.so | 0x23ebc -\u003e to: 0x77fdf62da0 | libaes.so | 0x57da0\n[call] from: 0x77fdf2eec4 | libaes.so | 0x23ec4 -\u003e to: 0x77fdf62da0 | libaes.so | 0x57da0\n[ret] from: 0x77fdf2ef10 | libaes.so | 0x23f10 -\u003e to: 0x789143a60c | Unknown | Unknown\n```\n\n\n假如汇编代码中 BLR X8 我们不知道它具体调用的是什么\n\n\n\n![word/media/image4.png](https://gitee.com/cyrus-studio/images/raw/master/062fadf6b432ff8d5f50357beb5d163f.png)\n\n\n通过 onRecive 解析可以知道 调用的是 libaes.so 偏移 0x25f60 的函数\n\n\n\n![word/media/image5.png](https://gitee.com/cyrus-studio/images/raw/master/424949bfe7c04835d6077b9f8fdf25e5.png)\n\n\n用 IDA 打开 libaes.so 并调整到对应的地址，可以找到调用的函数\n\n\n\n![word/media/image6.png](https://gitee.com/cyrus-studio/images/raw/master/69527ac7048bd6f720ccc1c316457c47.png)\n\n\n# hook 所有 call 分析参数\n\n\n\n把所有调用到的函数 hook 分析一下；\n\n去掉一些系统 api 的 hook；\n\n如果是跳转表则在 IDA 找到跳转的真实偏移地址。\n\n```\nfunction printArg(addr) {\n    // 查找给定地址所在的内存范围\n    var range = Process.findRangeByAddress(addr);\n    // 如果该地址属于进程中的已知内存范围（例如模块中的数据段或代码段等）\n    if (range) {\n        return hexdump(addr) + \"\\n\";\n    } else {\n        return ptr(addr) + \"\\n\";\n    }\n}\n\n\nfunction hookNativeAddr(addr) {\n\n    var module = Process.findModuleByAddress(ptr(addr))\n\n    Interceptor.attach(addr, {\n        onEnter: function (args) {\n\n            this.arg0 = args[0];\n            this.arg1 = args[1];\n            this.arg2 = args[2];\n            this.arg3 = args[3];\n            this.arg4 = args[4];\n            this.logs = [];\n            \n            this.logs.push(\"call \" + module.name + \" | \" + ptr(addr).sub(module.base) + \"\\n\");\n            this.logs.push(\"arg0:\" + printArg(this.arg0));\n            this.logs.push(\"arg1:\" + printArg(this.arg1));\n            this.logs.push(\"arg2:\" + printArg(this.arg2));\n            this.logs.push(\"arg3:\" + printArg(this.arg3));\n            this.logs.push(\"arg4:\" + printArg(this.arg4));\n        },\n\n        onLeave: function (retval) {\n            this.logs.push(\"onLeave arg0:\" + printArg(this.arg0));\n            this.logs.push(\"onLeave arg1:\" + printArg(this.arg1));\n            this.logs.push(\"onLeave arg2:\" + printArg(this.arg2));\n            this.logs.push(\"onLeave arg3:\" + printArg(this.arg3));\n            this.logs.push(\"onLeave arg4:\" + printArg(this.arg4));\n            this.logs.push(\"retval:\" + printArg(retval));\n            console.log(this.logs);\n        }\n    });\n}\n\n\nfunction main() {\n    // 目标 so 基址\n    var baseAddress = Module.findBaseAddress(\"libaes.so\");\n\n    // hookNativeAddr(baseAddress.add(0x23AD0));\n\n    // hookNativeAddr(baseAddress.add(0x57d50)); // .malloc\n    hookNativeAddr(baseAddress.add(0x23868));    // stringToSecretKey 跳转表，0x57d00 实际偏移是 0x23868\n    hookNativeAddr(baseAddress.add(0x23F8C));    // _JNIEnv::NewByteArray(_JNIEnv *this, unsigned int) 跳转表，0x57dd0 实际偏移是 0x23F8C\n    hookNativeAddr(baseAddress.add(0x2ABEC));    // 0x57f70 -\u003e 0x2ABEC\n    hookNativeAddr(baseAddress.add(0x2B528));    // 0x57d80 -\u003e 0x2B528\n    hookNativeAddr(baseAddress.add(0x25f60));\n    hookNativeAddr(baseAddress.add(0x23F1C));    // 0x57d30 -\u003e 0x23F1C\n    hookNativeAddr(baseAddress.add(0x2400C));\n    hookNativeAddr(baseAddress.add(0x25b8c));\n    hookNativeAddr(baseAddress.add(0x29580));\n    hookNativeAddr(baseAddress.add(0x237E0));\n    hookNativeAddr(baseAddress.add(0x2AE80));\n    // hookNativeAddr(baseAddress.add(0x57d60));  // _memcpy_chk\n    hookNativeAddr(baseAddress.add(0x253cc));\n    hookNativeAddr(baseAddress.add(0x238F0));\n    hookNativeAddr(baseAddress.add(0x23FC0));\n    hookNativeAddr(baseAddress.add(0x25bc8));\n    hookNativeAddr(baseAddress.add(0x26524));\n    hookNativeAddr(baseAddress.add(0x23F58));\n    // hookNativeAddr(baseAddress.add(0x2E038));    // operator new[](unsigned __int64)\n    hookNativeAddr(baseAddress.add(0x2AC50));\n    hookNativeAddr(baseAddress.add(0x29F6C));\n    // hookNativeAddr(baseAddress.add(0x2E090));   // operator delete[](void *)\n    hookNativeAddr(baseAddress.add(0x274DC));\n    // hookNativeAddr(baseAddress.add(0x57da0));   // free\n}\n\nsetImmediate(main)\n```\n\n\n附加到当前 app 并执行脚本\n\n```\nfrida -H 127.0.0.1:1234 -F -l hookNativeAddr.js\n```\n\n\napp 中加密结果\n\n\n\n![word/media/image7.png](https://gitee.com/cyrus-studio/images/raw/master/cadf194389cd8668fc99fbab2996057c.png)\n\n\n在日志中找到第一次出现结果的地方\n\n\n\n![word/media/image8.png](https://gitee.com/cyrus-studio/images/raw/master/77bfebd7c93957b339d270208ca6d44f.png)\n\n\n找到这个函数 call libaes.so | 0x274dc\n\n\n\n![word/media/image9.png](https://gitee.com/cyrus-studio/images/raw/master/53f9d4d4ce88bd67b4682d367a824ad0.png)\n\n\n用 IDA 看这个函数中多处引用到一个全局变量\n\n\n\n![word/media/image10.png](https://gitee.com/cyrus-studio/images/raw/master/44a9906e3321c9c3e0569892203cdef7.png)\n\n\n是一些常量值\n\n\n\n![word/media/image11.png](https://gitee.com/cyrus-studio/images/raw/master/9b9aaa0c12cab6e787db3cda9ab41667.png)\n\n\n搜索看看，是 AES 的特征\n\n\n\n![word/media/image12.png](https://gitee.com/cyrus-studio/images/raw/master/2233a082b15cb4aa18144ce68d23e62f.png)\n\n\n# 打印调用堆栈\n\n\n\n打印该函数的调用堆栈看看\n\n```\nfunction getModuleByAddressSafe(address) {\n    try {\n        // 尝试获取模块\n        var module = Process.getModuleByAddress(address);\n\n        // 如果模块存在，返回模块\n        if (module) {\n            return module;\n        } else {\n            // 如果没有找到模块，返回 null\n            return null;\n        }\n    } catch (e) {\n        // 捕获异常，返回 null\n        return null;\n    }\n}\n\nfunction main() {\n    var addr = Module.findBaseAddress(\"libaes.so\").add(0x274DC);\n\n    Interceptor.attach(addr, {\n        onEnter: function (args) {\n            console.log('called from:\\n' +\n                Thread.backtrace(this.context, Backtracer.ACCURATE)\n                    .map((address) =\u003e {\n                        const symbol = DebugSymbol.fromAddress(address);\n\n                        if (symbol \u0026\u0026 symbol.name) {\n                            // 如果有符号信息，直接显示\n                            return `${address} ${symbol.moduleName}!${symbol.name}+0x${symbol.address.sub(Module.findBaseAddress(symbol.moduleName)).toString(16)}`;\n                        } else {\n                            // 如果没有符号信息，尝试获取模块和偏移信息\n                            const module = getModuleByAddressSafe(address);\n                            if (module) {\n                                const offset = ptr(address).sub(module.base);\n                                return `${address} ${module.name} + 0x${offset.toString(16)}`;\n                            } else {\n                                return `${address} [Unknown]`;\n                            }\n                        }\n                    })\n                    .join('\\n') + '\\n');\n        },\n\n        onLeave: function (retval) {}\n    });\n}\n\nsetImmediate(main);\n```\n相关文档：[https://frida.re/docs/javascript-api/#Thread](https://frida.re/docs/javascript-api/#Thread)\n\n\n\n附加到当前 app 并执行脚本\n\n```\nfrida -H 127.0.0.1:1234 -F -l printStack.js\n```\n\n\n输出如下：\n\n```\n[Remote::AndroidExample]-\u003e called from:\n0x77fe5f0bf0 libaes.so + 0x25bf0\n0x77fe5f0bec libaes.so + 0x25bec\n0x77fe5f6190 libaes.so + 0x2b190\n0x77fe5eed80 libaes.so + 0x23d80\n0x780d554350 libart.so!art_quick_generic_jni_trampoline+0x90+0x13f350\n0x780d54b5b8 libart.so!art_quick_invoke_static_stub+0x238+0x1365b8\n0x780d55a0cc libart.so!_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+0x114+0x1450cc\n0x780d6f6f98 libart.so!_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_11ShadowFrameEtPNS_6JValueE+0x180+0x2e1f98\n0x780d6f2024 libart.so!_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+0x384+0x2dd024\n0x780d9b81f8 libart.so!MterpInvokeStatic+0x170+0x5a31f8\n0x780d545994 libart.so!mterp_op_invoke_static+0x14+0x130994\n0x7fd160b0ac [Unknown]\n```\n\n\n开始调用的位置在 0x77fe5eed80 libaes.so + 0x23d80\n\n\n\n![word/media/image13.png](https://gitee.com/cyrus-studio/images/raw/master/6fb2251412717fa0a06ed15f82ddf79e.png)\n\n\n函数真实地址是 0x2ae80\n\n\n\n![word/media/image14.png](https://gitee.com/cyrus-studio/images/raw/master/1738e0e620d146f37db5fa441b431f70.png)\n\n\n在 call libaes.so | 0x2ae80 的 arg3 中找到 key / iv\n\n\n\n![word/media/image15.png](https://gitee.com/cyrus-studio/images/raw/master/a74610f9bd72cf12fab78fe1e9038a87.png)\n\n\n# 验证算法\n\n\n\n把 arg3 的 hexdump 复制到 CyberChef \n\n\n\n![word/media/image16.png](https://gitee.com/cyrus-studio/images/raw/master/ecd088c6b859779ce7a657b514c896e8.png)\n得到 key / iv 应该是 “CYRUS STUDIO    ”\n\n\n\n使用 CyberChef  的 AES CBC 算法加密得到结果和 app 的是一样的。\n\n\n\n![word/media/image17.png](https://gitee.com/cyrus-studio/images/raw/master/1268e0ecfa816d78397875d07dbbde34.png)\n所有这就是一个标准的 AES CBC 算法，key 和 iv 都是  “CYRUS STUDIO    ”\n\n\n\n# Frida Trace\n\n\n\n把 exec 设置为 true 也可以当 trace 用\n\n```\nfunction getModuleByAddressSafe(address) {\n    try {\n        // 尝试获取模块\n        var module = Process.getModuleByAddress(address);\n\n        // 如果模块存在，返回模块\n        if (module) {\n            return module;\n        } else {\n            // 如果没有找到模块，返回 null\n            return null;\n        }\n    } catch (e) {\n        // 捕获异常，返回 null\n        return null;\n    }\n}\n\n\nfunction main(soName, offset) {\n    var baseAddress = Module.findBaseAddress(soName);\n    var targetAddr = baseAddress.add(offset);\n\n    Interceptor.attach(targetAddr, {\n        onEnter: function (args) {\n            console.log(`Entering function at: ${targetAddr}`);\n\n            Stalker.follow(Process.getCurrentThreadId(), {\n                events: {\n                    exec: true\n                },\n                onReceive: function (events) {\n                    var parsedEvents = Stalker.parse(events);\n                    parsedEvents.forEach(event =\u003e {\n                        if (event[0] === 'exec') {\n                            const address = ptr(event[1]);\n                            const instruction = Instruction.parse(address);\n                            const module = getModuleByAddressSafe(address);\n                            const offset = module ? address.sub(module.base) : null;\n\n                            // 判断地址是否属于目标 so\n                            if (module \u0026\u0026 module.name === soName) {\n                                if (module) {\n                                    const logMessage = `${address} | ${module.name} + 0x${offset.toString(16)} | ${instruction}`;\n                                    console.log(logMessage)\n                                } else {\n                                    const logMessage = `${address} | Unknown | ${instruction}`;\n                                    console.log(logMessage)\n                                }\n                            }\n                        }\n                    });\n                }\n            });\n        },\n\n        onLeave: function (retval) {\n            console.log(\"Leaving function\");\n            Stalker.unfollow(Process.getCurrentThreadId());\n        }\n    });\n}\n\nsetImmediate(function () {\n    main(\"libaes.so\", 0x274DC)\n});\n```\n\n\n附加到当前 app 并执行脚本，并把日志保存到 trace.txt\n\n```\nfrida -H 127.0.0.1:1234 -F -l trace.js | tee trace.txt\n```\n\n\n效果如下：\n\n\n\n![word/media/image18.png](https://gitee.com/cyrus-studio/images/raw/master/6b1f2f80184917ad1c88daabc38345e1.png)\n\n\n# 完整源码\n\n\n\n开源地址：[https://github.com/CYRUS-STUDIO/frida_stalker](https://github.com/CYRUS-STUDIO/frida_stalker)\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrus-studio%2Ffrida_stalker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcyrus-studio%2Ffrida_stalker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrus-studio%2Ffrida_stalker/lists"}