• Thứ Hai, 19/06/2006 09:45 (GMT+7)

    Override hàm API trong WinXP bằng Delphi

    Câu hỏi :
    Xin hướng dẫn kỹ thuật Override hàm API trong WinXP, ví dụ Override hàm SetSystemTime hoặc SetLocalTime của Windows bằng Delphi.


    Trả lời :
    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, ở đây chúng tôi chỉ trình bày 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 (lưu ý rằng môi trường lập trình Delphi dùng ngôn ngữ Pascal nên khó viết đoạn lệnh dưới đây hơn C++):
    //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);
    }
    Chuyên mục: Lập trình