《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位的汇编程序。
相关文章:

《Windows PE》6.4.1 无 DLL远程注入
本节我们将演示如何实现远程注入的两种不同方案。方案一选择远程注入代码和数据,方案二选择远程注入DLL。 本节必须掌握的知识点: 无DLL远程注入 远程注入DLL 6.4.1 无DLL远程注入 实验四十五:无DLL远程注入(C语言实现…...

浙大数据结构:10-排序6 Sort with Swap(0, i)
这道题用了数环的思想,MOOC最后视频中也有相关介绍,思想还是很巧妙的 机翻 1、思想 2、 主函数 先把数据存进来,然后从头开始遍历,如果该位置数不对则anwser,然后遍历整个环,一直加anwser,如…...

基于vue框架的的爱心捐赠物资信息系统85gsu(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:用户,爱心项目,捐赠类型,捐赠,积分兑换,兑换,捐赠名录,捐赠去向 开题报告内容 基于Vue框架的爱心捐赠物资信息系统开题报告 一、研究背景与意义 随着社会的发展和人们生活水平的提高,爱心捐赠活动逐渐成为连接捐赠者与受赠…...

AI对抗AI:如何应对自动化攻击新时代?
在当今这个生成式AI迅猛发展的时代,自动化攻击的威胁日益加剧。 在人工智能浪潮下,如何利用AI对抗AI,从而实现全方位的网络安全防护? 一、AI浪潮下,自动化攻击加剧 AI技术的发展既带来了前所未有的挑战,也…...

【微服务】微服务注册:构建灵活的服务管理机制
目录 引言一、什么是微服务注册?1.1 服务注册中心的作用1.2 服务注册中心的工作原理1.3 示意图 二、常见的微服务注册中心2.1 各注册中心详细对比 三、微服务注册的实现方式3.1 Spring Cloud Netflix Eureka3.2 Consul3.3 Zookeeper3.4 etcd 四、微服务注册的注意事…...

AsyncTask的工作原理和缺陷
AsyncTask的工作原理及其缺陷 AsyncTask是Android平台提供的一个轻量级的异步任务类,它允许开发者在后台线程中执行耗时操作,并在操作完成后将结果回调到主线程以更新UI。AsyncTask内部封装了线程池和Handler机制,简化了多线程编程的复杂性。…...

【React】事件绑定的方式
1. 内联函数绑定 这是最简单直接的方式,即在 JSX 语法中直接传递一个内联函数。这种方式每次渲染时都会创建新的函数实例,可能会导致不必要的性能开销。 class MyComponent extends React.Component {render() {return (<button onClick{() > th…...

Android ImageView scaleType使用
目录 一、src设置图片资源 二、scaleType设置图片缩放类型 三、scaleType具体表现 matrix: fitXY: fitStart: fitCenter: fitEnd: Center: centerCrop: centerInside: 控制ImageView和图片的大小保持一致…...

【PhpSpreadsheet】ThinkPHP5+PhpSpreadsheet实现批量导出数据
目录 前言 一、安装 二、API使用 三、完整实例 四、效果图 前言 为什么使用PhpSpreadsheet? 由于PHPExcel不再维护,所以建议使用PhpSpreadsheet来导出exlcel,但是PhpSpreadsheet由于是个新的类库,所以只支持PHP7.1及以上的版…...

Python剪辑视频
import os from moviepy.editor import VideoFileClipvideo_dir r"E:\学习\视频剪辑" s_video_file "1.mp4" d_video_file "剪辑片段1.mp4" s_video_path os.path.join(video_dir, s_video_file) # 原视频文件路径 d_video_path os.path…...

LabVIEW提高开发效率技巧----高效文件I/O
在LabVIEW开发中,文件I/O操作常常是性能瓶颈之一,特别是处理大数据时,如何高效地存储和读取数据显得尤为重要。本文将详细介绍如何利用TDMS Streaming来实现高效的文件I/O,并结合具体例子说明在实际开发中的应用技巧。 1. 什么是T…...

影刀RPA接口_查询应用主流程参数结构
影刀接口_查询应用主流程参数结构 链接 import requests from time import sleepaccessKeyId"XXX" accessKeySecret"XXX"#1.获取token def get_access_token():url"https://api.yingdao.com/oapi/token/v2/token/create"headers{"Content…...

2d实时数字人聊天语音对话使用案例,对接大模型
参看: https://github.com/wan-h/awesome-digital-human-live2d 电脑环境: ubuntu 1060ti 下载: git clone https://github.com/wan-h/awesome-digital-human-live2d.gitdocker部署; cd awesome-digital-human-live2d docker-compose -f docker-compose-quickStart.ya…...

LeetCode | 69.x的平方根
这道题很适合用二分来解决,算是二门入门的一个练手题吧思想就是首先设置两个指针,一个是0,一个是x,相当于在数轴上划定一个区域 [ 0 , x ]然后计算数轴中间值和我们想要找的值的大小关系,因为数轴是有序的,…...

使用Windows创建一个MFC应用【带界面】
MFC使用教程【对初学者保姆型友好!】 目录 前提条件 1:创建MFC应用程序 2. 项目结构解读 引用 外部依赖项 头文件 源文件 资源文件 文件功能详解 项目的主要流程 步骤2:配置OpenCV 安装OpenCV 包含目录与库文件 步骤3࿱…...

springboot整合lombok
只需要引入lombok依赖 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version></dependency> 然后application.yml配置文件中加上 logging: level: …...

使用Arcgis批量自动出图
操作方法如下: 1 2 3 4 5 6 7 设置好选项,开始打印。 8 生成pdf。 第一步:shp放到数据库中,标注转注记,然后编辑注记,符号样式设置好。准备出图:(转注记时候尽量压盖监测等选最…...

Web Worker加载外部文件实践
概述 在Web Worker 多线程编程一文中介绍了Web Worker的编程思想,碰巧最近工作中某个工程需要加载外部文件,最大的文件大小达到30MB,Web Worker无疑是不错的选择。 编程实现 不用 Web Worker 加载外部文件使用原生的fetch方法读取文件,其核心代码如下: function loadland…...

2024年中国工业大模型行业发展研究报告|附43页PDF文件下载
工业大模型伴随着大模型技术的发展,逐渐渗透至工业,处于萌芽阶段。 就大模型的本质而言,是由一系列参数化的数学函数组成的计算系统,且是一个概率模型,其工作机制是基于概率和统计推动进行的,而非真正的理解…...

99. UE5 GAS RPG 被动技能实现
在这一篇,我们在之前打下的基础下,实现一下被动技能。 被动技能需要我们在技能栏上面选择升级解锁技能后,将其设置到技能栏,我们先增加被动技能使用的标签。 FGameplayTag Abilities_Passive_HaloOfProtection; //被动技能-守护光…...

U盘装系统,使用U盘启动,提示需要装驱动
今天装win10系统,用的是U盘启动,但安装过程中出现了找不到驱动程序无法完成安装的问题,逛了许多的论坛,换过两三个iso文件都不顶用,使用了许多种方式,都安装失败,最后在某个论坛看到一种安装方式…...

gaussdb 主备 8 数据库安全学习
1 用户及权限 1.1 默认权限机制-未开启三权分立 1.1.1 数据库系统管理员具有与对象所有者相同的权限。也就是说对象创建后,默认只有对象所有者或者系统管理员可以查询、修改和销毁对象,以及通过GRANT将对象的权限授予其他用户。 1.1.2 GaussDB支持以下的…...

React 基础阶段学习计划
React 基础阶段学习计划 目标 能够创建和使用React组件。理解并使用State和Props。掌握事件处理和表单处理。 学习内容 环境搭建 安装Node.js和npm 访问 Node.js官网 下载并安装最新版本的Node.js。打开终端或命令行工具,输入 node -v 和 npm -v 检查是否安装…...

FFmpeg的简单使用【Windows】--- 指定视频的时长
目录 功能描述 效果展示 代码实现 前端代码 后端代码 routers 》users.js routers 》 index.js app.js 功能描述 此案例是在上一个案例【FFmpeg的简单使用【Windows】--- 视频混剪添加背景音乐-CSDN博客】的基础上的进一步完善,可以先去看上一个案例然后再…...

请求参数中字符串的+变成了空格
前端请求 后端接收到的结果 在URL中,某些字符(包括空格、、&、? 等)需要被编码。具体而言,在URL中,空格通常被编码为 或 %20。因此,如果你在请求参数中使用 ,它会被解释为一个空格。 如果…...

前端开发攻略---使用AJAX监控网络请求进度
1、XHR实现 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document</title></head&…...

[已解决]DockerTarBuilder永久解决镜像docker拉取异常问题
前阵子发现阿里云的docker加速镜像失效了(甚至连nginx都拉取不了),重新换了并且加多了网络上比较常用的dokcer加速源,可以解决一部分问题,但仍然有一些镜像的某个版本或一些比较冷的镜像就是拉取不了,原因未…...

机器学习实战27-基于双向长短期记忆网络 BiLSTM 的黄金价格模型研究
大家好,我是微学AI,今天给大家介绍一下机器学习实战27-基于双向长短期记忆网络 BiLSTM 的黄金价格模型研究。本文针对黄金价格预测问题,展开基于改造后的长短期记忆网络BiLSTM的黄金价格模型研究。文章首先介绍了项目背景,随后详细…...

阿拉伯应用市场的特点
阿拉伯应用程序市场是一个充满活力和快速增长的行业,由年轻、精通技术的人口、高智能手机渗透率和整个地区数字化程度的提高所塑造。作为世界上最大的消费群体之一,阿拉伯语受众为希望扩大影响力的开发商和品牌提供了巨大的潜力。 文化相关性在应用程序…...

音频响度归一化 - python 实现
在处理音频样本时,往往我们的音频样本由于录制设备,环境,人发音的音量大小的不同影响,会造成音频响度不统一,分布在一个不同的响度值域上。为了让语音模型更好的学习音频特征,就有必要对音频的响度进行归一…...