0x00-远程线程注入
前置知识
线程注入,是通过开启远程线程的方式,将DLL加载到目标宿主进程中的常用方式。
由于WinNT系统下进程空间的独立性,获取其他进程的信息,就需要进入目标进程空间的方式,而使用线程注入可以轻松实现。
使用LoadLibrary动态加载DLL
使用GetProcAddress获取DLL中导出函数的指针
DLL的分类
在VS的编译环境下,DLL又分为三类:
非MFC的DLL——即使用SDK API进行编程,能被其他所有语言调用
MFC规则DLL——可以使用MFC进行编程,能被其他所有语言调用
MFC扩展DLL——可以使用MFC进行编程,但只能被用MFC编写的程序调用
MFC——Microsoft Foundation Class-Library是微软用C++对API进行的封装,全部封装成了类,简化了使用
DLL的入口点和参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 BOOL WINAPI DllMain ( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ) { switch ( fdwReason ) { case DLL_PROCESS_ATTACH: break ; case DLL_THREAD_ATTACH: break ; case DLL_THREAD_DETACH: break ; case DLL_PROCESS_DETACH: break ; } return TRUE; }
DLL编写与导出
DLL的导出函数使用
extern “C” _declspec(dllexport)
DLL的导入函数使用
extern “C” _declspec(dllimport)
其中,extern "C"作为一种编译约定,表示按照C语言的方式导出
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。 这样我们就可以直接通过函数名对DLL导出函数进行调用
DLL动态加载
既然我们要把DLL注入到进程中,那么需要先了解一下,进程是怎样调用DLL的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef int (*ADD_IMPORT) (int a,int b) ;ADD_IMPORT add_proc=(ADD_IMPORT)GetProcAddress(hMod,"ADD" ); int result = add_proc(1 ,2 );FreeLibrary(hMod);
线程注入
注入的可行性
kernel32.dll和user32.dll是两个在大部分程序上都会调用的DLL
同一个DLL,在不同进程中不一定被映射(加载)在同一个内存地址下
但是kernel32.dll和user32.dll除外,它们总是被映射到进程的内存首选地址
因此在所有使用这两个DLL的进程中,这两个DLL的内存地址是相同的
因此我们在本进程获取的kernel32.dll中函数的地址,在目标进程也是一样的
线程注入过程
目标进程->开辟并传入DLL地址->开启远程线程->加载DLL->实现DLL的注入
依次使用以下函数
1 2 3 4 5 6 7 OpenProcess() VirtualAllocEx() WriteProcessMemory() GetProcAddress() CreateRemoteThreadEx() WaitForSingleObject() CloseHandle()
CreateRemoteThread()函数
1 2 3 4 5 6 7 8 HANDLE WINAPI CreateRemoteThread ( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ) ;
目标其实就是让目标进程调用LoadLibrary()
加载dll
将线程函数指定为LoadLibrary()函数,并且把DLL加载地址传递给线程参数,这样就在目标进程中成功调用了FreeLibrary函数
CreateRemoteThread()原意是在外部进程调用执行线程函数,不过这里的线程函数换成了FreeLibrary()
Inject.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include "windows.h" #include "tchar.h" BOOL InjectDll (DWORD dwPID, LPCTSTR szDllPath) { HANDLE hProcess = NULL , hThread = NULL ; HMODULE hMod = NULL ; LPVOID pRemoteBuf = NULL ; DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1 ) * sizeof (TCHAR); LPTHREAD_START_ROUTINE pThreadProc; if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { _tprintf(L"OpenProcess(%d) failed!!! [%d]\n" , dwPID, GetLastError()); return FALSE; } pRemoteBuf = VirtualAllocEx(hProcess, NULL , dwBufSize, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL ); hMod = GetModuleHandle(L"kernel32.dll" ); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW" ); hThread = CreateRemoteThread(hProcess, NULL , 0 , pThreadProc, pRemoteBuf, 0 , NULL ); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } int _tmain(int argc, TCHAR* argv[]){ if (argc != 3 ) { _tprintf(L"USAGE : %s <pid> <dll_path>\n" , argv[0 ]); return 1 ; } if (InjectDll((DWORD)_tstol(argv[1 ]), argv[2 ])) _tprintf(L"InjectDll(\"%s\") success!!!\n" , argv[2 ]); else _tprintf(L"InjectDll(\"%s\") failed!!!\n" , argv[2 ]); return 0 ; }
inject.dll
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include "pch.h" #include <windows.h> #include <stdio.h> extern "C" _declspec(dllexport) void Print () { printf ("helloworld" ); } BOOL APIENTRY DllMain ( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBoxA(0 , "注入成功" , "Hint" , MB_OK); break ; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; } return TRUE; }
效果
可以看到在notepad.exe的进程中注入了inject.dll
附
https://blog.csdn.net/xiewneqi/article/details/4683888?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~TopBlog-1.topblog&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~TopBlog-1.topblog&utm_relevant_index=1
有时候需要提升进程权限
在枚举/结束系统进程/或操作系统服务时,会出现自己权限不足而失败的情况,这时就需要提升自己进程到系统权限
前置知识
Windows的每个用户登录时,系统会产生一个访问令牌(access token),其中关联了当前用户的权限信息,用户登录后创建的每一个进程都含有用户access token的拷贝。当进程试图执行某些需要特殊权限的操作或者是访问受保护的内核对象时,系统会检查其access token中的权限信息以决定是否授权操作。
Administrator组成员的access token中会含有一些可以执行系统级操作的特权(privileges),如终止任意进程、关闭\重启系统、加载设备驱动和更改系统时间等,不过这些权限默认是被禁用的
当Administrator组成员创建的进程中包含一些需要特权的操作时,进程必须首先打开这些禁用的特权以提升自己的权限,否则系统将拒绝进程的操作。
windows以字符串的形式表示系统特权,如”SeCreatePageFilePrivilege“表示该特权用于创建页面文件,”SeDebugPrivilege“表示该特权可用于调试及更改其他进程的内存,为了方便使用这些字符串,微软在winnt.h定义了一组宏,如#define SE_DEBUG_NAME TEXT(“SeDebugPrivilege”)
在vs的定义也可以看到,这些宏定义对应了不同的权限,可以使用LookupPrivilege函数得到对应权限的LUID
虽然Windows使用字符串表示特权,但查询或更改特权的API需要LUID来引用相应的特权,LUID表示 local unique identifier ,在系统中是唯一的。为了提升进程权限到指定的特权,我们必须找到特权对应的LUID,这时候就需要调用LookupPrivilege函数,获取到特权对应的LUID时,我们要打开该特权,此时要用到LUID_AND_ATTRIBUTES结构
其定义如下
1 2 3 4 typedef struct _LUID_AND_ATTRIBUTES { LUID Luid; DWORD Attributes; } LUID_AND_ATTRIBUTES, * PLUID_AND_ATTRIBUTES;
当Attributes的值取SE_PRIVILEGE_ENABLED时将打开LUID对应的特权。设置完成后
AdjustTokenPrivileges函数通知操作系统将指定的access token权限中的特权置为打开状态,前面我们说过,进程执行需要特列权限的操作时,系统将检查其access token,因此更改了进程的access token特权设置,也就是更改了所属进程的特权设置
函数定义如下
1 2 3 4 5 6 7 8 BOOL WINAPI AdjustTokenPrivileges ( __in HANDLE TokenHandle, __in BOOL DisableAllPrivileges, __in_opt PTOKEN_PRIVILEGES NewState, __in DWORD BufferLength, __out_opt PTOKEN_PRIVILEGES PreviousState, __out_opt PDWORD ReturnLength ) ;
TokenHandle是要更改特权设置的access token的句柄,DisableAllPrivileges表示是否禁用该access token的所有特权,NewState用来传递新的特权设置,注意它的类型是PTOKEN_PRIVILEGES,它是TOKEN_PRIVILEGES结构的指针
TOKEN_PRIVILEGES定义如下
1 2 3 4 typedef struct _TOKEN_PRIVILEGES { DWORD PrivilegeCount; LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
其中ANYSIZE_ARRAY被定义为1,可以看到TOKEN_PRIVILEGES中包含了用于设置特权信息的LUID_AND_ATTRIBUTES结构,在使用时,只需要将PrivilegeCount赋为1,然后把Privileges数组的第1个元素(Privileges[0])的Luid域设置为指定特权的Luid,再将其Attributes域设置为SE_PRIVILEGE_ENABLED,就可以完成TokenHandle表示的access token权限的提升了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 BOOL SetPrivilege (LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { _tprintf(L"OpenProcessToken error: %u\n" , GetLastError()); return FALSE; } if (!LookupPrivilegeValue(NULL , lpszPrivilege, &luid)) { _tprintf(L"LookupPrivilegeValue error: %u\n" , GetLastError()); return FALSE; } tp.PrivilegeCount = 1 ; tp.Privileges[0 ].Luid = luid; if (bEnablePrivilege) tp.Privileges[0 ].Attributes = SE_PRIVILEGE_ENABLED; else tp.Privileges[0 ].Attributes = 0 ; if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof (TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL , (PDWORD)NULL )) { _tprintf(L"AdjustTokenPrivileges error: %u\n" , GetLastError()); return FALSE; } if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { _tprintf(L"The token does not have the specified privilege. \n" ); return FALSE; } return TRUE; }
0x01-注册表注入
原理
Windows操作系统的注册表默认提供了AppInit_DLLs与LoadAppInit_DLLs两个注册表项
在注册表编辑器中,将要注入的DLL路径字符串写入AppInit_DLLs项目,然后把LoadAppInit_DLLs的项目值设置为1.重启后,指定DLL会注入所有进程。
0x02-SetWindowsHookEx()
在另一篇文章Hook写了,就不过多介绍
下面是更详细的解释
https://cloud.tencent.com/developer/article/1199648?msclkid=f79c9cc7b96011ecbdb2f47296f3b9d3
注入DLL的第三个方法就是消息钩取
0x03-还有其他方法
https://bbs.pediy.com/thread-253918.htm