0

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...

clockw0rk
  • 576
  • 5
  • 26
  • `push 0x1080` - the 0x1080 appears to be an offset from where the image starts in memory, as opposed to an absolute virtual address. – 500 - Internal Server Error Oct 02 '19 at 12:19
  • I thought so, too, at first glance it appears really low. But why would readelf and also objdump point to this as the entry point? I would not assume an error in readelf tho – clockw0rk Oct 02 '19 at 12:26
  • 1
    Binary dump utilities have no way of knowing the actual run-time load address of a module, so addresses in dumps are generally offsets from the actual load address. The linker emits an entry into the relocation table for addresses, which allows the loader to fix up the address when the module is loaded. – 500 - Internal Server Error Oct 02 '19 at 12:30
  • Yes, ofc. But: Should'nt the interpreter translate the reference to the code instruction correctly? I mean it is information for the Instruction pointer, right? Maybe I got that wrong, so please correct me, but calls and jumps are normally pointing to in-file adresses rather than in-memory, aren't they? – clockw0rk Oct 02 '19 at 12:46
  • 1
    @clockw0rk why would interpreter know that this `push 0x1080` is for the Entry Point or any address at all? – Paweł Łukasik Oct 02 '19 at 18:19
  • 2
    @500-InternalServerError: in a non-PIE executable, the ELF headers *do* specify the run-time load address. This is what makes executables position *dependent*. See also [32-bit absolute addresses no longer allowed in x86-64 Linux?](//stackoverflow.com/q/43367427). But yes, in a PIE the image base is `0` when disassembling, unless you use objdump options to set it. – Peter Cordes Oct 03 '19 at 03:26

1 Answers1

3

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.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • This is important information! I will try it asap and report back. Would you mind telling us why the need for "position-independent" executables arose... – clockw0rk Oct 04 '19 at 10:59
  • "why the need ..." -- to prevent "known address" attacks, i.e. exactly the attack you are attempting. – Employed Russian Oct 04 '19 at 14:08
  • It worked! I was so frustrated. Thank you very much, mister employed russian! Could you, by chance, link something that explains how to calculate the entry point for pies too? – clockw0rk Oct 06 '19 at 20:02