yangyunhui

功不唐捐,玉汝于成

Android逆向学习笔记(八)

Android逆向学习笔记(八)

《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩

目录


动态调试

开始调试之前,我们需要做好前置工作

1.修改debug权限

方法1:在AndroidManifest.xml里添加可调试权限

修改xml文件,在application中加入android:debuggable="true"

在apk文件中,这里默认是false的,是不允许任何人随便窥探正在运行的应用程序内部内存和代码执行情况的。如果不把这个开关打开,调试器(如 JEB、Android Studio)就会被拒之门外。

方法2:XappDebug模块hook对应的app

下载好该apk文件之后,在LSPosed中启用XappDebug模块,启用之后重启才可以生效。

然后,打开XAppDebug勾选wuaipojie

这样即使我们没有修改xml文件的debug权限,我们也可以对文件进行debug

方法3:Magisk命令

在cmd中运行命令

1
2
3
4
5
6
7
1.  adb shell #adb进入命令行模式

2. su #切换至超级用户

3. magisk resetprop ro.debuggable 1

4. stop;start; #一定要通过该方式重启

方法4:刷入MagiskHide Props Config模块

一般来说,在4选项中如果有ro.debuggable那就直接修改
没有的话就选5

修改ro.debuggable的值为1

但是这边我使用该命令时,显示Not found不知道为啥

2.端口转发与开启adb调试权限

版本号连续点击7次进入开发者模式,开启USB调试选项

3.下断点

打开jeb,在想要断下来的地方下断点(ctrl+b)

4.debug模式启动

打开终端输入adb shell am start -D -n 包名/入口这里输入的是:adb shell am start -D -n com.zj.wuaipojie/.ui.MainActivity

输入命令之前,需要将adb的环境配置好,在path中加上adb.exe的路径E:\leidian\LDPlayer9\(注意后面不要跟文件名)

5.jeb动态调试

下面开始进行整个过程的复述

先加上android:debuggable="true"

开启开发者模式打开USB调试选项

我们看一下第四关的关键词是什么:

!

打开jeb搜索密钥错误,查找相关代码

check函数如下

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
public final boolean check(String s) {
int v = 0;
Integer integer0 = null;
if(!StringsKt.startsWith$default(s, "flag{", false, 2, null)) {
return false;
}

if(!StringsKt.endsWith$default(s, "}", false, 2, null)) {
return false;
}

String s1 = s.substring(5, s.length() - 1);
Intrinsics.checkNotNullExpressionValue(s1, "this as java.lang.String…ing(startIndex, endIndex)");
String s2 = SPUtils.INSTANCE.getString(((Context)this), "id", "");
if(s2 != null) {
integer0 = (int)s2.length();
}

int v1 = 1000;
Intrinsics.checkNotNull(integer0);
int v2 = (int)integer0;
if(v2 >= 0) {
while(true) {
v1 += -7;
if(v == v2) {
break;
}

++v;
}
}

byte[] arr_b = Encode.encode(s2 + v1).getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(arr_b, "this as java.lang.String).getBytes(charset)");
return Intrinsics.areEqual(s1, Base64Utils.INSTANCE.encodeToString(arr_b));
}

接下来我们需要下断点,但是下在哪里是一个问题

我们可以看看smali代码

这里有两个位置可以选择

  1. 000000E0 invoke-virtual Base64Utils->encodeToString([B)String, v1, v0
  2. 000000E8 invoke-static Intrinsics->areEqual(Object, Object)Z, p1, v0

那么为什么我们将断点断在这里?

000000E0 invoke-virtual Base64Utils->encodeToString([B)String, v1, v0:

  • 这是算法的入口
  • 关注“它是怎么算的”
  • 是在看加工过程

000000E8 invoke-static Intrinsics->areEqual(Object, Object)Z, p1, v0

  • 这是逻辑判断的终点
  • 关注“它算出来是什么”
  • 是在看结果

这两个断点下在这里都可以得到最后的结果

log插桩

log插桩原理

Log 插桩的本质是 “代码注入”。 安卓应用在运行时执行的是 Dalvik 字节码(Smali)。通过反编译工具(如 apktool),我们可以将 .dex 文件转换成可读的 Smali 代码。

  • 基本原理: 我们在目标方法的关键位置(如赋值、函数返回前、判断语句处)手动插入几行 Smali 指令。
  • 执行流程: 当程序运行到你插入代码的地方时,它会先执行你的“日志打印”代码,将你关心的寄存器值传给日志函数,打印到 Logcat 中,然后再继续执行原本的代码逻辑。

操作步骤

将老师给的文件日志插桩.dex放入apk文件中,命名为classes2.dex,如下

在classes.dex文件中添加代码invoke-static {对应寄存器}, Lcom/mtools/LogUtils;->v(Ljava/lang/Object;)V

作用: 程序正常运行时,v0 寄存器里的值(比如解密后的 Key、用户输入的密码、服务器返回的 Token)只存在于内存中,肉眼看不见。

当你插入这行指令后,程序运行到这里会暂停一下,把 v0 里的内容复制一份交给 LogUtils。LogUtils 内部会调用安卓系统的 API,把这个内容变成一行文字,打印到 Logcat 监控窗口里。

具体位置是这里:

1
2
3
4
invoke-virtual {v1, v0}, Lcom/zj/wuaipojie/util/Base64Utils;->encodeToString([B)Ljava/lang/String;

move-result-object v0
invoke-static {v0}, Lcom/mtools/LogUtils;->v(Ljava/lang/Object;)V

注意: 在 Smali 语法中,move-result-object(或 move-result)指令必须紧跟在调用指令(invoke-xxx)之后,中间绝对不能插入任何代码。

(因为之前将代码插在了invoke之后了)

之后保存,安装新的apk文件

将算法助手中的demo打开总开关,再打开Log捕获,之后随便输入一串字符(必须flag{}格式),查看日志。

这个参数值就是真正的key

课后作业

首先是需要打开debug的权限

使用MT管理器查看xml文件

可以看到权限是打开的,那么就不用特定去设置了

方法一:使用jeb动态调试

在jeb中搜索无效用户名查看代码

这里可以看到sn就是我们需要的注册码,按tab转成smali代码看看

将断点下在这里

在终端中输入adb shell am start -D -n com.droider.crackme0201/.MainActivity

需要注意的是:包名/入口的格式

一直往下执行,可以看到变量

这里不知道为什么没有v6的值,可能因为模拟器调试的原因,但是没关系,从上面java代码中也可以知道,计算出md5值之后,取0,2,4….的值(偶数位),这个值就是我们需要的

注册码

方法二:log插桩

在apk文件中加入classes2.dex文件之后

在classes.dex中搜索代码0x7F05000B,因为在这个smali代码中,它用的是16进制表示的中文,我们这里搜中文的话是搜索不到的。

将invoke-static {对应寄存器}, Lcom/mtools/LogUtils;->v(Ljava/lang/Object;)V写进去就ok了

接着查看日志

这个就是我们需要的注册码