那CTF,那VMre,那些事(三)

本篇文章为虚拟机保护re系列文章的第三篇,主要研究存在栈结构的虚拟机保护re的特点与解题步骤

0x01 前言

本篇文章为虚拟机保护re系列文章的第三篇,主要研究存在栈结构的虚拟机保护re的特点

0x02 初探栈虚拟机

学习栈虚拟机之前,我们不如先来了解一下虚拟机保护技术的分类以及栈虚拟机的特点。

虚拟机(CPU)的体系架构可分为3种,基于堆栈的(Stack based),基于寄存器的(Register based)和3地址机器。我们只讲述基于堆栈的虚拟机架构(Stack based);这种架构的虚拟机需要频繁操作堆栈,其使用的虚拟寄存器(虚拟的eax、ebx等等)保存在堆栈中;每个原子指令的handler都需要push、pop。

ctf re比赛中通常的.exe程序,一般是是基于寄存器的结构,堆栈一般只在函数传递参数时使用。而在vm保护中用来替代所保护程序的程序,则是基于堆栈的虚拟机架构。所以,在我们还原case,还原handler或者在还原程序逻辑的时候,要始终贯彻用栈操作的思想

既然使用栈操作,肯定就少不了汇编的常用指令(push pop add sub jmp 等)下面我们先看一下,在存在堆栈结构的虚拟机保护题目当中,我们常用的汇编指令是如何实现的:

  1. Vadd: ;virtual add
  2. Mov eax,[esp+4] ;取源操作数
  3. Mov ebx,[esp] ;取目的操作数
  4. Add ebx,eax ;
  5. Add esp,8 ;把参数从堆栈中删掉,平衡堆栈
  6. Push ebx ;把结果压入堆栈
  7. 而原有的add命令的参数,我们需要翻译为 push 命令 。根据push 的对象不同,需要不同的实现:
  8. vPushReg32: ;寄存器入栈 esi指向字节码的内存地址
  9. Mov eax,dword ptr[esi] ;从伪代码(字节码)中获得寄存器在VMcontext结构中的偏移地址
  10. Add esi,4 ;VMcontext结构保存了各个寄存器的值。该结构保存在堆栈内。
  11. Mov eax,dowrd ptr [edi +eax] ;得到寄存器的值。edi指向VMcontext结构的基址
  12. Push eax ;压入堆栈
  13. Jmp VMDispatcher ;任务完成,跳回任务分派点
  14. vPushImm32: ;立即数入栈
  15. Mov eax,dword ptr[esi] ;字节码,不用翻译就是了
  16. Add esi,4
  17. Push eax ;立即数入栈
  18. Jmp VMDispatcher
  19. vPopReg32: ;有Push指令了,也得有Pop指令:
  20. Mov eax,dword,ptr[esi] ;从伪代码(字节码)中获得寄存器在VMcontext结构中的偏移地址
  21. Add esi,4
  22. Pop dword ptr [edi+eax] ;弹回寄存器
  23. Jmp VMDispatcher
  24. Add esi,eax
  25. 转换为虚拟机的指令如下:
  26. vPushReg32 eax_index
  27. vPushReg32 esi_index
  28. Vadd
  29. vPopReg32 esi_index ;不弹eax_index,它作为返回结果保存在堆栈里
  30. Vjmp:
  31. Mov esi,dword ptr [esp] ;jmp的参数在堆栈中
  32. Add esp,4 ;平衡堆栈
  33. Jmp VMDispatcher
  34. Vcall:
  35. Push all vreg ;所有的虚拟的寄存器 (维护在堆栈中)
  36. Pop all reg ;弹出到真实的寄存器中(保存虚拟机的运行结果)
  37. Push 返回地址 ;可以让Call调用完成后,把控制权归还给虚拟机。
  38. Push 要调用的函数地址
  39. Retn

只看栈虚拟机各汇编指令的实现可能还不太形象。接下来我们比较一下基于堆栈虚拟机和基于寄存器虚拟机的区别。

一个基于Stack的虚拟机会通过IP来获取操作数,其操作数都是保存在Stack数据结构中,从栈中取出数据、计算然后再将结果存入栈中(LIFO,Last in first out)。

  1. 1POP 20
  2. 2POP 7
  3. 3ADD 20, 7, result
  4. 4PUSH result

image-20220219205252893

而基于寄存器的虚拟机,它们的操作数是存放在CPU的寄存器的。没有入栈和出栈的操作和概念。但是执行的指令就需要包含操作数的地址了,也就是说,指令必须明确的包含操作数的地址,这不像栈可以用栈指针去操作。比如如下的加法操作: ADD R1, R2, R3 ;就一条指令搞定了。不像Stack-Based一样,需要明确的制定操作数R1、R2、R3(这些都是寄存器)的地址。这种设计的优点就是去掉了入栈和出栈的操作,并且指令在寄存器虚拟机执行得更快。

image-20220219205404131

初步了解了栈结构的虚拟机保护原理,我们来看几道ctf题目,从实战中加深理解

0x03 hgame-week4-easyvm

main函数逻辑非常简单:输入一个字符串,然后初始化一个大数组,接下来执行sub_1400017D0(不顺眼可以改个名)函数,最后与一个已知数组进行匹配判定为节省篇幅这里放反编译main函数的伪代码

  1. int __cdecl main(int argc, const char **argv, const char **envp)
  2. {
  3. __int64 v3; // rcx
  4. __int64 v5[124]; // [rsp+20h] [rbp-E0h] BYREF
  5. int v6[6]; // [rsp+400h] [rbp+300h] BYREF
  6. char v7; // [rsp+418h] [rbp+318h]
  7. __int64 v8[5]; // [rsp+420h] [rbp+320h] BYREF
  8. char v9; // [rsp+448h] [rbp+348h]
  9. print("Input your flag: \n");
  10. v8[0] = 0i64;
  11. v8[1] = 0i64;
  12. v8[2] = 0i64;
  13. v8[3] = 0i64;
  14. v8[4] = 0i64;
  15. v9 = 0;
  16. scanf("%40s", (const char *)v8);
  17. v6[0] = 16909568;
  18. v5[0] = (__int64)v8;
  19. v6[1] = 83953669;
  20. v6[2] = 201657858;
  21. v6[3] = 100860676;
  22. v6[4] = 50792965;
  23. v6[5] = 184879105;
  24. v7 = 13;
  25. #初始化大数组
  26. V5[1]=40i64
  27. V5[2]=1i64
  28. ......
  29. sub_1400017D0(v6, v5);
  30. v3 = 0i64;
  31. while ( *((_BYTE *)v8 + v3) == byte_140004000[v3] )
  32. {
  33. if ( ++v3 >= 40 )
  34. return 0;
  35. }
  36. print("error\n");
  37. return 0;
  38. }

接下来跟进该关键函数

  1. __int64 __fastcall sub_1400017D0(__int64 a1, _QWORD *a2)
  2. {
  3. __int64 v3; // rsi
  4. _QWORD *v4; // rdx
  5. bool v5; // zf
  6. _QWORD *v6; // rdx
  7. _QWORD *v7; // rdx
  8. __int64 v8; // rcx
  9. _QWORD *v9; // rdx
  10. __int64 v10; // rcx
  11. _QWORD *v11; // r8
  12. __int64 v13[3]; // [rsp+20h] [rbp-39h] BYREF
  13. __int64 v14; // [rsp+38h] [rbp-21h] BYREF
  14. __int64 v15[7]; // [rsp+40h] [rbp-19h] BYREF
  15. __int64 v16; // [rsp+78h] [rbp+1Fh]
  16. _QWORD *v17; // [rsp+80h] [rbp+27h]
  17. __int64 v18; // [rsp+88h] [rbp+2Fh]
  18. __int64 v19; // [rsp+90h] [rbp+37h] BYREF
  19. v17 = a2;
  20. v3 = stack(200i64);# 构造栈
  21. v15[6] = a1;
  22. v16 = a1;
  23. v14 = 0i64;
  24. v15[0] = 0i64;
  25. v15[1] = 0i64;
  26. v15[2] = 0i64;
  27. v15[4] = 0i64;
  28. v15[3] = 0i64;
  29. v13[2] = 0i64;
  30. v18 = 0i64;
  31. if ( a1 )
  32. {
  33. while ( 2 )
  34. {
  35. switch ( *(_BYTE *)++v16 )
  36. {
  37. case 1:
  38. sub_1400010E0(v14, v3);
  39. goto LABEL_22;
  40. case 2:
  41. sub_1400010C0(&v14, v3);
  42. goto LABEL_22;
  43. case 3:
  44. sub_1400010E0(v15[0], v3);
  45. goto LABEL_22;
  46. case 4:
  47. sub_1400010C0(v15, v3);
  48. goto LABEL_22;
  49. case 5:
  50. sub_1400010E0(*v17++, v3);
  51. goto LABEL_22;
  52. case 6:
  53. v19 = 0i64;
  54. sub_1400010C0(&v19, v3);
  55. v10 = *(unsigned __int8 *)v19;
  56. v19 = v10;
  57. sub_1400010E0(v10, v3);
  58. goto LABEL_22;
  59. case 7:
  60. v19 = 0i64;
  61. v13[0] = 0i64;
  62. sub_1400010C0(&v19, v3);
  63. sub_1400010C0(v13, v3);
  64. *(_BYTE *)v13[0] = v19;
  65. goto LABEL_22;
  66. case 8:
  67. v19 = 0i64;
  68. sub_1400010C0(&v19, v3);
  69. v4 = (_QWORD *)(*(_QWORD *)(v3 + 16) + 8i64 * *(_QWORD *)(v3 + 8));
  70. *v4 += v19;
  71. v5 = *(_QWORD *)(*(_QWORD *)(v3 + 16) + 8i64 * *(_QWORD *)(v3 + 8)) == 0i64;
  72. LODWORD(v18) = 0;
  73. HIDWORD(v18) = v5;
  74. goto LABEL_22;
  75. case 9:
  76. v19 = 0i64;
  77. sub_1400010C0(&v19, v3);
  78. v6 = (_QWORD *)(*(_QWORD *)(v3 + 16) + 8i64 * *(_QWORD *)(v3 + 8));
  79. *v6 -= v19;
  80. HIDWORD(v18) = *(_QWORD *)(*(_QWORD *)(v3 + 16) + 8i64 * *(_QWORD *)(v3 + 8)) == 0i64;
  81. LODWORD(v18) = *(_DWORD *)(*(_QWORD *)(v3 + 16) + 8i64 * *(_QWORD *)(v3 + 8)) & 0x80000000;
  82. goto LABEL_22;
  83. case 0xA:
  84. v19 = 0i64;
  85. sub_1400010C0(&v19, v3);
  86. v7 = (_QWORD *)(*(_QWORD *)(v3 + 16) + 8i64 * *(_QWORD *)(v3 + 8));
  87. *v7 ^= v19;
  88. goto LABEL_22;
  89. case 0xB:
  90. v19 = 0i64;
  91. sub_1400010C0(&v19, v3);
  92. v16 += v19;
  93. goto LABEL_22;
  94. case 0xC:
  95. v19 = 0i64;
  96. sub_1400010C0(&v19, v3);
  97. if ( HIDWORD(v18) )
  98. v16 += v19;
  99. goto LABEL_22;
  100. case 0xD:
  101. free(*(void **)(v3 + 16));
  102. free((void *)v3);
  103. return 1i64;
  104. case 0xE:
  105. v9 = (_QWORD *)(*(_QWORD *)(v3 + 16) + 8i64 * *(_QWORD *)(v3 + 8));
  106. HIDWORD(v18) = *(v9 - 1) == *v9;
  107. goto LABEL_22;
  108. case 0xF:
  109. v11 = (_QWORD *)(*(_QWORD *)(v3 + 16) + 8i64 * *(_QWORD *)(v3 + 8));
  110. HIDWORD(v18) = *(v11 - 1) % *v11 == 0i64;
  111. goto LABEL_22;
  112. case 0x11:
  113. v19 = 0i64;
  114. sub_1400010C0(&v19, v3);
  115. v8 = -1i64;
  116. do
  117. ++v8;
  118. while ( *(_BYTE *)(v19 + v8) );
  119. sub_1400010E0(v8, v3);
  120. goto LABEL_22;
  121. case 0x12:
  122. DebugBreak();
  123. goto LABEL_22;
  124. default:
  125. LABEL_22:
  126. if ( !v16 )
  127. return 0i64;
  128. continue;
  129. }
  130. }
  131. }
  132. return 0i64;
  133. }

上述函数内存注释部分相当于构造了这么一个栈结构体

  1. struct StackInfo{
  2. QWORD Size; //栈尺寸
  3. QWORD CurrentStackTop; //栈顶
  4. QWORD pStack; // 指向分配出来的栈
  5. }

静态分析分析不太出来内容了。我们采取动态调试的方法,动态调试发现函数的作用就是取出输入字符串中的每三个其中的第二个和未知固定值进行一次异或,共循环0x20次。

接下来我们使用ida-python导出数组,得到要异或的值。运行以下脚本再输入40个a

  1. from idc_bc695 import *
  2. auto addr=0x7FF7889917D0; //函数处理后的位置
  3. auto x,i;
  4. for(i=0;i<48;i++){
  5. x=byte(addr);
  6. Message("0x%x,",x);
  7. addr=addr+1;
  8. }

得到的值再和‘a’异或,获得要异或的值。

导出数组后写最终脚本

  1. cipher = [58, 84, 47, 42, 47, 54, 19, 1, 46, 3,
  2. 53, 64, 71, 14, 95, 89, 1, 105, 39, 8,
  3. 61, 76, 51, 26, 45, 11, 64, 14, 75, 36,
  4. 65, 39, 37, 40, 41, 42, 2, 2, 93, 36]
  5. xor = [82, 51, 78, 71, 74, 77, 103, 105, 71, 112, 106, 54, 42, 81, 54, 42, 94, 54, 84, 103, 78, 35, 64, 117, 94, 100, 51, 97, 56, 75, 50, 72, 86, 71, 118, 79, 99, 113, 36, 89]
  6. for i in range(40):
  7. print(chr(cipher[i] ^ xor[i]), end='')

得到flag:hgame{this_vm_is__sosososososososo_easy}

0x04 虎符ctf-vm

题目的main函数比较简单(main函数比较简单的题一般都比较难吗?),只是调用了一个vm函数。vm函数就非常关键了。反编译一下

image-20220220165831378

可以看出是比较典型的一个while(1)switch(opcode)的结构做的虚拟机。

我们先分析一下声明的数据变量的含义

  1. vm_eip 偏移量,使用这个配合code地址检索opcodeopcode = *(&amp;code + vm_eip),
  2. vm_sp 代表栈内数据数量,也配合栈地址形成指向栈顶的指针,
  3. code code源码
  4. vm_stack:这个是这个栈堆机器操作的栈,
  5. vm_arr 一片内存空间,主要用于储存三个数组,一个预定义好了的arr1(在50-91),一个用户输入的arr2(在100-141),一个由arr2处理成的arr3(在0-41),对arr的处理是重点
  6. vm_block 这个储存另一部分数据,主要是用于储存循环时的计数器,是一个python解释器中block_stack的地位,

接着我们分析一下各opcode的作用(ida注释yyds)

对栈的基础操作:

  1. opcode(0x1) ==> 0x1, ==> push input 接收一个用户输入字节并压入栈中,
  2. opcode(0x4) ==> 0x4, var, ==> push var;
  3. opcode(0x7) ==> 0x7, index, ==> push vm_arr[index],
  4. opcode(0x5) ==> 0x5, index, ==> push vm_block[index]
  5. opcode(0x12) ==> 0x12, ==> vm_stack[vm_sp] = ~vm_stack[vm_sp] 对栈顶数值取反。
  6. opcode(0x19) ==> 0x19, ==> vm_stack[vm_sp-1] = vm_arr[vm_stack[vm_sp-1]],

运算,存储机制采用的是我们之前说过的堆栈机制。弹出栈顶两个,再把计算结果压栈

  1. opcode(9) ==> 9 ==> binary_add 加法 +
  2. opcode(0xa) ==> 0xa ==> binary_subtract 减法-
  3. opcode(0xb) ==> 0xb ==> binary_multiply乘法 ×
  4. opcode(0xd) ==> 0xd ==> binary_modulo 取余 %
  5. opcode(0xf) ==> 0xf ==> binary_and 按位与 &amp;
  6. opcode(0x10) ==> 0x10 ==> binary_or 按位或 |

判断和跳转:

  1. opcode(0x1d) ==> 0x1d, tar, ==>jump $+tar 直接跳转到参数指定的位置,但是要注意,有时候这个参数其实是负数, 回跳,形成一个循环结构。
  2. 剩下几个都是,根据栈顶两个值的大小关系,决定是否跳转到指定位置:
  3. opcode(0x18) ==> 0x18, tar, ==> if vm_stack[vm_sp] < vm_stack[vm_sp-1]: jump $+tar
  4. opcode(0x16) ==> 0x16, tar, ==> if vm_stack[vm_sp] > vm_stack[vm_sp-1]: jump $+tar
  5. opcode(0x14) ==> 0x14, tar, ==> if vm_stack[vm_sp] == vm_stack[vm_sp-1]: jump $+tar,

对于数组的操作:

  1. opcode(8) ==> 8, index, ==> vm_arr[index] = vm_stack[vm_sp];
  2. opcode(0x1a) ==> 0x1a, ==> vm_arr[vm_stack[vm_sp]] = vm_stack[vm_sp-1]

对于block的操作:

  1. opcode(0x5) ==> 0x5, index, ==> push vm_block[index]
  2. opcode(0x6) ==> 0x6, ==> vm_block[var] = vm_stack[vm_sp]
  3. opcode(0x1c) ==> 0x1c, ==> vm_block[vm_stack[vm_sp]] = vm_stack[vm_sp-1]

io:

  1. opcode(0x2) ==> 0x2, ==> print vm_stack[vm_sp]打印栈顶的数值对应字符。
  2. opcode(0x1) ==> 0x1, ==> push input 接收一个用户输入字节并压入栈中,

语句组合拳

这个是看了dalaoWP才悟出的操作,有一些op_code的字符构成了一些结构,我们需要注意。

  1. 0x1 or 0x4 ->0x8 #压栈,然后将栈顶弹出到arr中,其实就相当于是向arr里面赋值,一开始大片的0x4 ->0x8 是赋值arr1, 接着就是0x1->0x8是初始化arr2
  2. 0x4-0x6
  3. 设置block内的值
  4. 0x5-0x4-0x16
  5. 判断block里的值和0x4参数的大小,在code中出现过三次,都是循环的开头部分
  6. 0x1D 参数为负数
  7. 0x1d 后面的参数有负数的情况,其实就是相当于减法,回跳,一般配合前面这个0x5-0x4-0x16,形成一个循环结构

关于字节码的分析,我是根据字节码所对应的汇编操作以及语句组合拳一点一点分析出来的。当然更高阶的玩法是写翻译字节码的脚本,笔者暂时整不明白,一会儿piao个大佬的脚本以供大家学习,我先写一写小白做法。

小白做法:根据op_code手撕

先简单讲一下手撕逻辑吧:

arr1数组赋值初始化部分:

  1. 0x04, 0x66, 0x08, 0x32

0x04 … 0x08 0x32是一个完整的arr1数组一位的赋值语句。

list读入部分:

  1. 0x01, 0x08, 0x64
  2. 0x01, 0x08, 0x65
  3. 0x01, 0x08, 0x66
  4. ......
  5. 0x01,0x08,0x8D

arr2是从input中读入的数据获取的,从0x64~0x8D一共读取了41位

循环结构(就讲一个):

记得我们上文提到过的组合拳吗?

  1. 0x5-0x4-0x16

来看某一个循环对应的opcode

  1. for j in range(7):
  2. 0x05, 0x00, 0x04, 0x07,

再来看一个:

  1. for i in range(6):
  2. 0x05, 0x01, 0x04, 0x06

什么叫不言自明啊(战术后仰)相信明眼人已经都懂了qaq

手撕大法好

手撕撕了好久好久,在大佬博客的帮助下终于手撕出来了伪代码,下面是字节码和伪代码对应版

  1. # 0x04, 0x66, 0x08, 0x32, 0x04, 0x4E, 0x08, 0x33, 0x04, 0xA9, 0x08, 0x34, 0x04, 0xFD, 0x08, 0x35, 0x04, 0x3C, 0x08, 0x36, 0x04, 0x55, 0x08, 0x37, 0x04, 0x90, 0x08, 0x38, 0x04, 0x24, 0x08, 0x39, 0x04, 0x57, 0x08, 0x3A, 0x04, 0xF6, 0x08, 0x3B, 0x04, 0x5D, 0x08, 0x3C, 0x04, 0xB1, 0x08, 0x3D, 0x04, 0x01, 0x08, 0x3E, 0x04, 0x20, 0x08, 0x3F, 0x04, 0x81, 0x08, 0x40, 0x04, 0xFD, 0x08, 0x41, 0x04, 0x36, 0x08, 0x42, 0x04, 0xA9, 0x08, 0x43, 0x04, 0x1F, 0x08, 0x44, 0x04, 0xA1, 0x08, 0x45, 0x04, 0x0E, 0x08, 0x46, 0x04, 0x0D, 0x08, 0x47, 0x04, 0x80, 0x08, 0x48, 0x04, 0x8F, 0x08, 0x49, 0x04, 0xCE, 0x08, 0x4A, 0x04, 0x77, 0x08, 0x4B, 0x04, 0xE8, 0x08, 0x4C, 0x04, 0x23, 0x08, 0x4D, 0x04, 0x9E, 0x08, 0x4E, 0x04, 0x27, 0x08, 0x4F, 0x04, 0x60, 0x08, 0x50, 0x04, 0x2F, 0x08, 0x51, 0x04, 0xA5, 0x08, 0x52, 0x04, 0xCF, 0x08, 0x53, 0x04, 0x1B, 0x08, 0x54, 0x04, 0xBD, 0x08, 0x55, 0x04, 0x32, 0x08, 0x56, 0x04, 0xDB, 0x08, 0x57, 0x04, 0xFF, 0x08, 0x58, 0x04, 0x28, 0x08, 0x59, 0x04, 0xA4, 0x08, 0x5A, 0x04, 0x5D, 0x08, 0x5B,
  2. arr1 = [102, 78, 169, 253, 60, 85, 144, 36, 87, 246, 93, 177, 1, 32, 129, 253, 54, 169, 31, 161, 14, 13, 128, 143, 206, 119, 232, 35, 158, 39, 96, 47, 165, 207, 27, 189, 50, 219, 255, 40, 164, 93]
  3. # 0x01, 0x08, 0x64, 0x01, 0x08, 0x65, 0x01, 0x08, 0x66, 0x01, 0x08, 0x67, 0x01, 0x08, 0x68, 0x01, 0x08, 0x69, 0x01, 0x08, 0x6A, 0x01, 0x08, 0x6B, 0x01, 0x08, 0x6C, 0x01, 0x08, 0x6D, 0x01, 0x08, 0x6E, 0x01, 0x08, 0x6F, 0x01, 0x08, 0x70, 0x01, 0x08, 0x71, 0x01, 0x08, 0x72, 0x01, 0x08, 0x73, 0x01, 0x08, 0x74, 0x01, 0x08, 0x75, 0x01, 0x08, 0x76, 0x01, 0x08, 0x77, 0x01, 0x08, 0x78, 0x01, 0x08, 0x79, 0x01, 0x08, 0x7A, 0x01, 0x08, 0x7B, 0x01, 0x08, 0x7C, 0x01, 0x08, 0x7D, 0x01, 0x08, 0x7E, 0x01, 0x08, 0x7F, 0x01, 0x08, 0x80, 0x01, 0x08, 0x81, 0x01, 0x08, 0x82, 0x01, 0x08, 0x83, 0x01, 0x08, 0x84, 0x01, 0x08, 0x85, 0x01, 0x08, 0x86, 0x01, 0x08, 0x87, 0x01, 0x08, 0x88, 0x01, 0x08, 0x89, 0x01, 0x08, 0x8A, 0x01, 0x08, 0x8B, 0x01, 0x08, 0x8C, 0x01, 0x08, 0x8D,
  4. arr2 = list(input()) # len = 41
  5. # 0x04, 0x00, 0x06, 0x00, 0x05, 0x00, 0x04, 0x07, 0x16, 0x56,
  6. var1 = 0
  7. for j in range(7):
  8. # 0x04, 0x00, 0x06, 0x01, 0x05, 0x01, 0x04, 0x06, 0x16, 0x42,
  9. for i in range(6):
  10. var1 = arr2[6*j+i]
  11. var1 = ~var1
  12. var1 &amp;= (i + 2) * j
  13. var2 = arr2[6*j + i]
  14. var2 &amp;= ~((i + 2) * j)
  15. var1 |= var2
  16. arr3[i * 7 + j] = var
  17. for i in range(1, 42):
  18. # 0x05, 0x00, 0x04, 0x02, 0x0D, 0x04, 0x00, 0x14, 0x0F,
  19. if i % 2 == 0:
  20. # arr[i] += arr[i-1]
  21. # 注意还会进行高位舍去
  22. arr[i] = (arr[i] + arr[i - 1]) &amp; 0xff
  23. # 0x05, 0x00, 0x19, 0x05, 0x00, 0x04, 0x01, 0x0A, 0x19, 0x09, 0x05, 0x00, 0x1A,
  24. # 0x05, 0x00, 0x04, 0x02, 0x0D, 0x04, 0x01, 0x14, 0x0B,
  25. if i % 2 == 1:
  26. arr[i] = (arr[i] * 0x6b) &amp; 0xff
  27. # 0x04, 0x6B, 0x05, 0x00, 0x19, 0x0B, 0x05, 0x00, 0x1A, 0x05, 0x00, 0x04, 0x01, 0x09, 0x04, 0x00, 0x1C, 0x1D, 0xCA,
  28. for i in range(0x29):
  29. if arr[i] != arr1[i]:
  30. print('n')
  31. return 0
  32. print('y')
  33. return 0

手撕获得了伪代码,写脚本逆向就变成了多是一件美事~

大佬解法:翻译字节码脚本

  1. class Translate():
  2. def opcode(self):
  3. '''
  4. 得到一个字节码字典
  5. '''
  6. dic = r'''1 getchar()
  7. 2 pop a; printf(a)
  8. 3 vm_eip ++
  9. 4 push data
  10. 5 push d[data]
  11. 6 pop d[data]
  12. 7 push c[data]
  13. 8 pop c[data]
  14. 9 pop a; pop b; push b+a
  15. 0xa pop a; pop b; push b-a
  16. 0xb pop a; pop b; push b*a
  17. 0xc pop a; pop b; push b/a
  18. 0xd pop a; pop b; push b%a
  19. 0xe pop a; pop b; push b^a
  20. 0xf pop a; pop b; push b&amp;a
  21. 0x10 pop a; pop b; push b|a
  22. 0x11 pop a; push -a;
  23. 0x12 pop a; push ~a;
  24. 0x13 pop a; pop b; if (b!=a) {vm_eip+=2} else {vm_eip+=data}
  25. 0x14 pop a; pop b; if (b==a) {vm_eip+=2} else {vm_eip+=data}
  26. 0x15 pop a; pop b; if (b<=a) {vm_eip+=2} else {vm_eip+=data}
  27. 0x16 pop a; pop b; if (b<a) {vm_eip+=2} else {vm_eip+=data}
  28. 0x17 pop a; pop b; if (b>=a) {vm_eip+=2} else {vm_eip+=data}
  29. 0x18 pop a; pop b; if (b>a) {vm_eip+=2} else {vm_eip+=data}
  30. 0x19 pop a; push c[a]
  31. 0x1a pop a; pop b; c[a] = b
  32. 0x1b pop a; push d[a]
  33. 0x1c pop a; pop b; d[a] = b
  34. 0x1d vm_eip += data; if (*vm_eip>0x1d) return vm_eip
  35. 0x1e return vm_eip'''
  36. self.opcode_dict = {}
  37. for i in dic.split('\n'):
  38. num, opcode = int(i.split('\t')[0], 16), i.split('\t')[1]
  39. self.opcode_dict[num] = opcode
  40. def translate_part_3_opcode(self):
  41. '''
  42. 翻译字节码
  43. '''
  44. self.opcode()
  45. with open(r'code', 'rb')as f:
  46. self.b = f.read()
  47. eip = 0x126
  48. while eip < 0x1e8:
  49. op = self.b[eip]
  50. if 'data' in self.opcode_dict[op]:
  51. #将data替换为具体的操作数
  52. data = self.b[eip+1]
  53. print('{0:<8}{1:<5}{2:<7}{3}'.format(hex(eip), hex(op), hex(data), self.opcode_dict[op].replace('data', hex(data))))
  54. eip += 2
  55. else:
  56. print('{0:<8}{1:<12}{2}'.format(hex(eip), hex(op), self.opcode_dict[op]))
  57. eip += 1
  58. def __init__(self):
  59. self.opcode()
  60. self.translate_part_3_opcode()
  61. t = Translate()

这样的话可得到如下的伪汇编指令:

  1. 0x126 0x4 0x0 push 0x0
  2. 0x128 0x6 0x0 pop d[0x0]
  3. 0x12a 0x5 0x0 push d[0x0]
  4. 0x12c 0x4 0x7 push 0x7
  5. 0x12e 0x16 0x56 pop a; pop b; cmp a,b; jbe 0x184;
  6. 0x130 0x4 0x0 push 0x0
  7. 0x132 0x6 0x1 pop d[0x1]
  8. 0x134 0x5 0x1 push d[0x1]
  9. 0x136 0x4 0x6 push 0x6
  10. 0x138 0x16 0x42 pop a; pop b; cmp a,b; jbe 0x17a;
  11. 0x13a 0x5 0x0 push d[0x0]
  12. 0x13c 0x4 0x6 push 0x6
  13. 0x13e 0xb pop a; pop b; push b*a
  14. 0x13f 0x5 0x1 push d[0x1]
  15. 0x141 0x9 pop a; pop b; push b+a
  16. 0x142 0x4 0x64 push 0x64
  17. 0x144 0x9 pop a; pop b; push b+a
  18. 0x145 0x19 pop a; push c[a]
  19. 0x146 0x12 pop a; push ~a;
  20. 0x147 0x5 0x0 push d[0x0]
  21. 0x149 0x5 0x1 push d[0x1]
  22. 0x14b 0x4 0x2 push 0x2
  23. 0x14d 0x9 pop a; pop b; push b+a
  24. 0x14e 0xb pop a; pop b; push b*a
  25. 0x14f 0xf pop a; pop b; push b&amp;a
  26. 0x150 0x4 0x64 push 0x64
  27. 0x152 0x5 0x0 push d[0x0]
  28. 0x154 0x4 0x6 push 0x6
  29. 0x156 0xb pop a; pop b; push b*a
  30. 0x157 0x5 0x1 push d[0x1]
  31. 0x159 0x9 pop a; pop b; push b+a
  32. 0x15a 0x9 pop a; pop b; push b+a
  33. 0x15b 0x19 pop a; push c[a]
  34. 0x15c 0x5 0x0 push d[0x0]
  35. 0x15e 0x5 0x1 push d[0x1]
  36. 0x160 0x4 0x2 push 0x2
  37. 0x162 0x9 pop a; pop b; push b+a
  38. 0x163 0xb pop a; pop b; push b*a
  39. 0x164 0x12 pop a; push ~a;
  40. 0x165 0xf pop a; pop b; push b&amp;a
  41. 0x166 0x10 pop a; pop b; push b|a
  42. 0x167 0x5 0x1 push d[0x1]
  43. 0x169 0x4 0x7 push 0x7
  44. 0x16b 0xb pop a; pop b; push b*a
  45. 0x16c 0x5 0x0 push d[0x0]
  46. 0x16e 0x9 pop a; pop b; push b+a
  47. 0x16f 0x1a pop a; pop b; c[a] = b
  48. 0x170 0x5 0x1 push d[0x1]
  49. 0x172 0x4 0x1 push 0x1
  50. 0x174 0x9 pop a; pop b; push b+a
  51. 0x175 0x4 0x1 push 0x1
  52. 0x177 0x1c pop a; pop b; d[a] = b
  53. 0x178 0x1d 0xbc jmp 0x134
  54. 0x17a 0x5 0x0 push d[0x0]
  55. 0x17c 0x4 0x1 push 0x1
  56. 0x17e 0x9 pop a; pop b; push b+a
  57. 0x17f 0x4 0x0 push 0x0
  58. 0x181 0x1c pop a; pop b; d[a] = b
  59. 0x182 0x1d 0xa8 jmp 0x12a
  60. ;以上为第一部分,二重循环。设输入为x,求出f(x),存储在c[0]~c[0x29]
  61. ;第一个部分循环跳出至此
  62. 0x184 0x4 0x1 push 0x1
  63. 0x186 0x6 0x0 pop d[0x0]
  64. 0x188 0x5 0x0 push d[0x0]
  65. 0x18a 0x4 0x2a push 0x2a
  66. 0x18c 0x16 0x34 pop a; pop b; cmp a,b; jbe 0x1c0;
  67. 0x18e 0x5 0x0 push d[0x0]
  68. 0x190 0x4 0x2 push 0x2
  69. 0x192 0xd pop a; pop b; push b%a
  70. 0x193 0x4 0x0 push 0x0
  71. 0x195 0x14 0xf pop a; pop b; cmp a,b; jnz 0x1a4;
  72. 0x197 0x5 0x0 push d[0x0]
  73. 0x199 0x19 pop a; push c[a]
  74. 0x19a 0x5 0x0 push d[0x0]
  75. 0x19c 0x4 0x1 push 0x1
  76. 0x19e 0xa pop a; pop b; push b-a
  77. 0x19f 0x19 pop a; push c[a]
  78. 0x1a0 0x9 pop a; pop b; push b+a
  79. 0x1a1 0x5 0x0 push d[0x0]
  80. 0x1a3 0x1a pop a; pop b; c[a] = b
  81. 0x1a4 0x5 0x0 push d[0x0]
  82. 0x1a6 0x4 0x2 push 0x2
  83. 0x1a8 0xd pop a; pop b; push b%a
  84. 0x1a9 0x4 0x1 push 0x1
  85. 0x1ab 0x14 0xb pop a; pop b; cmp a,b; jnz 0x1b6;
  86. 0x1ad 0x4 0x6b push 0x6b
  87. 0x1af 0x5 0x0 push d[0x0]
  88. 0x1b1 0x19 pop a; push c[a]
  89. 0x1b2 0xb pop a; pop b; push b*a
  90. 0x1b3 0x5 0x0 push d[0x0]
  91. 0x1b5 0x1a pop a; pop b; c[a] = b
  92. 0x1b6 0x5 0x0 push d[0x0]
  93. 0x1b8 0x4 0x1 push 0x1
  94. 0x1ba 0x9 pop a; pop b; push b+a
  95. 0x1bb 0x4 0x0 push 0x0
  96. 0x1bd 0x1c pop a; pop b; d[a] = b
  97. 0x1be 0x1d 0xca jmp 0x188
  98. ;至此为第二部分,按照下标的奇偶,分别将上一步得到的数据进一步加密
  99. ;以下为第三部分,判断c[i]是否和c[i+0x32]是否相等,相等输出y,反之输出n
  100. 0x1c0 0x4 0x0 push 0x0
  101. 0x1c2 0x6 0x0 pop d[0x0]
  102. 0x1c4 0x5 0x0 push d[0x0]
  103. 0x1c6 0x4 0x29 push 0x29
  104. 0x1c8 0x18 0x4 pop a; pop b; cmp a,b; jae 0x1cc;
  105. 0x1ca 0x1d 0x1b jmp 0x1e5
  106. ;jmp to printf('y')
  107. 0x1cc 0x5 0x0 push d[0x0]
  108. 0x1ce 0x19 pop a; push c[a]
  109. 0x1cf 0x4 0x32 push 0x32
  110. 0x1d1 0x5 0x0 push d[0x0]
  111. 0x1d3 0x9 pop a; pop b; push b+a
  112. 0x1d4 0x19 pop a; push c[a]
  113. 0x1d5 0x14 0xc pop a; pop b; cmp a,b; jnz 0x1e1;
  114. ;jmp to printf('n')
  115. 0x1d7 0x5 0x0 push d[0x0]
  116. 0x1d9 0x4 0x1 push 0x1
  117. 0x1db 0x9 pop a; pop b; push b+a
  118. 0x1dc 0x4 0x0 push 0x0
  119. 0x1de 0x1c pop a; pop b; d[a] = b
  120. 0x1df 0x1d 0xe5 jmp 0x1c4
  121. 0x1e1 0x4 0x6e push 0x6e
  122. ;printf('n')
  123. 0x1e3 0x2 pop a; printf(a)
  124. 0x1e4 0x1e return vm_eip
  125. ;printf('y')
  126. 0x1e5 0x4 0x79 push 0x79
  127. 0x1e7 0x2 pop a; printf(a)

————————————————————————————————————————————————————————————

原文链接:http://blog.iyzyi.com/index.php/archives/1252/

作者:iyzyi

再将汇编翻译成伪代码就可以了,这个就得看你的汇编功底了,你只能靠你自己~

PS:关于如下部分的re

  1. for j in range(7):
  2. # 0x04, 0x00, 0x06, 0x01, 0x05, 0x01, 0x04, 0x06, 0x16, 0x42,
  3. for i in range(6):
  4. var1 = arr2[6*j+i]
  5. var1 = ~var1
  6. var1 &amp;= (i + 2) * j
  7. var2 = arr2[6*j + i]
  8. var2 &amp;= ~((i + 2) * j)
  9. var1 |= var2
  10. arr3[i * 7 + j] = var

以上花里胡稍的操作, 其实就是个异或关系。即

  1. a ^ b = (~a &amp; b) | (a &amp; ~b)

附解题脚本

  1. arr1 = [102, 78, 169, 253, 60, 85, 144, 36, 87, 246, 93, 177, 1, 32, 129, 253, 54, 169, 31, 161, 14, 13, 128, 143, 206, 119, 232, 35, 158, 39, 96, 47, 165, 207, 27, 189, 50, 219, 255, 40, 164, 93]
  2. arr = [0] * 42
  3. arr[0] = arr1[0]
  4. flag = ''
  5. for i in range(1, 42):
  6. if i % 2 ==0:
  7. arr[i] = (arr1[i] - arr1[i-1]) &amp; 0xff
  8. if i % 2 ==1:
  9. for j in range(0xff):
  10. if (j * 0x6b) &amp; 0xff == arr1[i]:
  11. arr[i] = j
  12. for j in range(7):
  13. for i in range(6):
  14. flag += chr(arr[i * 7+j] ^ ((i + 2) * j))
  15. print(flag)

0x05 2021长安杯-virture

虎符的代码量属实有点吓到了,我们来做一做去年长安杯的一道虚拟机题目缓一缓~

先打个预防针,题目关键词:三十二元一次方程

没找到main函数,那我们从start开始趴就。

  1. __int64 start()
  2. {
  3. unk_411D80 = 0;
  4. sub_402450();
  5. return (unsigned int)sub_40115D();
  6. }

跟进 sub_402450()函数。不断跟进分析,一直跟进到sub_401840函数。

很好 switch+while(1) vmre没跑了。结果我们兴高采烈的看到了30多个指令,2万多个字节的 opcode……

没关系,只要思想不滑坡,办法总比困难多

先学习大佬把op_code一条一条分开来分析

分析完之后发现,这题倒是没有那么丧心病狂,三十几个指令到头来只用到了十来个。

  1. 0x01 加法运算
  2. 0x02 乘法运算
  3. 0x09 转移指令,相当于 mov reg Dword
  4. 0x0d 异或运算
  5. 0x12 转移指令,相当于 mov [addr] , reg
  6. 0x13 转移指令,相当于 mov reg Dword
  7. 0x15 字符输出,相当于 putchar()
  8. 0x16 字符输入,相当于 getchar()
  9. 0x17 没摸清楚,可以确定的是有个比较的功能
  10. 0x1b 退出,相当于 exit()
  11. 0x20 条件跳转,但在那种条件下跳转不明
  12. 0x21 不等于跳转,相当于 jne

分析一下这个题目的 逻辑 倒是很简单

  1. 1. 输出字符串 Please input the key:”
  2. 2. 接收32个字符的输入
  3. 3. 检测输入是否合法
  4. 4. 32 轮校验

最后看一下校验部分(节省篇幅,截取部分):

  1. 09 01 00 00 00 00 mov num_32 , Dword 0
  2. 13 00 00 00 00 00 load char flag[00]
  3. 09 02 3e 00 00 00 load char 3e >
  4. 02 00 02 temp = 0x3e * flag[00]
  5. 01 01 00 num_32 += temp
  6. 13 00 01 00 00 00 load char flag[01]
  7. 09 02 21 00 00 00 load char 21 !
  8. 02 00 02 temp = 0x21 * flag[01]
  9. 01 01 00 num_32 += temp
  10. 13 00 02 00 00 00 load char flag[02]
  11. 09 02 33 00 00 00 load char 33 3
  12. 02 00 02 temp = 0x33 * flag[02]
  13. 01 01 00 num_32 += temp
  14. 13 00 03 00 00 00 load char flag[03]

可以发现,除了参数不同,剩余的部分都一样。整理出来发现是个32元一次方程,使用z3约束求解即可
最后附上解题脚本

  1. from z3 import *
  2. value1 = [
  3. 0xac241d89,0x3c748602,0xd050dee3,0x6437ffd9,
  4. 0x0d6f5ae8,0x8d08a075,0xbb43561f,0xa10913ca,
  5. 0x0d647c66,0x61cb01dd,0x51f6cf38,0x86009a31,
  6. 0xfb6afa06,0x26dfaeeb,0x76b24695,0xf3fde9a0,
  7. 0xd8934cc7,0xf1aa539d,0xac6ddc1d,0xf0caaf80,
  8. 0xf3401aff,0x980a0d4c,0x26e3a3bc,0x410f94d2,
  9. 0x738c4f02,0xa39ebcfe,0x64399bab,0xc7ac872a,
  10. 0xdc577d43,0xf82afcfd,0x993d28fc,0xb5aaf6b5]
  11. value2 = [
  12. 0xac268692,0x3c7639ab,0xd053e7d4,0x64358e07,
  13. 0x0d6c5c0a,0x8d0a4298,0xbb405f60,0xa10a1755,
  14. 0x0d66dd24,0x61c9e122,0x51f4aad8,0x86023671,
  15. 0xfb681443,0x26dd5538,0x76b17016,0xf3ff3d06,
  16. 0xd891a50f,0xf1a8e0e8,0xac6f3e04,0xf0c8014b,
  17. 0xf342a73a,0x9808cfeb,0x26e1269a,0x410d7bec,
  18. 0x738e895c,0xa39c7998,0x643b4245,0xc7ae4d47,
  19. 0xdc55acf7,0xf82815ad,0x993fe1e1,0xb5a9c95c]
  20. num_32 = []
  21. for i in range(32):
  22. num_32.append(value1[i]^value2[i])
  23. cha = ["v1","v2","v3","v4","v5","v6","v7","v8","v9","v10","v11","v12","v13","v14","v15","v16","v17","v18","v19","v20","v21","v22","v23","v24","v25","v26","v27","v28","v29","v30","v31","v32"]
  24. tab = [
  25. [62, 33, 51, 104, 42, 37, 80, 66, 46, 119, 63, 93, 40, 95, 69, 105, 54, 36, 93, 53, 66, 67, 62, 65, 80, 117, 50, 43, 36, 107, 108, 111],
  26. [ 91, 72, 76, 37, 63, 111, 56, 51, 95, 54, 98, 125, 34, 49, 34, 94, 62, 65, 69, 98, 97, 109, 88, 61, 63, 58, 126, 66, 117, 51, 98, 84],
  27. [ 105, 78, 82, 62, 119, 118, 88, 66, 97, 100, 86, 43, 124, 93, 70, 123, 116, 40, 93, 121, 95, 104, 86, 58, 122, 120, 101, 38, 123, 99, 49, 66],
  28. [ 67, 34, 108, 65, 70, 37, 65, 38, 88, 54, 97, 62, 82, 51, 63, 59, 126, 33, 91, 47, 106, 36, 77, 35, 77, 114, 71, 57, 36, 111, 62, 62],
  29. [ 83, 66, 38, 59, 124, 73, 120, 119, 82, 79, 36, 106, 74, 120, 124, 116, 71, 120, 43, 89, 61, 114, 124, 64, 97, 92, 71, 76, 43, 93, 38, 96],
  30. [ 86, 77, 79, 65, 79, 47, 116, 35, 34, 91, 68, 54, 80, 42, 34, 95, 56, 105, 54, 126, 118, 60, 98, 119, 103, 121, 90, 42, 105, 100, 101, 87],
  31. [ 70, 114, 91, 34, 116, 84, 123, 80, 124, 49, 41, 110, 107, 109, 99, 64, 74, 107, 88, 43, 105, 122, 60, 87, 68, 127, 83, 56, 75, 83, 48, 42],
  32. [ 83, 122, 47, 116, 79, 73, 54, 57, 38, 34, 125, 52, 110, 105, 124, 80, 124, 92, 117, 98, 93, 89, 95, 87, 49, 125, 84, 36, 80, 74, 106, 117],
  33. [ 97, 71, 81, 53, 97, 55, 54, 72, 45, 87, 113, 99, 126, 36, 47, 55, 41, 83, 106, 71, 60, 97, 74, 52, 82, 68, 71, 35, 85, 114, 39, 72],
  34. [ 70, 109, 76, 82, 74, 105, 96, 94, 85, 74, 72, 75, 57, 106, 99, 80, 107, 36, 78, 95, 96, 43, 51, 124, 98, 99, 58, 107, 90, 61, 43, 71],
  35. [ 52, 58, 46, 111, 78, 41, 39, 47, 105, 44, 43, 45, 37, 69, 125, 35, 104, 63, 34, 50, 117, 66, 89, 55, 56, 106, 83, 123, 72, 42, 62, 97],
  36. [ 109, 103, 106, 80, 58, 45, 100, 51, 109, 76, 66, 103, 61, 48, 37, 93, 50, 45, 105, 97, 45, 77, 38, 114, 69, 121, 85, 77, 53, 55, 38, 37],
  37. [ 90, 45, 47, 124, 64, 97, 84, 52, 83, 39, 100, 122, 101, 57, 83, 53, 82, 123, 122, 83, 102, 60, 61, 77, 109, 59, 123, 85, 54, 98, 101, 72],
  38. [ 95, 102, 105, 116, 70, 49, 94, 114, 106, 63, 58, 37, 74, 113, 112, 64, 115, 35, 89, 64, 90, 98, 41, 94, 48, 119, 59, 81, 122, 99, 80, 124],
  39. [ 33, 107, 67, 107, 48, 118, 81, 113, 58, 127, 125, 40, 123, 76, 117, 109, 66, 90, 107, 34, 95, 119, 76, 99, 117, 85, 117, 48, 47, 120, 70, 76],
  40. [ 52, 90, 76, 55, 119, 103, 34, 34, 47, 54, 84, 93, 61, 48, 118, 115, 72, 56, 66, 116, 110, 119, 122, 85, 75, 67, 98, 104, 93, 51, 36, 110],
  41. [ 32, 72, 111, 41, 78, 44, 122, 66, 46, 118, 42, 98, 112, 75, 113, 78, 105, 78, 124, 67, 124, 106, 86, 79, 38, 112, 51, 95, 108, 35, 36, 44],
  42. [ 53, 73, 69, 102, 64, 53, 104, 100, 34, 72, 96, 69, 49, 122, 102, 39, 119, 57, 49, 42, 61, 121, 39, 84, 124, 61, 125, 115, 57, 47, 116, 47],
  43. [ 110, 70, 62, 114, 84, 107, 46, 106, 99, 96, 74, 52, 121, 58, 127, 37, 106, 77, 116, 112, 118, 59, 50, 113, 56, 83, 74, 46, 42, 43, 50, 66],
  44. [ 69, 71, 55, 77, 84, 67, 123, 98, 120, 124, 105, 122, 88, 57, 99, 39, 35, 67, 42, 43, 41, 92, 103, 46, 84, 38, 43, 83, 33, 87, 79, 39],
  45. [ 41, 49, 91, 71, 122, 83, 69, 82, 45, 92, 94, 114, 50, 58, 48, 66, 109, 88, 62, 63, 121, 46, 41, 91, 107, 104, 125, 35, 119, 41, 71, 50],
  46. [ 67, 93, 83, 62, 127, 91, 103, 37, 62, 34, 72, 111, 112, 33, 93, 119, 112, 81, 57, 112, 64, 73, 120, 36, 63, 53, 76, 48, 106, 111, 44, 122],
  47. [ 116, 85, 120, 43, 81, 55, 115, 62, 102, 34, 38, 88, 36, 76, 35, 114, 97, 127, 60, 75, 73, 34, 52, 77, 42, 56, 93, 49, 76, 125, 56, 73],
  48. [ 77, 111, 116, 34, 85, 42, 37, 38, 89, 65, 115, 121, 116, 103, 108, 106, 126, 86, 107, 125, 110, 48, 60, 87, 35, 53, 100, 101, 50, 117, 56, 65],
  49. [ 103, 34, 77, 46, 44, 86, 124, 109, 77, 90, 50, 127, 40, 77, 39, 103, 84, 112, 89, 56, 96, 82, 36, 44, 88, 58, 72, 38, 89, 54, 123, 87],
  50. [ 43, 48, 54, 45, 79, 88, 33, 54, 103, 100, 54, 49, 127, 108, 116, 87, 127, 121, 117, 93, 60, 101, 108, 50, 32, 36, 76, 55, 111, 61, 47, 121],
  51. [ 113, 114, 113, 50, 122, 111, 120, 50, 122, 35, 102, 90, 89, 42, 62, 60, 86, 95, 43, 72, 114, 78, 82, 53, 70, 93, 118, 34, 87, 96, 62, 57],
  52. [ 67, 104, 82, 72, 109, 117, 53, 39, 85, 97, 33, 82, 59, 39, 102, 37, 103, 39, 104, 71, 86, 43, 108, 123, 37, 125, 107, 56, 96, 119, 36, 108],
  53. [ 91, 64, 43, 97, 46, 105, 49, 68, 68, 109, 33, 54, 90, 103, 117, 98, 52, 96, 32, 97, 87, 66, 72, 118, 66, 76, 87, 83, 53, 119, 90, 119],
  54. [ 65, 53, 85, 84, 94, 82, 77, 70, 68, 97, 94, 86, 124, 54, 38, 52, 50, 124, 92, 64, 74, 85, 45, 94, 97, 110, 49, 123, 76, 56, 89, 120],
  55. [ 109, 114, 33, 33, 76, 41, 61, 48, 41, 123, 65, 59, 85, 32, 127, 97, 98, 114, 87, 32, 67, 68, 108, 120, 116, 63, 109, 54, 92, 72, 72, 32],
  56. [ 68, 84, 102, 97, 121, 127, 110, 126, 90, 109, 54, 60, 126, 86, 98, 92, 48, 103, 75, 124, 103, 119, 52, 84, 84, 91, 94, 44, 124, 76, 57, 99]
  57. ]
  58. v1 = Int('v1')
  59. v2 = Int('v2')
  60. v3 = Int('v3')
  61. v4 = Int('v4')
  62. v5 = Int('v5')
  63. v6 = Int('v6')
  64. v7 = Int('v7')
  65. v8 = Int('v8')
  66. v9 = Int('v9')
  67. v10 = Int('v10')
  68. v11 = Int('v11')
  69. v12 = Int('v12')
  70. v13 = Int('v13')
  71. v14 = Int('v14')
  72. v15 = Int('v15')
  73. v16 = Int('v16')
  74. v17 = Int('v17')
  75. v18 = Int('v18')
  76. v19 = Int('v19')
  77. v20 = Int('v20')
  78. v21 = Int('v21')
  79. v22 = Int('v22')
  80. v23 = Int('v23')
  81. v24 = Int('v24')
  82. v25 = Int('v25')
  83. v26 = Int('v26')
  84. v27 = Int('v27')
  85. v28 = Int('v28')
  86. v29 = Int('v29')
  87. v30 = Int('v30')
  88. v31 = Int('v31')
  89. v32 = Int('v32')
  90. s = Solver()
  91. s.add(62 * v1 + 33 * v2 + 51 * v3 + 104 * v4 + 42 * v5 + 37 * v6 + 80 * v7 + 66 * v8 + 46 * v9 + 119 * v10 + 63 * v11 + 93 * v12 + 40 * v13 + 95 * v14 + 69 * v15 + 105 * v16 + 54 * v17 + 36 * v18 + 93 * v19 + 53 * v20 + 66 * v21 + 67 * v22 + 62 * v23 + 65 * v24 + 80 * v25 + 117 * v26 + 50 * v27 + 43 * v28 + 36 * v29 + 107 * v30 + 108 * v31 + 111 * v32 == 170779)
  92. s.add(91 * v1 + 72 * v2 + 76 * v3 + 37 * v4 + 63 * v5 + 111 * v6 + 56 * v7 + 51 * v8 + 95 * v9 + 54 * v10 + 98 * v11 + 125 * v12 + 34 * v13 + 49 * v14 + 34 * v15 + 94 * v16 + 62 * v17 + 65 * v18 + 69 * v19 + 98 * v20 + 97 * v21 + 109 * v22 + 88 * v23 + 61 * v24 + 63 * v25 + 58 * v26 + 126 * v27 + 66 * v28 + 117 * v29 + 51 * v30 + 98 * v31 + 84 * v32 == 180137)
  93. s.add(105 * v1 + 78 * v2 + 82 * v3 + 62 * v4 + 119 * v5 + 118 * v6 + 88 * v7 + 66 * v8 + 97 * v9 + 100 * v10 + 86 * v11 + 43 * v12 + 124 * v13 + 93 * v14 + 70 * v15 + 123 * v16 + 116 * v17 + 40 * v18 + 93 * v19 + 121 * v20 + 95 * v21 + 104 * v22 + 86 * v23 + 58 * v24 + 122 * v25 + 120 * v26 + 101 * v27 + 38 * v28 + 123 * v29 + 99 * v30 + 49 * v31 + 66 * v32 == 211255)
  94. s.add(67 * v1 + 34 * v2 + 108 * v3 + 65 * v4 + 70 * v5 + 37 * v6 + 65 * v7 + 38 * v8 + 88 * v9 + 54 * v10 + 97 * v11 + 62 * v12 + 82 * v13 + 51 * v14 + 63 * v15 + 59 * v16 + 126 * v17 + 33 * v18 + 91 * v19 + 47 * v20 + 106 * v21 + 36 * v22 + 77 * v23 + 35 * v24 + 77 * v25 + 114 * v26 + 71 * v27 + 57 * v28 + 36 * v29 + 111 * v30 + 62 * v31 + 62 * v32 == 160222)
  95. s.add(83 * v1 + 66 * v2 + 38 * v3 + 59 * v4 + 124 * v5 + 73 * v6 + 120 * v7 + 119 * v8 + 82 * v9 + 79 * v10 + 36 * v11 + 106 * v12 + 74 * v13 + 120 * v14 + 124 * v15 + 116 * v16 + 71 * v17 + 120 * v18 + 43 * v19 + 89 * v20 + 61 * v21 + 114 * v22 + 124 * v23 + 64 * v24 + 97 * v25 + 92 * v26 + 71 * v27 + 76 * v28 + 43 * v29 + 93 * v30 + 38 * v31 + 96 * v32 == 198370)
  96. s.add(86 * v1 + 77 * v2 + 79 * v3 + 65 * v4 + 79 * v5 + 47 * v6 + 116 * v7 + 35 * v8 + 34 * v9 + 91 * v10 + 68 * v11 + 54 * v12 + 80 * v13 + 42 * v14 + 34 * v15 + 95 * v16 + 56 * v17 + 105 * v18 + 54 * v19 + 126 * v20 + 118 * v21 + 60 * v22 + 98 * v23 + 119 * v24 + 103 * v25 + 121 * v26 + 90 * v27 + 42 * v28 + 105 * v29 + 100 * v30 + 101 * v31 + 87 * v32 == 189165)
  97. s.add(70 * v1 + 114 * v2 + 91 * v3 + 34 * v4 + 116 * v5 + 84 * v6 + 123 * v7 + 80 * v8 + 124 * v9 + 49 * v10 + 41 * v11 + 110 * v12 + 107 * v13 + 109 * v14 + 99 * v15 + 64 * v16 + 74 * v17 + 107 * v18 + 88 * v19 + 43 * v20 + 105 * v21 + 122 * v22 + 60 * v23 + 87 * v24 + 68 * v25 + 127 * v26 + 83 * v27 + 56 * v28 + 75 * v29 + 83 * v30 + 48 * v31 + 42 * v32 == 199039)
  98. s.add(83 * v1 + 122 * v2 + 47 * v3 + 116 * v4 + 79 * v5 + 73 * v6 + 54 * v7 + 57 * v8 + 38 * v9 + 34 * v10 + 125 * v11 + 52 * v12 + 110 * v13 + 105 * v14 + 124 * v15 + 80 * v16 + 124 * v17 + 92 * v18 + 117 * v19 + 98 * v20 + 93 * v21 + 89 * v22 + 95 * v23 + 87 * v24 + 49 * v25 + 125 * v26 + 84 * v27 + 36 * v28 + 80 * v29 + 74 * v30 + 106 * v31 + 117 * v32 == 197791)
  99. s.add(97 * v1 + 71 * v2 + 81 * v3 + 53 * v4 + 97 * v5 + 55 * v6 + 54 * v7 + 72 * v8 + 45 * v9 + 87 * v10 + 113 * v11 + 99 * v12 + 126 * v13 + 36 * v14 + 47 * v15 + 55 * v16 + 41 * v17 + 83 * v18 + 106 * v19 + 71 * v20 + 60 * v21 + 97 * v22 + 74 * v23 + 52 * v24 + 82 * v25 + 68 * v26 + 71 * v27 + 35 * v28 + 85 * v29 + 114 * v30 + 39 * v31 + 72 * v32 == 172354)
  100. s.add(70 * v1 + 109 * v2 + 76 * v3 + 82 * v4 + 74 * v5 + 105 * v6 + 96 * v7 + 94 * v8 + 85 * v9 + 74 * v10 + 72 * v11 + 75 * v12 + 57 * v13 + 106 * v14 + 99 * v15 + 80 * v16 + 107 * v17 + 36 * v18 + 78 * v19 + 95 * v20 + 96 * v21 + 43 * v22 + 51 * v23 + 124 * v24 + 98 * v25 + 99 * v26 + 58 * v27 + 107 * v28 + 90 * v29 + 61 * v30 + 43 * v31 + 71 * v32 == 188671)
  101. s.add(52 * v1 + 58 * v2 + 46 * v3 + 111 * v4 + 78 * v5 + 41 * v6 + 39 * v7 + 47 * v8 + 105 * v9 + 44 * v10 + 43 * v11 + 45 * v12 + 37 * v13 + 69 * v14 + 125 * v15 + 35 * v16 + 104 * v17 + 63 * v18 + 34 * v19 + 50 * v20 + 117 * v21 + 66 * v22 + 89 * v23 + 55 * v24 + 56 * v25 + 106 * v26 + 83 * v27 + 123 * v28 + 72 * v29 + 42 * v30 + 62 * v31 + 97 * v32 == 157152)
  102. s.add(109 * v1 + 103 * v2 + 106 * v3 + 80 * v4 + 58 * v5 + 45 * v6 + 100 * v7 + 51 * v8 + 109 * v9 + 76 * v10 + 66 * v11 + 103 * v12 + 61 * v13 + 48 * v14 + 37 * v15 + 93 * v16 + 50 * v17 + 45 * v18 + 105 * v19 + 97 * v20 + 45 * v21 + 77 * v22 + 38 * v23 + 114 * v24 + 69 * v25 + 121 * v26 + 85 * v27 + 77 * v28 + 53 * v29 + 55 * v30 + 38 * v31 + 37 * v32 == 175168)
  103. s.add(90 * v1 + 45 * v2 + 47 * v3 + 124 * v4 + 64 * v5 + 97 * v6 + 84 * v7 + 52 * v8 + 83 * v9 + 39 * v10 + 100 * v11 + 122 * v12 + 101 * v13 + 57 * v14 + 83 * v15 + 53 * v16 + 82 * v17 + 123 * v18 + 122 * v19 + 83 * v20 + 102 * v21 + 60 * v22 + 61 * v23 + 77 * v24 + 109 * v25 + 59 * v26 + 123 * v27 + 85 * v28 + 54 * v29 + 98 * v30 + 101 * v31 + 72 * v32 == 192069)
  104. s.add(95 * v1 + 102 * v2 + 105 * v3 + 116 * v4 + 70 * v5 + 49 * v6 + 94 * v7 + 114 * v8 + 106 * v9 + 63 * v10 + 58 * v11 + 37 * v12 + 74 * v13 + 113 * v14 + 112 * v15 + 64 * v16 + 115 * v17 + 35 * v18 + 89 * v19 + 64 * v20 + 90 * v21 + 98 * v22 + 41 * v23 + 94 * v24 + 48 * v25 + 119 * v26 + 59 * v27 + 81 * v28 + 122 * v29 + 99 * v30 + 80 * v31 + 124 * v32 == 195539)
  105. s.add(33 * v1 + 107 * v2 + 67 * v3 + 107 * v4 + 48 * v5 + 118 * v6 + 81 * v7 + 113 * v8 + 58 * v9 + 127 * v10 + 125 * v11 + 40 * v12 + 123 * v13 + 76 * v14 + 117 * v15 + 109 * v16 + 66 * v17 + 90 * v18 + 107 * v19 + 34 * v20 + 95 * v21 + 119 * v22 + 76 * v23 + 99 * v24 + 117 * v25 + 85 * v26 + 117 * v27 + 48 * v28 + 47 * v29 + 120 * v30 + 70 * v31 + 76 * v32 == 210563)
  106. s.add(52 * v1 + 90 * v2 + 76 * v3 + 55 * v4 + 119 * v5 + 103 * v6 + 34 * v7 + 34 * v8 + 47 * v9 + 54 * v10 + 84 * v11 + 93 * v12 + 61 * v13 + 48 * v14 + 118 * v15 + 115 * v16 + 72 * v17 + 56 * v18 + 66 * v19 + 116 * v20 + 110 * v21 + 119 * v22 + 122 * v23 + 85 * v24 + 75 * v25 + 67 * v26 + 98 * v27 + 104 * v28 + 93 * v29 + 51 * v30 + 36 * v31 + 110 * v32 == 185510)
  107. s.add(32 * v1 + 72 * v2 + 111 * v3 + 41 * v4 + 78 * v5 + 44 * v6 + 122 * v7 + 66 * v8 + 46 * v9 + 118 * v10 + 42 * v11 + 98 * v12 + 112 * v13 + 75 * v14 + 113 * v15 + 78 * v16 + 105 * v17 + 78 * v18 + 124 * v19 + 67 * v20 + 124 * v21 + 106 * v22 + 86 * v23 + 79 * v24 + 38 * v25 + 112 * v26 + 51 * v27 + 95 * v28 + 108 * v29 + 35 * v30 + 36 * v31 + 44 * v32 == 190920)
  108. s.add(53 * v1 + 73 * v2 + 69 * v3 + 102 * v4 + 64 * v5 + 53 * v6 + 104 * v7 + 100 * v8 + 34 * v9 + 72 * v10 + 96 * v11 + 69 * v12 + 49 * v13 + 122 * v14 + 102 * v15 + 39 * v16 + 119 * v17 + 57 * v18 + 49 * v19 + 42 * v20 + 61 * v21 + 121 * v22 + 39 * v23 + 84 * v24 + 124 * v25 + 61 * v26 + 125 * v27 + 115 * v28 + 57 * v29 + 47 * v30 + 116 * v31 + 47 * v32 == 177013)
  109. s.add(110 * v1 + 70 * v2 + 62 * v3 + 114 * v4 + 84 * v5 + 107 * v6 + 46 * v7 + 106 * v8 + 99 * v9 + 96 * v10 + 74 * v11 + 52 * v12 + 121 * v13 + 58 * v14 + 127 * v15 + 37 * v16 + 106 * v17 + 77 * v18 + 116 * v19 + 112 * v20 + 118 * v21 + 59 * v22 + 50 * v23 + 113 * v24 + 56 * v25 + 83 * v26 + 74 * v27 + 46 * v28 + 42 * v29 + 43 * v30 + 50 * v31 + 66 * v32 == 188953)
  110. s.add(69 * v1 + 71 * v2 + 55 * v3 + 77 * v4 + 84 * v5 + 67 * v6 + 123 * v7 + 98 * v8 + 120 * v9 + 124 * v10 + 105 * v11 + 122 * v12 + 88 * v13 + 57 * v14 + 99 * v15 + 39 * v16 + 35 * v17 + 67 * v18 + 42 * v19 + 43 * v20 + 41 * v21 + 92 * v22 + 103 * v23 + 46 * v24 + 84 * v25 + 38 * v26 + 43 * v27 + 83 * v28 + 33 * v29 + 87 * v30 + 79 * v31 + 39 * v32 == 175819)
  111. s.add(41 * v1 + 49 * v2 + 91 * v3 + 71 * v4 + 122 * v5 + 83 * v6 + 69 * v7 + 82 * v8 + 45 * v9 + 92 * v10 + 94 * v11 + 114 * v12 + 50 * v13 + 58 * v14 + 48 * v15 + 66 * v16 + 109 * v17 + 88 * v18 + 62 * v19 + 63 * v20 + 121 * v21 + 46 * v22 + 41 * v23 + 91 * v24 + 107 * v25 + 104 * v26 + 125 * v27 + 35 * v28 + 119 * v29 + 41 * v30 + 71 * v31 + 50 * v32 == 179653)
  112. s.add(67 * v1 + 93 * v2 + 83 * v3 + 62 * v4 + 127 * v5 + 91 * v6 + 103 * v7 + 37 * v8 + 62 * v9 + 34 * v10 + 72 * v11 + 111 * v12 + 112 * v13 + 33 * v14 + 93 * v15 + 119 * v16 + 112 * v17 + 81 * v18 + 57 * v19 + 112 * v20 + 64 * v21 + 73 * v22 + 120 * v23 + 36 * v24 + 63 * v25 + 53 * v26 + 76 * v27 + 48 * v28 + 106 * v29 + 111 * v30 + 44 * v31 + 122 * v32 == 180903)
  113. s.add(116 * v1 + 85 * v2 + 120 * v3 + 43 * v4 + 81 * v5 + 55 * v6 + 115 * v7 + 62 * v8 + 102 * v9 + 34 * v10 + 38 * v11 + 88 * v12 + 36 * v13 + 76 * v14 + 35 * v15 + 114 * v16 + 97 * v17 + 127 * v18 + 60 * v19 + 75 * v20 + 73 * v21 + 34 * v22 + 52 * v23 + 77 * v24 + 42 * v25 + 56 * v26 + 93 * v27 + 49 * v28 + 76 * v29 + 125 * v30 + 56 * v31 + 73 * v32 == 165158)
  114. s.add(77 * v1 + 111 * v2 + 116 * v3 + 34 * v4 + 85 * v5 + 42 * v6 + 37 * v7 + 38 * v8 + 89 * v9 + 65 * v10 + 115 * v11 + 121 * v12 + 116 * v13 + 103 * v14 + 108 * v15 + 106 * v16 + 126 * v17 + 86 * v18 + 107 * v19 + 125 * v20 + 110 * v21 + 48 * v22 + 60 * v23 + 87 * v24 + 35 * v25 + 53 * v26 + 100 * v27 + 101 * v28 + 50 * v29 + 117 * v30 + 56 * v31 + 65 * v32 == 192318)
  115. s.add(103 * v1 + 34 * v2 + 77 * v3 + 46 * v4 + 44 * v5 + 86 * v6 + 124 * v7 + 109 * v8 + 77 * v9 + 90 * v10 + 50 * v11 + 127 * v12 + 40 * v13 + 77 * v14 + 39 * v15 + 103 * v16 + 84 * v17 + 112 * v18 + 89 * v19 + 56 * v20 + 96 * v21 + 82 * v22 + 36 * v23 + 44 * v24 + 88 * v25 + 58 * v26 + 72 * v27 + 38 * v28 + 89 * v29 + 54 * v30 + 123 * v31 + 87 * v32 == 181854)
  116. s.add(43 * v1 + 48 * v2 + 54 * v3 + 45 * v4 + 79 * v5 + 88 * v6 + 33 * v7 + 54 * v8 + 103 * v9 + 100 * v10 + 54 * v11 + 49 * v12 + 127 * v13 + 108 * v14 + 116 * v15 + 87 * v16 + 127 * v17 + 121 * v18 + 117 * v19 + 93 * v20 + 60 * v21 + 101 * v22 + 108 * v23 + 50 * v24 + 32 * v25 + 36 * v26 + 76 * v27 + 55 * v28 + 111 * v29 + 61 * v30 + 47 * v31 + 121 * v32 == 181606)
  117. s.add(113 * v1 + 114 * v2 + 113 * v3 + 50 * v4 + 122 * v5 + 111 * v6 + 120 * v7 + 50 * v8 + 122 * v9 + 35 * v10 + 102 * v11 + 90 * v12 + 89 * v13 + 42 * v14 + 62 * v15 + 60 * v16 + 86 * v17 + 95 * v18 + 43 * v19 + 72 * v20 + 114 * v21 + 78 * v22 + 82 * v23 + 53 * v24 + 70 * v25 + 93 * v26 + 118 * v27 + 34 * v28 + 87 * v29 + 96 * v30 + 62 * v31 + 57 * v32 == 186862)
  118. s.add(67 * v1 + 104 * v2 + 82 * v3 + 72 * v4 + 109 * v5 + 117 * v6 + 53 * v7 + 39 * v8 + 85 * v9 + 97 * v10 + 33 * v11 + 82 * v12 + 59 * v13 + 39 * v14 + 102 * v15 + 37 * v16 + 103 * v17 + 39 * v18 + 104 * v19 + 71 * v20 + 86 * v21 + 43 * v22 + 108 * v23 + 123 * v24 + 37 * v25 + 125 * v26 + 107 * v27 + 56 * v28 + 96 * v29 + 119 * v30 + 36 * v31 + 108 * v32 == 182893)
  119. s.add(91 * v1 + 64 * v2 + 43 * v3 + 97 * v4 + 46 * v5 + 105 * v6 + 49 * v7 + 68 * v8 + 68 * v9 + 109 * v10 + 33 * v11 + 54 * v12 + 90 * v13 + 103 * v14 + 117 * v15 + 98 * v16 + 52 * v17 + 96 * v18 + 32 * v19 + 97 * v20 + 87 * v21 + 66 * v22 + 72 * v23 + 118 * v24 + 66 * v25 + 76 * v26 + 87 * v27 + 83 * v28 + 53 * v29 + 119 * v30 + 90 * v31 + 119 * v32 == 184756)
  120. s.add(65 * v1 + 53 * v2 + 85 * v3 + 84 * v4 + 94 * v5 + 82 * v6 + 77 * v7 + 70 * v8 + 68 * v9 + 97 * v10 + 94 * v11 + 86 * v12 + 124 * v13 + 54 * v14 + 38 * v15 + 52 * v16 + 50 * v17 + 124 * v18 + 92 * v19 + 64 * v20 + 74 * v21 + 85 * v22 + 45 * v23 + 94 * v24 + 97 * v25 + 110 * v26 + 49 * v27 + 123 * v28 + 76 * v29 + 56 * v30 + 89 * v31 + 120 * v32 == 190800)
  121. s.add(109 * v1 + 114 * v2 + 33 * v3 + 33 * v4 + 76 * v5 + 41 * v6 + 61 * v7 + 48 * v8 + 41 * v9 + 123 * v10 + 65 * v11 + 59 * v12 + 85 * v13 + 32 * v14 + 127 * v15 + 97 * v16 + 98 * v17 + 114 * v18 + 87 * v19 + 32 * v20 + 67 * v21 + 68 * v22 + 108 * v23 + 120 * v24 + 116 * v25 + 63 * v26 + 109 * v27 + 54 * v28 + 92 * v29 + 72 * v30 + 72 * v31 + 32 * v32 == 182557)
  122. s.add(68 * v1 + 84 * v2 + 102 * v3 + 97 * v4 + 121 * v5 + 127 * v6 + 110 * v7 + 126 * v8 + 90 * v9 + 109 * v10 + 54 * v11 + 60 * v12 + 126 * v13 + 86 * v14 + 98 * v15 + 92 * v16 + 48 * v17 + 103 * v18 + 75 * v19 + 124 * v20 + 103 * v21 + 119 * v22 + 52 * v23 + 84 * v24 + 84 * v25 + 91 * v26 + 94 * v27 + 44 * v28 + 124 * v29 + 76 * v30 + 57 * v31 + 99 * v32 == 212969)
  123. sk = ""
  124. if s.check() == sat:
  125. result = s.model()
  126. sk = str(result)
  127. else:
  128. print("Error")
  129. dict = {}
  130. sk = sk.replace(",", "").replace("]", ",").replace("[", "")
  131. for i in sk.split("\n"):
  132. if i == "":
  133. continue
  134. temp = i.replace(",","").strip().split(" = ")
  135. dict[temp[0]] = temp[1]
  136. print("flag{",end="")
  137. for i in cha:
  138. print(chr(eval(dict[i])),end="")
  139. print("}")

0x06 后记

我只想说,不愧是堆栈结构的虚拟机逆向保护,够复杂,我喜欢。做了这么几个题目,总结出来的最重要的一点就是:不要被贸然增加的代码量所吓住,仔细分析抽丝剥茧,有可能你会发现实际用到的代码量不过表面展示的1/3。当然也有可能你会发现代码量真的很大,不过也没关系,vm类题目的思路还是很清晰的,只要沿着正确的道路坚定不移的走下去,铁杵也能磨成针。

0x07 参考文章

https://blog.csdn.net/m0_46362499/article/details/106073746

https://blog.csdn.net/wlz_lc_4/article/details/104413282

https://blog.csdn.net/m0_48218081/article/details/120386650

https://blog.csdn.net/wlz_lc_4/article/details/105734339

https://www.freesion.com/article/6242316998/#hagemweek4easy_125

https://blog.csdn.net/u012481172/article/details/50904574

https://bbs.pediy.com/thread-259116.htm

http://blog.iyzyi.com/index.php/archives/1252/

  • 发表于 2022-03-01 09:34:57
  • 阅读 ( 6768 )
  • 分类:其他

0 条评论

绿冰壶
绿冰壶

学生

18 篇文章

站长统计