您的位置:首页 > 其它

Jni反破解调研

2016-07-15 09:45 232 查看
1.动态库破解主要方法

1.1静态分析

通过反编译工具如:IDA、readelf等。

 

1.2 动态调试

Ptrace、dump内存等

 

2.动态库防破解方法

2.1针对静态分析的破解形式,有如下的防止破解方式:

 2.1.1. 使用动态注册方法

做法:JNI_OnLoad中动态注册方法,而不使用javah静态的方式命名函数;

优点:简单;

缺点:保护力度过小,枚举java 声明的native方法,事实上基本上可以查到对应的函数,使用工具轻易破解;

 

 2.1.2.修改动态库so ELF头或者节表信息

原理:修改so动态库的elf head 中关于section 段的信息,如e_shsize、e_shoff等,因为共享库链接时,只关心程序头部表,不关心节区头部表,所以对节区头部表“描述信息”的篡改不会影响到共享库的加载;但是反编译工具读取分析错误的信息异常甚至崩溃

下图修改e_shsize的IDA报错:

还有修改程序头表这种保护方式,如下图 所示,对PT_NOTE段做了一些修改。在该段的属性值中填充了一些无效的数字,导致逆向工具无法正常解析。由于系统并不会对PT_NOTE段进行分析,防御逆向工具的同时保证了该文件能被系统正常加载。

 

优点:实现简单,增加破解难度

缺点:针对这个问题,获取破解工具的源码可以修改对应点然后再编译可以绕过改崩溃点,对应修改ELF头和节头表信息的技巧很容易被识破和修复,所以只能防住初级破解者

参考:http://tech.it168.com/a2014/0425/1616/000001616976.shtml

2.1.3.对so函数进行加密

原理:利用__attribute__((constructor))属性解密,这个属性是so在初始化main函数执行前就会自动执行,相当于构造函数的类似,因此在已经加密的函数的so运行功能前进行解密对应的函数。

优点:静态加密性好;

缺点:加密及解密的工作量大且复杂,需要根据动态库的属性查询对应的信息来对加载的内存进行修改;

参考:http://blog.csdn.net/jiangwei0910410003/article/details/49966719

2.1.4.对so段进行加密

原理:同上,复杂度稍微小点。

参考:http://blog.csdn.net/jiangwei0910410003/article/details/49962173

2.1.5.使用第三方工具进行混淆和加密

原理:使用第三方工具对so进行加壳或混淆,后面将会具体点分析。

优点:加密性好

缺点:需要配置环境,可能不支持少数的cpu,通用比较多人使用,因此假如使用还是原来通用的代码的版本,带有对应的工具的特征,容易被识别破解。

 

以上是防止静态分析的,通过动态调试时可以拿到解密后的运行内容,因此还需要对动态分析的预防。

 

2.2动态调试

2.2.1.限制调试器连接

应用程序可以通过使用特定的系统API来防止调试器附加到该进程。通过阻止调试器连接,攻击者干扰底层运行时的能力是有限的。攻击者为了从底层攻击应用程序必须首先绕过调试限制。这进一步增加了攻击复杂性。Android应用程序应该在manifest中设置
Android:debuggable=“false” ,这样就不会很容易在运行时被攻击者或者恶意软件操纵。

 

2.2.2 .Trace检查

 应用程序可以检测自己是否正在被调试器或其他调试工具跟踪。如果被追踪,应用程序可以执行任意数量的可能攻击响应行为,如丢弃加密密钥来保护用户数据,通知服务器管理员,或者其它类型自我保护的响应。这可以由检查进程状态标志或者使用其它技术,如比较ptrace附加的返回值,检查父进程,黑名单调试器进程列表或通过计算运行时间的差异来反调试。

2.2.2.1 父进程检测

通常,我们在使用gdb调试时,是通过gdb
这种方式进行的。而这种方式是启动gdb,fork出子进程后执行目标二进制文件。因此,二进制文件的父进程即为调试器。我们可通过检查父进程名称来判断是否是由调试器fork。示例代码如下

这里我们通过getppid获得父进程的PID,之后由/proc文件系统获取父进程的命令内容,并通过比较字符串检查父进程是否为gdb。

 

2.2.2.2 当前运行进程检测

如IDA是启动android_server进程挂载调试,通过对
android_server 进程检测。但是,针对这种检测只需将
android_server 改名就可绕过。

 

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

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

 

 

由此,我们便可通过检查status文件中TracerPid的值来判断是否有正在被调试。示例代码如下:

 

 

注意的是,/proc目录下包含了进程的大量信息。我们在这里是读取status文件,此外,也可通过/proc/self/stat文件来获得进程相关信息,包括运行状态。

 

2.2.2.4 读取 /proc/%d/wchan

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

 

 

2.2.2.5  ptrace 自身或者fork子进程相互ptrace,ptrace只能一次注入

 

 

2.2.2.6 设置程序运行最大时间

由于程序在调试时的断点、检查修改内存等操作,运行时间往往要远大于正常运行时间。所以,一旦程序运行时间过长,便可能是由于正在被调试。具体地,在程序启动时,通过alarm设置定时,到达时则中止程序。示例代码如下:

我们通过 __attribute__((constructor)) ,在程序启动时便设置好定时。实际运行中,当我们使用gdb在main函数下断点,稍候片刻后继续执行时,则触发了SIGALRM,进而检测到调试器。但是,这种方式可以轻易地被绕过。我们可以设置gdb对signal的处理方式,如果我们选择将SIGALRM忽略而非传递给程序,则alarmHandler便不会被执行。

 

2.2.2.7 检查进程打开的filedescriptor

如上所提及,如果被调试的进程是通过gdb 的方式启动,那么它便是由gdb进程fork得到的。而fork在调用时,父进程所拥有的fd(file
descriptor)会被子进程继承。由于gdb在往往会打开多个fd,因此如果进程拥有的fd较多,则可能是继承自gdb的,即进程在被调试。具体地,进程拥有的fd会在/proc/self/fd/下列出。示例代码如下:

 

检查/proc/self/fd/中是否包含fd为5。由于fd从0开始编号,所以fd为5则说明已经打开了6个文件。如果程序正常运行则不会打开这么多,所以由此来判断是否被调试。

 

2.2.2.8 设置单步调试陷阱

2.2.3 防止dump

2.2.3.1 Inotify防止

利用Inotify机制,对/proc/pid/mem和/proc/pid/pagemap文件进行监视。inotify
API提供了监视文件系统的事件机制,可用于监视个体文件,或者监控目录。具体原理可参考: http://man7.org/linux/man-pages/man7/inotify.7.html
参考:http://blog.csdn.net/maspchen/article/details/50568506

 

 

 

 

 

3.第三方混淆工具

 

3.1.Safengine LLVM

参考:
http://www.52pojie.cn/thread-311381-1-1.html
 

 

3.2.Obfuscator-LLVM

参考:

https://github.com/obfuscator-llvm/obfuscator/wiki

 

 

3.3.upx

参考:

 http://www.chinapyg.com/thread-77929-1-1.html

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: