首页
社区
课程
招聘
[原创]Frida检测思路
发表于: 2025-3-28 11:53 29289

[原创]Frida检测思路

2025-3-28 11:53
29289

Frida 注入目标进程后会使用Interceptor.attach对退出进程的方法进行inline hook。此处可以定位三个关键函数:exit、_exit、abort
GitHub源码链接:
470K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6X3M7X3W2V1j5g2)9J5c8X3k6J5K9h3c8S2i4K6u0V1j5$3!0J5k6g2)9J5c8X3u0D9L8$3u0Q4x3V1j5I4x3o6p5^5j5h3y4S2x3U0S2V1y4o6W2S2j5$3f1J5x3h3y4T1j5U0g2V1y4e0c8V1x3U0x3J5z5r3x3H3x3U0R3K6k6o6x3J5y4$3k6W2i4K6u0r3L8r3W2T1i4K6u0r3M7r3q4&6L8r3!0S2k6q4)9J5c8X3g2^5K9i4c8Q4x3X3c8E0L8$3&6A6N6r3!0J5i4K6u0W2N6X3q4D9j5g2)9J5x3@1H3K6y4l9`.`.

frida 调用了gum_interceptor_replace函数对libc库的signal和sigaction函数进行了inline hook。
GitHub链接:f4dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6X3M7X3W2V1j5g2)9J5c8X3k6J5K9h3c8S2i4K6u0V1k6%4g2E0i4K6u0r3j5X3I4G2j5W2)9J5c8U0l9$3y4K6q4U0x3U0N6V1z5e0b7I4k6o6M7%4y4o6V1H3k6X3k6X3y4r3y4S2y4X3c8T1j5U0W2U0j5e0p5H3j5e0m8X3z5o6M7J5j5X3u0Q4x3V1k6Y4N6h3#2Q4x3V1k6T1j5h3y4C8k6h3&6V1i4K6u0V1M7r3!0K6K9i4S2Q4x3V1k6Y4N6h3#2W2P5r3y4W2M7s2c8G2M7W2)9J5k6s2m8G2M7$3W2^5i4K6u0W2j5#2)9J5x3@1H3J5x3U0R3`.

前提:已知frida注入目标进程会hook libc库以下目标函数:
{"sigaction", "signal", "exit", "abort", "_exit"}

实现代码如下:

分析源码中frida注入逻辑进行检测,这种方法类似于开卷考试,不过优点明显:针对性很强,检测逻辑复杂度很低,性能开销小。
不知道后续frida会不会修改相关逻辑。

#if WINDOWS
            interceptor.attach ((void *) Gum.Process.find_module_by_name ("kernel32.dll").find_export_by_name ("ExitProcess"),
                listener);
#else
            var libc = Gum.Process.get_libc_module ();
            const string[] apis = {
                "exit",
                "_exit",
                "abort",
            };
            foreach (var symbol in apis) {
                interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
            }
#endif
#if WINDOWS
            interceptor.attach ((void *) Gum.Process.find_module_by_name ("kernel32.dll").find_export_by_name ("ExitProcess"),
                listener);
#else
            var libc = Gum.Process.get_libc_module ();
            const string[] apis = {
                "exit",
                "_exit",
                "abort",
            };
            foreach (var symbol in apis) {
                interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
            }
#endif
static void
gum_exceptor_backend_attach (GumExceptorBackend * self)
{
  GumInterceptor * interceptor = self->interceptor;
  const gint handled_signals[] = {
    SIGABRT,
    SIGSEGV,
    SIGBUS,
    SIGILL,
    SIGFPE,
    SIGTRAP,
    SIGSYS,
  };
  gint highest, i;
  struct sigaction action;
 
  highest = 0;
  for (i = 0; i != G_N_ELEMENTS (handled_signals); i++)
    highest = MAX (handled_signals[i], highest);
  g_assert (highest > 0);
  self->num_old_handlers = highest + 1;
  self->old_handlers = g_new0 (struct sigaction *, self->num_old_handlers);
 
  action.sa_sigaction = gum_exceptor_backend_on_signal;
  sigemptyset (&action.sa_mask);
  action.sa_flags = SA_SIGINFO | SA_NODEFER;
#ifdef SA_ONSTACK
  action.sa_flags |= SA_ONSTACK;
#endif
  for (i = 0; i != G_N_ELEMENTS (handled_signals); i++)
  {
    gint sig = handled_signals[i];
    struct sigaction * old_handler;
 
    old_handler = g_slice_new0 (struct sigaction);
    self->old_handlers[sig] = old_handler;
    gum_original_sigaction (sig, &action, old_handler);
  }
 
  gum_interceptor_begin_transaction (interceptor);
 
  gum_interceptor_replace (interceptor, gum_original_signal,
      gum_exceptor_backend_replacement_signal, self, NULL);
  gum_interceptor_replace (interceptor, gum_original_sigaction,
      gum_exceptor_backend_replacement_sigaction, self, NULL);
 
  gum_interceptor_end_transaction (interceptor);
}
static void
gum_exceptor_backend_attach (GumExceptorBackend * self)
{
  GumInterceptor * interceptor = self->interceptor;
  const gint handled_signals[] = {
    SIGABRT,
    SIGSEGV,
    SIGBUS,
    SIGILL,
    SIGFPE,
    SIGTRAP,
    SIGSYS,
  };
  gint highest, i;
  struct sigaction action;
 
  highest = 0;
  for (i = 0; i != G_N_ELEMENTS (handled_signals); i++)
    highest = MAX (handled_signals[i], highest);
  g_assert (highest > 0);
  self->num_old_handlers = highest + 1;
  self->old_handlers = g_new0 (struct sigaction *, self->num_old_handlers);
 
  action.sa_sigaction = gum_exceptor_backend_on_signal;
  sigemptyset (&action.sa_mask);
  action.sa_flags = SA_SIGINFO | SA_NODEFER;
#ifdef SA_ONSTACK
  action.sa_flags |= SA_ONSTACK;
#endif
  for (i = 0; i != G_N_ELEMENTS (handled_signals); i++)
  {
    gint sig = handled_signals[i];
    struct sigaction * old_handler;
 
    old_handler = g_slice_new0 (struct sigaction);
    self->old_handlers[sig] = old_handler;
    gum_original_sigaction (sig, &action, old_handler);
  }
 
  gum_interceptor_begin_transaction (interceptor);
 
  gum_interceptor_replace (interceptor, gum_original_signal,
      gum_exceptor_backend_replacement_signal, self, NULL);
  gum_interceptor_replace (interceptor, gum_original_sigaction,
      gum_exceptor_backend_replacement_sigaction, self, NULL);
 
  gum_interceptor_end_transaction (interceptor);
}
#include <dlfcn.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <elf.h>
#include <android/log.h>
#include <stdbool.h>
#include <errno.h>
#include <stdio.h>
#include <jni.h>
 
#define LOG_TAG "HookDetection"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define BYTE_BUFFER_SIZE 16
 
// 获取函数地址
void* get_function_address(void* handle, const char* func_name) {
    void* func_addr = dlsym(handle, func_name);
    if (!func_addr) {
        LOGD("[-] Function %s not found in global symbol table", func_name);
    } else {
        LOGD("[+] Function: %s, addr: 0x%lx", func_name, (uintptr_t)func_addr);
    }
    return func_addr;
}
 
// 获取函数偏移
uintptr_t get_function_offset(void* func_addr) {
    Dl_info info;
    if (dladdr(func_addr, &info) == 0) {
        LOGD("[-] Unable to get function info");
        return 0;
    }
    return (uintptr_t)func_addr - (uintptr_t)info.dli_fbase;
}
 
// 读取库文件中的字节
bool read_bytes_from_libso(const char* libpath, uintptr_t offset, uint8_t* buffer, size_t size) {
    int fd = open(libpath, O_RDONLY);
    if (fd == -1) {
        LOGD("[-] Failed to open %s: %s", libpath, strerror(errno));
        return false;
    }
 
    if (lseek(fd, offset, SEEK_SET) == -1) {
        LOGD("[-] Seek failed at %lx in %s: %s", (unsigned long)offset, libpath, strerror(errno));
        close(fd);
        return false;
    }
 
    ssize_t bytes_read = read(fd, buffer, size);
    close(fd);
 
    if (bytes_read != (ssize_t)size) {
        LOGD("[-] Read %zd bytes, expected %zu from %s at offset %lx", bytes_read, size, libpath, (unsigned long)offset);
        return false;
    }
 
    return true;
}
 
// 字节数组转十六进制字符串
void bytes_to_hex_string(const uint8_t* bytes, size_t size, char* hex_string) {
    for (size_t i = 0; i < size; i++) {
        sprintf(hex_string + i * 2, "%02x", bytes[i]);
    }
    hex_string[size * 2] = '\0';
}
 
// 比较内存中的字节和库文件中的字节
bool compare_function_bytes(void* func_addr, uint8_t* file_bytes, size_t size) {
    uint8_t mem_bytes[BYTE_BUFFER_SIZE];
    memcpy(mem_bytes, func_addr, size);
 
    char mem_hex_string[BYTE_BUFFER_SIZE * 2 + 1];
    char file_hex_string[BYTE_BUFFER_SIZE * 2 + 1];
 
    bytes_to_hex_string(mem_bytes, size, mem_hex_string);
    bytes_to_hex_string(file_bytes, size, file_hex_string);
 
    LOGD("[*] Memory: %s | File: %s", mem_hex_string, file_hex_string);
 
    return memcmp(mem_bytes, file_bytes, size) == 0;
}
 
// Hook 检测函数
bool detect_hook(void* handle, const char* lib_path, const char* func_name) {
    void* func_addr = get_function_address(handle, func_name);
    if (!func_addr) return false;
 
    uintptr_t offset = get_function_offset(func_addr);
    if (offset == 0) {
        LOGD("[-] Failed to get offset for %s", func_name);
        return false;
    }
 
    uint8_t file_bytes[BYTE_BUFFER_SIZE];
    if (!read_bytes_from_libso(lib_path, offset, file_bytes, sizeof(file_bytes))) {
        LOGD("[-] Failed to read bytes for %s", func_name);
        return false;
    }
 
    bool is_hooked = !compare_function_bytes(func_addr, file_bytes, sizeof(file_bytes));
    LOGD("[+] %s in %s is %s", func_name, lib_path, is_hooked ? "HOOKED" : "NOT HOOKED");
    return is_hooked;
}
 
// 执行 Hook 检测
int do_hook_check() {
    const char* libpath = "/system/lib64/libc.so";
    void* handle = dlopen(libpath, RTLD_LAZY);
    if (!handle) {
        LOGD("[-] Failed to load %s", libpath);
        return -1;
    }
 
    const char* funcs[] = {"sigaction", "signal", "exit", "abort", "_exit"};
    bool hooked = false;
 
    for (size_t i = 0; i < sizeof(funcs) / sizeof(funcs[0]); i++) {
        if (detect_hook(handle, libpath, funcs[i])) {
            hooked = true;
        }
    }
 
    dlclose(handle);
    return hooked;
}
#include <dlfcn.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <elf.h>
#include <android/log.h>
#include <stdbool.h>
#include <errno.h>
#include <stdio.h>
#include <jni.h>
 
#define LOG_TAG "HookDetection"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define BYTE_BUFFER_SIZE 16
 
// 获取函数地址
void* get_function_address(void* handle, const char* func_name) {
    void* func_addr = dlsym(handle, func_name);
    if (!func_addr) {
        LOGD("[-] Function %s not found in global symbol table", func_name);
    } else {
        LOGD("[+] Function: %s, addr: 0x%lx", func_name, (uintptr_t)func_addr);
    }
    return func_addr;
}
 
// 获取函数偏移
uintptr_t get_function_offset(void* func_addr) {
    Dl_info info;
    if (dladdr(func_addr, &info) == 0) {

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
最新回复 (3)
雪    币: 5
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
攻击方可以hook memcmp 和 open 函数,使其对应返回一致的结果。
守方可以选择自实现memcmp和open函数,这样不调用系统的库函数,不至于被轻易hook
2025-3-29 12:52
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
mb_yvuzbfvo 攻击方可以hook memcmp 和 open 函数,使其对应返回一致的结果。 守方可以选择自实现memcmp和open函数,这样不调用系统的库函数,不至于被轻易hook
本贴主旨是,frida在注入Android进程后会自动hook五个固定的native函数,这个特征有助于对frida进行有针对性的检测。
至于对检测方法本身的hook并非主旨,所以使用了最一般的方法来实现,但在app中完全可以用更隐蔽的方法实现该检测方案。
2025-3-29 15:16
0
雪    币: 15889
活跃值: (7123)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
frida是hook了这5个函数,但是不代表凡是hook了这5个函数就是frida?以前有人写过类似文章。
2025-3-30 09:37
0
游客
登录 | 注册 方可回帖
返回