-
-
[原创]CTF2018第十二题分析(qwertyaa)
-
2018-7-8 20:56 2390
-
这题相对前面几题简单不少,做题完全靠猜...
定位main函数
这是一个64位的程序,所以我们直接用IDA64分析。启动一遍程序可知:该程序的密钥由argv[1]
给出。于是定位GetCommandLineA/W
,跟几步就可以到达main函数。
分析程序
这个程序将命令行输入复制到一个全局变量处(记作key
),接下来调用一个函数判断key
是否正确。
由于这个判断程序有点花,我把程序F5结果复制下来,主要看出现key
处及其前后几行的逻辑。
其中,经常出现两个magic number(0xCBF29CE484222325i64和0x100000001B3i64),查询百度可知这里在计算fnvHash
(这就是vc-x64标准库中std::hash
的算法),计算函数如下:
size_t std::_Hash_bytes(const unsigned char*data, size_t size) { size_t hash=0xCBF29CE484222325ll; for(size_t i=0ll;i<size;++i)hash=0x100000001B3ll*(hash^data[i]); return hash; }
几经分析,可猜测这个key
主要满足以下条件(fnvStr
表示字符串的fnvHash
,fnvChr
表示由传入的单一字符组成的字符串的fnvHash
):
- 长度恰为30
- 至少三处
fnvChr(key[i])==0xAF63B44C8601A894ll
fnvChr(key[0])
到fnvChr(key[8])
均已给出- 字符串由
{prefix}{possibleSuffixA}9{dllName}{possibleSuffixB}9{symBolName}9{possibleSuffixC}
组成,{prefix}
可由第3点倒推,{dllName}
长度恰为5字节,程序最后将会调用位于{dllName}.DLL
内的名为{symBolName}
的导出函数,且传入参数为(0,30)。 fnvStr(key) == 5728707748789076223ll
猜+暴力得出key
前9字节的hash都已给出,所以我们枚举所有字符并将它们的hash值与给出hash比对就可以得出这一部分的正确值。暴力程序如下(defs.h
位于ida的plugins目录下,这个程序顺便计算出条件2所需的字符为9
):
#include <stdio.h> #include "defs.h" signed __int64 keys[]={-5808510693665524758i64, -5808494200991101593i64, -5808519489758550446i64, -5808507395130640125i64, -5808522788293435079i64, -5808606351177179115i64, 0xAF63AD4C86019CAFi64, 0xAF63AC4C86019AFCi64, 0xAF63B54C8601AA47i64, 0xAF63B44C8601A894i64,0}; signed __int64 fnvHash(char *a1) { char v1; // dl signed __int64 result; // rax signed __int64 v3; // rax v1 = *a1; for ( result = 0xCBF29CE484222325i64; *a1; result = 0x100000001B3i64 * v3 ) { ++a1; v3 = result ^ v1; v1 = *a1; } return result; } char ans[100]={0}; int main(){ unsigned __int64 v183; LOWORD(v183) = 0; for(char x=1;x<127;x++){ LOBYTE(v183) = x; signed __int64 hash=fnvHash(&v183); for(int i=0;keys[i];i++){ if(keys[i]==hash){ if(ans[i]==0)ans[i]=x; else printf("dup"); } } } puts(ans); }
运行可知前9字节为KXCTF2018
。
接下来这个key
的剩余部分除了还有三个9
外似乎只能猜出来。
首先,出于简单考虑,我们不妨假设所有{possibleSuffix*}
为空。(最后事实也如此)
在system32
目录下一番摸索后我找到一些相对可疑的dll:
imm32.dll
mmres.dll
msctf.dll
ole32.dll
psapi.dll
ntdll.dll
显然ntdll.dll是其中最可疑的,我把它的所有导出函数都存到一个*.h
文件中。格式如下:
const char symbol[][0x100]={ "RtlDispatchAPC", "RtlActivateActivationContextUnsafeFast", //*** *** "wcstol", "wcstombs", "wcstoul", NULL};
需要注意的是Win下的文件系统是大小写不敏感的,结合后面补的.DLL
可以猜测前面应该是全大写的NTDLL
。
所以我们可以写一个暴力程序(其中{symBolName}
的长度即30(总长)-9({prefix})-3(分隔用的9)-5({dllName})=13
),如下:
#include <stdio.h> #include <string.h> #include "symbols.h" char test[0x100]={0}; signed __int64 fnvHash(char *a1) { char v1; // dl signed __int64 result; // rax signed __int64 v3; // rax v1 = *a1; for ( result = 0xCBF29CE484222325i64; *a1; result = 0x100000001B3i64 * v3 ) { ++a1; v3 = result ^ v1; v1 = *a1; } return result; } int main(){ for(int i=0;symbol[i][0];i++)if(strlen(symbol[i])==13){ strcpy(test,"KXCTF20189"); strcat(test,"NTDLL9"); strcat(test,symbol[i]); strcat(test,"9"); if(5728707748789076223i64==fnvHash(test))printf("Key: %s\n",test,symbol[i]); } }
得到key
为KXCTF20189NTDLL9DbgUiContinue9
,经验证正确。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。