在研究360加固方案时,我发现其采用了一种独特的保护机制:在主SO文件内部嵌套了另一个SO文件,相当于为主SO添加了一层保护壳。这种技术实现引起了我的兴趣,经过深入研究,整理出本文,分享自定义Linker的实现原理及SO加固技术
看起来很简单对吧,让我们深入看看这两个函数都干了啥
这里有个有趣的地方:DT_INIT
和DT_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
组件关系图:

核心组件职责:
具体实现原理请参考项目源码
以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);
if
(si != NULL) {
si->CallConstructors();
}
soinfo* si = find_library(name);
if
(si != NULL) {
si->CallConstructors();
}
static
soinfo* find_library(
const
char
* name) {
soinfo* si = find_library_internal(name);
if
(si != NULL) {
si->ref_count++;
}
return
si;
}
static
soinfo* find_library(
const
char
* name) {
soinfo* si = find_library_internal(name);
if
(si != NULL) {
si->ref_count++;
}
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();
}
}
}
TRACE(
"\"%s\": calling constructors"
, name);
CallFunction(
"DT_INIT"
, init_func);
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();
}
}
}
TRACE(
"\"%s\": calling constructors"
, name);
CallFunction(
"DT_INIT"
, init_func);
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);
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)) {
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);
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)) {
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;
}
ElfReader elf_reader(name, fd);
if
(!elf_reader.Load()) {
return
NULL;
}
const
char
* bname =
strrchr
(name,
'/'
);
soinfo* si = soinfo_alloc(bname ? bname + 1 : name);
if
(si == NULL) {
return
NULL;
}
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;
}
ElfReader elf_reader(name, fd);
if
(!elf_reader.Load()) {
return
NULL;
}
const
char
* bname =
strrchr
(name,
'/'
);
soinfo* si = soinfo_alloc(bname ? bname + 1 : name);
if
(si == NULL) {
return
NULL;
}
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() &&
VerifyElfHeader() &&
ReadProgramHeader() &&
ReserveAddressSpace() &&
LoadSegments() &&
FindPhdr();
}
bool
ElfReader::Load() {
return
ReadElfHeader() &&
VerifyElfHeader() &&
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) {
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) {
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;
}
}
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;
}
}
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直播授课