吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2684|回复: 2
收起左侧

[会员申请] 申请会员ID:imlk【申请通过】

[复制链接]
吾爱游客  发表于 2018-5-6 19:41
1. 申请ID:imlk
2. 个人邮箱:1789778853@qq.com

3. 原创技术文章:
笔记-ZjDroid适配art虚拟机的尝试
上星期趁着放假玩了玩ZjDroid,自己编译了一个来玩,最终克服万难总算找齐了源码,给编译出来了。
上一篇文章:
笔记-第一次ZjDroid脱壳实战
https://blog.csdn.net/u010746456/article/details/80150250
虽然,最终拿出来的大数字加固的dex没能恢复onCreate这个native方法(本人实在太菜),但是其他部分还是能看源码的。起因
最近一个朋友给我看了一个爱加密的包,我放到模拟器里面用我的ZjDroid脱,没想到这个包却主动退出了!
我以为是检测到了ZjDroid,就卸载了ZjDroid,结果还是崩,后来上网查才发现,爱加密检测到是模拟器环境就会主动退出。
这可让我费脑筋啊!
我手上只有Android7.1.2的设备,而目前的ZjDroid只支持dalvik虚拟机上跑,这可咋办呢,要我刷机?懒得备份。。。
我记得ZjDroid的源码最后是4年前更新的,然后作者就不维护了,于是我想能不能学习ZjDroid的原理去适配art呢?
打开as就开始捣鼓了!稍微尝试
尝试在Android7.1.2上面安装ZjDroid,重启,打开上次我拆的应用(就是那个我自己的应用啦)。
看log,除了几个碍眼的异常以外,没什么大状况出现,
嗯,
发送广播执行dump_dexinfo命令,然后一下子就崩了。
这个问题,我在上一篇文章里面就提到过了。
ZjDroid在执行dump_dexinfo命令的时候并没有用到native层的函数,只是通过反射获取dalvik.system.DexFile中的mCookie变量打印出来,但是发生了类型强制转换的错误,错误地把long[]类型转换为了int类型。
解决的办法是:
查阅Android源码,对这个openDexFileNative分sdk版本适配

  • dalvik(Android4.4及以前 sdk <= 19)中的openDexFileNative
    http://androidxref.com/4.4.4_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java#301

  • art (Android5.1.1及以前 19 < sdk <= 22)中的openDexFileNative
    http://androidxref.com/5.1.1_r6/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java#308

  • art (Android6.0至今(已测试7.1.2) 22 < sdk)中的openDexFileNative
    http://androidxref.com/7.1.2_r36/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java#396
art虚拟机的mCookie
但是还有一个问题就是,
我们要的mCookie究竟是什么样子的呢?
通过对ZjDroid的旧版本代码进行分析发现,在dalvik虚拟机中,mCookie实际上就是一个结构体的内存地址,通过这个结构体可以获得内存中dex文件的地址,然后就能dump出来了。
既然如此,在art里面的mCookie时一个long[]类型的,我们就很有必要去了解这个东西是怎么形成的了。
查看Android7.1.2的源码,找到openDexFileNative方法的native层实现:
http://androidxref.com/7.1.2_r36/xref/art/runtime/native/dalvik_system_DexFile.cc#156
有一些情况下是返回空指针的,我们就只看返回正常值的情况,在第184-194行,···184  if (!dex_files.empty()) {185    jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);186    if (array == nullptr) {187      ScopedObjectAccess soa(env);188      for (auto& dex_file : dex_files) {189        if (linker->FindDexCache(soa.Self(), *dex_file, true) != nullptr) {190          dex_file.release();191        }192      }193    }194    return array;···
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这个ConvertDexFilesToJavaArray函数应该是很重要的一个函数
看看它的实现77static jlongArray ConvertDexFilesToJavaArray(JNIEnv* env,78                                             const OatFile* oat_file,79                                             std::vector<std::unique_ptr<const DexFile>>& vec) {80  // Add one for the oat file.81  jlongArray long_array = env->NewLongArray(static_cast<jsize>(kDexFileIndexStart + vec.size())); //初始化一个long类型的Java数组82  if (env->ExceptionCheck() == JNI_TRUE) {//检查是否出现异常83    return nullptr;84  }8586  jboolean is_long_data_copied;87  jlong* long_data = env->GetLongArrayElements(long_array, &is_long_data_copied);//这里应该是获取刚刚生成的Java的long类型数组中元素的原始的指针,熟悉c语言的就知道,c中的数组是一块连续的内存结构,通过指针可以读取数组中的任意一个位置的元素88  if (env->ExceptionCheck() == JNI_TRUE) {//检查是否出现异常89    return nullptr;90  }91  // 这里的kOatFileIndex定义在了dalvik_system_DexFile.h文件中:值是0;    // http://androidxref.com/7.1.2_r36/xref/art/runtime/native/dalvik_system_DexFile.h#2592  long_data[kOatFileIndex] = reinterpret_cast<uintptr_t>(oat_file);//这里是c++的一种类型转换的方式,把参数转化为了uintptr_t类型,而uintptr_t类型是一种指针类型,就把它看作一个指针吧。93  for (size_t i = 0; i < vec.size(); ++i) {//可以看到,之前在数组中第一个位置放了oat_file的地址,然后接下来从kDexFileIndexStart(这个值是1,也在上面那个文件里定义了)开始,把vec数组里面的东西填之前生成的数组里。94    long_data[kDexFileIndexStart + i] = reinterpret_cast<uintptr_t>(vec.get());95  }9697  env->ReleaseLongArrayElements(long_array, long_data, 0);//刷新数组信息(比如长度等)98  if (env->ExceptionCheck() == JNI_TRUE) {//检查是否出现异常99    return nullptr;100  }101102  // Now release all the unique_ptrs.103  for (auto& dex_file : vec) {104    dex_file.release();105  }106107  return long_array;108}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

可以大概了解到,第一个位置被赋值为oat_file这个指针(实际上就是把指向的地址存到了第一个位置里),然后依次填充vec这个数组里的东西到之前的long数组里面,看看这个vec:
在参数列表里:std::vector<std::unique_ptr<const DexFile>>& vec
  • 1

不要慌,看起来很复杂,但其实不难理解:
vec是一个引用,引用的是一个vector(可变长数组)对象,这个对象里装的都是unique_ptr类型,这也是一种指针,可以看到这个东西指向的类型是DexFile,上面的那段代码应该就是把这些指针指向的地址信息填到long数组里面了
最终返回的long类型数组里面,应该全都是地址。art中的DexFile
再看看DexFile这个东西:

http://androidxref.com/7.1.2_r36/xref/art/runtime/dex_file.hclass DexFile {
  • 1

是一个class,里面还有结构体比如  struct Header {
  • 1

之类的,这好像和dex文件的结构有点关联了。
继续往下翻
在第1235行的地方:http://androidxref.com/7.1.2_r36/xref/art/runtime/dex_file.h#1235···1234  // The base address of the memory mapping.1235  const uint8_t* const begin_;12361237  // The size of the underlying memory allocation in bytes.1238  const size_t size_;12391240  // Typically the dex file name when available, alternatively some identifying string.1241  //1242  // The ClassLinker will use this to match DexFiles the boot class1243  // path to DexCache::GetLocation when loading from an image.1244  const std::string location_;12451246  const uint32_t location_checksum_;12471248  // Manages the underlying memory allocation.1249  std::unique_ptr<MemMap> mem_map_;12501251  // Points to the header section.1252  const Header* const header_;12531254  // Points to the base of the string identifier list.1255  const StringId* const string_ids_;12561257  // Points to the base of the type identifier list.1258  const TypeId* const type_ids_;···
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

这个begin_的描述,似乎是什么什么内存映射的基地址,下面还有这个块区域的大小size_,接下来是一些指针,Header,StringId,TypeId啥的,结合相关代码,我猜测这就是我们要找的dex文件的信息了。总结
总结一下:
Java层获取到的mCookie是一个long类型数组,里面都是地址,其中第一个地址是oat_file也就是oat过的文件的地址,当然现在大多数加固都不会允许虚拟机进行oat操作了,因为oat操作会在储存中生成优化过的oat文件,对于加固来说,无疑是自己把代码给出去了。这也是我们上一篇文章里,long数组第一个元素为0的原因:

解决方法:
https://github.com/KB5201314/ZjDroid/blob/master/app/src/main/java/com/android/reverse/collecter/DexFileInfoCollecter.java#L190
接下来
我们只需要第二个位置开始的内容,每一个元素都是一个地址,把地址传到native方法里,在native层里,这个地址指向的就是一个DexFile类,而因为类也类似于结构体,也有它的储存结构,只要找到begin_和size_的内容就能dump出内存中的dex(odex)文件。遇到的几个问题
关于内存对不到的问题:
我们知道C++和Java是有很大的区别的,在C++里面,一个变量,编译了以后,在运行时你是不能通过变量的名称来找到这个类的。因为这些变量都变成了地址或者偏移量。只代表某个内存区域。不能像Java那样通过反射动态获取。
所以,和结构体类似,想要获取一个C++对象的某个成员变量,你只能通过这个对象的地址+这个成员变量在这个对象中的相对位置来获取到,
前者我们容易得到,而后者,则需要构造一个和生成这个对象的class或者struct一模一样的class或者struct然后通过指针的形式取得其中的成员变量
看到这里可能就有人有疑问了:
  • 为什么我在原始的ZjDroid源码里面看到了和Android源码里面一样的结构体定义或者class定义?
  • 为什么是一模一样?

那是因为,只有一模一样,才能有一样的偏移量啊!
假设已经取得的DexFile的某个对象的地址adress(假设是long类型),想获得对象中的成员变量begin_的值,应该用以下的步骤DexFile *dexFile_ptr = (DexFile *)adress; // cast为DexFile类型的指针dexFile_ptr -> begin_; // 这样取得begin_的内容
  • 1
  • 2
  • 3
  • 4

对于第一个问题:设想如果你的代码里面没有DexFile的定义,怎么通过编译?编译器会告诉你找不到符号
实际上第二句:dexFile_ptr -> begin_;
  • 1

可以理解为:
(dexFile_ptr的地址 + begin_这个成员变量在对象里的相对位置)就是begin_的内容在内存中的位置
而这个相对位置,是编译时决定的,与class的结构有关,与编译器有关,与平台有关。关于C++对象的内存结构
以下是我个人所了解到的
  • C++中的类如果有虚函数存在,那么对象的内存结构中第一个位置应该是虚函数表的指针。https://www.linuxidc.com/Linux/2014-12/111047.htm
  • C++中的函数与类绑定,在对象中不占内存
  • C++中的static成员变量与类绑定,在对象中不占内存
挫折
发现自己编译出的so文件中std::string类型的大小和art虚拟机中的不一致,
https://github.com/KB5201314/ZjDroid/blob/master/app/src/main/jni/dvmnative/dexfile_art.h#L454
通过dump出这一块内存经过分析可知
内存结构对应关系为:

这里的std::string占了3 * 4 = 12个字节,而我编译出来的so里面,它是只占了4个字节的。
这导致,在那个string之后的内容都发生错位,也就是说,我编译出来的class,偏移量和art虚拟机里面的so文件里的不一样,这导致向ZjDroid发送backsmali命令无法使用
解决办法:
在一定范围内进行内存搜索:
因为begin_和head_ptr的值是一样的,我在之后的一定的内存区域内搜索begin_的值就能找到head_ptr的位置了
https://github.com/KB5201314/ZjDroid/blob/master/app/src/main/jni/dvmnative/dvmnative.cpp#L583出炉
源码:
https://github.com/KB5201314/ZjDroid
apk下载:
https://github.com/KB5201314/ZjDroid/releases遗留问题
由于原始版本的源码过于老旧,似乎只适配到Android sdk 17
在高版本上部分api发生改变,会引发异常
已知:
  • 应用敏感行为监控有部分功能不能使用,尤其是网络相关,比如新版Android删了apache的http库改用Okhttp,ZjDroid还未跟进。
  • ZjDroid的backsmali命令虽然获取dex信息部分(native层)已经搞定,但是ZjDroid所使用的org.jf.dexlib2等库是四年前的版本,不支持art,要改为新版的话,要做很多修改。

欢迎提交改进查看Android源码的网站
  • grepcode:支持查看Android5.1.1及以前的源码,支持文件比较
    http://www.grepcode.com/
  • androidxref:资源全,但文件比较功能没上面的那个好用
    http://androidxref.com/

参考
  • 解决爱加密加固之后使用xposed hook的时候log打印不出来的问题:
    https://bbs.pediy.com/thread-216965.htm
  • C++类对象的内存模型:
    https://www.linuxidc.com/Linux/2014-12/111047.htm
  • std::string源码探秘和性能分析:
    https://blog.csdn.net/ybxuwei/article/details/51326830
  • 修改安卓源码:Art模式下的通用脱壳方法:
    http://www.freebuf.com/articles/terminal/166307.html
  • GDB中打印ART基础类:
    http://www.cnblogs.com/YYPapa/p/6858787.html
  • 阿里早期Android加固代码的实现分析:
    http://www.voidcn.com/article/p-ntseiwvg-bqs.html
  • [原创]阿里早期加固代码还原4.4-6.0:
    https://bbs.pediy.com/thread-215078.htm
  • dex_file.h头文件
    http://androidxref.com/7.1.2_r36/xref/art/runtime/dex_file.h
  • dalvik_system_DexFile.h头文件
    http://androidxref.com/7.1.2_r36/xref/art/runtime/native/dalvik_system_DexFile.h

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Hmily 发表于 2018-5-7 10:23
I D:imlk
邮箱:1789778853@qq.com

申请通过,欢迎光临吾爱破解论坛,期待吾爱破解有你更加精彩,ID和密码自己通过邮件密码找回功能修改,请即时登陆并修改密码!
登陆后请在一周内在此帖报道,否则将删除ID信息。

ps:你这个是太乱了,登录后把帖子整理一下发到移动安全区。
imlk 发表于 2018-5-7 15:38 来自手机
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 15:33

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表