安卓逆向
跟着正己大跌学安卓
环境安装
———这一段不过多解释,可以去网上搜,也可以看正己爹的视频
一、初识APK
1.apk的文件结构
| 静态资源文件(assets) | 存放静态资源,比如视频、音频、图片等 |
|---|---|
| 库文件(lib) | armeabi-v7a –> android设备基本通用;arm64-v8a –>64位android设备;x86 –> 常用于安卓模拟器,目录下的so文件时c or c++ 编译的动态链接库 |
| 签名文件(META-INF) | 签名信息,相当于身份证 |
| 编译资源文件(res) | 存放资源文件,包括图片,字符串等 |
| 配置清单文件(AndroidManifest.xml) | 描述应用名字、版本、权限、引用的库文件等 |
| 核心代码文件(classes.dex) | java源码编译后的字节码文件,Apk运行的主要逻辑 |
| 资源映射文件(resources.arsc) | 编译后的二进制资源文件,映射资源和id |
2.apk双开
目前只知道修改包名
3.apk汉化
利用MT和NP管理器
利用mt的搜索功能
mt dex 里面有单独的搜素功能
4.AndroidManifest.xml
- *package*:指定应用包名。
- versionCode* 和 versionName*:定义版本号和版本名称。
- mainfest* 中添加 uses-permission*:应用需要访问系统功能(如网络、位置)
1 | <uses-permission android:name="android.permission.INTERNET" /> |
- 调试模式:android:debuggable
二、smali代码
简单学习一下smali代码
dex里面修改即可
三、安卓组件、弹窗去除、广告分析
四、动态调试与Log插桩
五、签名校验
签名校验、dexcrc校验、apk完整性校验、路径文件校验
六、Xposed Hook
七、密码与算法
八、ida与so文件
编写简单的native项目
1 |
|
了解JNI
全称是Java_nativate_interface,Java本地接口,JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。
通过反射,c/c++也可以调用Java层代码
静态注册
就是上面我们写的那种
- 优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低
- 缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高
动态注册
通过使用JNI_OnLoad
1 |
|
| C++ 数据类型 | Java 数据类型 | JNI 数据类型签名 |
|---|---|---|
| jint | int | “I” |
| jboolean | boolean | “Z” |
| jbyte | byte | “B” |
| jchar | char | “C” |
| jshort | short | “S” |
| jlong | long | “J” |
| jfloat | float | “F” |
| jdouble | double | “D” |
| jobject | Object | “Ljava/lang/Object;” |
| jstring | String | “Ljava/lang/String;” |
| jarray | Array | “[elementType” |
| jobjectArray | Object[] | “[Ljava/lang/Object;” |
| jbooleanArray | boolean[] | “[Z” |
| jbyteArray | byte[] | “[B” |
| jcharArray | char[] | “[C” |
| jshortArray | short[] | “[S” |
| jintArray | int[] | “[I” |
| jlongArray | long[] | “[J” |
| jfloatArray | float[] | “[F” |
| jdoubleArray | double[] | “[D” |
patch方法
在ida里面用keypatch进行修改即可,将修改之后的so文件覆盖原来的,然后使用mt管理器进行重签名即可。
遇到签名校验我不炸了吗。
九、so分析(加载、混淆、动调、反调)
加载时机
动调与反调
调试步骤
分为两种模式,一种是以debug模式启动,第二种则以普通模式启动,二者的区别在于使用场景,有时候要动态调试的参数在app一启动的时候就产生了,时机较早,所以需要以debug模式去挂起app
1 | adb shell am start -D -n com.zj.wuaipojie/.ui.ChallengeEight (去掉-D 则表示不以debug模式启动app) |
PS:若不是以debug启动则不需要输入后两条命令
[超级详细]实战分析一个Crackme的过程 - 吾爱破解 - 52pojie.cn
bat脚本: 1
2
3
4
5
6
7
8
9@echo on %关闭回显命令%
start "" cmd /k call IDAdebug2.bat %新打开一个cmd并运行IDAdebug1.bat%
adb shell "su -c './data/local/tmp/as'"
pause
@echo on %关闭回显命令%
adb forward tcp:23946 tcp:23946 %端口转发%
adb shell am start -n com.zj.wuaipojie/.ui.ChallengeEight %debug状态启动手机端APP%
pause
ida显示问题:
我们端口转发完毕之后: 
注意:SELinux 是否处于 Enforcing 模式?
即使你用了 su 获取了 root 权限,较新版本的 Android
系统的 SELinux 策略依然可能拦截 ptrace 附加操作,导致 IDA
报错权限不足。
解决方法: 在启动
android_server64之前,在 root shell 下执行以下命令临时关闭 SELinux:1
setenforce 0
端口占用解决: 1
2adb shell "su -c 'lsof | grep 23946'" //获取pid
adb shell "su -c 'kill -9 PID'" //这里的pid要根据上一步获取的填写
ollvm以及IDA Trace
十、Frida
JNI静态注册,可以使用java的api进行hook也可以使用so的一些api进行hook
// 整数型、布尔值类型、char类型
1 | function hookTest1(){ |
// 字符串类型
1 | // 字符串类型 |
整数修改
1 | function hookTest4(){ |
字符串修改
1 | function hookTest5(){ |
导入导出表,通过ida查看即可
1 | function hookTest6(){ |
函数地址计算
- 安卓里一般32 位的 so 中都是
thumb指令,64 位的 so 中都是arm指令 - 通过IDA里的opcode bytes来判断,arm 指令为 4 个字节(options -> general -> Number of opcode bytes (non-graph) 输入4)
- thumb 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 + 1 arm 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移
1 | // Hook 未导出函数与函数地址的计算 |
frida API
01 write: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31function hookTest01(){
Java.perform(function(){
// 根据导出函数获取打印地址
var HelloAddr = Module.findExportByName("lib52pojie.so", "Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
// console.log("HelloAddr: " + HelloAddr);
// 拦截函数
if (HelloAddr){
Interceptor.attach(HelloAddr, {
// onEnter函数在目标函数被调用时执行,args参数包含了传递给目标函数的所有参数
onEnter: function(args){
console.log("onEnter");
},
// onLeave函数在目标函数返回时执行,retval参数包含了目标函数的返回值
onLeave: function(retval){
console.log("onLeave");
var returnedJstring = Java.cast(retval, Java.use('java.lang.String'));
console.log("returnedJstring: " + returnedJstring);
//一般写在app的私有目录里,不然会报错:failed to open file (Permission denied)(实际上就是权限不足)
var file_path = "/data/user/0/com.zj.wuaipojie/test.txt";
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
file_handle.write(returnedJstring.toString()); //写入数据
file_handle.flush(); //刷新
file_handle.close(); //关闭
}
}
})
}
})
}
02 Inline hook:
1 | function hexToBytes(str) { |
03 主动调用:
| 数据类型 | 描述 |
|---|---|
| void | 无返回值 |
| pointer | 指针 |
| int | 整数 |
| long | 长整数 |
| char | 字符 |
| float | 浮点数 |
| double | 双精度浮点数 |
| bool | 布尔值 |
1 | var funcAddr = Module.findBaseAddress("lib52pojie.so").add(0x1054C); |
jni的主动调用 参考java的主动调用,简单快捷
trace工具:
frida-trace
官方文档 frida-trace 可以一次性监控一堆函数地址。还能打印出比较漂亮的树状图,不仅可以显示调用流程,还能显示调用层次。并且贴心的把不同线程调用结果用不同的颜色区分开了。
我的frida版本是16,websockets==13.1,可以跑通
jnitrace:
jnitrace -m attach -l lib52pojie.so com.zj.wuaipojie -o trace.json
//attach模式附加52pojie.so并输出日志
十一、Frida检测
1.检测文件名、端口名、双进程保护、失效的检测点
1.检测/data/local/tmp路径下的是否有frida特征文件,可以在server端改名,例如:fr 2.指定端口转发
1 | /fs1 -l 0.0.0.0:6666 |
3.spawn启动过双进程保护
1 | frida -U -f 进程名 -l hook.js |
看注入报错的日志,比如说当app主动附加自身进程时,这时候再注入就会提示run frida as root(以spawn的方式启动进程即可)
4.借助脚本定位检测frida的so(之前写过)
1 | 复制代码 隐藏代码function hook_dlopen() { |
5检测点的失效 (1.)例如检测D-Bus D-Bus是一种进程间通信(IPC)和远程过程调用(RPC)机制,最初是为Linux开发的,目的是用一个统一的协议替代现有的和竞争的IPC解决方案。
1 | 复制代码 隐藏代码bool check_dbus() { |
(2)检测fd /proc/pid/fd 目录的作用在于提供了一种方便的方式来查看进程的文件描述符信息,这对于调试和监控进程非常有用。通过查看文件描述符信息,可以了解进程打开了哪些文件、网络连接等,帮助开发者和系统管理员进行问题排查和分析工作。
1 | 复制代码 隐藏代码bool check_fd() { |
(3)检测文件 众所周知frida我们一般都会放在data/local/tmp目录下,旧版fridaserver端运行时都会释放到re.frida.server,所以这里在旧版也会被当做一个检测点,而新版已不再释放
2、检测map
/proc/self/maps
是一个特殊的文件,它包含了当前进程的内存映射信息。当你打开这个文件时,它会显示一个列表,其中包含了进程中每个内存区域的详细信息。这些信息通常包括:
- 起始地址(Start Address)
- 结束地址(End Address)
- 权限(如可读、可写、可执行)
- 共享/私有标志(Shared or Private)
- 关联的文件或设备(如果内存区域是文件映射的)
- 内存区域的偏移量
- 内存区域的类型(如匿名映射、文件映射、设备映射等)
当注入frida后,在maps文件中就会存在
frida-agent-64.so、frida-agent-32.so等文件。
1 | OP591BL1:/ # cat /proc/22503/maps | grep frida |
检查无非就是检查字符串,想过掉直接hook字符串或者其他的一些东西都可以
anti1:(hook了strstr以及strcmp) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44// 定义一个函数anti_maps,用于阻止特定字符串的搜索匹配,避免检测到敏感内容如"Frida"或"REJECT"
function anti_maps() {
// 查找libc.so库中strstr函数的地址,strstr用于查找字符串中首次出现指定字符序列的位置
var pt_strstr = Module.findExportByName("libc.so", 'strstr');
// 查找libc.so库中strcmp函数的地址,strcmp用于比较两个字符串
var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
// 使用Interceptor模块附加到strstr函数上,拦截并修改其行为
Interceptor.attach(pt_strstr, {
// 在strstr函数调用前执行的回调
onEnter: function (args) {
// 读取strstr的第一个参数(源字符串)和第二个参数(要查找的子字符串)
var str1 = args[0].readCString();
var str2 = args[1].readCString();
// 检查子字符串是否包含"REJECT"或"frida",如果包含则设置hook标志为true
if (str2.indexOf("REJECT") !== -1 || str2.indexOf("frida") !== -1) {
this.hook = true;
}
},
// 在strstr函数调用后执行的回调
onLeave: function (retval) {
// 如果之前设置了hook标志,则将strstr的结果替换为0(表示未找到),从而隐藏敏感信息
if (this.hook) {
retval.replace(0);
}
}
});
// 对strcmp函数做类似的处理,防止通过字符串比较检测敏感信息
Interceptor.attach(pt_strcmp, {
onEnter: function (args) {
var str1 = args[0].readCString();
var str2 = args[1].readCString();
if (str2.indexOf("REJECT") !== -1 || str2.indexOf("frida") !== -1) {
this.hook = true;
}
},
onLeave: function (retval) {
if (this.hook) {
// strcmp返回值为0表示两个字符串相等,这里同样替换为0以避免匹配成功
retval.replace(0);
}
}
});
}
anti2:重定向我们修改之后的maps文件
1 | // 循环读取maps内容,并写入伪造的maps文件中,同时进行字符串替换以隐藏特定信息 |
3.检测status(线程名)
1 | ls /proc/pid/task 列出线程id |
- 在
/proc/pid/task目录下,可以通过查看不同的线程子目录,来获取进程中每个线程的运行时信息。这些信息包括线程的状态、线程的寄存器内容、线程占用的CPU时间、线程的堆栈信息等。通过这些信息,可以实时观察和监控进程中每个线程的运行状态,帮助进行调试、性能优化和问题排查等工作。 - 在某些app中就会去读取
/proc/stask/线程ID/status文件,如果是运行frida产生的,则进行反调试。例如:gmain/gdbus/gum-js-loop/pool-frida等
- gmain:Frida 使用 Glib 库,其中的主事件循环被称为 GMainLoop。在 Frida 中,gmain 表示 GMainLoop 的线程。
- gdbus:GDBus 是 Glib 提供的一个用于 D-Bus 通信的库。在 Frida 中,gdbus 表示 GDBus 相关的线程。
- gum-js-loop:Gum 是 Frida 的运行时引擎,用于执行注入的 JavaScript 代码。gum-js-loop 表示 Gum 引擎执行 JavaScript 代码的线程。
- pool-frida:Frida 中的某些功能可能会使用线程池来处理任务,pool-frida 表示 Frida 中的线程池。
- linjector 是一种用于 Android 设备的开源工具,它允许用户在运行时向 Android 应用程序注入动态链接库(DLL)文件。通过注入 DLL 文件,用户可以修改应用程序的行为、调试应用程序、监视函数调用等,这在逆向工程、安全研究和动态分析中是非常有用的。 PS:由于frida可以随时附加到进程,所以写的检测必须覆盖APP的全周期,或者至少是敏感函数执行前
——–10.26
十二、RPC
hook_RegisterNatives.js:hook打印动态注册的函数
frida -U -f com.zj.wuaipojie -l hook_RegisterNatives.js
hook_GetStringUTFChars.js: 不知道为什么我使用的不太正确
十三、抓包
——–11.2


