首页
社区
课程
招聘
[原创]DLL注入之创建远程线程
2021-4-7 21:22 9206

[原创]DLL注入之创建远程线程

2021-4-7 21:22
9206

DLL注入之创建远程线程

一:概念

1.dll注入是指向运行中的其它进程强制插入特定的dll文件。从技术细节来说,DLL注入命令其他进程自行调用LoadLibrary() API,加载用户指定的dll文件。

 

2.当dll被加载到进程中以后,就拥有了访问进程内存的权限。(用户可以通过这个来修复程序bug或增加功能等)。

 

3.dll被加载到进程后会自动运行DllMain函数。

 

4. 使用LoadLibrary() API加载某个DLL时,该DLL中的DllMain函数就会被自动执行。DLL注入的工作原理就是从外部促使目标进程调用LoadLibrary() API,所以会强制掉用执行DLL的DllMain()函数。

二:DLL注入的方法

(一):创建远程线程(CreateRemoteThread)

本示例把myhack.dll注入Notepad.exe进程,被注入的myhack.dll是用来下载http://www.syjblog.com文件的

1. hackme.dll源码使用到的函数解析

在看源码时对照此处进行理解

(1)#pragma comment( comment-type [, commentstring] )

将描述记录安排到目标文件或可执行文件中去。comment-type是下面说明的五个预定义标识符中的一个,用来指定描述记录的类型。可选的commentstring是一个字符串文字值用于为一些描述类型提供附加的信息。因为commentstring是一个字符串文字值,所以它遵从字符串文字值的所有规则,例如换码字符、嵌入的引号(")和联接。

 

当comment-type是lib的时候

 

将一个库搜索记录放置到目标文件中去。该描述类型必须有包含你要连接程序搜索的库名(和可能的路径)的commentstring参数。因为在目标文件中该库名先于默认的库搜索记录,所以连接程序将如同你在命令行输入这些库一样来搜索它们。你可以在一个源文件中放置多个库搜索记录,每个记录将按照它们出现在源文件中的顺序出现在目标文件中。

 

换句话说,就是将静态库导入到你的项目中去。

(2)ThreadProc

定义:ThreadProc是一个应用程序定义的函数的名称的占位符。作为一个线程的起始地址。在调用CreateThread函数时,指定该地址。

 

原型:DWORD WINAPI ThreadProc(LPVOID lpParameter);

 

参数:lpParameter

 

接收线程传递给函数使用的CreateThread函数lpParameter参数数据。

 

返回值:函数应该返回一个值,表示线程函数返回退出码,一般使用0作为返回值。

 

线程创建成功,返回非零值,否则为0。(一个进程可以通过调用GetExitCodeThread函数获取由CreateThread创建的线程的ThreadProc函数的返回值)

(3)GetModuleFileName

获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载。

 

如果想要获取另一个已加载模块的文件路径,可以使用GetModuleFileNameEx函数。

 

hModule Long: 一个模块的句柄。可以是一个DLL模块,或者是一个应用程序的实例句柄。如果该参数为NULL,该函数返回该应用程序全路径。

1
2
3
4
5
6
7
8
9
GetModuleFileName(
 
HMODULE hModule,
 
LPTSTR lpFilename,
 
DWORD nSize
 
);

lpFileName String: 指定一个字串缓冲区,要在其中容纳文件的用NULL字符中止的路径名,hModule模块就是从这个文件装载进来的

 

nSize Long: 装载到缓冲区lpFileName的最大字符数

 

返回值:如果返回为成功,将在lpFileName的缓冲区当中返回相应模块的路径,如果所设的nSize过小,那么返回仅按所设置缓冲区大小返回相应字符串内容。如果函数失败,返回值将为0,利用GetLastError可获得异常代码。

(4) _tcsrchr

查找字符串中某个字符最后一次出现的位置

 

两个参数

 

第一个参数:字符串

 

第二个参数:查找的字符

 

返回值:指向最后一次在字符串中出现的该字符的指针,如果要查找的字符再串中没有出现,则返回NULL。

(5)_tcscpy_s

字符拷贝函数,使用的如果是UNICODE编码,则采用wcscpy_s()函数,如果是多字节编码,则采用strcpy_s()函数。

 

功能:字符串拷贝

 

后缀_s表示使用安全的字符串拷贝函数,防止缓冲区不够大而引起错误。

(6)URLDownloadToFile

URLDownloadToFile,指从指定URL地址读取内容并将读取到的内容保存到特定的文件里的实现方法。

 

函数原型:

1
2
3
4
5
6
7
HRESULT URLDownloadToFile(
    LPUNKNOWN pCaller,
    LPCTSTR szURL,
    LPCTSTR szFileName,
    DWORD dwReserved,
    LPBINDSTATUSCALLBACK lpfnCB
);

参数:

 

pCallerPointer to the controlling IUnknown interface of the calling Microsoft ActiveX component (if the caller is an ActiveX component). //控件的接口,如果不是控件则为0.

 

szURL

 

Pointer to a string value containing the URL to be downloaded. Cannot be set to NULL

 

//要下载的url地址,不能为空.

 

szFileName

 

Pointer to a string value containing the name of the file to create for bits that come from the download.

 

//下载后保存的文件名.

 

dwReserved

 

Reserved. Must be set to 0.

 

//保留字段,必需为0

 

lpfnCB

 

Pointer to the caller's IBindStatusCallback interface. URLDownloadToFile calls this interface's IBindStatusCallback::OnProgress method on a connection activity, including the arrival of data. IBindStatusCallback::OnDataAvailable is never called.

 

//下载进度状态回调

 

返回值:

 

Returns one of the following values.

 

S_OK : The download started successfully.

 

E_OUTOFMEMORY: The buffer length is invalid, or there is insufficient memory to complete the operation.

 

INET_E_DOWNLOAD_FAILURE:The specified resource or callback interface was invalid.

(7)DLLMain:

函数原型:

1
2
3
4
5
BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // 指向自身的句柄
DWORD fdwReason, // 调用原因
LPVOID lpvReserved // 隐式加载和显式加载
);

静态链接时,或动态链接时调用LoadLibraryFreeLibrary都会调用DllMain函数。DllMain的第二个参数fdwReason指明了系统调用Dll的原因,它可能是:

 

DLL_PROCESS_ATTACH、

 

DLL_PROCESS_DETACH、

 

DLL_THREAD_ATTACH、

 

DLL_THREAD_DETACH。

 

进程映射

 

DLL_PROCESS_ATTACH

 

大家都知道,一个程序要调用Dll里的函数,首先要先把DLL文件映射到进程的地址空间。要把一个DLL文件映射到进程的地址空间,有两种方法:静态链接动态链接的LoadLibrary或者LoadLibraryEx。

 

当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH,这种调用只会发生在第一次映射时。如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或者LoadLibraryEx,操作系统只会增加DLL的使用次数,它不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。不同进程用LoadLibrary同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

 

可参考DllMainTest的DLL_PROCESS_ATTACH_Test函数。

 

进程卸载

 

DLL_PROCESS_DETACH

 

当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的fdwReason值是DLL_PROCESS_DETACH。当DLL处理该值时,它应该执行进程相关的清理工作。

 

那么什么时候DLL被从进程的地址空间解除映射呢?两种情况:

 

◆FreeLibrary解除DLL映射(有几个LoadLibrary,就要有几个FreeLibrary)

 

◆进程结束而解除DLL映射,在进程结束前还没有解除DLL的映射,进程结束后会解除DLL映射。(如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。)

 

注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明没有初始化成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain函数。因此,必须确保清理那些没有成功初始化的东西。

 

可参考DllMainTest的DLL_PROCESS_DETACH_Test函数。

 

线程映射

 

DLL_THREAD_ATTACH

 

当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。

 

新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许进程开始执行它的线程函数。

 

注意跟DLL_PROCESS_ATTACH的区别,我们在前面说过,第n(n>=2)次以后地把DLL映像文件映射到进程的地址空间时,是不再用DLL_PROCESS_ATTACH调用DllMain的。而DLL_THREAD_ATTACH不同,进程中的每次建立线程,都会用值DLL_THREAD_ATTACH调用DllMain函数,哪怕是线程中建立线程也一样。

 

线程卸载

 

DLL_THREAD_DETACH

 

如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。

 

注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。

(8)OutputDebugString

Win32 开发人员可能对 OutputDebugString()API 函数比较熟悉,它可以使你的程序和调试器进行交谈。它要比创建日志文件容易,而且所有“真正的”调试器都能使用它。应用程序和调试器交谈的机制相当简单,而本文将揭示整件事情是如何工作的。

 

<windows.h> 文件声明了 OutputDebugString() 函数的两个版本 - 一个用于 ASCII,一个用于 Unicode - 不像绝大多数 Win32 API 一样,原始版本是 ASCII。而大多数的 Win32 API 的原始版本是 Unicode。使用一个 NULL 结尾的字符串缓冲区简单调用 OutputDebugString() 将导致信息出现在调试器中,如果有调试器的话。

(9)CreateThread

CreateThread是一种微软在Windows API中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。

 

函数原型:

1
2
3
4
5
6
7
8
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)

参数:

 

lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE

 

dwStackSize,设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。

 

lpStartAddress,指向线程函数的指针,形式:函数名,函数名称没有限制,

 

线程有两种声明方式

 

(1)DWORD WINAPI 函数名 (LPVOID lpParam); //标准格式

1
2
3
4
5
DWORD WINAPI 函数名 (LPVOID lpParam)
{
    return 0;
}
CreateThread(NULL, 0, 函数名, 0, 0, 0);

(2)void 函数名();

 

使用void 函数名()此种线程声明方式时,lpStartAddress需要加入LPTHREAD_START_ROUTINE转换,如

1
2
3
4
5
void 函数名()
{
    return;
}
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)函数名, 0, 0, 0);

lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。

 

dwCreationFlags :线程标志,可取值如下

 

(1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程,

 

(2)0:表示创建后立即激活。

 

(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈 的大小,否则,dwStackSize指定提交的大小。该标记值在Windows 2000/NT and Windows Me/98/95上不支持。

 

lpThreadId:保存新线程的id。

 

返回值:函数成功,返回线程句柄;函数失败返回false。若不想返回线程ID,设置值为NULL。

2. hackme.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <windows.h>
#include <tchar.h>    //导入宽字节头文件
 
#pragma comment(lib, "urlmon.lib")   //将静态库链接到项目中
 
#define DEF_URL (L"http://www.syjblog.com/")  //define我们要保存的网页
#define DEF_FILE_NAME (L"savedwebsite.html") //define我们保存的网页的文件名
 
HMODULE g_hMod = NULL;
 
DWORD WINAPI ThreadProc(LPVOID lParam) //LPVOID是一个没有类型的指针,可以将LPVOID类型的变量赋值给任意类型的指针
{
    TCHAR szPath[_MAX_PATH] = { 0, };    //定义一个数组来储存路径
    if (!GetModuleFileName(g_hMod, szPath, MAX_PATH))
    {
        return FALSE;
    }
    TCHAR *p = _tcsrchr(szPath, '\\');      //在szPath储存的路径中从右到左寻找字符\\并返回字符串指针
    if (!p)
    {
        return FALSE;
    }
    _tcscpy_s(p + 1, _MAX_PATH, DEF_FILE_NAME);  //生成文件保存的路径, 将savedwebsite.html保存在szPath路径最后的'\'字符之后
    URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL); //下载文件保存到szPath所描述的文件中
 
    return 0;
}
 
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{//hinstDLL: 指向自身的句柄
    HANDLE hTread = NULL;  //定义一个通用句柄
    g_hMod = (HMODULE)hinstDLL;     //将dll自身的句柄赋给g_hMod
    switch(fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        OutputDebugString(L"myhack.dll Injection!!!");     //调用OutputDebugString函数和调试器交流,并在调试器中输出字符串
        hTread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        CloseHandle(hTread);
        break;
    }
    return TRUE;
}

3. InjectDll.exe用到的函数以及指针解析

(1)LPTHREAD_START_ROUTINE

LPTHREAD_START_ROUTINE是一种函数,该函数指向一个函数,通知宿主某个线程已开始执行。

 

定义:typedef DWORD (__stdcall *LPTHREAD_START_ROUTINE) (LPVOID lpThreadParameter);

 

参数:lpThreadParameter:一个指向已经开始执行的代码的指针

 

备注:LPTHREAD_START_ROUTINE 指向的函数是回调函数(就是将函数指针当作参数传递,并在函数中使用该指针回调函数),并且必须由承载应用程序的编写器实现

(2)OpenProcess

OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。

 

函数原型

 

HANDLE OpenProcess(

 

DWORD dwDesiredAccess, //渴望得到的访问权限(标志)

 

BOOL bInheritHandle, // 是否继承句柄

 

DWORD dwProcessId// 进程标示符

 

);

 

参数:

 

dwDesiredAccess [1] :获取的权限,可分为以下几种

 

PROCESS_ALL_ACCESS:获取所有权限

 

PROCESS_CREATE_PROCESS:创建进程

 

PROCESS_CREATE_THREAD:创建线程

 

PROCESS_DUP_HANDLE:使用DuplicateHandle()函数复制一个新句柄

 

PROCESS_QUERY_INFORMATION:获取进程的令牌、退出码和优先级等信息

 

PROCESS_QUERY_LIMITED_INFORMATION:获取进程特定的某个信息

 

PROCESS_SET_INFORMATION:设置进程的某种信息

 

PROCESS_SET_QUOTA:使用SetProcessWorkingSetSize函数设置内存限制

 

PROCESS_SUSPEND_RESUME:暂停或者恢复一个进程

 

PROCESS_TERMINATE:使用Terminate函数终止进程

 

PROCESS_VM_OPERATION:在进程的地址空间执行操作

 

PROCESS_VM_READ:使用ReadProcessMemory函数在进程中读取内存

 

PROCESS_VM_WRITE:使用WriteProcessMemory函数在进程中写入内存

 

SYNCHRONIZE:使用wait函数等待进程终止

 

bInheritHandle:TRUE或者FALSE

 

dwProcessId:pid

 

返回值:

 

如成功,返回值为指定进程的句柄。

 

如失败,返回值为空,可调用GetLastError获得错误代码。

(3)VirtualAllocEx

VirtualAllocEx 函数的作用是在指定进程的虚拟空间保留或提交内存区域,除非指定MEM_RESET参数,否则将该内存区域置0。

 

函数原形:

 

LPVOID VirtualAllocEx(

 

HANDLE hProcess,

 

LPVOID lpAddress,

 

SIZE_T dwSize,

 

DWORD flAllocationType,

 

DWORD flProtect

 

);

 

hProcess:

 

申请内存所在的进程句柄。

 

lpAddress:

 

保留页面的内存地址;一般用NULL自动分配 。

 

dwSize:

 

欲分配的内存大小,字节单位;注意实际分配的内存大小是页内存大小的整数倍

 

flAllocationType

 

可取下列值:

 

MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储

 

MEM_PHYSICAL :分配物理内存(仅用于地址窗口扩展内存)

 

MEM_RESERVE:保留进程的虚拟地址空间,而不分配任何物理存储。保留页面可通过继续调用VirtualAlloc()而被占用

 

MEM_RESET :指明在内存中由参数lpAddress和dwSize指定的数据无效

 

MEM_TOP_DOWN:在尽可能高的地址上分配内存(Windows 98忽略此标志)

 

MEM_WRITE_WATCH:必须与MEM_RESERVE一起指定,使系统跟踪那些被写入分配区域的页面(仅针对Windows 98)

 

flProtect

 

可取下列值:

 

PAGE_READONLY: 该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访

 

PAGE_READWRITE 区域可被应用程序读写

 

PAGE_EXECUTE: 区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。

 

PAGE_EXECUTE_READ :区域包含可执行代码,应用程序可以读该区域。

 

PAGE_EXECUTE_READWRITE: 区域包含可执行代码,应用程序可以读写该区域。

 

PAGE_GUARD: 区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限

 

PAGE_NOACCESS: 任何访问该区域的操作将被拒绝

 

PAGE_NOCACHE: RAM中的页映射到该区域时将不会被微处理器缓存(cached)

 

注:PAGE_GUARD和PAGE_NOCHACHE标志可以和其他标志合并使用以进一步指定页的特征。PAGE_GUARD标志指定了一个防护页(guard page),即当一个页被提交时会因第一次被访问而产生一个one-shot异常,接着取得指定的访问权限。PAGE_NOCACHE防止当它映射到虚拟页的时候被微处理器缓存。这个标志方便设备驱动使用直接内存访问方式(DMA)来共享内存块。

 

返回值:

 

执行成功就返回分配内存的首地址,不成功就是NULL。

(4)WriteProcessMemory

此函数能写入某一进程的内存区域(直接写入会出Access Violation错误),故需此函数入口区必须可以访问,否则操作将失败。

 

函数声明:

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesWritten
);

参数:

 

[1]. hProcess

 

OpenProcess返回的进程句柄。

 

如参数传数据为 INVALID_HANDLE_VALUE 【即-1】目标进程为自身进程

 

[2]. lpBaseAddress

 

要写的内存首地址

 

在写入之前,此函数将先检查目标地址是否可用,并能容纳待写入的数据。

 

[3]. lpBuffer

 

指向要写的数据的指针

 

[4]. nSize

 

要写入的字节数。

 

返回值

 

非零值代表成功。

 

可用GetLastError获取更多的错误详细信息。

(5)CreateRemoteThread

创建一个在其它进程地址空间中运行的线程(也称:创建远程线程)。

 

函数原型:

1
2
3
4
5
6
7
8
9
HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess,
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId
);

参数说明

 

[1] hProcess

 

线程所属进程的进程句柄.

 

该句柄必须具有 PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE,和PROCESS_VM_READ 访问权限.

 

[2] lpThreadAttributes

 

一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结构指定了线程的安全属性.

 

[3] dwStackSize

 

线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小.

 

[4] lpStartAddress

 

在远程进程的地址空间中,该线程的线程函数的起始地址.

 

[5] lpParameter

 

传给线程函数的参数.

 

[6] dwCreationFlags

 

线程的创建标志.

 

Table

 

[7] lpThreadId

 

指向所创建线程ID的指针,如果创建失败,该参数为NULL.

 

函数返回值

 

如果调用成功,返回新线程句柄.

 

如果失败,返回NULL.

(6)WaitForSingleObject

WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
WaitForSingleObject函数用来检测hHandle事件的信号状态,当函数的执行时间超过dwMilliseconds就返回,但如果参数dwMilliseconds为INFINITE时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到WaitForSingleObject有返回值才执行后面的代码。

 

原型:

1
2
3
4
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);

参数:

 

hHandle[in]对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。

 

dwMilliseconds[in]定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。

 

返回值:

 

执行成功,返回值指示出引发函数返回的事件。它可能为以下值:

 

可能值

 

参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。

 

WAIT_ABANDONED 0x00000080:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。

 

WAIT_OBJECT_0 0x00000000 :指定的对象出有有信号状态

 

WAIT_TIMEOUT 0x00000102:等待超时

 

WAIT_FAILED 0xFFFFFFFF :出现错误,可通过GetLastError得到错误代码

4. InjectDll.exe源码分析

自己对源码的理解:其实InjectDll函数就分为了大体的几个部分,先在目标进程中分配一块内存来存储我们要注入的那个dll的路径(这部分就是WriteProcessMemory及其之前的代码的作用),过了就是调用LoadLibraryW函数,最后就是利用函数CreateRemoteThread在进程内创建一个线程来执行LoadLibraryW("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
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
65
66
67
68
#include "windows.h"
#include "tchar.h
 
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath) //L是长指针,P代表指针的意思,C代表const常量的意思,T代表通用类型的意思
{
    HANDLE hProcess = NULL, hThread = NULL;
    HMODULE hMod = NULL;
    LPVOID pRemoteBuf = NULL;     //void型的长指针
 
    //确定路径需要占用的缓冲区大小,  _tcslen测量字符串的长度,然后加上结尾的空字符,再乘sizeof(TCHAR)
    DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
    LPTHREAD_START_ROUTINE pThreadProc; //LPTHREAD_START_ROUTINE等价于typedef DWORD (__stdcall *LPTHREAD_START_ROUTINE) (LPVOID lpThreadParameter);
 
    //OpenProcess函数获取目标进程句柄(PROCESS_ALL_ACCESS权限)
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    {
        _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError()); //如果打开失败,使用GetLastError()来获取错误信息
        return FALSE;
    }
 
    //使用VirtualAllocEx函数在目标进程中分配内存,大小为Dll路径的大小
    //VirtualAllocEx函数返回的是hProcess指向的目标进程的分配所得缓冲区的内存的首地址
    pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); //pRemoteBuf就是分配的内存的首地址
 
    //将myhack.dll路径 ("c:\\myhack.dll")写入目标进程中分配到的内存
    WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
 
            //获取LoadLibraryA() API的地址
          // 这里主要利用来了kernel32.dll文件在每个进程中的加载地址都相同这一特点,所以不管是获取加载到       
          // InjectDll.exe还是notepad.exe进程的kernel32.dll中的LoadLibraryW函数的地址都是一样的。这里的加载地
          // 址相同指的是在同一次系统运行中,如果再次启动系统kernel32.dll的加载地址会变,但是每个进程的
          // kernerl32.dll的加载地址还是一样的。
          hMod = GetModuleHandle(L"kernel32.dll"); //直接获取kernel32.dll的句柄
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");//为之前定义的函数指针赋成kernel32.dll中LoadLibraryW函数的地址
 
          //在目标进程notepad.exe中运行远程线程
          // pThreadProc = notepad.exe进程内存中的LoadLibraryW()地址
          // pRemoteBuf = notepad.exe进程内存中待加载注入dll的路径字符串的地址
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); //hThread为新线程句柄
    WaitForSingleObject(hThread, INFINITE);       
 
          //同样,记得关闭句柄
    CloseHandle(hThread);
    CloseHandle(hProcess);
 
    return TRUE;
}
 
int _tmain(int argc, TCHAR *argv[])
{
    if( argc != 3)    //检测doc输入的参数个数是否是3
    {
        _tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]); //printf的通用类型
        return 1;
    }
 
    // change privilege
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )     //提升权限,以便kill进程等操作
        return 1;
 
    // inject dll
    if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) )  //argv[1]为进程的pid, argv[2]为dll的路径
        _tprintf(L"InjectDll(\"%s\") success!!!\n", argv[2]);
    else
        _tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);
 
    return 0;
}

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

最后于 2021-4-7 23:08 被SYJ-Re编辑 ,原因:
收藏
免费 3
打赏
分享
最新回复 (2)
雪    币: 4856
活跃值: (3070)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
SAO 2021-4-8 22:14
2
0
有没有试过用createremotethread直接注入一段代码,不用loadlibrary
雪    币: 3159
活跃值: (3853)
能力值: ( LV5,RANK:73 )
在线值:
发帖
回帖
粉丝
wx_御史神风 2021-4-8 23:12
3
0
SAO 有没有试过用createremotethread直接注入一段代码,不用loadlibrary

不调用loadlibrary是指类似反射注入那样吗

最后于 2021-4-8 23:13 被wx_御史神风编辑 ,原因:
游客
登录 | 注册 方可回帖
返回