这几天也要尝试出题搞事。。于是尝试研究一下elf加密的原理吧
ELF段加密研究 – ELF文件格式
背景
之前做ctf之类逆向的题目的时候,总是有一种题目是这样的:
这个题目怪得很,没有main,但是实际上再运行的时候,会进入.init段,然后突然把某个段进行了异或,然后就会得到答案。我对这个过程很疑惑。。。所以打算好好研究一下这点
节(Section)的定义
一般一个正常的程序节中,会有.text(代码节),data(数据节),.bss(未定义数据节)等等。但是为了提供更加自由的定制化,gcc其实提供了给我们定制节的功能;
__attribute__((section (".sectionname")))
通过使用这个指令,能够将我们指定的一段内容放到一个我们自己定义的节中,如:
static int __attribute__((section (".loading"))) func(char* input){
这段内容能够把我们的func放入到.loading节里面,我们编译之后用readelf查看:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[13] .text PROGBITS 00000000004005f0 000005f0 0000000000000622 0000000000000000 AX 0 0 16
[14] .loading PROGBITS 0000000000400c12 00000c12 0000000000000167 0000000000000000 AX 0 0 1
从这可以看到,我们定义的.loading节被放到.text的后方。利用这个特点,我们就能够分离处指定的函数,然后可以对特定的节进行加密(本人主要还是担心如果直接对.text下手会不会出问题)
与段(Segment)的区别
注意一个特点,这个节(Section)和我们平时说的段(Segment)是不一样的。节是在程序编译之后存在的,而段是在程序载入主程序之后发生的。
从上图可以看出,程序linking阶段(也就是编译后,链接时),elf文件是以节(section)划分,而程序开始执行后,是以段(Segment)来划分的。这里要进行区别。
之所以这么做,因为节中包含的很多帮助信息,比如说重定向的信息,链接的信息,以及调试信息等。这些信息在我们调试程序的时候会有用,但是在我们真正发布运行一个程序额度时候,是不需要都用上的,所以载入的时候,会选择性的抛弃一些数据。
操作系统会从程序的ph_table(program header table)中拷贝文件的段到虚拟地址段中,甚至利用这点创建内存共享资源
ELF文件格式
扯到加密,我们这边就要考虑到使用Elf.h文件,也就是对ELF本身直接进行操作,所以这里记录一波学习过程。
ElfN_Addr Unsigned program address, uintN_t
ElfN_Off Unsigned file offset, uintN_t
ElfN_Section Unsigned section index, uint16_t
ElfN_Versym Unsigned version symbol information, uint16_t
Elf_Byte unsigned char
ElfN_Half uint16_t
ElfN_Sword int32_t
ElfN_Word uint32_t
ElfN_Sxword int64_t
ElfN_Xword uint64_t
上面是官方对之后结构体的定义文档(这里可以看出,大佬写的代码是用来看的),其中N的意思是说,32bit中N为32,64bit中为64。知道这个方便我们接下来的分析
ElfN_Ehdr:
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
ElfN_Addr e_entry; // 函数虚拟地址入口
ElfN_Off e_phoff; // program header 偏移量(可选,不一定存在)
ElfN_Off e_shoff; // section header 偏移量
uint32_t e_flags;
uint16_t e_ehsize; // elf头部大小
uint16_t e_phentsize; // 每个 program entries的大小
uint16_t e_phnum; // program header 的数量
uint16_t e_shentsize; // 每个 sections entries的大小
uint16_t e_shnum; // sections header的数量
uint16_t e_shstrndx; // 记录了sections header table 的地址
} ElfN_Ehdr;
这个是对整个ELF本身的定义,我们这里关注section header,就是从elf entry结束开始经过e_shoff个字节能够到达的节。同时,这个e_shentsize大小就是section header entry 的大小也就是说,我们从读入的位置开始移动e_shdoff + e_shentsize*e_shstrndx就能够到达【记录有sections的strndx】。通过这个段我们能够快速的定位到我们需要的段上,代码如下:
1 | elf = (Elf64_Ehdr*)buf; |
但是。。。得到了段之后要怎么处理呢。。。这里就暂时不知道了。。
(对于混淆的问题,后来暂时用花指令处理掉了)
__asm__ __volatile__(
"lea label, %rax\t\n"\
"push %rax\t\n"\
"ret\t\n"\
"label:"
);
为了防止IDA的检测,编译的时候选择了一下: -fvisibility=hidden 还strip了一下符号表:
strip file
嗯。。。暂时是有一个防护措施了。