frida检测与防护 端口检测 检测 frida-server 默认端口 27042 是否开放。
1 2 3 4 5 6 7 8 9 10 11 12 13 bool detectByDefaultPort () { struct sockaddr_in sa; int sock = socket (AF_INET , SOCK_STREAM , 0 ); memset (&sa, 0 , sizeof (sa)); sa.sin_family = AF_INET; sa.sin_port = htons (27047 ); inet_aton ("127.0.0.1" , &(sa.sin_addr)); if (connect (sock , (struct sockaddr*)&sa , sizeof sa) != -1 ) { LOGD ("%s => Found frida-server: running on default port" , TAG); return true ; } return false ; }
进程名检测 遍历运行的进程列表(/proc目录下),检测进程名是否包含 “frida-server” 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 bool detectByProcessName () { std::vector<std::string> pids; std::string target_name = "frida" ; DIR* proc_dir = opendir("/proc" ); std::string current_pid = std::to_string(getpid()); if (proc_dir != nullptr){ struct dirent* entry; while ((entry = readdir(proc_dir)) != nullptr){ if (entry->d_type == DT_DIR){ std::string dir_name (entry->d_name) ; if (std::all_of(dir_name.begin(), dir_name.end(), ::isdigit) && dir_name != current_pid) { pids.push_back(dir_name); } } } closedir(proc_dir); } for (int i = 0 ; i < pids.size(); i++) { std::string comm_path = "/proc/" + pids[i] + "/comm" ; std::ifstream comm_file (comm_path) ; if (comm_file.is_open()) { std::string process_name; std::getline(comm_file, process_name); comm_file.close(); if (process_name.find(target_name) != std::string::npos) { LOGD("%s => Found frida-server: pid is %s" , TAG, pids[i].c_str()); return true ; } } } return false ; }
双进程守护 应用程序可通过开启双进程守护,即让子进程附加父进程,从而使得 frida 不能 attach 目标进程,但 frida 可通过 spawn 方式启动,从而让子进程无法附加到父进程(那如果子进程附加不上父进程,导致整个应用进程关闭呢?)。
D-Bus 协议通信 frida 使用 D-Bus 协议通信,这个通信协议并不常见,因此可以遍历 /proc/net/tcp 文件,或者直接从 0-65535 向每个开放的端口发送 D-Bus 认证消息,哪个端口回复了 REJECT,哪个端口上就运行了 frida-server。(为啥一样的代码,而我的却没有回复消息??)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 bool detectByDBus () { int sock; struct sockaddr_in sa; char res[7 ]; memset (&sa, 0 , sizeof (sa)); sa.sin_family = AF_INET; inet_aton ("127.0.0.1" , &sa.sin_addr); for (int i = 0 ; i <= 65535 ; ++i) { sock = socket (AF_INET, SOCK_STREAM, 0 ); sa.sin_port = htons (i); if (connect (sock, (struct sockaddr*)(&sa), sizeof sa) != -1 ){ LOGD ("%s => Connect on port %d" , TAG, i); memset (res, 0 , 7 ); send (sock, "\x00" , 1 , NULL ); send (sock, "AUTH\r\n" , 6 , NULL ); usleep (1000 ); LOGD ("%s => send Message on port %d" , TAG, i); if (recv (sock, res, 6 , MSG_DONTWAIT) != -1 ){ LOGD ("%s => recv Message on port %d: %s" , TAG, i, res); if (strcmp (res, "REJECT" ) == 0 ){ LOGD ("%s => Found frida-server: running on port %d" , TAG, i); close (sock); return true ; } }else { LOGD ("%s => can't recv Message on port %d" , TAG, i); } close (sock); } } return false ; }
扫描 maps 文件 被 frida 附加的进程,在 proc/self/maps 文件中会多出如下文件(很奇怪为什么图中是 deleted ):
因此通过扫描 maps 文件是否存在“frida”字样可以判断进程是否被 frida 附加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool detectByMaps () { std::ifstream maps_file ("/proc/self/maps" ) ; if (maps_file.is_open ()) { std::string line; while (std::getline (maps_file, line)) { if (line.find ("frida" ) != std::string::npos) { LOGD (" %s => Found Frida agent in current process by scanf /proc/self/maps" , TAG); maps_file.close (); return true ; } } maps_file.close (); } return false ; }
扫描 task 目录 在 /proc/pid/task 目录下存储的是当前进程下的线程相关信息,文件结构类似 /proc/pid 目录。如果 frida 附加到了当前进程,那么在 task 目录下,就会存在运行 gmain、gdbus、gum-js-loop、pool-frida等的线程。
gmain:Frida 使用 Glib 库,其中的主事件循环被称为 GMainLoop。在 Frida 中,gmain 表示 GMainLoop 的线程。
gdbus:GDBus 是 Glib 提供的一个用于 D-Bus 通信的库。在 Frida 中,gdbus 表示 GDBus 相关的线程。
gum-js-loop:Gum 是 Frida 的运行时引擎,用于执行注入的 JavaScript 代码。gum-js-loop 表示 Gum 引擎执行 JavaScript 代码的线程。
pool-frida:Frida 中的某些功能可能会使用线程池来处理任务,pool-frida 表示 Frida 中的线程池。
因此通过扫描 task 目录下的线程的 comm 文件或 status 文件,看是否存在上述字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 bool detectByTask () { DIR* task_dir = opendir ("/proc/self/task" ); std::vector<std::string> pids; std::string current_pid = std::to_string (getpid ()); if (task_dir != nullptr ) { struct dirent * entry; while ((entry = readdir (task_dir)) != nullptr ){ if (entry->d_type == DT_DIR){ std::string dir_name (entry->d_name) ; if (std::all_of (dir_name.begin (), dir_name.end (), ::isdigit)) { pids.push_back (dir_name); } } } closedir (task_dir); } for (int i = 0 ; i < pids.size (); i++) { std::string comm_path = "/proc/self/task/" + pids[i] + "/comm" ; std::ifstream comm_file (comm_path) ; if (comm_file.is_open ()) { std::string process_name; std::getline (comm_file, process_name); comm_file.close (); if (process_name.find ("gmain" ) != std::string::npos || process_name.find ("gdbus" ) != std::string::npos || process_name.find ("gum-js-loop" ) != std::string::npos || process_name.find ("pool-frida" ) != std::string::npos) { LOGD ("%s => Found Frida agent in current process by scanf /proc/self/task, process_name: %s" , TAG, process_name.c_str ()); return true ; } } } return false ; }
内存搜索 借助 /proc/self/maps 文件,在内存中扫描 frida 库特征,例如字符串 “LIBFRIDA”、“rpc”等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 bool detectByScanfMemory () { std::string target_name = "LIBFRIDA" ; char permission[512 ]; unsigned long start, end; std::ifstream maps_file ("/proc/self/maps" ) ; if (maps_file.is_open ()) { std::string line; while (std::getline (maps_file, line)) { sscanf (line.c_str (), "%lx-%lx %s" , &start, &end, permission); if (permission[2 ] == 'x' ) { size_t region_size = end - start; char * buffer = new char [region_size]; std::ifstream mem_file ("/proc/self/mem" , std::ios::binary) ; if (mem_file.is_open ()) { mem_file.seekg (start); mem_file.read (buffer, region_size); LOGD ("%s => search memory: %lx - %lx" , TAG, start, end); if (std::search (buffer, buffer + region_size, target_name.begin (), target_name.end ()) != buffer + region_size) { delete [] buffer; mem_file.close (); maps_file.close (); LOGD ("%s => Found target string in memory: %s" , TAG, target_name.c_str ()); return true ; } mem_file.close (); } delete [] buffer; } } maps_file.close (); } return false ; }
inlinehook检测 通过 IDA 分析被 frida 调试的进程(需先frida附加到进程再使用 IDA 调试),会发现被 hook 的函数的开头改成了一串16进制数,例如
我们可以通过检测每个函数的开头是否有0xd61f020058000050
这样的一段代码来判断进程是否被frida 附加了。具体参考从inlinehook角度检测frida-Android安全-看雪-安全社区|安全招聘|kanxue.com 。
自定义 syscall 防 hook 当然,以上检测方法中使用到的函数如果被 frida hook,显然就绕过了这些检测,因此对于最关键的函数,例如open、read函数等,需要自定义相应的 syscall,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include "bionic_asm.h" .text .globl my_openat .type my_openat,function my_openat: .cfi_startproc mov ip, r7 .cfi_register r7, ip ldr r7, =__NR_openat swi #0 mov r7, ip .cfi_restore r7 cmn r0, #(4095 + 1) bxls lr neg r0, r0 b __set_errno_internal .cfi_endproc .size my_openat, .-my_openat; .text .globl my_read .type my_read,function my_read: .cfi_startproc mov ip, r7 .cfi_register r7, ip ldr r7, =__NR_read swi #0 mov r7, ip .cfi_restore r7 cmn r0, #(MAX_ERRNO + 1) bxls lr neg r0, r0 b __set_errno_internal .cfi_endproc .size my_read, .-my_read;
具体使用可参考https://github.com/qtfreet00/AntiFrida和https://github.com/muellerberndt/frida-detection
总结 对于 frida 的检测点远远不止这些,想要深入了解就需要阅读 frida 源码。然而对于 frida 反检测,也需要阅读 frida 源码,修改掉其中的特征,从而达到反检测目的。
参考:
[翻译]多种特征检测 Frida-外文翻译-看雪-安全社区|安全招聘|kanxue.com
https://bbs.kanxue.com/thread-278145-1.htm
frida 检测 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
https://xxr0ss.github.io/post/frida_detection/
https://github.com/qtfreet00/AntiFrida
https://frida.re/slides/osdc-2015-the-engineering-behind-the-reverse-engineering.pdf
从inlinehook角度检测frida-Android安全-看雪-安全社区|安全招聘|kanxue.com