Pwn/Exploit - Lỗi Tràn Bộ Đệm (Phần 1)

thanhlong

Moderator
gemgem
Tham gia
18/03/2025
Bài viết
64
Được Like
0
Coin
2,477
Points
355
Pwn/Exploit - Lỗi Tràn Bộ Đệm (Phần 1)

Xin chào các bạn, hôm nay mình xin trình bày 1 chuỗi các bài chia sẻ và hướng dẫn về khai thác lỗi phần mềm (exploit/pwn). Trong nội dung bài viết mình có tham khảo từ cuốn sách Nghệ thuật tận dụng lỗi phần mềm – Tác giả Nguyễn Thành Nam và nhiều bài viết khác. Mình sẽ cố gắng diễn tả thật rõ ràng và cặn kẽ với mục đích giúp đỡ các bạn học sinh, sinh viên đến với pwn 1 cách dễ hiểu nhất mà mình có thể.

loi-tran-bo-dem-png.14300


Đầu tiên ta cần nắm về stack

Stack: là một vùng bộ nhớ được hệ điều hành cấp phát sẵn cho chương trình khi nạp. Chương trình sẽ sử dụng vùng nhớ này để chứa các biến cục bộ (local variable) và lưu lại quá trình gọi hàm, thực thi của chương trình.

Stack hoạt động theo nguyên tác vào sau ra trước (Last In First Out). Thanh ghi ESP (Stack Pointer) lưu địa chỉ đỉnh của stack. Trong kiến trúc Intel x86, mỗi một ô trong stack là 4 byte, tương ứng với 32 bit. Stack có 2 thao tác là PUSH và POP. PUSH sẽ đẩy 1 giá trị ô nhớ vào đỉnh của stack, đồng thời esp sẽ tự động tăng lên 1 ô nhớ. POP sẽ lấy ra 1 ô nhớ của đỉnh stack ra, đồng thời esp sẽ giảm giá trị xuống 1 ô nhớ.

Ví dụ:

Mã:

08048449 PUSH 0x08048580

0804844E CALL printf

08048453 ADD ESP, 0x10

Trước khi gọi hàm printf thì ta phải truyền vào tham số địa chỉ chuỗi cần in (PUSH 0x08048580) .

Cấu trúc một hàm:
Một hàm (Function ) khi được biên dịch thì trình biên dịch sẽ tạo ra một hàm tương ứng gồm 3 phần:

- Phần dẫn nhập (prolog): là phần đầu của một hàm, có nhiệm vụ lưu trữ thông tin về vùng nhớ của hàm gọi và cấp phát bộ nhớ cho các biến nội bộ trước khi thân hàm được thực thi.

+ Thông thường phần dẫn nhập gồm 2 dòng lệnh chính là 1 dòng lệnh phụ như sau:

Mã:

PUSH EBP

MOV EBP, ESP

SUB ESP, 0x20

+ Hai dòng lệnh đầu lưu giá trị của thanh ghi EBP vào stack, sau đó gán giá trị của ESP vào EBP. Lệnh thứ 3 dùng để cấp phát bộ nhớ cho stack, ở đây là 0x20 byte. Đây là phần bộ nhớ được cấp phát cho tất cả các biến nội bộ trong hàm. Như vậy giá trị của vùng nhớ này (0x20) phải lớn hơn hoặc bằng tổng độ lớn của các biến nội bộ. Biến nội bộ kiểu int thì sẽ được cấp phát 4 byte, kiểu char thì 1 byte. Kiểu short là 2 byte nhưng sẽ được cấp 4 byte để tối ưu hóa tốc độ truy cập (mỗi ô stack là 4 byte).

+ Trong vùng nhớ được cấp phát thì vùng nhớ của các biến nội bộ sẽ được cấp phát theo thứ tự các biến nội bộ xuất hiện trong code nguồn C.

- Phần thân: là phần chính của hàm, bao gồm các lệnh thực hiện nhiệm vụ của hàm. Giá trị trả về của 1 hàm sẽ được lưu trong thanh ghi EAX. Trong thân hàm giá trị của thanh ghi EBP không thay đổi.

- Phần kết thúc ( epilog): là phần cuối của hàm, có nhiệm vụ hủy bỏ vùng nhớ đã được cấp phát ở phần dẫn nhập đồng thời chuyển lại vùng nhớ của hàm gọi.

+ Cũng như phần dẫn nhập, phần kết thúc gồm 2 lệnh chính và 1 lệnh phụ:

Mã:

MOV ESP, EBP

POP EBP

RET

+ Hai lệnh đầu tiên thực hiện tác vụ đảo của phần dẫn nhập. Gán giá trị EBP vào ESP. Sau đó khôi phục giá trị EBP đã được PUSH ở phần dẫn nhập về (POP EBP). Hai lệnh này có thể thay bằng lệnh LEAVE. Lệnh RET sẽ thay đổi luồng điều khiển quay về hàm gọi.

Cấu trúc stack của 1 hàm:

1-jpg.14301


Trong 1 chương trình thì sẽ có nhiều hàm. Hàm lớn gọi hàm nhỏ. Giả sử ta đang ở trong 1 hàm lớn là hàm main, thì khi gọi 1 hàm nhỏ ở hàm lớn sẽ có các bước như sau:

Gán các biến đầu vào argv0, argv1 vào đỉnh của stack. Chương trình sẽ dùng các lệnh PUSH các biến vào đỉnh của stack hoặc dùng lệnh MOV esp, giá trị biến để gán vào stack.

Gán giá trị địa chỉ câu lệnh tiếp theo sau khi gọi hàm con vào stack. (saved eip)

Trong hàm nhỏ phần prolog sẽ lưu trữ giá trị ebp của hàm lớn vào stack (saved ebp) bằng lệnh PUSH ebp. Sau đó là lưu lại giá trị esp của hàm lớn thành ebp của hàm nhỏ bằng lệnh MOV ebp, esp. Và cấp phát vùng nhớ cho stack cho hàm con sử dụng bằng lệnh SUB esp, <độ dài vủng nhớ cấp phát>.

Phần epilog (phần kết thúc) thường kết thúc bằng 2 lệnh là leave và retn. Lệnh leave thực hiện 2 việc là MOV esp, ebp (gán giá trị esp về giá trị ebp ban đầu, thao tác này là thao tác lấy lại vùng nhớ đã cấp phát ở phần đầu hàm con) và việc thứ 2 là POP ebp (lúc này esp đã bị gán về vị trí lưu saved ebp, nên lệnh này sẽ gán giá trị saved ebp vào thanh ghi ebp). Lệnh retn sẽ thực hiện POP eip, gán giá trị saved eip trong stack vào thanh ghi eip.

Tràn Bộ Đệm

Tràn bộ đệm là lỗi xảy ra khi dữ liệu xử lý (thường là dữ liệu nhập) dài quá giới hạn của vùng nhớ chứa nó. Tuy nhiên, nếu phía sau vùng nhớ này có chứa những dữ liệu quan trọng tới quá trình thực thi của chương trình thì dữ liệu dư có thể sẽ làm hỏng các dữ liệu quan trọng này. Tùy thuộc vào cách xử lý của chương trình đối với các dữ liệu quan trọng mà người tận dụng lỗi có thể điều khiển chương trình thực hiện tác vụ mong muốn.

– Thay đổi giá trị của biến:

2-png.14302


Hình stack1.c

Dễ thấy chương trình trên khai báo 2 biến cookie và buf. Biến buf 16 byte và biến cookie kiểu int 4 byte. Giá trị biến buf được người dùng nhập vào. Chương trình in ra giá trị biến cookie lúc đầu khai báo, in ra địa chỉ vùng nhớ của biến buf và biến cookie. Sau đó so sánh giá trị biến cookie với 0x41424344. Nếu bằng nhau sẽ in ra “You win!!”. Và nhiệm vụ của chúng ta là phải in ra dòng chữ đó.

Biên dịch chương trình : gcc –o stack1 stack1.c

Chạy thử chương trình:

3-jpg.14303


Hình: run stack1

Chúng ta cùng phân tích quá trình nạp bộ nhớ của chương trình bằng edb:

4-jpg.14304


Hình: stack1 memory map

Địa chỉ của biến buf là 0xbffff47c và địa chỉ của biến cookie là 0xbfff48c. Chúng ta theo dõi trong bảng bộ nhớ kế bên thì sẽ thấy được theo sơ đồ bộ nhớ thì khi ta nhập giá trị “😬😬” với độ dài 8 byte vào biến buf thì sẽ được ghi vào bộ nhớ từ trên xuống dưới. Do ta khai báo trong source code biến buff độ dài 16 byte, biến cookie kiểu int độ dài 4 byte. Do chúng ta không kiểm tra dữ liệu nhập vào nên nếu cố tình nhập vượt quá giá trị khai báo thì chúng ta dễ dàng ghi đè lên giá trị của biến cookie.

Như vậy chúng ta chỉ cần ghi đè lên biến cookie đúng giá trị 0x41424344 là sẽ thỏa mãn yêu cầu bài toán. Mã 0x41424344 khi chuyển qua string sẽ là DCBA. Cụ thể ta nhập vào 16 byte cho biến buf và 4 byte ghi đè lên biến cookie.

5-png.14305


Hình: stack1 exploited memory map

6-jpg.14306


Hình: stack1 exploited
 
Top Bottom