4. Buffer overflow trong cấu hình SPARC 32 bit
Để biết chi tiết hơn về cấu hình SPARC [1] của hãng Sun Microsystem, bạn có thể tham khảo phiên bản 9 [2] của SPARC Architecture Manual.
Thường thì trong cấu hình SPARC mỗi CPU có từ 40 đến khoảng 512 thanh ghi. Trong đó chỉ có 32 thanh ghi hiện hành dành cho một chương trình đang chạy ở bất kỳ thời điểm nào. Mỗi thanh ghi chứa 32 bits. Có 4 nhóm thanh ghi, mỗi nhóm chứa 8 thanh:
- Nhóm toàn cục (global): %g0 đến %g7
- Nhóm cục bộ (local): %l0 đến %l7
- Nhóm nhập (in): %i0 đến %i7
%i6 chứa FP giống như %ebp trong cấu hình Intel IA-32. Biến %fp cũng trỏ đến %i6 cho tiện.
%i7 chứa return address của hàm hiện hành. Cấu hình SPARC không PUSH return address vào stack như cấu hình Intel. Tuy vậy, ta vẫn có thể dùng buffer overflow để thay đổi return address với cách làm hơi khác trường hợp của Intel một chút.
- Nhóm xuất (out): %o0 đến %o7
%o6 chứa SP chỉ đến đỉnh của stack frame hiện hành.
%o7 được dùng để lưu return address cho hàm được gọi (callee) biết trả điều khiển về đâu.
Trong cấu hình SPARC, các tham số hàm, return address, … được truyền cho hàm được gọi dùng các thanh ghi này, thay vì được PUSH vào stack như trong cấu hình Intel.
Khi hàm foo() gọi hàm bar(), ngay trước khi bar() hoạt động thì nội dung các thanh ghi từ %i0 đến %i7, rồi %l0 đến %l7 của foo() được bỏ vào stack trước (tổng cộng 64 bytes), sau đó nội dung của tất cả các thanh ghi %o* được chuyển vào %i*. Như vậy, các thanh ghi xuất của hàm foo() “biến thành” các thanh ghi nhập của hàm bar(). Hàm bar() sau đó sẽ dùng nội dung của %i6 và %i7 để thiết lập lại trạng thái chương trình như cũ khi bar() kết thúc. Các thanh ghi %i0 đến %i5 được dùng để truyền tham số cho bar(). Nếu có nhiều tham số hơn nữa thì phải dùng stack.
Đến đây, ta đã có 64 bytes trên stack frame mới cho bar(). Kế tiếp có 4 bytes trỏ đến một struct nếu bar() trả về một struct. Kế đến có 24 bytes dành cho tham số (trong trường hợp %i0 đến %i5 không đủ dùng). Cuối cùng trên stack là các biến cục bộ của bar().
Để thay đổi dòng hoạt động của chương trình, ta có thể overflow lên nội dung thanh ghi %i7 được lưu trong stack của hàm foo(). Nội dung này là return address sau khi foo() chạy xong. Sau đây là một ví dụ trong cấu hình SPARC:
/* ---------------------------------------------------------------------
* Vi' du. 3:
* ---------------------------------------------------------------------
*/
void bar() {
int buffer[24]; int i;
(*(buffer+239)) += 8;
}
void bar8() { bar(); }
void bar7() { bar8(); }
void bar6() { bar7(); }
void bar4() { bar6(); }
void bar3() { bar4(); }
void bar2() { bar3(); }
void bar1() { bar2(); }
void foo() { bar1(); }
int main() {
int x=0; foo(); x=10;
printf("x = %d\n", x);
return 0;
}
Lý do mà ta dùng nhiều hàm bar() là để buộc hệ điều hành phải dùng giá trị thanh ghi lưu trong stack thay vì dùng giá trị có sẵn trong thanh ghi. Trong các chương trình thực tế, hệ điều hành sẽ thường phải dùng các thanh ghi làm nhiều việc tính toán khác; và vì thế ta không nhất thiết phải có độ gọi hàm sâu như vậy để hiện thực hóa lỗi tràn bộ đệm.
Article printed from Blog Khoa Học Máy Tính: http://www.procul.org/blog
URL to article: http://www.procul.org/blog/2005/08/18/l%e1%bb%97i-tran-b%e1%bb%99-o%e1%bb%87m-4/
URLs in this post:
[1] SPARC: http://www.sparc.org/
[2] phiên bản 9: http://www.sparc.com/standards/SPARCV9.pdf