获取其它进程密码框中的密码

来源:互联网 发布:ubuntu text entry 编辑:程序博客网 时间:2024/04/27 18:04

        程序编写的过程中,往往有些功能需要由其它的进程权限才能够完成这些工作,如得到其它进程中某个窗口的标题;建立隐藏的守护进程来监测本进程的运行情况;在Win7下,利用其它有高UAC权限的进程来完成一些功能等等。都需要使用到其它进程来完成这些工作。本文就来讲解如何获取其它进程中密码框中的密码。

        以上代码实现及理论均为Win32平台为主。

        A. 为什么选择远程线程 ?

        我们知道,远程线程就是想让其它线程执行我们自己的代码,如果要想使自己的代码被其它进程执行,首先能想到的就是DLL,因为DLL可以被其它进程附加,并且可以执行所有的功能。但强大的功能也需要一个导火索来给它这个机会爆发,因为在进程运行进来后,LoadLibrary仍然可以使一个动态库附加映射进某个进程,但当我们执行LoadLibrary时,是在自己进程中执行的,无法使其它进程有机会执行这个代码,从而也就没机会使我们的代码附加到其它进程中,结果就是,强大的代码永远没有得到一个可以执行的机会。

        而远程线程就是系统提供给我们的一个伯乐,它可以在你创建线程时,指定一个起始运行的入口,这样LoadLibrary就有了执行的机会,从而,DLL里面的代码就是会一气呵成的执行。

        所以远程线程是必须的。

         B. 一些必须了解的概念

                1.  地址空间

                在Win32平台下(Linux及主流平台),每个进程拥有4GB独立的地址空间,一般叫做线程地址空间,相对于每个进程来说,在自己的空间内访问一些地址内容,即访问变                量的值及执行一些代码都是非常方便的有效的,每个进程完成自己的工作,相互不会产生任何干扰。

                2.   远程线程

               一般而言,线程是线程可调试,实际完成一些工作(即执行一些代码)的最小实体,一个进程可以包含一个及以上的线程,这些线程并行工作,完成实际的功能。而远                程线程,故名思议,即非本进程的线程。假设有A,B两个进程,远程线程就是由A进程创建,但运行于B进程的地址空间中,拥有B进程的进程权限。

                3.  代码注入

                代码注入就是,将精心准备好的代码(即数据)放进某个进程的地址空间中。

 

        C.  为什么不选择DLL的方式 ?

                前面已经提到过,DLL里面就是可以执行的代码,我们只需要将DLL附加进进程就得以机会去执行这些代码。但DLL是以文件形式存放在磁盘中,有些功能可能会很                     小,或者有些功能需要隐藏一些实现细节,如果附加一个DLL的话,效果并不是那么的友好,所以如果有机会不用DLL,依然能够使远程线程实现强大的功能,那自然                是最佳选择,但这也使得代码编写起来更难,也就需要更强的编程的基本功。

 

        D. 需要使用到的主要API(以Win32平台为例)

                OpenProcess:打开一个进程,得到该进程的句柄。

                VirtualAllocEx:简言之为在指定的进程中申请一些空间。

                WriteProcessMemory:简言之为在指定进程中写入一些数据。

                CreateRemoteThread:在指定的进程中创建一个线程。


        E.       具体实现 -- Talk is cheap, show me the code! (Linus)

                以得到其它进程中某一窗体的标题为例,来简单的介绍远程线程的用法。

        得到一个窗口的标题,一般会调用 GetWindowText,它的使用方法如下:

        The GetWindowText function copies the text of the specified window's titlebar (if it has one) into a buffer. If the specified window is a control, thetext of the control is copied. However,GetWindowText cannot retrievethe text of a control in another application.

        上面的使用方法是摘自MSDN上GetWindowText的描述,从描述中可以看到,GetWindowText不能够得到其它应用程序的标题,即一个进程的程序不能获得其它进程窗口的句柄。但如果那段代码是由其它进程来执行,执行后我们再得到结果,是不是就可以做到了呢。

        假设我们的进程为A,我们想得到B进程中某一个控制的标题,要想完成以上功能,首先我们要能让我们的代码放到B进程的地址空间中,其次让B执行这些代码,最后从B进程中得到执行的结果。

        代码实现如下(为突出有效代码,合法性检测都没有添加):

#include <iostream>#include <windows.h>using namespace std;void Test();void main(){Test();}__declspec(naked) void __stdcall GetWindowTextSpy(DWORDdwParam){_asm{moveax, [esp + 4]    // 得到传入的参数 dwParam, 此时为窗口句柄push    [eax + 0]    // 缓冲区长度movebx, eaxaddebx, 8push    ebx// 缓冲区地址push [eax + 4]// 目标窗口句柄callGetWindowTextret4}}__declspec(naked) void __stdcall EndLabel(DWORDdwParam){}void Test(){// 得到目标窗口所在进程的ID,篇幅原因假设已经知道窗口句柄HWNDhwnd= (HWND)0x000108F8;DWORDdwProcessID;DWORDdwTitleSize= 0x20;DWORDdwDataLen= 0x30;GetWindowThreadProcessId(hwnd, &dwProcessID);HANDLEhProcess= OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessID);// 在B 进程中申请空间以存放返回的窗口标题LPBYTEpData= (LPBYTE)VirtualAllocEx(hProcess, 0, dwDataLen, MEM_COMMIT, PAGE_READWRITE);// 填充参数WriteProcessMemory(hProcess, pData, &dwTitleSize, 4, NULL);WriteProcessMemory(hProcess, pData + 4, &hwnd, 4, NULL);// 注入的代码长度DWORDdwCodeLen= 0;#ifdef _DEBUGconst DWORD dwSpyRealAddr= *(LPDWORD)((LPBYTE)(&GetWindowTextSpy)+1) + (DWORD)(&GetWindowTextSpy) + 5; const DWORDdwEndReadAddr= *(LPDWORD)((LPBYTE)(&EndLabel)+1) + (DWORD)(&EndLabel) + 5;#elseconst DWORDdwSpyRealAddr= (DWORD)GetWindowTextSpy;const DWORDdwEndReadAddr= (DWORD)EndLabel;#endifdwCodeLen= dwEndReadAddr - dwSpyRealAddr;LPBYTEpCode= (LPBYTE)VirtualAllocEx(hProcess, 0, dwCodeLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);LPBYTEpCodeBuff= (LPBYTE)malloc(dwCodeLen);memcpy((LPVOID)pCodeBuff, (LPVOID)dwSpyRealAddr, dwCodeLen);// 调整代码LPBYTEp= pCodeBuff;while(*p != 0xE8){p++;}    *(DWORD*)(p+1) = (DWORD)&GetWindowText - (DWORD)(p - (LPBYTE)pCodeBuff + (LPBYTE)pCode) - 5;WriteProcessMemory( hProcess, pCode, pCodeBuff, dwCodeLen, NULL);HANDLEhRThread= CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pCode, pData, 0, 0);WaitForSingleObject(hRThread, INFINITE);charszTitle[100]= {0};DWORDdwReadBytes= 0;ReadProcessMemory(hProcess, pData + 8, szTitle, dwTitleSize, &dwReadBytes);cout << szTitle << endl;CloseHandle(hRThread);free(pCodeBuff);VirtualFreeEx(hProcess, pCode, dwCodeLen, MEM_RELEASE);VirtualFreeEx(hProcess, pData, dwDataLen, MEM_RELEASE);CloseHandle(hProcess);}

        简化来看,整个过程分为以下几部:

        第一步:把我们的代码放到B进程中,这个工作可以由 WriteProcessMemory来完成,查API可知,它的参数中最重要的需要一个进程句柄。 

        第二步:把我们代码中需要的参数,即GetWindowText所需要参数,放到B进程中。此步过程也可以由WriteProcessMemory来完成。

        第三步,创建远程线程执行我们的代码。

        第四步,取出执行结果,在此为得到其它进程窗口的标题。

 

        代码分析:

        代码中有两个比较特殊的函数

        __declspec(naked)void __stdcall GetWindowTextSpy(DWORD dwParam)

        __declspec(naked)void __stdcall EndLabel(DWORD dwParam)

        其中GetWindowTextSpy为我们将要注入到B进程的代码,它们都以__declspec(naked)声明,这样编译器在编译代码时就不会做任何优化,而函数体又是汇编编写,所以代码的可控性会变强。而我们又要将它作为远程线程的启动函数使用,根据规定,必须为__stdcall的调用约定。

而EndLabel函数的作用仅仅是为了提供一个标签,因为函数在内存中的分布与源码的顺序一般不会变化,所以用EndLabel的地址减去GetWindowTextSpy的地址,即可得到GetWindowTextSpy函数的代码长度。

       GetWindowTextSpy的函数实现也很简单,它会被作用远程线程的入口函数使用,到时会传入一个数据在进程B中的线程地址,所以首先得到这个地址,依次取得窗口句柄,缓冲区长度,及缓冲区地址,然后传给系统函数GetWindowText来执行。

        这样就可以通过读取调用之后写入的内存得到其它进程的密码框的密码了。

        就远程线程的API调用而言,其实是很简单的,但要想真正理解系统公开这些API的用意,及其背后隐藏的一些系统原理,这才是真正需要我们去花时间学习的地方,比如进程的概念,编译原理,x86平台的实现细节。只有这样,我们才知道为什么要调用这些API,从而能够真正理解自己代码中每条语句的用处,长此以往,当我们项目中遇到一些问题时,才会有更宽广的思路。

1 0
原创粉丝点击