首页
论坛
课程
招聘
xp遗民再次挣扎下,Xp模块拖Art函数抽取壳
2020-9-16 16:51 4796

xp遗民再次挣扎下,Xp模块拖Art函数抽取壳

2020-9-16 16:51
4796

art脱壳机

特点

  1. 理论上兼容各种art机型,如Android5-android10,小米、华为、三星
  2. 不需要刷机或者编译系统,只要能注入代码就可以脱壳。可以使用virtualXposed或者太极之类的免root框架
  3. 脱壳粒度在方法粒度,也即大部分方法抽取,方法执行后解密等case都是可以脱壳(不支持VMP和方法执行后抹除场景)
  4. 支持32和64位手机
  5. 支持多classloader,动态加载类型dex,加载行为通过javaAPI控制。灵活方便

原理

定位method invoke方法

在art中有Method->Invoke方法导出,通过符号查找即可。

1
2
3
4
5
6
7
8
#define ART_METHOD_INVOKE_SYM "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc"
 
void *handle = getSymCompat(
            art_lib_path, ART_METHOD_INVOKE_SYM);
    if (handle == nullptr) {
        UNPACK_LOGW("can not find symbol: %s ratel unpack will not running", ART_METHOD_INVOKE_SYM);
        return;
    }

invoke方法hook

也不难,选择一个比较好的inlinehook框架就可以,我现在选择的是

有大佬的代码抄,还是很舒服的。 为什么需要hook,因为hook了这个函数才能拦截到方法执行流程。才能拦截到ArtMethod对象。hook了还可以做methodTrace

ArtMethod -> DexFile -> Dex

这一步有点麻烦,在dumpMethod的时候,需要知道这个Method在那个Dex上面,如果是编译系统方式,有一大堆API可以调用。
但是如果通过hook来操作的话,大部分API都被inline或者粉碎了。

 

ArtMethod有一个函数,GetDexFile,然后这个函数被inline了,所以直接通过getSymCompat也拿不到函数地址。不过我试了下,
AsLody的whale框架还是又点儿牛逼,whale框架可以获取到这个符号: https://github.com/asLody/whale/blob/master/whale/src/platform/linux/elf_image.h#L140

1
2
3
4
5
6
7
8
9
10
11
auto range = whale::FindExecuteMemoryRange(art_lib_path);
   if (range->IsValid()) {
       whale::ElfImage *image = new whale::ElfImage();
       if (!image->Open(range->path_, range->base_)) {
           delete image;
       } else {
           void *getDexFileHandle = image->FindSymbol<void *>("_ZN3art9ArtMethod10GetDexFileEv");
           UNPACK_LOGI("getDexFileHandle: %p", getDexFileHandle);
           this->getDexFile_handle = (void *(*)(art::mirror::ArtMethod *)) (getDexFileHandle);
       }
   }

不要问,用它就是了

指令定位

在方法执行的时候,找到了Dex,也知道执行的是那个ArtMethod对象,这个时候如果再找到ArtMethod的CodeItem,基本脱壳功能就能搞了。
指令定位这里,主要依靠ArtMethod的字段计算,包括 dex_code_itemoffset和 dex_methodindex,但是由于Android不同版本
碎片化问题,我们不能直接写死偏移。这个时候就可以抄sandhok了: https://github.com/ganyao114/SandHook

 

核心原理:手动构造两个方法,在虚拟机层面转换成ArtMethod,然后两个Artmethod的地址差值就是ArtMethod结构体的大小。
再然后在结构体中搜索accessFlag等已知属性的地址,找到一些航标偏移。再然后根据航标计算我们关心的成员的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CastDexMethodIndex : public IMember<art::mirror::ArtMethod, uint32_t> {
protected:
    Size calOffset(JNIEnv *jniEnv, art::mirror::ArtMethod *p) override {
        if (SDK_INT >= ANDROID_P) {
            return CastArtMethod::accessFlag->getOffset()
                   + CastArtMethod::accessFlag->size()
                   + sizeof(uint32_t);
        }
        int offset = 0;
        jint index = getIntFromJava(jniEnv, "com/virjar/artunpacker/SandHookMethodResolver",
                                    "dexMethodIndex");
        if (index != 0) {
            offset = findOffset(p, getParentSize(), 2, static_cast<uint32_t>(index));
            if (offset >= 0) {
                return static_cast<Size>(offset);
            }
        }
        return getParentSize() + 1;
    }
};

最后,计算CodeItem的数据,dump到文件,齐活儿:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
void DexFileHolder::dumpMethod(art::mirror::ArtMethod *artMethod) {
    if (artMethod->isAbstract()) {
        return;
    }
    if (artMethod->isNative()) {
        return;
    }
 
 
    // Offset to the CodeItem.
    uint32_t dex_code_item_offset_ = artMethod->getDexCodeItemIndex();
    // Index into method_ids of the dex file associated with this method.
    uint32_t dex_method_index_ = artMethod->getDexMethodIndex();
 
 
    auto *codeItem = (CodeItem *) (this->begin_ + dex_code_item_offset_);
 
    auto inter = this->dumped_method.find(dex_method_index_);
    if (inter != this->dumped_method.end()) {
        //dumped already
        return;
    }
    std::string method_sign = UnpackerInstance.prettyMethod(artMethod);
    UNPACK_LOGI("dump method: %s  dex_code_item_offset_: %u dex_method_index_: %u",
                method_sign.c_str(), dex_code_item_offset_, dex_method_index_);
    this->dumped_method.insert(std::pair<uint32_t, bool>(dex_method_index_, true));
 
    auto *item = (uint8_t *) codeItem;
    int code_item_len;
    if (codeItem->tries_size_) {
        const u1 *handler_data = reinterpret_cast<const uint8_t *>(GetTryItems(*codeItem,
                                                                               codeItem->tries_size_));
        uint8_t *tail = codeitem_end(&handler_data);
        code_item_len = (int) (tail - item);
    } else {
        //正确的DexCode的大小
        code_item_len = 16 + codeItem->ins_size_ * 2;
    }
 
    if (code_item_len == 0) {
        //空函数body
        return;
    }
 
    char method_dump_file[PATH_MAX] = {'\0'};
    sprintf(method_dump_file, "%s%d_%d.bin", this->method_dump_dirs.c_str(), dex_method_index_,
            code_item_len);
 
    if (access(method_dump_file, F_OK) == 0) {
        // 已经写过了
        return;
    }
    UNPACK_LOGI("dump method into file:%s", method_dump_file);
    std::ofstream out_method_file;
    out_method_file.open(method_dump_file, std::ios::out | std::ios::binary);
    if (!out_method_file.is_open()) {
        UNPACK_LOGE("can not open file: %s", method_dump_file);
        return;
    }
    out_method_file.write((const char *) (item), code_item_len);
    out_method_file.flush();
    out_method_file.close();
}

使用:

直接调用javaAPI: com.virjar.artunpacker.ArtUnPacker.enableUnpackComponent
传递一个文件下路径,然后脱壳机会把方法指令数据和dex数据dump到这个文件夹下面。

所以Xposed遗民也是可以用MethodTrace功能的emmmm

github地址:https://github.com/virjar/DVMUnpacker/tree/master/artunpacker/src/main

 

对了,脱壳机是内部代码剥离出来的,自己跑起来没问题,github项目暂时没有实际跑过。有bug的话自己改改。
要么就给我钱,捐赠就干活儿。


[看雪官方]《安卓高级研修班》线下班,网课(12月)班开始同步招生!!

收藏
点赞2
打赏
分享
最新回复 (17)
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_icmncwxy 活跃值 2020-9-16 17:58
2
0
第一排占位仰望
雪    币: 632
活跃值: 活跃值 (1240)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
supperlitt 活跃值 2020-9-16 18:04
3
0
66666
雪    币: 4025
活跃值: 活跃值 (1053)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
LeadroyaL 活跃值 1 2020-9-16 18:53
4
0
666 先占再看
雪    币: 34
活跃值: 活跃值 (88)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
winkar 活跃值 2020-9-17 13:45
5
0
看了下whale的代码,它能获取到符号应该是因为它直接去解析了人家的符号表,GetDexFile这个函数可能不在export里,所以dlsym解析不到?
雪    币: 1877
活跃值: 活跃值 (564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
virjar 活跃值 1 2020-9-17 14:03
6
0
winkar 看了下whale的代码,它能获取到符号应该是因为它直接去解析了人家的符号表,GetDexFile这个函数可能不在export里,所以dlsym解析不到?
不在export。whale多搜索了两个段:gnu_nbucket_, nbucket_,symtab_,   而dlsym只搜索了symtab_
雪    币: 1877
活跃值: 活跃值 (564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
virjar 活跃值 1 2020-9-17 14:06
7
0
winkar 看了下whale的代码,它能获取到符号应该是因为它直接去解析了人家的符号表,GetDexFile这个函数可能不在export里,所以dlsym解析不到?
GetDexFile,用ida看的话,没有export,但是ida可以解析到这个函数。所以理论上是可以计算出函数地址的
雪    币: 804
活跃值: 活跃值 (318)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
lhxdiao 活跃值 2020-9-17 15:43
8
0
resolve_symbol计算出来的偏移是完全错误的,你说的那个whale计算出来的偏移也不对。只能写死。另,这一整套方法只能说太复杂,实际上有更简单的,不超过20行。你的这种方案在所谓virtualxposed根本用不了,hook跟epic冲突,同样在sandvxp也是,原因是这些东西早就给art的各种函数上了钩子,再上钩子就炸。
雪    币: 1877
活跃值: 活跃值 (564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
virjar 活跃值 1 2020-9-17 17:29
9
0
lhxdiao resolve_symbol计算出来的偏移是完全错误的,你说的那个whale计算出来的偏移也不对。只能写死。另,这一整套方法只能说太复杂,实际上有更简单的,不超过20行。你的这种方案在所谓virtua ...
雪    币: 1877
活跃值: 活跃值 (564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
virjar 活跃值 1 2020-9-17 17:39
10
0
lhxdiao resolve_symbol计算出来的偏移是完全错误的,你说的那个whale计算出来的偏移也不对。只能写死。另,这一整套方法只能说太复杂,实际上有更简单的,不超过20行。你的这种方案在所谓virtua ...
刚刚发布的不知道为啥显示不出来。

首先这个是实践测试过的哈,目前我在sailfish Android8.0和小米9,Android9.0两个机型上面正常测试通过。
关于偏移错误这个,我通过编译aosp,在sailfish GetDexFile函数里面打印了DexFile对象的地址,然后hook调用之后也打印了DexFile对象地址,日志上这两个地址是一样的。这个看看你能不能给一个手机或者什么场景复现下。

和virtualXposd或者Epic冲突。。。貌似这个脱壳机并没有真的依赖ArtHook的什么功能,我常用的破解环境也是基于Sandhook的定制版本。我并没有发现Sandhok有对ArtMethod->Invoke有hook。一个是常规的inlinehook,一个是java的arthook,两个框架并没有直接影响才对啊。然后你说的SandVXP底层也是SandHook,讲道理底层和我的环境也是一套。

我之前测试过,如果同是arthook,两个hook框架同时工作会崩溃。比如sandhook和xposed。但是inlinehook貌似一般都可以被二次hook,这个我遇到过很多app,目前没有发现在inline hook层面两个hook框架不兼容的。然后,文章脱壳机代码,不依赖arthook功能,只依赖inlinehook

雪    币: 804
活跃值: 活跃值 (318)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
lhxdiao 活跃值 2020-9-17 18:12
11
0
virjar 刚刚发布的不知道为啥显示不出来。 首先这个是实践测试过的哈,目前我在sailfish Android8.0和小米9,Android9.0两个机型上面正常测试通过。 关于偏移错误这个,我通过编译 ...
inline hook在sandhook-native确实会崩溃,epic也是。推荐简单一点的方法,java把method传进去,硬偏移加4写死即可。
雪    币: 804
活跃值: 活跃值 (318)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
lhxdiao 活跃值 2020-9-17 18:19
12
0
virjar 刚刚发布的不知道为啥显示不出来。 首先这个是实践测试过的哈,目前我在sailfish Android8.0和小米9,Android9.0两个机型上面正常测试通过。 关于偏移错误这个,我通过编译 ...
另,偏移错误纯粹是因为resolve_symbol的问题,有的机型会有问题(vivo x50pro和我自己的redmi note 4x,其中redmi是第三方基于原生10的固件)。反而有时候dlsym是正确的,resolve出来的地址是错的。
雪    币: 1877
活跃值: 活跃值 (564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
virjar 活跃值 1 2020-9-17 18:34
13
0
lhxdiao inline hook在sandhook-native确实会崩溃,epic也是。推荐简单一点的方法,java把method传进去,硬偏移加4写死即可。
不理解,你没有回答我的问题。所谓崩溃到底是arthook导致的崩溃,还是inlinehook导致的崩溃。

偏移错误是whale计算GetDexFile地址错误,还是artmethod成员属性偏移计算错误。

雪    币: 804
活跃值: 活跃值 (318)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
lhxdiao 活跃值 2020-9-17 18:43
14
0
virjar 不理解,你没有回答我的问题。所谓崩溃到底是arthook导致的崩溃,还是inlinehook导致的崩溃。 偏移错误是whale计算GetDexFile地址错误,还是artmethod成员属性偏移 ...
artmethod 在 二次inlinehook以后 执行到函数地址 崩溃(跟指令修复有关)。
地址计算错误,不是成员偏移。
我想表达的主要是项目可以更简单一些的,不用做的太复杂。
雪    币: 1877
活跃值: 活跃值 (564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
virjar 活跃值 1 2020-9-17 18:50
15
0
lhxdiao artmethod 在 二次inlinehook以后 执行到函数地址 崩溃(跟指令修复有关)。 地址计算错误,不是成员偏移。 我想表达的主要是项目可以更简单一些的,不用做的太复杂。

artmethod我的hook点,不是sandhook的hook点啊。这个地方不存在二次inlinehook。

地址计算我就调用了一下whale,这里应该不至于导致复杂吧。当然artmethod的偏移,抄了sandhook,确实导致复杂了。不过这也是为了 兼容性。实际上sandhook这段代码兜底策略都有很多适配:


uint32_t accessFlag = getIntFromJava(jniEnv, sandSandHookClass,
                                                 "testAccessFlag");
            if (accessFlag == 0) {
                accessFlag = 524313;
                //kAccPublicApi
                if (SDK_INT >= ANDROID_Q) {
                    accessFlag |= 0x10000000;
                }
            }
            int offset = findOffset(p, getParentSize(), 2, accessFlag);
            if (offset < 0) {
                if (SDK_INT >= ANDROID_N) {
                    return 4;
                } else if (SDK_INT == ANDROID_L2) {
                    return 20;
                } else if (SDK_INT == ANDROID_L) {
                    return 56;
                } else {
                    return getParentSize() + 1;
                }
            } else {
                return static_cast(offset);
            }



更简单的方案,方便贴代码看看么?


雪    币: 632
活跃值: 活跃值 (1240)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
supperlitt 活跃值 2020-9-18 11:03
16
0
楼主给以给个app出来么,找不到可以用的案例
雪    币: 3025
活跃值: 活跃值 (143)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xianhuimin 活跃值 2020-9-19 15:42
17
0
这个只会dump软件执行的指令把?没有主动去遍历所有方法吧?
雪    币: 1877
活跃值: 活跃值 (564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
virjar 活跃值 1 2020-9-19 20:00
18
0
xianhuimin 这个只会dump软件执行的指令把?没有主动去遍历所有方法吧?
没有,我认为没有执行过的代码不需要实现脱壳。所以我没有构造主动调用
游客
登录 | 注册 方可回帖
返回