从OWASP CrackMe学Android逆向(一)
## 准备环境、工具Pixel(Magisk+Xposed/EdXposed)
这里要安利一下Magisk,它现在有个超强的Magisk Hide功能,能隐藏root状态,不过我还没研究过它的原理,如果后面有机会的话会稍微整理一下的。
## UnCrackable-Level1
进入到存有UnCrackable的文件目录下,`adb install UnCrackable-Level1.apk`把APK安装到手机上。打开应用之后,发现应用存在root检测,一旦发现了root状态,点击OK之后应用就会被kill。
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919408/OWASP/1.png)
可以反编译apk之后patch root检测部分的内容,重打包之后安装到手机里面绕过root检测,也可以用Magisk自带的Magisk Hide绕过。Magisk Hide设置方法如下:
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919459/OWASP/2.png)
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919460/OWASP/3.png)
当然第二张图里会不一样,我们需要选中UnCrackable-Level1.apk。
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919460/OWASP/4.png)
随便输个内容,按下VERIFY,发现出现了个弹窗,要我们再试一下。OK,可以看出我们应该从反编译apk开始入手,找找它这个验证的代码逻辑,看看能不能找到正确的字符串,或者看看有没有方法绕过这样的条件判断。
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919461/OWASP/5.png)
如果代码量过大的话,我们可以通过`adb shell dumpsys activity top | findstr ACTIVITY`定位要找的界面的代码。像下面这样,就能定位到界面
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919463/OWASP/6.png)
不过用JEB看一下,发现这个代码量真的不是很大....用JEB打开MainActivity之后看到一个verify的函数没有经过混淆,看代码逻辑,判断的条件由a.a()函数传递,返回的是一个boolean类型的值。如果Level1真要让我们弹出Success的话,就没什么意思了,那就跟进a.a()函数里面看看。
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919464/OWASP/7.png)
发现a.a()中调用了加密的算法,之后将用户输入的值也就是arg5与加密后的结果进行比对,若比对成功的话,就说明我们输入的是正确的。明显这个crackme的意图不是要我们patch源代码然后得到弹窗Success, 而是获得flag, 也就是密文`5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=`解密后的结果。
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919464/OWASP/8.png)
### CTF解题法
可以使用动态和静态两种方法获得明文, 静态的方法参照文章(https://nankeen.me/posts/owasp-android-level1/), 文章的作者使用了openssl和硬编码在源码中的密钥`8d127684cbc37c17616d806cf50473cc`解密`5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=`。
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919465/OWASP/9.png)
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919466/OWASP/10.png)
通过代码我们可以知道AES算法使用了ECB加密模式, 且在CTF中AES-128加密算法的密钥一般为32位, 输入命令`echo 5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc= | openssl enc -aes-128-ecb -base64 -d -nopad -K 8d127684cbc37c17616d806cf50473cc`可获取flag`I want to believe`。
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919467/OWASP/11.png)
### Xposed解法
感觉上面的方法太巧妙了, 下面会补充一个没那么巧妙且常用的方法——Xposed hook, 我一开始找到函数a.a(), 但是因为参数类型填写不正确的原因, 加上该函数实际上返回的是`byte []`类型的数据, 所以不能得到正确的flag, Xposed模块param.getResult()的返回值又是String类型的数据,所以在这里我们需要处理数据转换的问题。具体的Xposed模块代码如下:
```
package com.example.unlock;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class HookMain implements IXposedHookLoadPackage {
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if(!lpparam.packageName.equals("owasp.mstg.uncrackable1")) //过滤包名
return;
XposedBridge.log("Loaded app: " + lpparam.packageName); //Hook a方法
try {
XposedHelpers.findAndHookMethod("sg.vantagepoint.a.a", lpparam.classLoader, "a", byte [].class, byte [].class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
}
protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
// 转换数据类型
String flag = new String((byte []) param.getResult());
// 在这里使用Log.i()可能会输出失败, 最好还是用XposedBridge.log
XposedBridge.log("SECRET: " + flag);
}
});
} catch (Throwable e){
XposedBridge.log("hook failed");
XposedBridge.log(e);
}
}
}
```
安装Xposed模块并重启之后,随便输入之后提交,得到flag`I want to believe`。
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919467/OWASP/12.png)
### Frida解题法
还可以按照wp (https://enovella.github.io/android/reverse/2017/05/18/android-owasp-crackmes-level-1.html)使用Frida hook代码获取flag。
Frida使用的过程中不能和Magisk Hide一起使用, 不然会出现`Failed to spawn: unable to access zygote64 while preparing for app launch; try disabling Magisk Hide in case it is active`的报错。所以我们需要先关掉Magisk Hide(Magisk Manager > Settings >Magisk > Magisk Hide (关掉)), 并且还要在Frida脚本里面绕过root的检测。
因为之前没有接触过Frida, 也算是用这个实例来学习Frida的使用, 如果跟我一样不是很熟悉Frida, 可以配合(https://www.frida.re/docs/javascript-api/#java)一起食用, 文档中有Frida的js API使用说明, 下面的代码主要涉及的是调用Java函数的部分。作者在这里写的绕过root和debug的思路我觉得值得学习和思考, 有种听君一席话, 胜读十年书的感觉。当然, 这样讲还是有点夸张的, 不过真的很值得学习。
```
Java.perform(function () {
send("Starting hooks OWASP uncrackable1...");
/*
hook java.lang.System.exit, 使该函数只用来输出下面的字符串
避免了应用的检测机制导致应用退出, 使用该方法绕过Java层的root/debug检测
*/
var sysexit = Java.use("java.lang.System");
sysexit.exit.overload("int").implementation = function(var_0) {
send("java.lang.System.exit(I)V// We avoid exiting the application:)");
};
var aes_decrypt = Java.use("sg.vantagepoint.a.a");
aes_decrypt.a.overload("[B","[B").implementation = function(var_0,var_1) {
send("sg.vantagepoint.a.a.a([B[B)[B doFinal(enc)// AES/ECB/PKCS7Padding");
send("Key : " + var_0);
send("Encrypted : " + var_1);
/*
重载解密函数, 并获取其返回值, 因其类型为byte [],
js在调用Java方法之后只能返回一个对象, 而不是返回一个byte类型的数组
*/
var ret = this.a.overload("[B","[B").call(this,var_0,var_1);
send("Decrypted : " + ret);
var flag = "";
//将char类型转换为String类型
for (var i=0; i < ret.length; i++){
flag += String.fromCharCode(ret);
}
send("Decrypted flag: " + flag);
return ret; //[B
};
var mainactivity = Java.use("sg.vantagepoint.uncrackable1.MainActivity");
mainactivity.onStart.overload().implementation = function() {
send("MainActivity.onStart() HIT!!!");
var ret = this.onStart.overload().call(this);
};
//var mainactivity = Java.use("sg.vantagepoint.uncrackable1.MainActivity");
mainactivity.onCreate.overload("android.os.Bundle").implementation = function(var_0) {
send("MainActivity.onCreate() HIT!!!");
var ret = this.onCreate.overload("android.os.Bundle").call(this,var_0);
};
var activity = Java.use("android.app.Activity");
activity.onCreate.overload("android.os.Bundle").implementation = function(var_0) {
send("Activity HIT!!!");
var ret = this.onCreate.overload("android.os.Bundle").call(this,var_0);
};
send("Hooks installed.");
});
```
这里小小啰嗦一下Frida的使用方法, 安装的话直接`pip install frida-tools`, 还有下载适合自己设备的frida-server的版本, push进设备, 加上权限就行。下面是对frida-server进行的操作的操作:
```
$ adb shell
$ su
# cd /data/local/tmp # 进入frida-server的目录下
# ./frida-server &
```
之后重新开一个cmd窗口, 进入.js脚本, 也就是hook的脚本所在的目录下执行`frida -U owasp.mstg.uncrackable1 -l cracker.js`(我的hook文件名为cracker.js),就能获取flag:
![](https://res.cloudinary.com/dq4c0zqqy/image/upload/v1572919470/OWASP/13.png)
### 结语
其实在使用Frida绕过应用检测的时候我想到一个问题, 我认为重载` java.lang.System.exit`的方法只能绕过Java层调用的exit函数, 而不能绕过native层的函数。Java层,测试后已知hook`java.lang.System.exit`是不能阻止应用被系统层强制退出的。而native层的, 还没遇到一个这样的demo可以用来测试, 但是我猜测native层的还是需要hook native层的exit函数。如果跳出了这两个层级, 直接对系统层的exit进行注入, 会导致系统崩溃吗?如我的思考或文章出现了错误,希望各位大佬不吝赐教。
最后,附上(https://github.com/OWASP/owasp-mstg/tree/master/Crackmes/Android)。 SrEGS8Y4SJAGEZi 发表于 2019-11-20 15:32
最近在看这个,但是遇到了问题
我用jadx看java代码时看到调用sg.vantagepoint.uncrackable1.a 是直接调用 ...
很抱歉现在才回复你。我看了一下你问题的描述,有点不够清晰, 可能理解会出错,我尝试理解是这样的:为什么不hook sg.vantagepoint.uncrackable1.a.a, 而选择hook sg.vantagepoint.a.a?
上述问题的答案:
我们选择hook的函数,有一种是为了获取该函数的参数或者返回值,还有一种情况是为了那个函数不按照它原本的代码执行,而按照你想要方式执行(你对这个函数进行改写)。第一种情况也就是你所描述的这个,为了获取加密前的结果,我们选择hook sg.vantagepoint.a.a,获取加密的密钥var_0和密文var_1,之后再调用函数进行解密,所以在所有的函数中选择sg.vantagepoint.a.a是比较合理的,能达成我们解密的目的。而第二种改写的情况,就是文章中sysexit.exit.overload("int"),我们通过重写java层的exit函数,阻止应用检测到root或debug的状态后闪退。
具体的情况可以按照你想实现的功能去选择你想要hook的函数,希望能帮到你。 楼主你好,有个不太明白的地方我是一个安卓很白的一个人, 在“进入到存有UnCrackable的文件目录下,adb install UnCrackable-Level1.apk把APK安装到手机上。打开应用之后,发现应用存在root检测,一旦发现了root状态,点击OK之后应用就会被kill。”中有一句 adb install UnCrackable-Level1.apk把APK安装到手机上 这个我不太懂,难道不是直接下载安装就好了么为什么要用命令 这个命令在什么工具上用? 万紫千红一般春 帮忙顶一下 牛逼啊 大佬 第一次在吾爱看得懂这是什么代码 感谢楼主,准备学。 支持一下,发贴不容易 看来我要学的还有很多很多, 最近对这个感兴趣,谢谢分享 学习了
感谢分享!
页:
[1]
2