Chuỗi định dạng ● Hàm printf() có dạng ● printf(const char *format, …) ● Nếu gọi printf(“Hello”); ● Hello ● Nếu gọi printf(“1%”); ● 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) ● ! 12345678 ● printf(“%x %X”, 0x6789ABCD, 0x6789ABCD) ● 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 ● abcdef ● Nhập vào %x ● 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 4 PUSH 3 PUSH 2 PUSH 1 PUSH format CALL printf Chuỗi định dạng ● Xét trường hợp ● printf(“%x %x %x %x”); ● PUSH format CALL 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