常见Frida检测方法+一个小Demo


Frida检测方法

最近研究了一下常见的Frida检测方法,本文对其中一些比较简单的检测进行了一些记录,并且提供了一个检测的Demo,由于本人C语言水平极菜所以写出来的Demo也比较水,代码方面大家可以稍作参考。另外安装包使用了最简单的混淆和默认OLLVM方案,供小伙伴门试水。

D-Bus

属于比较常用的一种检测方式,fridaserver是使用 D-Bus 协议通信,我们为每个开放的端口发送 D-Bus 的认证消息,哪个端口回复了哪个就是 fridaserver,但是此方法需要的时间会比较长,一般来讲如果出现一个App启动后过了一会就退出那么就有可能是这种检测方案。

for(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) {
        __android_log_print(ANDROID_LOG_VERBOSE, APPNAME,  "FRIDA DETECTION [1]: Open Port: %d", i);
        memset(res, 0 , 7);
        // send a D-Bus AUTH message. Expected answer is “REJECT"
        send(sock, "\x00", 1, NULL);
        send(sock, "AUTH\r\n", 6, NULL);
        usleep(100);
        if (ret = recv(sock, res, 6, MSG_DONTWAIT) != -1) {
            if (strcmp(res, "REJECT") == 0) {
               /* Frida server detected. Do something… */
            }
        }
    }
    close(sock);
}

进程名

进程名方式检测在高版本Android上是无法使用的,目前使用这种方式检测frida的基本上已经没有了,这种检测方案基本上是没有意义的。

// Android5.1以上无效
public boolean checkRunningProcesses() {
  boolean returnValue = false;
  // Get currently running application processes
  List<RunningServiceInfo> list = manager.getRunningServices(300);
  if(list != null){
    String tempName;
    for(int i=0;i<list.size();++i){
      tempName = list.get(i).process;
      if(tempName.contains("fridaserver")) {
        returnValue = true;
      }
    }
  }
  return returnValue;
}
public static synchronized boolean mCheckFridaShell() {
  String rsp = "";
  synchronized (CheckFrida.class) {
    if (Build.VERSION.SDK_INT < 26) {
      rsp = shellExec("ps");
    } else {
      rsp = shellExec("ps -ef");
    }
  }
  return rsp.contains("frida");
}

默认端口

比较低级的检测方案,frida默认端口27047,通过检测默认端口是否开放来检测frida是否开启,只需要启动时指定端口即可绕过,也属于没有太多意义的检测方案。

boolean is_frida_server_listening() {
    struct sockaddr_in sa;
    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(27047);
    inet_aton("127.0.0.1", &(sa.sin_addr));
    int sock = socket(AF_INET , SOCK_STREAM , 0);
    if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) {
      
    }
}

默认路径

frida默认会在/data/local/tmp/re.frida.server/frida-agent-64.so中存放frida-agent,可以查找此路径下是否存在对应文件,也属于比较初级的检测方案,使用去除了特征的frida即可绕过

new File("/data/local/tmp/re.frida.server/frida-agent-64.so").exists()

maps

frida在注入App后会在maps中显示frida的frida-agent.so的内存信息,可以通过搜索特征字符串来检测frida。目前大佬们的去特征的frida一般会使用随机so名称,此时也可以通过查找maps文件内存信息中是否存在一些满足某些条件的特征路径,如data/local/tmp

char line[512];
FILE* fp;
fp = fopen("/proc/self/maps", "r");
if (fp) {
    while (fgets(line, 512, fp)) {
        if (strstr(line, "frida")) {
           
        }
    }
    fclose(fp);
    } else {
       
    }
}

内存扫描库特征

其实也是maps中的特征信息

static char keyword[] = "LIBFRIDA";
num_found = 0;
int scan_executable_segments(char * map) {
    char buf[512];
    unsigned long start, end;
    sscanf(map, "%lx-%lx %s", &start, &end, buf);
    if (buf[2] == 'x') {
        return (find_mem_string(start, end, (char*)keyword, 8) == 1);
    } else {
        return 0;
    }
}
void scan() {
    if ((fd = open(AT_FDCWD, "/proc/self/maps", O_RDONLY, 0)) >= 0) {
    while ((read_one_line(fd, map, MAX_LINE)) > 0) {
        if (scan_executable_segments(map) == 1) {
            num_found++;
        }
    }
    if (num_found > 1) {
        
    }
}

查看tcp连接信息

frida-server启动后/proc/net/tcp和/proc/net/tcp6中会有特殊标识:69a2,可以通过搜索tcp中的字符串来检测frida是否启动

public static boolean mCheckFridaTcp(){
  String[] stringArrayTcp6;
  String[] stringArrayTcp;
  String tcpStringTcp6 = mReadFile("/proc/net/tcp6");
  String tcpStringTcp = mReadFile("/proc/net/tcp");
  boolean isFridaExits = false;
  if(null != tcpStringTcp6 && !"".equals(tcpStringTcp6)){
    stringArrayTcp6 = tcpStringTcp6.split("\n");
    for(String sa : stringArrayTcp6){
      if(sa.toLowerCase().contains(":69a2")){
        Log.e(TAG,"tcp文件中发现Frida特征");
        isFridaExits = true;
      }
    }
  }
  if(null != tcpStringTcp && !"".equals(tcpStringTcp)){
    stringArrayTcp = tcpStringTcp.split("\n");
    for(String sa : stringArrayTcp){
      if(sa.toLowerCase().contains(":69a2")){
        Log.e(TAG,"tcp文件中发现Frida特征");
        isFridaExits = true;
      }
    }
  }
  return isFridaExits;

status文件特征

frida注入App后在App的/proc/self/task/pid/status文件中会存在一些frida的特征信息,如gmain、pool-frida、gdbus,可以通过这些特征进行检测,根据目前做过的几个检测来看,这还是比较常用的方法,一些人脸识别的SDK会用到此检测方案。

status

if (strstr(line, "frida") || strstr(line, "gum-js") || strstr(line, "gmain")) {
  fclose(fp);
  rst = filepath;
  strcat(rst, "==>");
  strcat(rst, line);
  return rst;
}

另外,以上检测方法检测皆使用系统函数读取文件和进行检测,实际应用中有很多检测方案会自己通过汇编实现读取文件或者比对方法,此时就需要对具体情况进行具体分析。

本文目前仅记录了比较简单的检测点和NB

最后:

如果需要下载测试Demo可到星球自取

参考文献

[翻译]多种特征检测 Frida-外文翻译-看雪论坛-安全社区|安全招聘|bbs.pediy.com[1]

详解Android studio ndk配置cmake开发native C - 博客 - 编程圈 (bianchengquan.com)[2]

darvincisec/DetectFrida: Detect Frida for Android (github.com)[3]

引用链接

[1] 翻译]多种特征检测 Frida-外文翻译-看雪论坛-安全社区|安全招聘|bbs.pediy.com: https://bbs.pediy.com/thread-217482.htm
[2] 详解Android studio ndk配置cmake开发native C - 博客 - 编程圈 (bianchengquan.com): https://www.bianchengquan.com/article/28252.html
[3] darvincisec/DetectFrida: Detect Frida for Android (github.com): https://github.com/darvincisec/DetectFrida

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐