Self Control

We are required to fix a corrupted elf file by patch two bytes at particular position. Chec it with readelf first.

$ readelf -h READFLAG 
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:                           Intel IA-64
  Version:                           0x1
  Entry point address:               0x10a1
  Start of program headers:          64 (bytes into file)
  Start of section headers:          15040 (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

Machine looks unusal and objdump complains architecture UNKNOWN. To continue analyse it with local analyse tools we need to change it to AMD x86-64 architecture, which is 62 for the e_machine field of ELF header Elf64_Ehdr.

$ objdump -D READFLAG 

READFLAG:     file format elf64-little

objdump: can't disassemble for architecture UNKNOWN!

For 64bits machine, e_machine is located at the 19th position and it takes two bytes.

#define EI_NIDENT 16

typedef struct {
        unsigned char   e_ident[EI_NIDENT];
        Elf64_Half      e_type;
        Elf64_Half      e_machine;
        Elf64_Word      e_version;
        Elf64_Addr      e_entry;
        Elf64_Off       e_phoff;
        Elf64_Off       e_shoff;
        Elf64_Word      e_flags;
        Elf64_Half      e_ehsize;
        Elf64_Half      e_phentsize;
        Elf64_Half      e_phnum;
        Elf64_Half      e_shentsize;
        Elf64_Half      e_shnum;
        Elf64_Half      e_shstrndx;
} Elf64_Ehdr;

Source: ELF Header

/* 64-bit ELF base types. */
typedef __u64   Elf64_Addr;
typedef __u16   Elf64_Half;
typedef __s16   Elf64_SHalf;
typedef __u64   Elf64_Off;
typedef __s32   Elf64_Sword;
typedef __u32   Elf64_Word;
typedef __u64   Elf64_Xword;
typedef __s64   Elf64_Sxword;

Source: elf.h

$ xxd READFLAG |head 
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0300 3200 0100 0000 a110 0000 0000 0000  ..2.............
00000020: 4000 0000 0000 0000 c03a 0000 0000 0000  @........:......
00000030: 0000 0000 4000 3800 0b00 4000 1e00 1d00  ....@.8...@.....
00000040: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
00000050: 4000 0000 0000 0000 4000 0000 0000 0000  @.......@.......
00000060: 6802 0000 0000 0000 6802 0000 0000 0000  h.......h.......
00000070: 0800 0000 0000 0000 0300 0000 0400 0000  ................
00000080: a802 0000 0000 0000 a802 0000 0000 0000  ................
00000090: a802 0000 0000 0000 1c00 0000 0000 0000  ................

As the difference is one byte, we just change 0x32 to 0x3e, at last we save it to fix.

readflag = open("READFLAG", "rb").read()
readflag = readflag[:18] + b'\x3e' + readflag[19:]


with open("fix", "wb") as fd:
    fd.write(readflag)

Run fix in gdb, segfault raise at _start, let’s take a look at the entry address.

   0x55555555509b                  add    BYTE PTR [rax], al
   0x55555555509d                  add    BYTE PTR [rax], al
   0x55555555509f                  add    BYTE PTR [rcx], dh
0x5555555550a1 <_start+1>       in     eax, dx
   0x5555555550a2 <_start+2>       mov    r9, rdx
   0x5555555550a5 <_start+5>       pop    rsi
   0x5555555550a6 <_start+6>       mov    rdx, rsp
   0x5555555550a9 <_start+9>       and    rsp, 0xfffffffffffffff0
   0x5555555550ad <_start+13>      push   rax
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "fix", stopped 0x5555555550a1 in _start (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555550a1 → _start()

The header said the entry point address is at 0x10a1.

$ readelf -h READFLAG 
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:               0x10a1
  Start of program headers:          64 (bytes into file)
  Start of section headers:          15040 (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

But *_start* actually located at 0x10a0.

$ readelf -s fix|grep _start
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    41: 0000000000003de8     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_start
    48: 0000000000004048     0 NOTYPE  WEAK   DEFAULT   24 data_start
    53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    54: 0000000000004048     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
    55: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    60: 00000000000010a0    43 FUNC    GLOBAL DEFAULT   14 _start
    61: 0000000000004058     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start

According to the Elf64_Ehdr struct, the entry point address is stored in field e_entry, at the 25th position of header and it takes 8 bytes. But the difference is just one byte, so we just need to overwrite 0xa1 with 0xa0

readflag = open("READFLAG", "rb").read()
readflag = readflag[:18] + b'\x3e' + readflag[19:]
readflag = readflag[:24] + b'\xa0' + readflag[25:]


with open("fix", "wb") as fd:
    fd.write(readflag)

Now we run it again, no error and no output either, these might be the two bytes we need to change, patch it to the server, it response with the flag.

TOP