本帖最后由 chkds 于 2018-11-26 15:30 编辑
XNUCA2018 Code_Interpreter
作为一只菜鸡,第一次搞定一个解释器的题目,决定记录一下
工具:gdb、IDA pro 6.8、DIE、010editor
解压后发现有两个文件,一个code,一个Code_Interpreter,首先把Code_Interpreter拖进DIE,确定是64位程序,无壳
拖进IDA进行分析,main函数如下
main函数逻辑很简单,以 rb 的方式读取了一个文件中的数据,键盘输入三个十进制格式的数字,能通过验证就会将输入按十六进制格式组合输出作为flag,重点就在Interpreter那个函数中
根据函数的调用可以看出Interpreter函数所用的参数是来自文件(code)中的数据,反汇编出的Interpreter函数如下:
[C] 纯文本查看 复制代码 __int64 __fastcall Interpreter_400806(__int64 code){
__int64 result; // rax@1
int v2; // eax@4
int v3; // ST1C_4@4
int v4; // eax@4
int v5; // ST1C_4@4
int v6; // eax@4
int v7; // ST1C_4@4
unsigned __int8 v8; // ST1A_1@6
unsigned __int8 v9; // ST1A_1@7
unsigned __int8 v10; // ST1A_1@8
unsigned __int8 v11; // ST1A_1@9
unsigned __int8 v12; // ST1A_1@10
unsigned __int8 v13; // ST1A_1@11
unsigned __int8 v14; // ST1A_1@12
unsigned __int8 v15; // ST1A_1@13
char v16; // [sp+19h] [bp-7h]@1
dword_6020A0[0] = dword_6024A0[11]; // first
dword_6020A4 = dword_6024A0[8]; // second
result = (unsigned int)dword_6024A0[10];
dword_6020A8 = dword_6024A0[10]; // third
dword_6024A0[7] = 0;
dword_6024A0[6] = 2;
dword_6024A0[5] = 0;
v16 = 1;
while ( v16 )
{
switch ( *(_BYTE *)((unsigned int)dword_6024A0[5] + code) )
{
case 0:
v16 = 0;
break;
case 1: // code[27],code[66],code[102]
v2 = ++dword_6024A0[5];
++dword_6024A0[5];
v3 = *(_BYTE *)((unsigned int)v2 + code);//0x6b
v4 = dword_6024A0[5]++;
v5 = (*(_BYTE *)((unsigned int)v4 + code) << 8) + v3;//0xcc << 8
v6 = dword_6024A0[5]++;
v7 = (*(_BYTE *)((unsigned int)dword_6024A0[5] + code) << 24) + (*(_BYTE *)((unsigned int)v6 + code) << 16) + v5;//0x7e << 16 + 0x1d << 24
++dword_6024A0[6];
dword_6020A0[(unsigned __int64)(unsigned int)dword_6024A0[6]] = v7;
break;
case 2: // code[38],code[77],code[113]
--dword_6024A0[6];
break;
case 3: // code[63],code[99]
++dword_6024A0[5];
v8 = *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
++dword_6024A0[5];
dword_6024A0[(unsigned __int64)v8] += dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0[5]
+ code)];// dword_6024a0[0]=dword_6024a0[2];dword_6024a0[2]
break;
case 4: // code[24],code[35],code[74],code[110]
++dword_6024A0[5];
v9 = *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
++dword_6024A0[5];
dword_6024A0[(unsigned __int64)v9] -= dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0[5]
+ code)];// dword_6024a0[0]-=dword[3];[1];[1];[1]
break;
case 5: // code[18],code[57]
++dword_6024A0[5];
v10 = *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
++dword_6024A0[5];
dword_6024A0[(unsigned __int64)v10] *= *(_BYTE *)((unsigned int)dword_6024A0[5] + code);// dw[1]*=0x15;dw[3]*=3
break;
case 6: // code[15],code[54],code[93]
++dword_6024A0[5];
v11 = *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
++dword_6024A0[5]; // dw[1] = dw[1] >> 4;dw[3] = dw[3]>>8;dw[1]=dw[1]>>8
dword_6024A0[(unsigned __int64)v11] = (unsigned int)dword_6024A0[(unsigned __int64)v11] >> *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
break;
case 7: // code[21],code[60],code[96]
++dword_6024A0[5];
v12 = *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
++dword_6024A0[5]; // dw[0] = dw[1];dw[0] = dw[3];dw[0] = dw[1]
dword_6024A0[(unsigned __int64)v12] = dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0[5]
+ code)];
break;
case 8: // code[6],code[9],code[12],code[32],code[45],code[48],code[51],code[71],code[84],code[87],code[90],code[107]
++dword_6024A0[5];
v13 = *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
++dword_6024A0[5]; // dw_24a0[1] = dw_20a0[0+0];dw[2] = dw[1];dw[3] = dw[2];dw[1]=dw[3];dw[1]=dw[0];dw[2]=dw[1];dw[3]=dw[2];dw[1]=dw[3];dw[1]=dw[0];dw[2]=dw[1];dw[3]=dw[2];dw[1]=dw[3]
dword_6024A0[(unsigned __int64)v13] = dword_6020A0[(unsigned __int64)(dword_6024A0[7]
+ (unsigned int)*(_BYTE *)((unsigned int)dword_6024A0[5] + code))];
break;
case 9: // code[0],code[3],code[42],code[81] 清零
++dword_6024A0[5];
v14 = *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
++dword_6024A0[5]; // dw[4] ^= dw[4];dw[0]^=dw[0];dw[0]^=dw[0];dw[0]^=dw[0]
dword_6024A0[(unsigned __int64)v14] ^= dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0[5]
+ code)];
break;
case 0xA: // code[39],code[78],code[114]
++dword_6024A0[5];
v15 = *(_BYTE *)((unsigned int)dword_6024A0[5] + code);
++dword_6024A0[5]; // dw[4] |= dw[0]
dword_6024A0[(unsigned __int64)v15] |= dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0[5]
+ code)];
break;
default:
fprintf(stderr, "Invalid opcode. %d\n", *(_BYTE *)((unsigned int)dword_6024A0[5] + code));
exit(1);
return result;
}
result = (unsigned int)(dword_6024A0[5]++ + 1);
}
return result;
}
一共10个分支,进行分支跳转和运算的数据均来自code文件,稳妥起见做的时候我在switch的跳转处下了断点,追踪了一下跳转的顺序,在函数中进行了注释,然后根据跳转顺序手动(QAQ)算了一下各个分支的运算大概的结果,最后确定下来这里进行了三次运算,先是将输入的第一个数右移四位,再乘以0x15,减去第三次的输入;二是将第三个输入的数右移8位,乘以3,加第二个输入;三是将输入的第一个数右移8位,加第二个输入,三个式子分别等于case1中计算出的三个常数:0x6b + (0x1d << 24) + (0x7e << 16) + (0xcc << 8) =0x1d7ecc6b 、 0x7c + (0x60 << 24) + (0x79 << 16) + (0x79 << 8) = 0x6079797c 、 0xbd + (0x5f << 24) + (0xbc << 16) + (0xbd << 8) = 0x5fbcbdbd PS:这三个常数可以自己看代码直接计算,也可以在gdb里下断点看,下断点比较方便一点,也不易出错,我开始就是算的时候顺序没弄对算错了几次,耽误时间
有了式子,再结合main函数里后面的判断条件,就可以写解题的脚本了,我这里使用的了z3,参考看雪论坛的一个帖子:[原创] z3 巧解CTF逆向题 写的脚本,解题脚本如下:
[Python] 纯文本查看 复制代码 from z3 import *
flag = [BitVec('u%d'%i,32) for i in xrange(3)]
slover = Solver()
slover.add((flag[0] >> 4) * 0x15 - flag[2] == 0x1d7ecc6b)
slover.add((flag[2] >> 8) * 3 + flag[1] == 0x6079797c)
slover.add((flag[0] >> 8) + flag[1] == 0x5fbcbdbd)
slover.add(flag[0] & 0xff == 0x5e)
slover.add(flag[1] & 0xff0000 ==0x5e0000)
slover.add(flag[2] & 0xff == 0x5e)
slover.check()
result = ""
out = ""
if slover.check() == sat:
model = slover.model()
for i in xrange(3):
result += str(hex(model[flag[i]].as_long().real))
for i in xrange(len(result)):
if result[i] == '0' or result[i] == 'x':
continue
else:
out += result[i]
print "X-NUCA{" + out + '}'
else:
print 'false'
最后得到flag:X-NUCA{5e5f5e5e5f5e5e5f5e5e5f5e}
结语:这个代码解释器的题还是比较简单的,适合新手练习,就是需要一点耐心,顺便get一下z3的用法
原题见附件
游客通道:https://pan.baidu.com/s/1wh5lG377gwqqNaViSKS9Fg 提取码:ojlr
|