篇幅有限
完整内容及源码关注公众号:ReverseCode,发送 冲
基本环境
as-create new project-Native C++,Language选择Java引入easyso1自动安装ndk frida启动hook native方法demo.js
frida -U -f com.roysue.easyso1 -l demo.js --no-pause
1 | setImmediate(function(){ |
ghidra jeb 反编译工具
build.gradle中android.defaultConfig.ndk配置abiFilters的cpu为’arm64-v8a’,’x86_64’,’armeabi-v7a’ 多个版本时
1 | objection -g com.android.settings explore 获取系统版本 |
adb install -r -t --abi arm-v7a app-debug.apk
强制指定安装测试版本
https://github.com/android/ndk-samples.git 打开hello-jni加上defaultConfig中添加ndk的abiFilters无效,被productFlavors中ndk的abiFilters覆盖成v8a
md5
编译md5
1 | git clone https://github.com/pod32g/MD5.git |
搜索 ndk cross compile 安卓交叉编译,源码编译成机器码
1 | /Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -target aarch64-linux-android21 md5.c 生成a.out |
整合md5
ndk开发以32位为主
1 | ndk { |
build.gradle中引入的cmake为CMakeLists.txt
1 | externalNativeBuild { |
修改CMakeLists.txt配置so库
1 | # 配置so库 |
修改MainActivity
1 | static { |
新建roysue.c
1 | #include <string.h> |
lldb
调试第三方app有一下两种方案
- app以debug模式启动,apk保重debuggable==true(重打包或xposed/frida去hook)
- aosp系统编译成userdebug模式(n5x/sailfish)
1 | cd ~/Android/Sdk/ndk/22.0.7026061 |
as调试模式Debug时也可以看到LLDB反汇编的结果
1 | apt install neofetch |
Capstone/Keystone跨平台反汇编器,硬编码直接修改so
frida调试native通过dwarf
1 | git clone https://github.com/iGio90/Dwarf.git |
frida
objection -g com.roysue.easyso1 explore
1 | memory list modules 找到libroysue.so |
1 | function dis(address, number) { |
frida -UF -l Intruction.js 打印地址
1 | arm-linux-androidabi-objdump -i libroysue.so |
具体工具查看GNU Binutils
patch so
010Editor安装插件,Templates-Template Repository-ELF.bt,修改so中的值保存,
1 | adb push libroysue.so /sdcard/Download |
反汇编器
ghidra
rm -rf ~/.cache/vmware
1 | apt search openjdk|grep openjdk |
jeb
JEB Decompiler PRO 3.19.1,./jeb_linux.sh
,首次输入密码pwd_ilbtcdnwiuypbzeo_
,License data使用python2 jebKeygen.py激活
打开roysue
ida
binary ninja
radare2
Cutter
C算法
WjCryptLib
1 | git clone https://github.com/WaterJuice/WjCryptLib |
AntiFrida
通过Frida内存特征对maps中elf文件进行扫描匹配特征,支持frida-gadget和frida-server
easyso1
cyberchef加解密,Android Inline Hook中的指令修复详解,Android APP逆向分析基础,Android NDK AES 加解密,**aesTool实现aes加解密**
CMakeLists.txt
1 | add_library( # Sets the name of the library. |
首先,我们要创建一个 Android 工程,还有一个 MainActivity
1 | public class MainActivity extends AppCompatActivity { |
对应的 c 文件 roysue.c
1 | #include <string.h> |
编译好后使用ghidra分析,New project后新建文件夹
1 | 7z x ghidra_9.2.1_PUBLIC_20201215.zip |
右侧反编译后的代码已经被混淆
frida
根据方法名获取地址frida_hook_libart,** hook_ArtMethod_RegisterNative**
1 | setImmediate(function () { |
frida -UF -l FridaInvokeNative.js invoke()
ExAndroidNativeEmu
1 | git clone https://github.com/maiyao1988/ExAndroidNativeEmu.git 基于Unicorn的模拟器 |
easyso1.py
1 | import logging |
gdb调试
- 可调试内核
- 支持全平台
- qemu内置
- 内核硬件断点,开启内存断点
建议使用aosp810自行编译的系统,将app编译成带调试符号的so
1 | tree -NChfl | grep -i gdb |
配置CMakeLists.txt 允许调试模块
1 | SET(CMAKE_BUILD_TYPE "Debug") |
在build.gradle的android模块中加入
1 | packagingOptions{ |
编译好后解包拿到libroysue.so
1 | objdump -T libroysue.so 导出表 |
HyperPwn调试
apt –fix-broken install
dpkg -i hyper_3.1.0-canary.4_amd64.deb
1 | su kali 使用非root用户权限运行 |
安装插件需要代理,虽然proxychains可以代理,但是只能单个命令使用
1 | vim /etc/redsocks.conf |
1 | redsocks 直接启动,ss -nltp|more 检查代理是否启动 |
sh iptables.sh
1 | #不重定向目的地址为服务器的包 |
安装插件
1 | hyper i hyperinator |
切换用户到kali
1 | hyper 更新插件报错error An unexpected error occurred: "...getaddrinfo ENOENT raw.githubusercontent.com". |
1 | vim /etc/hosts |
切回用户root
1 | adb push ~/Android/Sdk/ndk/22.0.7026061/prebuild /data/local/tmp |
切回kali插件安装
1 | git clone https://github.com/pwndbg/pwndbg.git |
1 | 经常忘记:kali开sshd服务: |
开始attach,通过查看app架构为32位
1 | ps -e|grep roysue 父进程id为3109,进程id为13709 |
切换到kali账户
1 | hyper |
1 | objection -g com.roysue.easyso1 explore |
1 | b *0xd3c31377 |
arm汇编
1 | file ~/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/* |
unicorn
hello.c
1 | #include <stdio.h> |
编译
1 | clang -target armv7a-linux-android21 -E hello.c -o hello.i 预编译 |
全局科学
1 | sh iptables.sh |
调试
1 | ./Hyper-3.1.0-canary.4.AppImage --no-sandbox |
1 | target remote 192.168.3.13:23946 |
1 | adb shell |
hex(0xaae13000+0x1410)=0xaae14410 是main函数的起始地址,但是断点却下在了0xaae1441c,ARM三级流水线,pc值=当前指令内存地址+8。
1 | b *0xaae14410 因为已经过了断点,重启gdbserver,在hyper中重打断点,但是每次启动地址可能不一样 |
结合frida
1 | clang -target arm-linux-android21 -mthumb hello.c -o hellogetchar |
hello.s
1 | .text |
编译
1 | clang -target armv7a-linux-android21 hello.s -o helloLAST |
objdump
1 | clang -target armv7a-linux-android21 -mthumb helloTHUMB.s -o helloTHUMB |
主动执行汇编
1 | import unicorn |
Unidbg
基于Unicorn(直接执行arm汇编),配合libc.so,libart.so等so库对精简安卓系统的模拟实现。
1 | function stringToByte(str) { |
cat /proc/10783/maps | grep system/lib64
IDA
看雪第三题
对frida做了检测,输入账户密码后上frida闪退
IDA64bit打开libnative-lib.so,查看导出表,shift+F12查看so中的所有字符串,c++filt _ZN7_JNIEnv15RegisterNativesEP7_jclassPK15JNINativeMethodi
,IDA会自动将这些字符串通过c++filt转为导出函数
进入init函数,F5反编译
1 | __int64 init() |
进入detect_frida_loop,可以尝试不要检测任意地址0.0.0.0
,或者strcmp(&v2, "REJECT")
不相等即可
1 | void __fastcall __noreturn detect_frida_loop(void *a1) |
双击0.0.0.0
,进入只读段的汇编代码
点中”0.0.0.0”,进入Hex View-1
C编码是ASCII码表,JAVA是UNICODE,尝试hex-to-ascii
Swap后将2.2.2.2转为十六进制
F2修改So中的Hex,改完后再F2再应用
保存
回到Pseudocode-C重新F5反编译刷新
一般使用apk d 3.apk
,再通过将so放到解开的文件夹中替换,apktool b 3 -o 3_mod.apk
,但是没有签名无法安装,签名回编译uber-apk-signer,java -jar uber-apk-signer-1.1.0.jar -a 3_mod.apk --allowResign
完成自动签名,重新安装即可。
回到Pseudocode-C页面,点中REJECT
双击进入IDA View-A,点中REJECT,进入Hex View-1
将REJECT改成“666666”
同样F2修改原字符串的十六进制,F2应用并保存
重新解析
完成过frida检测。
adb shell && ls /system/lib64
查看所有so
frida-ps -Ua
查看所有进程的包名
Frida尝试hook so层的函数
frida -U -f com.kanxue.pediy1 -l demo.js 启动后使用%resume重新加载
1 | function hookstrcmp(){ |
52第三题
https://www.52pojie.cn/thread-1369661-1-1.html
https://www.52pojie.cn/thread-1378761-1-1.html
https://www.52pojie.cn/thread-1371527-1-1.html
https://www.52pojie.cn/thread-1383999-1-1.html
1 | adb install 2021wuai.apk |
直接ida打开libnative-lib.so
,搜索check,或者查看shift+f12查看so中的字符串
查看Java_cn_pojie52_cm01_MainActivity_check
,并F5查看源代码,修改类型
Frida开始hook frida-ps -Ua 查看所有包名
frida -U -n com.pojie52.cm01 -l 2021wuai.js 调用inline_hook()
1 | // sub_B90((int)dest, v7, "areyousure??????"); |
调试frida又想调试ida,需要先跑frida再挂IDA,F2加上断点方便对比,8AC
1 | adb push android_server64 /data/local/tmp |
新起IDA
点击放行,由于so基地址通过frida拿到为0x73bdd49000+8AC=0x73bdd498AC,可以用python十进制转十六进制,hex(0x73bdd49000+0x8AC)G直接跳转,C转成字节码
对比静态分析
打上断点
点击屏幕删干掉验证按钮,F8单步调试,查看X0,就是我们输入的字符串值
以上是两种获取寄存器中值的方案。
hook方法sub_B90对输入内容加密和sub_D90是一个base64编码
1 | v29 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); |
在sub_B90(dest, v7, "areyousure??????");
获得加密拿到新dest后,v8 = strlen(dest);是dest长度, v9 = sub_D90(dest, v8);是base64加密,后续在while ( v9[v16] == v19[v16] )
中判断是否校验成功,和上面if ( (*a1)->GetStringUTFLength(a1, a3) != 30 )
一样判断一旦没有30位直接校验失败,只有返回1才校验成功,即v9[v16] == v19[v16]
。获取此时v19的地址0xB30,base64(密文)=v19
1 | var libnative = Module.findBaseAddress("libnative-lib.so"); |
打印5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8
,IDA调试如下
sub_B90中我们传入的a1参数只有最后进行了异或操作并重新赋值得到密文。
明文 ^ 密钥 = 密文
明文 ^ 密文 = 密钥
上述方案可得我们根据明文及密文可以拿到密钥,三十个字符总共需要异或30次
依次类推拿到
十进制密钥=[209, 90, 6, 144, 68, 230, 199, 229, 222, 40, 247, 242, 102, 145, 200, 133, 66, 223, 249, 224, 130, 1, 43, 59, 56, 99, 55, 189, 46, 77]
十六进制密钥=[0xd1,0x5a,0x6,0x90,0x44,0xe6,0xc7,0xe5,0xde,0x28,0xf7,0xf2,0x66,0x91,0xc8,0x85,0x42,0xdf,0xf9,0xe0,0x82,0x1,0x2b,0x3b,0x38,0x63,0x37,0xbd,0x2e,0x4d]
或者通过hook寄存器方式拿到EOR地址为D58,需要的寄存器是x12
1 | var ishook = true; |
python实现,返回52pojieHappyChineseNewYear2021
就是明文,在sub_B90与密钥[0xd1,0x5a,0x6,0x90,0x44,0xe6,0xc7,0xe5,0xde,0x28,0xf7,0xf2,0x66,0x91,0xc8,0x85,0x42,0xdf,0xf9,0xe0,0x82,0x1,0x2b,0x3b,0x38,0x63,0x37,0xbd,0x2e,0x4d]
异或返回dest,在sub_D90中加密得到密文5Gh2/y6Poq2/WIeLJfmh6yesnK7ndnJeWREFjRx8
1 | import base64 |
将keypatch,**findcrypt-yara**放入IDA/plugin目录
1 | pip install Keystone |
F5刷新伪代码永远返回1
将改好的libnative-lib.so替换
1 | aoktool d 2021wuai.apk 解包并替换so |
https://github.com/patrickfav/uber-apk-signer
1 | java -jar uber-apk-signer-1.2.1.jar -a 2021wuai_mod.apk --allowResign 重签名 |
JNI
ndk是安卓开发工具包,用于快速开发C/C++动态库,jni作为java原生接口交互c++规范,分为动静态注册
app\build.gradle
1 | android { |
加固流程
- init_array
- JNI_onLoad
- 反调试过得比较粗糙
- 自定义linker加载第二个so
- 手工修复elf_header
- 动态注册函数Interface11
- native化java函数分发器分析
- 根据注册vmp方法时的描述信息执行分支
- 分析指令映射表
1 | objection -g com.roysue.easyso1 explore |
动态加载
IDA从导出表中JNIEnv::RegisterNatives引用,找到动态加载调用的地方,双击到IDA View-A中找到动态注册的函数。
1 | frida -U -f com.roysue.easyso1 -l hook_RegisterNatives.js --no-pause |
roysue.cpp源码
1 | JNIEXPORT jstring JNICALL Java_com_roysue_easyso1_MainActivity_stringFromJNI( JNIEnv* env, jobject thiz ) { |
MainActivity源码
1 | public class MainActivity extends AppCompatActivity { |
动静态hook
1 | var is_hook_libart = false; |
主动调用并hook
1 | function hookJava(){ |
Native层主动调用
1 | function hook_RegisterNatives() { |
git clone https://github.com/GravityBox/GravityBox.git 安装到xposed组件
1 | XposedHelpers.getObjectField=getDeclaredField+setAccessible+getAccessible |
plugin wallbreaker classdump android.os.Build 查看系统参数信息
简单风控
roysue.cpp
1 | #define TAG "roysuejni" // 这个是自定义的LOG的标识 |
unidbg
1 | public class MainActivity extends AbstractJni { |
frida
1 | setTimeout(function () { |
OnCreate的Native化
看jni签名的方法,jadx的smali面板
MainActivity
1 | static { |
roysue.cpp
1 | // 动态注册不需要extern "C" |
frida
1 | var ArtMethod_PrettyMethod |
Android JNI(一)——NDK与JNI基础讲述了局部引用和全局引用的概念
JNI动静态绑定和追踪
一种通用超简单的Android Java Native方法Hook
Dalvik&Art_RegisterNativesTrace 目录下打印log
1 | void ononon2(JNIEnv *env, jobject thiz, |