Frida与Android CTF
字数 1195 2025-08-12 11:34:02
Frida与Android CTF实战教学文档
前言
本教学文档基于TideSec团队在FreeBuf上发布的《Frida与Android CTF》文章,详细讲解三个Android CTF题目的解题思路和技术要点,涵盖Frida Java Hook、动态加载Dex的Hook以及Native层反调试绕过等技术。
第一题:Frida静态函数的主动调用
题目分析
-
应用行为:
- 输入要求为5位纯数字
- 输入错误时提示"请继续加油"
-
静态分析:
- 用户名和密码拼接后传入
vvvv方法 VVVV函数限制输入长度为5位- 输入经过
eeeee方法处理后需与预设值p匹配 eeeee方法调用链:sssss获取字符串SHA-1值ccccc将SHA-1摘要转为16进制字符串
- 用户名和密码拼接后传入
解题思路
-
暴力破解:
- 遍历00000-99999所有5位数字组合
- 使用Frida主动调用验证函数
-
Hook绕过:
- 直接Hook返回值为true(不推荐,失去题目意义)
Frida脚本实现
var CONTEXT = null;
// 获取对象类名
function getObjClassName(obj) {
if (!jclazz) var jclazz = Java.use("java.lang.Class");
if (!jobj) var jobj = Java.use("java.lang.Object");
return jclazz.getName.call(jobj.getClass.call(obj));
}
// Hook VVVV方法获取上下文
function hookReturn() {
Java.perform(function () {
Java.use("com.kanxue.pediy1.VVVVV").VVVV.implementation = function (context, str) {
var result = this.VVVV(context, str);
console.log("context,str,result => ", context, str, result);
console.log("context className is => ", getObjClassName(context));
CONTEXT = context;
return true;
}
})
}
// 主动调用爆破
function invoke() {
Java.perform(function () {
var MainActivity = null;
Java.choose("com.kanxue.pediy1.MainActivity", {
onMatch: function (instance) { MainActivity = instance; },
onComplete: function () {}
});
var CONTEXT2 = Java.use("com.kanxue.pediy1.MainActivity$1").$new(MainActivity);
for (var x = 0; x <= 99999; x++) {
var result = Java.use("com.kanxue.pediy1.VVVVV").VVVV(CONTEXT2, String(x));
console.log("now x is => ", String(x));
if (result) {
console.log("found result is => ", String(x));
break;
}
}
})
}
function main() {
hookReturn();
}
setImmediate(main);
验证结果
成功爆破得到flag:66888
第二题:Frida Hook动态加载Dex
题目分析
-
特点:
- 动态加载dex文件
- 引入Native函数处理
-
静态分析:
- 算法与第一题类似
- 增加了
stringFromJNINative函数处理层
解题思路
-
枚举ClassLoader:
- 找到正确的ClassLoader来加载动态dex中的类
-
双层调用:
- 先调用Native函数处理输入
- 再调用动态dex中的验证函数
Frida脚本实现
function invoke2() {
Java.perform(function () {
// 获取MainActivity实例
var MainActivity = null;
Java.choose("com.kanxue.pediy1.MainActivity", {
onMatch: function(instance) { MainActivity = instance; },
onComplete: function() {}
});
// 枚举ClassLoader
var loader1 = null;
var loader2 = null;
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
if (loader.findClass("com.kanxue.pediy1.VVVVV")) {
console.log("Successfully found loader");
console.log(loader);
loader2 = loader;
Java.classFactory.loader = loader2;
} else if(loader.findClass("com.kanxue.pediy1.MainActivity")) {
console.log("Successfully found loader");
console.log(loader);
loader1 = loader;
}
} catch (error) {
console.log("find error:" + error);
}
},
onComplete: function () { console.log("end1"); }
});
// 双层调用爆破
for (var x = 0; x <= 99999; x++) {
var result1 = MainActivity.stringFromJNI(String(100000 - x));
var result2 = Java.use("com.kanxue.pediy1.VVVVV").VVVV(String(result1));
console.log("now x is => ", String(x));
if (result2) {
console.log("found result2 is => ", String(100000 - x));
break;
}
}
})
}
function main() {
invoke2();
}
setImmediate(main);
补充分析
通过IDA分析stringFromJNI函数发现:
- 将输入数字字符串转为int后加1返回
- 因此正确flag应为
66999 - 1 = 66998
验证结果
成功爆破得到flag:66998
第三题:Native层去反调试
反调试机制分析
- 检测逻辑:
- 循环创建Socket连接遍历端口
- 检查端口是否被占用
- 收到"REJECT"响应时判定frida-server运行
- 直接调用
kill终止进程
绕过思路
- 替换kill函数:
- 拦截libc.so中的kill函数调用
- 替换为自定义空实现
Frida脚本实现
function replaceKill() {
var kill_addr = Module.findExportByName("libc.so", "kill");
Interceptor.replace(kill_addr, new NativeCallback(function(arg0, arg1) {
console.log("arg0=> ", arg0);
console.log("arg1=> ", arg1);
}, "int", ['int', 'int']));
}
function main() {
replaceKill();
}
setImmediate(main);
使用方法
frida -U -f com.kanxue.pediy1 -l /path/to/script.js --no-pause
验证结果
成功绕过反调试,爆破得到flag:99998
总结
-
技术要点:
- Frida Java Hook与主动调用
- 动态加载Dex的ClassLoader处理
- Native层反调试检测与绕过
- 暴力破解数字组合
-
通用解题思路:
- 静态分析确定验证逻辑
- 动态验证确认关键函数
- 编写Frida脚本实现自动化
- 处理可能的反调试机制
-
工具链:
- Jadx:静态分析APK
- Frida:动态分析与Hook
- Objection:辅助分析
- IDA:Native层分析
参考资料
- 看雪论坛相关讨论
- Frida官方文档
- Android逆向工程相关技术资料