《Windows PE》6.4.1 无 DLL远程注入
本节我们将演示如何实现远程注入的两种不同方案。方案一选择远程注入代码和数据,方案二选择远程注入DLL。
本节必须掌握的知识点:
无DLL远程注入
远程注入DLL
6.4.1 无DLL远程注入
实验四十五:无DLL远程注入(C语言实现)
我们尝试使用C语言实现一个无DLL远程注入的例子,支持32位和64位PE。
源码:
resource.h
//{{NO_DEPENDENCIES}}// Microsoft Visual C++ 生成的包含文件。// 供 remoteThread.rc 使用//#define ICO_MAIN 1000#define DLG_MAIN 1000#define IDC_INFO 1001#define IDM_MAIN 2000#define IDM_OPEN 2001#define IDM_EXIT 2002// Next default values for new objects//#ifdef APSTUDIO_INVOKED#ifndef APSTUDIO_READONLY_SYMBOLS#define _APS_NEXT_RESOURCE_VALUE 105#define _APS_NEXT_COMMAND_VALUE 4000#define _APS_NEXT_CONTROL_VALUE 1001#define _APS_NEXT_SYMED_VALUE 101#endif#endif
RemoteInject.rc
// Microsoft Visual C++ generated resource script.//#include "resource.h"#define APSTUDIO_READONLY_SYMBOLS///// Generated from the TEXTINCLUDE 2 resource.//#include "winres.h"/#undef APSTUDIO_READONLY_SYMBOLS/// 中文(简体,中国) resources#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED#ifdef APSTUDIO_INVOKED///// TEXTINCLUDE//1 TEXTINCLUDEBEGIN"resource.h\0"END2 TEXTINCLUDEBEGIN"#include ""winres.h""\r\n""\0"END3 TEXTINCLUDEBEGIN"\r\n""\0"END#endif // APSTUDIO_INVOKED///// Icon//// Icon with lowest ID value placed first to ensure application icon// remains consistent on all systems.ICO_MAIN ICON "dpatch.ico"///// Dialog//DLG_MAIN DIALOG 50,50,544,399STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENUCAPTION "远程线程演示程序"MENU IDM_MAINFONT 9,"宋体"BEGINCONTROL "",IDC_INFO,"RichEdit20A",196 | ES_WANTRETURN | WS_CHILD | ES_READONLY| WS_VISIBLE |WS_BORDER | WS_VSCROLL | WS_TABSTOP,0,0,540,396END///// DESIGNINFO//#ifdef APSTUDIO_INVOKEDGUIDELINES DESIGNINFOBEGINIDD_DIALOG1, DIALOGBEGINLEFTMARGIN, 7RIGHTMARGIN, 302TOPMARGIN, 7BOTTOMMARGIN, 169ENDEND#endif // APSTUDIO_INVOKED///// Menu//IDM_MAIN menu discardableBEGINPOPUP "文件(&F)"BEGINmenuitem "插入到PEHeader.exe(&O)...",IDM_OPENmenuitem separatormenuitem "退出(&x)",IDM_EXITENDEND///// AFX_DIALOG_LAYOUT//IDD_DIALOG1 AFX_DIALOG_LAYOUTBEGIN0END#endif // 中文(简体,中国) resources/#ifndef APSTUDIO_INVOKED///// Generated from the TEXTINCLUDE 3 resource.///#endif // not APSTUDIO_INVOKED
EnableDebugPriv.c
#include <stdio.h>#include <windows.h>int EnableDebugPriv(const WCHAR *name){HANDLE hToken; //进程令牌句柄TOKEN_PRIVILEGES tp; //TOKEN_PRIVILEGES结构体,其中包含一个【类型+操作】的权限数组LUID luid; //上述结构体中的类型值//打开进程令牌环//GetCurrentProcess()获取当前进程的伪句柄,只会指向当前进程或者线程句柄,随时变化if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)){printf("OpenProcessToken error\n");return -8;}//获得本地进程name所代表的权限类型的局部唯一IDif (!LookupPrivilegeValue(NULL, name, &luid)){printf("LookupPrivilegeValue error\n");}tp.PrivilegeCount = 1; //权限数组中只有一个“元素”tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //权限操作tp.Privileges[0].Luid = luid; //权限类型//调整进程权限if (!AdjustTokenPrivileges(hToken, 0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)){printf("AdjustTokenPrivileges error!\n");return -9;}return 0;}
RemoteInject.c
/*------------------------------------------------------------------------FileName:remoteThread.c实验45:远程线程注入演示程序功能:目标是在进程PEHeader.exe中远程注入一段代码和数据,运行并显示"Welcome to PE!"对话框。测试步骤:当PEHeader.exe运行时,运行remoteThread.exe,文件菜单---插入到PEHeader.exe(c) bcdaren, 2024-----------------------------------------------------------------------*/#include <windows.h>#include <strsafe.h> //StringCchCopy#include <commctrl.h>#pragma comment(lib,"comctl32.lib")#include <Richedit.h>#include <tchar.h>#include <string.h>#include "resource.h"//使用typedef给函数指针类型一个别名,函数指针存储函数地址typedef FARPROC(__stdcall *_ApiSuspend)(HANDLE);typedef HMODULE(__stdcall *_ApiResume)(HANDLE);typedef HMODULE(__stdcall *_ApiLoadLibraryA)(LPCSTR);_ApiSuspend _suspendProcess;_ApiResume _resumeProcess;//INJDATA(注入的数据)typedef LRESULT(WINAPI *MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, UINT);typedef struct {HWND hwnd; // handle to edit controlMESSAGEBOX fnMessageBox; // pointer to user32!MessageBoxCHAR psText[128]; // buffer that is Text} INJDATA;extern int EnableDebugPriv(const WCHAR *);BOOL CALLBACK ProcDlgMain(HWND, UINT, WPARAM, LPARAM);BOOL _patchPEInfo();void _Init();HANDLE hInstance;HWND hWinMain, hWinEdit;//ThreadFunc(注入的代码)static DWORD WINAPI ThreadFunc(INJDATA *pData){pData->fnMessageBox(pData->hwnd, (LPCTSTR)pData->psText, NULL, MB_OK);return 0;}// This function marks the memory address after ThreadFunc.// int cbCodeSize = (PBYTE) AfterThreadFunc - (PBYTE) ThreadFunc.static void AfterThreadFunc(void){return;}int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nCmdShow){const TCHAR szDllEdit[] = TEXT("RichEd20.dll");const TCHAR szClassEdit[] = TEXT("RichEdit20W");//peinfo.rc中定义const TCHAR szNtdll[] = TEXT("ntdll.dll");const CHAR szSuspend[] = "ZwSuspendProcess";//函数名需要定义为ASCII字符const CHAR szResume[] = "ZwResumeProcess";const TCHAR szErr3[] = TEXT("Error happend when getting address.");HMODULE hRichEdit, hNtdll;hRichEdit = LoadLibrary((LPCWSTR)&szDllEdit);hNtdll = LoadLibrary((LPCWSTR)&szNtdll);_suspendProcess = (_ApiSuspend)GetProcAddress(hNtdll, szSuspend);if (!_suspendProcess)MessageBox(NULL, szErr3, NULL, MB_OK);_resumeProcess = (_ApiResume)GetProcAddress(hNtdll, szResume);if (!_resumeProcess)MessageBox(NULL, szErr3, NULL, MB_OK);hInstance = GetModuleHandle(NULL);DialogBoxParam(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)ProcDlgMain, (LPARAM)0);FreeLibrary(hRichEdit);return 0;}BOOL CALLBACK ProcDlgMain(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam){switch (wMsg){case WM_CLOSE:EndDialog(hWnd, 0);return TRUE;case WM_INITDIALOG:hWinMain = hWnd;_Init(); //初始化return TRUE;case WM_COMMAND:switch (wParam){case IDM_EXIT:EndDialog(hWnd, 0);return TRUE;case IDM_OPEN: //停止_patchPEInfo();return TRUE;}}return FALSE;}void _Init(){CHARFORMAT stCf;static TCHAR szClassEdit[] = TEXT("RichEdit20W"); //UNICODE版本//static TCHAR szClassEdit[] = TEXT("RichEdit20A"); //ASCII码版本static TCHAR szFont[] = TEXT("宋体");//设置编辑控件hWinEdit = GetDlgItem(hWinMain, IDC_INFO);SendMessage(hWinEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0);RtlZeroMemory(&stCf, sizeof(stCf));stCf.cbSize = sizeof(stCf);stCf.yHeight = 9 * 20;stCf.dwMask = CFM_FACE | CFM_SIZE | CFM_BOLD;StringCchCopy((LPTSTR)&stCf.szFaceName, lstrlen(szFont) + 1, (LPCTSTR)&szFont);SendMessage(hWinEdit, EM_SETCHARFORMAT, 0, (LPARAM)&stCf);SendMessage(hWinEdit, EM_EXLIMITTEXT, 0, -1);}/*;--------------------; 将远程线程打到进程PEHeader.exe中; 测试方法:首先运行PEHeader.exe; 启动该程序,单击第一个菜单的第一项; 会发现桌面上弹出"Welcome to PE!"对话框;--------------------*/BOOL _patchPEInfo(){HANDLE phwnd, hProcess, hCreate;DWORD parent, hProcessID;static TCHAR strTitle[256];static TCHAR szBuffer[256];const TCHAR szTitle[] = TEXT("PE文件头中几个关键地址的定位:");const TCHAR szErr1[] = TEXT("Error happend when openning.");const TCHAR szErr2[] = TEXT("Error happend when VirtualAllocEx.");static int dwProcessID, dwThreadID;_ApiLoadLibraryA lpLoadLibrary;INJDATA injdata = {0};PVOID lpData,lpCode;// 注意:写入内存中的字符都是ANSI字符const TCHAR szDllKernel[] = TEXT("Kernel32.dll");const CHAR szLoadLibrary[] = "LoadLibraryA";const CHAR szUser32[] = "user32.dll";const CHAR szMessageBox[] = "MessageBoxA";CHAR szText[] = "Welcome to PE!";//获取Kernel32.dll句柄,LoadLibrary函数地址lpLoadLibrary =(_ApiLoadLibraryA)GetProcAddress(GetModuleHandle(szDllKernel), (LPCSTR)szLoadLibrary);HMODULE huser32 = lpLoadLibrary(szUser32);MESSAGEBOX lpMessageBoxA = (MESSAGEBOX)GetProcAddress(huser32, (LPCSTR)szMessageBox);//通过标题获得进程的handleparent = 0; //复位标志phwnd = GetWindow(GetWindow(GetDesktopWindow(), GW_CHILD), GW_HWNDFIRST);if (!GetParent(phwnd))parent = 1;while (phwnd){if (parent){parent = 0; //复位标志//得到窗口标题文字GetWindowText(phwnd, strTitle, sizeof(strTitle));if (!_tcscmp(szTitle, strTitle))break;}//寻找这个窗口的下一个兄弟窗口phwnd = GetWindow(phwnd, GW_HWNDNEXT);if (!GetParent(phwnd)){if (IsWindowVisible(phwnd))parent = 1;}}//初始化INJDATAinjdata.hwnd = phwnd;injdata.fnMessageBox = lpMessageBoxA;StringCchCopyA(injdata.psText,128,szText);//根据窗口句柄获取进程IDGetWindowThreadProcessId(phwnd, &hProcessID);//获得调试权限if (EnableDebugPriv(SE_DEBUG_NAME)){printf("Add Privilege error\n");return -1;}//1.打开本地进程hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, hProcessID);if (!hProcess){MessageBox(NULL, szErr1, NULL, MB_OK);return FALSE;}//2.注入数据分配空间,以页为单位lpData = VirtualAllocEx(hProcess, NULL, sizeof(injdata), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (!lpData){MessageBox(NULL, szErr2, NULL, MB_OK);return FALSE;}//3.写入线程代码,注意:写入内存中的字符都是ANSI字符SIZE_T dwWrite = 0;if (!WriteProcessMemory(hProcess, lpData, &injdata, sizeof(injdata), &dwWrite)){MessageBox(NULL, L"写入内存失败!\n", NULL, MB_OK);return FALSE;}//4.注入数据分配空间,以页为单位(PBYTE)AfterThreadFunc - (PBYTE)ThreadFunclpCode = VirtualAllocEx(hProcess, NULL, 26, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (!lpData){MessageBox(NULL, szErr2, NULL, MB_OK);return FALSE;}//5.写入线程代码,注意:写入内存中的字符都是ANSI字符if (!WriteProcessMemory(hProcess, lpCode, (PBYTE)ThreadFunc, 26, &dwWrite)){MessageBox(NULL, L"写入内存失败!\n", NULL, MB_OK);return FALSE;}//6.创建一个在另一个进程的虚拟地址空间中运行的线程hCreate = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpCode,0,0,NULL);//7.等待线程结束返回,释放资源WaitForSingleObject(hCreate, -1);CloseHandle(hCreate);VirtualFreeEx(hProcess, lpData, 0, MEM_FREE);VirtualFreeEx(hProcess, lpCode, 0, MEM_FREE);CloseHandle(hProcess);return TRUE;}
运行:
图6-4 无DLL远程注入
总结
●我们总结一下使用该技术的步骤:
1.为了确保注入成功,注入之前先调用EnableDebugPriv(SE_DEBUG_NAME);获取调试权限。
2.得到远程进程的HANDLE(OpenProcess)。
3.在远程进程中为要注入的数据分配内存(VirtualAllocEx)。
4.把初始化后的INJDATA结构复制到分配的内存中(WriteProcessMemory)。
5.在远程进程中为要注入的数据分配内存(VirtualAllocEx)。
6.把ThreadFunc复制到分配的内存中(WriteProcessMemory)。
7.用CreateRemoteThread启动远程的ThreadFunc。
8.等待线程结束返回,释放资源(WaitForSingleObject)。
●让我们看一下CreateRemoteThread的声明和CreateThread相比,有以下不同:
1.增加了hProcess参数。这是要在其中创建线程的进程的句柄。
2.CreateRemoteThread的lpStartAddress参数必须指向远程进程的地址空间中的函数。这个函数必须存在于远程进程中,所以我们不能简单地传递一个本地ThreadFucn的地址,我们必须把代码复制到远程进程。
3.同样,lpParameter参数指向的数据也必须存在于远程进程中,我们也必须复制它。
●编写ThreadFunc时必须遵守以下规则:
1. ThreadFunc不能调用除kernel32.dll和user32.dll之外动态库中的API函数。只有kernel32.dll和user32.dll(如果被加载)可以保证在本地和目的进程中的加载地址是一样的。(注意:user32.dll并不一定被所有的Win32进程加载!)。如果你需要调用其他库中的函数,在注入的代码中使用LoadLibrary和GetProcessAddress动态加载。如果由于某种原因,你需要的动态库已经被映射进了目的进程,你也可以使用GetMoudleHandle代替LoadLibrary。同样,如果你想在ThreadFunc中调用你自己的函数,那么就分别复制这些函数到远程进程并通过INJDATA把地址提供给ThreadFunc。
2. 不要使用static字符串。把所有的字符串提供INJDATA传递。为什么?编译器会把所有的静态字符串放在可执行文件的“.data”段,而仅仅在代码中保留它们的引用(即指针)。这样,远程进程中的ThreadFunc就会执行不存在的内存数据(至少没有在它自己的内存空间中)。
3. 去掉编译器的/GZ(调用约定)编译选项。这个选项是默认的(VS项目”属性”->”C\C++”->”启用C++异常”改为”否”)。
4. 要么把ThreadFunc和AfterThreadFunc声明为static,要么关闭链接器的“启用增量连接(incremental linking)”。
5. ThreadFunc中的局部变量总大小必须小于4k字节。注意,当degug编译时,这4k中大约有10个字节会被事先占用。
6. 如果有多于3个switch分支的case语句,必须像下面这样分割开,或用if-else if代替。
switch( expression ) {
case constant1: statement1; goto END;
case constant2: statement2; goto END;
case constant3: statement2; goto END;
}
switch( expression ) {
case constant4: statement4; goto END;
case constant5: statement5; goto END;
case constant6: statement6; goto END;
}
END:
【注】可以参考我们的另外一本教材《汇编的角度C语言》中对于switch结构的地址表分析,当switch结构超过3个case语句时,启用地址表结构跳转目的地址。
●注入的数据和代码
注入数据INJDATA injdata ,长度为sizeof(injdata)。
注入代码ThreadFunc,长度理论上应该是(PBYTE)AfterThreadFunc-(PBYTE) ThreadFunc。
但是编译器编译过程中可能会添加额外代码或者并没有按照源码的先后顺序编译,因此更好的方法是使用OD调试器获取注入代码的准确长度。
将Release版的RemoteInject.exe拖入OD调试器,打开“内存映射”窗口,双击.text节区。找到函数ThreadFunc的反汇编代码,如下所示。
002A1050 > 55 PUSH EBP
002A1051 8BEC MOV EBP,ESP
002A1053 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8]
002A1056 6A 00 PUSH 0x0
002A1058 6A 00 PUSH 0x0
002A105A FF70 08 PUSH DWORD PTR DS:[EAX+0x8]
002A105D FF30 PUSH DWORD PTR DS:[EAX]
002A105F 8B40 04 MOV EAX,DWORD PTR DS:[EAX+0x4]
002A1062 FFD0 CALL EAX
002A1064 33C0 XOR EAX,EAX
002A1066 5D POP EBP
002A1067 C2 0400 RETN 0x4
注入代码二进制硬编码的长度为26个字节。为了印证,我们将RemoteInject.exe拖入WinHex,找到.text节区,在偏移地址00000450~00000469之间为ThreadFunc函数的二进制代码,如下所示:
00000450 55 8B EC 8B 45 08 6A 00 6A 00 FF 70 08 FF 30 8B U嬱婨.j.j.p.0?
00000460 40 04 FF D0 33 C0 5D C2 04 00 @.?繻?.
接着我们在VS源代码中调试,在CreateRemoteThread语句处下断点,监视窗口获取&ThreadFunc函数的地址为0x00A51050,内存窗口查看该地址处的数据如下所示:
0x00A51050 55 8b ec 8b 4d 08 6a 00 6a 00 8d 41 08 50 ff 31 8b 41 04 ff
0x00A51064 d0 33 c0 5d c2 04 00 cc cc cc cc cc 55 8b ec 81 ec a4 00 00
注入数据如下所示:
图6-5 远程注入数据
VS调试过程中,注入顺利完成,但是当执行CreateRemoteThread创建远程线程后弹出对话框。
我们还可以使用windbg调试器附加PEHeader.exe进程,F9下断点后,命令行输入如下命令查询内存:
0:012> db 0x07730000 ;查看注入的数据
07730000 a4 11 12 00 b0 fd 1b 76-57 65 6c 63 6f 6d 65 20 .......vWelcome
07730010 74 6f 20 50 45 21 00 00-00 00 00 00 00 00 00 00 to PE!..........
07730020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0:012> db 0x07740000 ;查看注入的代码
07740000 55 8b ec 8b 4d 08 6a 00-6a 00 8d 41 08 50 ff 31 U...M.j.j..A.P.1
07740010 8b 41 04 ff d0 33 c0 5d-c2 04 00 00 00 00 00 00 .A...3.]........
07740020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
●如果你的远程进程崩溃了,原因可能为下列之一:
1.你引用了ThreadFunc中一个不存在的字符串。
2.ThreadFunc中一个或多个指令使用了绝对寻址。
3.ThreadFunc调用了一个不存在的函数(这个函数调用可能是编译器或连接器添加的)。这时候你需要在反汇编器中寻找类似下面的代码:
:004014C0 push EBP ; entry point of ThreadFunc
:004014C1 mov EBP, ESP
...
:004014C5 call 0041550 ; 在这里崩溃了
; remote process
...
:00401502 ret
如果这个有争议的CALL是编译器添加的(因为一些不该打开的编译开关比如/GZ打开了),它要么在ThreadFunc的开头要么在ThreadFunc接近结尾的地方。
不管在什么情况下,当使用CreateRemoteThread & WriteProcessMemory技术时必须万分的小心,特别是编译器/链接器的设置,它们很可能会给你的ThreadFunc添加一些带来麻烦的东西。
●结论:
通过CreateRemoteThread和WriteProcessMemory来注入代码的技术,不需要一个额外的DLL文件,因此更灵活,但也更复杂更危险。一旦你的ThreadFunc中有错误,远程线程会立即崩溃。调试一个远程的ThreadFunc也是场恶梦,所以你应该在仅仅注入若干条指令时才使用这个方法。要注入大量的代码还是使用另外一种方法吧——远程注入DLL。
如果我们能够熟练的掌握汇编语言,当然也可以直接使用汇编语言编写一个无DLL注入的程序,毕竟使用汇编语言可以非常方便的直接对代码和数据进行重定位,这是C语言无法做到的。
实验四十六:无DLL远程注入(汇编语言实现)
我们尝试使用汇编语言实现一个无DLL远程注入的例子,实现远程注入PEHeader.exe,仅支持32位PE。
●remoteThread.asm
;FileName:remoteThread.asm;实验46:无DLL远程注入;by:bcdaren;2024.04.24;=======================================.386.model flat,stdcalloption casemap:noneinclude windows.incinclude user32.incincludelib user32.libinclude kernel32.incincludelib kernel32.libinclude comdlg32.incincludelib comdlg32.libICO_MAIN equ 1000DLG_MAIN equ 1000IDC_INFO equ 1001IDM_MAIN equ 2000IDM_OPEN equ 2001IDM_EXIT equ 2002IDM_1 equ 4000IDM_2 equ 4001IDM_3 equ 4002;声明函数BCGetProcAddress typedef proto :dword,:dword ;声明函数引用 ApiGetProcAddress typedef ptr BCGetProcAddress BCLoadLibrary typedef proto :dwordApiLoadLibrary typedef ptr BCLoadLibraryBCMessageBoxA typedef proto :dword,:dword,:dword,:dwordApiMessageBoxA typedef ptr BCMessageBoxA.datahInstance dd ?hProcess dd 0hProcessID dd 0phwnd dd ?hRichEdit dd ?hWinMain dd ?hWinEdit dd ?lpRemote dd ?szFileName db MAX_PATH dup(?)strTitle db 256 dup(0)parent dd 0szBuffer db 256 dup(0)dwPatchDD dd 1dwFlag dd 0szOut1 db '窗口ID=%d',0szOut2 db '进程号=%d',0szOut3 db '进程ID=%d',0szOut db '从进程PEInfo.exe中取出的标志位的值为:%08x',0;**************************************************;书中源码缺失的数据定义hNtdll dd ?BCSuspend typedef proto :dword ;声明函数BCResume typedef proto :dword ;声明函数ApiSuspend typedef ptr BCSuspend ;声明函数引用ApiResume typedef ptr BCResume ;声明函数引用_suspendProcess ApiSuspend ?_resumeProcess ApiResume ?;**************************************************.constszErr1 db 'Error happend when openning.',0szErr2 db 'Error happend when reading.',0szErr3 db 'Error happend when getting address.',0szDllEdit db 'RichEd20.dll',0szClassEdit db 'RichEdit20A',0szFont db '宋体',0szTitle db 'PE文件头中几个关键地址的定位:',0szNtdll db 'ntdll.dll',0szSuspend db 'ZwSuspendProcess',0szResume db 'ZwResumeProcess',0.codeREMOTE_THREAD_START equ this byte;------------------------------------; 获取kernel32.dll的基地址;------------------------------------_getKernelBase proclocal @dwRetpushadassume fs:nothingmov eax,fs:[30h] ;获取PEB所在地址mov eax,[eax+0ch] ;获取PEB_LDR_DATA 结构指针mov esi,[eax+1ch] ;获取InInitializationOrderModuleList 链表头;第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针lodsd ;获取双向链表当前节点后继的指针mov eax,[eax+8] ;获取kernel32.dll的基地址mov @dwRet,eaxpopadmov eax,@dwRetret_getKernelBase endp ;-------------------------------; 获取指定字符串的API函数的调用地址; 入口参数:_hModule为动态链接库的基址; _lpApi为API函数名的首址; 出口参数:eax为函数在虚拟地址空间中的真实地址;-------------------------------_getApi proc _hModule,_lpApilocal @retlocal @dwLenpushadmov @ret,0;计算API字符串的长度,含最后的零mov edi,_lpApimov ecx,-1xor al,alcldrepnz scasbmov ecx,edisub ecx,_lpApimov @dwLen,ecx;从pe文件头的数据目录获取导出表地址mov esi,_hModuleadd esi,[esi+3ch]assume esi:ptr IMAGE_NT_HEADERSmov esi,[esi].OptionalHeader.DataDirectory.VirtualAddressadd esi,_hModuleassume esi:ptr IMAGE_EXPORT_DIRECTORY;查找符合名称的导出函数名mov ebx,[esi].AddressOfNamesadd ebx,_hModulexor edx,edx.repeatpush esimov edi,[ebx]add edi,_hModulemov esi,_lpApimov ecx,@dwLenrepz cmpsb.if ZERO?pop esijmp @F.endifpop esiadd ebx,4inc edx.until edx>=[esi].NumberOfNamesjmp _ret@@:;通过API名称索引获取序号索引再获取地址索引sub ebx,[esi].AddressOfNamessub ebx,_hModuleshr ebx,1add ebx,[esi].AddressOfNameOrdinalsadd ebx,_hModulemovzx eax,word ptr [ebx]shl eax,2add eax,[esi].AddressOfFunctionsadd eax,_hModule;从地址表得到导出函数的地址mov eax,[eax]add eax,_hModulemov @ret,eax_ret:assume esi:nothingpopadmov eax,@retret_getApi endp_remoteThread proc uses ebx edi esi lParamcall @F ; 免去重定位@@:pop ebxsub ebx,offset @B;获取kernel32.dll的基地址invoke _getKernelBasemov [ebx+offset hKernel32Base],eax;从基地址出发搜索GetProcAddress函数的首址mov eax,offset szGetProcAddradd eax,ebxmov edi,offset hKernel32Basemov ecx,[ebx+edi]invoke _getApi,ecx,eaxmov [ebx+offset lpGetProcAddr],eax;为函数引用赋值 GetProcAddressmov [ebx+offset _getProcAddress],eax ;使用GetProcAddress函数的首址;传入两个参数调用GetProcAddress函数,获得LoadLibraryA的首址mov eax,offset szLoadLibadd eax,ebxmov edi,offset hKernel32Basemov ecx,[ebx+edi]mov edx,offset _getProcAddressadd edx,ebx;模仿调用 invoke GetProcAddress,hKernel32Base,addr szLoadLibpush eaxpush ecxcall dword ptr [edx] mov [ebx+offset _loadLibrary],eax;使用LoadLibrary获取user32.dll的基地址mov eax,offset user32_DLLadd eax,ebxmov edi,offset _loadLibrarymov edx,[ebx+edi]push eaxcall edx ; invoke LoadLibraryA,addr _loadLibrarymov [ebx+offset hUser32Base],eax;使用GetProcAddress函数的首址,获得函数MessageBoxA的首址mov eax,offset szMessageBoxadd eax,ebxmov edi,offset hUser32Basemov ecx,[ebx+edi]mov edx,offset _getProcAddressadd edx,ebx;模仿调用 invoke GetProcAddress,hUser32Base,addr szMessageBoxpush eaxpush ecxcall dword ptr [edx] mov [ebx+offset _messageBox],eax;调用函数MessageBoxAmov eax,offset szTextadd eax,ebxmov edx,offset _messageBoxadd edx,ebx;模仿调用 invoke MessageBoxA,NULL,addr szText,NULL,MB_OK push MB_OKpush NULLpush eaxpush NULLcall dword ptr [edx] ret_remoteThread endp;------------------------------------------------; 远程线程用到的数据;------------------------------------------------szText db 'Welcome to PE!',0szGetProcAddr db 'GetProcAddress',0szLoadLib db 'LoadLibraryA',0szMessageBox db 'MessageBoxA',0user32_DLL db 'user32.dll',0,0;定义函数_getProcAddress ApiGetProcAddress ? _loadLibrary ApiLoadLibrary ?_messageBox ApiMessageBoxA ?hKernel32Base dd ?hUser32Base dd ?lpGetProcAddr dd ?lpLoadLib dd ?REMOTE_THREAD_END equ this byteREMOTE_THREAD_SIZE=offset REMOTE_THREAD_END-offset REMOTE_THREAD_START;----------------;初始化窗口程序;----------------_init proclocal @stCf:CHARFORMATinvoke GetDlgItem,hWinMain,IDC_INFOmov hWinEdit,eaxinvoke LoadIcon,hInstance,ICO_MAINinvoke SendMessage,hWinMain,WM_SETICON,ICON_BIG,eax ;为窗口设置图标invoke SendMessage,hWinEdit,EM_SETTEXTMODE,TM_PLAINTEXT,0 ;设置编辑控件invoke RtlZeroMemory,addr @stCf,sizeof @stCfmov @stCf.cbSize,sizeof @stCfmov @stCf.yHeight,9*20mov @stCf.dwMask,CFM_FACE or CFM_SIZE or CFM_BOLDinvoke lstrcpy,addr @stCf.szFaceName,addr szFontinvoke SendMessage,hWinEdit,EM_SETCHARFORMAT,0,addr @stCfinvoke SendMessage,hWinEdit,EM_EXLIMITTEXT,0,-1ret_init endp;--------------------; 将远程线程打到进程PEInfo.exe中; 测试方法:首先运行PEInfo.exe; 启动该程序,单击第一个菜单的第一项; 会发现桌面上弹出HelloWorldPE对话框;--------------------_patchPEInfo proclocal @dwTemppushad;通过标题获得进程的handleinvoke GetDesktopWindowinvoke GetWindow,eax,GW_CHILDinvoke GetWindow,eax,GW_HWNDFIRSTmov phwnd,eaxinvoke GetParent,eax.if !eaxmov parent,1.endifmov eax,phwnd.while eax.if parentmov parent,0 ;复位标志;得到窗口标题文字invoke GetWindowText,phwnd,addr strTitle,\sizeof strTitlenopinvoke lstrcmp,addr strTitle,addr szTitle.if !eaxmov eax,phwnd.break.endif.endif;寻找这个窗口的下一个兄弟窗口invoke GetWindow,phwnd,GW_HWNDNEXTmov phwnd,eaxinvoke GetParent,eax.if !eaxinvoke IsWindowVisible,phwnd.if eaxmov parent,1.endif.endifmov eax,phwnd.endw;根据窗口句柄获取进程IDinvoke GetWindowThreadProcessId,phwnd,addr hProcessIDinvoke OpenProcess,PROCESS_ALL_ACCESS,\FALSE,hProcessID.if !eaxinvoke MessageBox,NULL,addr szErr1,NULL,MB_OKjmp @ret.endifmov hProcess,eax ;找到的进程句柄在hProcess中;分配空间invoke VirtualAllocEx,hProcess,NULL,\REMOTE_THREAD_SIZE,MEM_COMMIT,\PAGE_EXECUTE_READWRITE.if eaxmov lpRemote,eax;写入线程代码invoke WriteProcessMemory,hProcess,\lpRemote,\offset REMOTE_THREAD_START,\REMOTE_THREAD_SIZE,\addr @dwTempmov eax,lpRemoteadd eax,offset _remoteThread-offset REMOTE_THREAD_START;eax指向_remoteThread起始地址invoke CreateRemoteThread,hProcess,NULL,0,eax,0,0,NULL .endifinvoke CloseHandle,hProcess@ret:popadret_patchPEInfo endp;-------------------; 窗口程序;-------------------_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParammov eax,wMsg.if eax==WM_CLOSEinvoke EndDialog,hWnd,NULL.elseif eax==WM_INITDIALOG ;初始化push hWndpop hWinMaincall _init.elseif eax==WM_COMMAND ;菜单mov eax,wParam.if eax==IDM_EXIT ;退出invoke EndDialog,hWnd,NULL.elseif eax==IDM_OPEN ;停止invoke _patchPEInfo.elseif eax==IDM_1 .elseif eax==IDM_2.elseif eax==IDM_3.endif.elsemov eax,FALSEret.endifmov eax,TRUEret_ProcDlgMain endpstart:invoke LoadLibrary,offset szDllEditmov hRichEdit,eaxinvoke LoadLibrary,offset szNtdllmov hNtdll,eaxinvoke GetProcAddress,hNtdll,addr szSuspendmov _suspendProcess,eax.if !eaxinvoke MessageBox,NULL,addr szErr3,NULL,MB_OK.endifinvoke GetProcAddress,hNtdll,addr szResumemov _resumeProcess,eax.if !eaxinvoke MessageBox,NULL,addr szErr3,NULL,MB_OK.endifinvoke GetModuleHandle,NULLmov hInstance,eaxinvoke DialogBoxParam,hInstance,\DLG_MAIN,NULL,offset _ProcDlgMain,NULLinvoke FreeLibrary,hRichEditinvoke ExitProcess,NULLend start
●remoteThread.rc
#include <resource.h>#define ICO_MAIN 1000#define DLG_MAIN 1000#define IDC_INFO 1001#define IDM_MAIN 2000#define IDM_OPEN 2001#define IDM_EXIT 2002#define IDM_1 4000#define IDM_2 4001#define IDM_3 4002#define IDM_4 4003ICO_MAIN ICON "dpatch.ico"DLG_MAIN DIALOG 50,50,544,399STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENUCAPTION "远程线程演示程序"MENU IDM_MAINFONT 9,"宋体"BEGINCONTROL "",IDC_INFO,"RichEdit20A",196 | ES_WANTRETURN | WS_CHILD | ES_READONLY| WS_VISIBLE |WS_BORDER | WS_VSCROLL | WS_TABSTOP,0,0,540,396ENDIDM_MAIN menu discardableBEGINPOPUP "文件(&F)"BEGINmenuitem "插入到PEInfo.exe(&O)...",IDM_OPENmenuitem separatormenuitem "退出(&x)",IDM_EXITENDPOPUP "查看"BEGINmenuitem "源文件",IDM_1menuitem "窗口透明度",IDM_2menuitem separatormenuitem "大小",IDM_3menuitem "宽度",IDM_4ENDEND
运行:
图6-7 汇编代码无DLL注入
总结
上述汇编代码使用masm32编译而成,仅支持32位PE。有兴趣的读者可以尝试使用masm64编写一个64位的汇编程序。