Hợp ngữ
●
Gán 2 vào EAX
●
●
MOV
EBX, 3
Tính ECX = EAX + EBX
●
●
●
EAX, 2
Gán 3 vào EBX
●
●
MOV
MOV
ADD
ECX, EAX
ECX, EBX
Hoặc LEA
ECX, [EAX+EBX]
Tính EDX = EAX * EBX
●
...
Hợp ngữ
●
Cho EAX=1, dùng một lệnh luận lý để gán
EAX=-1
●
●
●
NEG
EAX
Cho EAX=FFFFFFFF, dùng một lệnh số học để
gán EAX=0
●
ADD
EAX, 1
●
Hoặc SUB
EAX, FFFFFFFF
Cho EAX=0, dùng một lệnh số học để gán
EAX=FFFFFFFF
●
SUB
EAX, 1
●
Hoặc ADD
EAX, FFFFFFFF
Hợp ngữ
●
●
Cho EAX là một số bất kỳ, dùng một lệnh khác
MOV để gán EAX=0
●
SUB
EAX, EAX
●
Hoặc XOR
EAX, EAX
●
Hoặc AND
EAX, 0
●
Hoặc LEA
EAX, [0]
Cho EAX là một số bất kỳ, dùng một lệnh khác
MOV để gán EAX=FFFFFFFF
●
OR
EAX, FFFFFFFF
Hợp ngữ
●
Gán giá trị ở đỉnh ngăn xếp vào EAX
●
●
POP
EBX
Gán giá trị của phần tử thứ hai trên ngăn xếp
vào ECX
●
●
EAX, [ESP]
Lấy ra giá trị ở đỉnh ngăn xếp vào EBX
●
●
MOV
MOV
ECX, [ESP + 4]
Lấy ra giá trị của phần tử thứ hai trên ngăn xếp
vào EDX (vẫn giữ giá trị của đỉnh ngăn xếp)
●
...
Hợp ngữ
●
Gán giá trị của EIP vào EAX
●
●
Gán giá trị của EAX vào EIP
●
●
…
Dùng một lệnh để thực hiện phép chia lấy dư
của EAX cho 16
●
●
...
AND
EAX, 0x0F
Kiểm tra nếu EAX chia hết cho 4
●
AND
EAX, 0x03
Hợp ngữ
●
Xét hàm
●
int func(int a, int b)
{
int c;
char d[7];
short e;
return 0;
}
●
2 đối số là a và b
●
3 biến nội bộ là c, d và e
●
Biến a dài 4 byte, b 4, c 4, d 7 và e 2.
Hợp ngữ
●
●
Một hàm sẽ có các phần dẫn nhập, thân hàm,
và kết thúc
Dẫn nhập:
●
●
●
●
PUSH
MOV
SUB
EBP
EBP, ESP
ESP, 0x20
Biến nội bộ được cấp trên ngăn xếp
Các biến nội bộ được cấp vùng nhớ theo thứ tự
khai báo
Biến được cấp vùng nhớ là bội số của 4
Hợp ngữ
Hợp ngữ
●
Thân hàm
●
XOR
EAX, EAX
●
Kết quả trả về của hàm được lưu trong EAX
●
Trong thân hàm, EBP không thay đổi
●
Kết thúc:
●
MOV
POP
RET
ESP, EBP
EBP
Hợp ngữ
Hợp ngữ
●
Gọi hàm
●
●
●
●
●
PUSH
PUSH
CALL
b
a
func
Đối số được “push” từ phải qua trái
Vùng từ đối số đến biến nội bộ gọi là vùng nhớ
(khung nhớ) ngăn xếp (stack frame)
Vùng nhớ ngăn xếp thể hiện một hàm chưa
thực thi xong
EBP luôn chỉ đến ô “EBP cũ” trong vùng nhớ.
Xác định được 1 ô → xác định các ô còn lại
Hợp ngữ
Hợp ngữ
Hợp ngữ
Tràn bộ đệm
●
●
Dữ liệu nhập dài quá giới hạn bộ nhớ chứa nó
Dữ liệu dư ra này có thể đè lên dữ liệu quan
trọng dẫn đến cơ hội tận dụng
Tràn bộ đệm
●
Dữ liệu quan trọng phải nằm sau bộ đệm
●
Phải tràn tới được dữ liệu quan trọng
●
Dữ liệu bị ghi đè phải được sử dụng
Tràn bộ đệm
●
Ghi đè giá trị biến nội bộ
●
Biến nội bộ nằm trong ngăn xếp
●
Tràn ngăn xếp có thể đè lên biến nội bộ
●
Ít bị phát hiện
●
Có thể có ảnh hưởng lớn đến kết quả chương trình
Tràn bộ đệm
●
Xét ví dụ
●
int main()
{
int cookie;
char buf[16];
gets(buf);
if (cookie==0x41424344)
{
puts(“You win!”);
}
}
Tràn bộ đệm
●
2 biến nội bộ
●
cookie kiểu int, 4 byte
●
buf kiểu mảng char, 16 byte
●
●
●
cookie nằm phía sau buf → tràn buf có thể làm
thay đổi cookie
Nhập 16 ký tự bất kỳ sẽ làm đầy buf, ký tự thứ
17 sẽ đè lên cookie → trở thành byte thấp của
cookie
Nhập 20 ký tự sẽ kiểm soát toàn bộ cookie
Tràn bộ đệm
Tràn bộ đệm
Tràn bộ đệm
Tràn bộ đệm
Tràn bộ đệm
Tràn bộ đệm
●
Ghi đè địa chỉ trở về
●
Địa chỉ trở về nằm trong ngăn xếp
●
Ghi đè địa chỉ trở về làm thay đổi luồng thực thi
●
Bị phát hiện trong các trình biên dịch hiện đại
Tràn bộ đệm
●
Xét ví dụ
●
int main()
{
int cookie;
char buf[16];
gets(buf);
if (cookie==0x000D0A00)
{
puts(“You win!”);
}
}
Tràn bộ đệm
●
●
Gần giống ví dụ trước
Hàm gets() ngắt tại 0x0A → không thể kiểm
soát trọn vẹn giá trị cookie
●
Nhưng vẫn kiểm soát được địa chỉ trở về
●
→ gán địa chỉ nhánh “==” vào ô ngăn xếp đó
Tràn bộ đệm
Tràn bộ đệm
Tràn bộ đệm
Tràn bộ đệm
●
●
0804849d:
080484a4:
080484a6:
080484a9:
080484ae:
080484b3:
080484b6:
cmp
jne
add
push
call
add
…
[ebp-4], 0x0d0a00
0x080484b6
esp, 0xfffffff4
0x080485e7
printf
esp, 0x10
Có thể dùng 080484a6 hoặc 080484a9
Tràn bộ đệm
Tràn bộ đệm
●
●
●
Một số hàm không an toàn là gets(), strcpy(),
strcat(), sprintf().
int main(int argc, char **argv) {
char name[128];
strcpy(name, argv[1]);
strcat(name, “ = “);
strcat(name, argv[2]);
return 0;
}
Cả ba lệnh đều có thể gây tràn biến name
Tràn bộ đệm
●
●
●
int main(int argc, char **argv) {
char name[128];
sprintf(name, “%s”, argv[1]);
return 0;
}
Hàm sprintf() có thể gây tràn biến name nếu
argv[1] là chuỗi dài hơn 128 ký tự.
Cách khắc phục thay bằng các hàm “an toàn”
hơn như fgets(), strncpy(), snprintf().
Tràn bộ đệm
●
Xét ví dụ
●
int main(void) {
char a[16];
char b[16];
char c[32];
strncpy(a, "0123456789abcdef",
sizeof(a));
strncpy(b, "0123456789abcdef",
sizeof(b));
strncpy(c, a, sizeof(c));
}
Tràn bộ đệm
●
●
●
●
Tất cả 3 hàm đều là hàm “an toàn”
strncpy() không tự động chèn ký tự NUL vào
chuỗi → chuỗi a, và b không được ngắt
(nhìn lên bảng)
Khắc phục bằng cách đảm bảo chuỗi phải
được ngắt bằng ký tự NUL
●
Ví dụ a[15] = b[15] = '\x00';
Tràn bộ đệm
●
Bảo đảm bộ đệm có thể chứa đủ
●
●
char *b = malloc(strlen(argv[1]) + 1);
if (b != NULL) {
strcpy(b, argv[1]);
printf("argv[1] = %s.\n", b);
}
Dùng các hàm “an toàn” hơn nữa như strlcpy(),
strlcat(), strcpy_s(), strcat_s(), strncpy_s(),
strncat_s()...
●
Compiler hiện đại với chức năng StackGuard
●
Luôn nghĩ về khả năng bị tràn
- Xem thêm -