
关键点1:系统调用native-lib
关键点2:new内部类
果然看到了String的flag


而这个checkflag方法是原生类中的,就需要去检查一下so文件
通过一个类似加密的字符串直接找到checkflag函数,可以看到函数javastring2Cstr,说明和JNI是脱不了干系的

for ( i = 0; i < j_strlen(v9); ++i )
v8[i] = (sub_10C0(v9[i] + aE116c5c66e7b37[i] - 64, 93) >> 32) + 32;
最关键的加密循环
想要搞个动态调试,把调试机环境整一整
[Android] adb shell开启root权限(Mi 8) - 知乎 (zhihu.com)
adb reboot bootloader
fastboot flash recovery D:\\twrp-3.6.2_9-0-dipper.img

用adb forward tcp:23946 tcp:23946
使得Pc端的IDA连上这个端口,进行ida调试端口转发
调试模式挂起app
adb shell am start -D -n [aok包名]/[主活动]
adb shell am start -D -n monkeylord.illusion/.MainActivity
进入Sdk安装目录 \\Sdk\\tools
下,运行 monitor.bat
脚本启动
然后在ida中附加进程
jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700
localhost可以填127.0.0.1


总而言之,启动so文件的动态调试:
- adb启动一下ida的linux_server
- ./android_server
- 新开一个命令窗口,进行端口转发
- adb forward tcp:23946 tcp:23946
- 启动应用
- adb shell am start -D -n 包名/activity路径
- 打开IDA,选择android debug,然后将option的【-3:-1:1】勾上,attach到app进程,记得要提前打下断点
- 使用jdb将app恢复执行
- jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600

接下来付诸实践
在静态分析的ida窗口中找到关键函数和JNI_LOAD的函数偏移


JNI_Onload : C34
关键函数:A7C
附加进程的动态调试IDA窗口,查看消息窗口是否已经进行加载目标so文件,ctrl s 再ctrl f找到目标so文件

计算出实际函数的地址:BC9184CE + A7C = BC918F4A,然后G跳转,打下断点
这道题不能进行断点调试是因为根本没有调用上这个函数。
(32条消息) 安卓逆向笔记-IDA动态调试so_HeartCircle的博客-CSDN博客_ida动态调试
这里有不能反调试的做法,和这题的解题思路在JNI_Onload中撞上了
首先了解一下JNI_ONLOAD函数的作用:动态注册函数
JNI_ONLOAD
在应用层加载so的时候,虚拟机首先回去/自动执行JNI_OnLoad 。在JNI_OnLoad函数中进行函数映射,将java里面的方法映射到自己实现的方法。
在so被成功卸载时,会回调另一个JNI方法:JNI_UnOnLoad。这两个方法声明如下:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);
第一个参数vm表示DVM虚拟机
//注册本地方法,第一个是方法对应的类,第二个是方法映射,第三个是映射方法的个数
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)
{ return functions->RegisterNatives(this, clazz, methods, nMethods); }
其实也就是说
如果要注册,只需要两步,首先FindClass,然后RegisterNatives。

v4是registernative的return值,dword_4010在前面的findclass找到了方法所对应的类,然后off_4004就是映射的函数,跟进去看一下off_4004

可以看到其实最后补充了一个DCD sub_DC8+1。接下来,我们学习一下arm指令集

可以看到终于在sub_DC8载入内存后sub_BC782DC8成为内存地址的函数

在app上点击check之后,就能打到这个函数断点。

最主要的就是这个循环里是怎么操作的

在汇编中是这样一个循环


在j_strlen处打下断点,可以看到r1与r0寄存器中存放了Flag,也就是用户输入字符串。

通过这个cmp,可以得知,r1相当于循环中的i,r0就是len() ,BCS也表明了跳出循环,那就只需要看一下下面的B跳转了。
在动态调试后,我们先回到这个循环

aLjavaLangStrin_0固定字符串为(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; ,同时第二个参数固定为93,

而进入sub_10C0后,a2=93,固定进入sub_1028函数

在汇编代码中,BEQ就相当于else跳转了sub_10ac,所以进入SUB_BC783028

看到位移和减法,立马就想到了计算机组成的除法运算的底层算法“恢复余数” ,果然查询资料就可以得到arm是没有除法运算的,原码的除法运算就是通过“恢复余数”进行计算的
浅谈ARM平台汇编如何进行整数除法运算 - 知乎 (zhihu.com)
那么这个函数的作用就显而易见了,加密的逆向也就更加简单了,

在jadx中找到

读取了asset的Flag文件

Ku@'G_V9v(yGS
v8 = "Ku@'G_V9v(yGS"
langstr="(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
v10 = []
for i in v8:
#print(i)
v10.append(ord(i))
for i in range(len(v10)):
if i in [0,1,6,8,10,11]:
v10[i] = chr(v10[i]-32 + 64 -ord(langstr[i]))
else:
v10[i] = chr(v10[i]-32 + 93 + 64 -ord(langstr[i]))
print(''.join(v10))
这里的else是通过输出对+93的个数一个一个试出来的
Comments | NOTHING