<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh">
	<id>https://game.etao.net/w/index.php?action=history&amp;feed=atom&amp;title=%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%86%E6%9E%90</id>
	<title>虚拟机分析 - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://game.etao.net/w/index.php?action=history&amp;feed=atom&amp;title=%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%86%E6%9E%90"/>
	<link rel="alternate" type="text/html" href="https://game.etao.net/w/index.php?title=%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%86%E6%9E%90&amp;action=history"/>
	<updated>2026-05-06T08:12:11Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://game.etao.net/w/index.php?title=%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%86%E6%9E%90&amp;diff=237&amp;oldid=prev</id>
		<title>127.0.0.1：​创建页面，内容为“{{Ctf_Wiki}} = 虚拟机分析 =  有关虚拟机分析部分, 我们以一道简单的crackme来进行讲解.  对应的&lt;code&gt;crackme&lt;/code&gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/FuelVM.exe FuelVM.exe]  对应的&lt;code&gt;keygenme&lt;/code&gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/fuelvm_keygen.py fuelvm_keygen.py]  对应的&lt;code&gt;IDA数据库&lt;/code&gt;…”</title>
		<link rel="alternate" type="text/html" href="https://game.etao.net/w/index.php?title=%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%88%86%E6%9E%90&amp;diff=237&amp;oldid=prev"/>
		<updated>2023-07-02T12:18:10Z</updated>

		<summary type="html">&lt;p&gt;创建页面，内容为“{{Ctf_Wiki}} = 虚拟机分析 =  有关虚拟机分析部分, 我们以一道简单的crackme来进行讲解.  对应的&amp;lt;code&amp;gt;crackme&amp;lt;/code&amp;gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/FuelVM.exe FuelVM.exe]  对应的&amp;lt;code&amp;gt;keygenme&amp;lt;/code&amp;gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/fuelvm_keygen.py fuelvm_keygen.py]  对应的&amp;lt;code&amp;gt;IDA数据库&amp;lt;/code&amp;gt;…”&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Ctf_Wiki}}&lt;br /&gt;
= 虚拟机分析 =&lt;br /&gt;
&lt;br /&gt;
有关虚拟机分析部分, 我们以一道简单的crackme来进行讲解.&lt;br /&gt;
&lt;br /&gt;
对应的&amp;lt;code&amp;gt;crackme&amp;lt;/code&amp;gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/FuelVM.exe FuelVM.exe]&lt;br /&gt;
&lt;br /&gt;
对应的&amp;lt;code&amp;gt;keygenme&amp;lt;/code&amp;gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/fuelvm_keygen.py fuelvm_keygen.py]&lt;br /&gt;
&lt;br /&gt;
对应的&amp;lt;code&amp;gt;IDA数据库&amp;lt;/code&amp;gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/FuelVM.idb FuelVM.idb]&lt;br /&gt;
&lt;br /&gt;
本题作者设计了一个具有多种指令的简单虚拟机. 我们使用IDA来进行分析. 并为了方便讲解, 我对反汇编出的一些变量重新进行了命名.&lt;br /&gt;
&lt;br /&gt;
== 运行程序 ==&lt;br /&gt;
&lt;br /&gt;
我们运行程序 FuelVM.exe. 界面如下所示&lt;br /&gt;
&lt;br /&gt;
[[File:./figure/start.png|start.png]]&lt;br /&gt;
&lt;br /&gt;
在这个界面中, 我们看到右两个输入框, 一个用于输入用户名Name, 另一个则用于输入密钥Key. 还有两个按钮, Go用于提交输入, 而Exit则用于退出程序.&lt;br /&gt;
&lt;br /&gt;
== 获取用户输入 ==&lt;br /&gt;
&lt;br /&gt;
那么我们就可以从这里入手. 程序想获取用户输入, 需要调用的一个API是&amp;lt;code&amp;gt;GetDlgItemTextA()&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;UINT GetDlgItemTextA(&lt;br /&gt;
  HWND  hDlg,&lt;br /&gt;
  int   nIDDlgItem,&lt;br /&gt;
  LPSTR lpString,&lt;br /&gt;
  int   cchMax&lt;br /&gt;
);&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
获取的输入字符串会保存在&amp;lt;code&amp;gt;lpString&amp;lt;/code&amp;gt;里. 那么我们就可以打开IDA查找有交叉引用&amp;lt;code&amp;gt;GetDlgItemTextA()&amp;lt;/code&amp;gt;的地方.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;asm&amp;quot;&amp;gt;.text:00401142                 push    0Ch             ; cchMax&lt;br /&gt;
.text:00401144                 push    offset inputName ; lpString&lt;br /&gt;
.text:00401149                 push    3F8h            ; nIDDlgItem&lt;br /&gt;
.text:0040114E                 push    [ebp+hWnd]      ; hDlg&lt;br /&gt;
.text:00401151                 call    GetDlgItemTextA&lt;br /&gt;
.text:00401156                 push    0Ch             ; cchMax&lt;br /&gt;
.text:00401158                 push    offset inputKey ; lpString&lt;br /&gt;
.text:0040115D                 push    3F9h            ; nIDDlgItem&lt;br /&gt;
.text:00401162                 push    [ebp+hWnd]      ; hDlg&lt;br /&gt;
.text:00401165                 call    GetDlgItemTextA&lt;br /&gt;
.text:0040116A                 mov     var_a, 0&lt;br /&gt;
.text:00401171                 call    process_input&lt;br /&gt;
.text:00401176                 jmp     short locExit&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
如上, IDA只有这里调用过&amp;lt;code&amp;gt;GetDlgItemTextA&amp;lt;/code&amp;gt;并且调用了两次分别获取&amp;lt;code&amp;gt;inputName&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;inputKey&amp;lt;/code&amp;gt;. 随后初始化了一个变量为0, 因为还不明白这个变量的作用, 因此先重命名为&amp;lt;code&amp;gt;var_a&amp;lt;/code&amp;gt;. 之后进行了一次函数调用并jmp跳转. 因为jmp跳转位置的代码是一些退出程序的代码, 因此我们可以断定上面的这个call, 是在调用处理用户输入的函数. 因此将jmp的位置重命名为&amp;lt;code&amp;gt;locExit&amp;lt;/code&amp;gt;, 函数则重命名为&amp;lt;code&amp;gt;process_input&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== 处理用户输入 ==&lt;br /&gt;
&lt;br /&gt;
我们进入&amp;lt;code&amp;gt;process_input&amp;lt;/code&amp;gt;函数, 该函数仅仅对输入字符串进行了很简单的处理.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;  result = strlength((int)inputName);&lt;br /&gt;
  if ( v1 &amp;gt;= 7 )                                // v1 = length of inputName&lt;br /&gt;
  {&lt;br /&gt;
    *(_DWORD *)&amp;amp;lenOfName = v1;&lt;br /&gt;
    result = strlength((int)inputKey);&lt;br /&gt;
    if ( v2 &amp;gt;= 7 )                              // v2 = length of inputKey&lt;br /&gt;
    {&lt;br /&gt;
      i = 0;&lt;br /&gt;
      do&lt;br /&gt;
      {&lt;br /&gt;
        inputName[i] ^= i;&lt;br /&gt;
        ++i;&lt;br /&gt;
      }&lt;br /&gt;
      while ( i &amp;lt;= *(_DWORD *)&amp;amp;lenOfName );&lt;br /&gt;
      unk_4031CE = i;&lt;br /&gt;
      dword_4031C8 = dword_4035FF;&lt;br /&gt;
      initVM();&lt;br /&gt;
      initVM();&lt;br /&gt;
      __debugbreak();&lt;br /&gt;
      JUMPOUT(*(_DWORD *)&amp;amp;word_4012CE);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  return result;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
首先是这个&amp;lt;code&amp;gt;strlength()&amp;lt;/code&amp;gt;函数. 函数使用&amp;lt;code&amp;gt;cld; repne scasb; not ecx; dec ecx&amp;lt;/code&amp;gt;来计算字符串长度并将结果保存在&amp;lt;code&amp;gt;ecx&amp;lt;/code&amp;gt;里. 是汇编基础知识就不多介绍. 所以我们将该函数重命名为&amp;lt;code&amp;gt;strlength&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;asm&amp;quot;&amp;gt;.text:004011C2 arg_0           = dword ptr  8&lt;br /&gt;
.text:004011C2&lt;br /&gt;
.text:004011C2                 push    ebp&lt;br /&gt;
.text:004011C3                 mov     ebp, esp&lt;br /&gt;
.text:004011C5                 mov     edi, [ebp+arg_0]&lt;br /&gt;
.text:004011C8                 sub     ecx, ecx&lt;br /&gt;
.text:004011CA                 sub     al, al&lt;br /&gt;
.text:004011CC                 not     ecx&lt;br /&gt;
.text:004011CE                 cld&lt;br /&gt;
.text:004011CF                 repne scasb&lt;br /&gt;
.text:004011D1                 not     ecx&lt;br /&gt;
.text:004011D3                 dec     ecx&lt;br /&gt;
.text:004011D4                 leave&lt;br /&gt;
.text:004011D5                 retn    4&lt;br /&gt;
.text:004011D5 strlength       endp&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
而在IDA生成的伪C代码处有&amp;lt;code&amp;gt;v1&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;v2&amp;lt;/code&amp;gt;, 我对其进行了注解, 可以看汇编, 里面是使用&amp;lt;code&amp;gt;ecx&amp;lt;/code&amp;gt;与&amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;进行比较, 而&amp;lt;code&amp;gt;ecx&amp;lt;/code&amp;gt;是字符串的长度, 于是我们可以知道, 这里对输入的要求是: &amp;#039;&amp;#039;inputName 和 inputKey 的长度均不少于 7&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
当&amp;lt;code&amp;gt;inputName&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;inputKey&amp;lt;/code&amp;gt;长度均不少于7时, 那么就可以对输入进行简单的变换. 以下是一个循环&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;      i = 0;&lt;br /&gt;
      do&lt;br /&gt;
      {&lt;br /&gt;
        inputName[i] ^= i;&lt;br /&gt;
        ++i;&lt;br /&gt;
      }&lt;br /&gt;
      while ( i &amp;lt;= *(_DWORD *)&amp;amp;lenOfName );&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
对应的python代码即&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;def obfuscate(username):&lt;br /&gt;
    s = &amp;quot;&amp;quot;&lt;br /&gt;
    for i in range(len(username)):&lt;br /&gt;
        s += chr(ord(username[i]) ^ i)&lt;br /&gt;
    return s&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
函数之后对一些变量进行了赋值(这些并不重要, 就忽略不讲了.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;注册seh&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
== 注册SEH ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;asm&amp;quot;&amp;gt;.text:004012B5                 push    offset seh_handler&lt;br /&gt;
.text:004012BA                 push    large dword ptr fs:0&lt;br /&gt;
.text:004012C1                 mov     large fs:0, esp&lt;br /&gt;
.text:004012C8                 call    initVM&lt;br /&gt;
.text:004012CD                 int     3               ; Trap to Debugger&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;initVM&amp;lt;/code&amp;gt;完成的是一些虚拟机启动前的初始化工作(其实就是对一些寄存器和相关的部分赋初值), 我们之后来讨论. 这里我们关注的是SEH部分. 这里注册了一个SEH句柄, 异常处理函数我重命名为&amp;lt;code&amp;gt;seh_handler&amp;lt;/code&amp;gt;, 并之后使用&amp;lt;code&amp;gt;int 3&amp;lt;/code&amp;gt;手动触发异常. 而在&amp;lt;code&amp;gt;seh_handler&amp;lt;/code&amp;gt;位置, IDA并未正确识别出对应的代码&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;.text:004012D7 seh_handler     db 64h                  ; DATA XREF: process_input+7Do&lt;br /&gt;
.text:004012D8                 dd 58Fh, 0C4830000h, 13066804h, 0FF640040h, 35h, 25896400h&lt;br /&gt;
.text:004012D8                 dd 0&lt;br /&gt;
.text:004012F4                 dd 1B8h, 0F7C93300h, 0F7C033F1h, 0FFC483E1h, 8F64FDEBh&lt;br /&gt;
.text:004012F4                 dd 5, 4C48300h, 40133068h, 35FF6400h, 0&lt;br /&gt;
.text:0040131C                 dd 258964h, 33000000h, 33198BC9h, 83E1F7C0h, 0FDEBFFC4h&lt;br /&gt;
.text:0040131C                 dd 58F64h, 83000000h, 5E6804C4h, 64004013h, 35FFh, 89640000h&lt;br /&gt;
.text:0040131C                 dd 25h, 0C033CC00h, 0C483E1F7h, 83FDEBFFh, 4035FF05h, 0D8B0200h&lt;br /&gt;
.text:0040131C                 dd 4035FFh, 3000B1FFh, 58F0040h, 4031C8h, 31C83D80h, 750A0040h&lt;br /&gt;
.text:0040131C                 dd 0B1FF4176h, 403000h, 31C8058Fh, 3D800040h, 4031C8h&amp;lt;/pre&amp;gt;&lt;br /&gt;
我们可以点击相应位置按下&amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt;键, 将这些数据转换成代码进行识别. (我们需要按下多次c键进行转换), 得到如下代码.&lt;br /&gt;
&lt;br /&gt;
如下, 在&amp;lt;code&amp;gt;seh_handler&amp;lt;/code&amp;gt;位置, 又用类似的方法注册了一个位于&amp;lt;code&amp;gt;401306h&amp;lt;/code&amp;gt;的异常处理函数, 并通过&amp;lt;code&amp;gt;xor ecx,ecx; div ecx&amp;lt;/code&amp;gt;手动触发了一个&amp;lt;code&amp;gt;除0异常&amp;lt;/code&amp;gt;. 而在&amp;lt;code&amp;gt;loc_401301&amp;lt;/code&amp;gt;位置, 这是一个反调试技巧, &amp;lt;code&amp;gt;jmp loc_401301+2&amp;lt;/code&amp;gt;会使得&amp;lt;code&amp;gt;EIP&amp;lt;/code&amp;gt;转向一条指令中间, 使得无法继续调试. 所以我们可以将&amp;lt;code&amp;gt;00401301~00401306&amp;lt;/code&amp;gt;部分的代码&amp;lt;code&amp;gt;nop&amp;lt;/code&amp;gt;掉, 然后在&amp;lt;code&amp;gt;00401306&amp;lt;/code&amp;gt;位置创建一个新函数&amp;lt;code&amp;gt;seh_handler2&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;seh_handler:                            ; DATA XREF: process_input+7Do&lt;br /&gt;
.text:004012D7                 pop     large dword ptr fs:0&lt;br /&gt;
.text:004012DE                 add     esp, 4&lt;br /&gt;
.text:004012E1                 push    401306h&lt;br /&gt;
.text:004012E6                 push    large dword ptr fs:0&lt;br /&gt;
.text:004012ED                 mov     large fs:0, esp&lt;br /&gt;
.text:004012F4                 mov     eax, 1&lt;br /&gt;
.text:004012F9                 xor     ecx, ecx&lt;br /&gt;
.text:004012FB                 div     ecx&lt;br /&gt;
.text:004012FD                 xor     eax, eax&lt;br /&gt;
.text:004012FF                 mul     ecx&lt;br /&gt;
.text:00401301&lt;br /&gt;
.text:00401301 loc_401301:                             ; CODE XREF: .text:00401304j&lt;br /&gt;
.text:00401301                 add     esp, 0FFFFFFFFh&lt;br /&gt;
.text:00401304                 jmp     short near ptr loc_401301+2&lt;br /&gt;
.text:00401306 ; ---------------------------------------------------------------------------&lt;br /&gt;
.text:00401306                 pop     large dword ptr fs:0&lt;br /&gt;
.text:0040130D                 add     esp, 4&lt;br /&gt;
.text:00401310                 push    401330h&lt;br /&gt;
.text:00401315                 push    large dword ptr fs:0&lt;br /&gt;
.text:0040131C                 mov     large fs:0, esp&lt;br /&gt;
.text:00401323                 xor     ecx, ecx&lt;br /&gt;
.text:00401325                 mov     ebx, [ecx]&lt;br /&gt;
.text:00401327                 xor     eax, eax&lt;br /&gt;
.text:00401329                 mul     ecx&amp;lt;/pre&amp;gt;&lt;br /&gt;
类似的, 还有&amp;lt;code&amp;gt;401330h&amp;lt;/code&amp;gt;重命名为&amp;lt;code&amp;gt;seh_handler3&amp;lt;/code&amp;gt;, 而&amp;lt;code&amp;gt;40135Eh&amp;lt;/code&amp;gt;是最后一个注册的异常处理函数, 我们可以推测这才是虚拟机真正的main函数, 因此我们将&amp;lt;code&amp;gt;40135Eh&amp;lt;/code&amp;gt;重命名为&amp;lt;code&amp;gt;vm_main&amp;lt;/code&amp;gt;. (有关SEH和反调试的部分, 可以推荐大家自己去动态调试一番弄清楚)&lt;br /&gt;
&lt;br /&gt;
== 恢复堆栈平衡 ==&lt;br /&gt;
&lt;br /&gt;
我们创建了一个&amp;lt;code&amp;gt;vm_main&amp;lt;/code&amp;gt;函数(重命名后还需要创建函数, IDA才能识别), 然后按下&amp;lt;code&amp;gt;F5&amp;lt;/code&amp;gt;提示失败, 失败的原因则是由于堆栈不平衡导致的. 因此我们可以点击IDA菜单项&amp;lt;code&amp;gt;Options-&amp;amp;gt;General&amp;lt;/code&amp;gt;在右侧勾选&amp;lt;code&amp;gt;stack pointer&amp;lt;/code&amp;gt;. 这样就会显示出对应的栈指针.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;.text:004017F2 000                 jmp     vm_main&lt;br /&gt;
.text:004017F7     ; ---------------------------------------------------------------------------&lt;br /&gt;
.text:004017F7 000                 push    0               ; uType&lt;br /&gt;
.text:004017F9 004                 push    offset aError   ; &amp;amp;quot;Error&amp;amp;quot;&lt;br /&gt;
.text:004017FE 008                 push    offset Text     ; &amp;amp;quot;The key is wrong.&amp;amp;quot;&lt;br /&gt;
.text:00401803 00C                 push    0               ; hWnd&lt;br /&gt;
.text:00401805 010                 call    MessageBoxA&lt;br /&gt;
.text:0040180A&lt;br /&gt;
.text:0040180A     locret_40180A:                          ; CODE XREF: vm_main+492j&lt;br /&gt;
.text:0040180A 000                 leave&lt;br /&gt;
.text:0040180B -04                 leave&lt;br /&gt;
.text:0040180C -08                 leave&lt;br /&gt;
.text:0040180D -0C                 leave&lt;br /&gt;
.text:0040180E -10                 leave&lt;br /&gt;
.text:0040180F -14                 leave&lt;br /&gt;
.text:00401810 -18                 leave&lt;br /&gt;
.text:00401811 -1C                 retn&lt;br /&gt;
.text:00401811     vm_main         endp ; sp-analysis failed&amp;lt;/pre&amp;gt;&lt;br /&gt;
我们来到最下显示不平衡的位置. 最上的&amp;lt;code&amp;gt;jmp vm_main&amp;lt;/code&amp;gt;表明虚拟机内在执行一个循环. 而&amp;lt;code&amp;gt;MessageBoxA&amp;lt;/code&amp;gt;的调用则是显示最后弹出的错误信息. 而在&amp;lt;code&amp;gt;locret_40180A&amp;lt;/code&amp;gt;位置处, 经过多次leave堆栈严重不平衡, 因此我们需要手动恢复堆栈平衡.&lt;br /&gt;
&lt;br /&gt;
这里也很简单, 在&amp;lt;code&amp;gt;0040180A&amp;lt;/code&amp;gt;位置已经堆栈平衡了(000), 因此我们只需要将这一句&amp;lt;code&amp;gt;leave&amp;lt;/code&amp;gt;修改为&amp;lt;code&amp;gt;retn&amp;lt;/code&amp;gt;就可以了. 如下这样&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;.text:0040180A     locret_40180A:                          ; CODE XREF: vm_main+492j&lt;br /&gt;
.text:0040180A 000                 retn&lt;br /&gt;
.text:0040180B     ; ---------------------------------------------------------------------------&lt;br /&gt;
.text:0040180B 004                 leave&lt;br /&gt;
.text:0040180C 004                 leave&lt;br /&gt;
.text:0040180D 004                 leave&amp;lt;/pre&amp;gt;&lt;br /&gt;
然后你就可以发现&amp;lt;code&amp;gt;vm_main&amp;lt;/code&amp;gt;可以F5生成伪C代码了.&lt;br /&gt;
&lt;br /&gt;
== 虚拟机指令分析 ==&lt;br /&gt;
&lt;br /&gt;
说实话, 虚拟机的分析部分是一个比较枯燥的还原过程, 你需要比对各个小部分的操作来判断这是一个怎样的指令, 使用的是哪些寄存器. 像这个crackme中, vm进行的是一个&amp;lt;code&amp;gt;取指-译码-执行&amp;lt;/code&amp;gt;的循环. &amp;lt;code&amp;gt;译码&amp;lt;/code&amp;gt;过程可给予我们的信息最多, 不同的指令都会在这里, 根据它们各自的&amp;lt;code&amp;gt;opcode&amp;lt;/code&amp;gt;, 使用&amp;lt;code&amp;gt;if-else if-else&amp;lt;/code&amp;gt;分支进行区分. 实际的还原过程并不复杂, 但有可能会因为虚拟机实现的指令数量而显得有些乏味.&lt;br /&gt;
&lt;br /&gt;
最后分析出的结果如下:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! opcode&lt;br /&gt;
! value&lt;br /&gt;
|-&lt;br /&gt;
| push&lt;br /&gt;
| 0x0a&lt;br /&gt;
|-&lt;br /&gt;
| pop&lt;br /&gt;
| 0x0b&lt;br /&gt;
|-&lt;br /&gt;
| mov&lt;br /&gt;
| 0x0c&lt;br /&gt;
|-&lt;br /&gt;
| cmp&lt;br /&gt;
| 0x0d&lt;br /&gt;
|-&lt;br /&gt;
| inc&lt;br /&gt;
| 0x0e&lt;br /&gt;
|-&lt;br /&gt;
| dec&lt;br /&gt;
| 0x0f&lt;br /&gt;
|-&lt;br /&gt;
| and&lt;br /&gt;
| 0x1b&lt;br /&gt;
|-&lt;br /&gt;
| or&lt;br /&gt;
| 0x1c&lt;br /&gt;
|-&lt;br /&gt;
| xor&lt;br /&gt;
| 0x1d&lt;br /&gt;
|-&lt;br /&gt;
| check&lt;br /&gt;
| 0xff&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
我们再来看分析后的&amp;lt;code&amp;gt;initVM&amp;lt;/code&amp;gt;函数&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;int initVM()&lt;br /&gt;
{&lt;br /&gt;
  int result; // eax@1&lt;br /&gt;
&lt;br /&gt;
  r1 = 0;&lt;br /&gt;
  r2 = 0;&lt;br /&gt;
  r3 = 0;&lt;br /&gt;
  result = (unsigned __int8)inputName[(unsigned __int8)cur_index];&lt;br /&gt;
  r4 = (unsigned __int8)inputName[(unsigned __int8)cur_index];&lt;br /&gt;
  vm_sp = 0x32;&lt;br /&gt;
  vm_pc = 0;&lt;br /&gt;
  vm_flags_zf = 0;&lt;br /&gt;
  vm_flags_sf = 0;&lt;br /&gt;
  ++cur_index;&lt;br /&gt;
  return result;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
这里有4个通用寄存器(&amp;lt;code&amp;gt;r1/r2/r3/r4&amp;lt;/code&amp;gt;), 1个&amp;lt;code&amp;gt;sp&amp;lt;/code&amp;gt;指针和1个&amp;lt;code&amp;gt;pc&amp;lt;/code&amp;gt;指针, 标志&amp;lt;code&amp;gt;zf&amp;lt;/code&amp;gt;和&amp;lt;code&amp;gt;sf&amp;lt;/code&amp;gt;. 先前我们不知道的&amp;lt;code&amp;gt;var_a&amp;lt;/code&amp;gt;也被重命名为&amp;lt;code&amp;gt;cur_index&amp;lt;/code&amp;gt;, 指向的是&amp;lt;code&amp;gt;inputName&amp;lt;/code&amp;gt;当前正在处理的字符索引.&lt;br /&gt;
&lt;br /&gt;
对于VM实现的多个指令我们就不再多说, 重点来看下&amp;lt;code&amp;gt;check&amp;lt;/code&amp;gt;部分的操作.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;int __fastcall check(int a1)&lt;br /&gt;
{&lt;br /&gt;
  char v1; // al@1&lt;br /&gt;
  int result; // eax@4&lt;br /&gt;
&lt;br /&gt;
  v1 = r1;&lt;br /&gt;
  if ( (unsigned __int8)r1 &amp;lt; 0x21u )&lt;br /&gt;
    v1 = r1 + 0x21;&lt;br /&gt;
  LOBYTE(a1) = cur_index;&lt;br /&gt;
  if ( v1 == inputKey[a1] )&lt;br /&gt;
  {&lt;br /&gt;
    if ( (unsigned __int8)cur_index &amp;gt;= (unsigned __int8)lenOfName )&lt;br /&gt;
      result = MessageBoxA(0, aGoodJobNowWrit, Caption, 0);&lt;br /&gt;
    else&lt;br /&gt;
      result = initVM();&lt;br /&gt;
  }&lt;br /&gt;
  else&lt;br /&gt;
  {&lt;br /&gt;
    result = MessageBoxA(0, Text, Caption, 0);&lt;br /&gt;
  }&lt;br /&gt;
  return result;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
如果&amp;lt;code&amp;gt;r1&amp;lt;/code&amp;gt;中的值跟&amp;lt;code&amp;gt;inputKey[cur_index]&amp;lt;/code&amp;gt;相等, 那么会继续判断是否已经检查完了整个&amp;lt;code&amp;gt;inputName&amp;lt;/code&amp;gt;, 如果没有出错且比对结束, 那么就会弹出&amp;lt;code&amp;gt;Good job! Now write a keygen.&amp;lt;/code&amp;gt;的消息框. 否则会继续&amp;lt;code&amp;gt;initVM&amp;lt;/code&amp;gt;进入下一轮循环.(出错了当然是弹出消息框提示错误了. )&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;cur_index&amp;lt;/code&amp;gt;会在&amp;lt;code&amp;gt;initVM&amp;lt;/code&amp;gt;中自增1, 那么还记得之前在&amp;lt;code&amp;gt;process_input&amp;lt;/code&amp;gt;里有执行2次&amp;lt;code&amp;gt;initVM&amp;lt;/code&amp;gt;吗. 因为有执行2次&amp;lt;code&amp;gt;initVM&amp;lt;/code&amp;gt;, 所以我们的&amp;lt;code&amp;gt;inputKey&amp;lt;/code&amp;gt;的前2位可以是任意字符.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;      unk_4031CE = i;&lt;br /&gt;
      opcode = vm_pc;&lt;br /&gt;
      initVM();&lt;br /&gt;
      initVM();&lt;br /&gt;
      __debugbreak();&lt;br /&gt;
      JUMPOUT(*(_DWORD *)&amp;amp;word_4012CE);&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
故而我们分析完了整个虚拟机, 便可以开始着手编写&amp;lt;code&amp;gt;Keygen&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
对应的&amp;lt;code&amp;gt;keygenme&amp;lt;/code&amp;gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/fuelvm_keygen.py fuelvm_keygen.py]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;$ python2 fuelvm_keygen.py ctf-wiki&lt;br /&gt;
[*] Password for user &amp;#039;ctf-wiki&amp;#039; is: 4mRC*TKJI&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
对应的&amp;lt;code&amp;gt;IDA数据库&amp;lt;/code&amp;gt;可以点击此处下载: [https://github.com/ctf-wiki/ctf-challenges/blob/master/reverse/vm/fuelvm/FuelVM.idb FuelVM.idb]&lt;br /&gt;
&lt;br /&gt;
[[Category: Reverse_Overview]]&lt;/div&gt;</summary>
		<author><name>127.0.0.1</name></author>
	</entry>
</feed>