• 由于保护技术更迭迅速,不保证本文方法适用于后续或者其它版本的梆梆加固,需要读者自行测试。

梆梆加固后的apk,里面的classes.dex只是个外壳,负责加载libDexHelper.so,而真正的dex被加密放在了\assets\classes0.jar,这不是常规的jar文件无法直接解压,我们的目标就是从内存中dump出解密后的classes0.jar/.dex(并进行适当修复)。

内存dump的方案有很多, 比如直接dd、DexHunter等,但都有一些缺点,dd需要内存地址和大小,这就要求进行暴力搜索,但是内存完全可能被处理过,dump时机很难掌握,于是转到动态调试期望在关键点下断,但由于反调试对抗的存在又会有一堆麻烦;DexHunter很强大,但它要求定制系统,这对逆向工程来说当然不是事,只是同样有针对DexHunter类似原理的对抗,比如检测不安全ROM,等等这些都会带来不必要麻烦,本文提供的方法似乎直接跳过了这些坑。

其实,无论是DexHunter还是本文提供的方案,最关键的不过是取得代码执行先机,因为代码一但被安全的注入,在壳启动前执行我们的代码,我们就能完成很多有意思的事情了。怎么实现呢?我们知道libDexHelper.so是整个壳的核心,修改libDexHelper.so?万一有自校验咋办,这里就有了两种替换方案,第一种查看依赖较少的so,将该依赖的so换成我们的,比如liblog.so,但是这要求我们实现和liblog.so相应的接口,否则dlopen会失败,第二种,也是本文采用的方案,我们直接把它替换了不就好了嘛?

首先,把原来的libDexHelper.so改成libDexHelper2.so, 然后我们需要写一个libDexHelper.so,只需实现JNI_OnLoad即可, 在里面加载libDexHelper2.so并显式调用JNI_OnLoad即可(这里竟然没有检测文件名,呵呵,不过检测也没多大意义,容易bypass。另外由于我用的x86机,梆梆在.cache目录下生成对应的x86 so才是目标):

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *unused)
{#define SO "/data/data/blog.csdn.net.rrrfff/.cache/libDexHelper-x862.so"LOGI("Trying to load lib " SO " 0x0");void *hk = dlopen(SO, RTLD_GLOBAL | RTLD_NOW);LOGI("Added shared lib " SO " %p %s", hk, hk == NULL ? dlerror() : "");void *ld = dlsym(hk, __FUNCTION__);LOGI("JNI_OnLoad at %p", ld);reinterpret_cast<__func_type(JNI_OnLoad)>(ld)(jvm, unused);
}

adb push我们的so到相应目录,am start启动目标app,运行一切正常,代码被顺利注入,接着我们切入重点,把dex相关的api统统hook一遍看看是咋回事,这里我把dexFile*全家桶都给hook了,包括dvmDexFileOpenFromFd,dvmDexFileFree,dexFileParse,dvmDexChangeDex1,dvmDexChangeDex2。
        通过被hook方法的执行情况,我们大概总结下梆梆加固还原dex的大概流程(dalvik系统):

  1. open读取 \assets\classes0.jar 并解密出真正的jar,然后解压到.cache目录,但此时的classes0.dex仍旧是加密的,这步仅.cache目录下没有对应.dex时会发生。
  2. open读取 classes0.dex 并解密出真正的dex,其实是odex,看文件头可以知道。
  3. 通过dvmDexFileOpenFromFd读取dex文件,这里的fd就是上一步的fd,进行内存映射。
  4. 系统调用dexFileParse对映射好的dex内存进行解析并返回DexFile结构,这里是dump的好时机。
  5. 壳调用dvmDexChangeDex2对已解析好的dex内存区进行混淆,写入脏数据,所以我们在程序跑起来后再dump出来的东西是有噪声的。

知道了这些之后我们就能愉快的干活了,编写代码在dexFileParse时完成dump(据说梆梆把一堆read,write,mmap等libc函数hook了, 防止读取相关dex的区域, 这里绕了个弯,先用一般人不会用到的memmove备份内存,然后跳过dex头写到文件,其实似乎多此一举):

 static info<DvmDex *(*)(const uint8_t* data, size_t length, int flags)> *p0 = NULL;p0 = p0->hook(dlsym(dvm, "_Z12dexFileParsePKhji"),[](const uint8_t* data, size_t length, int flags)->DvmDex * {LOGI("dexFileParse hit with %p %u %d", data, length, flags);if (length > 100000) {int  dexv = ::open("/data/data/blog.csdn.net.rrrfff/cache/core.odex", O_CREAT | O_TRUNC | O_WRONLY);auto dexp = reinterpret_cast<uint8_t *>(::malloc(length));::memmove(dexp, data, length);::write(dexv, "bmp", 3);::write(dexv, dexp + 3, dexl - 3); // bypass odex header::free(dexp);::close(dexv);} //ifauto pDvmDex = p4->invoke()(data, length, flags);return pDvmDex;});

顺利得到core.odex,jeb可以正常打开,但是会出现一些异常,估计存在一些反反编译手段,我们用DexClassLoader加载试试(因为我们加载的是odex, 所以需要在同一目录下放一个dex, 这里我随便弄了个zip包, 系统发现里面没dex会加载同目录下的odex):

 DexClassLoader *dexloader = new DexClassLoader(env,"/data/data/blog.csdn.net.rrrfff/cache/core.dex","/data/data/blog.csdn.net.rrrfff/.cache","/tmp",DexClassLoader::getSystemLoader(env));auto cls  = dexloader->loadClass(env, "blog.csdn.net.rrrfff.TBJWelcomeActivity");auto cls2 = dexloader->loadClass(env, "blog.csdn.net.rrrfff.bean.UserInfoModel");LOGI("cls = %p %p", cls, cls2);

类加载成功, 证明dump出的odex格式良好(对ClassLoader而言), 为了试试能否正常运行, 这里懒得去进行重打包、签名这些琐碎的工作,我们直接hook dvmLookupClass让它返回找不到的类(去掉对原SO的加载后会报类找不到):

 static info<void *(*)(const char *descriptor, void *loader, bool unprepOkay)> *p0 = NULL;p0 = p0->hook(dlsym(dvm, "_Z14dvmLookupClassPKcP6Objectb"),[](const char *descriptor, void *loader, bool unprepOkay)->void * {LOGI("dvmLookupClass hit with %s %p %d", descriptor, loader, unprepOkay);static volatile int during_load[10240] = { 0 };if (during_load[gettid()] == 0 && strstr(descriptor, "Lblog/csdn/net/rrrfff") == descriptor ) {char sdescriptor[128];int len = strlen(descriptor);::memcpy(sdescriptor, descriptor + 1, len - 2);sdescriptor[len -= 2] = 0;while (--len >= 0) {if (sdescriptor[len] == '/') sdescriptor[len] = '.';}JNIEnv *env;g_jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);LOGI("loading %s", sdescriptor);during_load[gettid()] = 1;auto cls = dexloader->loadClass(env, sdescriptor);during_load[gettid()] = 0;LOGI("%s loaded at %p", sdescriptor, cls);return decode_obj(get_self(), cls);} //ifvoid *ret = p0->invoke()(descriptor, loader, unprepOkay);return ret;});

看日志可以发现很多类被成功加载了,但这里会报一个native方法找不到, com/secneo/apkwrapper/Helper的attach方法, 签名(Landroid/app/Application;Landroid/content/Context;)V, 看参数有Application和Context,应该是壳完成资源修复等工作的切入点,后续再研究,我们给它注册个:

 void(*attach)(JNIEnv *env, jclass, jobject, jobject) = [](JNIEnv *env, jclass, jobject, jobject) {DEBUG_LINE_HIT;};JNINativeMethod gMethods[] = {{ "attach", "(Landroid/app/Application;Landroid/content/Context;)V", reinterpret_cast<void *>(attach) }};env->RegisterNatives(env->FindClass("com/secneo/apkwrapper/Helper"), gMethods, 1);

最后在 android.content.ContextWrapper.getResources(ContextWrapper.java:89) 处出现 java.lang.NullPointerException 异常, 可能是资源或者那个地方需要修复, 这里就没去深究了, 因为从jeb我们已经得到了想要的结果。

dalvik下替换so简单dump出梆梆加固保护的odex相关推荐

  1. 一花独放不是春 梆梆安全呼吁构建物联网安全共同体

    [51CTO.com原创稿件]就在上周,为期三天的2017年北京国际互联网科技博览会暨世界网络安全大会在北京展览馆拉开帷幕.会议召开的第二天,记者参加了由梆梆安全主办的物联网安全论坛.在整整一下午的时 ...

  2. linux下git的简单运用

    linux下git的简单运用 windows下也有git,是git公司出的bash,基本上模拟了linux下命令行.许多常用的命令和linux下操作一样.也就是说,windows下的git命令操作和l ...

  3. 梆梆加固之防内存dump分析

    声明 本文仅限于技术讨论,不得用于非法途径,后果自负. 参考资料 https://bbs.pediy.com/thread-206293.htm https://github.com/parkerpe ...

  4. android内存dump分析,[原创]梆梆加固之防内存dump分析

     声明(打字好累) 本文仅限于技术讨论,不得用于非法途径,后果自负.  参考资料https://bbs.pediy.com/thread-206293.htm https://github.com ...

  5. 【Android 逆向】Dalvik 函数抽取加壳 ( Dalvik 下的函数指令抽取与恢复 | dex 函数指令恢复时机点 | 类加载流程 : 加载、链接、初始化 )

    文章目录 前言 一.Dalvik 下的函数指令抽取与恢复 二.dex 函数指令恢复时机点 1.dex 函数指令恢复 2.Android 源码中搜索 dexFindClass 函数 3.类加载流程 : ...

  6. 从java进程里dump出类的class文件的小工具--dumpclass

    Serviceability Agent 想要查看一些被增强过的类的字节码,或者一些AOP框架的生成类,就需要dump出运行时的java进程里的字节码. 从运行的java进程里dump出运行中的类的c ...

  7. Bootstrap—解决下拉菜单不弹出问题

    最近学到Bootstrap下拉菜单,学懂了教程内容之后自己敲一个点击按钮底下弹出下拉菜单的小demo,写完代码发现运行之后点击按钮没反应,下拉菜单弹不出来,对照教程感觉代码没错. 我的代码如下: &l ...

  8. dump出Linux内核所有的slab对象缉拿内核Rootkit

    前面的文章介绍了很多种隐藏进程,隐藏TCP连接,隐藏内核模块的方法,总结起来和大多数网上介绍Rootkit的文章种介绍的方法不同点在于: 网上大多数文章均是hook procfs来达到隐藏对象的目的. ...

  9. linux 6.4简单使用mysql,Linux系统下Mysql使用简单教程(一)

    如果你会查询这些相关的问题,说明你是一个正在或者准备从事IT的程序猿,对于一个程序猿而言,不会使用linux系统的程序猿不是一好的程序猿哦!因为windows有时候真的让人很抓狂,而本人也相信没有什么 ...

最新文章

  1. .net反射详解 原文://http://blog.csdn.net/wenyan07/article/details/27882363
  2. 对Struts2的认识(-)
  3. 安装mysql 5.6.24给linux,Red Hat Enterprise Linux 5 64位安装Mysql5.6.24(DB5.6.24.rpm for rhel5 x86)...
  4. 【校招面试 之 C/C++】第12题 C++ 重载、重写和重定义
  5. xdoj判断堆栈出栈序列是否有效c++
  6. oracle索引分类与区分,深入理解Oracle表(6):堆组织表(HOT)和索引组织表(IOT)的区别...
  7. Oracle的锁表与解锁
  8. matlab 局部特征检测与提取(问题与特征)
  9. Java爬虫Crawler
  10. for/map循环里面进行异步操作async/await后返回数据,for里不能直接return执行方法函数...
  11. Python中终端彩色打印输出
  12. 如何在服务器里面新建文件夹,怎么在云服务器新建文件夹
  13. 哇哦,它让美味随心所“鲜”
  14. 旅游推荐系统python_Python 实现推荐系统
  15. 独立正交不相关定义关系
  16. 数据结构-----最长回文子串
  17. LeetCode #378 JavaScript
  18. vue中如何返回历史路由_如何配置局域网中的多台无线路由器【图文教程】
  19. 使用阿里百川云旺实现即时通讯
  20. P、V操作与c++代码实现爸爸放苹果,妈妈放桔子,两个儿子专吃盘子中桔子,两个女儿专吃盘子中苹果问题

热门文章

  1. 渐变背景(background)效果
  2. 常见字读音(粤语)---(5)
  3. 华为帐号登录游戏显示无法连接服务器,玩手机游戏提示无法连接服务器
  4. ArcGIS矢量化——半自动矢量化操作
  5. “七段数码管绘制”实例详解
  6. 《机器学习实战》萌新读书笔记 ② — — 第三章 决策树 内容提要、知识拓展和详细注释代码
  7. 希尔排序的时间复杂度为什么能小于O(n^2)
  8. AboutSpace
  9. 微信小程序云开发使用mysql数据库
  10. CodeForces 596B Wilbur and Array