[XCTF-Reverse] 83 2019_UNCTF_easyvm

到了后边就都不会了,只有这个VM还可以磨磨

主程序非常短,输入32个字节然后就去比较了

signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  unsigned int (__fastcall ***v3)(_QWORD, void *, void *, char *); // rbx
  char s; // [rsp+10h] [rbp-80h]
  int v6; // [rsp+70h] [rbp-20h]
  unsigned __int64 v7; // [rsp+78h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  memset(&s, 0, 0x60uLL);
  v6 = 0;
  v3 = (unsigned int (__fastcall ***)(_QWORD, void *, void *, char *))operator new(0x28uLL);
  sub_400C1E((__int64)v3);
  puts("please input your flag:");
  scanf("%s", &s);
  if ( strlen(&s) != 32 )
  {
    puts("The length of flag is wrong!");
    puts("Please try it again!");
  }
  if ( (**v3)(v3, &unk_602080, &unk_6020A0, &s) != 0 )
  {
    puts("Congratulations!");
    printf("The flag is UNCTF{%s}", &s);
  }
  return 1LL;
}

v3在sub_400C1E 给了一个地址,然后其它的运算都在这个位置上偏移

__int64 __fastcall sub_400C1E(__int64 a1)
{
  __int64 result; // rax

  *(_QWORD *)a1 = off_4010A8;
  *(_QWORD *)(a1 + 8) = 0LL;
  *(_BYTE *)(a1 + 16) = 0;
  *(_BYTE *)(a1 + 17) = 0;
  *(_BYTE *)(a1 + 18) = 0;
  *(_DWORD *)(a1 + 20) = 0;
  *(_QWORD *)(a1 + 24) = 0LL;
  result = a1;
  *(_QWORD *)(a1 + 32) = 0LL;
  return result;
}

偏移多少是哪个函数在数据off_4010A8这有,为方便专门都改了个名字,对应switch里的编号。刚一开始跳转表看串行了,困惑好久,像九阴真经练反了的感觉。

.rodata:00000000004010A8 off_4010A8      dq offset A__main_switch
.rodata:00000000004010A8                                         ; DATA XREF: sub_400C1E+8↑o
.rodata:00000000004010B0                 dq offset A0_16add1
.rodata:00000000004010B8                 dq offset A1_17add1
.rodata:00000000004010C0                 dq offset A2_18add1
.rodata:00000000004010C8                 dq offset A3_16sub18
.rodata:00000000004010D0                 dq offset A4_16xor17
.rodata:00000000004010D8                 dq offset A5_17xor16
.rodata:00000000004010E0                 dq offset A6_16sub51
.rodata:00000000004010E8                 dq offset A7_17in16
.rodata:00000000004010F0                 dq offset A8_18_cd
.rodata:00000000004010F8                 dq offset A9_16e32add18
.rodata:0000000000401100                 dq offset AA_17e32add18
.rodata:0000000000401108                 dq offset AB_16cmp24add18
.rodata:0000000000401110                 dq offset AC_17cmp24add18
.rodata:0000000000401118                 dq offset AD_18gt0x1f

偏移0的时候是主程序。开始的时候从a2[9]开始也就是A9。然后变换*(a1+8)实现了一个循环。+8这可以看成rip,a3指向校验数据在*(a1+24),a4是输入的数据在*(a1+32),另外有模拟3个寄存器+16,+17,+18和一个标志位+20如果处理后的输入字符校验未通过会置标志然后退出。

signed __int64 __fastcall s01_main_switch(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
  *(_QWORD *)(a1 + 8) = a2 + 9;                 // A0-AF
  *(_QWORD *)(a1 + 24) = a3;                    // code
  *(_QWORD *)(a1 + 32) = a4;                    // input
  while ( 2 )
  {
    switch ( **(unsigned __int8 **)(a1 + 8) )
    {
      case 0xA0u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 8LL))(a1);
        continue;
      case 0xA1u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x10LL))(a1);
        continue;
      case 0xA2u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x18LL))(a1);// 9
        *(_QWORD *)(a1 + 8) += 11LL;
        continue;
      case 0xA3u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x20LL))(a1);// 2
        *(_QWORD *)(a1 + 8) += 2LL;
        continue;
      case 0xA4u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x28LL))(a1);// 5
        *(_QWORD *)(a1 + 8) += 7LL;
        continue;
      case 0xA5u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x30LL))(a1);// 3
        ++*(_QWORD *)(a1 + 8);
        continue;
      case 0xA6u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x38LL))(a1);// 4
        *(_QWORD *)(a1 + 8) -= 2LL;
        continue;
      case 0xA7u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x40LL))(a1);// 7
        *(_QWORD *)(a1 + 8) += 7LL;
        continue;
      case 0xA8u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x48LL))(a1);
        continue;
      case 0xA9u:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x50LL))(a1);// 1
        *(_QWORD *)(a1 + 8) -= 6LL;
        continue;
      case 0xAAu:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x58LL))(a1);
        continue;
      case 0xABu:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x60LL))(a1);// 6
        *(_QWORD *)(a1 + 8) -= 4LL;
        continue;
      case 0xACu:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x68LL))(a1);
        continue;
      case 0xADu:
        (*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 0x70LL))(a1);// 10
        *(_QWORD *)(a1 + 8) += 2LL;
        continue;
      case 0xAEu:                               // 8
        if ( *(_DWORD *)(a1 + 20) )
          return 0LL;
        *(_QWORD *)(a1 + 8) -= 12LL;
        continue;
      case 0xAFu:                               // out   11
        if ( *(_DWORD *)(a1 + 20) != 1 )
        {
          *(_QWORD *)(a1 + 8) -= 6LL;
          continue;
        }
        return 1LL;
      default:
        puts("cmd execute error");
        return 0LL;
    }
  }
}

这里面有向个是没有rip变动的,比如A0,A1,A8,AA,AC这如果运行到这就会是死循环,所以这些运行不到。还有几个没有运行函数,是判断跳转语句AE,AF。其它在a1后边的偏移就是前面跳转表上偏移,表示运行哪个函数。

每个函数都很简单,明确以后就可以捋捋加密过程了。

+18这个位置放的是个计数器,相当于for(i=0;i<=31;i++)里的i

先读入一个输入的字符减去序号然后和r2异或(这个异或的r2后边有)。然后再与0xcd异或。然后和校验数据比较不同则退出

成功后会把这个数据(与检验同,逆向时使用检验数据)存入r2,循环下次时使用。

然后就是i++,判断是否结束。

'''
rip: *(a1+8)  A0-AF
r1:  *(a1+16)
r2:  *(a1+17)     
i:   *(a1+18)  *(ptr+i) = flag[i]
code:*(a1+24)  code
ptr: *(a1+32) input flag

1,A9: r1= *[ptr + i]
2,A3: r1-=i 
3,A5: r2^=r1
4,A6: r1 =0xcd
5,A4: r1^=r2
6,AB: cmp(r1,code[i])  #(flag[i]-i)^code[i-1]^0xcd = code[i]
7,A7: r2 =r1 
8,AE: jnz A2           # *(a1+20) == 0
9,A2: i++
10,AD: cmp(i,0x1f)     #结束标志,总行0x1f
11,AF: jmp A9     
'''

所以解码程序就有了(由于用到r2的结果,所以解码要从后向前)

code = bytes.fromhex('00'+'F40AF76499789E7DEA7B9E7B9F7EEB71E800E8079819F425F321A42FF42FA67C')
flag = bytes([(code[i+1]^code[i]^0xcd) + i for i in range(0x1f, -1, -1)])[::-1]
print(flag)    
#942a4115be2359ffd675fa6338ba23b6
#UNCTF{942a4115be2359ffd675fa6338ba23b6}        

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值