This page looks best with JavaScript enabled

NepCTF2023 Qriver3.0出题思路

 ·  ☕ 3 min read · 👀... views

前言

大体设计思路与 Qriver2.0出题思路 差不多,依旧是使用自己写的LLVM混淆编译了一个Windows驱动程序。主要原因是去年是0解,感觉题目完全没起到效果。今年在去年的基础上重新设计了算法,并换用了新写的混淆。因为题目的主要难度来自于混淆,这边简单的贴一下题目源码,大家可以对照着学习

主体与算法设计

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Qriver3.0.cpp

// NepCTF{4E65706E65702D5765316C63306D6521}
BYTE ans[] = { 0x63, 0x23, 0x02, 0x70, 0x01, 0x05, 0x01, 0x75, 0x00, 0x70, 0x01, 0x05, 0x05, 0x74, 0x07, 0x73, 0x03, 0x02, 0x05, 0x04, 0x05, 0x72, 0x00, 0x70, 0x05, 0x03, 0x05, 0x74, 0x00, 0x71, 0x04, 0x04 };
BOOL CheckInput(char input[], int length) {

	BYTE* buff = (BYTE*)ExAllocatePoolWithTag(NonPagedPool, 0x100, 'Nep');

	BYTE xor_key = 102;
	for(int i=0;i<length;++i)
	{
		BYTE orig_byte = input[i];
		buff[i] = orig_byte ^ xor_key;
		xor_key = orig_byte;
	}
	for (int i = 0; i < length; ++i)
	{
		BYTE orig_byte = buff[i];
		buff[i] = orig_byte ^ xor_key;
		xor_key = orig_byte;

		if (buff[i] != ans[i]) {
			return FALSE;
		}

	}


	kprintf("bytes.fromhex(\"%s\")\n", input);
	return TRUE;
}

std::wstring GetInput() {
	Registry reg;
	if (reg.Open(Registry::HKEY_LOCAL_MACHINE, xorstr(L"SOFTWARE\\Nepnep\\Qriver3.0").crypt_get()) == TRUE)
	{
		wchar_t szBuf[MAX_PATH] = { 0 };
		size_t BufferLen = MAX_PATH * 2;
		if (reg.GetValue(xorstr(L"flag").crypt_get(), REG_SZ, szBuf, BufferLen) == TRUE)
		{
			return std::wstring(szBuf);
		}
	}
	return L"";
}


//驱动卸载函数
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	if (DriverObject->DeviceObject)
	{
		IoDeleteSymbolicLink(&DeviceLink);
		IoDeleteDevice(DriverObject->DeviceObject);
	}
	

	// 卸载AntiDebug
	AntiDebug::Instance().~AntiDebug();
}


//驱动入口函数
extern "C" NTSTATUS DriverMain(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{

	UNREFERENCED_PARAMETER(RegistryPath);
	OBJECT_ATTRIBUTES ObjAddr = { 0 };
	NTSTATUS nStatus = STATUS_SUCCESS;
	
	// 初始化
	if (!NT_SUCCESS(KernelOffsets::init())) {
		kprintf("[Qriver] : Windows Version Too High Or Too Low!\n");
		return STATUS_FAILED_DRIVER_ENTRY;
	}
	if (!NT_SUCCESS(AntiDebug::Instance().Init())) {
		kprintf("[Qriver] : Init Error!\n");
		return STATUS_FAILED_DRIVER_ENTRY;
	}

	//创建设备
	PDEVICE_OBJECT Device = NULL;
	NTSTATUS Status = IoCreateDevice(DriverObject, sizeof(DriverObject->DriverExtension), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &Device);
	if (!NT_SUCCESS(Status))
	{
		return Status;
	}

	//创建链接
	Status = IoCreateSymbolicLink(&DeviceLink, &DeviceName);
	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(Device);
		return Status;
	}
	// 设置全局驱动对象
	g_pDriverObject = DriverObject;
	//设置卸载函数
	DriverObject->DriverUnload = DriverUnload;

    // 安装反调试
	AntiDebug::Instance().InstallAntiKernelDebug();
	AntiDebug::Instance().InstallAntiDebugThread();

	// 获取输入
	std::string input = Utils::string::wstring2string(  (wchar_t*)(GetInput().c_str()) );

	// 格式检查   这边故意不加密NepCTF{}的flag头,降低静态分析难度
	if (input == "" || input.find("NepCTF{") != 0 || input.c_str()[input.length() - 1] != '}') {
		IoDeleteSymbolicLink(&DeviceLink);
		IoDeleteDevice(DriverObject->DeviceObject);
		AntiDebug::Instance().~AntiDebug();
		return STATUS_ACCESS_DENIED;
	}
	// kprintf("[Qriver] : Input: %s\n", input.c_str());

	// 取花括号内
	std::string serials = input.substr(7, input.length() -7 - 1);

	if (AntiDebug::Instance().bAntiDebugThread == FALSE	|| 
		AntiDebug::Instance().bAntiKernelDebug == FALSE	||
		CheckInput( const_cast<char*>(serials.c_str()), serials.length() ) == FALSE)
	{
		IoDeleteSymbolicLink(&DeviceLink);
		IoDeleteDevice(DriverObject->DeviceObject);
		AntiDebug::Instance().~AntiDebug();
		return STATUS_ACCOUNT_DISABLED;
	}

	auto driver_exit = std::experimental::make_scope_exit([&]() { kprintf("[Qriver] : Success\n"); });
	return STATUS_SUCCESS;
}

反调试设计

从选手反馈情况来看,做出来的师傅都是静态硬撕,所以这边也讲一下反调试的设计部分。因为这部分的一些内容暂时不方便开源,所以这边手动列举出本题使用的反调试手段:

  1. 循环调用KdDisableDebugger
  2. IPI清除DR寄存器

第一种反调试手段应该是最最常见的Windows内核反调试手段,调用该API后会Detach调试器。第二种方法学自 瓦洛兰特反作弊系统Vanguard浅析,通过CPU IPI中断(IPI(Inter-Processor Interrupt)使所有核心执行一个函数,我这边执行的函数代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ULONG_PTR AntiDebug::IpiAntiDebugCall( ULONG_PTR  Argument){

	static int cnt = 0;

	__writedr(0, 0);
	__writedr(1, 0);
	__writedr(2, 0);
	__writedr(3, 0);
	__writedr(7, 0);
	cnt++;
	return NULL;
}

也就是使所有核心同时清除Dr寄存器,来达到一个反调试的效果。

混淆设计

这道题采用了最新的自实现的LLVM混淆,也对粒度做了更小的区分,对特点的算法函数和反调试函数做了保护,而忽略无关库函数。混淆设计如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"recipe": [
    {
        "混淆": "LowerSwitch",
        "范围": ".*"
    },
    {
        "函数名": "Virtualization",
        "范围": "IpiAntiDebugCall"
    },
    {
        "混淆": "QFlatten",
        "返回": "CheckInput"
    },
    {
        "混淆": "IndirectBranch",
        "范围": "CheckInput|AntiDebugRoutine|UninstallAntiDebugThread"
    },
    {
        "混淆": "IndirectGlobalVariable",
        "范围": "CheckInput|AntiDebugRoutine|UninstallAntiDebugThread"
    },
    {
        "混淆": "Flower",
        "范围": "CheckInput|AntiDebugRoutine|IpiAntiDebugCall|UninstallAntiDebugThread"
    }
],

EXP

下面公开exp。这个算法实现灵感来自于之前某比赛的某题,通过两次异或来达到可化简但不可逆的效果,所以exp使用爆破获得flag:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import string
ans = [0x63, 0x23, 0x02, 0x70, 0x01, 0x05, 0x01, 0x75, 0x00, 0x70, 0x01, 0x05, 0x05, 0x74, 0x07, 0x73, 0x03, 0x02, 0x05, 0x04, 0x05, 0x72, 0x00, 0x70, 0x05, 0x03, 0x05, 0x74, 0x00, 0x71, 0x04, 0x04]
for i in range(256):  
    tmp = ans.copy()
    xor_key = i
    for index in range(len(ans)):
        tmp[index] ^= xor_key
        xor_key = tmp[index]

    xor_key = 0x66
    for index in range(len(ans)):
        tmp[index] ^= xor_key
        xor_key = tmp[index]
    
    if i == tmp[-1]:
        s = "".join([chr(i) for i in tmp if chr(i) in string.ascii_letters+string.digits])
        print(s)
Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer