D3CTF Reverse WP
前言
这次比赛做了d3kernel和locked door这两题,也是都拿下了一血。这两题都是强对抗类型,和传统的加解密类型题目有区别。kernel是内核逆向&反调试对抗;第二题是vmp对抗+real world类型,模仿的场景是商业软件许可证伪造逆向。这两题都挺有意思的,质量也都很高。
locked door
分析
查壳
查壳发现是vmp 3.5.1+的壳,
绕过反调试
由于vmp 3.5.1+的检测很严,scyllahide插件已经没办法过了,这边赛中用的是下面这个付费调试器动调的,这边提供另一个方案。titanhide驱动来处理vmp调试。
使用VKD工具的target64中的vminstall在虚拟机中运行安装,会多出来一个引导启动,重启电脑选择新的引导启动就可以,他会进入内核调试模式,禁止驱动强制签名以及关闭PG,也就可以让我们加载titanhide驱动。
在github上下载titanhide编译版本。
https://github.com/mrexodia/TitanHide/releases
直接拖入TitanHide.sys加载运行。
将titanhide中的x64插件这两个文件拖入dbg的plugins文件夹即可。
现在直接打开x64dbg,拖入程序运行就可以进行愉快的调试了。
脱壳
vmp找OEP的思路如下,断点QueryPerformanceCounter或GetSystemTimeAsFileTime都可以,可能执行一次不太够,要执行到返回地址的第二个栈顶地址是在.text段范围内的,此时第二个栈顶地址就是ope了。
很明显的看出确实是ope的代码
f4让程序走到这个oep下面的这个jmp,这时直接dump即可。
IDA反编译,字符串搜索key1定位到核心main函数。
可以在oep这个jmp跳过来后的代码段下面找到核心main函数的调用,接下来就是调试就可以了。
流程分析
main函数发现反编译不完全,发现是sub_7FF73E5EC840里面调用的一个call导致的,可以从传参+函数具体实现发现这应该是一个读取key文件的函数。
暂时将两处调用读取key文件的函数相关代码nop,让我们的反编译可以看到完整代码。
以下是完整反编译代码,第一个if应该就是校验Key1的函数, sub_7FF73E5EC900跟进看发现是一堆加密代码,且其中调用的sub_7FF73E797910似乎存在虚拟化,没办法看代码,只能进行调试看看具体是什么功能。
调试走到sub_7FF73E5EC900调用处发现参数一传入的就是key1.bin的256字节数据指针。
步入跟进函数,走到sub_7FF73E797910函数调用处,发现参数二传入的是key1.bin数据。
步过函数执行,跳转到刚刚参数一的地址,发现只是把参数二的数据复制了一份到参数一,那么这个函数实际上的作用和memcpy是一样的。
sub_7FF73E5EC900这函数功能就是将key.bin输入数据进行一次加密。
if那边调用的函数是对加密后的key数据进行一次EVP签名校验,可以通过进入各个函数里面,看到一些文本就可以知道是openssl的EVP代码。
而参数一就是要校验的目标文本,让key1.bin加密后的数据和”Welcome”进行签名校验。
可以看到下面调用函数结构和上面key1差不多,都是读入key2.bin然后进行sub_7FF73E5EC900的一次加密,最后与”Here is the key”进行签名校验,不过key2的EVP校验函数被vmp保护了,没办法查看代码。
从题目的描述可知key1和key1校验逻辑完全相同,那我们就可以从这个点出发进行伪造绕过校验。
伪造绕过签名校验
上文可知key1和key2校验逻辑是完全一样,由于key1校验是通过的,那我们就可以复制一份key1.bin改名覆盖key2.bin,让程序读key2.bin的时候实际读入的是key1的数据,然后断点在key2的EVPCheck函数,将参数一字符串地址改成’Welcome”字串的地址,最终让程序校验成功。
以下实操:
修改完key2.bin后,断点在Check2处,将参数一改成”Welcome”的地址。
步过执行Check2后,key2校验成功,flag就从Check2保护函数中输出出来了。
d3ctf{Y0u_0p3n_7h3_d00r!!!}
d3kernel
R3分析
调试client.exe,发现单步走会触发除0异常,最后跳到这一个函数,发现里面全都是r3层单纯的io交互和校验,并没用用到驱动通信。
调试,随便输入点东西,在这两处strncmp可以找到两个目标对应字符串,但这个password是fake的,名字后面还能用的到。
汇编界面发现上层的这个call存在异常,将箭头处jmp nop即可看到完整的代码。
发现这边有个反调试,然后走了不同代码,如果检测到被调试,就走我们上面分析的那个纯r3的交互,如果没有被检测到,就走真实的R0通信交互。
要想调试真实的代码部分,我们就要在反调试和检测驱动handle的地方做一下处理,因为我们目前还没有运行驱动,找不到驱动句柄的。
反调试这边直接将返回值rax改成0就可以绕过反调试。
createFile处我们也可以从参数得到”\\.\d3ctf”,mov r15,rax处将rax改成1就可以绕过这边的合法句柄校验。
经过调试可以发现这边是解密出一些字符串,然后输出输入。
然后将name和password指针放到一个结构体里面({name,password}),通信传到R0,然后再通信一次得到返回数据,再解密输出。控制码分别是0x222000和0x222004。到此整体R3流程分析完毕。
R0分析
代码流程分析
DriverEntry进来,发现函数这边存在反调试检测以及对R3程序进程保护,反调试检测到会直接将windbg调试剥离。
直接将红框处代码全部nop,就可以去掉所有的反调试和保护了,主要分析就在这边的MajorFunction,sub_1400011A0进行分析。
我们确实也可以在这里面找到两个控制码的判断和执行代码。
在控制码0x222004下面,有两处文本解密,可以模拟代码,手动解密出来为这两个字符串。
解密代码:
1 |
|
那么就可以知道这部分就是对加密后的name和password进行校验,校验成功输出”qwq, why!!!!! my secret!!!!!”,校验失败,跳转到下面输出”zako, try again”。
这部分代码我们也可以看到外层校验了28个字节,内层校验了144个字节,分别除以4就是7和36,分别对应上了r3层的name和password的长度。
在上面代码部分也可以看到有password和name的数据加载,一个36循环一个7循环就可以分辨出来。
这边有调用到的sub_14000270C是一个虚拟机代码,目前不知道执行了什么。接下来就先在两处的数据加载动调分析入手,看看password和name都做了什么处理。
动调分析
这边实际调试发现,这边扫描nt模块地址时候会导致蓝屏,所以最好在箭头处断点,手动赋值上nt模块地址,d3ctf+0x1CC4。
先用sxe ld d3ctf命令拦截断点该驱动的加载。
加载驱动断下后,设置d3ctf+0x1CC4的断点。
断下后lm vm nt查询nt的地址得到fffff804`75e00000,手动给d3ctf+0x5400赋值即可。
在我们刚刚要动调的36循环处下断点,运行成功断下。
这边可以发现要给eax赋值的数组处就是我们r3输入传进来的password,单步执行完call,可以发现参数一是r14赋值给rcx的,查看执行完函数的r14,发现是一个两个DWORD64地址的结构体。
直接pa d3ctf+0x1596让代码执行到此处,也就是for循环结束后的代码。
再次查看r14结构的第二个地址,发现这循环实际作用就是将输入的36 password字节都转换成DWORD类型,存入到一个数组。
单步执行完循环下面的两个VM后,发现是给这个数组前面加了个0x24数据
pa d3ctf+0x19E2,跳到name那边循环赋值的循环结束地址,同样查看参数一,这边是r15,发现是和password初始操作一样,将输入字符串转成DWORD数组存入。
接下来我们先不管中途对password和name的一些加密操作,直接跳到Check处,这边是28字节Check,对应name的DWORD长度。
pa d3ctf+0x1A99
查看两个数组的数据,发现前七个DWORD,也就是前28字节是完全一样的,说明name确实是r3假流程那边得到的”mitsuha”。
再pa d3ctf+0x1AC4,执行到校验password的地方。
查询将要对比校验的两个数组数值,上面是我们password加密后的密文,下面是目标密文。
我们输入的password是”123456abcdef123456abcdef123456abcdef”,发现加密后的密文很有规律性和重复性,猜测是简单加密。
并且发现第一个数据0x15 ^ ‘1’ = 0x24,也就是刚刚上面出现的VM函数给password数组添加的第一个0x24数据。
发现第一个0x15 ^ 0x24 = ‘1’,然后’1’ ^ 0x3 = ‘2’,依次下去,就解密出来了原输入的password。
说明检验前password的加密如下:插入0x24到数组头,依次往后两两相邻数值异或。
那么这边直接提取37个DWORD的密文,进行两两异或解密即可。
解密
1 |
|
d3ctf{a68dfb06-798f-4bd1-9e81-011aaec113f0}