android trap攻防

android trap攻防

                                                                     圖/文 h_one

0x01 反編譯出錯

1.插入無效指令是部分逆向工具崩潰

原理:大部分逆向工具都是線性讀取字節碼並解析, 如dex2jar,baksmali,apktool等,當遇到無效字節碼時,就會引起反編譯工具解析失敗。
例如:新版的dex2jar 遇到這種情況任然沒法轉化成jar,在新版本的baksmail和apktool已修復此問題。


010editor查看,紅色框中就是加入的陷阱類,繞過方法很簡單,只要將這個三個類刪除,重編譯即可


2.利用apk包本質上是zip/jar包進行保護

a.僞加密

在Android4.2.x前的一種保護方 式,通過APK(壓縮文件)進行僞加密,其修改原理是修改連續4位字節標記爲“P K 01 02”後的第5字節(ps:一般在文件末尾有多處),奇數表示加密偶數不加密,這種保護方式一般只會出現在一些cm裏,因爲:
1.對系統不兼容;
2.僞加密 處理後的apk市場也無法對其進行安全檢測,部分市場會拒絕這類APK上傳市場


b. 文件名長度操作

android平臺對文件名的長度是沒有限制的,但是操作系統要求不能大於255;於是可以構建超長字符的類名達到反編譯錯誤目的。

3.axml文件保護

目的:防止/檢測二次打包

android在解析AXMl文件時,是用過屬性的資源id,而不是資源名,當android系統遇到非法資源id時,並不會做解析,說以可以對axml文件解析,添加無用的屬性。但是對於破解者來說,一般會在java層增加log信息,然後打包apk, 此時如apktool之類的逆向工具,就會無法解析無效屬性,或進入trap類(檢測二次打包)


0x02 運行環境檢測

環境檢測主要包括

  1. ▏運行在調試狀態下
  2. ▏系統代碼被hook
  3. ▏以及運行環境在模擬器中

目的:爲了保護關鍵代碼被逆向分析,一般放在應用程序初始化過程中,如init_array,或jni_onload函數裏進行檢查代碼執行。

1.調試檢測

對調試器的檢測(ida,gdb,strace, ltrace等調試工具)

a.父進程檢測

b.當前運行進程檢測

例如對android_server進程檢測。針對這種檢測只需將android_server改名就可繞過

pid_t GetPidByName(const char *as_name) {
        DIR *pdir = NULL;
        struct dirent *pde = NULL;
        FILE *pf = NULL;
        char buff[128];
        pid_t pid;
        char szName[128];
        // 遍歷/proc目錄下所有pid目錄  
        pdir = opendir("/proc");
        if (!pdir) {
                perror("open /proc fail.\n");
                return -1;
        }
        while ((pde = readdir(pdir))) {
                if ((pde->d_name[0] < '0') || (pde->d_name[0] > '9')) {
                        continue;
                }
                sprintf(buff, "/proc/%s/status", pde->d_name);
                pf = fopen(buff, "r");
                if (pf) {
                        fgets(buff, sizeof(buff), pf);
                        fclose(pf);
                        sscanf(buff, "%*s %s", szName);
                        pid = atoi(pde->d_name);
                        if (strcmp(szName, as_name) == 0) {
                                closedir(pdir);
                                return pid;
                        }
                }
        }
        closedir(pdir);
        return 0;
}

c.讀取進程狀態(/proc/pid/status)

State屬性值T 表示調試狀態,TracerPid 屬性值正在調試此進程的pid,在非調試情況下State爲S或R, TracerPid等於0

d.讀取 /proc/%d/wchan

下圖中第一個紅色框值爲非調試狀態值,第二個紅色框值爲調試狀態:

static void get_process_status(pid_t pid,const char* info,char *outline)
{
      FILE *fp;
      char filename;
      char line = {0};
      snprintf( filename, sizeof(filename), "/proc/%d/status", pid );
      fp = fopen( filename, "r" );
      if ( fp != NULL )
      {
                while ( fgets( line, sizeof(line), fp ) )
                {
                        if ( strstr( line, info ) )
                              strcpy(outline,line);
                }
                fclose( fp ) ;
      }
      return ;
}
static int getProcessStatus(int pid)
{
      char readline = {0};
      int result = STATUS_ELSE;
      get_process_status(pid,"State",readline);
      if(strstr(readline,"R"))      
                result = STATUS_RUNNING;
      else if(strstr(readline,"S"))
                result = STATUS_SLEEPING;
      else if(strstr(readline,"T"))
                result = STATUS_TRACING;
      return result;
}
static int getTracerPid(int pid)
{
      char readline = {0};
      int result = INVALID_PID;
      get_process_status(pid,"TracerPid",readline);
      char *pidnum = strstr(readline,":");
      result = atoi(pidnum + 1);
      return result;
}
static int getWchanStatus(int pid)
{
      FILE *fp= NULL;
      char filename;
      char wchaninfo = {0};
      int result = WCHAN_ELSE;
      char cmd = {0};
      sprintf(cmd,"cat /proc/%d/wchan",pid);
      LOGANTI("cmd= %s",cmd);
      FILE *ptr;         if((ptr=popen(cmd, "r")) != NULL)
      {
                if(fgets(wchaninfo, 128, ptr) != NULL)
                {
                        LOGANTI("wchaninfo= %s",wchaninfo);
                }
      }
      if(strncasecmp(wchaninfo,"sys_epoll\0",strlen("sys_epoll\0")) == 0)
                result = WCHAN_RUNNING;
      else if(strncasecmp(wchaninfo,"ptrace_stop\0",strlen("ptrace_stop\0")) == 0)
                result = WCHAN_TRACING;
      return result;
}

e. ptrace 自身或者fork子進程相互ptrace

ptrace me
if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
printf("DEBUGGING... Bye\n");
return 1;
}
void anti_ptrace(void)
{
    pid_t child;
    child = fork();
    if (child)
      wait(NULL);
    else {
      pid_t parent = getppid();
      if (ptrace(PTRACE_ATTACH, parent, 0, 0) < 0)
            while(1);
      sleep(1);
      ptrace(PTRACE_DETACH, parent, 0, 0);
      exit(0);
    }
}

f. 防止dump

利用Inotify機制,對/proc/pid/mem和/proc/pid/pagemap文件進行監視。inotify API提供了監視文件系統的事件機制,可用於監視個體文件,或者監控目錄。具體原理可參考:http://man7.org/linux/man- pages/man7/inotify.7.html
僞代碼:
void __fastcall anitInotify(int flag)
{
      MemorPagemap = flag;
      char *pagemap = "/proc/%d/pagemap";
      char *mem = "/proc/%d/mem";
      pagemap_addr = (char *)malloc(0x100u);
      mem_addr = (char *)malloc(0x100u);
      ret = sprintf(pagemap_addr, &pagemap, pid_);
      ret = sprintf(mem_addr, &mem, pid_);
      if ( !MemorPagemap )
      {
                ret = pthread_create(&th, 0, (void *(*)(void *)) inotity_func, mem_addr);
                if ( ret >= 0 )
                   ret = pthread_detach(th);
      }
      if ( MemorPagemap == 1 )
      {
                ret = pthread_create(&newthread, 0, (void *(*)(void *)) inotity_func, pagemap_addr);
                if(ret > 0)
                  ret = pthread_detach(th);
      }
}
void __fastcall __noreturn inotity_func(const char *inotity_file)
{
      const char *name; // r4@1
      signed int fd; // r8@1
      bool flag; // zf@3
      bool ret; // nf@3
      ssize_t length; // r10@3
      ssize_t i; // r9@7
      fd_set readfds; // @2
      char event; // @1
      name = inotity_file;
      memset(buffer, 0, 0x400u);
      fd = inotify_init();
      inotify_add_watch(fd, name, 0xFFFu);
      while ( 1 )
      {
                do
                {
                        memset(&readfds, 0, 0x80u);
                }
                while ( select(fd + 1, &readfds, 0, 0, 0) <= 0 );
                length = read(fd, event, 0x400u);
                flag = length == 0;
                ret = length < 0;
                if ( length >= 0 )
                {
                        if ( !ret && !flag )
                      {
                              i = 0;
                              do
                              {
                                        inotity_kill((int)&event);
                                        i += *(_DWORD *)&event + 16;
                              }
                              while ( length > i );
                        }
                }
                else
                {
                        while ( *(_DWORD *)_errno() == 4 )
                        {
                              length = read(fd, buffer, 0x400u);
                              flag = length == 0;
                              ret = length < 0;
                              if ( length >= 0 )
                        }
                }
      }
}

g. 對read做hook

因爲一般的內存dump都會調用到read函數,所以對read做內存hook,檢測read數據是否在自己需要保護的空間來阻止dump

h. 設置單步調試陷阱

int handler()
{
    return bsd_signal(5, 0);
}
int set_SIGTRAP()
{
    int result;
    bsd_signal(5, (int)handler);
    result = raise(5);
    return result;
}

2. 模擬器檢測

用戶層行爲和數據檢測,模擬器特有屬性值,以及模擬器體系結構特徵
  1. ▏電池狀態和電流,模擬器默認電話號碼檢測,檢測設備IDS 是不是“000000000000000”, 檢測imsi id是不是“310260000000000“,手機運營商等
  2. ▏API Demo,Dev tool一般模擬器上纔有的應用,檢測安裝應用,短信箱,通信錄,相冊等
  3. ▏讀取/system/build.prop文件
  4. ▏調用__system_property_get,或反射調用Systemproperty.get獲取系統屬性值
  5. ▏通過執行shell命令檢測模擬器,如getprop
  6. ▏檢查模擬器特有文件如 /dev/socket/qemud","/dev/qemu_pipe","/sysrtem/bin/qemud",/dev/qemu_pipe,/dev/qemu_trace等
  7. ▏模擬器cpu信息值差異,如hardware,Revision等
  8. ▏系統屬性值等(android.os.build)
  9. ▏基於Qemu二進制翻譯技術(ps:真機具有真正的物理CPU,在執行一段指令的時候只能一條一條的去執行指令(編譯器沒有對指令進行優化的前提下)。模擬器沒有真正的物理CPU,所以,他在執行一段指令的時候,這段指令已經被人爲的優化掉)http://www.dexlabs.org/blog/btdetect
  10. ▏通過觀察低級別的緩存行爲。檢測方法: 默認情況下,Android模擬器提供了Android SDK是基於QEMU,仿真器不具備分裂緩存。而在真實設備上存在兩個不同的緩存,一個用於數據訪問,一個用於指令。https://bluebox.com/technical/android-emulator-detection-by-observing-low-level-caching-behavior/

0x03防app運行環境被hook

hook代碼肯定是在app自身模塊加載之前運行的,那麼在app的maps表裏會首先加載hook框架的dex,我們只要對此dex做簡單的校驗,就會檢測到app被注入了。
編碼思路:

遍歷maps表,查看子串是否存在“[email protected]”的字符串,若存在獲取該模塊的startAddr和endAddr, 然後檢驗此odex的頭部是否爲真正的dex文件。


0x4抗靜態分析(ida F5 以及執行流程圖)

1.arm 指令插花

2.通過棧修改程序調用過程

STDFD保存寄存器值到棧上,LDMFD將棧上數據賦值到寄存器中,這個過程修改了函數返回的地址。

0x05如何繞過app環境檢測

1.調試過程中修改代碼

hook檢測點(重定向函數出關鍵文件操作,函數返回值修改等),修改源碼(改變字段屬性主要針對模擬器仿真,修改函數返回值,例如繞過簽名校驗等)
eg:
ida patch 線程退出函數: patch 地址48D9668A處的函數調用

a.在數據窗口定位到機器碼位置

b.F2 編輯機器碼 00 00 0A EF (movs R0, R0)

c.F2保存修改

2.fopen函數相關的檢測

由於/proc/pid/status,/proc/pid/wchan,/proc/pid/mem等都是針對文件狀態的檢測,入口點函數一般都爲fopen, 我們可以事先攔截fopen,查看app是否左右這方面的防護。
以某某app爲例:如下圖此app fopen了這些文件,我們就能猜測這是對調試檢測。

繞過方法:在指定目錄下(/data/local/tmp)新建一文件alimolisec, hook fopen函數檢測到文件名子串有/proc/self時,就重定位打開alimolisec文件
FILE * MyOpen( const char * filename, const char * mode ) {
      FILE *file = NULL;
      if (strstr(filename, "/proc/self")){
                LOGI("fileName:%s", filename);
                file = oldFopen("/data/local/tmp/hone", mode );
      }else
                file = oldFopen( filename, mode );
      return file;
}


0x06 CM中的那些殼子

加固手法:

1.不替換源classes.dex,也沒有做任何加密的處理,對classes.dex中的Activity,service, receiver
等的oncreate,onReceive,加密替換,殼首先拿到執行權,在自身so裏完成對源dex還原。
2.不替換源classes.dex,也沒有做任何加密的處理, 修改原Dex的Class_Data,將
MyContentProvider,Application,Activity,service類的入口函數 onCreate方法標記爲native方
法,但是原始字節碼仍然未加密保存在dex文件中。
3.對classes.dex整包加密,使用殼加載器內存解密classes.dex,並替換原始成源classes.dex
4.對classes.dex整包加密,並將原dex拆分成兩部分,在內存中分兩塊區域存儲。

脫殼手法:

1.對部分整包加密的可以通過運行時memory dump,部分通過攔截dvmDexFileOpenPartial函
數即可獲取完整dex。(dex連續)
2.對於修改了dex的Class_data和classes.dex做了拆分的,可以通過找到dex對應的pDvmDex

結構,重建dex(dex不連續或不完整的)


0x07 android平臺常用的hook框架

▏cydia substrate。原理:框架注入zygote進程,採用inline hook( 修改目標函數前N字節,跳轉到自定義函數入口,備份目標函數前N個字節,跳轉回目標函數)
▏Xposed。原理:替換app_process,將需要hook的java函數替換成JNI函數,所有需要HOOK的函數首先由xposedCallHandler處 理,xposedCallHandler負責調用註冊的beforeHookMethod和afterHookedMethod

▏adbi。原理:利用ptrace()函數attach到一個進程上,然後在其調用序列中插入一個調用dlopen()函數的步驟,將一個實現預先備好的.so文件加載到要hook的進程中,最終由這個加載的.so文件在初始化化函數中hook指定的函數。


參考文檔

http://man7.org/linux/man-pages/man7/inotify.7.html
http://www.dexlabs.org/blog/btdetect
https://bluebox.com/technical/android-emulator-detection-by-observing-low-level-caching-behavior/
https://github.com/crmulliner/adbi
http://www.cydiasubstrate.com/

發佈了28 篇原創文章 · 獲贊 9 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章