I'm trying to implement an example of an ELF .text infection as described in "Learning Linux Binary Analysis". I'm currently having a problem where I successfully injected my data into the .text section, readelf displays no errors but the program terminates during startup.
My process so far is:
- Increased sh_size of the .text section header by the size of the data I want to inject.
- Shifted all sections after the .text section by the size of the injected data (Modifying sh_offset and sh_addr in section headers).
- Find the program header where the .text section resides by checking p_type, p_flags, and p_offset; then increasing p_filesz and p_memsz by the size of injected data.
- For all other program headers afterwards, add the size of the injected data to p_offset.
The entry point to the file has not been modified.
When I try to debug the resulting file in radare2, it outputs:
[0x7fd315665007]> aa [Cannot find function 'entry0' at 0xffffffffff600530(aa) [x] Analyze all flags starting with sym. and entry0 (aa) [0x7fd315665007]>
If I list the number of functions I see a lot are missing:
[0x7f05c7bff007]> afl 0xffffffffff600000 1 10 sym.imp..6 0xffffffffff600800 1 10 fcn.ffffffffff600800 [0x7f05c7bff007]>
Compare to the original file:
[0x7f3700c6dea0]> afl 0x55daca36a000 3 73 -> 75 sym.imp.__libc_start_main 0x55daca36a4e8 3 23 sym._init 0x55daca36a510 1 6 sym.imp.puts 0x55daca36a520 1 6 fcn.55daca36a520 0x55daca36a530 1 43 entry0 0x55daca36a560 4 50 -> 40 sym.deregister_tm_clones 0x55daca36a5a0 4 66 -> 57 sym.register_tm_clones 0x55daca36a5f0 4 49 sym.__do_global_dtors_aux 0x55daca36a630 1 10 entry1.init 0x55daca36a63a 1 34 sym.main 0x55daca36a660 4 101 sym.__libc_csu_init 0x55daca36a6d0 1 2 sym.__libc_csu_fini 0x55daca36a6d4 1 9 sym._fini 0x55daca56afe0 1 1020 reloc.__libc_start_main [0x7f3700c6dea0]>
Listing the section headers using readelf also show that some sections are missing:
Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .dynamic .got .data 04 05 .note.gnu.build-id 06 07 08 .init_array .fini_array .dynamic
Compared to the original file:
Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .dynamic .got .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .dynamic .got
I think there's a problem in the way the section headers are being moved, but I haven't found the problem.
UPDATE I found a bug in the process which would edit a section/program header offset even if it was after the .text section. After fixing this, radare2 was able to find all functions including entry0. However, one function's name is different:
0x00000510 1 6 sym.imp.puts
0x00000510 1 6 fcn.00000510
this is clearly a call to the puts() functions. Maybe I need to modify an offset here as well.
The missing sections problem still happens even with the fix.
UPDATE 2 I found that the p_vaddr value of a program header also should be increased along with p_offset. This fixed the problem where sections would be missing from readelf -l. The resulting program will now successfully start but hits a segmentation fault at runtime.
UPDATE 3 I added procedures to update the .rel* sections by increasing the offsets of each rel by the virus size. This makes r2 list all of the function names correctly. The executable starts, but hits another segmentation fault at runtime.