Skip to content

2023腾讯游戏安全竞赛(PC客户端安全)

Updated: at 12:00

flag 为 catchmeifyoucan

分析

刚到手发现是vmp,但是拖进ida发现还有导入表可以看,这时候想到2020年初赛的一个驱动vmp的题,也是导入有部分函数可以看,那道题也是对导入函数进行下断点,进入到正常程序流程中。这里也是同理,直接拖进x64dbg对AddVectoredExceptionHandler VEH异常注册函数下断点(值得注意的是,对程序的代码段下断会被壳检测到)。

下完断点直接运行,来到断点处。

ctrl + f9向上跳转,回到程序的代码段,当出现AceSclShdRunCallBack之后,就知道这应该是程序的空间内了

到这里基本上壳就被绕过了,可以通过dump内存的方法开始分析程序了,但是我这里采用了一种比较取巧的办法。

火绒剑+x64dbg快速定位程序的行为特征

由于这道题会对文件进行操作,这种操作太敏感了,直接监控程序的行为就能知道哪段代码在进程文件操作

火绒剑配置进程过滤

x64dbg开始执行

有很多文件操作,但都不是我想要的,直接跳过,很容易就在刚刚触发断点的下面找到一个call 火绒剑提示有文件操作

继续跟进到函数内部,发现有很多花指令,但是这里依旧观察火绒剑的动态,然后出现文件操作就继续跟进函数。

狂按f8之后又看到了一个函数触发了火绒剑,跟进之后就会来的主要逻辑部分

在这个函数内部代码很庞大,但是基本上没什么函数调用。由于要执行文件操作,一定会执行函数调用,所以直接对所有call下断点,不断分析定位到几个关键函数,图中已经有注释

逻辑基本上就出来了,对flag先进行一个变表的base64,然后输出到文件中。

patch思路

这里这个程序有点坑,因为在我的电脑的上这个程序只能跑一次循环就退出了,主要在这里浪费了太多的时间。

通过上面的分析,flag是一个全局变量被存在一个固定位置,可以之后获取它的地址,然后直接利用。

createFile和writeFile都会调用到同一个函数,只需要hook这位置,改变传入的参数就能实现题目要求(但是要注意题目还打开了一个奇怪的文件,加个判断过滤一下就行)

这里createFile函数可以实现题目第三个要求,可以写入自己指定的文件

再f9运行一次,题目的第二个要求,可以写入明文的flag

综上,所以只需要修改栈上两个的地址和一个长度,就可以完成题目要求

脚本

由于要用到内存操作的, 直接BlackBone框架

https://github.com/DarthTon/Blackbone

#include <iostream>
#include <Windows.h>

#include "../include/BlackBone/Process/Process.h"

#pragma comment(lib,"..\\include\\build\\x64\\Debug\\BlackBone.lib")

using namespace blackbone;

Process process;

/*
cmp rbx, 0   creatFiel
jnz write

mov rax, [rsp+30]
cmp byte[rax], 0x63
jnz exit

mov rax, fileName->ptr()
mov [rsp+30], rax
exit:
mov rax, ordAddr
jmp rax
W:
mov rax, flagAddr
mvo [rsp+38], rax
mov rax, 00007FF60000000f
mov [rsp+40], rax
mov rax, ordAddr
jmp rax
*/

// hook代码只要就是用于修改栈上的参数
BYTE hookAsm[] = {
    0x48, 0x83, 0xfb, 0x00,
    0x75, 0x25,

    0x48, 0x8b, 0x44, 0x24, 0x30,
    0x80, 0x38, 0x63,
    0x75, 0xf,

    0x48, 0xb8, 0,0,0,0,0,0,0,0,
    0x48, 0x89, 0x44, 0x24, 0x30,
    // exit
    0x48, 0xb8, 0,0,0,0,0,0,0,0,  
    0xff, 0xe0, // 37

    0x48, 0xb8, 0,0,0,0,0,0,0,0,  
    0x48, 0x89, 0x44, 0x24, 0x38, 
    0x48, 0xb8, 0x0f, 0x00, 0x00, 0x00, 0xf6, 0x7f, 0x00, 0x00,  
    0x48, 0x89, 0x44, 0x24, 0x40, 
    0x48, 0xb8, 0,0,0,0,0,0,0,0,
    0xff, 0xe0,
};

BYTE hookData[] = {
    0x48,0xb8 ,0,0,0,0,0,0,0,0,
    0xff,0xe0
};

void attch() {

    // 控制创建文件的名字
    char myFileName[] = "s0rry.txt";

    // 输入hook进程的pid
    DWORD pid = 0;
    printf("pid: ");
    scanf("%d", &pid);
    printf("\n");

    // 附加进程
    process.Attach(pid);

    // 获取进程的模块信息 以及操作内存的对象
    ProcessModules& proModules = process.modules();
    ProcessMemory& memory = process.memory();
    ModuleDataPtr mainModule = proModules.GetMainModule();

    // 程序指定段的地址
    uint64_t text = mainModule->baseAddress + 0x1000;
    uint64_t data = mainModule->baseAddress + 0x77000;

    // hookAddr: 要hook的地址 ordAddr: hook注的代码本来要跳转的地址 
    // flagAddr: 原始flag存放的地址
    uint64_t hookAddr = text + 0xc6a0;
    uint64_t ordAddr = text + 0x17750;
    uint64_t flagAddr = data + 0x2e9;

    // 打印hook的地址 便于用CE去验证是否hook和patch成功
    BYTE out[15] = { 0 };
    memory.Read(hookAddr, 15, out);
    printf("hookAddr: %llx", hookAddr);

    // 在指定进程申请内存,用于执行shellcode 存放想要指定创建的文件名
    call_result_t<MemBlock> memShellCode = memory.Allocate(0x100, PAGE_EXECUTE_READWRITE);
    call_result_t<MemBlock> fileName = memory.Allocate(0x20, PAGE_READWRITE);

    if (NT_SUCCESS(memory.Protect(hookAddr , 0x100, PAGE_EXECUTE_READWRITE))) {

        printf("hookAddr 权限修改成功: %llx\n", hookAddr);

        if (memShellCode.success()&&fileName.success()) {

            printf("内存分配成功: \n");

            // 设置指定文件名
            fileName->Write(0, myFileName);

            // 设置hook目的地址
            *(uint64_t*)(&hookData[2]) = memShellCode->ptr();

            // 设置指定文件名的地址
            *(uint64_t*)(&hookAsm[18]) = fileName->ptr();

            // 设置返回地址
            *(uint64_t*)(&hookAsm[33]) = ordAddr;
            *(uint64_t*)(&hookAsm[75]) = ordAddr;

            // 设置flag地址
            *(uint64_t*)(&hookAsm[45]) = flagAddr;

        }

        // 写入shellcode
        memory.Write(hookAddr, 12, hookData);
        memShellCode->Write(0, hookAsm);

    }

    // 让hook进程进入循环,防止释放内存
    while (true)
    {
        for (int i = 0; i < 15; i++) {
            printf("%x ", out[i]);
        }
        printf("\n");
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }


}

int main() {

    attch();

    return 0;
}

执行结果