目的
elf文件静态分析的对抗。
构想
我们想要的效果:
目标程序被获取到时是被加密的,在运行时完成文件的解密,再将程序流交还给目标程序。
如何达到这个效果,就要借助ELF文件结构中两个特殊节INIT , INIT_ARRAY 。
INIT , INIT_ARRAY 都是会在elf文件被加载时最先被调用的函数。
假如我们在elf做加密的时候,避开对init和init_array 造成影响的部分,就可以实现elf加载完成后调用init或者init_array 完成elf文件的解密工作。
但是每一个目标文件,都被插入解密函数的话,实现起来简单,但是还不够优雅。
所以我们将解密函数提取成一个共享库,每一个目标文件的init段代码仅需要调用共享库的解密函数即可,节约了体积又增加了可维护性。
但是又需要保证这个共享库会被目标elf文件前被加载,
只需要在dynamic中添加一条neededso的记录即可。
实现流程
![](http://pareto.fun/wp-content/uploads/2021/11/elf解密流程.drawio.png)
其余部分都很好实现,还有个小问题在init 调用外部解密函数。
调用外部函数有两种方式
- 隐式调用,linker解析外部函数。
- 显式调用,走dlopen 、dlsym主动调用外部函数。
隐式调用
#include "library.h"
#include <iostream>
void func();
void hello() {
func();
std::cout << "Hello, World!" << std::endl;
}
将这段代码编译成共享库编译能通过吗?那编译成可执行程序呢?
why ?
共享库 能通过, 可执行程序 不能通过。一般人都会说func没有被实现,答案正确但是不够细节,准确点说,包含该共享库和其他运行时库都未导出该函数所以导致的不通过。
再看一段
共享库代码
头文件
#ifndef UNTITLED4_LIBRARY_H
#define UNTITLED4_LIBRARY_H
# define __nonnull(params) __attribute__ ((__nonnull__ params))
extern void hello();
extern void *dlsym (void *__restrict __handle,
const char *__restrict __name) __nonnull ((2));
#endif //UNTITLED4_LIBRARY_H
.c
#include "library.h"
#include <stdio.h>
void hello() {
dlsym(-1 , "printf");
printf("Hello, World!");
}
可执行程序
#include <stdio.h>
#include "lib/library.h"
int main() {
printf("HelloWorld");
hello();
return 0;
}
可正常运行。libraray 被编译器编译成so 时,dlsym 由于没有被实现会被标记为UND(undefine),表示函数的实现没有在这个elf文件实现。linker在加载so时,会将尝试在当前ELF文件和依赖的运行时库去找该符号的实现(这个过程也叫重定位,但是这个功能只是重定位之一)。但是和执行程序一起运行时,可执行程序依赖libdl ,其中又有dlsym的实现,linker遍历运行时库时,将library 的dlsym指向了libdl中的dlsym,所以可正常运行。
那么要想完成隐式调用需要哪些东西,最重要的东西搞定重定位表,然后使用重定位修复后的地址(这里这个地址也叫GOT)即可。
最小的添加单元:
- 新增got 表。
- 新增重定位表和对应的符号表。
显式调用
通过dlopen ,dlsym 去调用某个elf文件的导出函数。
这就很简单,很多情况下这两个函数都会被导入,可以选择写代码调用到对应的plt 或者got表即可。
0 条评论