【新手】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
``` 大佬,看不懂{:1_925:} 谢谢分享宝贵经验 膜拜大佬 感谢分享 謝謝分享
謝謝分享 谢谢分享 谢谢楼主分享 谢谢分享了。