Kỹ thuật Override hàm API
Kỹ thuật "override" một hàm API Windows là kỹ thuật "lái" các lời gọi hàm đó trong các ứng dụng về 1 hàm mới do mình viết nhằm thực hiện những chức năng theo yêu cầu riêng của mình. Để hiểu rõ ràng chi tiết kỹ thuật này, bạn cần trang bị nhiều kiến thức về hệ thống Windows và lập trình hệ thống, dưới đây chỉ là các điểm cơ bản.
Trước hết bạn cần biết rằng mỗi chương trình chạy trên Windows có không gian bộ nhớ riêng dài 4GB được chia làm 2 phần:
• Phần đầu dài 2GB từ địa chỉ 0 tới 2GB, đây là vùng nhớ chứa code và dữ liệu riêng của chương trình, không một chương trình nào khác có thể truy xuất được vùng bộ nhớ này.
• Phần sau dài 2GB từ địa chỉ 2GB tới 4GB, đây là vùng nhớ dùng chung giữa các chương trình, nó chứa các module HĐH Windows, các driver thiết bị I/O và tất cả các hàm trong các thư viện liên kết động *.dll đang được dùng bởi bất kỳ ứng dụng nào.
Như vậy các hàm API Windows và các hàm thư viện *.dll đều nằm trong vùng nhớ dùng chung bởi tất cả chương trình, việc "override" 1 hàm nào đó về nguyên tắc sẽ ảnh hưởng đến mọi chương trình gọi hàm này. Ý tưởng "override" 1 hàm API được minh họa bằng hình sau:
Thí dụ bạn cần override hàm "Func" trong thư viện *.dll nào đó (mỗi hàm API Windows đều nằm trong file *.dll nào đó), các bước cơ bản cần thực hiện là:
• Viết 1 hàm khác có cùng giao tiếp sử dụng như hàm "Func" (prototype, interface), đặt hàm "NewFunc" này trong 1 thư viện *.dll nào đó của bạn để khi được nạp vào bộ nhớ, Windows sẽ nạp nó vào vùng nhớ dùng chung, nhờ đó tất cả ứng dụng đều dùng được.
• Xác định địa chỉ hàm "Func" trong vùng nhớ dùng chung.
• Lưu 5 byte đầu của hàm "Func" để phục vụ cho việc phục hồi lại sau này (nếu cần).
• Ghi 5 byte miêu tả lệnh jump về địa chỉ đầu của hàm "NewFunc" của bạn (lệnh jump là lệnh máy của CPU Intel, gồm 1 byte mã lệnh + 4 byte miêu tả địa chỉ nhảy đến).
Từ đây mỗi khi 1 ứng dụng nào đó gọi hàm "Func", lệnh đầu của hàm này bây giờ là lệnh jump nhảy về hàm "NewFunc", ở đây chứa các lệnh mà bạn viết theo yêu cầu xử lý của mình.
Các bước cơ bản trên được miêu tả bằng đoạn lệnh C++ sau đây:
Ví phỏng đường đời bằng phẳng cả, anh hùng hào kiệt có hơn ai. (Nguyễn Công Trứ)
Trước hết bạn cần biết rằng mỗi chương trình chạy trên Windows có không gian bộ nhớ riêng dài 4GB được chia làm 2 phần:
• Phần đầu dài 2GB từ địa chỉ 0 tới 2GB, đây là vùng nhớ chứa code và dữ liệu riêng của chương trình, không một chương trình nào khác có thể truy xuất được vùng bộ nhớ này.
• Phần sau dài 2GB từ địa chỉ 2GB tới 4GB, đây là vùng nhớ dùng chung giữa các chương trình, nó chứa các module HĐH Windows, các driver thiết bị I/O và tất cả các hàm trong các thư viện liên kết động *.dll đang được dùng bởi bất kỳ ứng dụng nào.
Như vậy các hàm API Windows và các hàm thư viện *.dll đều nằm trong vùng nhớ dùng chung bởi tất cả chương trình, việc "override" 1 hàm nào đó về nguyên tắc sẽ ảnh hưởng đến mọi chương trình gọi hàm này. Ý tưởng "override" 1 hàm API được minh họa bằng hình sau:
Thí dụ bạn cần override hàm "Func" trong thư viện *.dll nào đó (mỗi hàm API Windows đều nằm trong file *.dll nào đó), các bước cơ bản cần thực hiện là:
• Viết 1 hàm khác có cùng giao tiếp sử dụng như hàm "Func" (prototype, interface), đặt hàm "NewFunc" này trong 1 thư viện *.dll nào đó của bạn để khi được nạp vào bộ nhớ, Windows sẽ nạp nó vào vùng nhớ dùng chung, nhờ đó tất cả ứng dụng đều dùng được.
• Xác định địa chỉ hàm "Func" trong vùng nhớ dùng chung.
• Lưu 5 byte đầu của hàm "Func" để phục vụ cho việc phục hồi lại sau này (nếu cần).
• Ghi 5 byte miêu tả lệnh jump về địa chỉ đầu của hàm "NewFunc" của bạn (lệnh jump là lệnh máy của CPU Intel, gồm 1 byte mã lệnh + 4 byte miêu tả địa chỉ nhảy đến).
Từ đây mỗi khi 1 ứng dụng nào đó gọi hàm "Func", lệnh đầu của hàm này bây giờ là lệnh jump nhảy về hàm "NewFunc", ở đây chứa các lệnh mà bạn viết theo yêu cầu xử lý của mình.
Các bước cơ bản trên được miêu tả bằng đoạn lệnh C++ sau đây:
- Code: Chọn hết
//hàm override 1 hàm trong file *.dll
//hàm này nên nằm chung file thư viện *.dll với hàm newfunc
void Ovr_func(char* libname, char *funcname, FARPROC newfunc,
char *funcbuf, char *f_Installed, BOOL yes)
{
//khai báo các biến cần dùng
FARPROC OldFunction,NewFunction;
HANDLE UserLib, hprocess;
PDWORD phl;
BOOL fOk = FALSE;
DWORD dwOldProtect, dwNewProtect, dwnumbyte;
BYTE buff[20];
MEMORY_BASIC_INFORMATION mbi;
//tìm handle của process hiện hành
hprocess = GetCurrentProcess();
//load thư viện chứa hàm cần override
UserLib=LoadLibrary(libname);
//xác định địa chỉ đầu hàm cần override
OldFunction=GetProcAddress(UserLib,funcname);
//xác định địa chỉ đầu hàm mới
NewFunction=MakeProcInstance(newfunc,hModuleDll);
//tạo lệnh jump từ hàm cũ đến hàm mới
buff[0]= 0xe9;
phl = (PDWORD)&buff[1];
*phl = (DWORD)NewFunction-(DWORD)OldFunction-5;
// đọc các thuộc tính bảo vệ bộ nhớ hiện hành của trang chứa hàm cũ
VirtualQuery((LPVOID)(DWORD)OldFunction, &mbi, sizeof(mbi) );
dwNewProtect = mbi.Protect;
dwNewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ);
dwNewProtect |= (PAGE_READWRITE);
// hiệu chỉnh lại các thuộc tính bảo vệ bộ nhớ hiện hành của trang chứa hàm cũ để có thể ghi nội dung lên
fOk=VirtualProtect((LPVOID)(DWORD)OldFunction, 5,dwNewProtect, &dwOldProtect);
// đọc các byte đầu cần lưu giữ của hàm cũ
fOk=ReadProcessMemory(hprocess, (LPVOID)(DWORD)OldFunction,
(LPVOID)(DWORD) funcbuf, 5, &dwnumbyte);
// ghi lệnh jump vào đầu hàm cũ
fOk=WriteProcessMemory(hprocess, (LPVOID)(DWORD)OldFunction,
(LPVOID)(DWORD) buff, 5, &dwnumbyte);
// giải phóng file thư viện
FreeLibrary(UserLib);
}