首页
社区
课程
招聘
[原创]DLL卸载(创建远程线程卸载强制注入的dll)
2021-4-13 14:48 7352

[原创]DLL卸载(创建远程线程卸载强制注入的dll)

2021-4-13 14:48
7352

DLL卸载

更为准确的说法应该叫:创建远程线程卸载强制注入的dll

一:工作原理

驱使目标进程调用FreeLibrary() API,和CreateRemoteThread() API来创建远程线程从而实现dll注入的方法类似,这个也是将FreeLibrary() API的地址传递给CreateRemoteThread()的lpStartAddress参数,并将要卸载的DLL的句柄传递给lpParameter参数。

 

注:每个windows内核对象都拥有一个引用计数,代表对象被使用的次数。每调用一个LoadLibrary(),引用计数就会加一,每调用依次FreeLibrary(),引用计数就会减一。(卸载dll的时候要充分考虑这个因素)。

二:示例代码用到的函数或结构体等

1. INVALID_HANDLE_VALUE

代码中出现的是,HANDLE hsnapshot = INVALID_HANDLE_VALUE

 

就是将系统快照的句柄初始化为INVALID_HANDLE_VALUE

 

如果CreateToolhelp32Snapshot获取快照失败,便会返回这个INVALID_HANDLE_VALUE

 

这个跟指针初始化为NULL同理。

2.PROCESSENTRY32结构体

用来存放快照进程信息的一个结构体。(存放进程信息和调用成员输出进程信息)用来Process32First指向第一个进程信息,并将进程信息抽取到PROCESSENTRY32中。用Process32Next指向下一条进程信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct tagPROCESSENTRY32
{
    DWORD dwSize;
    DWORD cntUsage;
    DWORD th32ProcessID;
    ULONG_PTR th32DefaultHeapID;
    DWORD th32ModuleID;
    DWORD cntThreads;
    DWORD th32ParentProcessID;
    LONG pcPriClassBase;
    DWORD dwFlags;
    TCHAR szExeFile[MAX_PATH];
} PROCESSENTRY32, *PPROCESSENTRY32;

dwSize(结构的大小)The size of the structure, in bytes. Before calling the Process32Firstfunction, set this member to sizeof(PROCESSENTRY32). If you do not initialize dwSize, Process32Firstfails.

 

(这个结构的长度,以字节为单位,初始化一个实例以后调用Process32First函数,设置成员的大小sizeof (PROCESSENTRY32).如果你没用PROCESSENTRY32中的成员dwSize初始化,process32First将会失败。)

 

cntUsage(此进程的引用计数)This member is no longer used and is always set to zero.(这个成员已经不再被使用,总是设置为零。)

 

th32ProcessID进程ID=process identifier=PIDThe process identifier.

 

(这个就是任务管理器里面的进程的PID,打开任务管理器--查看---选择列---PID(勾选)就可以显示进程的标示符(PID))

 

th32DefaultHeapID进程默认堆IDThis member is no longer used and is always set to zero.

 

(这个成员已经不再被使用,总是设置为零。)

 

th32ModuleID进程模块IDThis member is no longer used and is always set to zero.

 

(这个成员已经不再被使用,总是设置为零。)

 

cntThreads此进程开启的线程计数The number of execution threads started by the process.

 

(这个成员执行线程开始的进程。)

 

th32ParentProcessID父进程的ID)The identifier of the process that created this process (its parent process).

 

pcPriClassBase.(线程优先权)The base priority of any threads created by this process

 

当前进程创建的任何一个线程的基础优先级,即在当前进程内创建线程的话,其基本优先级的值。

 

dwFlagsThis member is no longer used, and is always set to zero.

 

(这个成员已经不再被使用,总是设置为零。)

 

szExeFile(一个数组)进程全名The name of the executable file for the process. To retrieve the full path to the executable file, call the Module32Firstfunction and check the szExePathmember of the MODULEENTRY32structure that is returned. However, if the calling process is a 32-bit process, you must call the QueryFullProcessImageNamefunction to retrieve the full path of the executable file for a 64-bit process.

 

(进程的可执行文件名称。要获得可执行文件的完整路径,应调用Module32First函数,再检查其返回的MODULEENTRY32结构的szExePath成员。但是,如果被调用进程是一个64位程序,您必须调用QueryFullProcessImageName函数去获取64位进程的可执行文件完整路径名。)

3. CreateToolhelp32Snapshot

CreateToolhelp32Snapshot可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。(理解: 为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程照了一个当前系统的进程快照)

 

说到底,可以获取系统中正在运行的进程信息,线程信息,等。

 

函数原型:

 

HANDLE WINAPI CreateToolhelp32Snapshot(

 

DWORD dwFlags, //用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等

 

DWORD th32ProcessID //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取 当前进程快照时可以设为0

 

);

 

参数:

 

dwFlags:

 

指定快照中包含的系统内容,这个参数能够使用下列数值(常量)中的一个或多个。

 

TH32CS_INHERIT(0x80000000) - 声明快照句柄是可继承的。

 

TH32CS_SNAPALL - 在快照中包含系统中所有的进程和线程

 

TH32CS_SNAPHEAPLIST(0x00000001) - 在快照中包含在th32ProcessID中指定的进程的所有的堆。

 

TH32CS_SNAPMODULE(0x00000008) - 在快照中包含在th32ProcessID中指定的进程的所有的模块。

 

TH32CS_SNAPPROCESS(0x00000002) - 在快照中包含系统中所有的进程。

 

TH32CS_SNAPTHREAD(0x00000004) - 在快照中包含系统中所有的线程

 

H32CS_SNAPALL = (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE)

 

th32ProcessID:

 

指定将要快照的进程ID。如果该参数为0表示快照当前进程。该参数只有在设置了TH32CS_SNAPHEAPLIST或者TH32CS_SNAPMODULE后才有效,在其他情况下该参数被忽略,所有的进程都会被快照。

 

返回值:

 

调用成功,返回快照的句柄,调用失败,返回INVALID_HANDLE_VALUE 。

4. Process32First

process32First是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后,我们可以利用process32First函数来获得第一个进程的句柄。

1
2
3
4
BOOL WINAPI Process32First(
    HANDLE hSnapshot,//_in
    LPPROCESSENTRY32 lppe//_out
);

5. Process32Next

Process32Next是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后,我们可以利用Process32Next函数来获得下一个进程的句柄。

1
2
3
4
BOOLWINAPIProcess32Next(
__inHANDLEhSnapshot,
__outLPPROCESSENTRY32lppe
);

参数:

 

hSnapshot

 

从CreateToolhelp32Snapshot 返回的句柄。

 

lppe

 

指向PROCESSENTRY32结构的指针。

6. TOKEN_PRIVILEGES结构体

一个LUID_AND_ATTRIBUTES结构体. 每个结构体包括LUID和特权的属性。

1
2
3
4
5
typedef struct _TOKEN_PRIVILEGES
{
ULONG PrivilegeCount; //数组元素的个数
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; //数组.类型为LUID_AND_ATTRIBUTES
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

PrivilegeCount

 

指定特权数组的个数(因为下一个参数是一个数组)

 

Privileges

 

一个LUID_AND_ATTRIBUTES结构体. 每个结构体包括LUID和特权的属性(.Attributes = ...). 特权的属性可以是下列值的组合:

 

table

7. OpenProcessToken

OpenProcessToken,函数用来打开与进程相关联的访问令牌。

1
2
3
4
5
BOOL OpenProcessToken(
__in HANDLE ProcessHandle, //要修改访问权限的进程句柄
__in DWORD DesiredAccess, //指定你要进行的操作类型
__out PHANDLE TokenHandle //返回的访问令牌指针
);

第二个参数指定你要进行的操作类型,如要修改访问令牌的特权,我们要指定第二个参数为TOKEN_ADJUST_PRIVILEGES = &H20(其它一些参数可参考Platform SDK)。

8. GetCurrentProcess

获取当前进程的一个伪句柄.

 

GetCurrentProcess(

 

VOID

 

);

9. LookupPrivilegeValue

函数查看系统权限的特权值,返回信息到一个LUID结构体里

1
2
3
4
BOOL LookupPrivilegeValue(
    LPCTSTR lpSystemName,
    LPCTSTR lpName,
    PLUID lpLuid);

第一个参数表示所要查看的系统,本地系统直接用NULL

 

第二个参数指向一个以零结尾的字符串,指定特权的名称,如在WinNT h头文件定义。例如,此参数可指定常数,se_security_name,或其对应的字符串,“sesecurityprivilege "。

 

第三个参数用来接收所返回的制定特权名称的信息。

 

函数调用成功后,信息存入第三个类型为LUID的结构体中,并且函数返回非0。

 

函数定义在winbase.h中,链接使用advapi32.lib库。

10. AdjustTokenPrivileges

AdjustTokenPrivileges是一种函数,用于启用或禁止,指定访问令牌的特权。

 

启用或禁用特权一个有TOKEN_ADJUST_PRIVILEGES访问的访问令牌.

 

BOOL AdjustTokenPrivileges(

 

HANDLE TokenHandle**,** //包含特权的句柄

 

BOOL DisableAllPrivileges**,**//禁用所有权限标志

 

PTOKEN_PRIVILEGES NewState**,**//新特权信息的指针(结构体)

 

DWORD BufferLength**,** //缓冲数据大小,以字节为单位的PreviousState的缓存区(sizeof)

 

PTOKEN_PRIVILEGES PreviousState**,**//接收被改变特权当前状态的Buffer

 

PDWORD ReturnLength //接收PreviousState缓存区要求的大小

 

);

 

参数:

 

TokenHandle

 

包含要修改特权的访问令牌的标识(句柄).这个句柄必须有TOKEN_ADJUST_PRIVILEGES访问令牌.如果PreviousState不是NULL,这个句柄还必须有TOKEN_QUERY访问特权.

 

DisableAllPrivileges

 

标志这个函数是否禁用该令牌的所有特权.如果为TRUE,这个函数禁用所有特权,NewState参数无效.如果为假,以NewState参数指针的信息为基础来修改特权.

 

NewState

 

一个TOKEN_PRIVILEGES结构体的指针指定了一组特权和他们的属性.

 

如果参数DisableAllPrivileges为FALSE,AdjustTokenPrivileges 启用或禁用这些令牌的特权.

 

如果你给一个特权设置了SE_PRIVILEGE_ENABLED的属性,这个函数将启动特权,否则禁用特权.

 

如果DisableAllPrivileges为TRUE,这个参数无效.

 

BufferLength

 

标志参数PreviousState指针以字节大小缓存区(sizeof).

 

如果参数PreviousState是NULL,这个参数可以为NULL.

 

PreviousState

 

这个函数填充一个TOKEN_PRIVILEGES结构体指针】,它包括该函数修改之前任何特权状态.这个参数可以为NULL.

 

如果指定的缓冲区太小,无法收到完整的修改权限列表,这个函数失败并不会修改任何特权.

 

这个函数设置了一个 拥有修改权限完成列表【 参数ReturnLength 】的字节数 的指针变量.[结果的Buffer]

 

ReturnLength

 

接收 参数PreviousState的缓存区指针的 字节大小 的 变量指针(长度指针).

 

如果PreviousState为NULL,这个参数可以为NULL.

返回值:

如果这个函数成功,返回非0.为了确定这个函数是否修改了所有指定的特权,可以调用GetLastError函数,当这个函数返回下面的值之一时就代表函数成功:

 

table

 

如果这个函数失败,返回0.要得到更多的错误信息,调用GetLastError.

 

备注

 

AdjustTokenPrivileges函数不能添加新的特权到访问令牌.它只能启用或禁用令牌现行的令牌.要想确定这个令牌的特权,调用GetTokenInformation函数.

 

请注意,参数NewState可以不给令牌指定权限,这不会导致函数失败.

 

在这种情况下,这个函数修改令牌现有的特权,其他特权无效,并成功返回.

 

调用GetLastError函数,以确定这个函数修改了所有指定的特权.

 

PreviousState参数表明特权被修改.

 

参数PreviousState 返回一个 包含 修改权限 原始状态的结构体 TOKEN_PRIVILEGES,

 

这样就可以在随后调用AdjustTokenPrivileges函数时,传递PreviousState指针到 参数NewState ,来恢复原来的状态.

11. MODULEENTRY32

MODULEENTRY32是一种编程数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tagMODULEENTRY32 {
DWORD dwSize;
DWORD th32ModuleID;
DWORD th32ProcessID;
DWORD GlblcntUsage;
DWORD ProccntUsage;
BYTE *modBaseAddr;
DWORD modBaseSize;
HMODULE hModule;
TCHAR szModule[MAX_PATH];
TCHAR szExePath[MAX_PATH];
} MODULEENTRY32, *PMODULEENTRY32, *LPMODULEENTRY32;

dwSize

 

指定结构的长度,以字节为单位。在调用Module32First功能,设置这个成员SIZEOF(MODULEENTRY32)。如果你不初始化的dwSize,Module32First将失败。

 

th32ModuleID

 

此成员已经不再被使用,通常被设置为1

 

th32ProcessID

 

正在检查的进程标识符。这个成员的内容,可以使用Win32 API的元素

 

GlblcntUsage

 

全局模块的使用计数,即模块的总载入次数。通常这一项是没有意义的,被设置为0xFFFF。

 

ProccntUsage

 

全局模块的使用计数(与GlblcntUsage相同)。通常这一项也是没有意义的,被设置为0xFFFF。

 

modBaseAddr

 

模块的基址,在其所属的进程范围内。

 

modBaseSize

 

模块的大小,单位字节。

 

hModule

 

所属进程的范围内,模块句柄。

 

szModule

 

NULL结尾的字符串,其中包含模块名。

 

szExePath

 

NULL结尾的字符串,其中包含的位置,或模块的路径。

三:Eject.exe的源码

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
 
#define DEF_PROC_NAME (L"notepad.exe") //定义要卸载对应dll的进程名
#define DEF_DLL_NAME (L"myhack.dll")      //定义要卸载的dll名
 
DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;                                //初始化PID为0xFFFFFFFF
    HANDLE hSnapShot = INVALID_HANDLE_VALUE; //初始化快照句柄为INVALID_HANDLE_VALUE
    PROCESSENTRY32 pe;    //定义一个存放 快照进程信息 的一个结构体
    //1.获取当前系统进程快照
    pe.dwSize = sizeof(PROCESSENTRY32);
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
    //2.查找进程
    Process32First(hSnapShot, &pe);
    do
    {
        if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))     //比较进程名
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    }while (Process32Next(hSnapShot, &pe));     //循环查找
    //关闭句柄并返回
    CloseHandle(hSnapShot);
    return dwPID;
}
 
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
    TOKEN_PRIVILEGES tp;      //用来储存特权值信息的结构体
    HANDLE hToken;                  //Token的句柄
    LUID luid;
 
    //1.打开一个与进程相关联的访问token
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        _tprintf(L"OpenProcessToken failed: %u\n", GetLastError());
        return FALSE;
    }
 
    //2.查找系统权限值并储存到luid中
    if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid))
    {
 
        _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError());
        return FALSE;
    }
 
    //3.将这些都存入到TOKEN_PRIVILEGES结构体中
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (bEnablePrivilege)  //根据判断本函数的第二个参数设置属性
    {
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    }
    else
    {
        tp.Privileges[0].Attributes = 0;
    }
 
    //4.提权
    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 dose not have the specified privilege.\n");
        return FALSE;
    }
    return TRUE;
}
 
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = { sizeof(me) }; //定义一个用于储存模块快照的结构体
    LPTHREAD_START_ROUTINE pThreadProc;
 
    //1.
    //dwPID=notepad进程ID
    //使用TH32CS_SNAPMODULE参数
    //获取加载到notepad进程的DLL名称
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
 
    //此函数检索与进程相关联的第一个模块的信息
    bMore = Module32First(hSnapshot, &me);
 
    for (; bMore; bMore = Module32Next(hSnapshot, &me))   //bMore用于判断该进程的模块快照是否还有,bFound用于判断是否找到了我们想要卸载的dll模块
    {
        if (!_tcsicmp((LPCTSTR)me.szModule, szDllName) || !_tcsicmp((LPCTSTR)me.szExePath, szDllName))
        {
            bFound = TRUE;
            break;
        }
    }
 
    if (!bFound)
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }
    //2. 通过进程PID获取进程句柄
    if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
    {
        _tprintf(L"OpenProcess(%d) failed!!![%d]\n", dwPID, GetLastError);
        return FALSE;
    }
    //3. 获取FreeLibrary函数的地址
    hModule = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    //4.创建线程来执行FreeLibrary(modBaseAddr要卸载的dll模块基址)
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
 
    WaitForSingleObject(hThread, INFINITE);
 
    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);
 
    return TRUE;
}
 
int _tmain(int argc, TCHAR* argv[])
{
    /*步骤一:查找对应进程的PID*/
    DWORD dwPID = 0xFFFFFFFF;    //进程的pid是DWORD格式的,先初始化为0xFFFFFFFF
    dwPID = FindProcessID(DEF_PROC_NAME);
    if (dwPID == 0xFFFFFFFF)
    {
        _tprintf(L"There is no %s process\n", DEF_PROC_NAME);
        return 1;
    }
    _tprintf(L"PID of \%s\ is %d\n",DEF_PROC_NAME, dwPID);
 
    /*步骤二:更改权限*/
    if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
        return 1;
 
    /*步骤三:调用函数卸载dll模块*/
    if (EjectDll(dwPID, DEF_DLL_NAME))
        _tprintf(L"EjectDll(%d,\"%s\") success!!!\n", dwPID, DEF_DLL_NAME);
    else
        _tprintf(L"EjectDll(%d,\"%s\") failed!!!\n", dwPID, DEF_DLL_NAME);
    return 0;
}

代码逻辑清理:先根据进程名查找进程的PID(获取进程快照, 查找进程,设置进程PID并返回该PID),然后更改权限(打开一个进程相关联的Token,获取我们想要的系统权限值,存放到结构体中并根据传入的第二个参数决定设置该系统权限值,最后启用该Token的特权),最后卸载dll(创建该进程的模块快照,查找要卸载的dll模块,根据PID获取进程句柄,根据kernel32.dll获取FreeLibrary函数的地址,创建线程执行FreeLibrary卸载dll)

 

备注:

 

使用FreeLibrary()的方法仅使用于卸载自己强制注入的DLL文件。PE文件直接导入的DLL文件是无法在进程运行过程中卸载的。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-4-13 14:49 被SYJ-Re编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回