最近研究了一些防内存检测方面的姿势, 异常so模块检测在对抗中占了挺重要的部分. 由于`Bionic/linker`完全是一个用户态的实现, 所以就想着对linker的代码做一番改造, 实现一个自己的so loader, 目的是隐藏so模块,躲过检测.
目前遍历进程内已加载模块的方法大概有两个:
遍历soinfo->next(本身为一个环形链表).
读取/proc/pid/maps.
涉及的核心逻辑主要在: linker.cpp与linker_phdr.cpp两个文件中, linker_phdr.cpp负责elf内存的映射与读取, linker.cpp主要负责对数据进行展开和初始化.
linker里面涉及的引用比较多,弄了很久才剥离出来,最后修修补补,提取出了以下几个必须的文件:
elf_machdep.h
exec_elf.h
linker.cpp
linker.h
linker_phdr.cpp
linker_phdr.h
在linker中是不允许调用malloc,free等函数的. 这是因为linker作为第一个被加载的模块(早于libc.so),所以可用资源方面比较严苛, 从代码中的一段注释可以得知原因:
虽然上面列举了linker中的诸多限制, 但是这仅仅是对于原生linker而言. 而我们自己改造的linker在使用时周围的资源已经是非常丰富了, libc等各种库基本都已经加载完毕. 所以可以放开手脚的干一番. 废话不多, 下面开始干活.
第一步: 改造各种输出宏定义.
linker中有很多不同种类的调试信息输出宏, 我并不想一次性删除, 因为这些输出对后面的调错还有很大帮助, 毕竟linker里的逻辑还是比较复杂的. 我全部都用__android_log_print():
第二步: 替换DT_NEED的依赖加载
在static bool soinfo_link_image(soinfo* si)函数的后半段,有一处代码是遍历DT_NEED并加载依赖库的代码,这里将代码直接替换成dlopen(), 调用原生linker来加载依赖库.
}
修改后为:
}
第三步: 去除soinfo链.
该链作为原生linker中的一条很关键的结构, 每个加载的soinfo都会被串到这条链上来. 去除soinfo链后我们加载的模块就无法被通过遍历soinfo链表检测到.
先删除掉以下变量定义, 编译一把使引用部位报错, 然后一点点清理.
一处关键的引用在函数soinfo_alloc中, 这里用于分配新的soinfo空间:
由于我们剥离了全局的soinfo链, 所以这里清理掉相关代码, 直接用malloc代替, 修改后的soinfo_alloc如下:
第四步: 从/proc/pid/maps中隐藏map信息
很多的对抗行为都发生在/proc/pid/目录下, 对于模块检测来说maps一定是非常重要的检测点. 所以下面我们修改代码, 不让模块信息出现在maps中.
首先理清原理: 之所以能在maps文件中看到模块信息, 是因为linker在加载so时使用mmap(xx, xx, xx, xx, fd_, xx)映射了so文件的fd(文件描述符). 这个操作会被内核记录下来体现在maps中:
下面我们必须改变直接映射fd的方式, 并且还不影响原有功能, 思路为: 先mmap一块无fd内存, 然后使用read读出so指定偏移内容填充到mmap的内存处, 最后将mmap的内存属性设置为segment的属性. 代码如下:
具体思路如上, nexus 5+ 4.4.4下测试通过. 对linker了解不深, 如果有偏差和错误还请大家指正. 附上代码:~
#define DL_WARN(fmt, x...) __android_log_print(ANDROID_LOG_DEBUG, "linker_WARN", fmt, x);
#define DL_TRACE(fmt, x...) __android_log_print(ANDROID_LOG_DEBUG, "linker_TRACE", fmt, x);
#define TRACE_TYPE(x, y...) __android_log_print(ANDROID_LOG_DEBUG, "linker_TRACE_TYPE", y);
#define DEBUG(x...) __android_log_print(ANDROID_LOG_DEBUG, "linker_DEBUG", x);
#define INFO(x...) __android_log_print(ANDROID_LOG_DEBUG, "linker_INFO", x);
#define LOOKUP 1 // 某个宏...作用忘了#define __libc_format_buffer(b, s, f, p...) sprintf(b, f, p); // 这里用sprintf代替, 否则就要把libc里面的一大堆文件剥离出来, 不建议入坑.
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();
}
}
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);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)