【Android学习】抽取壳的实现

工具学习

Posted by Corax on May 17, 2024

art下的抽取壳实现

记住恢复时间永远要早于这个被调用时机

要知道类的准备阶段

image-20240528013737805

装载 链接 初始化

姜维师傅选择dexfindclass

当dexclassloader被加载完之后 就要使用classloader去加载这些类了

重提一下

image-20240528013956110

有隐式加载和显式加载

比如说这个dexclassloader的loadclass

可以看到有一个双亲委派的一个体现

image-20240528014136344

在真的进行一个class的加载的时候肯定是null的,进入第一个分支,我们加壳之后的classloader的双亲要么是pathclassloader,要么是指定后的bootclassloader,肯定都加载不了

所以会进入下面这个判断,开始findclass

甄别一下,是dexpathlist里面的findclass

image-20240528014541946

遍历,然后用loadclassbinaryname去尝试加载这个类

image-20240528014626817

然后进去 这个mcookie是当前dexfile的一个对象的变量,理解成一个指针

image-20240528014711635

然后defineclass接着看 ,可以看到是一个native层的实现

image-20240528014824317

可以看到cookie变成了一个指针

image-20240528014834739

其中这里是关键 继续跟

image-20240528014906061

来到这,可以看到没有做什么特别的操作,只是一个指针的赋值

image-20240528015006345

再回到上一级的这里

image-20240528015057115

image-20240528015132504

接着 findclassnoinit

里面这个是关键

image-20240528015206061

这里面的逻辑通过计算哈希值,对比有没有加载过某个class

image-20240528015300154

当然是没有的

所以是null了

image-20240528015422863

可u一看到

image-20240528015446879

就是姜维师傅找到的实现对抽取函数实现,并解决的一个方案

以上就是一个dalvik的函数抽取的一个实现

下面是art下的抽取壳的实现

俩事 干掉dex2oat 实现抽取函数的还原

有一个东西要先提,就是这个dex2oat的产物oat,如果生成了这个oat,后续所用代码会从这个文件里获得,以至于我们动态修改的dex的smali就不会生效了。

所以如果我们再生成oat之后再进行对代码的填充,就无效了。

所以要么

禁用dex2oat 阻止掉dex2oat的过程
还原时机早于dex2oat

采用禁用dex2oat的一个方法

如何禁用呢,

需要回到ART下面dexclassloader动态加载的流程,或者直接使用inmemoryclassloader

回到我们之前进去的这个generateoatfilenochecks

image-20240528020227941

关键在这

image-20240528020243938

来到exec,来到execandreturncode,来到libc当中的execve

所以以上任何流程的打断,就会终止oat的生成

依然是通过hook,尝试使用hook execve

一个简单的实现 可以看到这个对exec的hook,首个是execve的地址,然后是我们替换的myexecve地址,最后是原函数的地址做保存

void hooklibc() {
    LOGD("go into hooklibc");
    //7.0 命名空间限制
    void *libc_addr = dlopen_compat("libc.so", RTLD_NOW);
    void *execve_addr = dlsym_compat(libc_addr, "execve");
    if (execve_addr != NULL) {
        if (ELE7EN_OK == registerInlineHook((uint32_t) execve_addr, (uint32_t) myexecve,
                                            (uint32_t **) &oriexecve)) {
            if (ELE7EN_OK == inlineHook((uint32_t) execve_addr)) {
                LOGD("inlineHook execve success");
            } else {
                LOGD("inlineHook execve failure");
            }
        }
    }
}

那我们看看这个myexecve怎么实现的

如果调用execve的时候里面意图是dex2oat,就直接null

image-20240528021537882

为啥这么写呢 参数方面 原execve如下

image-20240528021623557

一个试用,可以看到如下

image-20240528021733070

这样就干掉了dex2oat

然后就是对被抽空函数的还原

这是对class当中method加载的一个流程

image-20240528024502961

如果我们需要修补一个函数,就需要找到他的codeitem进行修补

出现一些时机点可以hook掉 就是这个loadmethod里面有codeitem相关的一个赋值

image-20240528024622214

先看看这个Artmethod里面有个变量 很重要

就是这个

image-20240528024903140

当一个class加载好之后,就准备好了这个每个函数对于的artmethod对象,每个artmethod对象都和java层的method 一一对应

当我们禁用了de2oat 就都在解释模式下运行

就可以找到这个codeitem

呐 进来了

image-20240528030133800

这里对method的codeitem偏移做了一个设置 理解成方法在内存中的偏移

hook这个loadmethod的好处就是

参数中有一个指针,就是执行这个artmethod的指针

image-20240528030242647

然后取出上面的codeitemoffset

我们现在手动来看看这个dex里面的对应的一个方法的结构

image-20240528031834402

image-20240528031849506

这个code_off 就是偏移!

image-20240528031902044

这个就是codeitem

image-20240528031918249

一共32个字节

前面16个字节固定

解读下来就是三个寄存器 一个参数 其他函数调用参数要两个 0个结构个数 然后00140830事故debug信息 然后是后面指令大小 就是8 以16bit为单位 就是2个字节

image-20240528032018937

通过GDA 也可以看到有16个字节

image-20240528032559586

对了吧

一个字节是8个bit 2个十六进制数

00000008是以16bit为单位,则4个十六进制数,则32个十六进制数,则16个字节 数目对上了

现在我们把code部分置0

image-20240528033307208

并且更改整个dex的checksum

看看效果

image-20240528033444838

ok,现在抽完了,要修复回去怎么弄呢。

函数逻辑写在一个cpp里面,我们看看

先贴出来

void *myloadmethod(void *a, void *b, void *c, void *d, void *e) {
    LOGD("process:%d,before run loadmethod:", getpid());
    struct ArtMethod *artmethod = (struct ArtMethod *) e;
    struct DexFile *dexfile = (struct DexFile *) b;
    LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d", getpid(), dexfile->begin,
         dexfile->size);//0,57344
    char dexfilepath[100] = {0};
    sprintf(dexfilepath, "/sdcard/%d_%d.dex", dexfile->size, getpid());
    int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666);
    if (fd > 0) {
        write(fd, dexfile->begin, dexfile->size);
        close(fd);
    }

    void *result = oriloadmethod(a, b, c, d, e);
    LOGD("process:%d,enter loadmethod:code_offset:%d,idx:%d", getpid(),
         artmethod->dex_code_item_offset_, artmethod->dex_method_index_);

    byte *code_item_addr = static_cast<byte *>(dexfile->begin) + artmethod->dex_code_item_offset_;
    LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,beforedumpcodeitem:%p", getpid(),
         dexfile->begin, dexfile->size, code_item_addr);


    if (artmethod->dex_method_index_ == 15203) {//TestClass.testFunc->methodidx
        LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,start repire method", getpid(),
             dexfile->begin, dexfile->size);
        byte *code_item_addr = (byte *) dexfile->begin + artmethod->dex_code_item_offset_;
        LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,beforedumpcodeitem:%p", getpid(),
             dexfile->begin, dexfile->size, code_item_addr);

        int result = mprotect(dexfile->begin, dexfile->size, PROT_WRITE);
        byte *code_item_start = static_cast<byte *>(code_item_addr) + 16;
        LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,code_item_start:%p", getpid(),
             dexfile->begin, dexfile->size, code_item_start);
        byte inst[16] = {0x1a, 0x00, 0xed, 0x34, 0x1a, 0x01, 0x43, 0x32, 0x71, 0x20, 0x91, 0x05,
                         0x10, 0x00, 0x0e, 0x00};
        for (int i = 0; i < sizeof(inst); i++) {
            code_item_start[i] = inst[i];
        }
        //2343->i am from com.kanxue.test02.TestClass.testFunc
        code_item_start[2] = 0x43;//34ed->kanxue
        code_item_start[3] = 0x23;
        memset(dexfilepath, 0, 100);
        sprintf(dexfilepath, "/sdcard/%d_%d.dex_15203_2", dexfile->size, getpid());
        fd = open(dexfilepath, O_CREAT | O_RDWR, 0666);
        if (fd > 0) {
            write(fd, dexfile->begin, dexfile->size);
            close(fd);
        }
    }
    LOGD("process:%d,after loadmethod:code_offset:%d,idx:%d", getpid(),
         artmethod->dex_code_item_offset_, artmethod->dex_method_index_);//0,57344
    return result;

}

hook代码出把loadmethod变成了我们这个myloadmethod

我们要修补回去函数,那需要codeitem的偏移对吧?

但是在进入我们这个loadmethod之后因为没有调用原来的loadmethod,所以压根没有这个codeitem

所以在这里先调用

image-20240528034441498

(上面这个只是一个dump,用来对比)

image-20240528034454884

然后获取offset,index

image-20240528034540164

进行一些判断,然后判断为15203 也就是函数为我们需要填充的函数的时候,才进行修补

image-20240528034607176

这样就恢复了一个原始的指令

测试之后木有问题啊,大成功

image-20240528034839511