脱壳成长之路之二代壳进阶:绕过反调试、函数体回填并修复onCreate
2022-12-18 18:2:12 Author: 看雪学苑(查看原文) 阅读量:11 收藏


看雪论坛作者ID:GitRoy

样本:很老的x度加固

分析工具:ida、jeb、010、apktool

NativeHook:基于Substrate的Nativehook

Android系统:4.4

本次脱壳直接从整体看,不过为了碰一下反调试,还是先进入了JNIONLOAD层。进入JNIONLOAD:
首先看到Java层有第一个反调试。
我直接采用hook方法去过掉这个校验:
int new_debug() {     return 0;}
          if (old_debug == NULL) {                elfHook("libdvm.so", "_Z25dvmDbgIsDebuggerConnectedv", (void *) new_debug,                        (void **) &old_debug);            }
然后我又增加了一些常用的过反调试检测的手段,比如下面这段等等.....
char *new_fgets(char *buf, int bufsize, FILE *stream) {    char *trace = "TracerPid:\t1";    if (strstr(buf, trace) != NULL) {        strcpy(buf, "TracerPid:\t0");    }    return old_fgets(buf, bufsize, stream);}
继续下一步, 使用NDK hook 卡死Jnionload函数
jint new_JNI_OnLoad(JavaVM *vm, void *reserved) {    log("%s", "jni on load");    while (!debug){     }    log("%s", "in jni onload");    debug = true;     return old_JNI_OnLoad(vm, reserved);}
然后启动App,并尝试用IDA附加,发现有下面两个进程。

使用ps命令,查看哪个进程是受精卵孵化的。

然后附加3622进程,报错:

我们搞老壳有个好处就是,网上能参考的资料比较多。通过一番查找,发现这个错误很可能是孵化进程后,再ptrace自己,然后造成我们不能附加。
那么我解决的手段很暴力,直接干掉子进程,然后在附加。幸运的,成功了。
附加成功后,修改寄存器中debug数值,强制跳转到app原jnionload函数,这里一定要注意T指令和A指令转换。
然后成功进入JNIONLOAD:之后就可以分析了。下面一篇不会继续接着JNIONLOAD分析,而是找一个更好的办法去解决,样本在最后给出。
上面主要分析了,如何直接进入JNIONLOAD,这篇我们继续分析。

修复dexdump后的文件

首先按照国际惯例,直接hook dvmDexFileOpenPartial 然后dump出dex,拖到010中查看:可以看到,到这个annotations_off就卡住了,解析不了。
我自己的思路,我要先确定壳没有去对其他关键函数做手脚。然后我就去找了一个,加载dex一定会用到的函数dexFindClass函数。
   if (old_findDex == NULL) {        elf_hook("libdvm.so", "_Z12dexFindClassPK7DexFilePKc", (void *) &new_dexFindClass,                 (void **) &old_findDex);    }
void *new_dexFindClass(const void *pDexFile, const char *descriptor) {}
我还特意打印了descriptor,确定了DexFile。然后我就根据pDexFile,将整个dexdump出来:
        char *file_name = "/data/data/com.qsq.qianshengqian/1.dex";        FILE *dex_file = fopen(file_name, "wb+");        fwrite(dexFile->pHeader, dexFile->pHeader->fileSize, 1, dex_file);        fclose(dex_file);
最终发现,两次dump出的dex是一样的,这就说明,系统确实也是用了这个dexFile去加载了。
好的 那么我们回到上面的图片继续分析,可以看到直接卡在了annotation_off就分析不下去了,这里我卡了一段时间,起初我以为是壳做了什么操作,还是分析了so。
后来发现并没有,可能是因为有些注解是动态的,所以导致无法解析dex文件,所以我做了一些操作为了能在010中更好的观察,我直接将注解偏移改为0。
u4 *off = &(pDef->annotationsOff);<br>*off = 0;
好了,现在好看很多。我们继续看010,发现class_data_off出现了超大偏移(实际上是负偏移)通过hook,打印class_data_off看看:果然很多负数偏移。
这种情况,我的思路,先从内存中dump class_data,dump到一个新文件中,然后原文件重新修复class_data_off地址,最后再拼接两个文件,直接上代码。
void *new_dexFindClass(const void *pDexFile, const char *descriptor) {    DexFile *dexFile = (DexFile *) pDexFile;      if (dexFile->pHeader->fileSize == 5573376 && a == 0 ) {        log("%s","start dump");        a = 1;        size_t size = dexFile->pHeader->fileSize;        DexClassDef *pDef = dexFile->pClassDefs;//        log("class def size is %d", dexFile->pHeader->classDefsSize);        int classDefSize = dexFile->pHeader->classDefsSize;        log("%d",classDefSize);        //2.dex保留classdef。        FILE *pFile = fopen("/data/data/com.qsq.qianshengqian/2.dex", "wb+");        u4 currentLength =dexFile->pHeader->fileSize;        for (int i = 0; i < classDefSize; ++i) {            if (pDef->classDataOff != 0) {                 const u1 *start = dexGetClassData(dexFile, pDef);                DexClassData *pData = ReadClassData(&start);                 int len = 0;                u4 f_off = currentLength + len;                uint8_t *string = EncodeClassData(pData, len);                fwrite(string, 1, len, pFile);                currentLength = currentLength + len;                //重置偏移,                if (i<4495) {                    u4 *def_off = &(pDef->classDataOff);                    *def_off = f_off;                }            }            //重置annotation            u4 *off = &(pDef->annotationsOff);            *off = 0;            pDef++;        }        log("%s","write compete");        fclose(pFile);        char *file_name = "/data/data/com.qsq.qianshengqian/1.dex";        FILE *dex_file = fopen(file_name, "wb+");        fwrite(dexFile->pHeader, dexFile->pHeader->fileSize, 1, dex_file);        fclose(dex_file);     }      return old_findDex(pDexFile, descriptor);}
注:上面dexGetClassData和readClassdata参考了dexhunter的代码。具体代码会在最后面打包给出。
最终会dump出两个文件,然后我们把两个文件拼接到一起,别忘记baksmali/smali一下,因为我拼接的文件并不是一个标准的dex文件,然后拖入jeb。
然后发现看得到代码了。但是重新打包崩溃了。大概的意思是onCreate中没有调用super.onCreate()。
结果仔细查看smali发现,onCreate中调用了onCreate001,但是onCreate001中都是nop。
OK,dex的第一步修复就到此为止,下一篇我们继续分析onCreate001。
修复onCreate001(https://bbs.pediy.com/thread-257510.htm)
上面我们已经初步修复了dex,然后这一篇也是最后一篇,把onCreate001修复。

修复onCreate001

先看Java层源代码:A.d和A.e 都是native函数,看到这里相信大家都已经有思路了,A.d肯定一定有对DexFile进行修复,把onCreate001的code_item变成正常的。A.e呢?经过一番测试(我分别dump调用A.e和不调用A.e进行对比),发现A.e是对修复的DexFile还原回错误的,为了防止我们直接dump修复后的dex。
那么这个时候,我个人思路,先防止它修复后还原,然后再指定时机进行Dump。注意这里指定时机,就是说一定要等到原程序调用A.d这个native函数之后,再进行dexFile的dump。
先通过nativehook,拦截A,e函数,下面直接上代码:
jint new_JNI_OnLoad(JavaVM *vm, void *reserved) {     vm->GetEnv((void **) &jniEnv, JNI_VERSION_1_4);    //0x35c为RegisterNatives偏移地址    elf_hook_Direct((void *) *(unsigned int *) ((*(unsigned int *) jniEnv) + 0x35C),                    (void *) &myRegisterNatives, (void **) &oldRegisterNatives);    return old_JNI_OnLoad(vm, reserved);}
//修复函数void new_d(JNIEnv *env, jclass c, jstring js) {    log("%s",env->GetStringUTFChars(js, false));    return old_d(env,c,js);} void (*old_e)(JNIEnv *env, jclass c,jstring js); //还原函数void new_e(JNIEnv *env, jclass c, jstring js) {    //这里直接拦截它恢复//    log("%s", "run new e");//    log("%s",env->GetStringUTFChars(js, false));//    return old_e(env,c,js);}  void on_native_reg(JNIEnv *env, jclass c, const JNINativeMethod *m, jint n) {     for (int i = 0; i < n; i++) {         if (strcmp(m[i].name, "d") == 0) {            elf_hook_Direct(m[i].fnPtr, (void *) &new_d, (void **) &old_d);        }         if (strcmp(m[i].name, "e") == 0) {            elf_hook_Direct(m[i].fnPtr, (void *) &new_e, (void **) &old_e);        }     } }
好的上面代码已经拦截了还原函数,那么下面我们就确认时机点了,我选择了Lcom/qsq/qianshengqian/LoginActivity这个时机点,确保SplashActivity已经onCreate了,然后再次dump DexFile,上代码。
void *new_dexFindClass(const void *pDexFile, const char *descriptor) {    DexFile *dexFile = (DexFile *) pDexFile;    char *aim = "Lcom/qsq/qianshengqian/LoginActivity;";    int cmp = strcmp(descriptor, aim);     if (dexFile->pHeader->fileSize == 5573376 && a == 0 && cmp) {        log("%s","start dump");        a = 1;        size_t size = dexFile->pHeader->fileSize;        DexClassDef *pDef = dexFile->pClassDefs;//        log("class def size is %d", dexFile->pHeader->classDefsSize);        int classDefSize = dexFile->pHeader->classDefsSize;        log("%d",classDefSize);        //2.dex保留classdef。        FILE *pFile = fopen("/data/data/com.qsq.qianshengqian/2.dex", "wb+");        u4 currentLength =dexFile->pHeader->fileSize;        for (int i = 0; i < classDefSize; ++i) {            if (pDef->classDataOff != 0) {                 const u1 *start = dexGetClassData(dexFile, pDef);                DexClassData *pData = ReadClassData(&start);                 int len = 0;                u4 f_off = currentLength + len;                uint8_t *string = EncodeClassData(pData, len);                fwrite(string, 1, len, pFile);                currentLength = currentLength + len;                //重置偏移,                if (i<4495) {                    u4 *def_off = &(pDef->classDataOff);                    *def_off = f_off;                }            }            //重置annotation            u4 *off = &(pDef->annotationsOff);            *off = 0;            pDef++;        }        log("%s","write compete");        fclose(pFile);        char *file_name = "/data/data/com.qsq.qianshengqian/1.dex";        FILE *dex_file = fopen(file_name, "wb+");        fwrite(dexFile->pHeader, dexFile->pHeader->fileSize, 1, dex_file);        fclose(dex_file);     }      return old_findDex(pDexFile, descriptor);}
同样我们这次dump好两个文件, 一个是修后后的dex,我们后面叫Fix_1.dex,另一个是修复后的class_data段,我们叫Fix_2.dex。然后再次拼接,上010看图。
Dump出来的文件有如下问题:
1、header被处理了,膜(摩)数都没了。上面那个我自己加上去的;
2、是一个相当不规则的文件,以至于baksmali都没办法转换。
不过我们可以先看看SplashActivity的CodeItem是不是修复了:
可以看到确实不一样了,那么下面我们要做的,就是把原dex进行修复(因为新dump出的dex太不规则了)。
这里我们先列一下,以免合并的时候懵逼了。
原dex:1.dex  这个是经过第一次修复后,onCreate001出问题的
原class_data段:2.dex 这个是第一次dump出的class_data段
修复后的dex::Fix_1.dex
修复后的class_data段:Fix_2.dex

然后我们就用1.dex与Fix_2.dex组合成一个新的dex,再将Fix_1.dex中的code_item覆盖到1.dex的code_item。可以看到已经修复成功了,到此为止这个样本完成了,样本以及代码资源奉上(原文附件中)。

[2023春季班]《看雪安卓高级研修班(网课)》招生!


《安卓高级研修班(网课)》

火热招生中!

1、课程内容

为了更加针对性、更高效地培养安全人才,为企业的发展和壮大赋能,提高企业在招聘活动中及人才在求职过程中的对接效率,结合看雪自身在安全圈的深厚技术积累沉淀,看雪正式针对《安卓高级研修班》学员推出《看雪安卓应用安全能力认证》。

2w班
3w班
2、服务对象

一定基础的初、中级安卓逆向研究员,迫切希望提高自身能力、学习能力强,升职加薪意愿强烈、学习意愿强烈。

3、服务内容
  • 上述列出的两大计划、各八大专题及其包含的二十四个细目;
  • 专属班主任,敦促学习、鼓励士气;良好的抱团学习的氛围;

  • 可以参加《安卓高级研修班》线下班,鼓励线下交流,与大佬谈笑风生;

  • 注意2W班和3W班是完全独立噢,没有交集;

4、开班时间

开班时间:2022年6月开班

PS:以上为总体服务计划,具体课程时间(段)安排以最终合同约定的课程表为准。

5、培训价格

高研网课

就业班

强化班

月薪三万计划

16999元

8599元

月薪两万计划

11199元

5599元

就业班注意事项:
  1. 就业班附带包就业服务(须达到合同规定的毕业标准),签合同保证就业及薪资,达不到退全款;

  2. 就业班有入学考核,缴费成功后进入考核流程,考核不通过退全款;

  3. 考核流程会包括简历筛选、班主任和老师(电话)面试等环节;

强化班注意事项:
  1. 强化班仅去除包就业服务,并且无入学考核,其余与就业班完全相同;

  2. 就业班与强化班一起授课,合计35人一个班,教学上不做任何区分。

金融风险注意事项:
  1. 《安卓高级研修班》全系列无任何金融计划,纯预付;无任何金融套路。

  2. 网络课程为虚拟商品,购买之前可以观看下述试看内容,购买成功之后不接受退款。

6、报名地址

网课月薪三万计划:

https://www.kanxue.com/book-brief-84.htm

扫码立即报名!
网课月薪两万链接:
https://www.kanxue.com/book-brief-83.htm

扫码立即报名!

试看地址:

3W:《ida trace分析非标准算法》

3W:《Fart&frida》

扫码免费试看

2W:《Fart中的脱壳点》
2W:《Dalvik下动态注册原理追踪 》

扫码免费试看

7、联系我们

课程顾问微信:r0ysue(备注“安卓高研网课”)

 
渴望知识和力量的你还在等什么,赶紧报名加入我们吧! 

免责条款

  • 以上所有宣传稿件内容均不作为服务承诺,最终以实际签订培训合同为准。

  • 课程大纲与细目会根据教学反馈不断优化、调整与更新,实际授课可能与宣传主题略有不同;

常见Q&A及预习指南

Q:有优惠么?!有优惠么?!有优惠么?!重要的事情说三遍!!

A:没有任何优惠噢。只送开学大礼包,把我们网课中需要准备的设备和环境直接送给大家。
3W班高研网课开学大礼包:
  • 一部pixel手机(sailfish)(安卓8脱壳镜像)

  • 安卓源码开发编译调试环境SSD移动硬盘500G

2W计划的话大礼包中的手机或移动硬盘二选一。
Q:网课内容与线下班内容一样么?

A:月薪三万计划的内容与线下班的内容是一样的,我们在线下班沉淀大家的切实的需求和疑问,重新编排和制作内容作为网课与大家分享。月薪两万计划的内容由三万计划的讲师全新制作,充分体现工作场景一线的需求,更加贴近实战、实用,有用、好用。

Q:网课内容与线下班内容一样么?

A:目前针对ollvm和vmp,任何所谓的自动化,都是带很多前提和条件限制的;目前最快的还原ollvm或vmp的方法,还是手动分析,一般快则两三日、慢则一两周,基本上可以还原出来。

ollvm或vmp虽然非常复杂,但是并不代表没有取巧和判断的方法;依托于我们丰富的经验,我们会在课上将我们调试和分析的普适方法和一般性及特殊性技巧教给大家,同时带领大家开发属于自己的ollvm和vmp虚拟机,让学员既能够自己给自己的程序加密,又能够分析别人的经过ollvm或vmp保护的算法,这才是我们看雪高级研修班所传达的授人以渔的精神。
Q:非常关心ollvm和vmp,可以详细介绍下还原的方法和细节么?

A:月薪两万计划推荐至少有实际安卓安全岗位工作经验一年以上为宜。初学者可以先看我们安卓版主非虫大佬的《Android软件安全权威指南》等安卓安全书籍进行入门,在看雪论坛看帖发帖提升自身水平,本套课程建议有工作经验的老手前来充电学习。

Q:想报名网课需要什么样的基础?像我这样的初学者可以报名么?

月薪三万计划视大家实际需求而定,一般看得懂目录及想要学习的人自己就懂,大家不用盲目跟风。如果看不懂目录及不理解目录的具体含义及意义,建议先从两万计划学起,多积累技术和经验。

Q:学习三万计划之前,需要先掌握两万计划的基础吗?

A:不需要,互相独立的。月薪两万计划的定位更加偏向工作岗位一线逆向需求,月薪三万计划则更加偏向于高级调试技巧,二者互为补充,相辅相成。有非常多地大佬两个计划一起报名了,我们也会确保直播时间不会冲突。

Q:想报三万的班,真的很想学高级技巧;但两万的班中也有很多是我想了解和学习的,大佬给些建议呢?

A:其实推荐两个班一起报,有好几位大佬就是两个班全报的。因为首先价格真心不贵,其实我们会将直播的时间错开,方便大家同时进修三万和两万计划,学习自己想要学习的、心仪的知识。

Q:直播答疑如果错过了,是否会有直播内容的回放?

A:每一场直播都有回放,在看雪课程中可以观看。

Q:就业班如何报名呢?流程是怎样的呢?

A:就业班是需要考核的。考核流程是先缴费报名,然后开始。会经过简历、(远程)一面和二面。通过之后补差价,不通过退全款。

Q:我已经报名了,趁开班前还想再预习一下,可否给个预习指南,让我好好利用开班前这段时间再恶补一下。
A:在月薪三万计划中,我们学习的主要目标是,掌握调试、分析和还原ollvm、vmp的方法,定制art虚拟机进行自动化脱壳的方法,主要涉及的技术栈是C\C++还原、arm(64),C++开发。
因此首先推荐邓凡平先生的《深入理解Android:Java虚拟机art》,里面的第五章详细讲解了art虚拟机的实现语言C++11,是阅读art源代码必备的知识;其余部分也详细讲解了Class文件、dex文件和ELF文件的格式和内容,以及art虚拟机的编译、runtime、解释执行、内存、线程等art的技术细节;
推荐的第二本书是《C++反汇编与逆向分析技术揭秘》,按照书中的方法自己编写实验代码对C++使用ndk编译后arm汇编进行对照,掌握c++数据类型、控制流、函数和类在编译后arm汇编的表现形式;希望大家预先掌握这些知识,即使现在不开始看,开课后也会要求大家必须掌握。
在月薪两万计划中,我们更加注重的是实际工作中遇到的各种场景、实际工作能力的提升,及解决实际问题的能力。因此各种逆向环境的搭建、逆向的综合能力和利用代码的编写是最关键的,这里主要涉及的技术栈也是比较杂的:比如网络、Ubuntu/安卓系统知识、应用安全开发、Frida/JS/Python等等、Java技巧,比较考验学员的计算机综合技术基础水平。
因此我们从工作实践中的需求出发,推荐大家首先强化安卓Java代码的开发、及各种网络和接口的知识,这两大技能被大量应用到应用安全、漏洞检测、渗透测试、黑灰攻防等方向,我们并不推荐具体的书目,只要涉及Java、安卓和网络的图书,都可以。有句话叫做开发的高度,决定了你逆向的高度,希望大家利用好开班前的时间,强化一下Java和网络开发的能力。
2021年度优秀学员作品展示:

# 十一月

《使用frida-net脱离pc在手机上直接暴漏app的算法供三方调用》
《Frida分析违法应用Native层算法》
《Frida实战:一次违法应用的破解尝试》
《使用unidbg破解孤挺花字符串混淆并修复so》
《破解某抢票软件的VPN抓包》
《从SSL库的内存漫游开发dump自定义客户端证书的通杀脚本》

# 十月

《dexvmp后的算法逆向分析和还原》

《使用unicorn对ollvm字符串进行解密》

《Frida追踪定Socket接口自吐游戏APK的服务器IP和地址》

 Frida hook Java/Native与init_array 自吐最终方案 

# 九月

《macOS安装调试llvm入门》

《fart的理解和分析过程》

《使用ollvm自定义简单的字符串加密》

《使用ida trace来还原ollvm混淆的非标准算法》

# 八月

ollvm算法还原案例分享

使用Frida打印Java类函数调用关系

# 七月

一个易上手的函数抽取样本还原

一个自定义classloader的函数抽取壳样本

利用Xposed对ollvm后的so中flag爆破

使用Frida分析动态注册jni函数绑定流程

frida跟踪应用中所有运行在解释模式的java函数

# 六月

举杯邀Frida,对影成三题

从三道题目入手入门frida

单纯使用Frida书写类抽取脱壳工具的一些心路历程和实践

某聊天app的音视频通话逆向

# 五月

记一次so文件动态解密

使用Frida简单实现函数粒度脱壳

初试IDA&FRIDA联合调试简单ollvm保护的加密函数源码

ollvm算法还原案例分享

# 四月

java函数转Native化的一些实践

某抽取壳的原理简析

frida辅助脱壳

一款最简单的关于动态注册的APP分析

# 三月

ollvm后的算法还原案例分享

ollvm CrackMe算法分析

ART下Hook系统函数修改内存中指定方法的运行指令逻辑案例分享

某类抽取加固APP的脱壳与修复

报 名 地 址

网课月薪三万计划:

https://www.kanxue.com/book-brief-84.htm

扫码立即报名!
网课月薪两万链接:
https://www.kanxue.com/book-brief-83.htm

扫码立即报名!

课程顾问微信:r0ysue(备注“安卓高研网课”)

 

看雪ID:GitRoy

https://bbs.pediy.com/user-home-762912.htm

*本文由看雪论坛 GitRoy 原创,转载请注明来自看雪社区

https://www.kanxue.com/book-leaflet-84.htm

# 往期推荐

1.CVE-2022-21882提权漏洞学习笔记

2.wibu证书 - 初探

3.win10 1909逆向之APIC中断和实验

4.EMET下EAF机制分析以及模拟实现

5.sql注入学习分享

6.V8 Array.prototype.concat函数出现过的issues和他们的POC们

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458488904&idx=1&sn=3d724d6752ee5f642fa0ef90bd289809&chksm=b18ea0c286f929d4a4a963e76e2fc6e184aa203d98200fd64da7d768def3c8a88338e414f380#rd
如有侵权请联系:admin#unsafe.sh