android反编译反调试入门资料

Mark_YPQ Mark_YPQ     2022-11-29     815

关键词:

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改名就可绕过

[objc] view plain copy
  1. pid_t GetPidByName(const charchar *as_name)   
  2.         DIR *pdir = NULL;  
  3.         struct dirent *pde = NULL;  
  4.         FILEFILE *pf = NULL;  
  5.         char buff[128];  
  6.         pid_t pid;  
  7.         char szName[128];  
  8.         // 遍历/proc目录下所有pid目录    
  9.         pdir = opendir("/proc");  
  10.         if (!pdir)   
  11.                 perror("open /proc fail.\\n");  
  12.                 return -1;  
  13.           
  14.         while ((pde = readdir(pdir)))   
  15.                 if ((pde->d_name[0] < '0') || (pde->d_name[0] > '9'))   
  16.                         continue;  
  17.                   
  18.                 sprintf(buff, "/proc/%s/status", pde->d_name);  
  19.                 pf = fopen(buff, "r");  
  20.                 if (pf)   
  21.                         fgets(buff, sizeof(buff), pf);  
  22.                         fclose(pf);  
  23.                         sscanf(buff, "%*s %s", szName);  
  24.                         pid = atoi(pde->d_name);  
  25.                         if (strcmp(szName, as_name) == 0)   
  26.                                 closedir(pdir);  
  27.                                 return pid;  
  28.                           
  29.                   
  30.           
  31.         closedir(pdir);  
  32.         return 0;  
  33.   

c.读取进程状态(/proc/pid/status)

State属性值T 表示调试状态,TracerPid 属性值正在调试此进程的pid,在非调试情况下State为S或R, TracerPid等于0

d.读取 /proc/%d/wchan

下图中第一个红色框值为非调试状态值,第二个红色框值为调试状态:

[objc] view plain copy
  1. static void get_process_status(pid_t pid,const char* info,charchar *outline)  
  2.   
  3.       FILEFILE *fp;  
  4.       char filename;  
  5.       char line = 0;  
  6.       snprintf( filename, sizeof(filename), "/proc/%d/status", pid );  
  7.       fp = fopen( filename, "r" );  
  8.       if ( fp != NULL )  
  9.         
  10.                 while ( fgets( line, sizeof(line), fp ) )  
  11.                   
  12.                         if ( strstr( line, info ) )  
  13.                               strcpy(outline,line);  
  14.                   
  15.                 fclose( fp ) ;  
  16.         
  17.       return ;  
  18.   
  19. static int getProcessStatus(int pid)  
  20.   
  21.       char readline = 0;  
  22.       int result = STATUS_ELSE;  
  23.       get_process_status(pid,"State",readline);  
  24.       if(strstr(readline,"R"))        
  25.                 result = STATUS_RUNNING;  
  26.       else if(strstr(readline,"S"))  
  27.                 result = STATUS_SLEEPING;  
  28.       else if(strstr(readline,"T"))  
  29.                 result = STATUS_TRACING;  
  30.       return result;  
  31.   
  32. static int getTracerPid(int pid)  
  33.   
  34.       char readline = 0;  
  35.       int result = INVALID_PID;  
  36.       get_process_status(pid,"TracerPid",readline);  
  37.       charchar *pidnum = strstr(readline,":");  
  38.       result = atoi(pidnum + 1);  
  39.       return result;  
  40.   
  41. static int getWchanStatus(int pid)  
  42.   
  43.       FILEFILE *fp= NULL;  
  44.       char filename;  
  45.       char wchaninfo = 0;  
  46.       int result = WCHAN_ELSE;  
  47.       char cmd = 0;  
  48.       sprintf(cmd,"cat /proc/%d/wchan",pid);  
  49.       LOGANTI("cmd= %s",cmd);  
  50.       FILEFILE *ptr;         if((ptr=popen(cmd, "r")) != NULL)  
  51.         
  52.                 if(fgets(wchaninfo, 128, ptr) != NULL)  
  53.                   
  54.                         LOGANTI("wchaninfo= %s",wchaninfo);  
  55.                   
  56.         
  57.       if(strncasecmp(wchaninfo,"sys_epoll\\0",strlen("sys_epoll\\0")) == 0)  
  58.                 result = WCHAN_RUNNING;  
  59.       else if(strncasecmp(wchaninfo,"ptrace_stop\\0",strlen("ptrace_stop\\0")) == 0)  
  60.                 result = WCHAN_TRACING;  
  61.       return result;  
  62.   

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

[objc] view plain copy
  1. ptrace me  
  2. if (ptrace(PTRACE_TRACEME, 010) < 0)   
  3. printf("DEBUGGING... Bye\\n");  
  4. return 1;  
  5.   
  6. void anti_ptrace(void)  
  7.   
  8.     pid_t child;  
  9.     child = fork();  
  10.     if (child)  
  11.       wait(NULL);  
  12.     else   
  13.       pid_t parent = getppid();  
  14.       if (ptrace(PTRACE_ATTACH, parent, 00) < 0)  
  15.             while(1);  
  16.       sleep(1);  
  17.       ptrace(PTRACE_DETACH, parent, 00);  
  18.       exit(0);  
  19.       
  20.   

f. 防止dump

利用Inotify机制,对/proc/pid/mem和/proc/pid/pagemap文件进行监视。inotify API提供了监视文件系统的事件机制,可用于监视个体文件,或者监控目录。具体原理可参考:http://man7.org/linux/man- pages/man7/inotify.7.html
伪代码:
[objc] view plain copy
  1. void __fastcall anitInotify(int flag)  
  2.   
  3.       MemorPagemap = flag;  
  4.       charchar *pagemap = "/proc/%d/pagemap";  
  5.       charchar *mem = "/proc/%d/mem";  
  6.       pagemap_addr = (charchar *)malloc(0x100u);  
  7.       mem_addr = (charchar *)malloc(0x100u);  
  8.       ret = sprintf(pagemap_addr, &pagemap, pid_);  
  9.       ret = sprintf(mem_addr, &mem, pid_);  
  10.       if ( !MemorPagemap )  
  11.         
  12.                 ret = pthread_create(&th, 0, (voidvoid *(*)(voidvoid *)) inotity_func, mem_addr);  
  13.                 if ( ret >= 0 )  
  14.                    ret = pthread_detach(th);  
  15.         
  16.       if ( MemorPagemap == 1 )  
  17.         
  18.                 ret = pthread_create(&newthread, 0, (voidvoid *(*)(voidvoid *)) inotity_func, pagemap_addr);  
  19.                 if(ret > 0)  
  20.                   ret = pthread_detach(th);  
  21.         
  22.   
  23. void __fastcall __noreturn inotity_func(const charchar *inotity_file)  
  24.   
  25.       const charchar *name; // r4@1  
  26.       signed int fd; // r8@1  
  27.       bool flag; // zf@3  
  28.       bool ret; // nf@3  
  29.       ssize_t length; // r10@3  
  30.       ssize_t i; // r9@7  
  31.       fd_set readfds; // @2  
  32.       char event; // @1  
  33.       name = inotity_file;  
  34.       memset(buffer, 00x400u);  
  35.       fd = inotify_init();  
  36.       inotify_add_watch(fd, name, 0xFFFu);  
  37.       while ( 1 )  
  38.         
  39.                 do  
  40.                   
  41.                         memset(&readfds, 00x80u);  
  42.                   
  43.                 while ( select(fd + 1, &readfds, 000) <= 0 );  
  44.                 length = read(fd, event, 0x400u);  
  45.                 flag = length == 0;  
  46.                 ret = length < 0;  
  47.                 if ( length >= 0 )  
  48.                   
  49.                         if ( !ret && !flag )  
  50.                         
  51.                               i = 0;  
  52.                               do  
  53.                                 
  54.                                         inotity_kill((int)&event);  
  55.                                         i += *(_DWORD *)&event + 16;  
  56.                                 
  57.                               while ( length > i );  
  58.                           
  59.                   
  60.                 else  
  61.                   
  62.                         while ( *(_DWORD *)_errno() == 4 )  
  63.                           
  64.                               length = read(fd, buffer, 0x400u);  
  65.                               flag = length == 0;  
  66.                               ret = length < 0;  
  67.                               if ( length >= 0 )  
  68.                           
  69.                   
  70.         
  71.   

g. 对read做hook

因为一般的内存dump都会调用到read函数,所以对read做内存hook,检测read数据是否在自己需要保护的空间来阻止dump

h. 设置单步调试陷阱

[objc] view plain copy
  1. int handler()  
  2.   
  3.     return bsd_signal(50);  
  4.   
  5. int set_SIGTRAP()  
  6.   
  7.     int result;  
  8.     bsd_signal(5, (int)handler);  
  9.     result = raise(5);  
  10.     return result;  
  11.   

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表,查看子串是否存在“apk@classes.dex”的字符串,若存在获取该模块的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文件
[objc] view plain copy
  1. FILEFILE * MyOpen( const charchar * filename, const charchar * mode )   
  2.       FILEFILE *file = NULL;  
  3.       if (strstr(filename, "/proc/self"))  
  4.                 LOGI("fileName:%s", filename);  
  5.                 file = oldFopen("/data/local/tmp/hone", mode );  
  6.       else  
  7.                 file = oldFopen( filename, mode );  
  8.       return file;  
  9.   


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/

java层反调试

安卓程序动态调试需要满足两个条件。1.在AndroidMainfest.xml文件中,在application标签下,Android:debuggable=true。2.系统默认调式,在build.prop(boot.img),ro.debugable=1。一:实例演示java层反调试以“百度加固”为例。1.将样本拖入jdax-gui中,... 查看详情

android逆向案例:干掉梆梆加固免费版的反调试检测

...a;使用【d2j-dex2jar】反编译后发现关键代码缺失,因为AndroidManifest.xml中的绝大部分activity和server的实现都是在cmb.pb这个包中,但是反编译的代码里没有cmb.pb这个包,只有一些无关紧要的第三方sdk,判断其核心代码进... 查看详情

android逆向案例:干掉梆梆加固免费版的反调试检测

...a;使用【d2j-dex2jar】反编译后发现关键代码缺失,因为AndroidManifest.xml中的绝大部分activity和server的实现都是在cmb.pb这个包中,但是反编译的代码里没有cmb.pb这个包,只有一些无关紧要的第三方sdk,判断其核心代码进... 查看详情

一种基于tls的高级反调试技术(代码片段)

...程序入口处插入断点命令来调试程序。对于使用C/C++语言编译的程序来说,问题通常会更严重,在执 查看详情

windows下反(反)调试技术汇总(代码片段)

反调试技术,恶意代码用它识别是否被调试,或者让调试器失效。恶意代码编写者意识到分析人员经常使用调试器来观察恶意代码的操作,因此他们使用反调试技术尽可能地延长恶意代码的分析时间。为了阻止调试器的分析,当... 查看详情

反调试技术常用api,用来对付检测od和自动退出程序

在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序可以检测到自己是否被调试器附加了,如果探知自己正在被调试,肯定是有人试图反汇编啦之类的方法破解自己。为了了解如何破解反调试技术... 查看详情

修改android手机内核,绕过反调试(代码片段)

本文博客链接:http://blog.csdn.net/qq1084283172/article/details/570864860x1.手机设备环境Modelnumber:Nexus5OSVersion:Android4.4.4KTU84PKernelVersion:3.4.0-gd59db4e0x2.Android内核提取查找Android设备的boot分区文件。高通芯片的设备可 查看详情

[转转]反调试技巧总结-原理和实现-----转

原本也是转的,但我没找到原作者标题: 反调试技巧总结-原理和实现-----转作者: lcx4时间: 2016-4-7链接: http://www.lcx4.com/?post=512008.8.7  shellwolf一、 前言    前段学习反调试和vc,写了antidebug-tester,经常会收到mes... 查看详情

android反调试实践(代码片段)

(一)xposed检测1.每一个被hook的进程,都会将xposed的相关库文件和jar文件加载到相应的进程空间中,如图:这里看到进程空间中加载了1)app_process32_xposed2)libxposed_art.so3)XResourcesSuperClass.dex既然能... 查看详情

反调试手法之createprocess反调试(代码片段)

              反调试手法之CreateProcess反调试在学习Win32创建进程的时候.我们发现了有一个进程信息结构体.STARTUPINFO.这个结构体可以实现反调试.具体CreateProcess可以参考上一篇博客.:  https://www.cnblogs.com/iBi... 查看详情

逆向手机内核,添加调试支持和反调试

 0x00前言  一个安卓应用可以被调试的条件是应用AndroidManifest.xml显示指定android:debuggable="true",如果没有设置android:debuggable的值,则默认android:debuggable="false",所以发布的应用大部分都是不可调试的,如果要调试,则需要解... 查看详情

frida反调试

...反调试。这里记录一下出现问题的情况和反调试的实现.Android8frida12.11.18windows10使用frida-U注入app结果如下这里有两个com.shark.tracerpidapp名称的进程我们直接进入adb查看进程可以看到确实有两个进程,frida分不清你要注入哪个进程所... 查看详情

过反调试

...二者本为一体破解技术就不要我多介绍了,下面我来介绍反调试技术也就是所谓的防破解技术反调试技术可以简单通俗的理解为:防止OD分析软件的技术,也就是反调试技术那么反调试技术又有几种呢?下面我介绍几种常用反调... 查看详情

反调试-checkremotedebuggerpresent

和IsDebuggerPresent差不多简单的反调试技术 查看详情

android签名验证与反调试机制的对抗技术(代码片段)

...2.6时间差异检测2.7内置函数检测2.8调试断点检测总结前言Android的APK文件为了防止被篡改和重打包,经常会做签名校验来保证自身完整性,当程序被篡改后将提示用户或者直接退出运行。同时有些 查看详情

反调试-isdebuggerpresent

目前这个是我知道的最简单的反调试方法了 查看详情

ida动态调试破解alicrackme与反调试对抗(代码片段)

...调试对抗2.1Ptrace2.2全局调试2.3反反调试APK破解(下)3.1重新编译3.2动态调试总结前言在前面的文章中IDA动态调试破解EXE文件与分析APK流程介绍了IDA对APK进行动态调试分析的简单流程,然而实际上很多APP为了防止被动态调试分析&#... 查看详情

反调试

反调试IsDebuggerPresent()__readgsqword(0x60)得到指向PEB64的指针,pPeb->BeingDebugged.__readfsdword(0x30)CheckRemoteDebuggerPresent(GetCurrentProcess(),&bIsDbgPresent)BOOL HeapFlags(),64位下,得到指向PEB的地址加 查看详情