首页
社区
课程
招聘
9
[原创]frida反调试总结+一把梭
发表于: 2024-12-23 10:16 35246

[原创]frida反调试总结+一把梭

2024-12-23 10:16
35246

普通的检测总结以下几种手段:

检测/data/local/tmp下的frida特征文件,frida默认端口27042

双进程检测

检测/proc/pid/maps、/proc/pid/task/tid/stat、/proc/pid/fd中的frida特征

检测D-BUS

安卓系统使用Binder机制来实现进程间通信(IPC),而不是使用D-Bus。检测函数向所有端口发送d-bus消息,如果返回reject就说明fridaserver开启。

我们可以看到,上面使用的绕过技巧,大多都和系统函数有关系,那么如果app中不调用这些系统函数,而是用自实现的函数来进行操作,不就很难hook了吗?

这里就有一种自实现函数的技术:svc

通过安卓架构的学习,我们知道了安卓从上到下也是由层来分隔的,而层与层之间不能直接交互,而是需要一个中间层来进行操作。我们常见的jni就是Java层和native层的交互。而syscall就是kernelnative之间的中间层。

svc是x86架构中的一个指令,用于在用户模式下发起系统调用。当执行svc指令时,处理器会从用户态转换为内核态,执行内核级别的命令。

开发者可以通过syscall来执行内核函数,而不是直接使用系统函数,下面介绍几种防护手段:

直接使用syscall替代libc函数:
不使用标准C库函数,而是直接调用系统调用。这样可以绕过常见的hook点,因为大多数hook工具主要针对libc函数。

实现关键功能的自定义syscall wrapper:

为关键的系统调用创建自己的包装函数

动态生成syscall:
在运行时动态生成syscall指令(直接使用机器码,和下面的汇编差不多)

使用汇编实现syscall
直接使用汇编语言实现系统调用

最后再举一个实际检测frida的syscall例子:

网上对frida的检测通常会使用openat、open、strstr、pthread_create、snprintf、sprintf、readlinkat等一系列函数,这里hook了strstr和strcmp函数。

检测点说明:

我们通过上面的学习看到,自实现系统函数一个重要的前提就是它们都有标准的系统调用号,标准的机器码。所以我们绕过的时候也可以用同样的思路。

Frida的Memory API可以直接查找整个系统的内存内容,我们直接搜索对应函数的特征码,定位到之后再使用Interceptor进行Hook。(要注意每个架构对应的特征可能不一样)

#include <sys/syscall.h>  //SYS_open SYS_read SYS_close都是syscall.h中的常量
                          //代表系统调用的编号。在Linux系统中,每个系统调用都有一个唯一的编号
#include <unistd.h>
#include <fcntl.h>
 
int my_open(const char *pathname, int flags) {
    return syscall(SYS_open, pathname, flags);
}
 
ssize_t my_read(int fd, void *buf, size_t count) {
    return syscall(SYS_read, fd, buf, count);
}
 
int my_close(int fd) {
    return syscall(SYS_close, fd);
}
 
// 使用示例
int main() {
    int fd = my_open("/path/to/file", O_RDONLY);  //fd代表文件标识符,代表打开的是哪个文件
    if (fd != -1) {
        char buffer[100];
        ssize_t bytes_read = my_read(fd, buffer, sizeof(buffer));
        my_close(fd);
    }
    return 0;
}
#include <sys/syscall.h>  //SYS_open SYS_read SYS_close都是syscall.h中的常量
                          //代表系统调用的编号。在Linux系统中,每个系统调用都有一个唯一的编号
#include <unistd.h>
#include <fcntl.h>
 
int my_open(const char *pathname, int flags) {
    return syscall(SYS_open, pathname, flags);
}
 
ssize_t my_read(int fd, void *buf, size_t count) {
    return syscall(SYS_read, fd, buf, count);
}
 
int my_close(int fd) {
    return syscall(SYS_close, fd);
}
 
// 使用示例
int main() {
    int fd = my_open("/path/to/file", O_RDONLY);  //fd代表文件标识符,代表打开的是哪个文件
    if (fd != -1) {
        char buffer[100];
        ssize_t bytes_read = my_read(fd, buffer, sizeof(buffer));
        my_close(fd);
    }
    return 0;
}
#include <sys/syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
 
ssize_t secure_read(int fd, void *buf, size_t count) {
    // 完整性检查
    if (syscall(SYS_gettid) != syscall(SYS_getpid)) {
        // 可能正在被调试,终止操作
        return -1;
    }
     
    // 执行实际的读取操作
    ssize_t bytes_read = syscall(SYS_read, fd, buf, count);
     
    // 数据校验 (简单示例,实际应用中可能需要更复杂的校验)
    if (bytes_read > 0) {
        for (ssize_t i = 0; i < bytes_read; i++) {
            ((char*)buf)[i] ^= 0x55;  // 简单的XOR操作
        }
    }
     
    return bytes_read;
}
#include <sys/syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
 
ssize_t secure_read(int fd, void *buf, size_t count) {
    // 完整性检查
    if (syscall(SYS_gettid) != syscall(SYS_getpid)) {
        // 可能正在被调试,终止操作
        return -1;
    }
     
    // 执行实际的读取操作
    ssize_t bytes_read = syscall(SYS_read, fd, buf, count);
     
    // 数据校验 (简单示例,实际应用中可能需要更复杂的校验)
    if (bytes_read > 0) {
        for (ssize_t i = 0; i < bytes_read; i++) {
            ((char*)buf)[i] ^= 0x55;  // 简单的XOR操作
        }
    }
     
    return bytes_read;
}
#include <sys/mman.h>
#include <string.h>
 
typedef long (*syscall_fn)(long, ...);
 
syscall_fn generate_write_syscall() {
    // 分配可执行内存
    void* mem = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     
    // x86-64 架构的 write syscall 机器码
    unsigned char code[] = {
        0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00,  // mov rax, 1 (write syscall number)
        0x0f, 0x05,                                // syscall
        0xc3                                       // ret
    };
     
    // 复制代码到可执行内存
    memcpy(mem, code, sizeof(code));
     
    return (syscall_fn)mem;
}
 
// 使用示例
int main() {
    syscall_fn my_write = generate_write_syscall();
    const char *msg = "Hello, World!\n";
    my_write(1, msg, strlen(msg));
    return 0;
}
#include <sys/mman.h>
#include <string.h>
 
typedef long (*syscall_fn)(long, ...);
 
syscall_fn generate_write_syscall() {
    // 分配可执行内存
    void* mem = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     
    // x86-64 架构的 write syscall 机器码
    unsigned char code[] = {
        0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00,  // mov rax, 1 (write syscall number)
        0x0f, 0x05,                                // syscall
        0xc3                                       // ret
    };
     
    // 复制代码到可执行内存
    memcpy(mem, code, sizeof(code));
     
    return (syscall_fn)mem;
}
 
// 使用示例
int main() {
    syscall_fn my_write = generate_write_syscall();
    const char *msg = "Hello, World!\n";
    my_write(1, msg, strlen(msg));
    return 0;
}
#include <jni.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
 
JNIEXPORT jboolean JNICALL
Java_com_example_SecurityCheck_detectFrida(JNIEnv *env, jobject thiz) {
    char line[256];
    int fd = syscall(SYS_open, "/proc/self/maps", O_RDONLY);
    if (fd != -1) {
        while (syscall(SYS_read, fd, line, sizeof(line)) > 0) {
            if (strstr(line, "frida") || strstr(line, "gum-js-loop")) {
                syscall(SYS_close, fd);
                return JNI_TRUE;
            }
        }
        syscall(SYS_close, fd);
    }
    return JNI_FALSE;
}
#include <jni.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
 
JNIEXPORT jboolean JNICALL
Java_com_example_SecurityCheck_detectFrida(JNIEnv *env, jobject thiz) {
    char line[256];
    int fd = syscall(SYS_open, "/proc/self/maps", O_RDONLY);
    if (fd != -1) {
        while (syscall(SYS_read, fd, line, sizeof(line)) > 0) {
            if (strstr(line, "frida") || strstr(line, "gum-js-loop")) {
                syscall(SYS_close, fd);
                return JNI_TRUE;
            }
        }
        syscall(SYS_close, fd);
    }
    return JNI_FALSE;
}
function replace_str() {
    var pt_strstr = Module.findExportByName("libc.so", 'strstr');
    var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
  
    Interceptor.attach(pt_strstr, {
        onEnter: function (args) {
            var str1 = args[0].readCString();
  
            var str2 = args[1].readCString();
            if (str2.indexOf("tmp") !== -1 ||
                str2.indexOf("frida") !== -1 ||
                str2.indexOf("gum-js-loop") !== -1 ||
                str2.indexOf("gmain") !== -1 ||
                str2.indexOf("gdbus") !== -1 ||
                str2.indexOf("pool-frida") !== -1||
                str2.indexOf("linjector") !== -1) {
                //console.log("strcmp-->", str1, str2);
                this.hook = true;
            }
        }, onLeave: function (retval) {
            if (this.hook) {
                retval.replace(0);
            }
        }
    });
  
    Interceptor.attach(pt_strcmp, {
        onEnter: function (args) {
            var str1 = args[0].readCString();
            var str2 = args[1].readCString();
            if (str2.indexOf("tmp") !== -1 ||
                str2.indexOf("frida") !== -1 ||
                str2.indexOf("gum-js-loop") !== -1 ||
                str2.indexOf("gmain") !== -1 ||
                str2.indexOf("gdbus") !== -1 ||
                str2.indexOf("pool-frida") !== -1||
                str2.indexOf("linjector") !== -1) {
                //console.log("strcmp-->", str1, str2);
                this.hook = true;
            }
        }, onLeave: function (retval) {
            if (this.hook) {
                retval.replace(0);
            }
        }
    })
  
}
  
replace_str(); 一把梭方案
function replace_str() {
    var pt_strstr = Module.findExportByName("libc.so", 'strstr');
    var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
  
    Interceptor.attach(pt_strstr, {
        onEnter: function (args) {
            var str1 = args[0].readCString();
  
            var str2 = args[1].readCString();
            if (str2.indexOf("tmp") !== -1 ||

[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

收藏
免费 9
支持
分享
赞赏记录
参与人
雪币
留言
时间
IndexGuc
+1
这个讨论对我很有帮助,谢谢!
2025-1-2 21:47
mb_zfqvurgb
感谢你的贡献,论坛因你而更加精彩!
2024-12-30 12:34
mb_hgrbqfun
+1
感谢你的积极参与,期待更多精彩内容!
2024-12-27 22:22
你瞒我瞒
非常支持你的观点!
2024-12-25 09:19
nevinhappy
这个讨论对我很有帮助,谢谢!
2024-12-24 09:58
mychanges
为你点赞!
2024-12-24 09:42
养只猫不好么
感谢你分享这么好的资源!
2024-12-23 19:03
huangyalei
感谢你分享这么好的资源!
2024-12-23 17:47
W4VE丶
感谢你的贡献,论坛因你而更加精彩!
2024-12-23 11:04
最新回复 (4)
雪    币: 1465
活跃值: (3371)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-12-23 19:04
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
谢谢分享
2024-12-24 10:01
0
雪    币: 1496
活跃值: (2258)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
4
现在哥们分析的都是自实现字符串比较函数,套一大堆混淆www我也要一把梭
5天前
0
雪    币: 1001
活跃值: (116)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学习了
4天前
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册