Java教程

【APP 逆向百例】某瓣 app 逆向分析

本文主要是介绍【APP 逆向百例】某瓣 app 逆向分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

7VgY7m.png

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请在公众号【K哥爬虫】联系作者立即删除!

逆向目标

  • 目标:某瓣 APP

  • apk 版本:7.89

  • 逆向参数:_sig 参数

  • 下载地址:aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzYyMjg0NDc=

我们先来简单了解一些常用 APP 工具。

SDK Platform-Tools

SDK Platform-Tools 是 Android 开发工具的一部分,由 Google 提供,主要用于与 Android 设备交互。它是开发者调试、管理设备以及支持应用程序开发的核心工具包,通常作为 Android SDK 的一部分使用。

下载地址

AndroidDevTools - Android 开发工具:https://www.androiddevtools.cn

7VKjGY.png

工具简介

常见工具

SDK Platform-Tools 包含多个实用工具,其中最常用的是 ADB(Android Debug Bridge)

什么是 ADB?

ADB 是一个通用的命令行工具,提供 Android 设备与 PC 端之间的桥梁。通过 ADB,用户可以:

  • 安装和调试应用程序;

  • 操作设备上的文件;

  • 查看设备的状态信息;

  • 执行其他与设备相关的操作。

安装 Platform-Tools

  1. 下载对应平台的 SDK Platform-Tools(Windows/Mac/Linux);

  2. 解压文件到本地目录,例如 D:\platform-tools

  3. 配置环境变量:

    • 将解压目录添加到系统的 PATH 环境变量中,以便在任意位置使用 ADB 命令。

ADB 的基本用法

以下是常用的 ADB 命令及其功能:

1. 查看已连接设备

adb devices

输出示例:

List of devices attached
1234567890abcdef device

2. 安装 APK 文件

adb install <apk_file_path>

示例:

adb install my_app.apk

3. 卸载应用

adb uninstall <package_name>

示例:

adb uninstall com.example.myapp

4. 推送文件到设备

adb push <local_file> <remote_path>

示例:

adb push my_file.txt /sdcard/

5. 从设备拉取文件

adb pull <remote_file> <local_path>

示例:

adb pull /sdcard/my_file.txt ./local_copy.txt

6. 进入设备的 shell

adb shell

进入 shell 后,可以执行设备上的 Linux 命令,例如:

ls /sdcard/

7. 重启设备

adb reboot

jadx

Jadx 是一款开源的反编译工具,主要用于将 Android 应用程序的 APK 文件或 DEX 文件反编译为人类可读的 Java 源代码或 Smali 代码。它支持图形界面操作,是 Android 逆向工程中常用的工具之一。

下载地址

Jadx Releases (v1.5.1):https://github.com/skylot/jadx/releases/tag/v1.5.1

7VKnzH.png

安装和运行

1. 下载并解压

  • 从上文 下载地址 获取工具包;

  • 解压到本地目录,例如:jadx/

2. 启动 Jadx

双击运行 jadx-gui 文件,启动图形界面。

3. 加载 APK 文件

使用图形界面载入 APK 文件,工具会自动将 APK 中的 DEX 文件解码并展示为 Java 源代码:

  • 打开 Jadx 图形界面;

  • 点击 File -> Open File,选择需要分析的 APK 文件;

  • 等待加载完成后,浏览解码后的 Java 源代码。

Frida

Frida 是一款轻量级的 Hook 框架,也是一种动态插桩工具,可以插入代码到原生应用的内存空间,从而动态监视和修改其行为。Frida 支持多个平台,包括 Windows、Mac、Linux、Android 和 iOS。

Frida 的组成

Frida 分为两部分:

  1. 服务端:运行在目标机器上,通过进程注入劫持应用的类和函数;

  2. 客户端:运行在自己的设备上,用于注入自定义脚本(支持 JavaScript、Python、C 等)。

环境准备

需要安装以下内容:

  • Frida Server:运行在目标设备上;

  • Frida Tools:运行在本地,用于与服务端交互。

以下以 Frida 16.5.6Android ARM64 系统为例。


安装 Frida Server

Frida Server 有两个版本:

  1. 普通版:https://github.com/frida/frida/releases;

  2. 魔改版(防检测优化版):https://github.com/hzzheyang/strongR-frida-android/releases。

下载并安装 Frida Server

  1. 下载对应版本的 Frida Server 文件:

    • 选择普通版或魔改版;

    • 确保下载与目标设备架构匹配的版本(如 ARM64)。

  2. 使用 ADB 命令将文件传输到目标设备:

    adb push frida-server-16.5.6-android-arm64 /data/local/tmp/

  3. (可选)传输魔改版的文件,命令类似,此处不再赘述;

  4. 修改 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 frida16.5.6
pip install frida-tools13.6.0

安装完成后,可使用以下命令验证安装:

frida --version

Frida 的基本用法

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 脚本。

IDA

IDA(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 进行抓包,结果如下:

7VKfHU.png

其中要逆向的参数为 _sig 参数。

逆向分析

我们把 apk 文件拖到 jadx 进行分析,直接搜索 _sig 参数,点进去:

7VKi1q.png

7VKs2s.png

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 方法:

7VK6Qa.png

只是对 header 做了一些操作,点进去 E 方法:

7VKFv7.png

这个 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

7VKZnI.png

我们可以先 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”)
}
});

7VKeLV.png

发现 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 文件开启了两个线程,地址分别是0x1c5440x1b8d4

7VKoxL.png

我们可以把这个 so 文件,拿到 ida 分析,分别搜索这两个地址,看看都做了什么操作:

0x1c544

7Vb9zJ.png

7VbB9G.png

代码很长,看着看着像是在检测一些字符的长度。

0x1b8d4

7VbTKB.png

这个函数一个死循环,并且有一个usleep(v1) 很可疑,像是在做循环检测。我们可以先把这个函数给替换掉,替换的时候要注意,有可能只 hook 这个地方可能不行,我们需要找到其他函数调用这个函数,也就是要找到他的引用,可以按住 x 看到函数的交叉引用。另外这个 hook 时机要早,因为这个函数的调用是通过 init_proc 调用的:

7Vbm1t.png

我们可以通过 hook call_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 = 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()
}
},
})
}

最后成功过掉检测,接着继续 hook 我们上面的 E 函数:

7Vbp6b.png

7Vb0Qe.png

发现结果一样,证明我们的位置没有找错,传入了三个参数分别为查询参数、请求方法 和 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 (Linux%3B Android 11%3B Pixel 3 Build%2FRQ1D.210205.004%3B wv) AppleWebKit%2F537.36 (KHTML%2C like Gecko) Version%2F4.0 Chrome%2F130.0.6723.107 Mobile Safari%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 &timezone=Asia%2FShanghai, str2: GET, str3: null

7VblIw.png

通过对传递的 str 参数不断操作,最终通过 HMAC_SHA1 算法生成加密值 str4 。其中算法的 key 值是由 str5 得来:

String str5 = j7.e.d().f30170e.b;

点进该方法,可以发现算法 key 值是为 h 函数第三个参数的值:

7VbEL6.png

直接 frida hook 该函数得到 key 值:

7VbQxO.png

最终 python 代码如下:

import hmac
import base64
import hashlib

def 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 None

if 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 脚本,会分享到知识星球当中,需要的小伙伴自取,仅供学习交流。

结果验证

这篇关于【APP 逆向百例】某瓣 app 逆向分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!