吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3023|回复: 10
收起左侧

[Android 脱壳] 自定义linker加固so

  [复制链接]
镇北看雪 发表于 2023-2-8 13:00
本帖最后由 镇北看雪 于 2023-2-8 17:05 编辑

其实自实现linker加固so与之前研究windows平台的PE文件的加密壳原理很相似。主要就是自定义文件格式加密so,然后壳代码实现将加密的so文件加载,链接重定位并修正soinfo(三部曲)。

自定义文件格式

格式可以自己定义,只要在壳代码加载so时能够知道正确的格式就可以,下面是对标准的ELF文件格式的各部分数据进行简单的重组加密。

  • 将原so文件的Elf64_Ehdr文件头进行重新定义为Custom_Elf64_Ehdr并使用rc4加密保存在elf文件末尾,旧的Elf64Ehdr被置0。
  • 将原so文件的program header table进行rc4加密后保存在elf文件末尾,旧的program header table被置0。
  • 将所有的PT_LOAD段进行rc4加密保存,文件偏移不变。
  • 在被置0后的原Elf64_Ehdr文件头和program header table位置写入自定义的Custom_Elf64_File文件头,此结构用来保存加密后放置在文件末尾的Custom_Elf64_Ehdrprogram header table的文件偏移和大小等信息。
    2052882-20230208002542048-755167136.png

加壳代码

加壳代码通过解析一个标准的so文件将其格式保存为上面自定义的文件格式。

int do_pack(const char *inputfile_buffer, size_t inputfile_size, char *outfile_buffer, size_t outfile_size) 
{
    if(NULL == inputfile_buffer || 0 == inputfile_size || NULL == outfile_buffer)    return -1;

    Elf64_Ehdr* elf64_header = inputfile_buffer;
    //自定义的elf文件头(此结构在加密文件的开始,不进行加密)
    Custom_Elf64_File my_elf64_file = {0};
    my_elf64_file.elf_header_off = inputfile_size + elf64_header->e_phnum * elf64_header->e_phentsize;
    my_elf64_file.elf_header_size = elf64_header->e_ehsize;
    my_elf64_file.elf_program_header_table_num = elf64_header->e_phnum;
    my_elf64_file.elf_program_header_table_off = inputfile_size;
    my_elf64_file.elf_program_header_table_size = elf64_header->e_phnum * elf64_header->e_phentsize;

    //将原elf文件头字段进行重排
    Custom_Elf64_Ehdr my_elf64_header = {0};
    my_elf64_header.e_ehsize = elf64_header->e_ehsize;
    my_elf64_header.e_entry = elf64_header->e_entry;
    my_elf64_header.e_flags = elf64_header->e_flags;
    my_elf64_header.e_machine = elf64_header->e_machine;
    my_elf64_header.e_shentsize = elf64_header->e_shentsize;
    my_elf64_header.e_shnum = elf64_header->e_shnum;
    my_elf64_header.e_shoff = elf64_header->e_shoff;
    my_elf64_header.e_shstrndx = elf64_header->e_shstrndx;
    my_elf64_header.e_phentsize = elf64_header->e_phentsize;
    my_elf64_header.e_phnum = elf64_header->e_phnum;
    my_elf64_header.e_phoff = my_elf64_file.elf_program_header_table_off;
    strcpy(my_elf64_header.e_ident, ".csf");

    //加密原elf文件头
    RC4(&my_elf64_header, my_elf64_header.e_ehsize, rc4_key, strlen(rc4_key), outfile_buffer + my_elf64_file.elf_header_off);
    //加密progrem table header
    RC4(inputfile_buffer + elf64_header->e_phoff, elf64_header->e_phnum * elf64_header->e_phentsize, rc4_key, strlen(rc4_key), outfile_buffer + my_elf64_file.elf_program_header_table_off);
    //加密所有的PT_LOAD区段
    Elf64_Phdr *p = inputfile_buffer + elf64_header->e_phoff;
    for(int i = 0; i < elf64_header->e_phnum; i++){
        if(p->p_type == PT_LOAD){
            RC4(inputfile_buffer + p->p_offset, p->p_filesz, rc4_key, strlen(rc4_key), outfile_buffer + p->p_offset);
        }
        p = (char*)p + sizeof(Elf64_Phdr); 
    }

    //将原elf文件头抹去并替换为自定义elf文件头
    memset(outfile_buffer, 0, sizeof(Elf64_Ehdr));
    memcpy(outfile_buffer, &my_elf64_file, sizeof(Elf64_Ehdr));
    return 0;
}

用ida查看加固后的so是肯定无法解析的,使用010editor看一下加固后的so,其数据全部都被加密了。

2052882-20230208004843603-1075944142.png

这里加壳代码并没有将加固so与壳so合并为一个so文件,现在的自定义linker加壳都是使用这种方式。

图片.png

壳so(自定义linker)

壳so负责将加固后的so文件解密,解析并加载,然后链接重定位,最后将壳so自己的soinfo修正为加固so。

解密文件

//此结构在文件中不加密
typedef struct custom_elf64_file{
    Elf64_Off   elf_header_off;
    Elf64_Half  elf_header_size;
    Elf64_Off   elf_program_header_table_off;
    Elf64_Half  elf_program_header_table_num;
    Elf64_Half  elf_program_header_table_size;
}Custom_Elf64_File;
void unpack(char *elf_pack_data, Custom_Elf64_File my_elf64_file)
{
    //对重排列后原elf头进行解密
    RC4(reinterpret_cast<unsigned char *>(elf_pack_data + my_elf64_file.elf_header_off), my_elf64_file.elf_header_size,
        reinterpret_cast<unsigned char *>(rc4_key), strlen(rc4_key),
        reinterpret_cast<unsigned char *>(elf_pack_data + my_elf64_file.elf_header_off));
    //对progrem table header进行解密
    RC4(reinterpret_cast<unsigned char *>( elf_pack_data +
                                          my_elf64_file.elf_program_header_table_off),  my_elf64_file.elf_program_header_table_size,
        reinterpret_cast<unsigned char *>(rc4_key), strlen(rc4_key),
        reinterpret_cast<unsigned char *>( elf_pack_data +
                                          my_elf64_file.elf_program_header_table_off));

    //对各个PT_LOAD段进行解密
    Elf64_Phdr *p = reinterpret_cast<Elf64_Phdr *>( elf_pack_data +
                                                   my_elf64_file.elf_program_header_table_off);
    for(int i = 0; i < my_elf64_file.elf_program_header_table_num; i++){
        if(p->p_type == PT_LOAD){
            RC4(reinterpret_cast<unsigned char *>(elf_pack_data + p->p_offset), p->p_filesz,
                reinterpret_cast<unsigned char *>(rc4_key), strlen(rc4_key),
                reinterpret_cast<unsigned char *>(elf_pack_data + p->p_offset));
        }
        p = reinterpret_cast<Elf64_Phdr *>((char *) p + sizeof(Elf64_Phdr));
    }
}

解析自定义格式的ELF文件并加载

获取自定义格式的ELF文件头部的Custom_Elf64_File文件头,此文件头中保存了原始elf文件的文件头和program header table文件偏移和大小。 解析自定义格式的ELF文件并加载到内存中。将program header table保存在其他地方供之后使用。

Custom_Elf64_File my_elf64_file = {0};
memcpy((void *)&my_elf64_file, new_so_file_data, sizeof(Custom_Elf64_File));

//加载elf文件
load LoadElf(fd, my_elf64_file,new_so_file_data);
if (!LoadElf.ReadElfHeader() ||
    !LoadElf.ReadProgramHeader() ||
    !LoadElf.ReserveAddressSpace() ||
    !LoadElf.LoadSegments()) {                         
    munmap(new_so_file_data, sb.st_size);
    close(fd);
    return false;
}

//将内存中的program header table放在其他地方
Elf64_Phdr *program_header_table = (Elf64_Phdr*)malloc( my_elf64_file.elf_program_header_table_size);
memcpy(program_header_table, (char*)new_so_file_data + my_elf64_file.elf_program_header_table_off, my_elf64_file.elf_program_header_table_size);
LoadElf.SetPHPtr(program_header_table);

获取壳so的soinfo

linker程序通过函数soinfo_from_handle利用handle从g_soinfo_handles_map表中获取到对应的soinfo,但是此函数并未导出,参考https://www.cnblogs.com/r0ysue/p/15331621.html调用ida反编译的代码。需要注意的是g_soinfo_handles_map在不同的Android版本中偏移不同。

2052882-20230208011404720-1328327979.png

获取到壳so的soinfo后将其修正为加壳so。

加载所有的依赖项

解析出加固so所有的依赖项并加载,注意因为链接器命名空间的问题需要再修改壳so的soinfo之前加载依赖项,具体原因参考https://www.cnblogs.com/revercc/p/17097115.html.

参考android源码的prelink_image函数,解析加固so的.dynamic节区信息并对soinfo进行修正。

2052882-20230208012118285-461002886.png

对soinfo中其他信息进行修正

old_soinfo->size = LoadElf.load_size_;
old_soinfo->phnum = LoadElf.phdr_num_;
old_soinfo->phdr = LoadElf.loaded_phdr_;
//修改soinfo对应的文件信息
old_soinfo->st_dev_ = sb.st_dev;
old_soinfo->st_ino_ = sb.st_ino;
old_soinfo->file_offset_ = 0;

加载所有的依赖库

通过dlopen加载所有的依赖库

//加载所有的依赖库
for(int s=0;s<needed;s++) {
    if(NULL == dlopen(old_soinfo->strtab_ + mynedd[s],RTLD_NOW)){
        goto exend;
    }
}

在加载完依赖项之后再修正壳soinfo的base和load_bias,原因是为了避免以为链接器命名空间产生的问题,具体问题分析参考 https://www.cnblogs.com/revercc/p/17097115.html

old_soinfo->base = LoadElf.load_start_;
old_soinfo->load_bias = LoadElf.load_bias_;

调用phdr_table_unprotect_segments去除加固so各个段的保护属性后调用link_image进行链接重定位,重定位完成之后再通过phdr_table_protect_segments恢复各个段的属性。

//去除各个段的保护
phdr_table_unprotect_segments(LoadElf.phdr_table_, LoadElf.phdr_num_, LoadElf.load_bias_));

//进行链接重定位
link_image(old_soinfo, &LoadElf, needed, mynedd);

//恢复各个段的属性
phdr_table_protect_segments(LoadElf.phdr_table_, LoadElf.phdr_num_, LoadElf.load_bias_));

link_image需要对.rel.plt节区中的R_AARCH64_JUMP_SLOT重定位类型进行重定位,对.rel.dyn节区中的R_AARCH64_RELATIVE, R_AARCH64_ABS64, R_AARCH64_GLOB_DAT重定位类型进行重定位。同样可以参考android源码

2052882-20230208013456955-263340328.png

脱壳

因为加固so被加载到内存中时其默认加载位置处的elf文件头和program header table都被抹去了。所以如果尝试dump整块内存,并利用映射基地址处的elf文件头去解析还原elf文件是无法实现的。(upx的脱壳方式)

2052882-20230208105138513-563589762.png

要想脱壳就需要通过soinfo_list获取对应的soinfo,soinfo中保存了加载到内存中elf文件的program header table,基地址,映射大小等信息。通过soinfo中保存的elf文件内存信息去dump内存并进行修复,这种方式其实和pe文件利用LordPE寻找到映射文件基地址和重定位表并进行dump修复很相似。

2052882-20230208105730570-235626711.png

参考:
https://www.cnblogs.com/theseventhson/p/16366038.html

免费评分

参与人数 8吾爱币 +10 热心值 +7 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
junjia215 + 1 + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
78zhanghao87 + 1 谢谢@Thanks!
debug_cat + 1 + 1 用心讨论,共获提升!
461735945 + 1 + 1 谢谢@Thanks!
iokeyz + 3 + 1 吾爱破解论坛因你更精彩!
SysEntry + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

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

codown2017 发表于 2023-2-8 14:47
看了大佬的分析,受益匪浅。膜拜
Light紫星 发表于 2023-2-8 15:26
大佬,学习到了,这种脱壳感觉可以在load之后的时候进行,不知道能不能脱掉
 楼主| 镇北看雪 发表于 2023-2-8 15:36
Light紫星 发表于 2023-2-8 15:26
大佬,学习到了,这种脱壳感觉可以在load之后的时候进行,不知道能不能脱掉

是的,主要是对elf文件格式的一个熟悉
a1046830 发表于 2023-2-8 15:56
好东西 学习学习
雁字回时月man楼 发表于 2023-2-8 19:29
大佬多出点这种教程
debug_cat 发表于 2023-2-8 21:16
大佬多出点这种教程
不会想象的明天 发表于 2023-2-9 00:05
这种东西值得学习,谢谢啦,辛苦
qq882011 发表于 2023-2-9 10:12
谢谢分享。。。。
metoo2 发表于 2023-2-9 10:17
厉害了,研究下看看
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-3-29 20:45

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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