Trao đổi với tôi

http://www.buidao.com

11/7/09

[Programming] Giới thiệu về kỹ thuật lập trình hook

*** Chôm chỉa bên HVA về ***

Có bao giờ các bạn đặt ra câu hỏi là tại sao chúng ta có thể gõ tiếng Việt được trong Microsoft Word và các phần mềm soạn thảo văn bản khác hay không? Rõ ràng là chúng ta không thể can thiệp vào mã của Microsoft Word để sửa thành tiếng Việt khi chúng ta muốn soạn thảo văn bản tiếng Việt. Vậy thì tại sao các phần mềm như VietKey, VNI-TanKy lại có thể làm được điều này? Câu trả lời là sử dụng các Hook. Trong bài viết này, chúng ta sẽ cùng tìm hiểu xem Hook là gì mà có thể làm được điều thần kỳ như vậy!


Truớc khi chúng ta tìm hiểu về Hook , chúng ta nhắc lại một chút về quá trình xử lý thông điệp của hệ điều hành Windows


Quá trình xử lý thông điệp của Windows diễn ra như sau : Đầu tiên từ các hành động của nguời dùng như là : click chuột, nhấn phím, .. thì hệ điều hành sẽ chuyển các hành động tương ứng thành các thông điệp (message). Rồi sau đó Windows đẩy các message này vào hàng đợi của hệ thống (system queue) và từ system queue các message đuợc chuyển cho các hàng đợi của ứng dụng (application queue). Từ lúc này các ứng dụng sẽ lấy các message này trong hàng đợi ứng dụng của mình để xử lý (thông qua các vòng lặp chờ thông điệp - message loop).

1. Hook là gì ?

Hook là cơ chế mà nhờ đó một hàm có thể chặn các sự kiện (message, mouse actions, keystrokes ) trước khi chúng được gửi đến hàng đợi của ứng dụng. Các hàm này có thể thực hiện một số thao tác trên sự kiện, và trong một vài trường hợp có thể định nghĩa lại hoặc hủy bỏ sự kiện mà nó chặn được. Một điểm quan trọng cần lưu ý là các hàm này được gọi bởi chính Windows chứ không phải bởi ứng dụng của chúng ta.

Windows hỗ trợ nhiều loại hook khác nhau, mỗi loại nhắm đến việc chặn bắt một loại thông điệp cụ thể nào đó. Ví dụ, một ứng dụng có thể sử dụng WH_KEYBOARD để giám sát sự di chuyển của thông điệp bàn phím trong hệ thống. Nhờ loại hook này mà một chương trình có thể can thiệp vào và tạo ra khả năng gõ tiếng Việt khi soạn thảo văn bản. Một loại hook khác là WH_MOUSE cho phép theo dõi các thông điệp liên quan đến hoạt động của con chuột.



Hình trên mô tả quá trình xử lý thông điệp của Windows khi có sử dụng các Hook .Thì như trên hình vẽ chúng ta có thể thấy rõ ràng rằng ,một khi chúng ta sử dụng các Hook thì các Hook này sẽ được đặt nằm giữa System Queue và Application Queue .

2. Cơ chế hoạt động của Hook

Hệ thống duy trì một chuỗi hook (hook chain) cho mỗi loại hook. Mỗi chuỗi này là một danh sách liên kết các con trỏ đặt biệt, con trỏ này chính là các hàm callback của ứng dụng có sẵn, nó còn được gọi là hàm hook (hàm lọc, filter function)

Khi có một thông điệp được sinh ra thuộc một loại hook nào đó, nó sẽ được hệ thống đẩy đi vào hàm hook đầu tiên trong chuỗi, lần lượt từng hàm một (qua tất cả các hook trong chuỗi). Công việc của hàm hook có thể phức tạp hay đơn giản tùy thuộc vào từng loại hook. Hàm hook cho một số loại chỉ có thể giám sát, số khác có thể sửa đổi thông điệp hoặc dừng lại việc xử lý thông điệp trên chuỗi hook trước khi chúng đến các hook tiếp theo hoặc đến cửa sổ đích.

3. Ứng dụng của Hook

- Cho phép tạo ra các chương trình hỗ trợ gõ tiếng Việt như : Vietkey

- Cho phép tạo ra các chuơng trình Test tự động phần mềm (bằng cách phát sinh các sự kiện phím, chuột giống như người dùng đang nhập vào).

- Cho phép thay đổi giao diện các ứng dụng đang chạy.

- Cho phép xem phần trợ giúp của các ứng dụng bằng việc nhấn một phím nào đó, ví dụ như nhấn F1 chẳng hạn

- Và nhiều ứng dụng khác nữa tùy vào trí tưởng tượng của các bạn !…

4 . Cài đặt Hook

Giao diện lập trình ứng dụng (API) của Windows cung cấp 3 hàm để thao tác với hook :

• SetWindowsHookEx

• UnhookWindowsHookEx

• CallNextHookEx

a) Cài đặt một Filter Function vào chuỗi các Filter Function của một hook
Tác vụ này được thực hiện thông qua hàm SetWindowsHookEx, khai báo của hàm này như sau :

HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn,
HINSTANCE hMod, DWORD dwThreadId);

Ý nghĩa của từng tham số :

idHook: Xác định loại hook mà ta muốn cài đặt, tham số này có thể là một trong các giá trị sau :

• WH_CALLWNDPROC : đặt một thủ tục hook quản lý các thông điệp trước lúc hệ thống gởi chúng tới cửa sổ đích.

• WH_CALLWNDPROCRET : đặt một thủ tục hook quản lý các thông điệp sau khi chúng được xử lý bởi thủ tục cửa sổ đích.

• WH_CBT : đặt một thủ tục hook nhận những thông báo có ích tới ứng dụng huấn luyện trên cơ sở tính toán (CBT).

• WH_DEBUG : đặt một thủ tục hook có ích cho việc debug những thủ tục hook khác.

• WH_FOREGROUNDIDLE : đặt một thủ tục hook sẽ được gọi khi thread foreground của ứng dụng sẽ trở thành không dùng đến. Hook này có ích cho hoạt động những nhiệm vụ (task) độ ưu tiên thấp trong thời gian không được dùng đến.

• WH_GETMESSAGE : đặt một thủ tục hook quản lý các thông điệp được post tới hàng đợi thông điệp.

•WH_JOURNALPLAYBACK : đặt một thủ tục hook post những thông điệp được ghi trước đó bởi thủ tục hook WH_JOURNALRECORD.

•WH_JOURNALRECORD : đặt một thủ tục hook ghi những thông điệp đầu vào được post tới hàng thông điệp hệ thống. Hook này có ích cho việc ghi các macro.

• WH_KEYBOARD : đặt một thủ tục hook quản lý các thông điệp keystroke.

• WH_MOUSE : đặt một thủ tục hook quản lý các thông điệp chuột.

• WH_MSGFILTER: đặt một thủ tục hook quản lý các thông điệp được kết sinh như là một kết quả cuả sự kiện đầu vào ở trong dialog box, message box, menu hay scroll bar.

• WH_SYSMSGFILTER : đặt một ứng dụng các thông điệp được kết sinh như là kết quả của một sự kiện đầu vào ở trong dialog box, message box, menu hay scroll bar. Thủ tục hook quản lý những thông điệp này cho tất cả các ứng dụng trong hệ thống.

Mỗi giá trị trên xác định một loại hook mà ta muốn cài đặt, mỗi loại hook có một ý nghĩa và tình huống sử dụng khác nhau.

lpfn : Địa chỉ của Filter Function mà ta muốn gắn với hook.

hMod : Handle của module chứa Filter Function. Nếu ta cài đặt một hook cục bộ (nghĩa là sự thực thi của Filter Function chỉ ảnh hưởng đối với tiến trình cài đặt hook), tham số này phải là NULL. Còn nếu chúng ta muốn có một hook với phạm vi toàn hệ thống (tức là mọi tiến trình đang hiện hữu đều chịu ảnh hưởng bởi Filter Function của chúng ta), tham số này sẽ là Handle của DLL chứa Filter Function.

dwThreadID : Định danh của thread ứng với hook đang được cài đặt . Nếu tham số này là một số khác 0, Filter Function được gắn với hook chỉ được gọi trong ngữ cảnh của một thread xác định. Còn nếu dwThreadID = 0, Filter Function sẽ có phạm vi toàn hệ thống, và dĩ nhiên, nó sẽ được gọi trong ngữ cảnh của bất kỳ thread nào đang tồn tại trên HĐH. Có thể sử dụng hàm GetCurrentThreadId để lấy được handle của thread muốn cài đặt hook.

Một hook có thể được sử dụng ở mức hệ thống, ở mức cục bộ, hoặc ở cả hai mức vừa nêu. Bảng sau mô tả các loại hook cùng tầm ảnh hưởng của nó :

WH_CALLWNDPROC
Thread , Global

WH_CALLWNDPROCRET
Thread , Global

WH_CBT
Thread , Global

WH_DEBUG
Thread , Global

WH_FOREGROUNDIDLE
Thread , Global

WH_GETMESSAGE
Thread , Global

WH_JOURNALPLAYBACK
Global

WH_JOURNALRECORD
Global

WH_KEYBOARD
Thread , Global

WH_MOUSE
Thread , Global

WH_MSGFILTER
Thread , Global

WH_SYSMSGFILTER
Global


Với một loại hook xác định, hook cục bộ sẽ được gọi trước, sau đó là hook toàn cục.

b) Gỡ bỏ một Filter Function ra khỏi chuỗi các Filter Function của một hook

Windows cung cấp hàm UnhookWindowsHookEx giúp chúng ta thực hiện việc này. Khai báo của nó như sau :

BOOL UnhookWindowsHookEx( HHOOK hhk);

Tham số : hhook chỉ ra hàm hook được dỡ bỏ . Đây là giá trị được trả vể bởi hàm SetWindowsHookEx khi hàm Hook được cài đặt.

Chú ý : Hàm UnhookWindowsHookEx phải được sử dụng trong sự kết hợp với hàm SetWindowsHookEx.

c) Chi tiết về Filter Function

Filter Function là một hàm được gắn với loại hook mà chúng ta muốn cài đặt. Hàm này được gọi bởi hệ điều hành Windows chứ không được gọi bởi ứng dụng, đây cũng là lý do mà người ta thường gọi nó là “Callback Function”. Tuy nhiên , để thống nhất về mặt thuật ngữ, từ nay về sau chúng ta vẫn gọi nó là Filter Function.

Tất cả các Filter Function đều có dạng sau :

LRESULT CALLBACK FilterFunc(int nCode, WPARAM wParam, LPARAM lParam);

Ở đây “FilterFunc” chỉ là tên hàm tượng trưng, khi cài đặt, Filter Function sẽ có tên bất kỳ theo ý của người lập trình .

Ý nghĩa của từng tham số truyền cho hàm :

nCode : tham số này thường được gọi là “hook code”, Filter Function sử dụng giá trị này để quyết định cách thức xử lý đối với sự kiện. Giá trị của hook code tùy thuộc vào từng loại hook cụ thể, và mỗi loại hook sẽ có tập hợp những giá trị hook code đặc trưng của riêng mình. Có một quy luật mà dường như các Filter Function của mọi loại hook cần tuân thủ : Khi Windows truyền cho hàm giá trị hook code âm, Filter Function không được xử lý sự kiện mà phải gọi hàm CallNextHookEx với chính những tham số mà hệ điều hành truyền cho nó. Sau đó, nó phải trả về giá trị được trả về bởi hàm CallNextHookEx.

wParam, lParam: Đây là những thông tin cần thiết cho Filter Function trong quá trình xử lý sự kiện. Các giá trị này sẽ có ý nghĩa khác nhau tuỳ thuộc vào từng loại hook. Ví dụ , Filter Function gắn với hook WH_KEYBOARD sẽ nhận mã phím ảo (Virtual-Key Code) từ wParam, đồng thời có được từ lParam thông tin mô tả trạng thái của bàn phím khi sự kiện gõ phím xảy ra.

d) Gọi Filter Function kế tiếp trong chuỗi các Filter Function

Khi một hook được cài đặt, Windows gọi hàm đầu tiên trong chuỗi các Filter Function, và kể từ thời điểm này, trách nhiệm Windows không còn nữa. Filter Function hiện hành phải đảm bảo với hệ thống là có được lời gọi đến hàm kế tiếp trong chuỗi các Filter Function. Bởi lẽ, có thể có một ứng dụng khác cũng cài đặt cùng loại hook để thi hành một số tác vụ nào đó, và nếu như ta không cho Filter Function của ứng dụng này tham gia xử lý sự kiện, sẽ có vấn đề rắc rối xảy ra. Vấn đề sẽ càng trở nên nghiêm trọng nếu ứng dụng này là một chương trình thuộc hệ thống, và rõ ràng sẽ không có gì đảm bảo cho sự an toàn của hệ thống chúng ta. Để giải quyết vấn đề trên, hãy sử dụng hàm CallNextHookEx, khai báo của nó như sau :

LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam );

hhk : là handle của hook hiện hành, giá trị này có thể lấy được từ hàm SetWindowsHookEx khi cài đặt hook.

nCode : chỉ định hook code để gởi đến hook kế tiếp. Hàm xử lý hook dùng giá trị này để chỉ định xử lý thông điệp được gởi từ hook như thế nào .

wParam: chỉ định 16 bits thông tin mở rộng của thông điệp.

lParam: chỉ định 32 bits thông tin mở rộng của thông điệp.

Giá trị trả về : giá trị trả về là kết quả của quá trình xử lý và tùy thuộc vào thông số nCode

Trong một số tình huống, Filter Function hiện hành có thể không muốn chuyển sự kiện cho Filter Function khác trong cùng một chuỗi. Lúc này, nếu loại hook chúng ta đang cài đặt cho phép huỷ bỏ sự kiện, và Filter Function của chúng ta cũng có cùng quyết định là hủy bỏ, nó sẽ không phải gọi hàm CallNextHookEx.
5. Chương trình minh họa

Để hiểu rõ hơn về Hook ,các bạn có thể xem một ví dụ đơn giản về sử dụng Hook. Trong ví dụ này chúng ta sẽ cài đặt một Hook ,cho phép đọc thông tin về menu bar chuẩn của bất kỳ chương trình ứng dụng nào và lưu các thông tin này thành một tập tin resource (để sau đó ta có thể dễ dàng sử dụng trong chương trình của mình!)

Trong ví dụ này chúng ta sẽ sử dụng Hook WH_GETMESSAGE để chặn bắt các thông điệp gửi đến hàng đợi thông điệp . Và khi chương trình của chúng ta bắt được thông điệp WM_NCLBUTTONDOWN (thông điệp này đuợc phát sinh khi chúng ta click chuột lện thanh tiêu đề của cửa sổ) , chương trình sẽ cho mở hộp thoại Save As để cho nguời dùng chọn đường dẫn và đặt tên cho file resource sẽ được tạo ra .Và lúc này chuơng trình sẽ lưu toàn bộ nội dung của menubar xuống thành file resource mà chúng ta đã đặt tên.

* Trong ví dụ này vì ta muốn lấy thông tin về menu bar của tất cả ứng dụng trên Windows nên ta phải sử dụng một tập tin .DLL để lập một Hook loại toàn cục (còn các hook loại cục bộ chỉ cho phép chặn các thông điệp trong nội bộ ứng dụng có cài đặt Hook mà thôi).

*** Chôm chỉa bên HVA về ***

Thêm nữa: muốn nghiên cứu thêm về hook có thể đọc thêm trên codeproject, Hook system (http://www.codeproject.com/system/hooksys.asp)
API hack (http://www.codeproject.com/system/api_spying_hack.asp)
hoặc kiếm ở codeguru (http://www.codeguru.com)
Em đang nghiên cứu vấn đề này nhưng chưa ngộ ra được, ai mà hiểu thì chia sẻ nhé :p