I am currently following a book by Dennis Andriesse about binary disassembling and instrumentation.
In a later chapter, we have written an injector for ELF type files. The injector places a code section at location 0x80000 [+offset %16], overwriting the .note.ABI-tag section (which contains only information unrelated to execution, so it's safe to overwrite it).
The program I am modding is a simple hello_username program, like this:
#include <iostream>
using namespace std;
main()
{
cout << "hello Lucky" << endl;
return 0;
}
The code I am placing there looks like this:
BITS 64
section .text
global main
main:
push rax
push rcx
push rdx
push rsi
push rdi
push r11
mov rax, 0x1 ;syscall to print (sys_write)
mov rdi, 0x1 ;stdout
lea rsi, [rel $+rankle-$] ;offset to prank
mov rdx, [rel $+size-$] ;length of prank
syscall
pop r11
pop rdi
pop rsi
pop rdx
pop rcx
pop rax
push 0x1080 ;find "entry_point" using readelf program
ret
rankle : db "Yikes! U got pranked!", 0
size : dd 22
Although I believe we need not push rcx and r11, the code is easy and straightforward, I compile it with
nasm -f bin -o pranked.bin hi_there.asm
After that, I inject it with the program we wrote, and besides injecting the section, I also overide the entry point. Here is the readelf output before the operation:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1080
Start of program headers: 64 (bytes into file)
Start of section headers: 15344 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 11
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 000002a8
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.build-i NOTE 00000000000002c4 000002c4
0000000000000024 0000000000000000 A 0 0 4
[ 3] .note.ABI-tag NOTE 00000000000002e8 000002e8
0000000000000020 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000308 00000308
0000000000000028 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000000330 00000330
0000000000000138 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000468 00000468
0000000000000163 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 00000000000005cc 000005cc
000000000000001a 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000000005e8 000005e8
0000000000000040 0000000000000000 A 6 2 8
[ 9] .rela.dyn RELA 0000000000000628 00000628
0000000000000120 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000000748 00000748
0000000000000060 0000000000000018 AI 5 23 8
[11] .init PROGBITS 0000000000001000 00001000
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000001020 00001020
0000000000000050 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000001070 00001070
0000000000000008 0000000000000008 AX 0 0 8
[14] .text PROGBITS 0000000000001080 00001080
00000000000001e1 0000000000000000 AX 0 0 16
[ .... ]
You can see the entry point is 0x1080, this is where the .text section begins. (The _start function is at this place, checked it out with objdump)
Now, after the injection took place, the same output:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x80370
Start of program headers: 64 (bytes into file)
Start of section headers: 15344 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 11
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 000002a8
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.build-i NOTE 00000000000002c4 000002c4
0000000000000024 0000000000000000 A 0 0 4
[ 3] .init PROGBITS 0000000000001000 00001000
0000000000000017 0000000000000000 AX 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000308 00000308
0000000000000028 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000000330 00000330
0000000000000138 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000468 00000468
0000000000000163 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 00000000000005cc 000005cc
000000000000001a 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000000005e8 000005e8
0000000000000040 0000000000000000 A 6 2 8
[ 9] .rela.dyn RELA 0000000000000628 00000628
0000000000000120 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000000748 00000748
0000000000000060 0000000000000018 AI 5 23 8
[11] .plt PROGBITS 0000000000001020 00001020
0000000000000050 0000000000000010 AX 0 0 16
[12] .plt.got PROGBITS 0000000000001070 00001070
0000000000000008 0000000000000008 AX 0 0 8
[13] .text PROGBITS 0000000000001080 00001080
00000000000001e1 0000000000000000 AX 0 0 16
[...]
[25] .bss NOBITS 0000000000004060 00003048
0000000000000118 0000000000000000 WA 0 0 32
[26] .pranked PROGBITS 0000000000080370 00004370
000000000000004a 0000000000000000 AX 0 0 16
[27] .symtab SYMTAB 0000000000000000 00003070
00000000000006f0 0000000000000018 28 49 8
[28] .strtab STRTAB 0000000000000000 00003760
0000000000000382 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 00003ae2
0000000000000107 0000000000000000 0 0 1
You can see that the new section is there, beginning at 0x80370, and the new entry point in the ELF header is pointing to that (double checked it with a hex editor).
So, now the problems start:
If I try to execute the program, it correctly prints out "Yikes!You got pranked!\" immediately followed by SEGMENTATION FAULT.
This is driving me nuts since I am fiddeling around with it for over a week now! Unfortunately, gdb would not be of much help here, but at least I could grab the stack frame before the SIGSEV:
#0 0x0000000000001080 in ?? ()
(gdb) info f
Stack level 0, frame at 0x7fffb20db438:
rip = 0x1080; saved rip = 0x1
called by frame at 0x7fffb20db440
Arglist at 0x7fffb20db428, args:
Locals at 0x7fffb20db428, Previous frame's sp is 0x7fffb20db438
Saved registers:
rip at 0x7fffb20db430
And that is where I am stuck. Why does it say 1080 in ?? (). My calculation of the entry point should also be correct, I guess. And the rip is pointing to the _start function, too....
How can I get this to jump to the correct entry point again? I just want to write a mod for a hello world program, this should not be too much of black magic...
My calculation of the entry point should also be correct, I guess. And the rip is pointing to the _start function,
There is no way that _start
is located at 0x1080
(at least not on Linux).
If readelf
tells you that that's the address of _start
, then you have a position-independent executable.
PIE executables didn't exist until a few years ago, so the book probably doesn't mention you need a non-PIE one (since at the time of writing non-PIE binaries were all that you could build/run).
To build a non-PIE binary, use g++ -fno-pie -no-pie hello.cc
. After doing that, the rest of code injection should work like it does in the book.