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;
}
执行结果