5

If the ELF header which usually can be read using readelf has been manually manipulated, let's say by increasing the value for the "Size of section headers" the binary still can be executed and works well.

However, this manipulation seems to trip up reverse engineering tools like GDC and GDB gives me the error: not in executable format: File format not recognized.

Is there a way to fix the ELF header without knowing the original value of "Size of section headers" in order to be able again to analyze the file using standard tools?

Detailed info:

GDB is failing to run the binary because it says the file is not in executable format : File format not recognized but it works outside the GDB. The same things happen with the libbfd parser, it can't parse because file format is not recognized. The fact is I only change the number of section headers.

Code

#include <stdio.h>

int main()
{
    printf("Hello World!\n");
    return 0;
}

Build by invoking make hello or on a 64-bit system make CFLAGS=-m32 hello.

ELF header before

$ readelf -h hello
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048320
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4472 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         30 <-- notice me!
  Section header string table index: 27

ELF header after

$ readelf -h hello
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048320
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4472 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         52 <-- already changed!
  Section header string table index: 27

GDB will output,

not in executable format: File format not recognized

But if I run it outside of GDB,

$ ./hello output:
Hello World!

So is there either a method to fix the value for e_shnum without knowing the correct value, or a workaround so I can debug this file in GDB?

2

In this particular case, repairing the header can be automated. Since the section header string table is present, the original value of e_shnum can be found by counting the number of strings in the table.

Original:

$ readelf -h hello
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x3e0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          6056 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         29  <-----------------
  Section header string table index: 28

Corrupted:

$ readelf -h hello
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x3e0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          6056 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         52  <------------------
  Section header string table index: 28
readelf: Error: Reading 2080 bytes extends past end of file for section headers
readelf: Error: Reading 7216 bytes extends past end of file for dynamic string table

Reading the section header string table:

#!/usr/bin/python3

from elftools.elf.elffile import ELFFile

with open('hello', 'rb') as f:
    elffile = ELFFile(f)
    print("original e_shnum:\t" + str(len(elffile.get_section(28).data().decode('ascii').split('\x00')) + 1))

When run against the binary with the corrupted header, the output is as follows:

$ python3 recover_e_shnum.py 
original e_shnum:   29

This script will repair the header automatically:

#!/usr/bin/python3

from elftools.elf.elffile import ELFFile
from struct import pack

with open('hello', 'rb+') as f:
    elffile = ELFFile(f)
    e_shnum = len(elffile.get_section(28).data().decode('ascii').split('\x00')) + 1 
    f.seek(48)
    f.write(pack('h', e_shnum))
1

I recreated your binary and then used Radare to corrupt the header exactly as you did, changing the number of section headers to 52.

r2 -w a.out -1c's 0; pfo elf64; .pf.elf_header.shnum=52'

This only generated a warning with subsequent invocations of readelf.

$ readelf -h a.out 
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:               0x1050
  Start of program headers:          64 (bytes into file)
  Start of section headers:          14624 (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:         52
  Section header string table index: 28
readelf: Error: Reading 3328 bytes extends past end of file for section headers

But I got the similar error from GDB,

not in executable format: file truncated

And, if I try to debug that file, I get

No executable file specified.

I could go back to the original easily with

r2 -w a.out -c's 0; pfo elf64; .pf.elf_header.shnum=52'

However to answer your question about tooling,

  • Radare works with it even if the header is set to 52. You can still debug it.
  • You can get gdb to work if the header is less than the original shnum, for instance setting it to 1 will only warn you,

    BFD: warning: /tmp/a.out has a corrupt string table index - ignoring

    but you can still debug the program.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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