RCTF 2018 Simple vm

155 阅读12分钟

参考 expend20.github.io/2018/05/24/… www.freebuf.com/column/1746…

打开bin看字符串,字符串没被加密 在这里插入图片描述 ida通过Imports通过fopen交叉引用到main

程序流程

fseek,ftell读到文件大小,将文件内容写入malloc的空间里,然后进入虚拟机

RECOMPILE

将ida伪代码转化为c,有几点要注意的:

  • v1类型byte *,*(_DWORD *)&v1[v2];先v1[v2]取第二个byte 然后&取地址 然后*(_DWORD *)取出的一个int
  • v2为下一位索引(v0+1),v0为当前位索引(eip),v1是data
  • ida宏定义,__int64这里定义为unsigned int,因为参与运算的变量最大为int,没必要long long
  • 把main简单定义为int main()
  • ++,--注意回显的位置
  • 有分支的opcde要标识出来,这些opcode又分为循环opcode和条件opcode
  • printf按照 opcode eip变化 最终值 = 中间过程 = 值 来写
  • 最终值 = 中间过程 = 值 如c = v1[%d] = %d ;c = ~(dword_6010A4 & c) = ~(%d & %d) = %d

按照以上要求写的话程序就比较清晰了

#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include "conio.h"

#define _BYTE unsigned char
#define _DWORD unsigned int
#define __int64 unsigned int

__int64 sub_400896(_BYTE *ptr,int filesize)
{
  __int64 v0; // rax
  _BYTE *v1; // rbp
  int v2; // ebx
  int v4; // rdx
  int v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  int v10; // eax
  __int64 v11; // rax
  char v12; // dl
  int v13; // eax
  int v14; // eax
  _BYTE *v15; // rax
  __int64 v16; // rax
  __int64 v17; // rax
  __int64 v18; // rax
  _DWORD dword_6010A4;
  _DWORD c;

  v0 = 0LL;
  v1 = ptr;
  while ( 1 )
  {
    v2 = v0 + 1;		//v2下一位的索引
    switch ( v1[v0] )	//v0当前位的索引(eip)
    {                                           // 读取到的opcode
      case 0uLL:
		printf("0x00	返回,返回值为%d\n",*(unsigned int *)&v1[v2]);
        return *(unsigned int *)&v1[v2];
      case 1uLL:
		printf("0x01	eip = %d\n",*(_DWORD *)&v1[v2]);
        goto LABEL_35;
      case 2uLL:
		printf("0x02	eip += 9 v1[%d] = v1[%d] = %d\n",*(int *)&v1[v2],v0+5,*(_DWORD *)&v1[(int)v0 + 5]);
        v4 = v2;
        v2 = v0 + 9;
        v1[*(int *)&v1[v4]] = *(_DWORD *)&v1[(int)v0 + 5];//v1[下一位的值] = v1[当前值的索引+5]
        break;
      case 3uLL:
		printf("0x03	eip += 5 c = v1[%d] = %d\n",*(int *)&v1[v2],v1[*(int *)&v1[v2]]);
        v5 = v2;
        v2 += 4;//v2为当前开始第五位的索引
        v6 = *(int *)&v1[v5];//下一位的值
        goto LABEL_27;
      case 4uLL:
		printf("0x04	eip += 5  v1[%d] = c = %d\n",*(int *)&v1[v2],c);
        v7 = v2;
        v2 += 4;
        v8 = *(int *)&v1[v7];
        goto LABEL_31;
      case 5uLL:
		printf("0x05	eip += 5 dword_6010A4 = v1[%d] = %d\n",*(int *)&v1[v2],(char)v1[*(int *)&v1[v2]]);
        v9 = v2;
        v2 += 4;
        v10 = (char)v1[*(int *)&v1[v9]];
        goto LABEL_21;
      case 6uLL:
		printf("0x06	eip += 5 v1[%d] = %d\n",*(int *)&v1[v2],dword_6010A4);
        v11 = v2;
        v12 = dword_6010A4;
        v2 += 4;
        v8 = *(int *)&v1[v11];
        goto LABEL_9;
      case 7uLL:
		printf("0x07	eip++  c += %d = %d\n",dword_6010A4,c+dword_6010A4);
        v13 = dword_6010A4;
        goto LABEL_23;
      case 8uLL:
		printf("0x08	eip++  c = ~(dword_6010A4 & c) = ~(%d & %d) = %d\n",dword_6010A4,c,~(dword_6010A4 & c));
        v14 = ~(dword_6010A4 & c);
        goto LABEL_12;
      case 0xAuLL:
		printf("输入开始\n");
		v14 = _getch();        
		printf("0x0A	eip++  c = 输入字符的askii %d\n",v14);
        goto LABEL_12;
      case 0xBuLL:
		printf("0x0B	eip++  输出 c = ");
        putchar(c);
		printf("\n");
        break;
      case 0xCuLL:
		printf("0xC分支开始\n");
        v15 = &v1[*(int *)&v1[v2]];
        if ( *v15 )
        {
          v2 = *(_DWORD *)&v1[v2 + 4];
          printf("0x0C	分支一v1[%d]不等于0 eip = %d v1[%d]-- = %d\n",*(int *)&v1[v2],*(_DWORD *)&v1[v2 + 4],*(int *)&v1[v2],--*v15);
        }
        else
        {
		  printf("0x0C	分支二v1[%d]等于0 eip += 9\n",*(int *)&v1[v2]);
          v2 += 8;
        }
        break;
      case 0xDuLL:
        ++c;
		printf("0x0D	eip++ c++ = %d\n",c);
        break;
      case 0xEuLL:
		printf("0x0E	eip++ dword_6010A4++ = %d\n",++dword_6010A4);
        break;
      case 0xFuLL:
		printf("0x0F	eip++ c = dword_6010A4 = %d\n",dword_6010A4);
        v14 = dword_6010A4;
        goto LABEL_12;
      case 0x10uLL:
		printf("0x10	eip++ dword_6010A4 = c = %d\n",c);
        v10 = c;
        goto LABEL_21;
      case 0x11uLL:
		printf("0x11	eip += 5 c += v1[%d] = %d\n",v2,c+*(_DWORD *)&v1[v2]);
        v16 = v2;
        v2 += 4;
        v13 = *(_DWORD *)&v1[v16];
LABEL_23:
        c += v13;
        break;
      case 0x12uLL:
		printf("0x12	eip++ c = v1[%d] = %d\n",dword_6010A4,(char)v1[dword_6010A4]);
        v6 = dword_6010A4;
        goto LABEL_27;
      case 0x13uLL:
		printf("0x13	eip++ c = v1[%d] = %d\n",c,(char)v1[c]);
        v6 = c;
LABEL_27:
        v14 = (char)v1[v6];//v1[下一位的值]
        goto LABEL_12;
      case 0x14uLL:
		printf("0x14	eip += 5 c = v1[%d] = %d\n",v2,*(_DWORD *)&v1[v2]);
        v17 = v2;
        v2 += 4;
        v14 = *(_DWORD *)&v1[v17];
        goto LABEL_12;
      case 0x15uLL:
		printf("0x15	eip += 5 dword_6010A4 = v1[%d] = %d\n",v2,*(_DWORD *)&v1[v2]);
        v18 = v2;
        v2 += 4;
        v10 = *(_DWORD *)&v1[v18];
LABEL_21:
        dword_6010A4 = v10;
        break;
      case 0x16uLL:
		printf("0x16	eip++ v1[%d] = c = %d\n",dword_6010A4,c);
        v8 = dword_6010A4;
LABEL_31:
        v12 = c;
LABEL_9:
        v1[v8] = v12;
        break;
      case 0x17uLL:
		printf("0x17	eip++ c = c - dword_6010A4 = %d\n",c - dword_6010A4);
        v14 = c - dword_6010A4;
LABEL_12:
        c = v14;
        break;
      case 0x18uLL:
		printf("0x18分支开始 c为%d\n",c);
		c = 0;
        if ( c ){
LABEL_35:
		  printf("0x18	分支一c不为零 eip = v1[%d] = %d\n",v2,*(_DWORD *)&v1[v2]);
          v2 = *(_DWORD *)&v1[v2];
		}
        else{
		  printf("0x18 分支二c为0 eip += 5\n");
          v2 = v0 + 5;
		}
        break;
      default:
		printf("DEFAULT eip++\n");
        break;
    }
    if ( v2 >= filesize )
      return 0LL;
    v0 = v2;
  }
}

int main()
{
  FILE *v3; // rax
  const char *v4; // rdi
  FILE *v5; // rbx
  errno_t err;

  err = fopen_s(&v3, "C:\\Users\\lenovo\\Desktop\\fuxian\\simplevm\\p.bin", "rb");
  if(err == 0) {
        printf("The file was opened\n");
  }
  else {
        printf("The file was not opened\n");
  }
  v4 = "err 0";
  if ( !v3 )
    goto LABEL_4;
  v5 = v3;
  fseek(v3, 0LL, 2);                            // 文件结尾
  int filesize = ftell(v5);                     // 得到文件大小
  fseek(v5, 0LL, 0);                            // 文件开头
  if ( filesize <= 0 )
  {
    v4 = "err 1";
LABEL_4:
    puts(v4);
    return 0xFFFFFFFF;
  }
  _BYTE *ptr = (_BYTE *)malloc(filesize);
  v4 = "err 3";
  if ( !ptr )
    goto LABEL_4;
  v4 = "err 4";
  if ( filesize != fread(ptr, 1uLL, filesize, v5) )
    goto LABEL_4;
  fclose(v5);
  v4 = "err 5";
  if ( (unsigned int)sub_400896(ptr,filesize) )             // 函数必返回0
    goto LABEL_4;
  free(ptr);
  return 0LL;
}

回显信息

The file was opened
0x01    eip = 48
0x18    分支一c不为零 eip = v1[1] = 48
0x15    eip += 5 dword_6010A4 = v1[49] = 256
0x0E    eip++ dword_6010A4++ = 257
0x12    eip++ c = v1[257] = 73
0x0B    eip++  输出 c = I
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 256 v1[202052110]-- = 9
0x0E    eip++ dword_6010A4++ = 258
0x12    eip++ c = v1[258] = 110
0x0B    eip++  输出 c = n
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 256 v1[202052110]-- = 8
0x0E    eip++ dword_6010A4++ = 259
0x12    eip++ c = v1[259] = 112
0x0B    eip++  输出 c = p
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 256 v1[202052110]-- = 7
0x0E    eip++ dword_6010A4++ = 260
0x12    eip++ c = v1[260] = 117
0x0B    eip++  输出 c = u
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 256 v1[202052110]-- = 6
0x0E    eip++ dword_6010A4++ = 261
0x12    eip++ c = v1[261] = 116
0x0B    eip++  输出 c = t

由这可推出,想要回显信息,流程为

index = 256
index++
c = v1[index] 
输出c
for i in range(10-0) 
   	 	i--
   	 	index++ 
   	 	c = v1[index] 
   	 	输出c

保存输入

//输入abc
输入开始
0x0A    eip++  c = 输入字符的askii 97
DEFAULT eip++
0x16    eip++ v1[273] = c = 97
0xC分支开始
0x0C    分支一v1[375785998]不等于0 eip = 69644 v1[375785998]-- = 30
0x0E    eip++ dword_6010A4++ = 274
输入开始
0x0A    eip++  c = 输入字符的askii 98
DEFAULT eip++
0x16    eip++ v1[274] = c = 98
0xC分支开始
0x0C    分支一v1[375785998]不等于0 eip = 69644 v1[375785998]-- = 29
0x0E    eip++ dword_6010A4++ = 275
输入开始
0x0A    eip++  c = 输入字符的askii 99
DEFAULT eip++
0x16    eip++ v1[275] = c = 99
0xC分支开始
0x0C    分支一v1[375785998]不等于0 eip = 69644 v1[375785998]-- = 28
0x0E    eip++ dword_6010A4++ = 276

由v1[375785998]推出输入长度为32,将输入存到了v1[273]开始的空间 大致流程为

index = 272
index++
for i in range(31-0)
    	v1[index] = 输入
    	i--
    	index++

之后流程

循环做了些事,不好分析,去看末尾回显那有没有关键点

来到末尾回显wrong这

DEFAULT eip++
0x03    eip += 5 c = v1[320] = 63
0x11    eip += 5 c += v1[150] = 304
0x10    eip++ dword_6010A4 = c = 304
0x03    eip += 5 c = v1[324] = 90
0x16    eip++ v1[304] = c = 90
0x05    eip += 5 dword_6010A4 = v1[320] = 63
0x0E    eip++ dword_6010A4++ = 64
0x06    eip += 5 v1[320] = 64
0xC分支开始
0x0C    分支二v1[325]等于0 eip += 9
DEFAULT eip++
0x03    eip += 5 c = v1[326] = 31
0x11    eip += 5 c += v1[188] = 36
0x13    eip++ c = v1[36] = 9
0x10    eip++ dword_6010A4 = c = 9
0x03    eip += 5 c = v1[326] = 31
0x11    eip += 5 c += v1[200] = 304
0x13    eip++ c = v1[304] = 90
0x17    eip++ c = c - dword_6010A4 = 81
0x18分支开始
0x18    分支一c不为零 eip = v1[207] = 352
0x15    eip += 5 dword_6010A4 = v1[353] = 336
0x0E    eip++ dword_6010A4++ = 337
0x12    eip++ c = v1[337] = 87
0x0B    eip++  输出 c = W
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 336 v1[202052110]-- = 4
0x0E    eip++ dword_6010A4++ = 338
0x12    eip++ c = v1[338] = 114
0x0B    eip++  输出 c = r
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 336 v1[202052110]-- = 3
0x0E    eip++ dword_6010A4++ = 339
0x12    eip++ c = v1[339] = 111
0x0B    eip++  输出 c = o
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 336 v1[202052110]-- = 2
0x0E    eip++ dword_6010A4++ = 340
0x12    eip++ c = v1[340] = 110
0x0B    eip++  输出 c = n
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 336 v1[202052110]-- = 1
0x0E    eip++ dword_6010A4++ = 341
0x12    eip++ c = v1[341] = 103
0x0B    eip++  输出 c = g
0xC分支开始
0x0C    分支一v1[202052110]不等于0 eip = 336 v1[202052110]-- = 0
0x0E    eip++ dword_6010A4++ = 342
0x12    eip++ c = v1[342] = 10
0x0B    eip++  输出 c =

0xC分支开始
0x0C    分支二v1[336]等于0 eip += 9
0x00    返回,返回值为0

注意到0x18分支,0xC是loop opcode,0x18是conditional opcode(意味着只有这里能check) 猜测是不是c=0输出,源码改这里c=0,会发现显示Right 在这里插入图片描述 But our goal is to find that input flag. So we need to understand how ‘c’ variable depends on input flag. 源码在0x18分支处下断,发现每次输入都有一次0x18,且要为0,回溯c,记住输入的地方为273开始到304 输入a-z1-6共32位 0xC分支开始 0x0C 分支二v1[325]等于0 eip += 9 DEFAULT eip++ 0x03 eip += 5 c = v1[326] = 31 0x11 eip += 5 c += v1[188] = 36 0x13 eip++ c = v1[36] = 9 0x10 eip++ dword_6010A4 = c = 9 0x03 eip += 5 c = v1[326] = 31 0x11 eip += 5 c += v1[200] = 304 0x13 eip++ c = v1[304] = 9这次是最后一位输入 0x17 eip++ c = c - dword_6010A4 = 0 0x18分支开始 c为0

0xC分支开始 0x0C 分支一v1[83459]不等于0 eip = 332032 v1[83459]-- = 30 0x03 eip += 5 c = v1[326] = 30 0x11 eip += 5 c += v1[188] = 35 0x13 eip++ c = v1[35] = 88 0x10 eip++ dword_6010A4 = c = 88 0x03 eip += 5 c = v1[326] = 30 0x11 eip += 5 c += v1[200] = 303 0x13 eip++ c = v1[303] = 11 0x17 eip++ c = c - dword_6010A4 = -77 0x18分支开始 c为-77

多次调试后可知最后要比对的值v1[5]到v1[36] [0x10,0x18,0x43,0x14,0x15,0x47,0x40,0x17,0x10,0x1d,0x4b,0x12,0x1f,0x49,0x48,0x18,0x53,0x54,0x1,0x57,0x51,0x53,0x5,0x56,0x5a,0x8,0x58,0x5f,0xa,0xc,0x58,0x9]

追踪v1[304,找与输入有关的 在这里插入图片描述 分析后大致进行了如下过程

def f(i,j):
    return ~(i & j)

for i in range(len(s)):#0-31
    res = f(f(f(i+32,ord(s[i])),i+32),f(f(i+32,ord(s[i])),ord(s[i])))

解题脚本

def f(i,j):

    return ~(i & j)


res = [0x10,0x18,0x43,0x14,0x15,0x47,0x40,0x17,0x10,0x1d,0x4b,0x12,0x1f,0x49,0x48,\

    0x18,0x53,0x54,0x1,0x57,0x51,0x53,0x5,0x56,0x5a,0x8,0x58,0x5f,0xa,0xc,0x58,0x9]


import z3

s = z3.Solver()

x = [z3.BitVec("x%d"%i,8) for i in range(32)]

for i in range(len(x)):

    s.add(31<x[i])

    s.add(x[i]<127)

    
for i in range(len(res)):#0-31

    s.add( f(f(f(i+32,x[i]),i+32),f(f(i+32,x[i]),x[i])) == res[i] )


flag = ''

if s.check() == z3.sat:

    m = s.model()

    for i in range(len(x)):

        flag += chr(m[x[i]].as_long())

print(flag) #09a71bf084a93df7ce3def3ab1bd61f6

在这里插入图片描述 在这里插入图片描述

还有另一种解法是逐位爆破

import subprocess, re


def checkIdx(s):

	p1 = subprocess.Popen(["ConsoleApplication2.exe"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)

	p1.stdin.write(s)
	p1.stdin.close()


	res = p1.stdout.read()
	print(res)

	r = re.search('\[(\d+)\]', res)

	return int(r.group(1))


tail = ''
head = '0000000000000000000000000000000'


idx = 1
while idx < 33:

	for i in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789':


		s = head + i + tail

		print('try %s...' % s)

		r = checkIdx(s)

		if idx != r:

		 	print("found: %s (idx = %d, r = %d)" % (s, idx, r))
		 	idx += 1
		 	
		 	head = head[:-1]
		 	tail = i + tail
		 	break
		 	
# Input Flag:09a71bf084a93df7ce3def3ab1bd61f6
# Right