May contain jump thunks to handle relocation of functions to new addresses.
void
shellcodeFunc(PMY_PARAMS pParams) {
}
DWORD
size = 0, ssss=0;
DWORD
* jmpAddr = (
DWORD
*) ((
BYTE
*) shellcodeFunc + 1);
WORD
* Memx0 = (
WORD
*) ((
BYTE
*) shellcodeFunc + 5 + *jmpAddr);
LONG_PTR
* Memx = (
LONG_PTR
*) Memx0;
while
(*Memx != 0xCCCCCCCCCCCCCCCC) {
Memx++;
size += 8;
}
HANDLE
hFile = CreateFile(LOADECODE, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL);
if
(hFile) {
WriteFile(hFile, Memx0, size, &ssss, NULL);
CloseHandle(hFile);
}
void
shellcodeFunc(PMY_PARAMS pParams) {
}
DWORD
size = 0, ssss=0;
DWORD
* jmpAddr = (
DWORD
*) ((
BYTE
*) shellcodeFunc + 1);
WORD
* Memx0 = (
WORD
*) ((
BYTE
*) shellcodeFunc + 5 + *jmpAddr);
LONG_PTR
* Memx = (
LONG_PTR
*) Memx0;
while
(*Memx != 0xCCCCCCCCCCCCCCCC) {
Memx++;
size += 8;
}
HANDLE
hFile = CreateFile(LOADECODE, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL);
if
(hFile) {
WriteFile(hFile, Memx0, size, &ssss, NULL);
CloseHandle(hFile);
}
May contain jump thunks to handle relocation of functions to new addresses.
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
|
void shellcodeFunc(PMY_PARAMS pParams) {
}
DWORD size = 0, ssss=0;
DWORD * jmpAddr = ( DWORD *) (( BYTE *) shellcodeFunc + 1);
WORD * Memx0 = ( WORD *) (( BYTE *) shellcodeFunc + 5 + *jmpAddr);
LONG_PTR * Memx = ( LONG_PTR *) Memx0;
while (*Memx != 0xCCCCCCCCCCCCCCCC) {
Memx++;
size += 8;
}
HANDLE hFile = CreateFile(LOADECODE, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL);
if (hFile) {
WriteFile(hFile, Memx0, size, &ssss, NULL);
CloseHandle(hFile);
}
|
- 使用
vs2019
编写注入器程序, 在生成的注入器可用前, 踩了不少坑, 因此记录一下.
- 本文涉及三种恶意代码注入方法: 直接dll注入, 反射式dll注入, 镂空注入. 之所以选这三种注入方法, 是因最近在做一个检测进程内存空间以期发现代码注入的程序, 而实验发现这三种方法对目标进程的改变各有特点:
- 直接dll注入: 还有APC注入, 本质都是在目标进程中执行
LoadLibrary
函数, 因而在枚举目标进程的模块列表时可看到注入的dll.
- 反射式dll注入: 这种方法也会在目标进程中开辟新的内存空间并写入代码. 但因为没有调用
LoadLibrary
, 所以**枚举目标进程的模块列表并不能看到注入的dll. (意味着目标进程的PEB没有变化). **
- 镂空注入: 直接改进程中某一模块的内存空间, 或者先注入一个合法模块, 再镂空该模块. **因为该方法没有注入恶意dll, 所以直接枚举目标进程的模块列表也无法发现恶意代码, 需扫描进程的内存空间. **
- 标了
注:
的地方基本都是踩的坑.
- 直接dll注入: 还有APC注入, 本质都是在目标进程中执行
LoadLibrary
函数, 因而在枚举目标进程的模块列表时可看到注入的dll.
- 反射式dll注入: 这种方法也会在目标进程中开辟新的内存空间并写入代码. 但因为没有调用
LoadLibrary
, 所以**枚举目标进程的模块列表并不能看到注入的dll. (意味着目标进程的PEB没有变化). **
- 镂空注入: 直接改进程中某一模块的内存空间, 或者先注入一个合法模块, 再镂空该模块. **因为该方法没有注入恶意dll, 所以直接枚举目标进程的模块列表也无法发现恶意代码, 需扫描进程的内存空间. **
- 示例: LibSpy项目的
OnMouseMove
和InjectDll
- 流程:
-
OpenProcess
打开目标进程(一参为PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE| PROCESS_VM_READ
, 后面CreateRemoteThread
才能执行)
- 需要给进程提权, 得到SeDebug权限.
- 注: 准确地说不是提权, 而是将访问令牌中禁用的权限启用. 成功调用下面几个函数的前提是进程具备该权限, 只是访问令牌中没有启用该权限. 而如果进程没有该权限, 则使用下面的函数后再调用
GetLastError
会返回ERROR_NOT_ALL_ASSIGN
.
- 先用
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid)
得到用户的debug权限.
- 然后用
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,&hToken)
获取进程的令牌句柄.
- 最后用
AdjustTokenPrivileges
启用特权.
- 若要打开关键进程(
csrss.exe
等), 需在驱动中打开, 去掉关键进程的EPROCESS
中的PsIsProtectProcess
标志位, 并关闭dll签名策略
. (参考开源项目blackbone
)
- 获取待注入的dll路径, 在目标进程内分配一块内存(
VirtualAllocateEx
), 将路径拷贝到该内存中(WriteProcessMemory
)
- 获取kernel32中的
LoadLibraryA
地址(如果dll路径是宽字符串, 则用LoadLibraryW
)
- 调用
CreateRemoteThread
, 在目标进程中执行LoadLibrary
, 进而执行DllMain
函数中的目标代码
-
OpenProcess
打开目标进程(一参为PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE| PROCESS_VM_READ
, 后面CreateRemoteThread
才能执行)
- 需要给进程提权, 得到SeDebug权限.
- 注: 准确地说不是提权, 而是将访问令牌中禁用的权限启用. 成功调用下面几个函数的前提是进程具备该权限, 只是访问令牌中没有启用该权限. 而如果进程没有该权限, 则使用下面的函数后再调用
GetLastError
会返回ERROR_NOT_ALL_ASSIGN
.
- 先用
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid)
得到用户的debug权限.
- 然后用
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,&hToken)
获取进程的令牌句柄.
- 最后用
AdjustTokenPrivileges
启用特权.
- 若要打开关键进程(
csrss.exe
等), 需在驱动中打开, 去掉关键进程的EPROCESS
中的PsIsProtectProcess
标志位, 并关闭dll签名策略
. (参考开源项目blackbone
)
- 获取待注入的dll路径, 在目标进程内分配一块内存(
VirtualAllocateEx
), 将路径拷贝到该内存中(WriteProcessMemory
)
- 获取kernel32中的
LoadLibraryA
地址(如果dll路径是宽字符串, 则用LoadLibraryW
)
- 调用
CreateRemoteThread
, 在目标进程中执行LoadLibrary
, 进而执行DllMain
函数中的目标代码
- 需要给进程提权, 得到SeDebug权限.
- 注: 准确地说不是提权, 而是将访问令牌中禁用的权限启用. 成功调用下面几个函数的前提是进程具备该权限, 只是访问令牌中没有启用该权限. 而如果进程没有该权限, 则使用下面的函数后再调用
GetLastError
会返回ERROR_NOT_ALL_ASSIGN
.
- 先用
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid)
得到用户的debug权限.
- 然后用
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,&hToken)
获取进程的令牌句柄.
- 最后用
AdjustTokenPrivileges
启用特权.
- 若要打开关键进程(
csrss.exe
等), 需在驱动中打开, 去掉关键进程的EPROCESS
中的PsIsProtectProcess
标志位, 并关闭dll签名策略
. (参考开源项目blackbone
)
- 概要
- 没有使用
LoadLibrary
函数注入dll.
- 需由注入器自行解析PE文件:
- 将dll头部(包括
DOS头
, PE头
, 区块表
)逐字节写入新开辟的内存.
- 按重定位表的信息手动重定位
- 修复导入函数表: 使用
LdrLoadDll
得到shellcode需要的库的内存地址, 用LdrGetProcedureAddress
得到要导入的函数的内存地址, 然后将这些地址填入导入表.
- 流程
- 将自己实现的LoadLibrary功能函数保存为shellcode.
- 注: 在shellcode中使用的系统api都要事先通过
GetProcAddress
获取(使用GetModuleHandleA
获取模块句柄, 传入的模块名不用后缀), 并作为参数传给shellcode.
- 注: 需要审查注入器保存的shellcode是否是真实的函数体. 调试发现在vs2019中, 按默认选项编译得到的函数地址处是一条跳转到实际函数体的jmp指令. 因此
需要使用jmp指令的操作数计算实际函数地址
. 如下为获取一个shellcode函数的代码:
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
|
void shellcodeFunc(PMY_PARAMS pParams) {
}
DWORD size = 0, ssss=0;
DWORD * jmpAddr = ( DWORD *) (( BYTE *) shellcodeFunc + 1);
WORD * Memx0 = ( WORD *) (( BYTE *) shellcodeFunc + 5 + *jmpAddr);
LONG_PTR * Memx = ( LONG_PTR *) Memx0;
while (*Memx != 0xCCCCCCCCCCCCCCCC) {
Memx++;
size += 8;
}
HANDLE hFile = CreateFile(LOADECODE, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL);
if (hFile) {
WriteFile(hFile, Memx0, size, &ssss, NULL);
CloseHandle(hFile);
}
|
- 20230810:发现可以通过修改编译和链接选项, 让编译的程序不会多一个跳转表.
- 编译时带上
/GL
选项(C/C++
-> 优化
-> 全程序优化
, 选择是 (/GL)
).
- 链接时带上
/INCREMENTAL:NO
选项(链接器
-> 常规
-> 启用增量链接
, 选否 (/INCREMENTAL:NO)
). 在微软对这个链接选项的说明文档中, 有如下一句:
May contain jump thunks to handle relocation of functions to new addresses.
也就是说/INCREMENTAL
选项会让程序多一张跳转表, 以处理函数的重定位. 在Debug模式下就会默认开启此选项.
- 注: 用windbg调试远程线程, 发现远程线程中
出现地址访问冲突
:
- 原因1: 出问题的地方试图读取
__security_cookie
- 原因2: 远程线程中试图调用一些不可用的函数, 包括
__CheckForDebuggerJustMyCode
, _RTC_CheckStackVars
- 解决: 禁用
/JMC
选项, /RTC
选项, 其他的如果是必要使用的动态库函数, 则需要用LoadLibrary
和GetProcAddress
获取, 且要确保目标进程已载入相应dll.
- 注: shellcode函数中不要用字符串常量, 因为这些字符串总是存在于注入器进程的堆栈上, 不会随着shellcode一起注入到目标进程中, 这样一来shellcode在运行时无从获取这些字符串常量. 在shellcode中最好是通过传参的方式获取需要用到的字符串常量.
- 在目标进程中开辟新内存, 依次写入:
- 要注入的dll的文件内容.
- shellcode.
- 传给shellcode的参数, 主要是shellcode需要的如下系统api的函数地址.
-
LdrLoadDll
: 获取注入的dll依赖的dll的内存地址.
-
LdrGetProcedureAddress
: 获取注入的dll需导入的函数的内存地址.
-
RtlInitAnsiString
RtlAnsiStringToUnicodeString
RtlFreeUnicodeString
: 用以配合上述两个函数, 得到导入函数的内存地址.
-
NtAllocateVirtualMemory
: 分配内存空间, 以写入要注入的dll的文件内容.
- 创建远程线程, 执行shellcode, 由shellcode载入dll.
- 优点
- 没有使用
LoadLibrary
函数注入dll.
- 需由注入器自行解析PE文件:
- 将dll头部(包括
DOS头
, PE头
, 区块表
)逐字节写入新开辟的内存.
- 按重定位表的信息手动重定位
- 修复导入函数表: 使用
LdrLoadDll
得到shellcode需要的库的内存地址, 用LdrGetProcedureAddress
得到要导入的函数的内存地址, 然后将这些地址填入导入表.
- 将dll头部(包括
DOS头
, PE头
, 区块表
)逐字节写入新开辟的内存.
- 按重定位表的信息手动重定位
- 修复导入函数表: 使用
LdrLoadDll
得到shellcode需要的库的内存地址, 用LdrGetProcedureAddress
得到要导入的函数的内存地址, 然后将这些地址填入导入表.
- 将自己实现的LoadLibrary功能函数保存为shellcode.
- 注: 在shellcode中使用的系统api都要事先通过
GetProcAddress
获取(使用GetModuleHandleA
获取模块句柄, 传入的模块名不用后缀), 并作为参数传给shellcode.
- 注: 需要审查注入器保存的shellcode是否是真实的函数体. 调试发现在vs2019中, 按默认选项编译得到的函数地址处是一条跳转到实际函数体的jmp指令. 因此
需要使用jmp指令的操作数计算实际函数地址
. 如下为获取一个shellcode函数的代码:
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
|
void shellcodeFunc(PMY_PARAMS pParams) {
}
DWORD size = 0, ssss=0;
DWORD * jmpAddr = ( DWORD *) (( BYTE *) shellcodeFunc + 1);
WORD * Memx0 = ( WORD *) (( BYTE *) shellcodeFunc + 5 + *jmpAddr);
LONG_PTR * Memx = ( LONG_PTR *) Memx0;
while (*Memx != 0xCCCCCCCCCCCCCCCC) {
Memx++;
size += 8;
}
HANDLE hFile = CreateFile(LOADECODE, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL);
if (hFile) {
WriteFile(hFile, Memx0, size, &ssss, NULL);
CloseHandle(hFile);
}
|
- 20230810:发现可以通过修改编译和链接选项, 让编译的程序不会多一个跳转表.
- 编译时带上
/GL
选项(C/C++
-> 优化
-> 全程序优化
, 选择是 (/GL)
).
- 链接时带上
/INCREMENTAL:NO
选项(链接器
-> 常规
-> 启用增量链接
, 选否 (/INCREMENTAL:NO)
). 在微软对这个链接选项的说明文档中, 有如下一句:
May contain jump thunks to handle relocation of functions to new addresses.
也就是说/INCREMENTAL
选项会让程序多一张跳转表, 以处理函数的重定位. 在Debug模式下就会默认开启此选项.
- 注: 用windbg调试远程线程, 发现远程线程中
出现地址访问冲突
:
- 原因1: 出问题的地方试图读取
__security_cookie
- 原因2: 远程线程中试图调用一些不可用的函数, 包括
__CheckForDebuggerJustMyCode
, _RTC_CheckStackVars
- 解决: 禁用
/JMC
选项, /RTC
选项, 其他的如果是必要使用的动态库函数, 则需要用LoadLibrary
和GetProcAddress
获取, 且要确保目标进程已载入相应dll.
- 注: shellcode函数中不要用字符串常量, 因为这些字符串总是存在于注入器进程的堆栈上, 不会随着shellcode一起注入到目标进程中, 这样一来shellcode在运行时无从获取这些字符串常量. 在shellcode中最好是通过传参的方式获取需要用到的字符串常量.
- 在目标进程中开辟新内存, 依次写入:
- 要注入的dll的文件内容.
- shellcode.
- 传给shellcode的参数, 主要是shellcode需要的如下系统api的函数地址.
-
LdrLoadDll
: 获取注入的dll依赖的dll的内存地址.
-
LdrGetProcedureAddress
: 获取注入的dll需导入的函数的内存地址.
-
RtlInitAnsiString
RtlAnsiStringToUnicodeString
RtlFreeUnicodeString
: 用以配合上述两个函数, 得到导入函数的内存地址.
-
NtAllocateVirtualMemory
: 分配内存空间, 以写入要注入的dll的文件内容.
- 创建远程线程, 执行shellcode, 由shellcode载入dll.
- 注: 在shellcode中使用的系统api都要事先通过
GetProcAddress
获取(使用GetModuleHandleA
获取模块句柄, 传入的模块名不用后缀), 并作为参数传给shellcode.
- 注: 需要审查注入器保存的shellcode是否是真实的函数体. 调试发现在vs2019中, 按默认选项编译得到的函数地址处是一条跳转到实际函数体的jmp指令. 因此
需要使用jmp指令的操作数计算实际函数地址
. 如下为获取一个shellcode函数的代码:
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
|
void shellcodeFunc(PMY_PARAMS pParams) {
}
DWORD size = 0, ssss=0;
DWORD * jmpAddr = ( DWORD *) (( BYTE *) shellcodeFunc + 1);
WORD * Memx0 = ( WORD *) (( BYTE *) shellcodeFunc + 5 + *jmpAddr);
LONG_PTR * Memx = ( LONG_PTR *) Memx0;
while (*Memx != 0xCCCCCCCCCCCCCCCC) {
Memx++;
size += 8;
}
HANDLE hFile = CreateFile(LOADECODE, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL);
if (hFile) {
WriteFile(hFile, Memx0, size, &ssss, NULL);
CloseHandle(hFile);
}
|
- 20230810:发现可以通过修改编译和链接选项, 让编译的程序不会多一个跳转表.
- 编译时带上
/GL
选项(C/C++
-> 优化
-> 全程序优化
, 选择是 (/GL)
).
- 链接时带上
/INCREMENTAL:NO
选项(链接器
-> 常规
-> 启用增量链接
, 选否 (/INCREMENTAL:NO)
). 在微软对这个链接选项的说明文档中, 有如下一句:
May contain jump thunks to handle relocation of functions to new addresses.
也就是说/INCREMENTAL
选项会让程序多一张跳转表, 以处理函数的重定位. 在Debug模式下就会默认开启此选项.
- 注: 用windbg调试远程线程, 发现远程线程中
出现地址访问冲突
:
- 原因1: 出问题的地方试图读取
__security_cookie
- 原因2: 远程线程中试图调用一些不可用的函数, 包括
__CheckForDebuggerJustMyCode
, _RTC_CheckStackVars
- 解决: 禁用
/JMC
选项, /RTC
选项, 其他的如果是必要使用的动态库函数, 则需要用LoadLibrary
和GetProcAddress
获取, 且要确保目标进程已载入相应dll.
- 注: shellcode函数中不要用字符串常量, 因为这些字符串总是存在于注入器进程的堆栈上, 不会随着shellcode一起注入到目标进程中, 这样一来shellcode在运行时无从获取这些字符串常量. 在shellcode中最好是通过传参的方式获取需要用到的字符串常量.
- 编译时带上
/GL
选项(C/C++
-> 优化
-> 全程序优化
, 选择是 (/GL)
).
- 链接时带上
/INCREMENTAL:NO
选项(链接器
-> 常规
-> 启用增量链接
, 选否 (/INCREMENTAL:NO)
). 在微软对这个链接选项的说明文档中, 有如下一句:
May contain jump thunks to handle relocation of functions to new addresses.
也就是说/INCREMENTAL
选项会让程序多一张跳转表, 以处理函数的重定位. 在Debug模式下就会默认开启此选项.
- 原因1: 出问题的地方试图读取
__security_cookie
- 原因2: 远程线程中试图调用一些不可用的函数, 包括
__CheckForDebuggerJustMyCode
, _RTC_CheckStackVars
- 解决: 禁用
/JMC
选项, /RTC
选项, 其他的如果是必要使用的动态库函数, 则需要用LoadLibrary
和GetProcAddress
获取, 且要确保目标进程已载入相应dll.
- 解决: 禁用
/JMC
选项, /RTC
选项, 其他的如果是必要使用的动态库函数, 则需要用LoadLibrary
和GetProcAddress
获取, 且要确保目标进程已载入相应dll.
- 要注入的dll的文件内容.
- shellcode.
- 传给shellcode的参数, 主要是shellcode需要的如下系统api的函数地址.
-
LdrLoadDll
: 获取注入的dll依赖的dll的内存地址.
-
LdrGetProcedureAddress
: 获取注入的dll需导入的函数的内存地址.
-
RtlInitAnsiString
RtlAnsiStringToUnicodeString
RtlFreeUnicodeString
: 用以配合上述两个函数, 得到导入函数的内存地址.
-
NtAllocateVirtualMemory
: 分配内存空间, 以写入要注入的dll的文件内容.
-
LdrLoadDll
: 获取注入的dll依赖的dll的内存地址.
-
LdrGetProcedureAddress
: 获取注入的dll需导入的函数的内存地址.
-
RtlInitAnsiString
RtlAnsiStringToUnicodeString
RtlFreeUnicodeString
: 用以配合上述两个函数, 得到导入函数的内存地址.
-
NtAllocateVirtualMemory
: 分配内存空间, 以写入要注入的dll的文件内容.
- 概要:
- 两种方式:
- 镂空已有进程模块: 直接修改进程中已有模块的代码节, 注入恶意代码.
- 先注入后镂空: 注入一个合法dll(拥有合法签名), 然后修改dll入口点处代码为自己想执行的代码.
- 下面只讲先注入dll后镂空的方法.
- 流程
- 首先, 如普通的dll注入,
CreateRemoteThread
创建远程线程, 执行LoadLibrary
注入一个dll, 不同的是注入到进程的是一个合法dll(比如system32目录下的dll).
-
EnumProcessModules
枚举进程模块, GetModuleBaseNameA
得到每个模块的名称, 从而找到注入的dll.
- 注入器进程中分配0x1000的内存空间(可用
malloc
或HeapAlloc
), 然后将找到的dll的PE头部内容读进来.
- 由PE头中的optional头得到目标dll的入口地址, 加上枚举模块时得到的dll的基址, 得到实际dll入口地址, 并用
WriteProcessMemory
向该地址写入shellcode.
- 注: 通过windbg调试发现, 注入入口地址不一定能成功执行shellcode, 因为DllMain函数可能多次执行. 如果只想执行一次shellcode,
可把shellcode及其写在dll的末尾对齐空间.
- 如同反射式dll注入, 生成shellcode的方法也是在注入器中定义shellcode函数并获取其机器码.
- 创建远程线程, 并以写入shellcode的地址为线程执行地址.
- 优点
- 两种方式:
- 镂空已有进程模块: 直接修改进程中已有模块的代码节, 注入恶意代码.
- 先注入后镂空: 注入一个合法dll(拥有合法签名), 然后修改dll入口点处代码为自己想执行的代码.
- 下面只讲先注入dll后镂空的方法.
- 镂空已有进程模块: 直接修改进程中已有模块的代码节, 注入恶意代码.
- 先注入后镂空: 注入一个合法dll(拥有合法签名), 然后修改dll入口点处代码为自己想执行的代码.
- 首先, 如普通的dll注入,
CreateRemoteThread
创建远程线程, 执行LoadLibrary
注入一个dll, 不同的是注入到进程的是一个合法dll(比如system32目录下的dll).
-
EnumProcessModules
枚举进程模块, GetModuleBaseNameA
得到每个模块的名称, 从而找到注入的dll.
- 注入器进程中分配0x1000的内存空间(可用
malloc
或HeapAlloc
), 然后将找到的dll的PE头部内容读进来.
- 由PE头中的optional头得到目标dll的入口地址, 加上枚举模块时得到的dll的基址, 得到实际dll入口地址, 并用
WriteProcessMemory
向该地址写入shellcode.
- 注: 通过windbg调试发现, 注入入口地址不一定能成功执行shellcode, 因为DllMain函数可能多次执行. 如果只想执行一次shellcode,
可把shellcode及其写在dll的末尾对齐空间.
- 如同反射式dll注入, 生成shellcode的方法也是在注入器中定义shellcode函数并获取其机器码.
- 创建远程线程, 并以写入shellcode的地址为线程执行地址.
- 注: 通过windbg调试发现, 注入入口地址不一定能成功执行shellcode, 因为DllMain函数可能多次执行. 如果只想执行一次shellcode,
可把shellcode及其写在dll的末尾对齐空间.
- 如同反射式dll注入, 生成shellcode的方法也是在注入器中定义shellcode函数并获取其机器码.
- 注: 准确地说不是提权, 而是将访问令牌中禁用的权限启用. 成功调用下面几个函数的前提是进程具备该权限, 只是访问令牌中没有启用该权限. 而如果进程没有该权限, 则使用下面的函数后再调用
GetLastError
会返回ERROR_NOT_ALL_ASSIGN
.
- 先用
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid)
得到用户的debug权限.
- 然后用
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,&hToken)
获取进程的令牌句柄.
- 最后用
AdjustTokenPrivileges
启用特权.
使用vs2019
编写注入器程序, 在生成的注入器可用前, 踩了不少坑, 因此记录一下. 本文涉及三种恶意代码注入方法: 直接dll注入, 反射式dll注入, 镂空注入. 之所以选这三种注入方法, 是因最近在做一个检测进程内存空间以期发现代码注入的程序, 而实验发现这三种方法对目标进程的改变各有特点:
- 直接dll注入: 还有APC注入, 本质都是在目标进程中执行
LoadLibrary
函数, 因而在枚举目标进程的模块列表时可看到注入的dll.
- 反射式dll注入: 这种方法也会在目标进程中开辟新的内存空间并写入代码. 但因为没有调用
LoadLibrary
, 所以**枚举目标进程的模块列表并不能看到注入的dll. (意味着目标进程的PEB没有变化). **
- 镂空注入: 直接改进程中某一模块的内存空间, 或者先注入一个合法模块, 再镂空该模块. **因为该方法没有注入恶意dll, 所以直接枚举目标进程的模块列表也无法发现恶意代码, 需扫描进程的内存空间. **
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2023-8-11 14:06
被wx_Niatruc编辑
,原因: 修改错误表述