逆向目标目标:某瓣 APP
apk 版本:7.89
逆向参数:_sig参数
下载地址:aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzYyMjg0NDc=
我们先来简单了解一些常用 APP 工具。
SDK Platform-ToolsSDK Platform-Tools 是 Android 开发工具的一部分,由 Google 提供,主要用于与 Android 设备交互。它是开发者调试、管理设备以及支持应用程序开发的核心工具包,通常作为 Android SDK 的一部分使用。
下载地址AndroidDevTools - Android 开发工具:https://www.androiddevtools.cn
工具简介常见工具SDK Platform-Tools 包含多个实用工具,其中最常用的是 ADB(Android Debug Bridge)。
什么是 ADB?ADB 是一个通用的命令行工具,提供 Android 设备与 PC 端之间的桥梁。通过 ADB,用户可以:
安装和调试应用程序;
操作设备上的文件;
查看设备的状态信息;
执行其他与设备相关的操作。
安装 Platform-Tools下载对应平台的 SDK Platform-Tools(Windows/Mac/Linux);
解压文件到本地目录,例如D:\platform-tools;
配置环境变量:
将解压目录添加到系统的 PATH 环境变量中,以便在任意位置使用 ADB 命令。
ADB 的基本用法以下是常用的 ADB 命令及其功能:
1. 查看已连接设备adb devices输出示例:
List of devices attached1234567890abcdef device2. 安装 APK 文件adb install
adb install my_app.apk3. 卸载应用adb uninstall
adb uninstall com.example.myapp4. 推送文件到设备adb push
adb push my_file.txt /sdcard/5. 从设备拉取文件adb pull
adb pull /sdcard/my_file.txt ./local_copy.txt6. 进入设备的 shelladb shell进入 shell 后,可以执行设备上的 Linux 命令,例如:
ls /sdcard/7. 重启设备adb rebootjadxJadx 是一款开源的反编译工具,主要用于将 Android 应用程序的 APK 文件或 DEX 文件反编译为人类可读的 Java 源代码或 Smali 代码。它支持图形界面操作,是 Android 逆向工程中常用的工具之一。
下载地址Jadx Releases (v1.5.1):https://github.com/skylot/jadx/releases/tag/v1.5.1
安装和运行1. 下载并解压从上文下载地址获取工具包;
解压到本地目录,例如:jadx/。
2. 启动 Jadx双击运行 jadx-gui文件,启动图形界面。
3. 加载 APK 文件使用图形界面载入 APK 文件,工具会自动将 APK 中的 DEX 文件解码并展示为 Java 源代码:
打开 Jadx 图形界面;
点击 File -> Open File,选择需要分析的 APK 文件;
等待加载完成后,浏览解码后的 Java 源代码。
FridaFrida 是一款轻量级的 Hook 框架,也是一种动态插桩工具,可以插入代码到原生应用的内存空间,从而动态监视和修改其行为。Frida 支持多个平台,包括 Windows、Mac、Linux、Android 和 iOS。
Frida 的组成Frida 分为两部分:
服务端:运行在目标机器上,通过进程注入劫持应用的类和函数;
客户端:运行在自己的设备上,用于注入自定义脚本(支持 JavaScript、Python、C 等)。
环境准备需要安装以下内容:
Frida Server:运行在目标设备上;
Frida Tools:运行在本地,用于与服务端交互。
以下以 Frida 16.5.6和 Android ARM64系统为例。
安装 Frida ServerFrida Server 有两个版本:
普通版:https://github.com/frida/frida/releases;
魔改版(防检测优化版):https://github.com/hzzheyang/strongR-frida-android/releases。
下载并安装 Frida Server下载对应版本的 Frida Server 文件:
选择普通版或魔改版;
确保下载与目标设备架构匹配的版本(如 ARM64)。
使用 ADB 命令将文件传输到目标设备:
adb push frida-server-16.5.6-android-arm64 /data/local/tmp/(可选)传输魔改版的文件,命令类似,此处不再赘述;
修改 Frida Server 的权限并启动服务:
adb shell // 进入手机su // 切换成 root cd /data/local/tmp/ // 进入 tmp 文件chmod 777 frida-server-16.5.6-android-arm64 //修改文件权限./frida-server-16.5.6-android-arm64 // 启动 frida 服务端安装 Frida Client在本地使用 pip 安装 Frida Client 和 Frida Tools:
pip install frida==16.5.6pip install frida-tools==13.6.0安装完成后,可使用以下命令验证安装:
frida --versionFrida 的基本用法Frida 的基本用法主要有两种形式:
1. 附加到正在运行的应用使用-U和-F参数附加到设备上正在运行的应用程序:
frida -U -F -l script.js-U:通过 USB 连接的设备;
-F:附加到设备上当前正在运行的应用(无需手动指定包名);
-l script.js:运行指定的 JavaScript 脚本(如script.js)。
2. 强制启动并附加到指定应用使用-f参数强制启动并附加到指定的应用:
frida -U -f com.package.name -l script.js-f:强制启动应用;
com.package.name:目标应用的包名;
-l script.js:运行指定的 JavaScript 脚本。
IDAIDA(Interactive Disassembler Professional)是一款功能强大的交互式静态反汇编工具,广泛应用于程序分析和逆向工程。它具有以下特点:
多处理器支持:支持多种架构的二进制文件分析;
跨平台:支持 Windows、Linux、MacOS 等平台的程序分析;
可编程和可扩展:通过 Python 或 IDC 脚本扩展功能;
交互式操作:用户可以在反汇编的基础上动态修改和注释。
IDA 的下载和版本说明IDA 是一款商业工具,正版软件需要授权。如果只是学习使用,可以在社区论坛(如吾爱破解)找到适合的版本。注意不要用于非法用途。
IDA 的常用快捷键这里简单介绍一下快捷键,帮助快速上手:
快捷键功能说明空格在 图形视图(Graph View)和 汇编代码视图(Text View)之间切换。F5反编译代码,生成伪 C 代码(仅在支持的架构中可用)。G跳转到指定地址。X查看某个函数或变量的交叉引用(Xref)。N更改变量或函数的名称(命名更直观)。Y更改变量或函数的类型。Ctrl + F搜索字符串、代码或地址。Alt + T查找特定的函数、变量或模块(导航更快捷)。Ctrl + Space快速切换视图模式,便于分析。生于某瓣,始于某瓣,在介绍了常用的逆向工具之后,开始我们的主题。
抓包分析打开app,在首页进行刷新,charles配合SocksDroid进行抓包,结果如下:
其中要逆向的参数为_sig参数。
逆向分析我们把apk文件拖到jadx进行分析,直接搜索_sig参数,点进去:
Pair F3 = i0.d.F(request);request = request.newBuilder().url(request.url().newBuilder().setQueryParameter("_sig", (String) F3.first).setQueryParameter(bs.h, (String) F3.second).build()).build();发现新增了以下两个查询参数参数值,其中就有我们的_sig参数,点进去 F 方法:
只是对header做了一些操作,点进去 E 方法:
这个 E 很有可能是我们参数的生成地方,我们右键 E 方法复制frida代码,frida完整代码如下:
function hook1(){let d = Java.use("i0.d");d["E"].implementation = function (str, str2, str3) {console.log('E is called' + ', ' + 'str: ' + str + ', ' + 'str2: ' + str2 + ', ' + 'str3: ' + str3);let ret = this.E(str, str2, str3);console.log('E ret value is ' + ret);return ret;};}function main(){Java.perform(function (){hook1()})}setImmediate(main)使用如下frida命令启动发现frida退出,而我们APP没有退出,这说明我们frida被检测了:
frida -U -f com.douban.frodo -l 脚本名.js
我们可以先hook dlopen方法,看看是打开了哪个so文件退出了,dlopen是一个能动态加载指定的共享库到内存中,基本上所有的so文件加载都要经过该方法,hook代码如下:
var dlopen = Module.findExportByName(null, "dlopen");var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");Interceptor.attach(dlopen, {onEnter: function (args) {var path_ptr = args[0];var path = ptr(path_ptr).readCString();console.log("[dlopen -> enter", path);},onLeave: function (retval) {console.log("dlopen -> leave")}});Interceptor.attach(android_dlopen_ext, {onEnter: function (args) {var path_ptr = args[0];var path = ptr(path_ptr).readCString();console.log("[android_dlopen_ext -> enter", path);},onLeave: function (retval) {console.log("android_dlopen_ext -> leave")}});
发现libmsaoaidsec.so并没有leave,推测是在该so文件里面开启了线程,做循环检测,我们尝试hook pthread方法,pthread用于线程的创建、同步、管理和终止,hook代码如下:
function hook_pth() {var pth_create = Module.findExportByName("libc.so", "pthread_create");console.log("[pth_create]", pth_create);Interceptor.attach(pth_create, {onEnter: function (args) {var module = Process.findModuleByAddress(args[2]);if (module != null) {console.log("开启线程-->", module.name, args[2].sub(module.base));}},onLeave: function (retval) {}});}hook_pth()可以发现在这个 so 文件开启了两个线程,地址分别是0x1c544和0x1b8d4:
我们可以把这个so文件,拿到ida分析,分别搜索这两个地址,看看都做了什么操作:
0x1c544:
代码很长,看着看着像是在检测一些字符的长度。
0x1b8d4:
这个函数一个死循环,并且有一个usleep(v1)很可疑,像是在做循环检测。我们可以先把这个函数给替换掉,替换的时候要注意,有可能只hook这个地方可能不行,我们需要找到其他函数调用这个函数,也就是要找到他的引用,可以按住 x 看到函数的交叉引用。另外这个hook时机要早,因为这个函数的调用是通过init_proc调用的:
我们可以通过 hookcall_constructors这个,call_constructors主要作用是执行那些需要在程序开始运行之前完成初始化的代码,hook 代码如下:
function hook_call_constructors() {var linker64_base_addr = Module.getBaseAddress("linker64")var call_constructors_func_off = 0x4a174 var call_constructors_func_addr = linker64_base_addr.add(call_constructors_func_off)var listener = Interceptor.attach(call_constructors_func_addr, {onEnter: function (args) {console.log("call_constructors -> enter")var module = Process.findModuleByName("libmsaoaidsec.so")if (module != null) {Interceptor.replace(module.base.add(0x1B924), new NativeCallback(function () {console.log("替换成功")}, "void", []))listener.detach() }},})}通过打开这个libmsaoaidsec.so文件进行调用,完整代码如下:
var dlopen = Module.findExportByName(null, "dlopen");var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");Interceptor.attach(dlopen, {onEnter: function (args) {var path_ptr = args[0];var path = ptr(path_ptr).readCString();console.log("[dlopen -> enter", path);},onLeave: function (retval) {console.log("dlopen -> leave")}});Interceptor.attach(android_dlopen_ext, {onEnter: function (args) {var path_ptr = args[0];var path = ptr(path_ptr).readCString();console.log("[android_dlopen_ext -> enter", path);if (args[0].readCString() != null && args[0].readCString().indexOf("libmsaoaidsec.so") >= 0) {hook_call_constructors()}},onLeave: function (retval) {console.log("android_dlopen_ext -> leave")}});function hook_call_constructors() {var linker64_base_addr = Module.getBaseAddress("linker64")var call_constructors_func_off = 0x4a174var call_constructors_func_addr = linker64_base_addr.add(call_constructors_func_off)var listener = Interceptor.attach(call_constructors_func_addr, {onEnter: function (args) {console.log("call_constructors -> enter")var module = Process.findModuleByName("libmsaoaidsec.so")if (module != null) {Interceptor.replace(module.base.add(0x1B924), new NativeCallback(function () {console.log("替换成功")}, "void", []))listener.detach()}},})}最后成功过掉检测,接着继续 hook 我们上面的 E 函数:
发现结果一样,证明我们的位置没有找错,传入了三个参数分别为查询参数、请求方法 和 null:
E is called, str: https://frodo.douban.com/api/v2/elendil/recommend_feed?start=0&count=20&screen_width=1080&screen_height=2028&wx_api_ver=0&opensdk_ver=638058496&webview_ua=Mozilla%2F5.0%20%28Linux%3B%20Android%2011%3B%20Pixel%203%20Build%2FRQ1D.210205.004%3B%20wv%29%20AppleWebKit%2F537.36%20%28KHTML%2C%20like%20Gecko%29%20Version%2F4.0%20Chrome%2F130.0.6723.107%20Mobile%20Safari%2F537.36&sugar=0&update_mark=1735024878.512534157&network=wifi&enable_sdk_bidding=1&apikey=0dad551ec0f84ed02907ff5c42e8ec70&channel=ali_market&udid=3e71b8653a2b6b25b07876b25012c50ae5074f2a&os_rom=android&oaid=EdGi3zYQCRzmwwB1YR7WKg%3D%3D%0A&timezone=Asia%2FShanghai, str2: GET, str3: null
通过对传递的str参数不断操作,最终通过HMAC_SHA1算法生成加密值str4。其中算法的 key 值是由str5得来:
String str5 = j7.e.d().f30170e.b;点进该方法,可以发现算法 key 值是为 h 函数第三个参数的值:
直接 frida hook 该函数得到key值:
最终 python 代码如下:
import hmacimport base64import hashlibdef hmac_hash1(key: str, data: str) -> str:try:# 将 key 转换为字节key_bytes = key.encode()# 将 data 转换为字节data_bytes = data.encode()# 使用 HMAC-SHA1 进行加密mac = hmac.new(key_bytes, data_bytes, hashlib.sha1)# 返回 Base64 编码的结果return base64.b64encode(mac.digest()).decode('utf-8')except Exception as e:print(f"Error: {e}")return Noneif __name__ == '__main__':# 测试代码key = "bf7dddc7c9cfe6f7"data = "GET&%2Fapi%2Fv2%2Felendil%2Frecommend_feed&1735019437"hashed_value = hmac_hash1(key, data)print("HMAC Hash (Base64):", hashed_value)至此,该参数加密分析流程就结束了。
相关 hook 脚本,会分享到知识星球当中,需要的小伙伴自取,仅供学习交流。
结果验证
声明本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!