adworld-mobile-illusion学习笔记

发布于 1 天前  1 次阅读


关键点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文件的动态调试:

  1. adb启动一下ida的linux_server
    • ./android_server
  2. 新开一个命令窗口,进行端口转发
    • adb forward tcp:23946 tcp:23946
  3. 启动应用
    • adb shell am start -D -n  包名/activity路径
  4. 打开IDA,选择android debug,然后将option的【-3:-1:1】勾上,attach到app进程,记得要提前打下断点
  5. 使用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的个数一个一个试出来的


间桐桜のお菓子屋さん