chkds 发表于 2018-11-26 15:29

【新手】一道简单的代码解释器逆向题分析

本帖最后由 chkds 于 2018-11-26 15:30 编辑

XNUCA2018Code_Interpreter
作为一只菜鸡,第一次搞定一个解释器的题目,决定记录一下:lol
工具:gdb、ida pro 6.8、DIE、010editor
解压后发现有两个文件,一个code,一个Code_Interpreter,首先把Code_Interpreter拖进DIE,确定是64位程序,无壳

拖进IDA进行分析,main函数如下

main函数逻辑很简单,以 rb 的方式读取了一个文件中的数据,键盘输入三个十进制格式的数字,能通过验证就会将输入按十六进制格式组合输出作为flag,重点就在Interpreter那个函数中
根据函数的调用可以看出Interpreter函数所用的参数是来自文件(code)中的数据,反汇编出的Interpreter函数如下:
__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; // @1

dword_6020A0 = dword_6024A0;         // first
dword_6020A4 = dword_6024A0;               // second
result = (unsigned int)dword_6024A0;
dword_6020A8 = dword_6024A0;            // third
dword_6024A0 = 0;
dword_6024A0 = 2;
dword_6024A0 = 0;
v16 = 1;
while ( v16 )
{
    switch ( *(_BYTE *)((unsigned int)dword_6024A0 + code) )
    {
      case 0:
      v16 = 0;
      break;
      case 1:                                 // code,code,code
      v2 = ++dword_6024A0;
      ++dword_6024A0;
      v3 = *(_BYTE *)((unsigned int)v2 + code);//0x6b
      v4 = dword_6024A0++;
      v5 = (*(_BYTE *)((unsigned int)v4 + code) << 8) + v3;//0xcc << 8
      v6 = dword_6024A0++;
      v7 = (*(_BYTE *)((unsigned int)dword_6024A0 + code) << 24) + (*(_BYTE *)((unsigned int)v6 + code) << 16) + v5;//0x7e << 16 + 0x1d << 24
      ++dword_6024A0;
      dword_6020A0[(unsigned __int64)(unsigned int)dword_6024A0] = v7;
      break;
      case 2:                                 // code,code,code
      --dword_6024A0;
      break;
      case 3:                                 // code,code
      ++dword_6024A0;
      v8 = *(_BYTE *)((unsigned int)dword_6024A0 + code);
      ++dword_6024A0;
      dword_6024A0[(unsigned __int64)v8] += dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0
                                                                                    + code)];// dword_6024a0=dword_6024a0;dword_6024a0
      break;
      case 4:                                 // code,code,code,code
      ++dword_6024A0;
      v9 = *(_BYTE *)((unsigned int)dword_6024A0 + code);
      ++dword_6024A0;
      dword_6024A0[(unsigned __int64)v9] -= dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0
                                                                                    + code)];// dword_6024a0-=dword;;;
      break;
      case 5:                                 // code,code
      ++dword_6024A0;
      v10 = *(_BYTE *)((unsigned int)dword_6024A0 + code);
      ++dword_6024A0;
      dword_6024A0[(unsigned __int64)v10] *= *(_BYTE *)((unsigned int)dword_6024A0 + code);// dw*=0x15;dw*=3
      break;
      case 6:                                 // code,code,code
      ++dword_6024A0;
      v11 = *(_BYTE *)((unsigned int)dword_6024A0 + code);
      ++dword_6024A0;                      // dw = dw >> 4;dw = dw>>8;dw=dw>>8
      dword_6024A0[(unsigned __int64)v11] = (unsigned int)dword_6024A0[(unsigned __int64)v11] >> *(_BYTE *)((unsigned int)dword_6024A0 + code);
      break;
      case 7:                                 // code,code,code
      ++dword_6024A0;
      v12 = *(_BYTE *)((unsigned int)dword_6024A0 + code);
      ++dword_6024A0;                      // dw = dw;dw = dw;dw = dw
      dword_6024A0[(unsigned __int64)v12] = dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0
                                                                                    + code)];
      break;
      case 8:                                 // code,code,code,code,code,code,code,code,code,code,code,code
      ++dword_6024A0;
      v13 = *(_BYTE *)((unsigned int)dword_6024A0 + code);
      ++dword_6024A0;                      // dw_24a0 = dw_20a0;dw = dw;dw = dw;dw=dw;dw=dw;dw=dw;dw=dw;dw=dw;dw=dw;dw=dw;dw=dw;dw=dw
      dword_6024A0[(unsigned __int64)v13] = dword_6020A0[(unsigned __int64)(dword_6024A0
                                                                            + (unsigned int)*(_BYTE *)((unsigned int)dword_6024A0 + code))];
      break;
      case 9:                                 // code,code,code,code 清零
      ++dword_6024A0;
      v14 = *(_BYTE *)((unsigned int)dword_6024A0 + code);
      ++dword_6024A0;                      // dw ^= dw;dw^=dw;dw^=dw;dw^=dw
      dword_6024A0[(unsigned __int64)v14] ^= dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0
                                                                                       + code)];
      break;
      case 0xA:                                 // code,code,code
      ++dword_6024A0;
      v15 = *(_BYTE *)((unsigned int)dword_6024A0 + code);
      ++dword_6024A0;                      // dw |= dw
      dword_6024A0[(unsigned __int64)v15] |= dword_6024A0[(unsigned __int64)*(_BYTE *)((unsigned int)dword_6024A0
                                                                                       + code)];
      break;
      default:
      fprintf(stderr, "Invalid opcode. %d\n", *(_BYTE *)((unsigned int)dword_6024A0 + code));
      exit(1);
      return result;
    }
    result = (unsigned int)(dword_6024A0++ + 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) = 0x5fbcbdbdPS:这三个常数可以自己看代码直接计算,也可以在gdb里下断点看,下断点比较方便一点,也不易出错,我开始就是算的时候顺序没弄对算错了几次,耽误时间:'(weeqw
有了式子,再结合main函数里后面的判断条件,就可以写解题的脚本了,我这里使用的了z3,参考看雪论坛的一个帖子:[原创] z3 巧解CTF逆向题 写的脚本,解题脚本如下:
from z3 import *

flag =
slover = Solver()
slover.add((flag >> 4) * 0x15 - flag== 0x1d7ecc6b)
slover.add((flag >> 8) * 3 + flag == 0x6079797c)
slover.add((flag >> 8) + flag == 0x5fbcbdbd)
slover.add(flag & 0xff == 0x5e)
slover.add(flag & 0xff0000 ==0x5e0000)
slover.add(flag & 0xff == 0x5e)
slover.check()
result = ""
out = ""
if slover.check() == sat:
    model = slover.model()
    for i in xrange(3):
      result += str(hex(model].as_long().real))
    for i in xrange(len(result)):
      if result == '0' or result == 'x':
            continue
      else:
            out += result
    print "X-NUCA{" + out + '}'
else:
    print 'false'
最后得到flag:X-NUCA{5e5f5e5e5f5e5e5f5e5e5f5e}

结语:这个代码解释器的题还是比较简单的,适合新手练习,就是需要一点耐心,顺便get一下z3的用法:victory:
原题见附件
游客通道:https://pan.baidu.com/s/1wh5lG377gwqqNaViSKS9Fg 提取码:ojlr

chkds 发表于 2018-12-5 22:07

金野喵君 发表于 2018-11-30 15:40
应该分享下你的对虚拟机分析的结果吧,比如反汇编后的内容。

每个分支的功能和调用有写在IDA那个页面的注释里的

zmr9988 发表于 2018-12-2 14:51

厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害厉害

陌小全 发表于 2018-11-26 15:41

感谢楼主分享

Overcoder 发表于 2018-11-26 15:43

有理有据,感谢大佬的分享!

TuziF 发表于 2018-11-26 15:50

大佬牛啊!很详细

1360423 发表于 2018-11-26 22:23

小白,表示看不大懂

killer0 发表于 2018-11-26 23:08

小白路过,凑热闹

大鱼爱吃猫 发表于 2018-11-26 23:22

好深奥的样子,学习啦

huazai996 发表于 2018-11-27 00:08

一脸的兴奋的进来了, 一脸的懵逼出去了..

buxiaoquan 发表于 2018-11-27 05:38

先从模仿开始,书抄十遍,其意自见!

Hungarian 发表于 2018-11-27 10:07

先学习一下
页: [1] 2 3 4
查看完整版本: 【新手】一道简单的代码解释器逆向题分析