-
-
[原创]CTF2016看雪--最后一题
-
发表于:
2016-12-29 23:36
5134
-
最后一题,这题的作者是我同学--小野老板
做题的当天也是我离开武汉的日子,这题是在高铁上做的,做完以后看着窗外风景呼啸而过心情很惆怅。。。其实什么也看不见天都黑了
感谢科锐,感谢各位同学的帮助,感谢钱老师,感谢张老师,感谢柯老师,感谢姚老师,感谢李老师,感谢袁老师,感谢CCTV,感谢国家。。。
注:
1.以下数字全部是16进制
这题应该是用纯汇编写的,10KB够小,看到熟悉的push ebp mov ebp,esp,接着push各位寄存器大仙,这样的标准格式很容易让人想到radasm。
而题目本身应该是构思了一个比较大的验证框架,但是时间来不及所以有点赶工,回想起来那段时间确实学校的作业也比较多。
进入程序,程序用了两种反调试手段,准确的说应该是反断点手段:
1.遍历程序本身代码段,加总每个字节算校验和,如果不对则人为制造C05退出程序,这种反调试手段用了3次,这是为了防止下CC断点,地点如下图:
1
2
3
2.另一种反调试手段,是创建一个线程计时修改内存属性,这是为了防止下内存断点,地点如下图(图中的call ebx,第一个是Sleep,第二个是VirtualProtect,至于为什么是之后再表):
第1个手段nop调产生C05异常的代码即可,第2个手段可以直接ret调这个线程函数,由于我做这题没有下内存断点,所以我也没管他。
接着说为什么call ebx就是call Sleep,当然可以动态调出来,因为OD有这个功能。但是还是需要分析下401039这个函数,这是因为后面破解也需要了解他在干嘛,我直接说答案了,这个函数就是在GetProcAddress,作者是自己解析导出表自己实现这个功能,然后填入到全局数据区,但是填入函数指针地址是做了手脚,这样不能直接由OD分析,而需要修正后call到这个指针才会提示出函数名。
而由于修正手法一样,要解析这些全局数据区也有了方便法门,如下图:
可以在标红那里下断点,然后修改eax为对应要解析的全局地址,然后单步走完修改ebx的值,ebx就是修正后的API地址,这样把全局数据区一一对应就可以全部解析出来了,但也不需要全部解析,只要解析出最关键的一个即可,即403B19,这里对应的是MessageBox,也是后面提示成功弹框的地方,记住403B19这个地址后面有用。
程序的验证流程是放在4016F0和4017F9这两个线程函数中进行的,而在主线程中有看到拿输入数据并验证其实后面没什么用,这也是我为什么说作者设计了一个比较大的框架。
在4016F0拿到输入字符(必须为8个字节),会计算所有输入字符的总和,并同时会逐个字符异或66后结果保存到一个全局数组,如下图:
紧接着会根据字符总和做跳转,唯一正确的是325这个总和,因为325这个才会启动4017F9的等待事件,接着进入到4017F9这个线程函数中拿出刚才异或结果的全局数组的前4个字节与后4个字节加总,看是否等于32113442,不等于则验证失败,等于进入下一步,如下图:
在这里会用异或结果的全局数组解密一段40个字符的代码段,然后跑这段代码,解密算法比较简单就是加上后4个字节,然后异或前4个字节,如下图:
由于作者有提示验证成功是弹框good!,这里就有个推断了,代码中call API不可能把地址写死,这也是PE中导入表存在的意义,而作者自己模拟了导入表,那必然就会用到自己模拟的导入表来调用MessageBox,所以403B19这个保存了MessageBox跳转地址的全局变量就有用了,而这段解密代码正确的话,应该存在403B19这个地址。
有了上面的推断我们可以写枚举代码了,可以首先找到适合加总等于325,并且都逐位异或66后前4个字节与后4个字节加总等于的解的集合,然后在这些集合中去用程序的方法去解密那段28个字节的代码段,然后看解密的代码里面是否存在连续3个字节分别是19,3B,40,这样进一步缩小适合的解集,最终找到了100个左右的解集,求解集的C代码如下:
#include <windows.h>
BYTE g_ary_btSocCodes[40] = {
0x69, 0x92, 0x14, 0x09, 0x58, 0x1A, 0x65, 0x79,
0x60, 0x13, 0xCC, 0xD1, 0x28, 0x34, 0x0A, 0x2C,
0x0F, 0x40, 0x0B, 0x5C, 0x83, 0x08, 0x4B, 0xF4,
0x30, 0x85, 0x39, 0x34, 0x18, 0x40, 0x0B, 0xCD,
0xE9, 0x6F, 0xCA, 0x8C, 0x1F, 0x0F, 0x4B, 0xF4
};
BYTE g_ary_btDesCodes[40] = {
0x00
};
void backToGood()
{
BYTE* pbtLeft = NULL;
BYTE* pbtRight = NULL;
DWORD dwLeft = 0;
DWORD dwRight = 0;
DWORD dwConst = 0x32113442;
BYTE ary_btTmp[0x10] = "\0";
do {
pbtLeft = (BYTE*)&dwLeft;
ary_btTmp[0] = (pbtLeft[0] ^ 0x66);
ary_btTmp[1] = (pbtLeft[1] ^ 0x66);
ary_btTmp[2] = (pbtLeft[2] ^ 0x66);
ary_btTmp[3] = (pbtLeft[3] ^ 0x66);
if (ary_btTmp[0] < '0' || ary_btTmp[0] > 'z' ||
(ary_btTmp[0] > '9' && ary_btTmp[0] < 'A') ||
(ary_btTmp[0] > 'Z' && ary_btTmp[0] < 'a') ||
ary_btTmp[1] < '0' || ary_btTmp[1] > 'z' ||
(ary_btTmp[1] > '9' && ary_btTmp[1] < 'A') ||
(ary_btTmp[1] > 'Z' && ary_btTmp[1] < 'a') ||
ary_btTmp[2] < '0' || ary_btTmp[2] > 'z' ||
(ary_btTmp[2] > '9' && ary_btTmp[2] < 'A') ||
(ary_btTmp[2] > 'Z' && ary_btTmp[2] < 'a') ||
ary_btTmp[3] < '0' || ary_btTmp[3] > 'z' ||
(ary_btTmp[3] > '9' && ary_btTmp[3] < 'A') ||
(ary_btTmp[3] > 'Z' && ary_btTmp[3] < 'a')) {
dwLeft++;
continue;
}
DWORD dwSum = 0;
dwRight = dwConst - dwLeft;
pbtRight = (BYTE*)&dwRight;
ary_btTmp[4] = (pbtRight[0] ^ 0x66);
ary_btTmp[5] = (pbtRight[1] ^ 0x66);
ary_btTmp[6] = (pbtRight[2] ^ 0x66);
ary_btTmp[7] = (pbtRight[3] ^ 0x66);
if (ary_btTmp[4] < '0' ||
ary_btTmp[4] > 'z' ||
(ary_btTmp[4] > '9' && ary_btTmp[4] < 'A') ||
(ary_btTmp[4] > 'Z' && ary_btTmp[4] < 'a') ||
ary_btTmp[5] < '0' ||
ary_btTmp[5] > 'z' ||
(ary_btTmp[5] > '9' && ary_btTmp[5] < 'A') ||
(ary_btTmp[5] > 'Z' && ary_btTmp[5] < 'a') ||
ary_btTmp[6] < '0' ||
ary_btTmp[6] > 'z' ||
(ary_btTmp[6] > '9' && ary_btTmp[6] < 'A') ||
(ary_btTmp[6] > 'Z' && ary_btTmp[6] < 'a') ||
ary_btTmp[7] < '0' ||
ary_btTmp[7] > 'z' ||
(ary_btTmp[7] > '9' && ary_btTmp[7] < 'A') ||
(ary_btTmp[7] > 'Z' && ary_btTmp[7] < 'a')) {
dwLeft++;
continue;
}
dwSum = dwSum + ary_btTmp[0];
dwSum = dwSum + ary_btTmp[1];
dwSum = dwSum + ary_btTmp[2];
dwSum = dwSum + ary_btTmp[3];
dwSum = dwSum + ary_btTmp[4];
dwSum = dwSum + ary_btTmp[5];
dwSum = dwSum + ary_btTmp[6];
dwSum = dwSum + ary_btTmp[7];
if (dwSum == 0x325) {
DWORD dwIndex = 0;
do {
DWORD* pdwSocTmp = (DWORD*)g_ary_btSocCodes;
DWORD* pdwDesTmp = (DWORD*)g_ary_btDesCodes;
pdwDesTmp[dwIndex] = pdwSocTmp[dwIndex] + dwRight;
pdwDesTmp[dwIndex] = pdwDesTmp[dwIndex] ^ dwLeft;
dwIndex++;
} while (dwIndex < 10);
bool isRight = false;
dwIndex = 0;
do {
if (g_ary_btDesCodes[dwIndex] == 0xFF) {
isRight = true;
}
dwIndex++;
} while (dwIndex < 40);
if (isRight) {
dwIndex = 0;
do {
if ((dwIndex < 38 &&
g_ary_btDesCodes[dwIndex] == 0x19 &&
g_ary_btDesCodes[dwIndex +1] == 0x3b &&
g_ary_btDesCodes[dwIndex + 2] == 0x40)) {
printf("%s\r\n", ary_btTmp);
}
dwIndex++;
} while (dwIndex < 40);
}
DWORD dwTmp = 0;
}
dwLeft++;
} while (dwLeft < dwConst);
}
int main(int argc, char* argv[])
{
backToGood();
return 0;
}
找到这个比较小的解集,手动输入验证吧。。。,边听着小说边试,最终找到了这个KAhuskey会弹good!