嗨👋,最近工作有亿点忙,难得请了假,终于有机会能写点有趣的东西啦。之前的文章大部分都是实战挖洞思路,所以今天还是想写点逆向相关的内容,顺便充实一下我的安卓逆向专栏🚩
0x01 前言
玩Frida,光看理论可不行,还是得找个 App 练练手。商业应用的复杂性往往令人望而却步,而我自己编写的演示 App 又因能力所限,逻辑过于简单,缺乏挑战性,没什么实战意义。(想看之前的逆向文章,可以看看公众号的主页底部的动态,找到逆向专栏)所以,我更喜欢用 CTF 题目来练一下Frida,顺便提高自己的逆向思维。可惜国内这方面的优质题目不多,我只好找找国外的。真巧,今天就找到一个超级有意思的 APK,所以记录一下解体思路📝
0x02 Oh,Bugger(噢,讨厌鬼)
题目描述:
Hello agent,
Our field operatives
========巴拉巴拉讲了一大堆废话(省略)=========
to find a way to retrieve it.
Good luck!
题目原名就是标题“Oh,Bugger”,先把apk下载到手机上:
0x03 源码分析
JEB启动!!
因为刚刚点击按钮的时候,出现了一个字符提醒“bugger off”,所以直接搜索这个字符串,看看是哪段代码触发了这个提示。
在MainActivity类⬇️
也就是(com.w.buggeroff.MainActivity)的onCreat方法中找到了这个字符串。关键代码1:
if(!MainActivity.setOperation(0)) {
Toast.makeText(context0, "bugger off!", 0).show();
return;
}
try {
this.cipher = Cipher.getInstance(this.alg);
byte[] arr_b = GenerateKeys.getKey(context0);
SecretKeySpec secretKeySpec0 = new SecretKeySpec(Arrays.copyOfRange(arr_b, 0, 0x20), this.alg);
IvParameterSpec ivParameterSpec0 = new IvParameterSpec(Arrays.copyOfRange(arr_b, 0x30, 0x40));
this.cipher.init(2, secretKeySpec0, ivParameterSpec0);
byte[] arr_b1 = Base64.decode("iGWkBaxepj8l7BrKpeIntuEjRqHv3Tt41hRw7w+UwwcXTrlb/l9tELh9RflIpyDT", 0);
this.cipher.doFinal(arr_b1);
}
catch(NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | InvalidAlgorithmParameterException generalSecurityException0)
{
generalSecurityException0.printStackTrace();
}
try {
// 1. 获取加密器实例
this.cipher = Cipher.getInstance(this.alg);
// 2. 获取密钥
byte[] key = GenerateKeys.getKey(context);
// 3. 初始化加密器
this.cipher.init(2, // 模式:2 通常表示解密模式 (Cipher.DECRYPT_MODE)
new SecretKeySpec(Arrays.copyOfRange(key, 0, 32), this.alg), // 从获取的key中取前32字节作为密钥
new IvParameterSpec(Arrays.copyOfRange(key, 48, 64))); // 从获取的key中取第48到63字节作为初始化向量(IV)
// 4. 执行解密/加密操作
this.cipher.doFinal(Base64.decode("iGWkBaxepj8l7BrKpeIntuEjRqHv3Tt41hRw7w+UwwcXTrlb/l9tELh9RflIpyDT", 0));
} catch (InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e) {
e.printStackTrace();
}
让Frida使得setOperation(0)总是返回true,这样!setOperation(0)就会永远为false,因此,代码才能往下走,否则就会一直打印“bugger off!”
第二个hook,查看javax.crypto.Cipher类中的doFinal方法处理的数据,特别是尝试将处理的字节数据转换成字符串打印出来,希望能从中找到“Flag”。
0x03 Frida hook
Java.perform(function() {
var buggeroff = Java.use("com.w.buggeroff.MainActivity");
var cipher = Java.use("javax.crypto.Cipher");
// 定位到 buggeroff (MainActivity类) 中的 setOperation 方法。
// .overload("int") 指定我们要Hook的是接受一个整型(int)参数的那个 setOperation 方法版本。
buggeroff.setOperation.overload("int").implementation = function(i) { // 参数 i 对应Java方法中的 int 类型参数
// 当被Hook的 setOperation 方法被调用时,在Frida控制台打印一条日志信息。
console.log("setOperation 永远为 true");
// 强制该方法总是返回布尔值 true,无论其原始逻辑或输入参数 i 是什么。
return true;
};
// 定位到 cipher (javax.crypto.Cipher类) 中的 doFinal 方法。
// .overload('[B') 指定我们要Hook的是接受一个字节数组(byte[])作为参数的那个 doFinal 方法版本。
cipher.doFinal.overload('[B').implementation = function(b) { // 参数 b 对应Java方法中的 byte[] 类型输入数据
// 当被Hook的 doFinal 方法被调用时,在Frida控制台打印一条日志信息。
console.log("doFinal方法被调用了");
// 调用 this.doFinal(b) 来执行原始的、未被修改的 Cipher.doFinal(byte[] input) 方法。
// 并将其返回值(一个byte[],通常是加密或解密的结果)存储在 retVal 变量中。
var retVal = this.doFinal(b);
// 将 Java 的 byte[] (即 retVal) 包装成JavaScript 数组 buffer。
var buffer = Java.array('byte', retVal);
// 初始化一个空字符串,用于累积从字节转换过来的字符。
var result = "";
// 循环遍历 buffer (即 doFinal 方法返回的字节数组) 中的每一个字节。
for(var i = 0; i < buffer.length; ++i){
// String.fromCharCode(buffer[i]) 将当前字节的数值当作字符编码值(如ASCII)转换为对应的字符。
// 然后将转换后的字符追加到 result 字符串。
result+= (String.fromCharCode(buffer[i]));
}
// 在Frida控制台打印 "Flag: " 后跟着上面转换得到的字符串 result。
console.log("Flag是: " + result);
// 将原始 doFinal 方法的返回值 retVal 返回给调用者,以确保应用程序的正常流程。
return retVal;
};
}, 0);
有对Frida脚本格式和框架不熟的可以看看Eureka公众号主页动态栏中的安卓逆向内容,也就是公众号的第一篇文章,里面有介绍,这里就不再赘述,每一行我都写了备注,非常清晰。
frida-ps -Ua
frida -U -f com.w.buggeroff -l zangcc_bugger.js --no-pause
完美拿到Flag.
0x03 Frida hook
原文始发于微信公众号(Eureka安全):国外Matrix CTF「Oh, Bugger」思路解析:Frida hook 轻松拿Flag
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论