Android 逆向 | Frida 是萬能的嗎? 檢測 Frida 的幾種辦法

Frida 在逆向工程獅中很受歡迎,你基本能夠在運行時訪問到你能想到的任何東西,內存地址、native 函數、Java 實例對象等。python

在 OWASP 的移動測試指南里就提到了 Frida。可是啊,每出來個好用的注入工具,都會有反注入、反反注入、反反反注入、反...注入。android

這篇文章要介紹的是 Android APP 檢測 Frida 的方法。git

檢查 Frida 的痕跡

一種簡易方法是檢測 Frida 的運行痕跡,也適用於同類工具的檢測,好比包文件、二進制文件、庫文件、進程、臨時文件等等。github

本例中針對的對象是 fridaserver,它經過 TCP 對外與 frida 通訊,此時能夠用 Java 遍歷運行的進程列表從而檢查 fridaserver 是否在運行。web

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;
}

若 Frida 運行在默認配置時此法有效,如果遇到個笨拙的腳本小子,在第一步就能絆倒他。繞過也是至關簡單,只需重命名 fridaserver,咱們得找個更好的方法。微信

fridaserver 默認的 TCP 端口是 27047,能夠檢查這個端口是否開放。native 代碼以下:app

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 server detected. Do something… */
    }
}

一樣,這也得要求 fridaserver 是默認配置運行,命令行指定參數就能夠改變它的監聽端口,繞過也太不麻煩了。socket

不過咱們能夠用nmap -sV找到開放端口來改善這個方法,由於 fridaserver 使用 D-Bus 協議通訊,咱們爲每一個開放的端口發送 D-Bus 的認證消息,哪一個端口回覆了哪一個就是 fridaserver。ionic

/*
 * Mini-portscan to detect frida-server on any local port.
 */

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);
}

咱們如今好像有了個很是好用的方法了呢,可是還存在問題。編輯器

Frida 提供不須要 fridaserver 運行的模式!怎麼檢測?!

Frida 的各個模式都是用來注入的,咱們能夠利用的點就是 frida 運行時映射到內存的庫。最直接的是挨個檢查加載的庫。

char line[512];
FILE* fp;
fp = fopen("/proc/self/maps""r");
if (fp) {
    while (fgets(line, 512, fp)) {
        if (strstr(line, "frida")) {
            /* Evil library is loaded. Do something… */
        }
    }
    fclose(fp);
    } else {
       /* Error opening /proc/self/maps. If this happens, something is off. */
    }
}

這段代碼檢測名字含有「frida」的庫,代表上有用,實際上:

- 還記得爲何檢測名字是「fridaserver」的方法爲何不可靠吧?這裏也是同樣,稍微改一下 frida 就能重命名代理庫名。

- 這段代碼依賴的是標準庫的fopen()strstr()函數,好笑的是,咱們竟想用能被 frida 垂手可得就 hook 的函數來檢測 frida !

第一點能夠用經典的病毒掃描法解決,在內存中掃描 frida 的庫特徵 「gadgets」。

我選擇字符串 「LIBFRIDA」,它在全部 frida-gadget 和 frida-agent 的版本中都有出現。

下面的代碼掃描了在 /proc/sel/maps 裏找到的全部的可執行段,爲了簡潔我放了部分代碼,完整的在 https://github.com/b-mueller/frida-detection-demo/blob/master/AntiFrida/app/src/main/cpp/native-lib.cpp

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 = my_openat(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) {
        /* Frida Detected */
    }
}

注意 my_openat() 等函數,它們並不是日常的 libc 庫函數,是自定義實現的,可是功能和 libc 中的同樣,設置了系統調用的參數,執行了軟中斷。由於直接調用公共 API 並不可靠,這樣不容易被 hook。

完整的實如今 https://github.com/b-mueller/frida-detection-demo/blob/master/AntiFrida/app/src/main/cpp/syscall.S 。

下面是 my_openat 的代碼:

#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;

到這裏總算是有效的方法了,只用 frida 的話也不容易繞過,加了混淆更難。即便這樣,依然有不少辦法能夠繞過,直接能想到的就是打補丁、hook 系統調用。可是記住,逆向工程永遠勝利

想要試驗,能夠在這裏 https://github.com/b-mueller/frida-detection-demo/ 下載 Android studio 工程。frida 注入時的運行結果以下:

原文連接:http://www.vantagepoint.sg/blog/90-the-jiu-jitsu-of-detecting-frida

本文由看雪翻譯小組 kiyaa編譯

本文轉載自看雪論壇

Love & Share 

[ 完 ]

本文分享自微信公衆號 - 鹹魚學Python(xianyuxuepython)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索