相隔一个月,第二篇文章终于开始写了,鬼知道这一个月我干了什么。再上一篇文章中,用了SendMessage()和PostMessage()这两个API实现了星号密码查看器的功能,但是在测试中却发现,有些控件读不出来,猜测可能是SendMessage()消息在外部无法读取的原因,后来在网上看到一篇对星号密码查看器的分析帖子,就仿照其代码做了些修改,下面记录一下。其原理还是使用消息机制,但是这次使用了注入代码的方式,将我们的代码注入到指定进程中,在内部实现密码的读取。
0x01 基本原理
依然获取鼠标所在位置的控件进程句柄和控件句柄,然后使用VirtualAllocEx()函数在进程中开空间,将我们的读取密码框程序的代码使用WriteProcessMemory()注入到进程中,然后使用CreateRemoteThread()函数运行我们注入的代码函数,然后使用ReadProcessMemory()将读取到的密码拷贝到本地。
说白了,一堆API的调用。
0x02 定义注入代码
在注入代码中仍然是使用消息来获取密码——“SendMessage(hwnd, WM_GETTEXT, 255, long(buffer)) ”,我们需要定义个函数,然后函数里需要放我们的参数,包括读取的控件句柄,要发送的消息,缓冲区。因为的以线程的方式进行调用,所以我们定义成结构体的形式,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
struct ParamStruct //注入到目标窗口的参数 { HWND hWnd;//目标窗口句柄 FARPROC MySendMessage; //要发送的消息 TCHAR Buffer[256]; //缓冲区 }; _declspec(naked) DWORD Injectbegin(ParamStruct* ps) { __asm { push esi; mov esi, ps; lea eax, [esi + 8]; //取地址给eax push eax; //buffer缓冲区 push 100h; //要获取的字节数 push 0Dh; //WM_GETTEXT push[esi]; call[esi + 4]; //SendMessage(ps->hWnd,WM_GETTEXT,256,(LPARAM)ps->Bufer) and byte ptr[esi + 107h], 0; //ps->Buffer[255]='\0' ,(esp+8)+255 pop esi; retn 4; } } |
Injectbegin()函数就是我们的注入函数,看不懂的建议看看8086汇编代码。
0x03 定义注入程序
注入程序我们需要拿到被注入进程的进程id,我们首先要对上面的结构体进行初始化,因为要用到SendMessage(),所以我们要在user32模块中使用GetModuleHandle("user32")和 GetProcAddress(hmod, _T("SendMessageA"))拿到SendMessage的函数地址,保存到结构体中,然后再进程中开辟两块空间,一块放结构体参数,一块放我们的注入代码。
代码注入成功后就可以使用CreateRemoteThread()函数调用远程Call,调用完成后密码就保存到了buffer缓冲区,我们只要使用ReadProcessMemory()函数读取进程的结构体复制到本地的结构体中就拿到了密码。具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
void GetPassFromStar(HANDLE hProcess, HWND hWnd, LPTSTR buf) { DWORD writenum = 0; HANDLE hThread = NULL; DWORD ThreadId = 0; DWORD ExitCode = 0; LPVOID lpParameter=0, lpStartAddress=0; __try { HMODULE hmod = GetModuleHandle("user32"); if (hmod) { ParamStruct ps; ps.hWnd = hWnd; ps.MySendMessage = GetProcAddress(hmod, _T("SendMessageA")); if (ps.MySendMessage) { //先在目标进程分配参数空间,以便把参数传给下面之后写入进程的代码,返回值为分配后的首地址 if (lpParameter = VirtualAllocEx(hProcess, NULL, sizeof(ParamStruct), MEM_COMMIT, PAGE_READWRITE)) { WriteProcessMemory(hProcess, lpParameter, &ps, sizeof(ParamStruct), &writenum); const int INJECTCODESIZE = 30; //要申请的内存大小 存放注入函数,调试发现最小为29 if (lpStartAddress = VirtualAllocEx(hProcess, NULL, INJECTCODESIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) { WriteProcessMemory(hProcess, lpStartAddress, (LPVOID)Injectbegin, INJECTCODESIZE, &writenum); hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpStartAddress, lpParameter, 0, &ThreadId); if (hThread) { WaitForSingleObject(hThread, INFINITE); //等待线程执行 ReadProcessMemory(hProcess, lpParameter, &ps, sizeof(ParamStruct), &writenum); //创建新线程 strcpy(buf, ps.Buffer); //拷贝到缓冲区 } } } } } } __finally { //释放申请的内存以及关闭线程 if (lpParameter) VirtualFreeEx(hProcess, lpParameter, 0, MEM_RELEASE); if (lpStartAddress) VirtualFreeEx(hProcess, lpStartAddress, 0, MEM_RELEASE); if (hThread) { GetExitCodeThread(hThread, &ExitCode); //CloseHandle表示我不再使用该句柄,即不对这个句柄对应的线程做任何干预了。并没有结束线程 CloseHandle(hThread); } } } |
需要注意的是,记得再注入完成后将空间释放掉。
0x04 完整的MFC程序
MFC程序包:
0x05结束语
在次声明:本文中的注入程序代码部分参考自某论坛中的一篇星号密码查看器逆向分析修改得到,非本人原创。