首页
社区
课程
招聘
[原创] 自定义Linker与SO加固技术
发表于: 2天前 453

[原创] 自定义Linker与SO加固技术

2天前
453

在研究360加固方案时,我发现其采用了一种独特的保护机制:在主SO文件内部嵌套了另一个SO文件,相当于为主SO添加了一层保护壳。这种技术实现引起了我的兴趣,经过深入研究,整理出本文,分享自定义Linker的实现原理及SO加固技术

看起来很简单对吧,让我们深入看看这两个函数都干了啥

这里有个有趣的地方:DT_INITDT_INIT_ARRAY是很多加固方案的脱壳点,因为这是SO被加载后最早执行的地方之一

关于重定位:想象一下,你的SO文件里调用了printf函数,但编译的时候并不知道printf在内存的哪个位置,所以先用个占位符(比如call 0x1234)。重定位就是把这个占位符改成真实的地址

基本流程梳理

发现:Android只读Program Header,而IDA依赖Section Header!这就是为什么很多加固会"抹头"——把Section Header搞坏,IDA就懵了,但Android照样能跑

si = load_library(name)拿到SO信息后,就要开始一系列复杂操作了:

又一个有趣的发现:源码只处理第一个PT_DYNAMIC段!所以你可以加多个动态节来迷惑IDA,因为IDA会全部解析,而Android只看第一个。这算是个小技巧吧~

这部分代码太长了,主要就是初始化各种表:符号表、字符串表、重定位表等等。

说白了,Linker就是个搬运工,把磁盘上的SO文件按照一定的规则搬到内存里,让我们的程序能够调用。主要干这么几件事:

基于对Android Linker的理解,实现自定义Linker无需像AOSP源码那样考虑所有情况。我们可以专注于核心功能,实现一个精简版本

详细实现可参考:自實現Linker加載so

我实现的自定义Linker项目:soLoader

组件关系图

Relationship

核心组件职责

具体实现原理请参考项目源码

以360加固为例,其核心思想是将主SO加密后嵌入壳SO中。壳SO作为loader被原生Linker加载后,负责解密、映射、链接、重定位主SO,最终调用主SO的函数来释放DEX

我们自己在设计的时候可以直接照搬这种模式,但是把两个so合并的操作有些麻烦,而且主SO本身就是被加密的,我个人觉得放在哪里都无所谓,你要想解密都得去分析壳SO,所以这里我实现了一个简化的版本,把主SO加密之后藏在了图片的后面,这样看上去它就是一张普通的图片

为简化实现,本方案采用以下设计:

加密脚本的核心功能:

完整的解密加载流程:

在ElfReader中添加解密功能:

解密工具类的核心实现包括:

这样,自定义Linker就能透明地加载加密的SO文件,实现了简单的SO加固保护

本文分析了Android Linker的工作原理,并基于此实现了自定义Linker和SO加固方案。整个项目涉及很多内存操作和底层知识,还是从中学到了很多东西的OvO。最后附上实现的小demo,希望这篇文章对想了解Linker的同学有所帮助。如果有什么问题或建议,欢迎交流

参考资料

soinfo* si = find_library(name);    // 查找并加载SO到内存
if (si != NULL) {
    si->CallConstructors();          // 调用SO的初始化函数
}
soinfo* si = find_library(name);    // 查找并加载SO到内存
if (si != NULL) {
    si->CallConstructors();          // 调用SO的初始化函数
}
static soinfo* find_library(const char* name) {
  soinfo* si = find_library_internal(name);  // 寻找相应的SO信息
  if (si != NULL) {
    si->ref_count++;   // 这个计数很重要,用来判断SO是否已经加载过
  }
  return si;
}
static soinfo* find_library(const char* name) {
  soinfo* si = find_library_internal(name);  // 寻找相应的SO信息
  if (si != NULL) {
    si->ref_count++;   // 这个计数很重要,用来判断SO是否已经加载过
  }
  return si;
}
// 直接看重点
if (dynamic != NULL) {
    for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
      if (d->d_tag == DT_NEEDED) {    // 先调用依赖库的构造函数
        const char* library_name = strtab + d->d_un.d_val;
        TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);
        find_loaded_library(library_name)->CallConstructors();
      }
    }
}
// DT_INIT要在DT_INIT_ARRAY之前调用
TRACE("\"%s\": calling constructors", name);
CallFunction("DT_INIT", init_func);           // SO文件加壳的脱壳点!划重点!
CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
// 直接看重点
if (dynamic != NULL) {
    for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
      if (d->d_tag == DT_NEEDED) {    // 先调用依赖库的构造函数
        const char* library_name = strtab + d->d_un.d_val;
        TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);
        find_loaded_library(library_name)->CallConstructors();
      }
    }
}
// DT_INIT要在DT_INIT_ARRAY之前调用
TRACE("\"%s\": calling constructors", name);
CallFunction("DT_INIT", init_func);           // SO文件加壳的脱壳点!划重点!
CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
static soinfo* find_library_internal(const char* name) {
  if (name == NULL) {
    return somain;
  }
 
  soinfo* si = find_loaded_library(name);
  if (si != NULL) {
    if (si->flags & FLAG_LINKED) {
      return si;    // 已经加载过了,直接返回
    }
    DL_ERR("OOPS: recursive link to \"%s\"", si->name);
    return NULL;
  }
 
  TRACE("[ '%s' has not been loaded yet.  Locating...]", name);
  si = load_library(name);    // 真正加载SO文件的地方
  if (si == NULL) {
    return NULL;
  }
 
  TRACE("[ init_library base=0x%08x sz=0x%08x name='%s' ]",
        si->base, si->size, si->name);
 
  if (!soinfo_link_image(si)) {    // 执行重定位(会用mmap,所以在/proc/pid/maps能看到)
    munmap(reinterpret_cast<void*>(si->base), si->size);
    soinfo_free(si);
    return NULL;
  }
   
  return si;
}
static soinfo* find_library_internal(const char* name) {
  if (name == NULL) {
    return somain;
  }
 
  soinfo* si = find_loaded_library(name);
  if (si != NULL) {
    if (si->flags & FLAG_LINKED) {
      return si;    // 已经加载过了,直接返回
    }
    DL_ERR("OOPS: recursive link to \"%s\"", si->name);
    return NULL;
  }
 
  TRACE("[ '%s' has not been loaded yet.  Locating...]", name);
  si = load_library(name);    // 真正加载SO文件的地方
  if (si == NULL) {
    return NULL;
  }
 
  TRACE("[ init_library base=0x%08x sz=0x%08x name='%s' ]",
        si->base, si->size, si->name);
 
  if (!soinfo_link_image(si)) {    // 执行重定位(会用mmap,所以在/proc/pid/maps能看到)
    munmap(reinterpret_cast<void*>(si->base), si->size);
    soinfo_free(si);
    return NULL;
  }
   
  return si;
}
static soinfo* load_library(const char* name) {
    // 打开文件
    int fd = open_library(name);
    if (fd == -1) {
        DL_ERR("library \"%s\" not found", name);
        return NULL;
    }
 
    // 读取ELF头并加载段
    ElfReader elf_reader(name, fd);
    if (!elf_reader.Load()) {    // 注意:这里只读Program段!
        return NULL;
    }
     
    const char* bname = strrchr(name, '/');
    // 为SO分配soinfo结构
    soinfo* si = soinfo_alloc(bname ? bname + 1 : name);
    if (si == NULL) {
        return NULL;
    }
     
    // 初始化soinfo对象
    si->base = elf_reader.load_start();
    si->size = elf_reader.load_size();
    si->load_bias = elf_reader.load_bias();
    si->flags = 0;
    si->entry = 0;
    si->dynamic = NULL;
    si->phnum = elf_reader.phdr_count();
    si->phdr = elf_reader.loaded_phdr();
    return si;
}
static soinfo* load_library(const char* name) {
    // 打开文件
    int fd = open_library(name);
    if (fd == -1) {
        DL_ERR("library \"%s\" not found", name);
        return NULL;
    }
 
    // 读取ELF头并加载段
    ElfReader elf_reader(name, fd);
    if (!elf_reader.Load()) {    // 注意:这里只读Program段!
        return NULL;
    }
     
    const char* bname = strrchr(name, '/');
    // 为SO分配soinfo结构
    soinfo* si = soinfo_alloc(bname ? bname + 1 : name);
    if (si == NULL) {
        return NULL;
    }
     
    // 初始化soinfo对象
    si->base = elf_reader.load_start();
    si->size = elf_reader.load_size();
    si->load_bias = elf_reader.load_bias();
    si->flags = 0;
    si->entry = 0;
    si->dynamic = NULL;
    si->phnum = elf_reader.phdr_count();
    si->phdr = elf_reader.loaded_phdr();
    return si;
}
bool ElfReader::Load() {
  return ReadElfHeader() &&          // 读ELF头
         VerifyElfHeader() &&        // 校验ELF头
         ReadProgramHeader() &&      // 读程序头
         ReserveAddressSpace() &&    // 分配内存空间
         LoadSegments() &&           // 加载段(脱壳的好地方)
         FindPhdr();                 // 设置加载地址
}
bool ElfReader::Load() {
  return ReadElfHeader() &&          // 读ELF头
         VerifyElfHeader() &&        // 校验ELF头
         ReadProgramHeader() &&      // 读程序头
         ReserveAddressSpace() &&    // 分配内存空间
         LoadSegments() &&           // 加载段(脱壳的好地方)
         FindPhdr();                 // 设置加载地址
}
phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,
                               int               phdr_count,
                               Elf32_Addr        load_bias,
                               Elf32_Dyn**       dynamic,
                               size_t*           dynamic_count,
                               Elf32_Word*       dynamic_flags)
{
    const Elf32_Phdr* phdr = phdr_table;
    const Elf32_Phdr* phdr_limit = phdr + phdr_count;
 
    for (phdr = phdr_table; phdr < phdr_limit; phdr++) {
        if (phdr->p_type != PT_DYNAMIC) {    // 找DYNAMIC段
            continue;
        }
 
        *dynamic = reinterpret_cast<Elf32_Dyn*>(load_bias + phdr->p_vaddr);
        if (dynamic_count) {
            *dynamic_count = (unsigned)(phdr->p_memsz / 8);
        }
        if (dynamic_flags) {
            *dynamic_flags = phdr->p_flags;
        }
        return;    // 注意:找到第一个就返回了!
    }
    *dynamic = NULL;
    if (dynamic_count) {
        *dynamic_count = 0;
    }
}
phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,
                               int               phdr_count,
                               Elf32_Addr        load_bias,
                               Elf32_Dyn**       dynamic,
                               size_t*           dynamic_count,
                               Elf32_Word*       dynamic_flags)
{
    const Elf32_Phdr* phdr = phdr_table;
    const Elf32_Phdr* phdr_limit = phdr + phdr_count;
 
    for (phdr = phdr_table; phdr < phdr_limit; phdr++) {
        if (phdr->p_type != PT_DYNAMIC) {    // 找DYNAMIC段
            continue;
        }
 
        *dynamic = reinterpret_cast<Elf32_Dyn*>(load_bias + phdr->p_vaddr);
        if (dynamic_count) {
            *dynamic_count = (unsigned)(phdr->p_memsz / 8);
        }
        if (dynamic_flags) {
            *dynamic_flags = phdr->p_flags;
        }
        return;    // 注意:找到第一个就返回了!
    }
    *dynamic = NULL;
    if (dynamic_count) {
        *dynamic_count = 0;
    }
}
// 加载依赖库 (DT_NEEDED)
for (Elf32_Dyn* d = si->dynamic; d->d_tag != DT_NULL; ++d) {
    if (d->d_tag == DT_NEEDED) {
        const char* library_name = si->strtab + d->d_un.d_val;
        DEBUG("%s needs %s", si->name, library_name);
        soinfo* lsi = find_library(library_name);    // 递归加载依赖
        if (lsi == NULL) {
            strlcpy(tmp_err_buf, linker_get_error_buffer(), sizeof(tmp_err_buf));
            DL_ERR("could not load library \"%s\" needed by \"%s\"; caused by %s",
                   library_name, si->name, tmp_err_buf);
            return false;
        }
        *pneeded++ = lsi;
    }
}
// 加载依赖库 (DT_NEEDED)
for (Elf32_Dyn* d = si->dynamic; d->d_tag != DT_NULL; ++d) {
    if (d->d_tag == DT_NEEDED) {
        const char* library_name = si->strtab + d->d_un.d_val;
        DEBUG("%s needs %s", si->name, library_name);
        soinfo* lsi = find_library(library_name);    // 递归加载依赖
        if (lsi == NULL) {
            strlcpy(tmp_err_buf, linker_get_error_buffer(), sizeof(tmp_err_buf));
            DL_ERR("could not load library \"%s\" needed by \"%s\"; caused by %s",
                   library_name, si->name, tmp_err_buf);
            return false;
        }
        *pneeded++ = lsi;
    }
}
if (si->has_text_relocations) {
    // 让代码段可写,这样才能修改
    DL_WARN("%s has text relocations. This is wasting memory and is "
            "a security risk. Please fix.", si->name);
    if (phdr_table_unprotect_segments(si->phdr, si->phnum, si->load_bias) < 0) {
        DL_ERR("can't unprotect loadable segments for \"%s\": %s",
               si->name, strerror(errno));
        return false;
    }
}
 
if (si->plt_rel != NULL) {
    DEBUG("[ relocating %s plt ]", si->name );
    if (soinfo_relocate(si, si->plt_rel, si->plt_rel_count, needed)) {
        return false;
    }
}
if (si->rel != NULL) {
    DEBUG("[ relocating %s ]", si->name );
    if (soinfo_relocate(si, si->rel, si->rel_count, needed)) {
        return false;
    }
}
if (si->has_text_relocations) {
    // 让代码段可写,这样才能修改
    DL_WARN("%s has text relocations. This is wasting memory and is "
            "a security risk. Please fix.", si->name);
    if (phdr_table_unprotect_segments(si->phdr, si->phnum, si->load_bias) < 0) {
        DL_ERR("can't unprotect loadable segments for \"%s\": %s",
               si->name, strerror(errno));
        return false;
    }
}
 
if (si->plt_rel != NULL) {
    DEBUG("[ relocating %s plt ]", si->name );
    if (soinfo_relocate(si, si->plt_rel, si->plt_rel_count, needed)) {
        return false;
    }
}
if (si->rel != NULL) {
    DEBUG("[ relocating %s ]", si->name );
    if (soinfo_relocate(si, si->rel, si->rel_count, needed)) {
        return false;

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 44
支持
分享
最新回复 (17)
雪    币: 423
活跃值: (1966)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
兼容性如何
2天前
0
雪    币: 90
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
6666
2天前
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2天前
0
雪    币: 2252
活跃值: (2684)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
过来瞧瞧
2天前
0
雪    币: 13
活跃值: (215)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢分享
1天前
0
雪    币: 104
活跃值: (5701)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
666
1天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
666
1天前
0
雪    币: 13
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
666
1天前
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
666
1天前
0
雪    币: 1218
活跃值: (927)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
666
1天前
0
雪    币: 144
活跃值: (688)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
666
1天前
0
雪    币: 409
活跃值: (999)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
学习
9小时前
0
雪    币: 102
活跃值: (2785)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
mark
8小时前
0
雪    币: 5813
活跃值: (4859)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
学习mark
8小时前
0
雪    币: 0
活跃值: (235)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
mark
3小时前
1
雪    币: 1498
活跃值: (2533)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
17
666
1小时前
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
666
47分钟前
0
游客
登录 | 注册 方可回帖
返回