实现远程代码注入

Posted 尚书左仆射

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现远程代码注入相关的知识,希望对你有一定的参考价值。

前面我写过DLL的注入代码(详见:DLL注入与卸载),但是也并非就一定要另外实现一个动态链接库再注入我们的所需操作。这次我们就直截了当一点,直接在目标进程中注入我们所需的代码来实现操控。

要在目标进程中完成一定的功能,当然需要调用相关的API函数了,只是这次我们要使用的API出现在不同的DLL中。Kernel32.dll文件在每个进程中的地址是相同的,但这并不代表其他DLL文件在每个进程中的地址也是一样的,我相信大家都懂得。由此,我们在目标进程中使用API时,就需要用到LoadLibrary()函数和GetProcAddress()函数动态调用每个用到的API。我们可以把将要用到的API函数和对应API所在的DLL封装到一个结构体,然后写入目标进程中。另外将需要远程执行的代码也写入目标进程的内存空间,最后就可以调用CreateRemoteThread()函数将先前的“伏笔”都调用起来了(*^-^*)

下面我们就实现一个让目标进程弹出提示框的程序。当然,该程序并没有什么实际效用,但是相信大家能够从中得到些许启发,嘻嘻。

首先,先热热身,做一些预备工作:

#define STRLEN 20
//自定义结构体用于数据写入
typedef struct _DATA

	DWORD dwLoadLibrary;
	DWORD dwGetProcAddress;
	DWORD dwGetModuleHandle;
	DWORD dwGetModuleFileName;

	char User32Dll[STRLEN];
	char MessageBox[STRLEN];
	char Str[STRLEN];
DATA, *PDATA;

上述结构体中保存了LoadLibraryA()、GetProcAddress()、GetModuleHandle()、和GetModuleFileName()函数的地址。这四个函数都隶属于Kernel32.dll中,这意味着我们可以在注入之前获得(因为每个进程中的相应函数的地址都是一样的嘛)。User32Dll中保存“User32.dll”字符串,因为我们要用到MessageBoxA()函数,而该函数是User32.dll的导出函数。MessageBox中保存我们将以调用的函数名“MessageBoxA”,而Str中则保存着通过MessageBoxA()函数弹出的字符串。

对结构体进行相关初始化操作的函数如下:

void CMyDlg::InjectCode(DWORD dwPid)
//此处需要进程id作为参数,取得的方法可参考DLL注入相关介绍,我就不再重复了
    //打开进程并获取进程句柄
    HANDLEhProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
 
    if(NULL== hProcess)
       return;
 
    DATAData = 0;
 
    //获取kernel32.dll中相关的导出函数
    Data.dwLoadLibrary= (DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),
                                              "LoadLibraryA");
    Data.dwGetProcAddress= (DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),
                                              "GetProcAddress");
    Data.dwGetModuleHandle= (DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),
                                              "GetModuleHandleA");
    Data.dwGetModuleFileName= (DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),
                                              "GetModuleFileNameA");
    //需要的其他dll和导出函数
    lstrcpy(Data.User32Dll,"user32.dll");
    lstrcpy(Data.MessageBox,"MessageBoxA");
    //提示字符串
    lstrcpy(Data.Str,"Code Inject !!!");
 
    //在目标进程中申请空间
    LPVOIDlpData = VirtualAllocEx(hProcess, NULL, sizeof(Data),
                     MEM_COMMIT,PAGE_EXECUTE_READWRITE);
    DWORDdwWriteNum = 0;
    WriteProcessMemory(hProcess,lpData, &Data,sizeof(Data), &dwWriteNum);
 
    //在目标进程空间中申请用于保存代码的长度
    WORDdwFunSize = 0x4000;
    LPVOIDlpCode = VirtualAllocEx(hProcess, NULL, dwFunSize,
                     MEM_COMMIT,PAGE_EXECUTE_READWRITE);
 
    WriteProcessMemory(hProcess,lpCode,&RemoteThreadProc,
                     dwFunSize,&dwWriteNum);
    HANDLEhThread = CreateRemoteThread(hProcess, NULL, 0,
                     (LPTHREAD_START_ROUTINE)lpCode,
                     lpData,0, NULL);
    WaitForSingleObject(hThread,INFINITE);
 
    CloseHandle(hThread);
    CloseHandle(hProcess);

以上代码对结构体的变量进行了初始化,并且将线程函数写入目标进程空间的内存中。用到的相关API在文章开始处提到的博客中都有介绍,我是一个重视代码重用的程序员,所以…

下面提供线程函数的实现参考:

DWORD WINAPI RemoteThreadProc(LPVOID lpParam)

	PDATA pData = (PDATA)lpParam;

	//定义API函数原型
	HMODULE (__stdcall *MyLoadLibrary)(LPCTSTR);
	FARPROC (__stdcall *MyGetProcAddress)(HMODULE, LPCSTR);
	HMODULE (__stdcall *MyGetModuleHandle)(LPCTSTR);
	int (__stdcall *MyMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT);
	DWORD (__stdcall *MyGetModuleFileName)(HMODULE, LPTSTR, DWORD);

	//对各函数地址进行赋值
	MyLoadLibrary = (HMODULE (__stdcall *)(LPCTSTR))pData->dwLoadLibrary;
	MyGetProcAddress = (FARPROC (__stdcall *)(HMODULE, LPCSTR))pData->dwGetProcAddress;
	MyGetModuleHandle = (HMODULE (__stdcall *)(LPCTSTR))pData->dwGetModuleHandle;
	MyGetModuleFileName = (DWORD (__stdcall *)(HMODULE, LPTSTR, DWORD))pData->dwGetModuleFileName;

	//加载user32.dll
	HMODULE hModule = MyLoadLibrary(pData->User32Dll);
	//获得MessageBoxA的函数地址
	MyMessageBox = (int (__stdcall *)(HWND, LPCTSTR, LPCTSTR, UINT))
						MyGetProcAddress(hModule, pData->MessageBox);
	char szModuleFileName[MAX_PATH] = 0;
	MyGetModuleFileName(NULL, szModuleFileName, MAX_PATH);

	MyMessageBox(NULL, pData->Str, szModuleFileName, MB_OK);

	return 0;

主要的代码就是这样,编译链接并运行程序,启动一个目标进程作为注入靶子,然后填入相应的进程名进行注入。VC6中,如果是debug版本将会报错,但是使用release版本就可以啦。原因是VC6的默认编译器是debug版本,这样会加入很多调试信息。而某些调试信息并不存在于代码之中,而是在其他DLL模块内。这样当执行到调试相关的代码时会访问不存在的DLL模块中的代码,如是,就导致了错误的发生。

VC6转到release版本的方法:

vc右上角菜单栏或工具栏的空白处右键,选上Build项,使build工具栏可见,在Build工具栏上就可以选择工程的Debug或Release版本。

或者菜单栏Build->BatchBuild,在弹出的对话框中选择编译哪个版本,或者两个版本都编译。

好了,看一些运行后的效果吧。




相关的进程名可以通过任务管理器来获取哟。



以上是关于实现远程代码注入的主要内容,如果未能解决你的问题,请参考以下文章

C语言实现远程代码注入

远程线程注入代码

JNA实现远程线程注入

log4j2 远程代码注入漏洞 demo

星号密码探测工具 - 代码远程线程注入的简单运用

远程线程DLL注入, 如何释放DLL和结束DLL的线程