Mô tả:
Chuỗi định dạng
●
Hàm printf() có dạng
●
●
Nếu gọi printf(“Hello”);
●
●
Hello
Nếu gọi printf(“1%”);
●
●
printf(const char *format, …)
1%
Nếu gọi printf(“1%%”);
●
1%
Chuỗi định dạng
●
●
●
→ Dấu % có ý nghĩa đặc biệt
% đánh dấu sự bắt đầu của một yêu cầu định
dạng
Yêu cầu định dạng tận cùng bởi ký tự định
dạng
Chuỗi định dạng
●
% in ra ký tự %
●
c in tham số thứ nhất như một ký tự
●
x in tham số thứ nhất ở dạng thập lục
●
X in tham số thứ nhất ở dạng THẬP LỤC
●
s in chuỗi được chỉ tới bởi tham số thứ nhất
●
●
n ghi vào ô nhớ có địa chỉ xác định bởi tham số
thứ nhất số lượng ký tự đã in, 4 byte
hn
giống n nhưng chỉ ghi 2 byte
Chuỗi định dạng
●
printf(“%c %X”, 0x87654321,
0x12345678)
●
●
printf(“%x %X”, 0x6789ABCD,
0x6789ABCD)
●
●
! 12345678
6789abcd 6789ABCD
printf(“%s”, “1%%”)
●
1%%
Chuỗi định dạng
●
int cookie = 0
printf(“12345678%n”, &cookie)
●
●
12345678
int cookie = 0
printf(“1234%n5678”, &cookie)
●
12345678
●
Sau lệnh printf() đầu tiên, cookie = 8
●
Sau lệnh printf() thứ hai, cookie = 4
Chuỗi định dạng
●
Xét ví dụ:
●
●
int main()
{
char buffer[512];
int cookie = 0;
gets(buffer);
printf(buffer);
}
Chương trình nhận một chuỗi qua gets(), và
truyền chính chuỗi đó làm tham số thứ nhất của
printf()
Chuỗi định dạng
●
Nhập vào abcdef
●
●
Nhập vào %x
●
●
abcdef
0
Nhập vào %x %x %x %x
●
0 0 0 6
Chuỗi định dạng
●
Xét trường hợp
●
●
printf(“%x %x %x %x”, 1, 2, 3, 4);
PUSH
PUSH
PUSH
PUSH
PUSH
CALL
4
3
2
1
format
printf
Chuỗi định dạng
●
Xét trường hợp
●
●
printf(“%x %x %x %x”);
PUSH
CALL
format
printf
Chuỗi định dạng
Chuỗi định dạng
●
Nhập vào %x %x %x %x %x %x %x %x %x
%x %x %x
●
●
0 0 0 6 b7ead8e0 fffff 51 0 0 25207825
78252078 20782520
Chúng ta gặp lại dữ liệu nhập → Có thể kiểm
soát tham số của hàm printf()
●
Dữ liệu nhập bắt đầu từ tham số thứ 10
●
Giả sử địa chỉ cookie là BFFFF854
Chuỗi định dạng
Chuỗi định dạng
●
Để gán 0x64 vào cookie
●
●
●
●
●
[địa chỉ cookie]%x%x%x%x%x%x%x%x%x[...]%n
[địa chỉ cookie] in ra 4 byte
\x54\xF8\xFF\xBF
9 %x in ra 21 byte 0006b7ead8e0fffff5100
Để in ra tổng cộng 100 ký tự ta cần thêm 100 –
21 – 4 = 75 ký tự
Vậy […] sẽ là 75 ký tự
Chuỗi định dạng
●
Để gán 0x100 vào cookie
●
●
●
●
●
[địa chỉ cookie]%x%x%x%x%x%x%x%x%x[...]%n
[địa chỉ cookie] in ra 4 byte
\x54\xF8\xFF\xBF
9 %x in ra 21 byte 0006b7ead8e0fffff5100
Để in ra tổng cộng 256 ký tự ta cần thêm 256 –
21 – 4 = 231 ký tự
Vậy […] sẽ là 231 ký tự
Chuỗi định dạng
●
Để gán 0x300 vào cookie
●
●
●
●
●
[địa chỉ cookie]%x%x%x%x%x%x%x%x%x[...]%n
[địa chỉ cookie] in ra 4 byte
\x54\xF8\xFF\xBF
9 %x in ra 21 byte 0006b7ead8e0fffff5100
Để in ra tổng cộng 0x300 ký tự ta cần thêm
0x300 – 21 – 4 = ... ký tự
Vậy […] sẽ là ... ký tự → tràn biến buffer
Chuỗi định dạng
●
●
Để gán 0x300 vào cookie, ta phải nhập vào ít
hơn, nhưng vẫn đảm bảo in ra đủ 0x300 ký tự
Tùy chọn độ dài tối thiểu là một số nguyên
dương, không bắt đầu bằng số 0, nằm giữa dấu
% và ký tự định dạng
●
$10x → in số thập lục rộng tối thiểu 10 ký tự
●
$2x → in số thập lục rộng tối thiểu 2 ký tự
●
●
lớn hơn 0xFF → cần nhiều hơn 2 ký tự → 0 kiểm
soát được
→ luôn sử dụng tối thiểu là độ rộng tối đa cần thiết
(ví dụ $8x vì không số nào cần nhiều hơn 8 ký tự)
Chuỗi định dạng
●
4 byte địa chỉ
●
8 yêu cầu %8x
●
0x300 – 4 – 4 * 8 = 700 → yêu cầu %700x
●
Và yêu cầu %n
●
Tổng số ký tự nhập vào là 4 + 8 * 3 + 5 + 2 = 23
ký tự
Chuỗi định dạng
●
●
●
●
●
4 byte địa chỉ và 1 yêu cầu %764x đã in ra đủ
0x300 ký tự
Làm sao để ép yêu cầu %n nhận tham số thứ
10 thay vì tham số thứ 2
Tùy chọn vị trí tham số là một số nguyên dương
đi ngay sau ký tự % và kết thúc bằng ký tự $
%10$n sẽ ép yêu cầu %n sử dụng tham số thứ
10
%24$8x sẽ in giá trị của tham số thứ 24 ở dạng
thập lục với độ rộng tối thiểu là 8 ký tự
Chuỗi định dạng
●
Để cookie = 0x87654321
●
Không thể in hơn 2 tỷ ký tự!
●
●
Nhận xét rằng 0x87654321 gồm 4 byte 21, 43,
65, 87
Thay vì ghi một lần, chúng ta có thể ghi tuần tự
4 lần
●
4 lần ghi → 4 địa chỉ → đã in 0x10 ký tự
●
Số đầu là 0x21 → cần thêm 0x11 ký tự đệm
●
Số hai là 0x43 → cần thêm 0x22 ký tự đệm
●
...
Chuỗi định dạng
●
Để cookie = 0x12345678
●
Hai câu hỏi:
●
●
78, 56, 34, 12 → ghi 12 trước, 34 sau...?
●
Làm sao để từ 78 thành 56?
Khi ghi vào bộ nhớ, 4 byte sẽ được ghi cùng
một lúc → lần ghi sau sẽ đè lên lần ghi trước
●
●
→ nếu ghi ở địa chỉ cao trước thì khi ghi ở địa chỉ
thấp sẽ đè lên giá trị đã ghi ở địa chỉ cao
→ vì lần ghi sau ở địa chỉ cao đè lên lần ghi trước ở
địa chỉ thấp hơn nên chúng ta chỉ quan tâm đến các
byte thấp → 0x156 hay 0x256 đều đem lại 0x56
Chuỗi định dạng
Chuỗi định dạng
●
Có nhiều cách cắt
●
●
●
2-2
hn-hn (không lem)
1-1-2 n-n-hn hoặc n-hn-hn hoặc hn-hn-hn... (có thể
bị lem)
1-1-1-1 n-n-n-n (lem ít nhất 1 byte)
Chuỗi định dạng
Chuỗi định dạng
●
Để cookie = 0x04030201
●
01 02 03 04
●
→ lần đầu ghi 0x101
●
→ chèn một ký tự bất kỳ, ghi lần 2
●
→ chèn một ký tự bất kỳ, ghi lần 3
●
→ chèn một ký tự bất kỳ, ghi lần 4
●
→ [16 byte địa chỉ...]%241x%10na%11nb
%12nc%13n
Chuỗi định dạng
●
Khi chuỗi nhập bắt đầu bằng “UIT”
●
Nhận xét:
●
●
●
●
●
…
“UIT” chiếm mất tham số thứ 10
“UIT” (3 byte) chưa đủ lấp tham số thứ 10 → cần
thêm 1 byte bất kỳ
Vì tham số thứ 10 đã bị chiếm nên các lần ghi sẽ
phải sử dụng tham số 11, 12, 13, và 14
Vì có 4 ký tự chèn vào trước, nên cần tính toán lại
các đoạn đệm
Chuỗi định dạng
Chuỗi định dạng
●
●
Các hàm cùng họ printf() như fprintf(), sprintf()
cũng mắc phải lỗi này
Nguyên nhân là:
●
●
Chuỗi nhập được sử dụng như chuỗi định dạng
Hàm printf() cổ điển cho phép %n, %hn (các thư
viện hiện đại đã bỏ %n và %hn)
–
●
Nhưng vẫn không ngăn được việc quét ngăn xếp
Để tránh lỗi thì cần phải sử dụng hàm đúng
cách, phải luôn luôn xác định rõ chuỗi định
dạng khi muốn in tham số do người dùng nhập
- Xem thêm -