[原创]CTF2017第7题分析
1.调试分析
OD加载,发现入口不是OEP指定的,估计有tls,直接清掉了tls,然后根据正常运行的程序,修复了OEP,这时就能正常断在OEP了,后面调试发现有个文件校验,有修改就退出程序了.

这里修改后,就可以patch程序了.
调试过程中,发现输入后验证就会异常,刚开始根据异常链查了半天,陷进去了,后来翻了下作者之前的帖子,发下之前有个Cm是在顶级异常处理那处理的.
直接IDA找到:

看了下,就是判断异常类型,然后发消息,直接索引ds:Msg,定位到关键点:
调试时候,可以输入字符串后,断在40E3E5,然后设置EIP为40E3ED,就可以跟踪字符串的验证操作了.
大概流程是:
004104FC |. FF15 283E4500 call dword ptr ds:[<&USER32.GetDlgItemTextA>]
先用GetDlgItemTextA读取到输入字符串,然后解码一个函数00411B30并调用
00410558 E8 D3150000 call 7crackme.00411B30
这里面先是对输入字符串依次XOR 0xCC,

然后就是对各个字符查表处理,得到的结果再通过sub_410F75 异或移位处理,然后在sub_411975中每个字符通过对4的商和余数,转化成2个字节,形成的字节流要和已知的一致,相关具体数据见代码.
验证通过后,会再根据输入字符串,解码一段数据,还原成功时候弹框用的函数.

2. 程序实现
程序是边调试边修改的,变量有点乱,不过可能更方便看流程.
function ctf_7() {
//首先根据确定表,反推出一个确定串
var checkBuf = new Buffer([
0x02, 0x02, 0x00, 0x03, 0x00, 0x00, 0x02, 0x03, 0x00, 0x01,
0x02, 0x03, 0x02, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02,
0x02, 0x03, 0x02, 0x03, 0x01, 0x00, 0x02, 0x02, 0x02, 0x01,
0x02, 0x03, 0x02, 0x02, 0x02, 0x01, 0x02, 0x03, 0x02, 0x03,
0x02, 0x01, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x00, 0x03, 0x01, 0x02, 0x02, 0x03, 0x02, 0x00, 0x00, 0x02,
0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00,
0x02, 0x02, 0x02, 0x03, 0x00, 0x02, 0x01, 0x00, 0x00, 0x03]);
var resultBuf = new Buffer(checkBuf.length / 2);
var pattern = new Buffer("0123456789ABCDEF");
for (var i = 0; i < checkBuf.length; i=i+2)
{
var iTemp = checkBuf.readUInt8(i) * 4 + checkBuf.readUInt8(i + 1);
resultBuf[i / 2] = pattern[iTemp];
}
console.log(resultBuf.toString());
var resultBuf2 = Buffer.from(resultBuf.toString(), 'hex');
console.log(resultBuf2);
console.log(resultBuf2.toString('hex').toUpperCase());
//return;
//原始串加密过程
var srcBuf = new Buffer("12345678901234567890");
//2. 查表
var tableBuf = new Buffer([
0x89, 0xBC, 0x95, 0xFC, 0xFB, 0xBA, 0xED, 0x9A, 0xBB, 0xAE,
0xFE, 0x99, 0xA2, 0x98, 0xB9, 0xF9, 0x9F, 0x84, 0x9C, 0xFD,
0x83, 0xAD, 0xB6, 0xA9, 0xA5, 0xF5, 0x8C, 0xA7, 0x9E, 0x96,
0x8A, 0xF4, 0x85, 0xBE, 0xA8, 0x8F, 0x86, 0xAF, 0x88, 0x9D,
0x87, 0xBF, 0xFF, 0xA1, 0x8B, 0x81, 0xA0, 0xAB, 0x8E, 0xBD,
0xB5, 0xAA, 0x82, 0x94, 0xA4, 0x8D, 0xA3, 0xF8, 0xB4, 0xFA,
0x9B, 0xA6, 0xB8, 0x80
]);
var pattern2 = new Buffer("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
var initBuf = new Buffer(srcBuf.length);
for (var i = 0; i < srcBuf.length; i++) {
//枚举,修改srcBuf的值
for (var loopIndex = 0; loopIndex < pattern2.length; loopIndex++) {
srcBuf[i] = pattern2[loopIndex];
//1. 先xor 0xCC
srcBuf[i] ^= 0xCC;
//定位索引
var iFoundIndex = 0;
for (var index = 0; index < tableBuf.length; index++) {
if (srcBuf[i] == tableBuf[index]) {
iFoundIndex = index;
//console.log("iFoundIndex: " + iFoundIndex.toString(16));
for (var iCount = 0; iCount <= i; iCount++) {
if (iFoundIndex == 0) iFoundIndex = 1;
//余数+5,要循环的次数,再加上原来的索引 /0x40(超过表大小,就从头开始),得到新的值
iFoundIndex = parseInt(iFoundIndex / 5) + 5 + iFoundIndex;
iFoundIndex = iFoundIndex % 0x40;
//如果为0,则设1
if (iFoundIndex == 0) iFoundIndex = 1;
}
//得到新的索引后,再查表,更新为查到的新值
srcBuf[i] = tableBuf[iFoundIndex];
srcBuf[i] = 8 * (srcBuf[i] ^ 0xCC) | ((srcBuf[i] ^ 0xCC) >> 5);
break;
}
}
if (srcBuf[i] == resultBuf2[i]) {
console.log("found :" + srcBuf[i].toString(16) + " " + resultBuf2[i].toString());
console.log("i: " +i+" found2 :" + pattern2[loopIndex].toString());
initBuf[i] = pattern2[loopIndex];
//break;
}
else {
//console.log("not found :");
}
}
}
console.log(initBuf);
console.log(initBuf.toString());
}
结果其实有多种组合,这个不确定是不是有其它的限制条件没看到,不过也是考虑到时间,直接从这里面选了带Pediy2017的字符串,尝试了几个去验证解码上面提到的函数(还原成功时候弹框用的函数,其实也可以验证函数中的操作码),确定结果:
BwnsAtPediy2017KX9Ok
