“既然认准了一条路,就不要去打听要走多久。” ——卢思浩
接上篇
5. 动态调试
5.1 环境和工具
java11/8
MT管理器/NP管理器
jeb
雷电模拟器
XappDebug
教程demo
5.2 动态调试
什么是动态调试?
动态调试是指自带的调试器跟踪自己软件的运行,可以在调式的过程中知道参数或者局部变量的值以及捋清代码运行的先后顺序,多用于爆破注册码。
在进行调试之前我们需要调试的apk具有debug权限,开启debug权限的方式有四种
1.在AndroidMainfest.xml里添加可调式权限
2.XappDebug模块hook对应的app
3.Magisk命令(重启失效)
1)adb shell #adb进入命令行模式
2)su #切换至超级用户
3)magisk resetprop ro.debuggable 1
4)stop;start; #一定要通过该方式重启
4.刷入MgiskHide Props Config模块(永久有效,最好用真机,我这里用模拟器没成功)
接下来详细介绍前两种方法:
方法一:在AndroidMainfest.xml里添加可调试权限
在AndroidMainfest.xml中添加代码android:debuggable="true"
方法二:XappDebug模块hook对应的app
安装XappDebug软件,在LPosed中启用模块并勾选要hook的app和系统框架,然后打开XappDebug勾选要hook的app,如果打开后找不到要hook的app,在右上角勾选Show Already Debuggable Apps
按照上述方法开启了debug权限后,接下来需要开启端口转发以及adb权限
设置->关于平板电脑-点击七次版本号,打开开发者模式
设置->系统->高级->开发者选项->开启USB调试
雷电模拟器自带端口转发,其他模拟器需要开启端口转发
开始调试:
在jeb中打开调试的App
adb启动debug模式
adb shell am start -D -n com.zj.wuaipojie/.ui.MainActivity
com.zj.wuaipojie:包名
.ui.MainActivity:类名
am start -n 表示启动一个activity
am start -D表示将应用设置为可调试模式
类名(以main启动的Activity的name):
先来看看教程demo,要求输入正确的密钥,直接点击验证弹出“密钥错误哦,再想想!”
我们在jeb中搜索弹窗字符串
跳转到代码位置,转java查看
protected void onCreate(Bundle bundle0) {
super.onCreate(bundle0);
this.setContentView(0x7F0B001E); // layout:activity_challenge_fourth
((Button)this.findViewById(0x7F08006B)).setOnClickListener((View view0) -> {
if(this.check(((EditText)this.findViewById(0x7F0800AC)).getText().toString())) { // id:edit_check
Context context0 = (Context)this;
Toast.makeText(context0, "恭喜你,密钥正确!", 1).show();
SPUtils.INSTANCE.saveInt(context0, "level", 3);
return;
}
Toast.makeText(((Context)this), "密钥错误哦,再想想!", 1).show();
});
}
代码解析(大致意思):判断语句中调用了check方法,check中传入的参数为根据Id获取到的文本再转换为字符串。check方法执行后会返回一个布尔值,如果值为true,则密钥正确。
那么我们就去看看check方法中的代码:
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));
}
代码解析(大致意思):
首先在前两个if中分别判断了密钥是否以"flag{"开头和"}"结尾,这里确定了提交密钥的格式。
接下来获取了我们传入的字符串s中间的字符串,经过处理后赋给了s1,并且在后面的代码中将其与由id生成的字符串s2(经过处理后为Base64Utils.INSTANCE.encodeToString(arr_b))在areEqual函数中做了比较,如果相等则返回true,所以显然我们这里需要获取与s1进行比较的字符串,它就是我们的密钥。
回到smali代码,ctri+b下断点(经分析,v0的值为我们需要获取的值)
然后在cmd执行:adb shell am start -D -n com.zj.wuaipojie/.ui.MainActivity(前提adb配置了环境变量),看到如下图所示即为成功:
然后点击jeb中debug图标
点击附上:
然后在教程demo页面输入密钥值触发断点
调试进入areEqual函数:
结束调试后输入密钥: