目的

elf文件静态分析的对抗。

构想

我们想要的效果:

目标程序被获取到时是被加密的,在运行时完成文件的解密,再将程序流交还给目标程序。

如何达到这个效果,就要借助ELF文件结构中两个特殊节INIT , INIT_ARRAY 。

INIT , INIT_ARRAY 都是会在elf文件被加载时最先被调用的函数。

假如我们在elf做加密的时候,避开对init和init_array 造成影响的部分,就可以实现elf加载完成后调用init或者init_array 完成elf文件的解密工作。

但是每一个目标文件,都被插入解密函数的话,实现起来简单,但是还不够优雅。

所以我们将解密函数提取成一个共享库,每一个目标文件的init段代码仅需要调用共享库的解密函数即可,节约了体积又增加了可维护性。

但是又需要保证这个共享库会被目标elf文件前被加载,

只需要在dynamic中添加一条neededso的记录即可。

实现流程

其余部分都很好实现,还有个小问题在init 调用外部解密函数。

调用外部函数有两种方式

  1. 隐式调用,linker解析外部函数。
  2. 显式调用,走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)即可。

最小的添加单元:

  1. 新增got 表。
  2. 新增重定位表和对应的符号表。

显式调用

通过dlopen ,dlsym 去调用某个elf文件的导出函数。

这就很简单,很多情况下这两个函数都会被导入,可以选择写代码调用到对应的plt 或者got表即可。

分类: ELF

pareto

未来什么方向不管,先做自己喜欢做的事情。

0 条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注