安卓刷量技术揭秘(二) 高级攻防技巧
接下来讲一下APP的高级攻防技巧。
估计厂商也认识到了APP在JAVA环境下运行收集到的信息被篡改的几率很大,所以很多厂商把目光标准了JNI层。
JNI层的运算是独立于JAVA层以外的、更底层的运算,也就是说,你用XPOSED安装了一个JAVA层的钩子修改了机器的重要信息A,但从JNI层读取信息A的时候,并不会触发你的钩子,所以返回值是系统真实值。
那么JNI层的数据如何修改呢?如何进入JNI层?进入JNI层后如何使用钩子?
这里我提供两个思路 两个思路都有完整的JAVA+JNI层的HOOK功能和框架 而且两种方案都有自己的优缺点,靠你自己取舍。
1 Cydia Substrate
具体可以参考
http://www.cydiasubstrate.com/
http://www.cydiasubstrate.com/inject/dalvik/ (java层支持)
http://www.cydiasubstrate.com/inject/android/ (JNI/Native层支持)
cydia框架的优势很明显,同时支持jni和java层的HOOK功能,缺点也很明显,兼容性不好,官网上标注最高支持安卓4.3版,但实际中测试发现一些第三方的ROM会出现一些莫名其妙的问题,而据说有人用安卓原生5.0也可以完美运行。所以是否使用CYDIA框架,要根据你目前的硬件情况来选择,如果你的手机ROM可以运行CYDIA,我更推荐使用这种完整的集成方案
所以是否使用CYDIA框架,要根据你目前的硬件情况来选择,如果你的手机ROM可以运行CYDIA,我更推荐使用这种同时支持两个环境完整的集成方案。
使用cydia开发 搭建框架我这里不多重复了,可以登录官网查看文档。
根据cydia和xposed的框架原理来看,cydia的开发成本会更高一些。
在你第一次使用这个框架时,编译后会出现很多莫名其妙的问题,如果你之前在c/c++语言上有过比较深的基础并且做过hook的相关项目,这些莫名其妙的问题解决起来会很轻松.
其中有三个点需要注意:
① cydia中的所有变量都是全局变量,例如,你在App A中个修改了nTest,那么会同步到其他App进程所有对Main.nTest的引用上。
② cydia中因为无法直接获得当前App的信息,你需要使用一个聪明的方式获得你想要东西作为你是否要HOOK目标进程的判断依据。
③ cydia引入了hookClassLoad方法,这个和xposed中的设计理念是不同的,详细资料看文档。
例如你可以用cydia hook捕获到Activity的OnCreate创建过程
也可以使用cydia jni hook拦截getprop的返回值
上面的例子HOOK了native层的__system_property_read方法,重定向到new__system_property_read。该方法是执行命令getprop的关键过程,配置好代码可以修改getprop的所有返回值
2 Xposed+Libc_hook.so
下面说第二种方案,如果你的安卓系统不能安装cydia 但同时又需要jni层的HOOK 那么我推荐你使用下面这套方案,实现成本相对第一种要复杂一些,但是跟着我所写的步骤,一定能得到你所要的功能。
在不能安装cydia的情况下如何进入jni层HOOK呢?很简单,找到关键so库文件,以病毒修改elf的方式,加载自己的注入代码,把自己的hook.so注入到该进程中即可
首先找到一个注入点,这里我推荐libc.so 因为基本上每个进程都要用到libc,更重要的是 libc的加载时机足够早。
① abd pull /system/lib/libc.so 先把手机上的libc.so到本地 然后使用ida打开
② 打开Function Window窗口找到dlopen函数的地址 0xEAD0
③ 打开Program Segmentation窗口找到.init_array的地址 0x57480
④ 按ALT+B搜索80个0 此地址随意 本例中找到地址为 0x51F00
⑤ 按照上面的三个数字 对应带入下面的代码中 使用vs2015 c#编译通过
⑥ 运行时 把libc.so放在exe同目录下可得到libc_ok.so
使用上面的程序生成的libc_ok.so覆盖回/system/lib/libc.so即可
以后每当程序运行,都会自动加载/system/lib/libckis.so库文件 可以尽情的HOOK了!
上面着重讲解了cydia框架的使用以及手动注入jni层的方法,这种方法从原理上,可以100%的过目前的所有的检测,这是单一xposed框架做不到的。
估计厂商也认识到了APP在JAVA环境下运行收集到的信息被篡改的几率很大,所以很多厂商把目光标准了JNI层。
JNI层的运算是独立于JAVA层以外的、更底层的运算,也就是说,你用XPOSED安装了一个JAVA层的钩子修改了机器的重要信息A,但从JNI层读取信息A的时候,并不会触发你的钩子,所以返回值是系统真实值。
那么JNI层的数据如何修改呢?如何进入JNI层?进入JNI层后如何使用钩子?
这里我提供两个思路 两个思路都有完整的JAVA+JNI层的HOOK功能和框架 而且两种方案都有自己的优缺点,靠你自己取舍。
1 Cydia Substrate
具体可以参考
http://www.cydiasubstrate.com/
http://www.cydiasubstrate.com/inject/dalvik/ (java层支持)
http://www.cydiasubstrate.com/inject/android/ (JNI/Native层支持)
cydia框架的优势很明显,同时支持jni和java层的HOOK功能,缺点也很明显,兼容性不好,官网上标注最高支持安卓4.3版,但实际中测试发现一些第三方的ROM会出现一些莫名其妙的问题,而据说有人用安卓原生5.0也可以完美运行。所以是否使用CYDIA框架,要根据你目前的硬件情况来选择,如果你的手机ROM可以运行CYDIA,我更推荐使用这种完整的集成方案
所以是否使用CYDIA框架,要根据你目前的硬件情况来选择,如果你的手机ROM可以运行CYDIA,我更推荐使用这种同时支持两个环境完整的集成方案。
使用cydia开发 搭建框架我这里不多重复了,可以登录官网查看文档。
代码:
public class Main { public static int nTest=0; static void initialize() { //函数入口 //do something........ } }
在你第一次使用这个框架时,编译后会出现很多莫名其妙的问题,如果你之前在c/c++语言上有过比较深的基础并且做过hook的相关项目,这些莫名其妙的问题解决起来会很轻松.
其中有三个点需要注意:
① cydia中的所有变量都是全局变量,例如,你在App A中个修改了nTest,那么会同步到其他App进程所有对Main.nTest的引用上。
② cydia中因为无法直接获得当前App的信息,你需要使用一个聪明的方式获得你想要东西作为你是否要HOOK目标进程的判断依据。
③ cydia引入了hookClassLoad方法,这个和xposed中的设计理念是不同的,详细资料看文档。
例如你可以用cydia hook捕获到Activity的OnCreate创建过程
代码:
Class<?> resources = android.app.Activity.class; Method method_name=HTool.GetDeclaredMethodFromClass(resources,"onCreate",Bundle.class); if (method_name != null) { final MS.MethodPointer old = new MS.MethodPointer(); MS.hookMethod(resources, method_name, new MS.MethodHook() { @Override public Object invoked(Object obj, Object... args) throws Throwable { Object result_old = old.invoke(obj, args); Log.i(CreateTag,"onCreate PackName:"+PackName); return result_old; } }, old); }
代码:
int (*old__system_property_read)(int pi, char *name, char *value); int new__system_property_read(int pi, char *name, char *value) { int result=old__system_property_read(pi,name,value); return result; } //程序入口 MSInitialize { MSImageRef image = MSGetImageByName("/system/lib/libc.so"); if (image != NULL) { void *hook__system_property_read=MSFindSymbol(image,"__system_property_read"); if(hook__system_property_read) MSHookFunction(hook__system_property_read,(void*)&new__system_property_read,(void **)&old__system_property_read); else LOGE("error find __system_property_read "); } }
2 Xposed+Libc_hook.so
下面说第二种方案,如果你的安卓系统不能安装cydia 但同时又需要jni层的HOOK 那么我推荐你使用下面这套方案,实现成本相对第一种要复杂一些,但是跟着我所写的步骤,一定能得到你所要的功能。
在不能安装cydia的情况下如何进入jni层HOOK呢?很简单,找到关键so库文件,以病毒修改elf的方式,加载自己的注入代码,把自己的hook.so注入到该进程中即可
首先找到一个注入点,这里我推荐libc.so 因为基本上每个进程都要用到libc,更重要的是 libc的加载时机足够早。
① abd pull /system/lib/libc.so 先把手机上的libc.so到本地 然后使用ida打开
② 打开Function Window窗口找到dlopen函数的地址 0xEAD0
③ 打开Program Segmentation窗口找到.init_array的地址 0x57480
④ 按ALT+B搜索80个0 此地址随意 本例中找到地址为 0x51F00
⑤ 按照上面的三个数字 对应带入下面的代码中 使用vs2015 c#编译通过
⑥ 运行时 把libc.so放在exe同目录下可得到libc_ok.so
使用上面的程序生成的libc_ok.so覆盖回/system/lib/libc.so即可
以后每当程序运行,都会自动加载/system/lib/libckis.so库文件 可以尽情的HOOK了!
代码:
class Program { static int dlopenAddr = 0xEAD0; //opendl地址 static int InitArrayAddr = 0x57480-0x1000; //此处IDA的地址总是比UE中的多0x1000 static int asmAddr = 0x51F00; //代码插入位置 static byte[] asm = { 0xFF,0x40,0x2D,0xE9,0x18,0x00,0x9F,0xE5,0x00,0x10,0xA0,0xE3,0x00,0x00,0x8F,0xE0, 0x01,0xF7,0xFF,0xEB,0xFF,0x40,0xBD,0xE8,0x08,0x00,0x9F,0xE5,0x00,0x00,0x8F,0xE0, 0x10,0xFF,0x2F,0xE1,0x9C,0x68,0x03,0x00,0x09,0xBF,0x01,0x00,0x00,0x00,0x00,0x00}; static int StrAddr = asmAddr + 0x40; static void Main(string[] args) { BinaryReader br = null; try { br = new BinaryReader(new FileStream("libc.so", FileMode.Open)); } catch (IOException e) { Console.WriteLine(e.Message + "Cannot open file."); return; } int length = Convert.ToInt32(br.BaseStream.Length); byte[] data = br.ReadBytes(length); br.Close(); byte[] LibCkisSo = System.Text.Encoding.ASCII.GetBytes("libckis.so"); Array.ConstrainedCopy(LibCkisSo, 0, data, StrAddr, LibCkisSo.Length); int JmpReturnAddr = System.BitConverter.ToInt32(data, InitArrayAddr); byte[] byAsmAddr = System.BitConverter.GetBytes(asmAddr); Array.ConstrainedCopy(byAsmAddr, 0, data, InitArrayAddr, byAsmAddr.Length); byte[] JmpOffset = System.BitConverter.GetBytes(JmpReturnAddr - asmAddr - 0x24); Array.ConstrainedCopy(JmpOffset, 0, asm, 0x28, JmpOffset.Length); byte[] StrOffset = System.BitConverter.GetBytes(StrAddr - asmAddr - 0x14); Array.ConstrainedCopy(StrOffset, 0, asm, 0x24, StrOffset.Length); byte[] DlopenOffset = System.BitConverter.GetBytes((dlopenAddr - asmAddr - 0x18) / 4); Array.ConstrainedCopy(DlopenOffset, 0, asm, 0x10, StrOffset.Length - 1); Array.ConstrainedCopy(asm, 0, data, asmAddr, asm.Length); BinaryWriter bw = new BinaryWriter(new FileStream("libc_ok.so", FileMode.Create)); bw.Write(data); bw.Close(); } }