The elf in ELF use 0-day to cheat all disassemblers david942j @ CyberSEC 2019 1 . 1
This talk Tricks to cheat disassemblers objdump, readelf, IDA Pro, etc. 2 . 1
IDA Pro The best tool for reverse-engineering Take it as examples in this talk 2 . 2
anti-reverse-engineering - What you see is NOT what it really is IDA Pro 2 . 3
Introduction to ELF 3 . 1
ELF Executable and Linkable Format Linux 3 . 2
Header ELF header Section header (not important here) Program header 3 . 3
3 . 4
ELF header ELF class: 32 / 64-bit arch: x86 / ARM / MIPS .. section / program header 3 . 5
Section header (static linker) ELF .text , .rodata , etc. 3 . 6
Program header Needed Libraries, Segment Permissions, etc. _DYNAMIC table 3 . 7
_DYNAMIC 3 . 8
Example #include <stdio.h> #include <iostream> using std::cout; int main() { char s[100] = {}; scanf("%99s", s); // libc.so.6#scanf cout << "Hello, " << s << "!\n"; // libstdc++.so.6#std::cout return 0; } 3 . 9
_DYNAMIC Need libraries: libc.so.6, libstdc++.so.6 Need functions: scanf & std::cout ld.so _DYNAMIC function 3 . 10
In this talk IDA Pro _DYNAMIC table e.g. IDA Pro printf system 0-day bug in Linux kernel Bug(?) in ld.so 3 . 11
The Linux 0-day bug 4 . 1
PT_LOAD 4 . 2
PT_LOAD Program header PT_LOAD entry ELF memory 4 . 3
PT_LOAD 4 . 4
Memory mapping ELF file In memory 0x0 0x400000 ELF header program header many tables.. executable executable code .rodata .eh_frame 0x400e08 0xe08 ... .init_array/.fini_array 0x600000 .dynamic 0x600e08 .data/.bss data 4 . 5
Linux#execve 4 . 6
linux/fs/binfmt_elf.c#load_elf_binary Read and check ELF header Parse program header PT_INTERP PT_LOAD PT_GNU_STACK Setup AUXV 4 . 7
AUXV AUXiliary Vector interpreter(ld.so) AT_PHDR AT_ENTRY AT_UID ... 4 . 8
Flow of execve execve("a.out", ... ) load_elf_binary mmap(PT_LOADs) kernel space load_elf_interp (ld.so) create_elf_tables (AUXV) *phdr, phnum, *entry, *auxv ld.so#dl_main load_libraries elf_dynamic_do_rela (relocation) 4 . 9
Bug Kernel AT_PHDR 4 . 10
binfmt_elf.c#create_elf_tables 4 . 11
Normally load_addr exec->e_phoff 0x400000 0x40 0x400040 4 . 12
load_addr is The �rst LOADed address 4 . 13
ELF file In memory 0x0 0x400000 ELF header program header many tables.. executable executable code .rodata .eh_frame 0x400e08 0xe08 ... .init_array/.fini_array 0x600000 .dynamic 0x600e08 .data/.bss data 4 . 14
Nobody promises PHDR is located in the �rst PT_LOAD 4 . 15
Put PHDR in the second PT_LOAD 4 . 16
ELF file In memory 0x0 0x400000 ELF header load_addr many tables.. executable code .eh_frame ... .init_array/.fini_array 0x4000 fake program header 0x604000 fake prog. hdr e_phoff .data .data 0x204000 program header 0x804000 program header 4 . 17
Effect Kernel loads binary correctly But kernel cheats ld.so the address of PHDR 4 . 18
ld.so 4 . 19
ld.so ? Load shared libraries Process dynamic relocation 4 . 20
_DYNAMIC 4 . 21
function e.g. printf -> system 4 . 22
4 . 23
Relocation type 1: scanf scanf@got type 2: put backdoor on scanf@got 4 . 24
relocation table IDA scanf relocate 4 . 25
lea rdi,[rip+0xba] int ret = scanf(args); mov eax,0x0 if ( trigger (args)) call 5f0 <scanf@plt> backdoor (); lea rdx,[rbp0xe0] return ret; lea rax,[rbp0x70] 4 . 26
Demo 4 . 27
Let's play ld.so 5 . 1
PT_PHDR in Program header 5 . 2
PT_PHDR points to itself ELF file ELF header program header PT_PHDR PT_LOAD PT_LOAD ... 5 . 3
glibc/elf/rtld.c#1147 for (ph = phdr; ph < &phdr[phnum]; ++ph) switch (ph->p_type) { case PT_PHDR: /* Find out the load address. */ main_map->l_addr = phdr - ph->p_vaddr; break; case PT_DYNAMIC: /* This tells us where to find the dynamic section, which tells us everything we need to do. */ main_map->l_ld = main_map->l_addr + ph->p_vaddr; break; 5 . 4
PT_PHDR ld.so binary ! 5 . 5
the Linux kernel bug Program header for kernel for ld.so 5 . 6
? ld.so binary 5 . 7
program header PT_PHDR main_map->l_addr = phdr - ph->p_vaddr PT_LOAD PT_LOAD PT_DYNAMIC main_map->l_ld = main_map->l_addr + ph->p_vaddr ... 5 . 8
Use two PT_PHDR s 5 . 9
glibc/elf/rtld.c#1147 for (ph = phdr; ph < &phdr[phnum]; ++ph) switch (ph->p_type) { case PT_PHDR: /* Find out the load address. */ main_map->l_addr = phdr - ph->p_vaddr; break; case PT_DYNAMIC: /* This tells us where to find the dynamic section, which tells us everything we need to do. */ main_map->l_ld = main_map->l_addr + ph->p_vaddr; break; 5 . 10
Two PT_PHDR s PT_PHDR main_map->l_addr = phdr - ph->p_vaddr PT_DYNAMIC main_map->l_ld = main_map->l_addr + ph->p_vaddr PT_PHDR main_map->l_addr = phdr - ph->p_vaddr PT_LOAD PT_LOAD ... 5 . 11
_DYNAMIC relocation 5 . 12
Demo Given two ELFs Looks like A in IDA Pro but actually B 5 . 13
Conclusion 6 . 1
The Linux kernel 0-day bug Kernel calculates PHDR incorrectly ld.so gets wrong address of program header 6 . 2
ld.so ld.so using PT_PHDR for calculating base address Nobody checks correctness of PT_PHDR 6 . 3
_DYNAMIC table 6 . 4
david942j @ 7 . 1
Recommend
More recommend