吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 16874|回复: 60
收起左侧

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

  [复制链接]
AssassinQ 发表于 2018-11-24 20:32
本帖最后由 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[sha1]=0391bf87f6f7a11b4d23e29eb39330a762aff5b4, stripped

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

[Desktop] ./WxyVM1                                                     3:25:21 
[WxyVM 0.0.1]
input your flag:
nctf{123456}
wrong

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

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char v4; // [rsp+Bh] [rbp-5h]
  signed int i; // [rsp+Ch] [rbp-4h]

  puts("[WxyVM 0.0.1]");
  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[i] )
      v4 = 0;
  }
  if ( v4 )
    puts("correct");
  else
    puts("wrong");
  return 0LL;
}

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

__int64 vm_start()
{
  unsigned int v0; // ST04_4
  __int64 result; // rax
  signed int i; // [rsp+0h] [rbp-10h]
  char v3; // [rsp+8h] [rbp-8h]

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

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

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

1.png

最后的脚本:

import re
f = open('WxyVM1.txt', 'r')
enc = [0xFFFFFFC4, 0x00000034, 0x00000022, 0xFFFFFFB1, 0xFFFFFFD3, 0x00000011, 0xFFFFFF97, 0x00000007, 0xFFFFFFDB, 0x00000037, 0xFFFFFFC4, 0x00000006, 0x0000001D, 0xFFFFFFFC, 0x0000005B, 0xFFFFFFED, 0xFFFFFF98, 0xFFFFFFDF, 0xFFFFFF94, 0xFFFFFFD8, 0xFFFFFFB3, 0xFFFFFF84, 0xFFFFFFCC, 0x00000008]
text = f.read()
f.close()
pat = re.compile(r'db.{5}')
find_pat = pat.findall(text)
nums = []
for n in find_pat:
    n = n[2:].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[index] = (enc[index] - v3) % 256
    elif v0 == 2:
        enc[index] = (enc[index] + v3) % 256
    elif v0 == 3:
        enc[index] = (enc[index] ^ v3) % 256
    elif v0 == 4:
        enc[index] = (enc[index] / v3) % 256
    elif v0 == 5:
        enc[index] = (enc[index] ^ enc[v3]) % 256

for i in range(5000):
    t = 5000 - i
    v0 = nums[3 * t - 3]
    v3 = nums[3 * t - 1]
    res = nums[3 * t - 2]
    cal(v0, v3, res)
flag = ''
for i in range(len(enc)):
    flag += chr(enc[i])
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[sha1]=e57d1a1b70ac3d843afa30523dbbbc53c4ff341f, stripped

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

[Desktop] ./WxyVM2                                                     3:25:36 
[WxyVM 0.0.2]
input your flag:
nctf{123456}
wrong

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

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char v4; // [rsp+Bh] [rbp-5h]
  signed int i; // [rsp+Ch] [rbp-4h]

  puts("[WxyVM 0.0.2]");
  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[i] )
      v4 = 0;
  }
  if ( v4 )
    puts("correct");
  else
    puts("wrong");
  return 0LL;
}

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

2.png

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

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

f = open('WxyVM2.txt', 'r')
text = f.read()
f.close()
enc = [0xFFFFFFC0, 0xFFFFFF85, 0xFFFFFFF9, 0x0000006C, 0xFFFFFFE2, 0x00000014, 0xFFFFFFBB, 0xFFFFFFE4, 0x0000000D, 0x00000059, 0x0000001C, 0x00000023, 0xFFFFFF88, 0x0000006E, 0xFFFFFF9B, 0xFFFFFFCA, 0xFFFFFFBA, 0x0000005C, 0x00000037, 0xFFFFFFFF, 0x00000048, 0xFFFFFFD8, 0x0000001F, 0xFFFFFFAB, 0xFFFFFFA5]
ori = text.split(';\n')
ops = []
for s in ori:
    if s.startswith('d'):
        continue
    elif s.startswith('b'):
        t = s[:1] + s[9:11] + s[12:14] + s[15:]
        ops.append(t)
    elif s.startswith('--'):
        t = s[2:3] + s[-2:] + '-=1'
        ops.append(t)
    elif s.startswith('++'):
        t = s[2:3] + s[-2:] + '+=1'
        ops.append(t)
    else:
        continue
ops = ops[::-1]

def getPart(op):
    index = int(op[1:3], 16)
    symbol = op[3:4]
    num = op[5:]
    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[index] = (enc[index] - num) % 256
    elif symbol == '-':
        enc[index] = (enc[index] + num) % 256
    elif symbol == '^':
        enc[index] = (enc[index] ^ 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[i])
print flag

免费评分

参与人数 12吾爱币 +9 热心值 +12 收起 理由
瑟瑟发抖小菜虾 + 1 + 1 我很赞同!
springkang + 1 + 1 用心讨论,共获提升!
coralzyzy + 1 + 1 热心回复!
菜鸟也想飞 + 1 + 1 谢谢@Thanks!
tyutbigdata1839 + 1 + 1 谢谢@Thanks!
lzhjlx + 1 谢谢@Thanks!
uto + 2 热心回复!
key81 + 1 我很赞同!
szjzxm4321 + 1 + 1 我很赞同!
q739806133 + 1 + 1 我很赞同!
wa774779 + 1 + 1 我很赞同!
lylzy + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

nedzq 发表于 2018-11-25 17:34
大佬,看不懂
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
谢谢分享了。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 15:52

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表