Windows下的代码注入

木马和病毒的好坏很大程度上取决于它的隐蔽性,木马和病毒本质上也是在执行程序代码,如果采用独立进程的方式需要考虑隐藏进程否则很容易被发现,在编写这类程序的时候可以考虑将代码注入到其他进程中,借用其他进程的环境和资源来执行代码。远程注入技术不仅被木马和病毒广泛使用,防病毒软件和软件调试中也有很大的用途,最近也简单的研究过这些东西,在这里将它发布出来。

想要将代码注入到其他进程并能成功执行需要解决两个问题:

  1. 第一个问题是如何让远程进程执行注入的代码。原始进程有它自己的执行逻辑,想要破坏原来的执行流程,使EIP寄存器跳转到注入的代码位置基本是不可能的
  2. 第二个问题是每个进程中地址空间是独立的,比如在调用某个句柄时,即使是同一个内核对象,在不同进程中对应的句柄也是不同的,这就需要进行地址转化。

要进行远程代码注入的要点和难点主要就是这两个问题,下面给出两种不同的注入方式来说明如何解决这两个问题

DLL注入

DLL注入很好的解决了第二个问题,DLL被加载到目标进程之后,它里面的代码中的地址就会自动被转化为对应进程中的地址,这个特性是由于DLL加载的过程决定的,它会自己使用它所在进程中的资源和地址空间,所以只要DLL中不存在硬编码的地址,基本不用担心里面会出现函数或者句柄需要进行地址转化的问题。
那么第一个问题改怎么解决呢?

要执行用户代码,在Windows中最常见的就是使用回调的方式,Windows采用的是事件驱动的方式,只要发生了某些事件就会调用回调,在众多使用回调的场景中,线程的回调是最简单的,它不会干扰到目标进程的正常执行,也就不用考虑最后还原EIP的问题,因此DLL注入采用的最常见的就是创建一个远程线程,让线程加载DLL代码。

DLL注入中一般的思路是:使用CreateRemoteThread来在目标进程中创建一个远程的线程,这个线程主要是加载DLL到目标进程中,由于DLL在入口函数(DLLMain)中会处理进程加载Dll的事件,所以将注入代码写到这个事件中,这样就能执行注入的代码了。那么如何在远程进程中执行DLL的加载操作呢?我们知道加载DLL主要使用的是函数LoadLibrary,仔细分析线程的回调函数和LoadLibrary函数的原型,会发现,它们同样都是传入一个参数,而CreateRemoteThread函数正好需要一个函数的地址作为回调,并且传入一个参数作为回调函数的参数。这样就有思路了,我们让LoadLibrary作为线程的回调函数,将对应dll的文件名和路径作为参数传入,这样就可以在对应进程中加载dll了,进一步也就可以执行dllmain中的对应代码了。

还有一个很重要的问题,我们知道不同进程中,地址空间是隔离的,那么我在注入的进程中传入LoadLibrary函数的地址,这算是一个硬编码的地址,它在目标进程中是否是一样的呢?答案是,二者的地址是一样的,这是由于kernel32.dll在32位程序中加载的基地址是一样的,而LoadLibrary在kernel32.dll中的偏移是一定的(只要不同的进程加载的是同一份kernel32.dll)那么不同进程中的LoadLibrary函数的地址是一样的。其实不光是LoadLibrary函数,只要是kernel32.dll中导出的函数,在不同进程中的地址都是一样的。注意这里只是32位,如果想要使用32位程序往64位目标程序中注入,可能需要考虑地址转换的问题,只要知道kernel32.dll在64位中的偏移,就可以计算出对应函数的地址了。

LoabLibrary函数传入的代表路径的字符串的首地址在不同进程中同样是不同的,而且也没办法利用偏移来计算,这个时候解决的办法就是在远程进程中申请一块虚拟地址空间,并将目标字符串写入对应的地址中,然后将对应的首地址作为参数传入即可。

最后总结一下DLL注入的步骤:

  1. 获取LoadLibrary函数的地址
  2. 调用VirtualAllocEx 函数在远程进程中申请一段虚拟内存
  3. 调用WriteProcessMemory 函数将参数写入对应的虚拟内存
  4. 调用CreateRemoteThread 函数创建远程线程,线程的回调函数为LoadLibrary,参数为对应的字符串的地址

按照这个思路可以编写如下的代码:

typedef HMODULE(WINAPI *pfnLoadLibrary)(LPCWSTR);

if (!DebugPrivilege()) //提权代码,在Windows Vista 及以上的版本需要将进程的权限提升,否则打开进程会失败
{
  return FALSE;
}

//打开目标进程
HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); //dwPid是对应的进程ID
if (NULL == hRemoteProcess)
{
  AfxMessageBox(_T("OpenProcess Error"));
}

//查找LoadLibrary函数地址
pfnLoadLibrary lpLoadLibrary = (pfnLoadLibrary)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryW");
//在远程进程中申请一块内存用于保存对应线程的参数
PVOID pBuffer = VirtualAllocEx(hRemoteProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
//在对应内存位置处写入参数值
DWORD dwWritten = 0;
WriteProcessMemory(hRemoteProcess, pBuffer, m_csDLLName.GetString(), (m_csDLLName.GetLength() + 1) * sizeof(TCHAR), &dwWritten);

//创建远程线程并传入对应参数
HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpLoadLibrary, pBuffer, 0, NULL);
WaitForSingleObject(hRemoteThread, INFINITE);
VirtualFreeEx(hRemoteProcess, pBuffer, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
CloseHandle(hRemoteProcess);

卸载远程DLL

上面进行了代码的注入,作为一个文明的程序,自然得考虑卸载dll,毕竟现在提倡环保,谁使用,谁治理。这里既然注入了,自然得考虑卸载。
卸载的思路与注入的类似,只是函数变为了FreeLibrary,传入的参数变成了对应的dll的句柄了。

如何获取这个模块的句柄呢?我们可以枚举进程中的模块,根据模块的名称来找到对应的模块并获取它的句柄。枚举的方式一般是使用toolhelp32中对应的函数,下面是卸载的例子代码

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, m_dwPid);
if (INVALID_HANDLE_VALUE == hSnapshot)
{
  AfxMessageBox(_T("CreateToolhelp32Snapshot Error"));
  return;
}

MODULEENTRY32  me = {0};
me.dwSize = sizeof(MODULEENTRY32);
BOOL bRet = Module32First(hSnapshot, &me);
while (bRet)
{
  CString csModuleFile = _tcsupr(me.szExePath);
  if (csModuleFile == _tcsupr((LPTSTR)m_csDLLName.GetString()) != -1)
  {
    break;
  }

  ZeroMemory(&me, sizeof(me));
  me.dwSize = sizeof(PROCESSENTRY32);
  bRet = Module32Next(hSnapshot, &me);
}

CloseHandle(hSnapshot);

typedef BOOL (*pfnFreeLibrary)(HMODULE);
pfnFreeLibrary FreeLibrary = (pfnFreeLibrary)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "FreeLibrary");

HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_dwPid);
if (hRemoteProcess == NULL)
{
    AfxMessageBox(_T("OpenProcess Error"));
    return;
}

HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, me.modBaseAddr, 0, NULL);
WaitForSingleObject(hRemoteThread, INFINITE);
CloseHandle(hRemoteThread);
CloseHandle(hRemoteProcess);

无DLL的注入

注入不一定需要使用DLL,虽然使用DLL比较简单一点,无DLL注入在解决上述两个问题的第一个思路是一样的,也是使用CreateRemoteThread来创建一个远程线程来执行目标代码。

无dll的注入主要麻烦是在进行地址转化上,在调用API的时候,如果无法保证对应的dll的基地址不变的话,就得在目标进程中自行调用LoadLibrary来动态获取函数地址,并调用。

在动态获取API函数的地址的时候,主要使用的函数是LoadLibrary、GetModuleHandle、GetProcAddress这三个函数,而线程的回调函数只能传入一个参数,所以我们需要将对应的需要传入的参数组成一个结构体,并将结构体对应的数据写入到目标进程的内存中,特别要注意的是,里面不要使用指针或者句柄这种与地址有关的东西。
例如我们想在目标进程中注入一段代码,让它弹出一个对话框,以便测试是否注入成功。这种情况除了要传入上述三个函数的地址外,还需要MesageBox,而MessageBox是在user32.dll中,user32.dll在每个进程中的基地址并不相同,因此在注入的代码中需要动态加载,因此可以定义下面一个结构

typedef struct REMOTE_DATA
{
    DWORD dwLoadLibrary;
    DWORD dwGetProcAddress;
    DWORD dwGetModuleHandle;

    DWORD dwGetModuelFileName; //辅助函数
    char szUser32dll[MAX_PATH]; //存储user32dll的路径,以便调用LoadLibrary加载
    char szMessageBox[128]; //存储字符串MessageBoxA 这个字符串,以便使用GetProcAddress加载MesageBox函数
    char szMessage[512]; //弹出对话框上显示的字符
}

不使用DLL注入与使用DLL注入的另一个区别是,不使用DLL注入的时候需要自己加载目标代码到对应的进程中,这个操作可以借由WriteProcessMemory 将函数代码写到对应的虚拟内存中。
最后注入的代码主要如下:

DWORD WINAPI RemoteThreadProc(LPVOID lpParam)
{
	LPREMOTE_DATA lpData = (LPREMOTE_DATA)lpParam;
	typedef HMODULE (WINAPI *pfnLoadLibrary)(LPCSTR);
	typedef FARPROC (WINAPI *pfnGetProcAddress)(HMODULE, LPCSTR);
	typedef HMODULE (*pfnGetModuleHandle)(LPCSTR);
	typedef DWORD (WINAPI *pfnGetModuleFileName)( HMODULE,LPSTR, DWORD);

	pfnGetModuleHandle MyGetModuleHandle = (pfnGetModuleHandle)lpData->dwGetModuleHandle;
	pfnGetModuleFileName MyGetModuleFileName = (pfnGetModuleFileName)lpData->dwGetModuleFileName;
	pfnGetProcAddress MyGetProcAddress = (pfnGetProcAddress)lpData->dwGetProcAddress;
	pfnLoadLibrary MyLoadLibrary = (pfnLoadLibrary)lpData->dwGetProcAddress;

	typedef int (WINAPI *pfnMessageBox)(HWND, LPCSTR, LPCSTR, UINT);
	//加载User32.dll
	HMODULE hUser32Dll = MyLoadLibrary(lpData->szUerDll);
	//加载MessageBox函数
	pfnMessageBox MyMessageBox = (pfnMessageBox)MyGetProcAddress(hUser32Dll, lpData->szMessageBox);
	char szTitlte[MAX_PATH] = "";
	MyGetModuleFileName(NULL, szTitlte, MAX_PATH);

	MyMessageBox(NULL, lpData->szMessage, szTitlte, MB_OK);
	return 0;
}

m_dwPid = GetPid(); //获取目标进程ID
DebugPrivilege(); //进程提权
HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE,  m_dwPid);
if (NULL == hRemoteProcess)
{
  AfxMessageBox(_T("OpenProcess Error"));
  return;
}

LPREMOTE_DATA lpData = new REMOTE_DATA;
ZeroMemory(lpData, sizeof(REMOTE_DATA));

//获取对应函数的地址
lpData->dwGetModuleFileName = (DWORD)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "GetModuleFileNameA");
lpData->dwGetModuleHandle = (DWORD)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "GetModuleHandleA");
lpData->dwGetProcAddress = (DWORD)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "GetProcAddress");
lpData->dwLoadLibrary = (DWORD)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryA");

// 拷贝对应的字符串
StringCchCopyA(lpData->szMessage, MAX_STRING_LENGTH, "Inject Success!!!");
StringCchCopyA(lpData->szUerDll, MAX_PATH, "user32.dll");
StringCchCopyA(lpData->szMessageBox, MAX_PROC_NAME_LENGTH, "MessageBoxA");

//在远程空间中申请对应的内存,写入参数和函数的代码
LPVOID lpRemoteBuf = VirtualAllocEx(hRemoteProcess, NULL, sizeof(REMOTE_DATA), MEM_COMMIT, PAGE_READWRITE); // 存储data结构的数据
LPVOID lpRemoteProc = VirtualAllocEx(hRemoteProcess, NULL, 0x4000, MEM_COMMIT, PAGE_EXECUTE_READWRITE); // 存储函数的代码
DWORD dwWrittenSize = 0;
WriteProcessMemory(hRemoteProcess, lpRemoteProc, &RemoteThreadProc, 0x4000, &dwWrittenSize);
WriteProcessMemory(hRemoteProcess, lpRemoteBuf, lpData, sizeof(REMOTE_DATA), &dwWrittenSize);

HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteProc, lpRemoteBuf, 0, NULL);
WaitForSingleObject(hRemoteThread, INFINITE);
VirtualFreeEx(hRemoteProcess, lpRemoteBuf, 0, MEM_RELEASE);
VirtualFreeEx(hRemoteProcess, lpRemoteProc, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
CloseHandle(hRemoteProcess);

delete[] lpData;

posted @ 2018-09-09 19:00  masimaro  阅读(431)  评论(0编辑  收藏  举报