网络安全编程:远程线程编程

安全
关于远程线程的知识,本文介绍3个例子,分别是DLL的注入、卸载远程DLL和不依赖DLL进行代码注入。

[[385466]]

 Windows操作系统下,为了避免各个进程相互影响,每个进程地址空间都是被隔离的。所谓 “远程线程”,并不是跨计算机的,而是跨进程的。简单来说,就是进程A要在进程B中创建一个线程,这就叫远程线程。

远程线程被木马、外挂等程序广泛使用,反病毒软件中也离不开远程线程的技术。技术应用的两面性取决于自己的个人行为意识,良性的技术学习对自己的人生发展是非常有好处的,就算谈不上好处,至少不会给自己带来不必要的麻烦。

关于远程线程的知识,本文介绍3个例子,分别是DLL的注入、卸载远程DLL和不依赖DLL进行代码注入。

1. DLL远程注入

木马或病毒编写的好坏取决于其隐藏的程度,而不在于其功能的多少。无论是木马还是病毒,都是可执行程序。如果它们是EXE文件的话,那么在运行时必定会产生一个进程,就很容易被发现。为了不被发现,在编写木马或病毒时可以选择将其编写为DLL文件。DLL文件的运行不会单独创建一个进程,它的运行被加载到进程的地址空间中,因此其隐蔽性相对较好。DLL文件如果不被进程加载又如何在进程的地址空间中运行呢?方式是强制让某进程加载DLL文件到其地址空间中去,这个强制的手段就是现在要介绍的远程线程。

创建远程线程的函数CreateRemoteThread()的定义如下: 

  1. HANDLE CreateRemoteThread(  
  2.  HANDLE hProcess,  
  3.  LPSECURITY_ATTRIBUTES lpThreadAttributes,  
  4.  DWORD dwStackSize,  
  5.  LPTHREAD_START_ROUTINE lpStartAddress,  
  6.  LPVOID lpParameter,  
  7.  DWORD dwCreationFlags,  
  8.  LPDWORD lpThreadId  
  9. ); 

该函数的功能是创建一个远程的线程。我们把CreateThread()函数和CreateRemoteThread()函数进行比较。对于CreateThread()函数来说,CreateRem oteThread()函数比其多了一个hProcess参数,该参数是指定要创建线程的进程句柄。其实CreateThread()函数的内容实现就是依赖于CreateRemoteThread()函数来完成的。CreateThread()函数的代码实现如下: 

  1. /*  
  2.   * @implemented  
  3. */  
  4. HANDLE  
  5. WINAPI  
  6. CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,  
  7.   DWORD dwStackSize, 
  8.   LPTHREAD_START_ROUTINE lpStartAddress,  
  9.   LPVOID lpParameter,  
  10.   DWORD dwCreationFlags,  
  11.   LPDWORD lpThreadId)  
  12.  
  13.   /* 创建远程线程  
  14.   return CreateRemoteThread(NtCurrentProcess(), 
  15.     lpThreadAttributes,  
  16.     dwStackSize,  
  17.     lpStartAddress, 
  18.     lpParameter,  
  19.     dwCreationFlags,  
  20.     lpThreadId);  

在上面的代码中,NtGetCurrentProcess()函数的功能是获得当前进程的句柄。

CreateRemoteThread()函数是给其他进程创建线程使用的,其第一个参数是指定某进程的句柄,获取进程的句柄使用API函数OpenProcess(),该函数需要提供PID作为参数。

除了hProcess参数以外,剩余的关键参数就只有lpStartAddress和lpParameter两个了。lpStartAddress指定线程函数的地址,lpParameter指定传递给线程函数的参数。前面提到,每个进程的地址空间是隔离的,那么新创建的线程函数的地址也应该在目标进程中,而不应该在调用CreateRemoteThread()函数的进程中。同样,传递给线程函数的参数也应该在目标进程中。

如何让线程函数的地址在目标进程中呢?如何让线程函数的参数也可以传递到目标进程中呢?在讨论这个问题以前,先来考虑线程函数要完成的功能。这里主要完成的功能是注入一个DLL文件到目标进程中,那么线程函数的功能就是加载DLL文件。加载DLL文件使用的是LoadLibrary()函数。LoadLibrary()函数的定义: 

  1. HMODULE LoadLibrary(  
  2.  LPCTSTR lpFileName  
  3. );  
  4. 看一下线程函数的定义格式,具体如下: 
  5. DWORD WINAPI ThreadProc(  
  6.  LPVOID lpParameter  
  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()函数,其定义如下: 

  1. BOOL WriteProcessMemory(  
  2.  HANDLE hProcess, // handle to process  
  3.  LPVOID lpBaseAddress, // base of memory area  
  4.  LPVOID lpBuffer, // data buffer  
  5.  DWORD nSize, // number of bytes to write  
  6.  LPDWORD lpNumberOfBytesWritten // number of bytes written  
  7. ); 

该函数的功能是把lpBuffer中的内容写到进程句柄是hProcess进程的lpBaseAddress地址处,写入长度为nSize。

参数说明如下。

hProcess:该参数是指定进程的进程句柄。

lpBaseAddress:该参数是指定写入目标进程内存的起始地址。

lpBuffer:该参数是要写入目标进程内存的缓冲区起始地址。

nSize:该参数是指定写入目标内存中的缓冲区的长度。

lpNumberOfBytesWritten:该参数用于接收实际写入内容的长度。

该函数的功能非常强大,比如在破解方面,用该函数可以实现一个“内存补丁”;在开发方面,该函数可以用于修改目标进程中指定的值(比如游戏修改器可以修改游戏中的钱、红、蓝等)。

使用该函数可以把DLL文件的完整路径写入到目标进程的内存地址中,这样就可以在目标进程中用LoadLibrary()函数加载指定的DLL文件了。解决了上面的两个问题,还有第3个问题需要解决。WriteProcessMemory()函数的第2个参数是指定写入目标进程内存的缓冲区起始地址。这个地址在目标进程中,那么这个地址在目标进程的哪个位置呢?目标进程中的内存块允许把DLL文件的路径写进去吗?

第3个要解决的问题是如何确定应该将DLL文件的完整路径写入目标进程的哪个地址。对于目标进程来说,事先是不会准备一块地址让用户进行写入的,用户能做的是自己在目标进程中申请一块内存,然后把DLL文件的路径进行写入,写入在目标进程新申请到的内存空间中。在目标进程中申请内存的函数是VirtualAllocEx(),其定义如下: 

  1. LPVOID VirtualAllocEx(  
  2.  HANDLE hProcess,  
  3.  LPVOID lpAddress,  
  4.  SIZE_T dwSize,  
  5.  DWORD flAllocationType,  
  6.  DWORD flProtect  
  7. ); 

VirtualAllocEx()函数的参数说明如下。

hProcess:该参数是指定进程的进程句柄。

lpAddress:该参数是指在目标进程中申请内存的起始地址。

dwSize:该参数是指在目标进程中申请内存的长度。

flAllocationType:该参数指定申请内存的状态类型。

flProtect:该参数指定申请内存的属性。

该函数的返回值是在目标进程申请到的内存块的起始地址。

到此,关于编写一个DLL注入的所有知识都已经具备了。现在开始编写一个DLL注入的工具,其界面如图1所示。

图1  DLL注入/卸载器

该工具有2个作用,分别是注入DLL和卸载被注入的DLL。关于卸载被注入的DLL的功能,将在后面进行介绍。在界面上要求输入两部分内容,第1部分是欲注入的DLL文件的完整路径(一定要是完整路径),第2部分是进程的名称。

首先看一下关于界面的操作,代码如下: 

  1. void CInjectDllDlg::OnBtnInject()  
  2.  
  3.   // 添加处理程序代码  
  4.   char szDllName[MAX_PATH] = { 0 };  
  5.   char szProcessName[MAXBYTE] = { 0 };  
  6.   DWORD dwPid = 0 
  7.   GetDlgItemText(IDC_EDIT_DLLFILE, szDllName, MAX_PATH);  
  8.   GetDlgItemText(IDC_EDIT_PROCESSNAME, szProcessName, MAXBYTE);  
  9.   // 由进程名获得 PID  
  10.   dwPid = GetProcId(szProcessName);  
  11.   // 注入 szDllName 到 dwPid  
  12.   InjectDll(dwPid, szDllName);  

代码中调用了另外两个函数,第1个是由进程名获得PID的函数,第2个是用于DLL注入的函数。GetProcId()函数的代码如下: 

  1. DWORD CInjectDllDlg::GetProcId(char *szProcessName)  
  2.  
  3.   BOOL bRet;  
  4.   PROCESSENTRY32 pe32;  
  5.   HANDLE hSnap;  
  6.   hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);  
  7.   pe32.dwSize = sizeof(pe32);  
  8.   bRet = Process32First(hSnap, &pe32);  
  9.   while ( bRet )  
  10.   {  
  11.     // strupr()函数是将字符串转化为大写  
  12.     if ( lstrcmp(strupr(pe32.szExeFile),strupr(szProcessName)) == 0 )  
  13.     {  
  14.       return pe32.th32ProcessID;  
  15.     }  
  16.     bRet = Process32Next(hSnap, &pe32);  
  17.   }  
  18.   return 0;  
  19. } +

InjectDll()函数的代码如下: 

  1. VOID CInjectDllDlg::InjectDll(DWORD dwPid, char *szDllName)  
  2.  
  3.   if ( dwPid == 0 || lstrlen(szDllName) == 0 ) 
  4.   {  
  5.     return ;  
  6.   }  
  7.   char *pFunName = "LoadLibraryA" 
  8.   // 打开目标进程  
  9.   HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);  
  10.   if ( hProcess == NULL )  
  11.   {  
  12.     return ;  
  13.   }  
  14.   // 计算欲注入 DLL 文件完整路径的长度  
  15.   int nDllLen = lstrlen(szDllName) + sizeof(char);  
  16.   // 在目标进程申请一块长度为 nDllLen 大小的内存空间  
  17.   PVOID pDllAddr = VirtualAllocEx(hProcess,NULL, nDllLen,MEM_COMMIT,PAGE_READWRITE);  
  18.   if ( pDllAddr == NULL )  
  19.   {  
  20.     CloseHandle(hProcess);  
  21.     return ;  
  22.   }  
  23.   DWORD dwWriteNum = 0 
  24.   // 将欲注入 DLL 文件的完整路径写入在目标进程中申请的空间内  
  25.   WriteProcessMemory(hProcess, pDllAddr, szDllName,nDllLen, &dwWriteNum);  
  26.   // 获得 LoadLibraryA()函数的地址  
  27.   FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"),pFunName);  
  28.   // 创建远程线程  
  29.   HANDLE hThread = CreateRemoteThread(hProcess,NULL, 0,(LPTHREAD_START_ROUTINE)pFunAddr,pDllAddr, 0, NULL);  
  30.   WaitForSingleObject(hThread, INFINITE);  
  31.   CloseHandle(hThread);  
  32.   CloseHandle(hProcess);  

InjectDll()函数有 2 个参数,分别是目标进程的 ID 值和要被注入的 DLL 文件的完整路径。在代码中获得的不是 LoadLibrary()函数的地址,而是 LoadLibraryA()函数的地址。在系统中其实没有 LoadLibrary()函数,有的只是 LoadLibraryA()和 LoadLibraryW()两个函数。这两个函数分别针对 ANSI 字符串和 UNICODE 字符串。而 LoadLibrary()函数只是一个宏。在编写程序的时候,直接使用该宏是可以的。如果要获取 LoadLibrary()函数的地址,就要明确指定是获取 LoadLibraryA()还是 LoadLibraryW()。

LoadLibrary()宏定义如下: 

  1. #ifdef UNICODE  
  2. #define LoadLibrary LoadLibraryW  
  3. #else  
  4. #define LoadLibrary LoadLibraryA  
  5. #endif // !UNICODE 

只要涉及字符串的函数,都会有相应的ANSI版本和UNICODE版本;其余不涉及字符串的函数,没有ANSI版本和UNICODE版本的区别。

为了测试DLL加载是否成功,在代码的DllMain()函数中加入如下代码: 

  1. case DLL_PROCESS_ATTACH:  
  2.  
  3.   MsgBox("!DLL_PROCESS_ATTACH!");  
  4.   break;  
  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(),其定义如下: 

  1. BOOL FreeLibrary(  
  2.  HMODULE hModule // handle to DLL module  
  3. ); 

该函数的参数是要卸载的模块的句柄。

FreeLibrary()函数使用的模块句柄可以通过Module32First()和Module32Next()两个函数获取。在使用Module32First()和Module32Next()两个函数的时候,需要用到MODULEENTRY32结构体,该结构体中保存了模块的句柄。MODULEENTRY32结构体的定义如下: 

  1. typedef struct tagMODULEENTRY32 {  
  2.  DWORD dwSize;  
  3.  DWORD th32ModuleID;  
  4.  DWORD th32ProcessID;  
  5.  DWORD GlblcntUsage;  
  6.  DWORD ProccntUsage;  
  7.  BYTE * modBaseAddr;  
  8.  DWORD modBaseSize;  
  9.  HMODULE hModule;  
  10.  TCHAR szModule[MAX_MODULE_NAME32 + 1];  
  11.  TCHAR szExePath[MAX_PATH];  
  12.  } MODULEENTRY32;  
  13. typedef MODULEENTRY32 *PMODULEENTRY32; 

该结构体中的hModule为模块的句柄,szModule为模块的名称,szExePath是完整的模块的名称(所谓完整,包括路径和模块名称)。

卸载远程进程中DLL模块的代码如下: 

  1. VOID CInjectDllDlg::UnInjectDll(DWORD dwPid, char *szDllName)  
  2.  
  3.   if ( dwPid == 0 || lstrlen(szDllName) == 0 )  
  4.   { 
  5.     return ;  
  6.   }  
  7.   HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,dwPid);  
  8.   MODULEENTRY32 me32;  
  9.   me32.dwSize = sizeof(me32);  
  10.   // 查找匹配的进程名称  
  11.   BOOL bRet = Module32First(hSnap, &me32);  
  12.   while ( bRet )  
  13.   {  
  14.     if ( lstrcmp(strupr(me32.szExePath),  
  15.     strupr(szDllName)) == 0 )  
  16.     {  
  17.       break;  
  18.     }  
  19.     bRet = Module32Next(hSnap, &me32);  
  20.   }  
  21.   CloseHandle(hSnap);  
  22.   char *pFunName = "FreeLibrary" 
  23.   HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);  
  24.   if ( hProcess == NULL )  
  25.   {  
  26.     return ;  
  27.   }  
  28.   FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"),pFunName);  
  29.   HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,  
  30.     (LPTHREAD_START_ROUTINE)pFunAddr,me32.hModule, 0, NULL);  
  31.   WaitForSingleObject(hThread, INFINITE);  
  32.   CloseHandle(hThread);  
  33.   CloseHandle(hProcess);  

卸载远程进程中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函数在前面都已经介绍过了。根据前面的步骤先来定义一个结构体,其定义如下: 

  1. #define STRLEN 20  
  2. typedef struct _DATA  
  3.  
  4.  DWORD dwLoadLibrary;  
  5.  DWORD dwGetProcAddress;  
  6.  DWORD dwGetModuleHandle;  
  7.  DWORD dwGetModuleFileName;  
  8.  char User32Dll[STRLEN];  
  9.  char MessageBox[STRLEN];  
  10.  char Str[STRLEN];  
  11. }DATA, *PDATA; 

该结构体中保存了LoadLibraryA()、GetProcAddress()、GetModuleHandle()和GetModu leFileName()四个API函数的地址。这四个API函数都属于Kernel32.dll的导出函数,因此可以在注入前进行获取。User32Dll中保存“User32.dll”字符串,因为MessageBoxA()函数是由User32.dll的导出函数。Str中保存的是通过MessageBoxA()函数弹出的字符串。

注入代码类似于前面介绍的注入代码,不过需要在注入代码中定义一个结构体变量,并进行相应的初始化,代码如下: 

  1. VOID CNoDllInjectDlg::InjectCode(DWORD dwPid)  
  2.  
  3.   // 打开进程并获取进程句柄  
  4.   HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);  
  5.   if ( hProcess == NULL )  
  6.   {  
  7.     return ;  
  8.   }  
  9.   DATA Data = { 0 };  
  10.   // 获取 kernel32.dll 中相关的导出函数  
  11.   Data.dwLoadLibrary = (DWORD)GetProcAddress(  
  12.     GetModuleHandle("kernel32.dll"),"LoadLibraryA");  
  13.   Data.dwGetProcAddress = (DWORD)GetProcAddress(  
  14.     GetModuleHandle("kernel32.dll"),"GetProcAddress");  
  15.   Data.dwGetModuleHandle = (DWORD)GetProcAddress(  
  16.     GetModuleHandle("kernel32.dll"),"GetModuleHandleA");  
  17.   Data.dwGetModuleFileName = (DWORD)GetProcAddress(  
  18.     GetModuleHandle("kernel32.dll"),"GetModuleFileNameA");  
  19.   // 需要的其他 DLL 和导出函数  
  20.   lstrcpy(Data.User32Dll, "user32.dll");  
  21.   lstrcpy(Data.MessageBox, "MessageBoxA");  
  22.   // MessageBoxA()弹出的字符串  
  23.   lstrcpy(Data.Str, "Inject Code !!!");  
  24.   // 在目标进程申请空间  
  25.   LPVOID lpData = VirtualAllocEx(hProcess, NULL, sizeof(Data),  
  26.     MEM_COMMIT | MEM_RELEASE,PAGE_READWRITE);  
  27.   DWORD dwWriteNum = 0 
  28.   WriteProcessMemory(hProcess, lpData, &Data,  
  29.     sizeof(Data), &dwWriteNum);  
  30.   // 在目标进程空间申请的用于保存代码的长度  
  31.   DWORD dwFunSize = 0x4000 
  32.   LPVOID lpCode = VirtualAllocEx(hProcess, NULL, dwFunSize,  
  33.     MEM_COMMIT,PAGE_EXECUTE_READWRITE); 
  34.   WriteProcessMemory(hProcess, lpCode, &RemoteThreadProc,  
  35.     dwFunSize, &dwWriteNum); 
  36.   HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0 
  37.     (LPTHREAD_START_ROUTINE)lpCode,lpData, 0, NULL);  
  38.   WaitForSingleObject(hThread, INFINITE);  
  39.   CloseHandle(hThread);  
  40.   CloseHandle(hProcess);  

上面的注入代码除了对结构体变量初始化外,还将线程函数代码写入目标进程空间的内存中。线程函数的代码如下: 

  1. DWORD WINAPI RemoteThreadProc(LPVOID lpParam)  
  2.  
  3.   PDATA pData = (PDATA)lpParam;  
  4.   // 定义 API 函数原型  
  5.   HMODULE (__stdcall *MyLoadLibrary)(LPCTSTR); 
  6.    FARPROC (__stdcall *MyGetProcAddress)(HMODULE, LPCSTR);  
  7.   HMODULE (__stdcall *MyGetModuleHandle)(LPCTSTR);  
  8.   int (__stdcall *MyMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT);  
  9.   DWORD (__stdcall *MyGetModuleFileName)(HMODULE, LPTSTR, DWORD);  
  10.   // 对各函数地址进行赋值  
  11.   MyLoadLibrary = (HMODULE (__stdcall *)(LPCTSTR))  
  12.     pData->dwLoadLibrary;  
  13.   MyGetProcAddress = (FARPROC (__stdcall *)(HMODULE, LPCSTR))  
  14.     pData->dwGetProcAddress;  
  15.   MyGetModuleHandle = (HMODULE (__stdcall *)(LPCSTR))  
  16.     pData->dwGetModuleHandle;  
  17.   MyGetModuleFileName = (DWORD (__stdcall *)(HMODULE, LPTSTR, DWORD))  
  18.     pData->dwGetModuleFileName;  
  19.   // 加载 User32.dll  
  20.   HMODULE hModule = MyLoadLibrary(pData->User32Dll);  
  21.   // 获得 MessageBoxA 函数的地址  
  22.   MyMessageBox = (int (__stdcall *)(HWND, LPCTSTR, LPCTSTR, UINT))  
  23.     MyGetProcAddress(hModule, pData->MessageBox);  
  24.   char szModuleFileName[MAX_PATH] = { 0 };  
  25.   MyGetModuleFileName(NULL, szModuleFileName, MAX_PATH);  
  26.   MyMessageBox(NULL, pData->Str, szModuleFileName, MB_OK);  
  27.   return 0; 
  28. }

上面就是无DLL注入的全部代码,编译连接并运行它。启动一个记事本程序来进行测试,可惜报错了。问题出在哪里呢?VC6的默认编译是Debug版本,这样会加入很多调试信息。而某些调试信息并不存在于代码中,而是在其他DLL模块中。这样,当执行到调试相关的代码时会访问不存在的DLL模块中的代码,就导致了报错。

将以上代码使用Release方式进行编译连接,然后可以无误地执行,如图4所示。

图4  Release方式下编译注入成功

编译的Debug版也可以进行无DLL的注入,只是实现起来略有不同。 

 

责任编辑:庞桂玉 来源: 计算机与网络安全
相关推荐

2021-03-01 11:20:13

网络安全多线程代码

2021-03-03 12:20:42

网络安全DLL编程

2021-01-26 13:45:03

网络安全Winsock编程

2021-02-23 10:20:07

网络安全进程代码

2021-02-21 18:19:43

网络安全网络安全编程创建进程

2016-10-10 00:18:27

2021-06-18 09:55:09

网络安全目录监控

2021-04-26 10:32:38

网络安全PE编程工具

2021-04-30 18:50:44

网络安全PE编程添加节区

2021-02-04 10:50:11

网络安全非阻塞模Winsock编程

2021-06-24 08:37:34

网络安全内核代码

2021-04-19 10:26:41

网络安全PE文件

2021-05-12 14:57:13

网络安全密码代码

2021-06-15 11:16:24

网络安全U盘软件

2021-05-24 11:55:55

网络安全Windows钩子函数

2021-01-18 10:35:18

网络安全Windows代码

2021-04-28 14:35:48

网络安全PE编程代码

2021-04-25 21:25:09

网络安全网络安全编程PE编程

2021-02-07 10:55:01

网络安全文件API

2021-03-01 11:38:15

网络安全进程代码
点赞
收藏

51CTO技术栈公众号