1

I am a newbie in this, and I am trying to add a program header to a simple ELF64 "Hello World" program on Ubuntu. All in changing the binary data of the ELF.

I went to the end of the program headers (Start of program headers + (Size of program headers * Number of program headers)) and inserted a 56 byte length program header of a valid address in the file. I then increased the Number of programs headers by 1, and increased the Entry point address and the Start of section headers by 56.

Is there anything I am still missing? I get errors while trying to run readelf on my modified ELF and it clearly shows that I messed something up.

I get many errors when using -a (most of them is: readelf: Error: Reading 16 bytes extends past end of file for version need aux (3)). When I am trying to run my modified ELF, it gives me segmentation fault at the beginning:

>strace ./helloWorld-modified
execve("./helloWorld-modified", ["./helloWorld-modified"], 0x7fffbb83ddb0 /* 25 vars */) = -1 EINVAL (Invalid argument)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=NULL} ---
+++ killed by SIGSEGV +++
Segmentation fault

Please tell if I need to add more context here. I am adding the original readelf -h:

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:               0x530
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6448 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28

This is the program header data I added:

  LOAD           0x00000000000020a8 0x0000000000010000 0x0000000000000000
                 0x0000000000000020 0x0000000000000020  R      0x0

1 Answer 1

1

I'm by no means an expert in this area, but in non-PIE ELFs I believe there can be header entries near the end of the binary that are easy to mess up that way, on top of all the other data offsets you're shifting.

Whatever you're ultimately trying to do, it'd probably help to get a library that's already designed to do it like LIEF: https://lief-project.github.io/doc/stable/tutorials/

Then you can look at the binaries to see how it made the changes.

This will add a new section:

import lief

bin = lief.parse('test.o')

print('Original sections:')
print('Name\tType\tOffset\tVirtual\tSize\nFlags')
for section in bin.sections:
    print(f'{section.name}\t{section.type}\t{hex(section.offset)}\t{hex(section.virtual_address)}\t{hex(section.size)}')
    if len(section.flags_list):
        print(f'\t\t{section.flags_list}')

    if section.name == '.text':
        text_flags = section.flags

# This alloocates new bytes in the binary and is the safest way to add a section
# Instead of setting content, you can try to manually set the other fields but it's hit and miss working with it that way
sec = lief.ELF.Section()
sec.name = "my_section"
sec.type = lief.ELF.SECTION_TYPES.PROGBITS
sec.content = [0xCC] * 512
sec.alignment = 8

# Copy .text sections flags to our section (should be ALLOC and EXECINSTR )
if text_flags:
    sec.flags = text_flags

bin.add(sec)

print('\n\nModified sections:')
print('Name\tType\tOffset\tVirtual\tSize')
for section in bin.sections:
    if section.name == 'my_section':
        section.virtual_address = 0x20000

    print(f'{section.name}\t{section.type}\t{hex(section.offset)}\t{hex(section.virtual_address)}\t{hex(section.size)}')
    if len(section.flags_list):
        print(f'\t\t{section.flags_list}')

bin.write('test2.o')

Then verify:

$ objdump -h test2.o
...
27 my_section    00000200  0000000000020000  0000000000020000  00005000  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

You can dig down and do some more manual surgery with it, but I'm not that well-versed in it.

Keep in mind that when you do have a process compiled with PIE enabled, it's going to completely ignore the virtual address and any code there had better be relocatable:

(gdb) info proc mappings
process 3550
Mapped address spaces:

          Start Addr           End Addr       Size     Offset  Perms  objfile
      0x555555554000     0x555555556000     0x2000        0x0  r--p   /home/osboxes/test2.o
      0x555555556000     0x555555557000     0x1000     0x2000  r-xp   /home/osboxes/test2.o
      0x555555557000     0x555555558000     0x1000     0x3000  r--p   /home/osboxes/test2.o
      0x555555558000     0x55555555a000     0x2000     0x3000  rw-p   /home/osboxes/test2.o
      0x555555561000     0x555555562000     0x1000     0x5000  r-xp   /home/osboxes/test2.o

(gdb) x /16xb 0x555555561000
0x555555561000: 0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc
0x555555561008: 0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc

Not the answer you're looking for? Browse other questions tagged or ask your own question.