AssassinQ 发表于 2018-11-24 20:32

【新手】CTF逆向-简单虚拟机指令类题目分析

本帖最后由 AssassinQ 于 2020-2-11 19:37 编辑

# 南邮平台-两道VM-writeup

刚接触逆向的时候做过一两道虚拟机指令的题,记得有一道是RCTF的magic,当时还跟着无名大佬的wp做了很久。但是那个时候对VM的理解不是很深,然后这两天看到南邮的两道题,仔细做了一下,发现很适合入门VM这类型的题目,所以记录一下。都是很简单基础的题目,求大佬轻喷。如果有什么不对的地方,也欢迎大家指出。

相关文件网盘链接:链接:https://pan.baidu.com/s/12plhFY7qR7emktie9BUlcQ密码:ovwc

## WxyVM1

拿到文件先file一下:

```
AssassinQ@MacBook-Air  ~/Downloads  file WxyVM1
WxyVM1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID=0391bf87f6f7a11b4d23e29eb39330a762aff5b4, stripped
```

然后拿到虚拟机下运行一下看看什么样:

```
./WxyVM1                                                   3:25:21

input your flag:
nctf{123456}
wrong
```

没看出啥东西,基本判断就是程序应该是一个对flag的加密。然后拖进ida里分析:

```c
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char v4; //
signed int i; //

puts("");
puts("input your flag:");
scanf("%s", &input);
v4 = 1;
vm_start();
if ( strlen(&input) != 24 )
    v4 = 0;
for ( i = 0; i <= 23; ++i )
{
    if ( *(&input + i) != enc )
      v4 = 0;
}
if ( v4 )
    puts("correct");
else
    puts("wrong");
return 0LL;
}
```

main函数中输入一个flag,然后一个vm加密函数,再将加密过后的flag与存放在data段中的enc比较,如果相等那么输出`correct`。所以基本思路应该是通过enc逆出flag。然后进到`vm_start`函数中看看:

```c
__int64 vm_start()
{
unsigned int v0; // ST04_4
__int64 result; // rax
signed int i; //
char v3; //

for ( i = 0; i <= 14999; i += 3 )
{
    v0 = byte_6010C0;
    v3 = byte_6010C0;
    result = v0;
    switch ( v0 )
    {
      case 1u:
      result = byte_6010C0;
      *(&input + result) += v3;
      break;
      case 2u:
      result = byte_6010C0;
      *(&input + result) -= v3;
      break;
      case 3u:
      result = byte_6010C0;
      *(&input + result) ^= v3;
      break;
      case 4u:
      result = byte_6010C0;
      *(&input + result) *= v3;
      break;
      case 5u:
      result = byte_6010C0;
      *(&input + result) ^= *(&input + byte_6010C0);
      break;
      default:
      continue;
    }
}
return result;
}
```

这里发现在data段中还有一个byte数组。总共有15000个数,每三个数一组。第一个数作为需要执行的指令,第二个数为输入flag的下标,第三个数为与其进行操作的数据。到这里基本已经清楚了,把数据都dump下来,写个脚本逆一下就ok了。然后还需要注意的是,这里的运算是以byte为单位,可能会产生溢出,所以应该每次操作之后模一下256。

看到网上大多数wp都是用idc脚本patch,因为数据确实太多了,连lazyida都dump不出来。我是选择手动复制出来所有的数据,然后再用python正则匹配一下,提取出来。



最后的脚本:

```python
import re
f = open('WxyVM1.txt', 'r')
enc =
text = f.read()
f.close()
pat = re.compile(r'db.{5}')
find_pat = pat.findall(text)
nums = []
for n in find_pat:
    n = n.strip()
    if n.endswith('h'):
      n = int(n[:-1], 16)
    else:
      n = int(n)
    nums.append(n)

def cal(v0, v3, index):
    if v0 == 1:
      enc = (enc - v3) % 256
    elif v0 == 2:
      enc = (enc + v3) % 256
    elif v0 == 3:
      enc = (enc ^ v3) % 256
    elif v0 == 4:
      enc = (enc / v3) % 256
    elif v0 == 5:
      enc = (enc ^ enc) % 256

for i in range(5000):
    t = 5000 - i
    v0 = nums
    v3 = nums
    res = nums
    cal(v0, v3, res)
flag = ''
for i in range(len(enc)):
    flag += chr(enc)
print flag
```

## WxyVM2

file一下:

```
AssassinQ@MacBook-Air  ~/Downloads  file WxyVM2
WxyVM2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID=e57d1a1b70ac3d843afa30523dbbbc53c4ff341f, stripped
```

运行一下,发现和上一题应该基本是同一个类型:

```
./WxyVM2                                                   3:25:36

input your flag:
nctf{123456}
wrong
```

然后拖进ida里,只有一个main函数。f5反编译发现提示说函数太大,无法反编译。这个时候需要先修改一下ida的配置文件`hexrays.cfg`,具体操作可以百度或者谷歌一下。修改完后看一下main函数的情况:

```c
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char v4; //
signed int i; //

puts("");
puts("input your flag:");
scanf("%s", &input);
v4 = 1;
if ( strlen(&input) != 25 )
    v4 = 0;
[......]
for ( i = 0; i <= 24; ++i )
{
    if ( *(&input + i) != enc )
      v4 = 0;
}
if ( v4 )
    puts("correct");
else
    puts("wrong");
return 0LL;
}
```

头和尾是基本一样的,主要是中间的部分,是一段又臭又长的对数据的加密:



我们输入的input应该都是byte,而这么多dword的操作其实都是对加密部分的混淆。然后这里的话我是把main函数提出来,然后筛选出byte开头的语句,并且通过一系列切片简化语句。然后把数据段里被加密的flag即enc数组dump出来,将提取出来的语句进行逆向的实现,就能输出flag。

其他的一些注意实现和前一题一样。最后的实现脚本:

```python
f = open('WxyVM2.txt', 'r')
text = f.read()
f.close()
enc =
ori = text.split(';\n')
ops = []
for s in ori:
    if s.startswith('d'):
      continue
    elif s.startswith('b'):
      t = s[:1] + s + s + s
      ops.append(t)
    elif s.startswith('--'):
      t = s + s[-2:] + '-=1'
      ops.append(t)
    elif s.startswith('++'):
      t = s + s[-2:] + '+=1'
      ops.append(t)
    else:
      continue
ops = ops[::-1]

def getPart(op):
    index = int(op, 16)
    symbol = op
    num = op
    if num.endswith('u'):
      num = num[:-1]
    if num.startswith('0x'):
      num = int(num, 16)
    else:
      num = int(num)
    return index, symbol, num

def cal(index, symbol, num):
    if symbol == '+':
      enc = (enc - num) % 256
    elif symbol == '-':
      enc = (enc + num) % 256
    elif symbol == '^':
      enc = (enc ^ num) % 256
    else:
      print 'error'

for op in ops:
    index, symbol, num = getPart(op)
    # print 'enc[', index, ']', symbol, num
    cal(index, symbol, num)

flag = ''
for i in range(len(enc)):
    flag += chr(enc)
print flag
```

nedzq 发表于 2018-11-25 17:34

大佬,看不懂{:1_925:}

wcjcn 发表于 2018-11-25 20:12

谢谢分享宝贵经验

Airs_Lau 发表于 2018-11-25 20:14

膜拜大佬

成都 发表于 2018-11-25 20:34

感谢分享

linsnow7272 发表于 2018-11-25 20:53

謝謝分享

q82237677 发表于 2018-11-25 20:55


謝謝分享

ArcherMars 发表于 2018-11-25 22:05

谢谢分享

331231443 发表于 2018-11-25 22:52

谢谢楼主分享

zjlzhok 发表于 2018-11-26 08:56

谢谢分享了。
页: [1] 2 3 4 5 6 7
查看完整版本: 【新手】CTF逆向-简单虚拟机指令类题目分析