重做De1CTF2020的Flw

初识虚拟机

当时第一次做这个题目的时候不懂这个奇怪的虚拟机,现在重新捞出来重做了一遍
首先使用普通的IDA7.5 x32会打不开,卡在打开的界面,具体原因可能是某个花指令导致的让IDA控制流分析的时候爆炸?。可以先使用IDA7.5 x64查看下汇编代码,尝试Patch掉一些不正常的代码。
首先是main函数下的经典假跳转,然后就是跳转到指令内部的花指令
发现这个跳转有说法,其实就是内部有inc和dec两个指令构成,非常的巧妙

所以直接Patch掉这一整句jmp指令即可
修复所有这样的花指令之后发现还是无法IDA打开,发现这个函数很奇怪,附近有无法分析的代码

发现就是通过call自己这个小trick来进行跳转,直接patch一下就可,发现可以IDAx32打开了,发现main函数里使用了SEH,用除零异常触发,而逻辑就放在SEH处理函数里面,这导致了IDA F5难以看到,我们直接Patch main函数逻辑,让它直接跳转到SEH处理函数中,就可以F5了。

发现逻辑十分简单,主要是有一个VM函数,我们来分析VM函数

分析虚拟机

大概可以整理一下IDA代码,可以发现这个虚拟机结构体有mem数组还有一个arr数组,然后由ptr1和ptr2来从这两个数组中取数,具体的计算操作发生在arr,还有一个reg的short变量可能用来暂存2字节的数据。

然后似乎这个虚拟机也不是普通的栈结构,这个arr似乎就是个会轮回的数组,和栈似乎没什么关系,具体的计算方式都是输入数据,然后取出开头两位计算之后放入最后一位,然后指针ptr2向前进2位,ptr1进一位,所以ptr2应该是这个数组的头指针,ptr1应该是尾指针。
然后不难想到这个arr数组应该就是用来提供寄存操作,真正算完的结果都得放回mem数组,然后所以这个arr数组应该计算完之后经常保持ptr1==ptr2,不然就很难看。写出解析器,然后开始硬看

char VM_Print(VMState *a1)
{
  char result; // al
  int v2; // [esp+Ch] [ebp-13Ch]
  int v3; // [esp+Ch] [ebp-13Ch]
  int v4; // [esp+Ch] [ebp-13Ch]
  int v5; // [esp+Ch] [ebp-13Ch]
  int v6; // [esp+Ch] [ebp-13Ch]
  int v7; // [esp+Ch] [ebp-13Ch]
  int v8; // [esp+Ch] [ebp-13Ch]
  int v9; // [esp+Ch] [ebp-13Ch]
  size_t i; // [esp+E0h] [ebp-68h]
  unsigned __int8 v13; // [esp+FBh] [ebp-4Dh]
  unsigned __int8 v14; // [esp+107h] [ebp-41h]
  unsigned __int8 v15; // [esp+113h] [ebp-35h]
  unsigned __int8 v16; // [esp+11Fh] [ebp-29h]
  unsigned __int8 v17; // [esp+12Bh] [ebp-1Dh]
  int pc; // [esp+134h] [ebp-14h]

  pc = 0;
  int xx=0;
  while ( true )
  {
    result = a1->opcodes[pc];
    if ( !result )
      return result;
    
    printf("label_%03d:\n\t",pc);
    printf("a1->ptr1%=256;a1->ptr2%=256;\n\t");
    switch ( a1->opcodes[pc] )
    {
      case 0x14u:
        printf("a1->arr[a1->ptr1++] = %d;\n",a1->opcodes[pc + 1]);	//push IMM
        pc += 2;
        continue;
      case 0x15u:
        printf("++a1->ptr2;\n");		
        ++pc;
        continue;
      case 0x20u:
        printf("a1->mem[%d] = a1->arr[a1->ptr2++];\n",a1->opcodes[pc + 1]);
        pc += 2;
        continue;
      case 0x2Au:
        printf("a1->arr[a1->ptr1++] = a1->mem[%d];\n",a1->opcodes[pc + 1]);
        pc += 2;
        continue;
      case 0x2Bu:
        printf("a1->arr[a1->ptr1++] = a1->mem[a1->arr[a1->ptr2++]];\n");
        ++pc;
        continue;
      case 0x2Cu:
        printf("tmp = a1->arr[a1->ptr2++];\n");
        printf("\ta1->ptr2 %= 256;\n");
        printf("\ta1->mem[tmp] = a1->arr[a1->ptr2++];\n");
        ++pc;
        continue;
      case 0x30u:
        
        printf("a1->reg = a1->arr[a1->ptr2++] + (a1->reg << 8);\n");
        ++pc;
        continue;
      case 0x31u:
        
        
        printf("a1->arr[a1->ptr1++] = a1->reg % (int)%d;\n",a1->opcodes[pc + 1]);
        printf("\ta1->reg /= (int)%d;\n",a1->opcodes[pc + 1]);
        pc += 2;
        continue;
      case 0x32u:
        printf("a1->arr[a1->ptr1++] = table[a1->arr[a1->ptr2++]];\n");
        ++pc;
        continue;
      case 0x33u:
        printf("tmp = a1->arr[a1->ptr2++];\n");
        printf("\ta1->ptr2 %= 256;\n");
        printf("\ta1->arr[a1->ptr1++] = a1->arr[a1->ptr2++] + tmp;\n");
        ++pc;
        continue;
      case 0x34u:
        printf("tmp = a1->arr[a1->ptr2++];\n");
        printf("\ta1->ptr2 %= 256;\n");
        printf("\ta1->arr[a1->ptr1++] = a1->arr[a1->ptr2++] - tmp;\n");
        ++pc;
        continue;
      case 0x35u:
        printf("tmp = a1->arr[a1->ptr2++];\n");
        printf("\ta1->ptr2 %= 256;\n");
        printf("\ta1->arr[a1->ptr1++] = a1->arr[a1->ptr2++] * tmp;\n");
        ++pc;
        continue;
      case 0x36u:
        printf("a1->arr[a1->ptr1++] = a1->reg;\n");
        printf("\ta1->reg = 0;\n");
        ++pc;
        continue;
      case 0x37u:
        printf("tmp = a1->arr[a1->ptr2++];\n");
        printf("\ta1->ptr2 %= 256;\n");
        printf("\ta1->arr[a1->ptr1++] = a1->arr[a1->ptr2++] ^ tmp;\n");
        ++pc;
        continue;
      case 0x3Au:
      	printf("a1->arr[a1->ptr1++] = strlen((char*)a1->your_input);\n");
        ++pc;
        continue;
      case 0x40u:
      	xx=a1->opcodes[pc + 1];
        printf("if(a1->arr[a1->ptr2++]) {goto label_%03d;}\n",pc - xx);
        pc += 2;
        continue;
      case 0x41u:
        printf("for (int i = 0; i < strlen((const char *)a1->your_input); ++i )\n\t{\n\t\ta1->arr[a1->ptr1++] = a1->your_input[i];\n\t\ta1->ptr1 %= 256;\n\t}\n\tmemset(a1->your_input, 0, 0x200u);\n");
        ++pc;
        continue;
      case 0xABu:
      	printf("return 1;");
        return 1;
      case 0xFFu:
        printf("if ( a1->arr[a1->ptr2++] ) {return 0;}\n");
        ++pc;
        continue;
      default:
        return 2;
    }
  }
}

就按照这种方法慢慢分析最后可以还原出具体算法,就是先base58然后进行一个for循环加密。最后有一个一个个字节比较的过程,dump出数据即可一条龙解出。

for(int i = 0,i<30,i+=3)
{
	mem[0x40+i+1] = mem[0x40+i+1]+mem[0x40+i+0];
	mem[0x40+i+2] = mem[0x40+i+2]-mem[0x40+i+1];
	mem[0x40+i+0] = mem[0x40+i+0]^mem[0x40+i+2];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值