Posts RedPwn CTF 2021 - simultaneity [Pwn]
Post
Cancel

RedPwn CTF 2021 - simultaneity [Pwn]

Simultaneity is a pwn challenge from RedpwnCTF 2021. We are provided a 64-bit Linux ELF. If we check the binary’s memory protection, we notice that it has full RELRO, PIE and NX protections enabled. Because of full RELRO the GOT overwrite technique is not possible, and we need a memory leak to bypass full address randomization.

1
2
3
4
5
6
$ checksec simultaneity
Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled

We decompile the executable with Ghidra and look at the output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void main(void)

{
  long in_FS_OFFSET;
  size_t size;
  void *ptr;
  undefined8 local_10;
  
  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  setvbuf(stdout,(char *)0x0,2,0);
  puts("how big?");
  __isoc99_scanf("%ld",&size);
  ptr = malloc(size);
  printf("you are here: %p\n",ptr);
  puts("how far?");
  __isoc99_scanf("%ld",&size);
  puts("what?");
  __isoc99_scanf("%zu",(void *)((long)ptr + size * 8));
                    /* WARNING: Subroutine does not return */
  _exit(0);
}

The program asks for three inputs from the user. The first input is a size passed to malloc, and the second one is used to determine the address where the third input will be stored (as an offset from the allocated heap memory). The vulnerability is the third scanf call, as we can obtain an arbitrary write using the second input.

1
__isoc88_scanf("%zu",(void *)((long)ptr + size*8))

Controlling the size passed to malloc and knowing the allocation’s base address we can calculate the address to overwrite:

1
address_to_overwrite = leak + size * 8

As previously mentioned, the binary is compiled with full RELRO, so the GOT overwrite approach is not possible. Alternatively, we can overwrite __malloc_hook or __free_hook to change the execution flow when free or malloc are called.

We noticed that we can bypass address randomization simply by allocating a considerable size of bytes. Doing this we will obtain a libc address instead of a heap address.

1
2
3
how big?
10
you are here: 0x557f6fa6e6b0
1
2
3
how big?
274432
you are here: 0x7fcbd411b010

Now, we can calculate the base address of libc and we are ready to predict any address in the libc section. Using the one_gadget tool, we obtain three single-jump ROP gadgets:

1
2
3
4
5
6
7
8
9
10
11
12
$ one_gadget libc.so.6
0x4484f execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x448a3 execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xe5456 execve("/bin/sh", rsp+0x60, environ)
constraints:
  [rsp+0x60] == NULL

Through trial and error, we found that the gadget at 0x448a3 works. Since our approach in this case is to overwrite __free_hook with the absolute address for our gadget, we must force the program to call free at some point. There are no direct calls to free in the decompiled source, so we must find another way.

The best approach is to look for a call to free in some of the used libc functions; in this case, we used scanf. If we input a string long enough, scanf will call malloc and free internally. To achieve this, we will left-pad our third input with zeroes, increasing the input length without altering the final number. The full exploit code is available here.

1
flag{sc4nf_i3_4_h34p_ch4l13ng3_TKRs8b1DRlN1hoLJ}
This post is licensed under CC BY 4.0 by the author.