安卓刷量技术揭秘(二) 高级攻防技巧

接下来讲一下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开发 搭建框架我这里不多重复了,可以登录官网查看文档。
代码:
public class Main
{
  public static int nTest=0;
  static void initialize()
  {
    //函数入口
    //do something........
  }
}
根据cydia和xposed的框架原理来看,cydia的开发成本会更高一些。
在你第一次使用这个框架时,编译后会出现很多莫名其妙的问题,如果你之前在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);
}
也可以使用cydia jni hook拦截getprop的返回值

代码:
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 ");
  }
}
上面的例子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了!

代码:
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();
  }
}
上面着重讲解了cydia框架的使用以及手动注入jni层的方法,这种方法从原理上,可以100%的过目前的所有的检测,这是单一xposed框架做不到的。