CTF

PWNABLE - Start

Analysis of the start binary

Reading the ELF Headers

By reading the ELF headers we learn some details about the binary:

  • it's a 32-bit executable (ELF32)
  • it's written in assembler (start.s)
sh# readelf -a ./start 
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:               0x8048060
  Start of program headers:          52 (bytes into file)
  Start of section headers:          364 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         5
  Section header string table index: 2

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        08048060 000060 000043 00  AX  0   0 16
  [ 2] .shstrtab         STRTAB          00000000 00014b 000021 00      0   0  1
  [ 3] .symtab           SYMTAB          00000000 0000a4 000080 10      4   4  4
  [ 4] .strtab           STRTAB          00000000 000124 000027 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x000a3 0x000a3 R E 0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text 

There is no dynamic section in this file.

There are no relocations in this file.

The decoding of unwind sections for machine type Intel 80386 is not currently supported.

Symbol table '.symtab' contains 8 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 08048060     0 SECTION LOCAL  DEFAULT    1 
     2: 00000000     0 FILE    LOCAL  DEFAULT  ABS start.s
     3: 0804809d     0 NOTYPE  LOCAL  DEFAULT    1 _exit
     4: 08048060     0 NOTYPE  GLOBAL DEFAULT    1 _start
     5: 080490a3     0 NOTYPE  GLOBAL DEFAULT    1 __bss_start
     6: 080490a3     0 NOTYPE  GLOBAL DEFAULT    1 _edata
     7: 080490a4     0 NOTYPE  GLOBAL DEFAULT    1 _end

No version information found in this file.

Dissassembling the binary

By dissassembling the binary we can study what the binary is actually doing in order to find a weakness we can exploit.

sh# objdump -d ./start

./start:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
; safe value of current stack pointer on stack (!)
 8048060:  54                    push   %esp

; push the address of the exit function on the stack, which will be used at the end of the _start function
 8048061: 68 9d 80 04 08        push   $0x804809d

; initialize registers eax, ebx, ecx, edx to zero
 8048066: 31 c0                 xor    %eax,%eax
 8048068: 31 db                 xor    %ebx,%ebx
 804806a: 31 c9                 xor    %ecx,%ecx
 804806c: 31 d2                 xor    %edx,%edx

; Pushes the message "Let's start the CTF:" on the stack
 804806e: 68 43 54 46 3a        push   $0x3a465443
 8048073: 68 74 68 65 20        push   $0x20656874
 8048078: 68 61 72 74 20        push   $0x20747261
 804807d: 68 73 20 73 74        push   $0x74732073
 8048082: 68 4c 65 74 27        push   $0x2774654c

; sets the register ecx to the address of esp
 8048087: 89 e1                 mov    %esp,%ecx

; dl is the 8bit part of edx which is set to 20 = 0x14, which is the 3rd argument of the write-syscall
; write( int fd, const void *buf, size_t count) => write( ebx=1, ecx, edx=20 )
 8048089: b2 14                 mov    $0x14,%dl
; bl is the 8bit part of ebx, which is the 1st argument passed to syscall write - the file descriptor )
 804808b: b3 01                 mov    $0x1,%bl
; al is the 8bit part of eax, which is the syscall id - 4 = write
 804808d: b0 04                 mov    $0x4,%al
; make the syscall
 804808f: cd 80                 int    $0x80

; zero out ebx
 8048091: 31 db                 xor    %ebx,%ebx
; dl is set to 60 = 0x3c - 3rd argument of read (count)
; read(int fd, void *buf, size_t count) => read( ebx=0, ecx, edx=60 )
 8048093: b2 3c                 mov    $0x3c,%dl
; al ist set to 3, which stands for syscall with id =3, read(int fd, void *buf, size_t count)
 8048095: b0 03                 mov    $0x3,%al
; make the syscall
 8048097: cd 80                 int    $0x80

; set esp to the location before we wrote the message "Let's start the CTF:" wich is 20 bytes long
; because the stack addresses get smaller when we put stuff on the stack, we have to add 20
; to get to the location prior writing that message
 8048099: 83 c4 14              add    $0x14,%esp

; our stack pointer points to the address 0x804809d, which we pushed there in the second instruction of _start
; thus we give control to the function _exit, which just exits the executable
 804809c: c3                    ret    

0804809d <_exit>:
 804809d: 5c                    pop    %esp
 804809e: 31 c0                 xor    %eax,%eax
 80480a0: 40                    inc    %eax
 80480a1: cd 80                 int    $0x80

So now we know, if we start the binary, it will output the String "Let's start the CTF:" and await our input.

We also know as first instruction of the _start function the address of esp get pushed on the stack and after that the address of the _exit function.

 8048087: 89 e1                 mov    %esp,%ecx

That means if we input more than 20 bytes (60 bytes are allowed in the read), we can overwrite the return address of the _start function which happens to start at the 21st byte and is 4 bytes long, because we deal with a 32-bit binary.