Windows操作系统下,为了避免各个进程相互影响,每个进程地址空间都是被隔离的。所谓 “远程线程”,并不是跨计算机的,而是跨进程的。简单来说,就是进程A要在进程B中创建一个线程,这就叫远程线程。
远程线程被木马、外挂等程序广泛使用,反病毒软件中也离不开远程线程的技术。技术应用的两面性取决于自己的个人行为意识,良性的技术学习对自己的人生发展是非常有好处的,就算谈不上好处,至少不会给自己带来不必要的麻烦。
关于远程线程的知识,本文介绍3个例子,分别是DLL的注入、卸载远程DLL和不依赖DLL进行代码注入。
1. DLL远程注入
木马或病毒编写的好坏取决于其隐藏的程度,而不在于其功能的多少。无论是木马还是病毒,都是可执行程序。如果它们是EXE文件的话,那么在运行时必定会产生一个进程,就很容易被发现。为了不被发现,在编写木马或病毒时可以选择将其编写为DLL文件。DLL文件的运行不会单独创建一个进程,它的运行被加载到进程的地址空间中,因此其隐蔽性相对较好。DLL文件如果不被进程加载又如何在进程的地址空间中运行呢?方式是强制让某进程加载DLL文件到其地址空间中去,这个强制的手段就是现在要介绍的远程线程。
创建远程线程的函数CreateRemoteThread()的定义如下:
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
该函数的功能是创建一个远程的线程。我们把CreateThread()函数和CreateRemoteThread()函数进行比较。对于CreateThread()函数来说,CreateRem oteThread()函数比其多了一个hProcess参数,该参数是指定要创建线程的进程句柄。其实CreateThread()函数的内容实现就是依赖于CreateRemoteThread()函数来完成的。CreateThread()函数的代码实现如下:
/*
* @implemented
*/
HANDLE
WINAPI
CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId)
{
/* 创建远程线程
return CreateRemoteThread(NtCurrentProcess(),
lpThreadAttributes,
dwStackSize,
lpStartAddress,
lpParameter,
dwCreationFlags,
lpThreadId);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
在上面的代码中,NtGetCurrentProcess()函数的功能是获得当前进程的句柄。
CreateRemoteThread()函数是给其他进程创建线程使用的,其第一个参数是指定某进程的句柄,获取进程的句柄使用API函数OpenProcess(),该函数需要提供PID作为参数。
除了hProcess参数以外,剩余的关键参数就只有lpStartAddress和lpParameter两个了。lpStartAddress指定线程函数的地址,lpParameter指定传递给线程函数的参数。前面提到,每个进程的地址空间是隔离的,那么新创建的线程函数的地址也应该在目标进程中,而不应该在调用CreateRemoteThread()函数的进程中。同样,传递给线程函数的参数也应该在目标进程中。
如何让线程函数的地址在目标进程中呢?如何让线程函数的参数也可以传递到目标进程中呢?在讨论这个问题以前,先来考虑线程函数要完成的功能。这里主要完成的功能是注入一个DLL文件到目标进程中,那么线程函数的功能就是加载DLL文件。加载DLL文件使用的是LoadLibrary()函数。LoadLibrary()函数的定义:
HMODULE LoadLibrary(
LPCTSTR lpFileName
);
看一下线程函数的定义格式,具体如下:
DWORD WINAPI ThreadProc(
LPVOID lpParameter
);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
比较两个函数可以发现,除了函数的返回值类型和参数类型以外,其函数格式是相同的。这里只考虑其相同的部分。因为其函数的格式相同,首先调用约定相同,都是WINAPI(也就是__stdcall方式);其次函数个数相同,都只有一个。那么,可以直接把LoadLibrary()函数作为线程函数创建到指定的进程中。LoadLibrary()的参数是欲加载的DLL文件的完整路径,只要在CreateRemoteThread()函数中赋值一个指向DLL文件完整路径的指针给LoadLibrary()函数即可。这样使用CreateRemoteThread()函数就可以创建一个远程线程了。不过,还有两个问题没有解决,首先是如何将LoadLibrary()函数的地址放到目标进程空间中让CreateRemoteThread()调用,其次是传递给LoadLibrary()函数的参数也需要在目标进程空间中,并且要通过CreateRemoteThread()函数指定给LoadLibrary()函数。
首先解决第1个问题,即如何将LoadLibrary()函数的地址放到目标进程空间中。LoadLibrary()函数是系统中的Kernel32.dll的导出函数,Kernel32.dll这个DLL文件在任何进程中的加载位置都是相同的,也就是说,LoadLibrary()函数的地址在任何进程中的地址都是相同的。因此,只要在进程中获得LoadLibrary()函数的地址,那么该地址在目标进程中也可以使用。CreateRemoteThread()函数的线程地址参数直接传递LoadLibrary()函数的地址即可。
其次解决第2个问题,即如何将欲加载的DLL文件完整路径写入目标进程中。这需要借助WriteProcessMemory()函数,其定义如下:
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process
LPVOID lpBaseAddress, // base of memory area
LPVOID lpBuffer, // data buffer
DWORD nSize, // number of bytes to write
LPDWORD lpNumberOfBytesWritten // number of bytes written
);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
该函数的功能是把lpBuffer中的内容写到进程句柄是hProcess进程的lpBaseAddress地址处,写入长度为nSize。
参数说明如下。
hProcess:该参数是指定进程的进程句柄。
lpBaseAddress:该参数是指定写入目标进程内存的起始地址。
lpBuffer:该参数是要写入目标进程内存的缓冲区起始地址。
nSize:该参数是指定写入目标内存中的缓冲区的长度。
lpNumberOfBytesWritten:该参数用于接收实际写入内容的长度。
该函数的功能非常强大,比如在破解方面,用该函数可以实现一个“内存补丁”;在开发方面,该函数可以用于修改目标进程中指定的值(比如游戏修改器可以修改游戏中的钱、红、蓝等)。
使用该函数可以把DLL文件的完整路径写入到目标进程的内存地址中,这样就可以在目标进程中用LoadLibrary()函数加载指定的DLL文件了。解决了上面的两个问题,还有第3个问题需要解决。WriteProcessMemory()函数的第2个参数是指定写入目标进程内存的缓冲区起始地址。这个地址在目标进程中,那么这个地址在目标进程的哪个位置呢?目标进程中的内存块允许把DLL文件的路径写进去吗?
第3个要解决的问题是如何确定应该将DLL文件的完整路径写入目标进程的哪个地址。对于目标进程来说,事先是不会准备一块地址让用户进行写入的,用户能做的是自己在目标进程中申请一块内存,然后把DLL文件的路径进行写入,写入在目标进程新申请到的内存空间中。在目标进程中申请内存的函数是VirtualAllocEx(),其定义如下:
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
VirtualAllocEx()函数的参数说明如下。
hProcess:该参数是指定进程的进程句柄。
lpAddress:该参数是指在目标进程中申请内存的起始地址。
dwSize:该参数是指在目标进程中申请内存的长度。
flAllocationType:该参数指定申请内存的状态类型。
flProtect:该参数指定申请内存的属性。
该函数的返回值是在目标进程申请到的内存块的起始地址。
到此,关于编写一个DLL注入的所有知识都已经具备了。现在开始编写一个DLL注入的工具,其界面如图1所示。
图1 DLL注入/卸载器
该工具有2个作用,分别是注入DLL和卸载被注入的DLL。关于卸载被注入的DLL的功能,将在后面进行介绍。在界面上要求输入两部分内容,第1部分是欲注入的DLL文件的完整路径(一定要是完整路径),第2部分是进程的名称。
首先看一下关于界面的操作,代码如下:
void CInjectDllDlg::OnBtnInject()
{
// 添加处理程序代码
char szDllName[MAX_PATH] = { 0 };
char szProcessName[MAXBYTE] = { 0 };
DWORD dwPid = 0;
GetDlgItemText(IDC_EDIT_DLLFILE, szDllName, MAX_PATH);
GetDlgItemText(IDC_EDIT_PROCESSNAME, szProcessName, MAXBYTE);
// 由进程名获得 PID
dwPid = GetProcId(szProcessName);
// 注入 szDllName 到 dwPid
InjectDll(dwPid, szDllName);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
代码中调用了另外两个函数,第1个是由进程名获得PID的函数,第2个是用于DLL注入的函数。GetProcId()函数的代码如下:
DWORD CInjectDllDlg::GetProcId(char *szProcessName)
{
BOOL bRet;
PROCESSENTRY32 pe32;
HANDLE hSnap;
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
pe32.dwSize = sizeof(pe32);
bRet = Process32First(hSnap, &pe32);
while ( bRet )
{
// strupr()函数是将字符串转化为大写
if ( lstrcmp(strupr(pe32.szExeFile),strupr(szProcessName)) == 0 )
{
return pe32.th32ProcessID;
}
bRet = Process32Next(hSnap, &pe32);
}
return 0;
} +
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
InjectDll()函数的代码如下:
VOID CInjectDllDlg::InjectDll(DWORD dwPid, char *szDllName)
{
if ( dwPid == 0 || lstrlen(szDllName) == 0 )
{
return ;
}
char *pFunName = "LoadLibraryA";
// 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);
if ( hProcess == NULL )
{
return ;
}
// 计算欲注入 DLL 文件完整路径的长度
int nDllLen = lstrlen(szDllName) + sizeof(char);
// 在目标进程申请一块长度为 nDllLen 大小的内存空间
PVOID pDllAddr = VirtualAllocEx(hProcess,NULL, nDllLen,MEM_COMMIT,PAGE_READWRITE);
if ( pDllAddr == NULL )
{
CloseHandle(hProcess);
return ;
}
DWORD dwWriteNum = 0;
// 将欲注入 DLL 文件的完整路径写入在目标进程中申请的空间内
WriteProcessMemory(hProcess, pDllAddr, szDllName,nDllLen, &dwWriteNum);
// 获得 LoadLibraryA()函数的地址
FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"),pFunName);
// 创建远程线程
HANDLE hThread = CreateRemoteThread(hProcess,NULL, 0,(LPTHREAD_START_ROUTINE)pFunAddr,pDllAddr, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
}
- 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.
InjectDll()函数有 2 个参数,分别是目标进程的 ID 值和要被注入的 DLL 文件的完整路径。在代码中获得的不是 LoadLibrary()函数的地址,而是 LoadLibraryA()函数的地址。在系统中其实没有 LoadLibrary()函数,有的只是 LoadLibraryA()和 LoadLibraryW()两个函数。这两个函数分别针对 ANSI 字符串和 UNICODE 字符串。而 LoadLibrary()函数只是一个宏。在编写程序的时候,直接使用该宏是可以的。如果要获取 LoadLibrary()函数的地址,就要明确指定是获取 LoadLibraryA()还是 LoadLibraryW()。
LoadLibrary()宏定义如下:
#ifdef UNICODE
#define LoadLibrary LoadLibraryW
#else
#define LoadLibrary LoadLibraryA
#endif // !UNICODE
- 1.
- 2.
- 3.
- 4.
- 5.
只要涉及字符串的函数,都会有相应的ANSI版本和UNICODE版本;其余不涉及字符串的函数,没有ANSI版本和UNICODE版本的区别。
为了测试DLL加载是否成功,在代码的DllMain()函数中加入如下代码:
case DLL_PROCESS_ATTACH:
{
MsgBox("!DLL_PROCESS_ATTACH!");
break;
}
- 1.
- 2.
- 3.
- 4.
- 5.
现在测试一下注入的效果,如图2和图3所示。
图2 DLL文件被注入成功的提示
图3 查看进程中的DLL列表确认被装载成功
在图2中,弹出的对话框是DLL程序在DLL_PROCESS_ATTACH时出现的。其所在的进程为notepad.exe。从图2中可以看出,弹出提示框的标题处是notepad.exe进程的路径。图3是用工具查看进程中所加载的DLL文件列表,可以看出,通过注入工具注入的DLL文件已经被加载到notepad.exe的进程空间中。
如果要对系统进程进行注入的话,由于进程权限的关系是无法注入成功的。在打开目标进程时用到了OpenProcess()函数,由于权限不够,会导致无法打开进程并获得进程句柄。通过调整当前进程的权限,可以打开系统进程并获得进程句柄。如果在Win8或更高版本上运行注入程序的话,需要选中注入工具单击右键,选择“以管理员身份运行”才可以完成注入。
2. 卸载被注入的DLL文件
DLL注入如果应用在木马方面,危害很大,这里完成一个卸载被注入DLL的程序。卸载被注入DLL程序的思路和注入的思路是一样的,而且代码的改动也非常小。区别在于现在的功能是卸载,而不是注入。
DLL卸载使用的API函数是FreeLiabrary(),其定义如下:
BOOL FreeLibrary(
HMODULE hModule // handle to DLL module
);
- 1.
- 2.
- 3.
该函数的参数是要卸载的模块的句柄。
FreeLibrary()函数使用的模块句柄可以通过Module32First()和Module32Next()两个函数获取。在使用Module32First()和Module32Next()两个函数的时候,需要用到MODULEENTRY32结构体,该结构体中保存了模块的句柄。MODULEENTRY32结构体的定义如下:
typedef struct tagMODULEENTRY32 {
DWORD dwSize;
DWORD th32ModuleID;
DWORD th32ProcessID;
DWORD GlblcntUsage;
DWORD ProccntUsage;
BYTE * modBaseAddr;
DWORD modBaseSize;
HMODULE hModule;
TCHAR szModule[MAX_MODULE_NAME32 + 1];
TCHAR szExePath[MAX_PATH];
} MODULEENTRY32;
typedef MODULEENTRY32 *PMODULEENTRY32;
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
该结构体中的hModule为模块的句柄,szModule为模块的名称,szExePath是完整的模块的名称(所谓完整,包括路径和模块名称)。
卸载远程进程中DLL模块的代码如下:
VOID CInjectDllDlg::UnInjectDll(DWORD dwPid, char *szDllName)
{
if ( dwPid == 0 || lstrlen(szDllName) == 0 )
{
return ;
}
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,dwPid);
MODULEENTRY32 me32;
me32.dwSize = sizeof(me32);
// 查找匹配的进程名称
BOOL bRet = Module32First(hSnap, &me32);
while ( bRet )
{
if ( lstrcmp(strupr(me32.szExePath),
strupr(szDllName)) == 0 )
{
break;
}
bRet = Module32Next(hSnap, &me32);
}
CloseHandle(hSnap);
char *pFunName = "FreeLibrary";
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);
if ( hProcess == NULL )
{
return ;
}
FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"),pFunName);
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunAddr,me32.hModule, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
}
- 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.
卸载远程进程中DLL的实现代码比DLL注入的代码要简单,这里就不做过多的介绍了。
3. 无DLL的代码注入
DLL文件的注入与卸载都完成了,整个注入与卸载的过程其实就是让远程线程执行一次LoadLibrary()函数或FreeLibrary()函数。远程线程装载一个DLL文件,通过DllMain()调用DLL中的具体功能代码,这样注入DLL后就可以让DLL做很多事情了。是否可以不依赖DLL文件直接向目标进程写入要执行的代码,以完成特定的功能呢?答案是可以。
要在目标进程中完成一定的功能,就需要使用相关的API函数,不同的API函数实现在不同的DLL中。Kernel32.dll文件在每个进程中的地址是相同的,但是并不代表其他DLL文件在每个进程中的地址都是一样的。这样,在目标进程中调用API函数时,必须使用LoadLibrary()函数和GetProcAddress()函数动态调用用到的每个API函数。把想要使用的API函数及API函数所在的DLL文件都封装到一个结构体中,直接写入目标进程的空间中。同时也直接把要在远程执行的代码也写入目标进程的内存空间中,最后调用CreateRemoteThread()函数即可将其运行。
通过实现一个简单的例子让远程线程弹出一个提示对话框,但是不借助于DLL。本程序所使用的API函数在前面都已经介绍过了。根据前面的步骤先来定义一个结构体,其定义如下:
#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;
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
该结构体中保存了LoadLibraryA()、GetProcAddress()、GetModuleHandle()和GetModu leFileName()四个API函数的地址。这四个API函数都属于Kernel32.dll的导出函数,因此可以在注入前进行获取。User32Dll中保存“User32.dll”字符串,因为MessageBoxA()函数是由User32.dll的导出函数。Str中保存的是通过MessageBoxA()函数弹出的字符串。
注入代码类似于前面介绍的注入代码,不过需要在注入代码中定义一个结构体变量,并进行相应的初始化,代码如下:
VOID CNoDllInjectDlg::InjectCode(DWORD dwPid)
{
// 打开进程并获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);
if ( hProcess == NULL )
{
return ;
}
DATA Data = { 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");
// MessageBoxA()弹出的字符串
lstrcpy(Data.Str, "Inject Code !!!");
// 在目标进程申请空间
LPVOID lpData = VirtualAllocEx(hProcess, NULL, sizeof(Data),
MEM_COMMIT | MEM_RELEASE,PAGE_READWRITE);
DWORD dwWriteNum = 0;
WriteProcessMemory(hProcess, lpData, &Data,
sizeof(Data), &dwWriteNum);
// 在目标进程空间申请的用于保存代码的长度
DWORD dwFunSize = 0x4000;
LPVOID lpCode = VirtualAllocEx(hProcess, NULL, dwFunSize,
MEM_COMMIT,PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, lpCode, &RemoteThreadProc,
dwFunSize, &dwWriteNum);
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0
(LPTHREAD_START_ROUTINE)lpCode,lpData, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
}
- 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.
上面的注入代码除了对结构体变量初始化外,还将线程函数代码写入目标进程空间的内存中。线程函数的代码如下:
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 *)(LPCSTR))
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;
}
- 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.
上面就是无DLL注入的全部代码,编译连接并运行它。启动一个记事本程序来进行测试,可惜报错了。问题出在哪里呢?VC6的默认编译是Debug版本,这样会加入很多调试信息。而某些调试信息并不存在于代码中,而是在其他DLL模块中。这样,当执行到调试相关的代码时会访问不存在的DLL模块中的代码,就导致了报错。
将以上代码使用Release方式进行编译连接,然后可以无误地执行,如图4所示。
图4 Release方式下编译注入成功
编译的Debug版也可以进行无DLL的注入,只是实现起来略有不同。