题外话:恭喜ID【飞翔的猫咪】,获得看雪安卓应用安全能力认证高级安全工程师噢!
解题
简单查看app
app启动时 调用stringFromJNI 进行frida检测,然后开启一个线程死循环一直进行frida检测。
第一个检测点
安装一下app启动fridaserver,打开app发现卡在启动界面一直白屏此时还没有附加app,猜测可能是进行了默认端口(27042)的检测。
通过修改frida默认端口启动server
./tuzi -l 127.0.0.1:12345
在次打开app已经可以正常启动。
第二个检测点
尝试使用常规手段hook strstr,readlinke,io重定向maps和task,app崩溃,肯定是使用了syscall去调用。
处理svc
使用ida搜索svc指令。
编写一个fridahook脚本来处理这里所有的svc指令,懒得编写可以通过从内存中搜索这里我选择从内存中搜索。
function hookdlsym() {
var libcmodule = Process.getModuleByName("libc.so");
var dlsymaddr = libcmodule.getExportByName("dlsym");
Interceptor.attach(dlsymaddr, {
onEnter: function (args) {
this.symbolname = args[1].readCString();
}, onLeave: function (retval) {
if (retval != null) {
var symbolinfo = DebugSymbol.fromAddress(retval);
if (symbolinfo.toString().indexOf("libbypassfridadetection.so") != -1) {
var libAppGuard = Process.getModuleByName("libbypassfridadetection.so");
Memory.scan(libAppGuard.base, libAppGuard.size, "01 00 00 D4", {
onMatch: function (matchaddr) {
Interceptor.attach(matchaddr, {
onEnter: function (args) {
var syscallnum = this.context.x8;
if(syscallnum == 0x3f){
var args1 = ptr(this.context.x1).readCString();
console.log("read :" + args1);
}else if(syscallnum == 0x4e){
var args1 = ptr(this.context.x1).readCString();
var args2 = ptr(this.context.x2).readCString();
console.log("readlink args1 1:" + args1," args2 2:" + args2);
}else if(syscallnum == 0x38){
var filepath = ptr(this.context.x1).readCString();
console.log("openat:" + filepath);
}
}, onLeave: function (retval) {
}
})
}, onComplete: function () {
// console.warn("search svc syscall over");
}
})
}
}
}
})
}
效果还是算ok,可以看到他这个一次只读了一个字符,那字符串拼接是在哪里做的呢找到字符串拼接的地方是不是就可以直接修改了呢。
通过对svc指令的扫描监控发现改案例只使用了三个系统调用read,readlike,oepnat。通过对readlike和openat进行io重定向也是成功绕过检测。
完整代码 pass
function hookdlsym() {
var libcmodule = Process.getModuleByName("libc.so");
var dlsymaddr = libcmodule.getExportByName("dlsym");
Interceptor.attach(dlsymaddr, {
onEnter: function (args) {
this.symbolname = args[1].readCString();
}, onLeave: function (retval) {
if (retval != null) {
var symbolinfo = DebugSymbol.fromAddress(retval);
if (symbolinfo.toString().indexOf("libbypassfridadetection.so") != -1) {
var libAppGuard = Process.getModuleByName("libbypassfridadetection.so");
Memory.scan(libAppGuard.base, libAppGuard.size, "01 00 00 D4", {
onMatch: function (matchaddr) {
var addrinfo = DebugSymbol.fromAddress(matchaddr);
Interceptor.attach(matchaddr, {
onEnter: function (args) {
var syscallnum = this.context.x8;
const openPtr = Module.getExportByName('libc.so', 'open');
const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
var readPtr = Module.findExportByName("libc.so", "read");
var read = new NativeFunction(readPtr, 'int', ['int', 'pointer', "int"]);
var fakePath2 = "/data/data/com.hanbingle.bypassfridadetection/task";
//openat
if(syscallnum == 0x38){
var pathname = this.context.x1.readCString();
if(pathname.indexOf("task") !== -1){
var realFd = open(args[1],args[2].toInt32());
var buffer2 = Memory.alloc(512);
console.log("open task:", pathname);
var isoneLine = null;
while(parseInt(read(realFd, buffer2, 512)) !== 0){
var oneLine = Memory.readCString(buffer2);
if(oneLine == " "){
isoneLine = oneLine;
//console.log("task buffer: ",oneLine)
return;
}
//console.log("task buffer: ",oneLine)
if(oneLine.indexOf("gum-js-loop")!=-1){
var replaceStr = "aaaaaaaaaaa"
oneLine = oneLine.replace("gum-js-loop", replaceStr)
console.log("oneLine",oneLine)
}
if(oneLine.indexOf("pool-frida")!=-1){
var replaceStr = "BBBBBBBBBB"
oneLine = oneLine.replace("pool-frida", replaceStr)
console.log("oneLine",oneLine)
}
if(oneLine.indexOf("gmain")!=-1){
var replaceStr = "CCCCC"
oneLine = oneLine.replace("gmain", replaceStr)
console.log("oneLine",oneLine)
}
if(oneLine.indexOf("linjector")!=-1){
var replaceStr = "aaaaaaaaa"
oneLine = oneLine.replace("gmain", replaceStr)
console.log("oneLine",oneLine)
}
var addr_fopen = Module.findExportByName("libc.so", "fopen");//fopen打开文件
var addr_fputs = Module.findExportByName("libc.so", "fputs");//fputs写入数据
var addr_fclose = Module.findExportByName("libc.so", "fclose");//fclose关闭文件
//获取函数指针 NativeFunction获取函数指针
var fopen = new NativeFunction(addr_fopen, "pointer", ["pointer", "pointer"]);
var fputs = new NativeFunction(addr_fputs, "int", ["pointer", "pointer"]);
var fclose = new NativeFunction(addr_fclose, "int", ["pointer"]);
//c语言的字符串直接写的话是js的string 用perform frida会转换成java的string
// 使用allocUtf8String 可以将 java的string 转换成 nativePintre 开辟一块内存生成一个char*
var filename = Memory.allocUtf8String(fakePath2);
var open_mode = Memory.allocUtf8String("w");
var file = fopen(filename, open_mode);
var buffer = Memory.allocUtf8String(oneLine);
var retval = fputs(buffer, file);
fclose(file);
}
if(isoneLine != " "){
var filename = Memory.allocUtf8String(fakePath2);
args[1] = filename;
}
}
}
}, onLeave: function (retval) {
}
})
}, onComplete: function () {
// console.warn("search svc syscall over");
}
})
Memory.scan(libAppGuard.base, libAppGuard.size, "01 00 00 D4", {
onMatch: function (matchaddr) {
var addrinfo = DebugSymbol.fromAddress(matchaddr);
Interceptor.attach(matchaddr.add(0x4), {
onEnter: function (args) {
var syscallnum = this.context.x8;
//readlinke
if(syscallnum == 0x4e){
if(args[2].readCString().indexOf("frida:rpc")!==-1 ||
args[2].readCString().indexOf("gum-js-loop")!==-1||
args[2].readCString().indexOf("gmain")!==-1 ||
args[2].readCString().indexOf("linjector")!==-1){
console.log('readlink(' +
's1="' + args[1].readCString() + '"' +
', s2="' + args[2].readCString() + '"' +
', s3="' + args[3] + '"' +
')');
args[2].writeUtf8String("/system/framework/boot.art")
console.log("replce with: "+args[2].readCString())
}
}else if(syscallnum == 0x3f){
// console.log("read args[1] :",args[1].readCString())
}
}, onLeave: function (retval) {
}
})
}, onComplete: function () {
// console.warn("search svc syscall over");
}
})
}
}
}
})
}
function main() {
hookdlsym()
}
setImmediate(main);
修改frida特征字符串巧妙过检测
打开ida的字符串窗口可以看到字符串并未加密。
可以清晰的看到改案例检测了那些frida的特征。
使用010Editor 直接修改字符串 然后推送修改完的so到app目录来进行pass也是直接绕过。
绕过检测的一些思路总结
1,ida搜索(或者内存中搜索)svc指令获取所有调用svc处进行hook。
2,这题的一个bug,字符串未加密隐藏(在一些粗心的app中还是可以使用这个技巧的,如果没有对字符串进行特殊处理)。
3,ebpf监控系统调用并修改。(暂时还是工具人)
4,使用seccomp+ptrace直接修改系统调用。
看雪ID:王麻子本人
https://bbs.kanxue.com/user-home-928079.htm
# 往期推荐
球分享
球点赞
球在看
原文始发于微信公众号(看雪学苑):魔法打败魔法:Frida过Frida检测