前言

有意思的。


环境搭建

首先需要C++的开发环境,下载MinGW安装程序,得到一个mingw-w64-install.exe。

运行之后发现网络问题导致在线安装失败,选择需要的版本,比如我就是Windowsx64,离线下载安装,网络问题好像还是很大,总归是能下载了。

下载完成后解压,并配置好环境变量,将bin下面的可执行文件加入path中。

执行gcc -v命令,可以看到安装成功了:

Using built-in specs.
...
Thread model: win32
gcc version 8.1.0 (x86_64-win32-seh-rev0, Built by MinGW-W64 project)

测试编译C++程序

新建一个hello.cpp文件,写一个hello world:

#include <iostream>
using namespace std;

int main() {
    cout<<"hello world"<<endl;
    return 0;
}

配置好g++编译器后编译运行,不过我的环境下似乎自动配置好了,新建终端并输入:

g++ .\hello.cpp

生成a.exe程序并运行,可以看到输出:

PS D:\Cpp> .\a.exe
hello world

关于函数调用栈

观摩一下一个函数最开始会干什么,将程序放到IDA中打开,看到main函数的前几个汇编代码:

.text:0000000000401550                 push    rbp
.text:0000000000401551                 mov     rbp, rsp
.text:0000000000401554                 sub     rsp, 20h

保存rbp寄存器,更新rbp寄存器并开拓栈空间用于保存局部变量。

也就是说,如果是x86环境,此时的栈中只有函数返回地址和函数参数,还没有rbp。

API钩取

然而我是x64环境,此时前四个参数被放在了寄存器中。

根据参考文章稍作修改,要注意的点比如寄存器名字、一些变量的类型要放大到适合64位系统以及ContextFlags要设置为CONTEXT_FULL来获取所有寄存器:

#include <iostream>
#include <windows.h>
#include <tchar.h>
#include <tlhelp32.h>
#include <stdio.h>
#include <shlobj.h>

LPVOID g_pWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC;
BYTE g_orgByte = 0;

BOOL CreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
    g_pWriteFile = (void*)GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");

    memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO)); 

    ReadProcessMemory(g_cpdi.hProcess, g_pWriteFile, &g_orgByte, sizeof(BYTE), NULL);

    WriteProcessMemory(g_cpdi.hProcess, g_pWriteFile, &g_chINT3, sizeof(BYTE), NULL); 

    return TRUE;
}

BOOL ExceprtionDebugEvent(LPDEBUG_EVENT pde) 
{
    CONTEXT ctx;
    PBYTE lpBuffer = NULL; 
    ULONG_PTR dwNumOfBytesToWrite, dwAddrOfBuffer;
    DWORD i = 0;
    PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;

    if (per->ExceptionCode == EXCEPTION_BREAKPOINT)
    {
        if (g_pWriteFile == per->ExceptionAddress) 
        {

            WriteProcessMemory(g_cpdi.hProcess, g_pWriteFile, &g_orgByte, sizeof(BYTE), NULL); 

            ctx.ContextFlags = CONTEXT_FULL;
            GetThreadContext(g_cpdi.hThread, &ctx); 

            // ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Rsp + 0x10), &dwAddrOfBuffer, sizeof(DWORD), NULL); 
            // ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Rsp + 0x18), &dwNumofBytestTowrite, sizeof(DWORD), NULL);

            dwAddrOfBuffer = ctx.Rdx;
            dwNumOfBytesToWrite = ctx.R8;

            lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite + 1);
            memset(lpBuffer, 0, dwNumOfBytesToWrite + 1); 

            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumOfBytesToWrite, NULL);
            printf("orignal string: %s\n", lpBuffer);

            for (i = 0; i < dwNumOfBytesToWrite; i++)
            {
                if (lpBuffer[i] >= 0x61 && lpBuffer[i] <= 0x7A)
                {
                    lpBuffer[i] -= 0x20; 
                }
            }
            printf("string after changing:%s\n", lpBuffer);

            WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer, dwNumOfBytesToWrite, NULL);

            free(lpBuffer);

            ctx.Rip = (DWORD64)g_pWriteFile;
            SetThreadContext(g_cpdi.hThread, &ctx);

            ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
            Sleep(0); 

            WriteProcessMemory(g_cpdi.hProcess, g_pWriteFile, &g_chINT3, sizeof(BYTE), NULL);

            return TRUE;
        }
    }
    return FALSE;
}

void DebugLoop()
{
    DEBUG_EVENT de;
    DWORD dwContinueStatus;

    while (WaitForDebugEvent(&de, INFINITE))
    {
        dwContinueStatus = DBG_CONTINUE;
        if (de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
        {
            CreateProcessDebugEvent(&de);
        }
        else if (de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
        {
            if (ExceprtionDebugEvent(&de))
                continue;
        }
        else if (de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
        {
            break;
        }

        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }

}

int main(int argc, char* argv[])
{
    DWORD dwPID = 0;

    dwPID = atoi(argv[1]);

    if (!DebugActiveProcess(dwPID))
    {
        printf("DebugActiveProcess(%d) failed!!!\n""Error Code = %d\n", dwPID, GetLastError());
        return -1;
    }

    printf("DebugActiveProcess(%d) successfully!!!\n", dwPID);

    DebugLoop();

    return 0;
}

打开一个记事本,并使用PowerShell运行该程序调试记事本进程:

.\a.exe 27276

开始调试后,往记事本中写入一些字符串并保存,可以看到调试程序捕获了这些字符串:

orignal string: twings
twings hello world
string after changing:TWINGS
TWINGS HELLO WORLD

关闭记事本后再打开,可以发现写入的字符串已经被改为了大写字母。


参考

wingw-w64安装时 the file has been downloaded incorrectly!

MinGW-w64安装教程——著名C/C++编译器GCC的Windows版本

VSCode配置C/C++环境

API钩取:通过调试手段钩取API函数

x64 下记事本WriteFile() API钩取


二进制 C++

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

CVE-2022-39198 Dubbo Hession反序列化漏洞
ThinkPHP多语言模块文件包含漏洞