进程注入的学习(下)
转载请声明出处:http://www.cnblogs.com/predator-wang/p/5076681.html
5. 无DLL注入(远程代码注入)
在第三中方法种,我们启动远程线程时,线程函数是我们从Kernel32.dll中取得的LoadLibrary函数的地址为线程函数的地址,其实我们可以直接将线程函数体和函数参数写入目标进程的地址空间,然后创建远程线程。
使用这个方法时,需要注意以下几个问题:
(1) 远程线程函数体不得使用kernel32.dll,user32.dll以外的函数。因为这个两个模块在各个进程的相对地址是一样的,如果一定要使用其它函数,则必须将函数体写入目标进程空间。
(2) 不能使用任何字符串常量,因为字符串常量是存放在PE文件里.data这个段里面的,函数里保存的只是相对地址。
(3) 去掉编译器的/GZ编译选项,这个选项是用来Enable Stack Frame Run-Time Error Checking。当这个选项打开时,编译器会在每个函数中加入一些代码,用来检验ESP在函数体中是否被改变,但是这些检验函数的地址在不同PE文件中 有可能是不一样的。
(4) 不得使用增量链接(incremental linking)。增量链接是编译器为了减少链接时间做的处理,把函数体用一个JMP指令代替,这样就可以随意改变函数的内容,而不用修改CALL指令。
(5) 不要在函数体内使用超过4kb的局部变量。局部变量是存放在栈中的,例如下面这个函数
:00401000 push ebp
:00401001 mov ebp, esp
:00401003 sub esp, 00000100 ; change ESP as storage for
; local variables is needed
:00401006 mov byte ptr [esp], 00 ; var[0] = 0;
:0040100A mov byte ptr [esp+01], 01 ; var[1] = 1;
:0040100F mov byte ptr [esp+FF], FF ; var[255] = 255;
:00401017 mov esp, ebp ; restore stack pointer
:00401019 pop ebp
:0040101A ret
但是当局部变量的大小超过4kb时,栈指针并不直接改版,而是调用另一个函数来分配内存,这个函数有可能在不同进程中的地址不一样。
(6) 函数体内switch语句中的case不要超过3个,否则编译器会在PE文件中使用跳转表,而这个跳转表有可能在目标进程中并不存在。
下面是一个无DLL注入(代码注入)的例子:
例如我们要把代码注入到HW.exe中,HW.exe的源码很简单,如下:
#include <iostream> #include <windows.h> using namespace std; int main()//指针数组 { cout << "HelloWorld" << endl; MessageBoxW(NULL, L"hello", L"hello", MB_OK); system("pause"); return 0; }
查看进程ID:
实现注入代码的程序NoDLLInject.exe:
#include <iostream> #include <Windows.h> #include <string.h> #include <tchar.h> using namespace std; typedef struct _RemotePara{ PVOID dwMessageBox; wchar_t strMessageBox[12]; }RemotePara; // 远程线程执行体 DWORD __stdcall ThreadProc(RemotePara *Para) { typedef int( *PMessageBox) (HWND, LPCWSTR, LPCWSTR, UINT); PMessageBox MessageBoxFunc = (PMessageBox)Para->dwMessageBox; MessageBoxFunc(NULL, Para->strMessageBox, Para->strMessageBox, MB_OK); return 0; } void EnableDebugPriv() { HANDLE hToken; LUID sedebugnameValue; TOKEN_PRIVILEGES tkp; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) return; if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)) { CloseHandle(hToken); return; } tkp.PrivilegeCount = 1; tkp.Privileges[0].Luid = sedebugnameValue; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL)) { CloseHandle(hToken); } } int main(int argc, char *argv[]) { DWORD THREADSIZE = 1024; DWORD pID = 23060; DWORD byte_write; HANDLE hRemoteProcess, hThread; RemotePara myRemotePara, *pRemotePara; void *pRemoteThread; HINSTANCE hUser32; hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID); cout << hRemoteProcess << endl; if (!hRemoteProcess) return 0; // 在远程进程地址空间分配虚拟内存 pRemoteThread = VirtualAllocEx(hRemoteProcess, 0, THREADSIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); cout << pRemoteThread << endl; if (!pRemoteThread) return 0; // 将线程执行体ThreadProc写入远程进程 if (!WriteProcessMemory(hRemoteProcess, pRemoteThread, &ThreadProc, THREADSIZE, 0)) { VirtualFreeEx(hRemoteProcess, pRemoteThread, 1024, MEM_RELEASE); return 0; } //cout << &ThreadProc << endl; ZeroMemory(&myRemotePara, sizeof(RemotePara)); hUser32 = LoadLibraryW(L"user32.dll"); myRemotePara.dwMessageBox = (PVOID)GetProcAddress(hUser32, "MessageBoxW"); cout << myRemotePara.dwMessageBox << endl; wcscat(myRemotePara.strMessageBox, L"Hello!"); //复制MessageBox函数的参数 //写进目标进程 pRemotePara = (RemotePara *)VirtualAllocEx(hRemoteProcess, 0, sizeof(RemotePara), MEM_COMMIT, PAGE_EXECUTE_READWRITE); cout << pRemotePara << endl; if (!pRemotePara) return 0; if (!WriteProcessMemory(hRemoteProcess, pRemotePara, &myRemotePara, sizeof myRemotePara, 0)) { VirtualFreeEx(hRemoteProcess, pRemotePara, sizeof(RemotePara), MEM_RELEASE); return 0; } // 启动线程 hThread = CreateRemoteThread(hRemoteProcess, 0, 0, (LPTHREAD_START_ROUTINE)pRemoteThread, pRemotePara, 0, &byte_write); //myRemotePara.dwMessageBox(NULL, L"Hello", L"Hello", MB_OK); FreeLibrary(hUser32); CloseHandle(hRemoteProcess); system("pause"); return 0; }
注入成功:
实验成功之前有个小插曲,就是同样的NoDLLInject.exe,但是注入时就会报错:
其原因就在于最开始被注入的HW.exe的源码是:
#include <iostream> #include <windows.h> using namespace std; int main()//指针数组 { cout << "HelloWorld" << endl; system("pause"); return 0; }
这里没有用到MessageBoxW,查看其导入表,也就没有user32.dll:
而提示访问违法的地方:
正好是从user32.dll中取到的MessageBoxW的地址:
这就说明了NoDLLInject.exe中的导入表中有user32.dll,所以可以取到MessageBoxW的地址;而HW.exe由于没有导入user32.dll,所以user32.dll没有加载到它的进程地址空间中,无法执行MessageBoxW。而如果加上语句:
MessageBoxW(NULL, L"hello", L"hello", MB_OK);
则,HW.exe的导入表中就会自动包含user32.dll:
这说明,Windows默认同一个系统中dll的加载位置是固定的,同时,我们又知道dll里有一系列按序排列的输出函数,因此这些函数在任何进程的地址空间中的位置是固定(相同)的。
参考:
http://www.cnblogs.com/fanzhidongyzby/archive/2012/08/30/2664287.html
http://andylin02.iteye.com/blog/459489
6.CreateProcess注入
如果是父子进程关系,父进程可以得到子进程的主线程的句柄,使用该句柄,可以修改线程执行的代码。控制子进程的主线程执行:
1)使用CreateProcess生成一个子进程,并暂停这个子进程的运行;
2)从子进程PE结构中解析出主线程的起始内存地址(可以借助ImageHlp.dll中的函数实现);
3)将子进程的入口代码保存起来;
4)将某些硬编码的机器指令强制放入该子进程的入口地址处,这些指令其实就是LoadLibrary(“MyDll”)的汇编指令,从而实现Dll注入;
5)恢复运行修改后的子进程的主线程,使该代码得以执行(利用ResumeThread),LoadLibrary("MyDll")实现注入;
6)将原始指令重新放入子进程的起始地址;
7)让进程继续从起始地址重新开始执行,如同没有任何事一样(Jmp跳转)。
- 示例代码:
#include <iostream> #include "windows.h" //有人问我为什么第三个参数是指针的指针,下边会讲解 void createShellcode(int ret, int str, unsigned char** shellcode, int* shellcodeSize) { unsigned char* retChar = (unsigned char*)&ret; unsigned char* strChar = (unsigned char*)&str; int api = (int)GetProcAddress(LoadLibraryA("kernel32.dll"), "LoadLibraryA"); unsigned char* apiChar = (unsigned char*)&api; unsigned char sc[] = { // Push ret 0x68, retChar[0], retChar[1], retChar[2], retChar[3],//保存原本要执行的下一条指令的地址,eip // Push all flags 0x9C, // Push all register 0x60, // Push 0x66666666 (later we convert it to the string of D:\\Coding\\test\\InjCreateProcDll\\InjCreateProcDll\\Debug\\InjCreateProcDll.dll;) 0x68, strChar[0], strChar[1], strChar[2], strChar[3], // Mov eax, 0x66666666 (later we convert it to LoadLibrary adress) 0xB8, apiChar[0], apiChar[1], apiChar[2], apiChar[3], // Call eax 0xFF, 0xD0, // Pop all register 0x61, // Pop all flags 0x9D, // Ret 还原要执行的下一条指令,eip 0xC3 }; *shellcodeSize = 22; *shellcode = (unsigned char*)malloc(22); memcpy(*shellcode, sc, 22); } int main() { char dllPath[] = "D:\\Coding\\test\\InjCreateProcDll\\InjCreateProcDll\\Debug\\InjCreateProcDll.dll"; unsigned char* shellcode = nullptr;//unsigned char 1个字节 int shellcodeLen = 0; CONTEXT ctx; PROCESS_INFORMATION pi; STARTUPINFOA Startup; ZeroMemory(&Startup, sizeof(Startup)); ZeroMemory(&pi, sizeof(pi)); //挂起创建子进程 CreateProcessA("D:\\Coding\\test\\HW\\HW\\Debug\\HW.exe", NULL, NULL, NULL, NULL, CREATE_SUSPENDED, NULL, NULL, &Startup, &pi); //开辟一段空间存放dll的路径 LPVOID remote_dllStringPtr = VirtualAllocEx(pi.hProcess, NULL, strlen(dllPath) + 1, MEM_COMMIT, PAGE_READWRITE); //为shellcode开辟空间 LPVOID remote_shellcodePtr = VirtualAllocEx(pi.hProcess, NULL, shellcodeLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE); ctx.ContextFlags = CONTEXT_CONTROL;//查询控制寄存器组 GetThreadContext(pi.hThread, &ctx); //eip存放着要读取的指令的地址 //变量shellcode通过createShellcode函数获得一个返回值(在createShellcode中开辟空间,从而获得开辟空间的地址), //所以传递的是shellcode的指针。而他本身又是一个指针变量所以createShellcode的第三个参数是指针的指针 //比如,如果需要更改参数int i的值,我们给子函数fun传递的就必须是&i。 //如果给fun传递的仍是i,由于fun(int j)其实是在其自身内部创建一个局部变量j,然后把 //i的值传给j,fun是对局部变量j的修改,返回到父函数时,j被销毁。 //如果传递的是指针,则fun通过对i的地址空间的数据进行修改保证返回父函数后i的数据得以修改。 //同理,你初始化的变量是一个指针shellcode,想要通过createShellcode获得新的值就得传递shellcode的指针 createShellcode(ctx.Eip, (int)remote_dllStringPtr, &shellcode, &shellcodeLen); //把"D:\\Coding\\test\\InjCreateProcDll\\InjCreateProcDll\\Debug\\InjCreateProcDll.dll"写到HW.exe的remote_dllStringPtr处 WriteProcessMemory(pi.hProcess, remote_dllStringPtr, dllPath, strlen(dllPath) + 1, NULL); WriteProcessMemory(pi.hProcess, remote_shellcodePtr, shellcode, shellcodeLen, NULL); //更改要执行的指令的地址 ctx.Eip = (DWORD)remote_shellcodePtr; ctx.ContextFlags = CONTEXT_CONTROL; SetThreadContext(pi.hThread, &ctx); ResumeThread(pi.hThread); Sleep(8000); VirtualFreeEx(pi.hProcess, remote_dllStringPtr, strlen(dllPath) + 1, MEM_DECOMMIT); VirtualFreeEx(pi.hProcess, remote_shellcodePtr, shellcodeLen, MEM_DECOMMIT); return 0; }
其中DLL中的代码很简单,就是弹一个MessageBox.
运行后弹窗:
后,又能显示:
参考:
https://msdn.microsoft.com/en-us/library/ms682425%28VS.85%29.aspx
http://bbs.pediy.com/showthread.php?p=1237836
http://bbs.pediy.com/showthread.php?t=191311