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)
- 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
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
- shellcode mẫu cho file 64 bits
- $rdi phải trỏ tới đỉnh stack

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
- shellcode mẫu cho file 32 bits
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 ; link2
- 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:
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:
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ụ:
- hoặc ta có thể làm vs cú pháp sau
- 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
Off_by_One
- đây là dạng bug thường gặp trong hàm scanf() (fmtstr là %s)

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
- đâ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
%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
- 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ề payload, ta sẽ chain hợp lí để nó ow chính xác
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
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
- 2 khái niệm tương đối khác nhau
- full form thì nó sẽ thực thi lần lượt từng fmt từ trái sang phải
- 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 |

- 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.
- mẹo: với đơn vị hex thì byte 0x80.. sẽ là số âm, và 0x7f vẫn còn là dương.
- mẹo: với đơn bị bin thì bit đầu tiên sẽ quyết định là số âm hay dương
é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:

$rdi hiểu là 0x31 (số 1) nhưng stack hiện tại là 0x3ff0000000000000

- trong python có cách convert về kiểu double:
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
here
- system call
here
- privillege escalation base on
/usr/bin
here
CTF Note