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

0收藏

0点赞

浏览量:179

2022-03-04

举报

0x00前言

vmre的简单知识点研究的差不多了,这篇我们只聊做题,复现分析三道较为复杂的vmre题目,谈谈感受

0x01[defcon2016quals]_baby-re

题目标签:进阶angr利用。

解题过程:

查壳 无壳64位elf文件,ida打开直接反编译main函数。发现函数逻辑非常简单。输入匹配13位的字符串。看到这种和输入,匹配相关的,当然立马就想到angr符号执行一把梭啦。

  1. int __cdecl main(int argc, const char **argv, const char **envp)
  2. {
  3. unsigned int v4; // [rsp+0h] [rbp-60h] BYREF
  4. unsigned int v5; // [rsp+4h] [rbp-5Ch] BYREF
  5. unsigned int v6; // [rsp+8h] [rbp-58h] BYREF
  6. unsigned int v7; // [rsp+Ch] [rbp-54h] BYREF
  7. unsigned int v8; // [rsp+10h] [rbp-50h] BYREF
  8. unsigned int v9; // [rsp+14h] [rbp-4Ch] BYREF
  9. unsigned int v10; // [rsp+18h] [rbp-48h] BYREF
  10. unsigned int v11; // [rsp+1Ch] [rbp-44h] BYREF
  11. unsigned int v12; // [rsp+20h] [rbp-40h] BYREF
  12. unsigned int v13; // [rsp+24h] [rbp-3Ch] BYREF
  13. unsigned int v14; // [rsp+28h] [rbp-38h] BYREF
  14. unsigned int v15; // [rsp+2Ch] [rbp-34h] BYREF
  15. unsigned int v16; // [rsp+30h] [rbp-30h] BYREF
  16. unsigned __int64 v17; // [rsp+38h] [rbp-28h]
  17. v17 = __readfsqword(0x28u);
  18. printf("Var[0]: ");
  19. fflush(_bss_start);
  20. __isoc99_scanf("%d", &v4);
  21. printf("Var[1]: ");
  22. fflush(_bss_start);
  23. __isoc99_scanf("%d", &v5);
  24. printf("Var[2]: ");
  25. fflush(_bss_start);
  26. __isoc99_scanf("%d", &v6);
  27. printf("Var[3]: ");
  28. fflush(_bss_start);
  29. __isoc99_scanf("%d", &v7);
  30. printf("Var[4]: ");
  31. fflush(_bss_start);
  32. __isoc99_scanf("%d", &v8);
  33. printf("Var[5]: ");
  34. fflush(_bss_start);
  35. __isoc99_scanf("%d", &v9);
  36. printf("Var[6]: ");
  37. fflush(_bss_start);
  38. __isoc99_scanf("%d", &v10);
  39. printf("Var[7]: ");
  40. fflush(_bss_start);
  41. __isoc99_scanf("%d", &v11);
  42. printf("Var[8]: ");
  43. fflush(_bss_start);
  44. __isoc99_scanf("%d", &v12);
  45. printf("Var[9]: ");
  46. fflush(_bss_start);
  47. __isoc99_scanf("%d", &v13);
  48. printf("Var[10]: ");
  49. fflush(_bss_start);
  50. __isoc99_scanf("%d", &v14);
  51. printf("Var[11]: ");
  52. fflush(_bss_start);
  53. __isoc99_scanf("%d", &v15);
  54. printf("Var[12]: ");
  55. fflush(_bss_start);
  56. __isoc99_scanf("%d", &v16);
  57. if ( (unsigned __int8)CheckSolution(&v4) )
  58. printf("The flag is: %c%c%c%c%c%c%c%c%c%c%c%c%c\n", v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16);
  59. else
  60. puts("Wrong");
  61. return 0;
  62. }

avoid_addr和 find_addr都肥肠好找

image-20220226101048156

基本的angr脚本不难写,但是我们发现执行起来居然有问题,原因是使用了 __isoc99_scanf这一特殊函数。仅凭我们现有的angr的那点可怜知识,怕是解决不了这个问题了。所以我们需要进一步学习angr的奇特操作。

  1. import angr
  2. import claripy
  3. def main():
  4. proj =angr.Project('./2016baby-re',auto_load_libs=False)
  5. state= proj.factory.entry_state(add_options={angr.options.LAZY_SOLVES})
  6. simgr=proj.factory.simulation_manager(state)
  7. simgr.explore(find=0x40292c,avoid=0x402941)

angr操作进阶:编写自定义函数替代系统函数

首先明确一点:编写自定义函数替代系统函数的目的,是为了控制输入,从而方便我们的输出。

可以通过这样的方式,替换函数:

  1. proj.hook_symbol('__isoc99_scanf', my_scanf(), replace=True)

这样我们就把__isoc99_scanf替换成了我们需要的my_scanf()函数。我们在保持scanf功能不变的情况下,将我们的符号变量存储进去。下面按照需求编写一下my_scanf()函数

  1. class my_scanf(angr.sim_procedure):
  2. def run(self,fmt,ptr):
  3. self.state.mem[ptr].dword =flag_chars[self.state.globals['scanf_count']]
  4. self.state.globals['scanf_count']+=1

当程序每次调用scanf时,my_scanf就会将flag_chars[i]存储到self.state.mem[ptr]当中,这其中ptr参数,其实是scanf传递进来的rdi。为了控制下标,还设置了一个全局变量scanf_count.如此一来,只要angr执行到我们想要到达的分支,那么我们就可以通过solver.eval()的方式将其打印出来。

好了,万事俱备,只欠东风。

解题代码如下:

  1. import angr
  2. import claripy
  3. def main():
  4. proj =angr.Project('./2016baby-re',auto_load_libs=False)
  5. flag_chars =[claripy.BVS('flag_%d' % i,32)for i in range(13)]
  6. class my_scanf(angr.SimProcedure):
  7. def run(self, fmt, ptr):
  8. self.state.mem[ptr].dword = flag_chars[self.state.globals['scanf_count']]
  9. self.state.globals['scanf_count'] += 1
  10. proj.hook_symbol('__isoc99_scanf', my_scanf(), replace=True)
  11. smage=proj.factory.simulation_manager()
  12. smage.one_active.options.add(angr.options.LAZY_SOLVES)## 取代了什么 可以观察一下
  13. smage.one_active.globals['scanf_count'] = 0
  14. smage.explore(find=0x4028E9,avoid=0x402941)
  15. flag = ''.join(chr(smage.one_found.solver.eval(c)) for c in flag_chars)
  16. return flag
  17. if __name__ == '__main__':
  18. print(main())

image-20220226111057250

0x02[2021长安杯] VP

题目标签:40万规模的op_code

题目结构并不复杂,就是opcode的体量大了亿点点。当我看到那份40多万字节的opcode的时候,内心是崩溃的。(不得不吐槽一下出题人,真是超大规模数据集的狂热拥簇。长安杯前两个题目让解32元一次方程就算了,这又来一个40万规模的opcode)

定位主要操作函数也是个手艺活,耐心跟进,直到sub_401840()。看到了熟悉的while(1)switch结构

image-20220226130150027

做vm题目嘛,最重要的还是分割opcode和分析指令集功能:

指令 作用
0x01 加法运算

0x02 乘法运算

0x03 除法运算

0x04 异或运算

0x05 与运算

0x06 或运算
0x09 取值,可看作mov
0x0a 异或运算
0x0b 加法运算
0x0c 乘法运算
0x0e 异或运算
0x12 位运算 相当于 <<
0x13 转移指令
0x14 转移指令
0x15 转移指令
0x16 字符输出,相当于 getchar()
0x17 字符输入,相当于 putchar()
0x18 比较指令,相当于 cmp
0x1b 赋值
0x1f 条件跳转,不符合直接退出的那种
0x27 跳转指令,相当于jmp

vm这玩意,显然是没办法静态调试调出来的,一开始进行了大量的赋值和跳转操作,整的跟花指令似的。把前面的赋值和跳转屏蔽掉(有大佬清点了一下大概是八千多个赋值,算上花指令我要点差不多 16000 多次运行。)flag的接受轮次太多了,需要写脚本辅助调试,然而笔者暂时没有这个能力,找到网上一个大佬的unicorn脚本辅助调试。

  1. from ctypes import addressof
  2. from unicorn import *
  3. from unicorn.x86_const import *
  4. from capstone import *
  5. import binascii
  6. vp_base = 0x401000 # 程序加载的地址
  7. vp_opcode_base = 0x405000 # 输入的地址
  8. vp_stack_base = 0x670000
  9. with open("vp_opcode.bin", "rb") as f:
  10. vp_opcode = f.read() # 读取 vm 运行所需的 opcode 指令
  11. f.close()
  12. with open("vp_code.bin", "rb") as f:
  13. x64_code = f.read() # 读取 vp.exe 的指令
  14. f.close()
  15. with open("vp_stack.bin", "rb") as f:
  16. vp_stack = f.read() # 从 x64dbg 里面dump出来的现成的栈上的数据
  17. f.close()
  18. xxx = [b'\x00', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06', b'\x07', b'\x08', b'\x09', b'\x0a', b'\x0b',
  19. b'\x0c', b'\x0d', b'\x0e', b'\x0f', b'\x10', b'\x11', b'\x12', b'\x13', b'\x14', b'\x15', b'\x16', b'\x17',
  20. b'\x18', b'\x19', b'\x1a', b'\x1b', b'\x1c', b'\x1d', b'\x1e', b'\x1f', b'\x20', b'\x21', b'\x22', b'\x23',
  21. b'\x24', b'\x25', b'\x26', b'\x27', b'\x28', b'\x29', b'\x2a', b'\x2b', b'\x2c', b'\x2d', b'\x2e', b'\x2f',
  22. b'\x30', b'\x31', b'\x32', b'\x33', b'\x34', b'\x35', b'\x36', b'\x37', b'\x38', b'\x39', b'\x3a', b'\x3b',
  23. b'\x3c', b'\x3d', b'\x3e', b'\x3f', b'\x40', b'\x41', b'\x42', b'\x43', b'\x44', b'\x45', b'\x46', b'\x47',
  24. b'\x48', b'\x49', b'\x4a', b'\x4b', b'\x4c', b'\x4d', b'\x4e', b'\x4f', b'\x50', b'\x51', b'\x52', b'\x53',
  25. b'\x54', b'\x55', b'\x56', b'\x57', b'\x58', b'\x59', b'\x5a', b'\x5b', b'\x5c', b'\x5d', b'\x5e', b'\x5f',
  26. b'\x60', b'\x61', b'\x62', b'\x63', b'\x64', b'\x65', b'\x66', b'\x67', b'\x68', b'\x69', b'\x6a', b'\x6b',
  27. b'\x6c', b'\x6d', b'\x6e', b'\x6f', b'\x70', b'\x71', b'\x72', b'\x73', b'\x74', b'\x75', b'\x76', b'\x77',
  28. b'\x78', b'\x79', b'\x7a', b'\x7b', b'\x7c', b'\x7d']
  29. cmp_list = []
  30. rsi_2_6 = []
  31. rsi_2_5 = []
  32. class Unidbg:
  33. def __init__(self, flag, except_hit):
  34. self.except_hit = except_hit
  35. self.hit = 0
  36. self.flag = flag
  37. self.success = False
  38. self.fff = False
  39. self.code_hook = True
  40. mu = Uc(UC_ARCH_X86, UC_MODE_64)
  41. # 程序基址为 0x401000,分配 8 MB内存
  42. mu.mem_map(0x400000, 0x300000)
  43. mu.mem_write(vp_base, x64_code)
  44. # 程序基址为 0x405020,分配 8 MB内存
  45. # mu.mem_map(vp_opcode_base, 0x63000)
  46. mu.mem_write(vp_opcode_base, vp_opcode)
  47. # 程序栈基址为 0x670000,分配 64 KB内存
  48. # mu.mem_map(vp_stack_base, 0x10000)
  49. mu.mem_write(vp_stack_base + 0xA000, vp_stack)
  50. # 设置寄存器的值
  51. mu.reg_write(UC_X86_REG_RAX, 0x0000C35000000000)
  52. mu.reg_write(UC_X86_REG_RBX, 0)
  53. mu.reg_write(UC_X86_REG_RCX, 0x67FDA0)
  54. mu.reg_write(UC_X86_REG_RDX, 0xB56AE0)
  55. mu.reg_write(UC_X86_REG_RSI, 0)
  56. mu.reg_write(UC_X86_REG_RDI, 0)
  57. mu.reg_write(UC_X86_REG_RBP, 0x67FDF0)
  58. mu.reg_write(UC_X86_REG_RSP, 0x67FD78)
  59. mu.reg_write(UC_X86_REG_RIP, 0x401840)
  60. mu.hook_add(UC_HOOK_CODE, self.hook_function_scanf, begin=0x401C82, end=0x401C9D)
  61. mu.hook_add(UC_HOOK_CODE, self.hook_function_putchar, begin=0x401C26, end=0x401C1f)
  62. mu.hook_add(UC_HOOK_CODE, self.hook_code, begin=0x401882, end=0x401884)
  63. # mu.hook_add(UC_HOOK_CODE, self.trace)
  64. # mu.hook_add(UC_HOOK_CODE, self.hook_emu_exit, begin=0x401BF9, end=0x401BFC)
  65. mu.hook_add(UC_HOOK_CODE, self.vp_hook_case18_cmp, begin=0x4019E9, end=0x4019ED)
  66. # patch putchar()
  67. mu.mem_write(0x401C2A, b'\x90\x90\x90\x90\x90')
  68. # patch scanf()
  69. mu.mem_write(0x401C98, b'\x90\x90\x90\x90\x90')
  70. self.mu = mu
  71. self.md = Cs(CS_ARCH_X86, CS_MODE_64)
  72. def solve(self):
  73. try:
  74. self.mu.emu_start(0x401840, 0x402090)
  75. except:
  76. pass
  77. if self.hit > self.except_hit:
  78. self.success = True
  79. return self.success
  80. def trace(self, mu, address, size, data):
  81. if self.fff == False:
  82. return
  83. disasm = self.md.disasm(mu.mem_read(address, size), address)
  84. for i in disasm:
  85. print(i)
  86. def hook_emu_exit(self, mu, address, size, user_data):
  87. if address == 0x401BF9:
  88. pass
  89. # print(self.flag,self.hit)
  90. pass
  91. def vp_hook_case18_cmp(self, mu, address, size, user_data):
  92. if address == 0x4019E9:
  93. v1 = binascii.b2a_hex(mu.mem_read(0x67FDA0 + 8 + mu.reg_read(UC_X86_REG_RAX) * 4, 4))
  94. v2 = binascii.b2a_hex(mu.mem_read(0x67FDA0 + 8 + mu.reg_read(UC_X86_REG_RDX) * 4, 4))
  95. print("cmp ", v1, ",", v2)
  96. cmp_list.append("cmp " + v1.decode() + "," + v2.decode())
  97. v3 = binascii.b2a_hex(mu.mem_read(0x67FDC0, 4))
  98. rsi_2_6.append(v3)
  99. v4 = binascii.b2a_hex(mu.mem_read(0x67FDC0-4, 4))
  100. rsi_2_5.append(v4)
  101. pass
  102. def hook_function_putchar(self, mu, address, size, user_data):
  103. if address == 0x401C2A:
  104. char = mu.reg_read(UC_X86_REG_RCX)
  105. if char == ord(":"):
  106. self.code_hook = False
  107. print(chr(char), end="")
  108. if char == ord("."):
  109. pass
  110. # print("\n",self.flag, self.hit)
  111. def hook_function_scanf(self, mu, address, size, user_data):
  112. y = hex(address)
  113. x = address
  114. if address == 0x401C98:
  115. try:
  116. mu.mem_write(0x67FDA8, xxx[self.flag[self.hit]])
  117. self.hit += 1
  118. except UcError as e:
  119. print(e)
  120. # print(self.flag, binascii.b2a_hex(self.mu.mem_read(0x67FDA8, 4)))
  121. def hook_code(self, mu, address, size, user_data):
  122. code = mu.reg_read(UC_X86_REG_RAX)
  123. if code != 0x27 and code != 0x15 and self.code_hook != True:
  124. print(" " + hex(code)[2:])
  125. # for s in range(6):
  126. # print(hex(0x67FDA0+16*s)[2:].upper(), end=" ")
  127. # hex_mem = binascii.b2a_hex(self.mu.mem_read(0x67FDA0+16*s, 16)).decode()
  128. # for k in range(0,32,2):
  129. # print(hex_mem[k:k+2],end=" ")
  130. # print()
  131. # print("=================================================================================")
  132. if code == 0x17:
  133. print(binascii.b2a_hex(self.mu.mem_read(0x401c98, 32)).decode(), self.hit)
  134. self.fff = True
  135. flag = b'10' * 64
  136. Unidbg(bytes(flag), 127).solve()
  137. for i in cmp_list:
  138. print(i)

来源:https://pimouren.blog.csdn.net/article/details/120386650

作者:pimouren

调试出来的汇编语言翻译成python代码就是

  1. from opcode_s_s_s import num_tab
  2. # num_tab : 最开始的那一堆数据
  3. right_x7 = 0x18ad2
  4. print(hex(right_x7))
  5. flag = b'11' * 64
  6. x6 = 0
  7. x7 = 0x87
  8. for i in range(128):
  9. x6 = x6 + i + flag[i] - 48 + 1
  10. x5 = 0x90
  11. x5 += x6 * 4
  12. x4 = num_tab[x5 // 4]
  13. x7 += x4
  14. print(hex(x7))
  15. if x7 != right_x7:
  16. exit(0)

形象的翻译一下

image-20220226125844942

总的来说,就是从最高层0x87开始下山,每一次可以选择向正下方或者向右下方移动,走到最后一层。路径上经过的所有数之和为0x0x18ad2

这里尝试使用dfs(深度优先搜索)算法。

先写个小程序预估我们要开多大空间的二维数组

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int sum=0;
  6. int num=400000;
  7. int i=1;
  8. while(num>=sum)
  9. {
  10. sum+=i;
  11. i++;
  12. }
  13. printf("%d",i);
  14. return 0;
  15. }

唔,还好,可以接受。

接下来尝试使用dfs算法写出解题脚本
定义一个搜索函数,设计山高,总和两个参数进行深度优先搜索。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int a[900][900];//代码山
  4. int flag[900];
  5. int high;//代码山的行数
  6. void dfs(int h,int ans)
  7. {
  8. if (high>strlen[a])
  9. return;
  10. if(high==strlen[a])
  11. {
  12. if(ans==0x0x18ad2)
  13. {
  14. for(int i=0;i<h;i++)
  15. printf("%d",flag[i]);
  16. return;
  17. }
  18. else return;
  19. }
  20. for(int i=0;i<h;i++)
  21. dfs(h+1,ans+a[h][i]);
  22. }
  23. int main()
  24. {
  25. dfs(1);
  26. return 0;
  27. }

忙活了半天这个题目还是没有完全复现出来,只搞出来一份c++形式的伪代码。思路wojio的应该没什么问题,就是时间复杂度有点高了。网上也没有更多资料了,可能这种题目对于本菜鸡来讲还是难了一些,有没有哪个老哥会的帮个忙(有大佬提供一个思路,可以用贪心算法降低复杂度)

0x03[2020网鼎杯玄武组] _babyvm

题目标签:复杂过程的vm逆向

无壳64位,无main函数,start跟进太困难,shift+f12 发现 Tell Me Your Flag 关键字符串。找到关键函数。

  1. __int64 sub_14007FEF0()
  2. {
  3. char *v0; // rdi
  4. __int64 i; // rcx
  5. char v3[48]; // [rsp+0h] [rbp-20h] BYREF
  6. char v4[144]; // [rsp+30h] [rbp+10h] BYREF
  7. char v5[132]; // [rsp+C0h] [rbp+A0h] BYREF
  8. int v6; // [rsp+144h] [rbp+124h]
  9. int j; // [rsp+164h] [rbp+144h]
  10. __int64 v8; // [rsp+188h] [rbp+168h]
  11. __int64 v9; // [rsp+268h] [rbp+248h]
  12. v0 = v3;
  13. for ( i = 162i64; i; --i )
  14. {
  15. *(_DWORD *)v0 = -858993460;
  16. v0 += 4;
  17. }
  18. sub_140075A41(&unk_1401B5035);
  19. sub_140076CDE("Tell Me Your Flag:\n");
  20. sub_14007805C((__int64)&unk_14016D510, (__int64)v4, 50i64);
  21. dword_1401A3260 = sub_1400804A0(0i64);
  22. sub_14007584D();
  23. if ( sub_1400753CF(v4) != 42 )
  24. goto LABEL_25;
  25. sub_14007584D();
  26. if ( v4[0] != 'f'
  27. || v4[1] != 'l'
  28. || v4[2] != 'a'
  29. || v4[3] != 'g'
  30. || v4[4] != '{'
  31. || v4[41] != '}'
  32. || v4[13] != '-'
  33. || v4[18] != '-'
  34. || v4[23] != '-'
  35. || v4[28] != '-' )
  36. {
  37. goto LABEL_25;
  38. }
  39. sub_14007584D();
  40. v6 = 0;
  41. for ( j = 5; j < 41; ++j )
  42. {
  43. if ( v4[j] >= 48 && v4[j] <= 57 || v4[j] >= 97 && v4[j] <= 102 )
  44. v5[v6++] = v4[j];
  45. }
  46. sub_14007584D();
  47. if ( v6 == 32 )
  48. {
  49. sub_14007584D();
  50. v9 = v6;
  51. if ( (unsigned __int64)v6 >= 0x64 )
  52. j___report_rangecheckfailure();
  53. v5[v9] = 0;
  54. sub_14007584D();
  55. v8 = sub_1400780D9(v5);
  56. sub_14007584D();
  57. if ( (unsigned __int8)sub_140075A78(v8) )
  58. {
  59. sub_140076CDE("Right flag!\n");
  60. }
  61. else
  62. {
  63. sub_14007584D();
  64. sub_140076CDE("Wrong flag!\n");
  65. sub_14007584D();
  66. }
  67. sub_1400788EF("pause");
  68. }
  69. else
  70. {
  71. LABEL_25:
  72. sub_140076CDE("No flag for you!\n");
  73. }
  74. sub_14007878C(v3, &unk_14016D4E0);
  75. return 0i64;
  76. }

分析关键函数中对v4数组中的处理可以得出flag的格式

flag长度42,uuid格式,并且由0-9,a-f组成,也就是小写16进制数。形如:

  1. flag{12345678-0123-5678-0123-567890123456}

接着动调下断点规避检测调试的ifdebug函数

查看chkflag函数

image-20220226134924274

学习了一波大佬的分析,得知该函数的基本逻辑为

  1. 1.首先对输入(flag去掉头尾和-之后转换成16进制数之后)进行md5,然后对flag进行某种变换接着对变换结果进行md5。
  2. 2.再然后分别取输入(flag去掉头尾和-之后转换成16进制数之后)、输入的md5、输入的变换的md5的依次4个字节转换成整数
  3. 3.将上述三个整数进入VM虚拟机进行计算,一共四轮
  4. 4.每一轮的计算结果(4字节)和输入的md5的4字节一起存入一个数组,然后和一个全局变量校验
  5. 5.返回校验结果,如果相同则通过,输出flag正确

接下来就得分析一波vm了。以笔者目前的水平无法完全复现,推荐去看https://blog.csdn.net/Breeze\_CAT/article/details/106373603

使用ida将操作数导出

  1. D0 00 00 00 00 02 01 0D 01 02 04 01 10 00 00 00
  2. 02 01 01 00 00 00 00 02 03 01 01 00 00 00 02 02
  3. 09 01 02 0D 01 02 05 01 08 00 00 00 02 06 14 05
  4. 06 11 04 05 01 B9 79 37 9E 02 05 0C 04 05 0D 04
  5. 03 AD 00 00 00 0C 04 01 02 04 0E 01 03 07 06 19
  6. 00 00 00 D0 01 00 00 00 D0 02 00 00 00 02 02 02
  7. 03 0D 04 02 05 10 05 02 10 05 03 0D 05 0D 04 02
  8. 05 0F 05 02 0F 05 03 0D 05 0D 04 02 05 0F 05 02
  9. 0D 05 0D 04 02 05 0F 05 03 0D 05 0D 02 02 05 0F
  10. 05 03 02 06 0C 05 06 02 06 0C 05 06 02 06 0C 05
  11. 06 02 06 0C 05 06 0D 05 02 01 13 01 FF 00 08 00
  12. 00 00 01 01 03 00 00 00 02 02 08 01 02 E0 08 00
  13. 00 00 01 00 09 00 00 00 02 01 06 00 00 00 02 03
  14. 12 02 03 E0 09 00 00 00 02 04 00 00 00 00 00 00
  15. 90 36 D8 C5 CC 02 79 1F EA 62 81 97 15 3D AE 2F
  16. 6C A9 32 19 91 FE EB CE 69 26 22 04 42 AF F6 AF

分析的opcode表如下

  1. opcode:
  2. 00 xx 00 00 00 0x: 把栈偏移xx mov 给0x寄存器
  3. E0 xx 00 00 00 0x: 把第x个寄存器mov到栈偏移xx处
  4. 01 xx xx xx xx:push xxxxxxxx 立即数
  5. 0d 0x : push xreg
  6. 02 0x : pop reg0x
  7. 03 AD 00 00 00 0C 04 01: call AD
  8. 04 : 返回
  9. 06 19 00 00 00 : jmp 19
  10. 07 : falg=~flag
  11. 08 0x 0y : 加法运算
  12. 09 0x 0y : 减法运算
  13. 0c 0x 0y : 异或
  14. 0f 0x 0y : 与
  15. 10 0x 0y : 或
  16. 11 0x 0y : regx 循环右移regy位
  17. 12 0x 0y : regx 循环左移regy位
  18. 13 0x : 对 0x寄存器中的内容取反
  19. 14 0x 0y : 取余数运算
  20. D0 0x 00 00 00 : 缓冲区 第x部分(输入)拿到栈上
  21. ff : 退出

整理一下虚拟机的操作翻译成c++就是

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int num,num2,num3,input1,input2,input3,newnum;
  6. num=input1;
  7. num2=input2;
  8. num3=input3;
  9. for(i=0;i<=16;i++)
  10. {
  11. x=(15-i)%8;
  12. num=((temp>>x)&0xffffffff|(num<<(32-x))&0xffffffff)&0xffffffff;
  13. num=num^0x9e3779b9;
  14. num=((num<<6)&0xffffffff|(num>>(32-6))&0xffffffff)&0xffffffff;
  15. }
  16. newnum=(num3&num2)^(num & num2)^(num & num3)^(num & num3& num2)^(num | num3| num2);
  17. newnum=newnum^0xffffffff;
  18. }

对代码的理解:input1 input2 input3分别是输入的flag转换成16进制之后的4个字节、flag的md5转换成16进制的4个字节和flag变换后md5后的4个字节。接着对输入的这三个内容进行了一些列的逻辑运算,然后得到的结果(newtemp6)就是要返回去对比的。

但是由于逻辑运算的复杂性,这题的脚本我写不出来,这里只能呈上大佬的脚本,具体解题思路见大佬博客https://blog.csdn.net/Breeze\_CAT/article/details/106373603

  1. import hashlib
  2. import binascii
  3. result=[0xC5D83690, 0x1F7902CC, 0x978162EA, 0x2FAE3D15, 0x1932A96C,0xCEEBFE91, 0x04222669, 0xAFF6AF42] #结果数组
  4. flagmd5='9036d8c5ea6281976ca9321969262204' #flag的md5
  5. vmRetable={
  6. "111":"1",
  7. "100":"1",
  8. "010":"1",
  9. "001":"1",
  10. "110":"0",
  11. "101":"0",
  12. "011":"0",
  13. "000":"0"
  14. }
  15. def transform(input): #变换函数
  16. output=""
  17. for k in range(100):
  18. a=0
  19. input^=0xC3
  20. output+=chr(input&0xff)
  21. for j in range(8):
  22. a^=((input&0xff)>>j)&1
  23. input=(a|2*input)&0xff
  24. m = hashlib.md5()
  25. m.update(output)
  26. output=m.hexdigest()
  27. return output
  28. def getinputbit(a,b,c): #查表复原最后胡的逻辑运算
  29. return vmRetable[a+b+c]
  30. def movere(temp): #复原逻辑运算前的循环位移
  31. i=15
  32. while i>=0:
  33. x=(15-i)%8
  34. temp=((temp>>6)&0xffffffff|(temp<<(32-6))&0xffffffff)&0xffffffff
  35. temp=temp^0x9e3779b9
  36. temp=((temp<<x)&0xffffffff|(temp>>(32-x))&0xffffffff)&0xffffffff
  37. i-=1
  38. return temp
  39. def big2small(data): #大小端转换
  40. return binascii.hexlify(binascii.unhexlify(data)[::-1])
  41. for i in range(0x64): #循环生成变换内容
  42. transoutput=transform(i)
  43. flag=""
  44. for i in range(4):
  45. md5bin=bin(int(result[2*i]))[2:].rjust(32,'0')
  46. transbin=bin(int(big2small(transoutput[8*i:8*i+8]),16))[2:].rjust(32,'0')
  47. resultbin=bin(result[2*i+1]^0xffffffff)[2:].rjust(32,'0')
  48. flagbin="" #先将三个输入转换成二进制
  49. for j in range(32): #对二进制内容查表复原flag的位移后二进制形式
  50. flagbin+=getinputbit(md5bin[j],transbin[j],resultbin[j])
  51. flagtemp=movere(int(flagbin,2)) #复原flag的一部分
  52. flagpart=""
  53. for j in range(4): #将flag从整型转换成字符串型(好md5)
  54. flagpart+=chr((flagtemp>>(j*8))&0xff)
  55. flag+=flagpart
  56. m = hashlib.md5()
  57. m.update(flag)
  58. flagmd5_=m.hexdigest() #对flag求md5
  59. flagstr="flag{"
  60. if(flagmd5_==flagmd5): #如果和真flag的md5相同则计算正确,输出
  61. for i in range(len(flag)):
  62. if(i==4 or i==6 or i==8 or i==10):
  63. flagstr+='-'
  64. flagstr+=hex(ord(flag[i]))[2:].rjust(2,'0')
  65. flagstr+='}'
  66. print flagstr

原文链接:https://blog.csdn.net/Breeze_CAT/article/details/106373603

作者:breezeO_o

0x04后记

这是笔者vmre系列学习的第四篇文章,用这篇文章给笔者的vmre学习之路暂时画上一个不完美的句号。这篇文章选择的三个题目,可以说都具有相当的难度,说实在的,以笔者目前的水平能够完整复现的,只有第一道。

通过做这些较难的题目,虽然没有拿到flag,笔者同样获益匪浅,学到了很多新的知识点,同时,也发现了自己re知识体系的许多欠缺,接下来,是该总结经验,补足短板的时间了。

VMRE 江湖路远,我们有缘再见。

0x05参考文章

https://xz.aliyun.com/t/4137#toc-1

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

https://blog.csdn.net/Breeze_CAT/article/details/106373603

http://www.yxfzedu.com/rs_show/243


(来源:奇安信攻防社区)

(原文链接:https://forum.butian.net/share/1336

发表评论

点击排行

钓鱼邮件-如何快速成为钓鱼达人

一、前言在大型企业边界安全做的越来越好的情况下,不管是 APT 攻击还是红蓝对抗演练,钓鱼邮件攻击使用的...

【渗透实战系列】| 1 -一次对跨境赌博类APP的渗透实战(getshell并获得全部数据)

本次渗透实战主要知识点:1.app抓包,寻找后台地址2.上传绕过,上传shell3.回shell地址的分析4.中国蚁剑工...

HTTPS - 如何抓包并破解 HTTPS 加密数据?

HTTPS 在握手过程中,密钥规格变更协议发送之后所有的数据都已经加密了,有些细节也就看不到了,如果常规的...

无线电安全攻防之GPS定位劫持

一、需要硬件设备HackRFHackRF 连接数据线外部时钟模块(TCXO 时钟模块)天线(淘宝套餐中的 700MHz-2700MH...

记一次Fastadmin后台getshell的渗透记录

1.信息搜集先来看看目标站点的各种信息后端PHP,前端使用layui,路由URL规则看起来像ThinkPHP,那自然想到...

华为防火墙实战配置教程,太全了

防火墙是位于内部网和外部网之间的屏障,它按照系统管理员预先定义好的规则来控制数据包的进出。防火墙是系...

ADCS系列之ESC1、ESC8复现

对原理感兴趣的可以去https://www.specterops.io/assets/resources/Certified_Pre-Owned.pdf看原文,这里只...

【干货分享】利用MSF上线断网主机的思路分享

潇湘信安&nbsp;Author 3had0w潇湘信安一个不会编程、挖SRC、代码审计的安全爱好者,主要分享一些安全经验、...

扫描二维码下载APP