吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 11364|回复: 43
收起左侧

[Android 原创] frida 检测

  [复制链接]
2016976438 发表于 2023-5-9 19:34
本帖最后由 2016976438 于 2023-5-9 19:37 编辑

frida 检测

(本章内容需要用到真机)

frida 特征检测仿造自qtfreet00darvincisec:

看样子还是好几年前的,看来大佬几年前就摸透了

项目创建

  • 首先我们创建 cpp 的项目

01.png

  • 创建完的结构如下
    02.png

  • 我并不擅长写 android,后续我全部都写 android 日志进行交互。

关于android 日志 请参考文档:

https://developer.android.google.cn/ndk/reference/group/logging

proc self maps 说明

通过下面指令可以看到内存映射段

cat /proc/self/maps 
platina:/ # ps -ef|grep antifrida_demo1
u0_a214       9608 31372 1 17:26:01 ?     00:00:01 com.luckfollow.antifrida_demo1
root          9871  9826 2 17:27:24 pts/5 00:00:00 grep antifrida_demo1
platina:/ # cat /proc/9608/maps
12c00000-13200000 rw-p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
13200000-140c0000 ---p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
140c0000-14100000 ---p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
14100000-14140000 rw-p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
14140000-14180000 ---p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
14180000-14240000 rw-p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
14240000-16b80000 ---p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
16b80000-32c00000 rw-p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
70ab7000-70d61000 rw-p 00000000 103:2d 1989                              /system/framework/arm64/boot.art
70d61000-70e76000 rw-p 00000000 103:2d 1953                              /system/framework/arm64/boot-core-libart.art
70e76000-70eb1000 rw-p 00000000 103:2d 1971                              /system/framework/arm64/boot-okhttp.art
70eb1000-70f0b000 rw-p 00000000 103:2d 1947                              /system/framework/arm64/boot-bouncycastle.art
70f0b000-70f54000 rw-p 00000000 103:2d 1944                              /system/framework/arm64/boot-apache-xml.art
70f54000-70f57000 rw-p 00000000 103:2d 1932                              /system/framework/arm64/boot-QPerformance.art
70f57000-70f59000 rw-p 00000000 103:2d 1935                              /system/framework/arm64/boot-UxPerformance.art
70f59000-718c6000 rw-p 00000000 103:2d 1959                              /system/framework/arm64/boot-framework.art
718c6000-7190b000 rw-p 00000000 103:2d 1956                              /system/framework/arm64/boot-ext.art
7190b000-71a29000 rw-p 00000000 103:2d 1980                              /system/framework/arm64/boot-telephony-common.art
71a29000-71a3a000 rw-p 00000000 103:2d 1986                              /system/framework/arm64/boot-voip-common.art
71a3a000-71a53000 rw-p 00000000 103:2d 1962                              /system/framework/arm64/boot-ims-common.art
71a53000-71aab000 rw-p 00000000 103:2d 1965                              /system/framework/arm64/boot-miuisdk@boot.art
71aab000-71ad1000 rw-p 00000000 103:2d 1968                              /system/framework/arm64/boot-miuisystemsdk@boot.art

就以某一段为例

70ab7000-70d61000 rw-p 00000000 103:2d 1989                              /system/framework/arm64/boot.art

分别含义如下:

70ab7000-70d61000          本段内存映射的虚拟地址空间范围,对应vm_area_struct中的vm_start和vm_end
rw-p                                 此段虚拟地址空间的属性。每种属性用一个字段表示,r表示可读,w表示可写,x表示可执行,p和s共用一个字段,互斥关系,p表示私有段,s表示共享段,如果没有相应权限,则用’-’代替
00000000                          针对有名映射,指本段映射地址在文件中的偏移
103:2d                                所映射的文件所属设备的设备号,
1989                                映射文件所属节点号 
/system/framework/arm64/boot.art  映射的文件

但我们用 frida 使用 spwan 附加上去后

frida -U -f com.luckfollow.antifrida_demo1

其 maps 中多处了这一段

platina:/ # cat /proc/12186/maps|grep frida
7ea7841000-7ea8231000 r--p 00000000 fc:00 1114131                        /data/local/tmp/re.frida.server/frida-agent-64.so
7ea8232000-7ea8f4e000 r-xp 009f0000 fc:00 1114131                        /data/local/tmp/re.frida.server/frida-agent-64.so
7ea8f4e000-7ea901d000 r--p 0170b000 fc:00 1114131                        /data/local/tmp/re.frida.server/frida-agent-64.so
7ea901e000-7ea903a000 rw-p 017da000 fc:00 1114131                        /data/local/tmp/re.frida.server/frida-agent-64.so

这一段应该是 frida 附加上去的。

我们借助 ida pro 看一下

03.png
04.png

可以看到在内存中 每个 segments 的具体情况。

地址跟

platina:/ # cat /proc/12186/maps|grep frida
7ea7841000-7ea8231000 r--p 00000000 fc:00 1114131                        /data/local/tmp/re.frida.server/frida-agent-64.so
7ea8232000-7ea8f4e000 r-xp 009f0000 fc:00 1114131                        /data/local/tmp/re.frida.server/frida-agent-64.so
7ea8f4e000-7ea901d000 r--p 0170b000 fc:00 1114131                        /data/local/tmp/re.frida.server/frida-agent-64.so
7ea901e000-7ea903a000 rw-p 017da000 fc:00 1114131                        /data/local/tmp/re.frida.server/frida-agent-64.so

完美对应

frida 检测思路

以下只是个人结合开源 antifrida的一些列开源项目 ,并没有看 frida 源码 并没有深入追究,肯定会有漏的情况。

上诉按道理直接 看 maps 中映射的文件是否包含 /tmp 目录就可以了。但可能有些改目录的情况。所以检测会根据 内存特征 或者 elf中描述信息对比。

elf 目前我还不了解。 先看看基于 内存线程

1.基于线程

05.png

我们可以看到线程中多了 gmainpool-frida

我们可以通过

/proc/self/task/thread_id/status 
/proc/self/task/thread_id/stat

获取线程名

platina:/proc/14270/task # cat 14294/status
Name:   pool-frida
State:  t (tracing stop)
Tgid:   14270
Pid:    14294
PPid:   31372
.....

platina:/proc/14270/task # cat 14294/stat
14294 (pool-frida) t 31372 31372 0 0 -1 1077952576 14 0 0 0 0 0 0 0 20 0 19 0 196500473 5490044928 19924 18446744073709551615 424577748992 424577773808 549642079824 545349434336 547774104380 0 4612 1 1073775864 1 0 0 -1 1 0 0 0 0 0 424577777664 424577779096 425002627072 549642081909 549642082008 549642082008 549642084318 0

2.打开的文件

ls /pro/self/fd -l
platina:/proc/14270/task/14294/fd # ls -l /proc/14270/fd|grep /tmp
l-wx------ 1 u0_a214 u0_a214 64 2023-05-06 20:39 43 -> /data/local/tmp/re.frida.server/linjector-45

可以看到fd软链接到文件 linjector

3.内存特征

通过 ida pro segmentsCODE段是代码段。

06.png

我们双击点进去下面看

07.png

找到了 frida_agent_main方法。

我们可以通过这个方法一些代码特征码 来寻找是否被 frida 注入了。

当然个人觉得 直接解析 elf 更快点,看特征符号是否包含 frida_agent_main方法。

内存搜索需要带上算法 (BM 或 Sunday) ,那就不好说了。

4.trace 检测

调试工具 进行附加程序的时候,会产生TracerPid

如下图所示:

08.png

有些程序会 自己附加自己 达到 frida 无法附加的功能

不过只用 frida 附加 不会出现 PtracerPid. 原因不知,愿大佬解答

5.总结检测

上述所说,除了 /proc/self/fd 只是用到目录函数外。

其余的都需要用到 openat 函数。

总结一下

  • openat
    • /proc/self/maps
    • 通过判断 elf 是否是执行段扫描代码特征码
    • 解析 elf 中导出符号名称
    • 直接解析链接名称是否出现非系统目录
    • /proc/self/task/thread_id/stat
    • 判断线程名称是否存在 frida gmain关键字
    • /proc/self/status
    • 判断是否有调试工具附加
  • opendir->open
    • /proc/self/fd
    • 查找被打开的文件描述符的文件

由于 elf 需要了解elf格式  扫内存需要用到算法。这对我来说还是有点挑战性的。

所以我只演示三个:

  • 直接解析内存链接名称是否带/tmp
  • 判断线程名称是否存在 frida gmain关键字
  • 查找被打开的文件描述符的文件是否是/tmp路径

代码演示

1.判断 maps linker的文件 是否存在 tmp 目录

 static const char *CHECK_FEATURE = "/tmp";
 static const char *TAG = "ANTI_FRIDA";
 static const char *PROC_MAPS = "/proc/self/maps";

 void check_path()
 {
     char buffer[BUFFER_LEN];

     int fd = 0;
     // 64 位地址
     unsigned long long base;
     unsigned long long end;
     unsigned long offset;
     char path[256];
     char perm[5];
     if ((fd = openat(AT_FDCWD, PROC_MAPS, O_RDONLY)) > 0)
     {
         while (read_line(fd, buffer, BUFFER_LEN) > 0)
         {
             // sscanf 函数用于 格式化输入 到参数
             // x 十六进制 l长整型 * 可要可不要
             if (sscanf(buffer, "%x-%lx %4s %lx %*s %*s %s", &base, &end, perm, &offset, path) < 5)
                 continue;
             if (strlen(path) == 0)
                 continue;
             // check tmp path
             if (strstr(path, CHECK_FEATURE) != NULL)
             {
                 __android_log_print(ANDROID_LOG_DEBUG, TAG, "maps 不通过:%s", path);
                 break;
             }
         }
         close(fd);
     }
 }

2.检查task 中 stat 线程名称

void check_thread_name()
{
    static const char *PROC_TASK = "/proc/self/task";
    static const char *PROC_STATUS = "/proc/self/task/%s/stat";
    static const char *THREAD_NAME1 = "gmain";
    static const char *THREAD_NAME2 = "pool-frida";
    // 打开目录
    DIR *dir = opendir(PROC_TASK);
    if (dir != NULL)
    {
        struct dirent *entry = NULL;
        // 遍历子目录
        while ((entry = readdir(dir)) != NULL)
        {
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..")==0)
            {
                continue;
            }
            char filePath[BUFFER_LEN] = "";
            snprintf(filePath, sizeof(filePath), PROC_STATUS, entry->d_name);
            int fd = openat(AT_FDCWD, filePath, O_RDONLY | O_CLOEXEC, 0);
            if (fd > 0)
            {
                char buf[BUFFER_LEN] = "";
                read_line(fd, buf, BUFFER_LEN);
                if (strstr(buf, THREAD_NAME1) != NULL || strstr(buf, THREAD_NAME2) != NULL)
                {
                    __android_log_print(ANDROID_LOG_DEBUG, TAG, "thread 不通过: %s",buf);
                    break;
                }
            }
        }
        closedir(dir);
    }
}

3.检查使用的文件描述符

void check_fd()
{
    static const char *PROC_FD = "/proc/self/fd";
    DIR *dir = opendir(PROC_FD);
    if (dir != NULL)
    {

        struct dirent *entry = readdir(dir);
        struct stat filestat;
        while ((entry = readdir(dir)) != nullptr)
        {
            char filepath[BUFFER_LEN] = "";
            char buf[BUFFER_LEN] = "";
            snprintf(filepath, sizeof(filepath), "/proc/self/fd/%s", entry->d_name);
            // linker 文件状态
            lstat(filepath, &filestat);

            // st_mode 包含 文件权限 和 文件类型
            // (__buf.st_mode & S_IFMT) 代表只取 高位4位 文件类型
            // S_IFLNK 文件类型是 linker 链接文件
            if ((filestat.st_mode & S_IFMT) == S_IFLNK)
            {
                // 取linker的实际路径
                readlinkat(AT_FDCWD, filepath, buf, BUFFER_LEN);
                if (strstr(buf, CHECK_FEATURE) != NULL)
                {
                    __android_log_print(ANDROID_LOG_DEBUG, TAG, "FD 未通过: %s",buf);
                }
            }
        }
        closedir(dir);
    }
}

frida 演示

我们使用frida 进行附加

frida -U -f com.luckfollow.antifrida_demo1

logcat 可以看到下面的信息

D/ANTI_FRIDA: maps 不通过:/data/local/tmp/re.frida.server/frida-agent-64.so
D/ANTI_FRIDA: thread 不通过: 25187 (gmain) S 31372 31372 0 0 .....
D/ANTI_FRIDA: FD 未通过: /data/local/tmp/re.frida.server/linjector-4

基于frida hook __openat 完成过检测

是不是 这样就安全了呢?答案肯定是否的

上诉所有检测中,几乎都离不开 openat 函数。

哪怕是 opendir 底层也是用到 open 函数打开目录文件描述符

openopenat 最终都使用到了 __openatsvc调用。

所以说我们可以 hook __openat 有几个方案处理:

  • 判断 proc
    • 判断 maps
    • 返回值改 -1
    • 重定向修正文件
    • 判断 fd
    • 返回值改-1
    • 判断 task
    • 返回值改-1

__openat 我们可以直接hook  为了以防万一也 hook syscall

1.通过 __openat

 function anti_open() {
        //prepared fun
        let openatPtr: NativePointer | null = NativeUtil.open_io.find_real_openat();
        let openat_fun = NativeUtil.open_io.openat_fun(openatPtr!);

        Interceptor.replace(openatPtr!, new NativeCallback(function (fd, pathname, flags) {
            const pathnamestr = pathname.readCString();
            if (pathnamestr != null) {
                if (pathnamestr.indexOf("proc") != -1) {
                    if (pathnamestr.indexOf("maps") > 0) return maps_handle(pathnamestr);
                    if (pathnamestr.indexOf("task") > 0 && pathnamestr.indexOf("status") > 0) return thread_handle(pathnamestr);
                    if (pathnamestr.indexOf("fd") > 0) return fd_handle(pathnamestr);
                }
            }

            return openat_fun(fd, pathname, flags);

        }, "int", ["int", "pointer", "int"]));
    }

    function maps_handle(pathnamestr: string) {
        DebugUtil.LOGD("anti_maps:" + pathnamestr);
        return -1;
    }

    function thread_handle(pathnamestr: string) {
        DebugUtil.LOGD("anti_thread:" + pathnamestr);
        return -1;
    }

    function fd_handle(pathnamestr: string) {
        DebugUtil.LOGD("anti_fd:" + pathnamestr);
        return -1;
    }

    function status_handle(pathnamestr: string) {
        DebugUtil.LOGD("anti_status:" + pathnamestr);
        return -1;
    }

2.通过syscall

syscall 比较麻烦。 需要判断 arm64arm32

openat 使用 syscall 函数调用的时候,如下:

int pick_openat(int fd, const char *pathname, int flags,...)
{
    // 0 系统 call
    // 1 原始openat
    // 2 自定义系统 call
    static int SYSCALL_INVOKE = 0;
    switch (SYSCALL_INVOKE)
    {
    case 0:
        return syscall(__NR_openat,fd,pathname,flags);    
    default:
        return openat(fd,pathname,flags);
    }
}

我们虽然不能 hook svc 的内核调用

但是可以hook 到外部使用 svc 的 syscall

    function anti_syscall_openat() {
        let syscallPtr = NativeUtil.unistd.get_syscall_call_ptr();
        let syscallFun = NativeUtil.unistd.get_syscall_call_function()!;

        let openatPtr: NativePointer | null = NativeUtil.open_io.find_real_openat();
        let openat_fun = NativeUtil.open_io.openat_fun(openatPtr!);

        function handle_openat(args: NativePointer[], sysFun: NativeFunction<any, any>): number {
            const pathnamestr = args[2].readCString();
            if (pathnamestr != null && pathnamestr.indexOf("proc") != -1) {
                if (pathnamestr.indexOf("maps") > 0) return maps_handle(pathnamestr);
                if (pathnamestr.indexOf("task") > 0 && pathnamestr.indexOf("status") > 0) return thread_handle(pathnamestr);
                if (pathnamestr.indexOf("fd") > 0) return fd_handle(pathnamestr);
            }
            return sysFun.apply(null, args);
        }

        if (Process.arch === "arm64") {
            DebugUtil.LOGD("anti_syscall_openat start arm64...")
            Interceptor.replace(syscallPtr, new NativeCallback(function (sysSign, arg1, arg2, arg3, arg4, arg5, arg6) {
                if (sysSign === NativeUtil.unistd.syscall_asm.__NR_openat) {
                    DebugUtil.LOGW("syscall openat arm64");
                    return handle_openat([...arguments], syscallFun);
                }
                return syscallFun(sysSign, arg1, arg2, arg3, arg4, arg5, arg6);
            }, "int", ["int", "pointer", "pointer", "pointer", "pointer", "pointer", "pointer"]))
        } else {
            DebugUtil.LOGD("anti_syscall_openat start arm32...")
            Interceptor.replace(syscallPtr, new NativeCallback(function (sysSign, arg1, arg2, arg3) {
                if (sysSign === NativeUtil.unistd.syscall_asm.__NR_openat) {
                    DebugUtil.LOGW("syscall openat arm32");
                    return handle_openat([...arguments], syscallFun);
                }
                return syscallFun(sysSign, arg1, arg2, arg3);
            }, "int", ["int", "pointer", "pointer", "pointer"]))
        }
    }

3.重定向maps

当然,我们还可以生成一个处理过的 maps 文件 重定向上面去。

这也可以防止 误伤 或者 检测内容 的问题

操作可以留给大家尝试,

自定义syscall 防止被hook

为了防止被 hook 寻常的 __openat 以及 syscall

我们自定义 syscall 完成防止hook

(syscall 使用我会在 arm学习篇 写几篇教程)

为了方便,我只实现 arm64

// syscall.S
#include "bionic_asm.h"

#if defined(__aarch64__)

ENTRY(my_syscall)
    // x8 系统调用号
    mov     x8, x0
    // x0 - x5 系统函数传参
    mov     x0, x1
    mov     x1, x2
    mov     x2, x3
    mov     x3, x4
    mov     x4, x5
    mov     x5, x6
    // 系统调用
    svc     #0

    // 当 CF = 1  代表无符号 溢出 则 x0 是负数 有错误码 
    cmn     x0, #(MAX_ERRNO + 1)
    // hi 条件为 CF = 1 一般用于 无符号比较大小
    cneg    x0, x0, hi
    // 调用 __set_errno_internal  传入错误码
    b.hi    __set_errno_internal

    ret
END(my_syscall)

#endif
extern "C" int my_syscall(int sys_no, ...);
int pick_openat(int fd, const char *pathname, int flags, ...)
{
    // 0 系统 call
    // 1 原始openat
    // 2 自定义系统 call
    static int SYSCALL_INVOKE = 2;
    switch (SYSCALL_INVOKE)
    {
    case 0:
        return syscall(__NR_openat, fd, pathname, flags);
    case 2:
        return my_syscall(__NR_openat, fd, pathname, flags);    
    default:
        return openat(fd, pathname, flags);
    }
}

总结

实际上有很多问题。

比没有采用  elf执行段 中的 内存特征 去检测 frida

因为 elf结构 我还不太了解

总归来说, 大多数都用 openat 来打开 /proc/self/maps 获取内存映射信息,方便扫描内存。

不过应该还有其他方式,目前我只能通过开源的方案去寻找答案

svc 搜过一些资料还是可以被观察到的。比如一些指令跟踪。 或者 一些基于 linux 限制强制跳转到  __set_errno_internal函数进行转发处理。目前我还看不懂。能做到这些的大佬指定是个大佬

不过还有一种方案我也测试了的。

debug_cat大佬发了我一个他改版的frida 去掉了内存特征并将残留文件变成随机名,奈何我不会改机,原可以将 frida生成的残留文件 放在系统目录下,由于权限设置不了 不然就可以解决了

免费评分

参与人数 21吾爱币 +21 热心值 +21 收起 理由
laozhang4201 + 1 + 1 热心回复!
junjia215 + 1 + 1 谢谢@Thanks!
ruiheng + 1 + 1 用心讨论,共获提升!
天空宫阙 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
OYyunshen + 1 + 1 我很赞同!
m2kar + 1 感谢楼主!用心讨论,共获提升!
sorryzzital + 1 + 1 我很赞同!
chenjingyes + 1 + 1 用心讨论,共获提升!
mEIhUAlU123 + 1 + 1 热心回复!
byinto + 1 谢谢@Thanks!
D3M1 + 1 + 1 谢谢@Thanks!
yp17792351859 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
b12312312 + 1 + 1 热心回复!
zhiPang0 + 1 很感谢作者基础逆向知识的普及
GS_小东 + 1 + 1 很享受知识流过脑海的感觉。
menghen + 1 + 1 可否分享下改版的frida
chinawolf2000 + 1 + 1 热心回复!
NPC2000 + 1 + 1 我很赞同!
debug_cat + 2 + 1 热心回复!支持老铁~
coder9527 + 1 + 1 热心回复!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

jobs_steven 发表于 2023-5-12 08:52
debug_cat大佬发了我一个他改版的frida 去掉了内存特征并将残留文件变成随机名,奈何我不会改机,原可以将 frida生成的残留文件 放在系统目录下,由于权限设置不了 不然就可以解决了。——我想要这个可以吗?
discuz0 发表于 2023-5-9 20:26
头像被屏蔽
moruye 发表于 2023-5-9 22:17
debug_cat 发表于 2023-5-9 22:55
进度太快了,我晕车~
 楼主| 2016976438 发表于 2023-5-9 23:03
debug_cat 发表于 2023-5-9 22:55
进度太快了,我晕车~

大佬停下,我想上车~
superworker2022 发表于 2023-5-10 08:31
看的有点晕,内容太深了
Spacecraft 发表于 2023-5-10 08:39
认真学习
2982ZXC 发表于 2023-5-10 09:30
感谢大佬
zjh889 发表于 2023-5-11 08:48
这东西太复杂了,俺只能慢慢学习了!
zduyiwuer 发表于 2023-5-11 09:28
太难了,慢慢学习
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-23 05:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表