Kĩ thuật hook trong Windows

Một phần của tài liệu XÂY DỰNG hệ THỐNG từ điển BKDICTIONARY sử DỤNG CHUẨN DICT (Trang 38)

II. KIẾN THỨC CHUNG

1.Kĩ thuật hook trong Windows

a. Thư viện liên kết động (Dynamic-Link Library – DLL):

Thư viện liên kết động DLL là một tập tin chứa các hàm được gọi từ chương trình chính hoặc từ các chương trình và DLL khác nhằm thực hiện mtộ tác vụ nào đó. DLL chỉ được kích hoạt khi một module chương trình gọi đến các hàm chứa trong nó. DLL được ra đời cùng với sự ra đời của phiên bản Windows đầu tiên và đã trở thành một phần gắn liền với Windows cho đến nay.

Liên kết động “Dynamic Link” là cơ chế Windows sử dụng để liên kết lời gọi hàm từ một chương trình đến thư viện DLL, nơi thực sự cài đặt hàm đó. Khác biệt với liên kết tĩnh “Static Link” được dùng để liên kết lời gọi hàm khi biên dịch tạo thành một file chạy duy nhất. Mã thực thi của hàm thường đặt trong những tập tin như .OBJ,.LIB hay .RES. Tiếp đến, quá trình biên dịch sẽ nhúng nội dung hàm vào chương trình thực thi .EXE của Windows. Liên kết tĩnh không hiệu quả bởi vì đoạn mã để thực thi một hàm thường phải nhứng nhiều lần vào các tập tin .EXE khác nhau, đặc biệt có những hàm xuất hiện mọi nơi ví dụ như các hàm thực hiện chức năng vào ra. Điều này làm hạn chế sự khả năng của các ứng dụng rất nhiều. Ngượi lại, với sự hỗ trợ của hệ điều hành, trong cơ chế liên kết động, tất cả cả các hàm càn thiết được đặt trong thư viện đông DLL. Các tập tin thực thi .EXE hay các module khác nhau đều có khả năng gọi đến cùng một hàm chứa trong các thư viện DLL. Những tập tin hệ thống như KERNEL32.DLL, USER32.DLL, GDI32.DLL, KEYBOARD.DRV, SYSTEM.DRV hay MOUSE.DRV và các trình điều khiển thiết bị màn hình, máy in,… đều là các dạng thư viện liên kết động. Mọi chương trình Windows đều có thể truy xuất và sử dụng chung những hàm chứa trong các DLL này.

Trong môi trường Win32, mỗi process có thể sử dụng 4 GB không gian địa chỉ (0x00000000 đến 0xFFFFFFFF). Các process không có khả năng tạo ra con trỏ tham khảo đến các vùng bộ nhớ của các process khác. Do vậy, nếu một process có một lỗi mà ghi đè lên bộ nhớ tại một địa chỉ ngẫu nhiên, thì sẽ gây ra lỗi access violation nếu địa chỉ đấy đang được sử dụng bởi một process khác.

Trong Win16 thì vấn đề lớn nhất đấy là mọi process đều chạy trong cùng một không gian địa chỉ, do đó một process có thể ghi đè vào vùng nhớ thuộc một process khác, thậm chí là vùng nhớ của hệ điều hành. Win32 thì rất khó để một process có thể gây lỗi như vậy.

Môi trường Win32 với miền không gian địa chỉ như vậy rất thuận lợi cho cả người dùng và nhà phát triển phần mềm. Đối với nhà phát triển thì dễ quản lý và phát triển chương trình. Đối với người dùng thì hệ thống sẽ ổn định hơn.

 Tuy nhiên sau đây là một số trường hợp cần phải breaking through proces boundary walls để truy cập vào không gian bộ nhớ của process khác. Subclassing một cửa sổ được tạo bởi một process khác.

 Phục vụ cho việc debug (Xác định DLLs nào mà một process sử dụng).

 Hooking các process khác

Khi muốn thay đổi trạng thái, hành động của một cửa sổ. Trong win16, ta chỉ đơn giản gọi hàm SetWindowLong để thay đổi địa chỉ the window procedure trỏ đến địa chỉ của hàm WndProc:

SetWindowLong(hwnd, GWL_WNDPROC, MySubclassProc);

Ta chỉ ra cho hệ thống là mọi thông điệp được gửi đến cửa sổ được chỉ định bởi hwnd bây giờ sẽ được chuyển sang MySubclassProc (thay thế cho hàm bàn đầu). Một cách hiểu đơn giản là bây giờ hệ thống sẽ thấy cái hàm MySubclassProc sẽ liên kết với cái cửa sổ (hwnd) và sẽ trực tiếp gọi MySubclassProc.

Vấn đề ở trong Win32 đấy là cái hàm Subclass ở trong một vùng địa chỉ khác. Giả sử có Process A đang chạy. User32.dll chịu trách nhiệm việc nhận và gửi các thông điệp đến các cửa sổ được tạo ra bởi các thread chạy trong ProcessA. Khi có thông điệp thì đâu tiên nó xác định địa chỉ của hàm WndProc của cửa sổ ở đâu và sau đấy thì gọi hàm đấy, thông qua handle của cửa sổ, thông điệp, giá trị của wParam và lParam. Sau khi WndProc xử lý thông điệp, User32 lại chờ đón các thông điệp tiếp theo để lại tiến hành gửi.

Bây giờ hãy hình dung tiến trình đang ở Process B, và ta muốn subclass một cái cửa sổ được tạo ra bởi một thread trong Process A. Thì Process B đầu tiên phải xác định handle của cửa số muốn subclass. Điều đấy có thể được tiến hành bằng rất nhiều cách khác nhau.

SetWindowLong(hwnd, GWL_WNDPROC, MySubclassProc);

Đầu tiên là tìm handle của cửa số và sau đấy thread in Process B gọi hàm SetWindowLong để cố gắng thay đổi địa chỉ của hàm WndProc của cửa sổ.

Tuy nhiên, trong Win32 thì hàm này không làm gì mà đơn giản chỉ trả về NULL. Code trong SetWindowLong kiểm tra nếu một process muốn thay đổi địa chỉ hàm WinProc của một cửa số được tạo ra bởi một process khác thì đơn giản là từ chối process đó đi.

Điều gì có thể xảy ra nếu cái hàm SetWindowLong trong Win32 có thể thay đổi the Windows’ WndProc? Nghĩa là hệ thống có thể liên kết địa chỉ của hàm MySubclassProc với các cửa số xác định khác. Nghĩa là khi cửa sổ này được gửi một thông điệp thì User32 ở trong Process A có thể nhận thông điệp, lấy địa chỉ của hàm MySubclassProc và cố gắng gọi cái địa chỉ này. Nhưng sau đấy, một vấn đề vô cùng nghiêm trọng xảy ra. Mysubclass nằm trong Process B, và Process A có thể là Active Process. Chú ý, nếu User32 gọi địa chỉ hàm này thì có thể User32 đang gọi một địa chỉ này trong không gian nhớ của Proces A, điều này dẫn đến hiện tượng memory access violation.

Để tránh vấn đề trên, ta muốn hệ thống biết hàm MySubClass nằm trong không gian nhớ của Process B và để sau đấy hệ thống chuyển đổi vị trí (địa chỉ vùng nhớ) trước khi gọi hàm SubClass. Suy nghĩ trên không được vì một số lý do sau đây:

- Mọi Process đều được đối xử công bằng, không ưu tiên Process nào cả - Chuyện đổi process active rất là tốn bộ nhớ

- Một thread trong Process B được thi hành trong MySubclassProc thì hệ thống biết sử dụng cái nào, cái thread đang tồn tại trong A hay là cái thread mới trong B

Vậy làm thế nào để User32 báo địa chỉ của cái hàm SubClas đấy là nằm trong A hay nằm trong B?Bởi vì không có giải pháp nào nổi bật cho những vấn đề trên cho nên Microsoft quyết định không cho phép hàm SetWindowLong thay đổi window procedure bằng một cái hàm khác ở một Process khác.

Tuy nhiên, chúng ta phải đi bằng một con đường khác. Nếu bạn có thể làm cách nào đấy để mà đưa được địa chỉ của cái hàm SubClass của bạn vào không gian nhớ của Procees A thì bạn có thể đơn giản gọi hàm SetWindowLong (Không còn sợ sự khác biết về khôn gian nhớ nữa)

Đấy gọi là kỹ thuật đưa một DLL vào không gian nhớ của một process. Có 3 cách để làm điều đấy. Chúng ta sẽ cùng nghiên cứu từng cách một.

Dùng Registry:

HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Wi ndows\AppInit_DLLS

Chèn giá trị string là đường dẫn các dll. Sau khi khởi động lại máy vi tính, User32 sẽ gọi hàm LoadLibrary cho một DLL trong chuỗi đấy, sau khi mỗi thư viện load lên thì mỗi thư viện liên kết với DLLMain và được gọi với một giá trị fdwReason thuộc DLL_PROCESS_ATTACH. User32 không kiểm tra xem một DLL có được nạp thành công hay không?

Một số vấn đễ khi dùng theo cách này:

- Phải Restart lại máy mỗi khi muốn thêm một DLL (hay ngược lại) để update hệ thống

- DLL chèn vào được sắp xếp vào process map User32.dll, vì vậy ta sẽ không thể sử dụng DLL trong một chương trình compiler hoặc linker. - DLL đó được sắp xếp vào mỗi chương trình nền tảng GUI (Graphic user

interface), trong khi ta chỉ muốn đính cái thư viện của bạn vào một hoặc vài processes (adsbygoogle = window.adsbygoogle || []).push({});

- Ta không thể sử dụng DLL ấy trong một khoảng thời gian xác định.

Chèn DLL sử dụng Windows Hooks.

Process A cài đặt một thông điệp WH_GETMESSAGE hook để nhìn thấy các thông điệp gửi bởi windows trong hệ thống. Hook được xây dựng như sau:

HHOOK hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, hinstDLL, NULL);

WH_GETMESAGE xác định loại Hook cài đặt. GetMsgProc xác định địa chỉ của hàm mà hệ thông sẽ gọi bất cứ khi nào cửa số tiến hành một thông điệp hinstDLL xác định DLL mà chứa cái hàm GetMsgProc function. Trong Win32 thì DLL’s hinstDLL xác định giá trị 32 bit của địa chỉ một nhớ khi mà DLL được map vào trong một không gian nhớ của một Process NULL xác định thread để hook. ở đây là NULL để chúng ta báo cho hệ thống biết là chúng ta hook toàn bộ các threads trong hệ thống.

Cách mà hệ điều hành tiến hành như sau:

 Một thread trong Process B chuẩn bị gửi một thông điệp cho một cửa sổ.

 Hệ thống sẽ kiểm tra có một hàm hook các message được cài đặt trong cái thread này hay không?

 Hệ thống kiểm tra xem có hàm GetMsgProc đã được sắp xếp trong không gian nhớ của Process B chưa?

 Nếu cái DLL chưa được map (sắp xếp) thì hệ thống sẽ “gây áp lực” để map cái DLL đấy vào không gian nhớ của Process B.

 Hệ thống sẽ xem cái DLL’hinstDLL có cùng một vị trí as it apllies to Proces A. Nếu là một thì địa chỉ bộ nhớ của hàm GetMsgPro cũng giống

nhau trong 2 không gianh nhớ của 2 process. Trong trường hợp này hệ thống đơn giản gọi hàm GetMsgPro trong Process A’s address space.

 Nếu the hinstDLls khác nhau, hệ thống phải xác định vịt trí ảo của hàm GetMsgProc trong ProcessB’ address space. Tính theo công thức:GetMsgProc B = hinstDLL B + (GetMsgProc A - hinstDLL A)

 Hệ thống increments a lock count on the DLL’s mapping in Process B

 Hệ thống gọi hàm GetMsgProc trong không gian nhớ của Process B

 Khi mà hàm GetMsgProc trả về, hệ thống decrements a lock count on the DLL’s mapping in Proces B.

Một điều chú ý là sau khi DLL được gọi thì ta có thể sử dụng tất cả các hàm có trong DLL đấy từ các threads chạy trong Process B. Khác với kiểu dùng Registry, chúng ta có thể unmap DLL khi mà không cần dùng đến nữa.

BOOL UnhookWindowsHookEx(HHOOK hhook);

Khi một thread mà gọi hàm UnhookWindowsHookEx. Bạn sẽ cần phải gọi lại nếu hệ thống gọi đến hàm GetMsgProc. Điều này sẽ tránh được lỗi Memory Access Violation.

Chèn DLL sử dụng Remote Threads.

Phương pháp này rất khó để sử dụng nhưng nó lại cung cấp năng lực rất lớn. Nó được dùng trong nhiều trong Win32: Process, Threads, thread synchronization, structured exeption handling, virual memory management and Unicode. Để inject một DLL theo phương pháp này. Chúng ta cần tạo và thực thi threads trong the target process’ addresss space và truy cập vào physical storage committed to a thread’s stack. Nhưng đầu tiên ta phải hiểu làm thế nào hệ thống tạo ra thread và một thread sử dụng stack của nó như thế nào.

Để làm cho biến này có thể dùng chung cho mọi process ta phải đặt nó vào phân đoạn nhớ dùng chung. Data cùng phân đoạn nhớ này phải được khởi tạo, nếu không data sẽ được chứa trong phân đoạn mặc định và chỉ thị #pragma data_seg() sẽ không có tác dụng?

//--- // Shared by all processes variables

//--- #pragma data_seg(".HKT")

HHOOK sg_hGetMsgHook = NULL; BOOL sg_bHookInstalled = FALSE; // We get this from the application who // calls SetWindowsHookEx()'s wrapper

HWND sg_hwndServer = NULL;

#pragma data_seg() kèm theo #pragma comment(linker, "/section:.HKT, rws") Một khi DLL đã được load vào add space của tiến trình, không có cách nào gỡ bỏ nó trừ khi UnhookWindowsHookEx()được gọi hoặc hook app bị ngừng. Khi chương trình gọi UnhookWindowsHookEx() hệ điều hành sẽ duyệt qua một danh sách các tiến trình mà đã load hook DLL. Hệ thống sẽ giảm dần DLL's lock count về không và khi đó DLL sẽ tự động unmap khỏi add space của tiến trình.

Sau đây là ưu điểm của phương pháp này:

- Có thể dùng trên 9x/NT/2K và hy vọng là các version trong tương lai - Không giống như ghi vào registry, pp này cho phép unload DLL khi

nó ko còn cần thiết với lời gọi hàm UnhookWindowsHookEx(). Tuy nhiên nó cũng có nhược điểm sau: (adsbygoogle = window.adsbygoogle || []).push({});

- Làm giảm đáng kể hiệu năng của hệ thống vì nó làm tăng lượng lớn việc processing thông điệp

- Debug khó khăn.Tuy nhiên có thể làm đơn giản bằng chạy nhiều thể hiện của VC một lúc?

- Có thể gây treo hệ thống.

Hook Win32 API.

Sau khi chèn DLL vào một tiến trình, ta có thể thêm vào các hoạt động cho các tiến trình. Tuy nhiên các tiến trình của Windows luôn có một số hàm hoạt động cố định theo thiết kế của Windows, nếu chúng ta muốn thay đổi những hoạt động đó thì phương pháp chèn DLL vào chưa thể thực hiện được. Ở đây chúng ta đi nghiên cứu một kĩ thuật có khả năng thực hiện hành động đó là kĩ thuật hook Win32 API.

Có 2 ý tưởng thực hiện là:

• Viết chèn mã thực hiện hàm trong bộ nhớ. Cách thực hiện là tìm ra vị trí mã thực hiện hàm đó rồi thay thế mã thực hiện mà ta cần vào vị trí đó bằng cách thay đổi mã cũ bằng một lệnh JUMP đến mã lệnh mà ta viết ra. Tuy nhiên phương pháp này thấy ngay tính bất cập của nó khi đưa ra cơ chế không an toàn chút nào là thay đổi bộ nhớ của một tiến trình. Và thực tế phương pháp này chỉ thực hiện được trên Win16 còn trong các hệ điều hành hiện tại thì phương pháp này không thể thực hiện được.

• Ý tưởng thứ 2 xuất phát từ việc tất cả các hàm Win32 API đều được đặt trong một DLL nào đó của hệ thống. Khi chạy chương trình, chương trình sẽ lần lượt load mã thực hiện từ DLL đó vào trong đoạn mã thực hiện của mình. Vậy nếu có thể điều khiển được quá trình load mã thực hiện thì ta hoàn toàn có thể thay thế bằng một đoạn mã thực hiện mà ta mong muốn

mà không phải tiến hành bất cứ sự thay đổi nào trong bộ nhớ. Đó chính là ý tưởng của kĩ thuật hook windows API bằng cách điều khiển quá trình import các module chương trình từ DLL.

Phương pháp này thực hiện được thực hiện như sau:

1. Chèn DLL vào process cần thay đổi theo một trong các phương pháp trên.

2. Kích hoạt API trong module: Thay đổi IAT của hàm trong module. Duyệt qua Import Table của module, tìm và thay đổi địa chỉ hàm cần hook bằng địa chỉ hàm mới. Thực hiện như phần dưới.

 Thay thế địa chỉ hàm truy nhập từ DLL vào bằng một hàm khác cho một tiến trình nào đó.

PROC *ReplaceIATEntryInOneMod(PCSTR pCalleeModName,PROC pfnCur,PROC pfnNew,HMODULE hModCaller)

{ if(pfnCur== pfnOrigExitProc) { return NULL; } ULONG ulSize; PIMAGE_IMPORT_DESCRIPTOR pImportDesc; //if Win9x if(osvi.dwMajorVersion< 5) { IMGDIRENTRYTODATA pfnImageDirectoryEntryToData=

(IMGDIRENTRYTODATA)GetProcAddress(hDbghelp,"ImageDirectoryEntryToData");

pImportDesc=PIMAGE_IMPORT_DESCRIPTOR)(pfnImageDirectoryEntryToData)(

hModCaller,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulSize); }

else//WinXP

{ pImportDesc= (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(

hModCaller,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulSize); }

if(pImportDesc== NULL)

return NULL;//no import section

for(;pImportDesc->Name;pImportDesc++)

if(lstrcmpiA(pModName,pCalleeModName)== 0)

break; }

if(pImportDesc->Name== 0)

return NULL;

PIMAGE_THUNK_DATA pThunk= (PIMAGE_THUNK_DATA)( (PBYTE)hModCaller +

pImportDesc->FirstThunk); (adsbygoogle = window.adsbygoogle || []).push({});

for(;pThunk->u1.Function;pThunk++) {//get address of function

PROC *ppfn= (PROC *)&pThunk->u1.Function;

BOOL bFound= (*ppfn== pfnCur);

/*Special hack for Win9X, which builds stubs for imported functions in system DLLs (Loaded above 2GB). These stubs are writeable, so we have to explicitly check for this case */

if ( (*ppfn > pvMaxAppAddr) && !bFound ) { PBYTE pbInFunc = (PBYTE) *ppfn;

// Is this a wrapper (debug thunk) represented by PUSH instruction?

if (pbInFunc[0] == cPushOpCode) { ppfn= (PROC*) &pbInFunc[1];

bFound= (*ppfn == pfnCur); }

}

// Is this the function we're looking for?

if(bFound)

{//change func address

//Get the current protection attributes

Một phần của tài liệu XÂY DỰNG hệ THỐNG từ điển BKDICTIONARY sử DỤNG CHUẨN DICT (Trang 38)