吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8867|回复: 33
收起左侧

[原创] Lance师傅的Do not lie to me 的vm部分分析

  [复制链接]
currwin 发表于 2014-10-6 11:04
Lance师傅的Do not lie to me vm部分分析

    菜鸟在此,大牛飞过~~~~~~~
    以上仅为个人的一点小小的心得,有什么不对的地方还请各位指出。
         大家好,我是F8。很久没有做cm分析了。这次看到了一个非常有趣的cm,所以就来弄一个简单的分析。请各位大牛们不要喷。
        首先感谢Lance师傅弄了个这么有趣的cm出来,让我学到了不少。这个cm的难度挺高的,当中的vm部分更是让人兴奋不已,特别是对于我这种第一次接触vm的人来说,简直就是最好的教材。所以在这里做一下简单的记录。不对的地方请大家指正。
     个人喜欢热心,也喜欢CB,大家如果觉得好的话还请多多投一下CB啊
        那么,下面正式开始:

----------------------------------请叫我分割线-------------------------------------分割线-----------------------------------割线--------------------------------------线------------------------------
        附件已给出。内含这篇文章里面的所有分析以及分析代码。。如果觉得网页排版太乱的,或者觉得需要原分析代码的,请自行下载。
       收费1cb,应该不贵。。。哈哈

分析附件.zip

534.82 KB, 下载次数: 232, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 4热心值 +4 收起 理由
rain灿 + 1 师父的帖子必须顶
Mrack + 1 先膜拜下F8师傅。
SaberMason + 1 好贴要顶
吾爱扣扣 + 1 膜拜分析VN的大大

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| currwin 发表于 2014-10-6 11:07
本帖最后由 currwin 于 2014-10-6 12:11 编辑

[Asm] 纯文本查看 复制代码
004030f8:    VM_Add                                                //两个结果相加

004030f9:    VM_Pop   Data[0x9]                //XXXX掉
004030fb:    VM_Pop   Data[flag]                //保存一下相加得到的flag

004030fd:    VM_Push  0x0040313c                //下一个判断地址
00403102:    VM_Push  0x00403073                //VM退出地址
00403107:    VM_Push_ebp
      ----------区域处理 分割线③开始---------------------区域处理 分割线③----
00403108:    VM_Push  Data[flag]                //push flag
0040310a:    VM_Push  Data[flag]
0040310c:    VM_Or_Non                                //求得 ~flag
0040310d:    VM_Pop   Data[0x9]                //XXXX掉
0040310f:    VM_Push  0xffffffbe                //~0xffffffbe = 0x41 (CF | ZF)
00403114:    VM_Or_Non                                //实际上求 flag & 0x41
      ----------区域处理 分割线③结束---------------------区域处理 分割线③----
00403115:    VM_Pop   Data[0xa]                //保存或非得到的 flag3
00403117:    VM_Pop   Data[0x9]                //XXXX掉
00403119:    VM_Push  Data[0xa]                //push flag3
0040311b:    VM_Push  Data[0xa]                //push flag3
0040311d:    VM_Or_Non                                //求得 ~flag3
0040311e:    VM_Pop   Data[0x9]                //XXXX掉
00403120:    VM_Push_ebp
00403121:    VM_DecodePoint_Dword                //复制 ~flag3
00403122:    VM_Pop   Data[0xb]                
00403124:    VM_Push  Data[0xb]                //mov Data[0xb], ~flag3

00403126:    VM_Or_Non                                //求回 ~~flag3 = flag3
00403127:    VM_Pop   Data[0x9]                //XXXX掉
00403129:    VM_Push  0xffffffbf                //~ 0xffffffbf = 0x40 (ZF)
0040312e:    VM_Or_Non                                //求得 ~flag3 & 0x40

0040312f:    VM_Pop   Data[0x9]                //XXXX掉
00403131:    VM_Push  0x0004
00403134:    VM_Div                                                // >>4位,
00403135:    VM_Pop   Data[0x9]                //XXXX掉

00403137:    VM_Add                                                //  结果与前面的push ebp的值相加()
00403138:    VM_Pop   Data[0x9]                //XXXX掉
0040313a:    VM_DecodePoint_Dword                //选择跳向的地址:0x0040313c(下一个判断地址)或0x00403073(退出地址)
0040313b:    VM_Jmp   ebp

         对第一个key 的判断说完了,可能让人感觉有点乱,不要紧,我也不懂。尽管如此,还是勉强的来说明一下吧。
         首先,取key的值,并计算 key - ‘0’ = dx 的结果。
         Flag1 = ~dx + 9; (flag) 决定 CF
        Flag2 = dx - 9;  (的flag)决定ZF
        Flag 是由flag1flag2对应的位组成的。由flag1构成(CF , PF, AF, OF)位,由flag2构成其他位。
        Flag = flag1 & 815 + flag2 & ~815


         接着验证: flag3 = flag & 0x41 (CF | ZF) (flag)。 要求 CF ZF 等于1.
                 也就是说 ~dx + 9 < 0 dx - 9 == 0
                于是  0 <= dx <= 9        --> (+0x30)
        结论就是 key = 0x30 ~ 0x39  数字 '0'~'9'

        接着取 ~flag3 & 0x40(ZF) >> 4  = temp 的值。
        这样,如果flag3ZF = 0,那么 temp = 4, 如果 flag3ZF = 1,那么temp = 0.
        最后进行跳跃jmp [ebp+temp]
       这里,temp = 0 的话就直接跳到VM_Retn, temp = 4 的话就跳到 下一个处理

        好了,这段VM的功能就只是用来判断key 是否在 ‘0’ ~ ‘9’ 之间而已。虽然很长呵。下面也是类似的。
[Asm] 纯文本查看 复制代码
0040313c:    VM_Pop   Data[0x9]        //消掉地址: 0x0040313c
0040313e:    VM_Pop   Data[0x9]        //消掉地址: 0x00403073
这样堆栈就平衡了。
00403140:    VM_Push  0x004031a3        //验证Key的函数地址
00403145:    VM_Push  0x00403099        //循环判断Key是否由数字组成的函数地址
0040314a:    VM_Push_ebp

0040314b:    VM_Push  0x00000000        
00403150:    VM_Push  Data[esi]        //取回前面保存的key地址
00403152:    VM_DecodePoint_Byte        //从Key中读取一个数据 key[i]
00403153:    VM_Push_ebp
00403154:    VM_DecodePoint_Dword        //复制这个数据  key[i]
00403155:    VM_Or_Non                        //求非 ~key[i]

00403156:    VM_Pop   Data[0x9]        //XXXX掉
00403158:    VM_Add                                        //0+~key[i] = ~key[i]
00403159:    VM_Pop   Data[flag]        //保存flag1 ~key[i]
0040315b:    VM_Push_ebp
0040315c:    VM_DecodePoint_Dword        //复制上面相加的结果 ~key[i]
0040315d:    VM_Or_Non                        //求回 key[i] 
0040315e:    VM_Pop   Data[0x9]        //保存flag2 key[i]
00403160:    VM_Pop   Data[0xa]        //XXXX掉
      ----------区域处理 分割线①开始---------------------区域处理 分割线①----
00403162:    VM_Push  Data[flag]        //push flag1
00403164:    VM_Push  Data[flag]        //push flag1
00403166:    VM_Or_Non                        //求 ~flag1
00403167:    VM_Pop   Data[0xa]        //XXXX掉
00403169:    VM_Push  0xfffff7ea        // ~0xfffff7ea = 0x815 (OF | CF | AF | PF)
0040316e:    VM_Or_Non                        //求flag1 & 0x815.
0040316f:    VM_Pop   Data[0xa]        //XXXX掉
      ----------区域处理 分割线①结束---------------------区域处理 分割线①----
      ----------区域处理 分割线②开始---------------------区域处理 分割线②----
00403171:    VM_Push  Data[0x9]        //push flag2
00403173:    VM_Push_ebp
00403174:    VM_DecodePoint_Dword        //复制 flag2
00403175:    VM_Or_Non                        //求 ~flag2
00403176:    VM_Pop   Data[0xa]        //XXXX掉
00403178:    VM_Push  0x00000815
0040317d:    VM_Or_Non                        //求 flag2 & ~815
0040317e:    VM_Pop   Data[0xa]        //XXXX掉
      ----------区域处理 分割线②结束---------------------区域处理 分割线②----
00403180:    VM_Add                                        //flag1 & 815 + flag2 & ~815 = a
00403181:    VM_Pop   Data[0xa]        //XXXX掉
00403183:    VM_Pop   Data[0xb]        //保存相加结果 a
      ----------区域处理 分割线③开始---------------------区域处理 分割线③----
00403185:    VM_Push  Data[0xb]        //push a
00403187:    VM_Push  Data[0xb]        //push a
00403189:    VM_Or_Non                        //求 ~a
0040318a:    VM_Pop   Data[0xa]        //XXXX掉
0040318c:    VM_Push  0xffffffbf        //~ffffffbf = 0x40(ZF)
00403191:    VM_Or_Non                        //   实际上求 a & 0x40,判断ZF标志
      ----------区域处理 分割线③结束---------------------区域处理 分割线③----
00403192:    VM_Pop   Data[0xa]        //XXXX掉
00403194:    VM_Push  0x0004
00403197:    VM_Div                                        //左移4位,如果ZF = 1,那么移动结果为4,ZF = 0,则移动结果为 0
00403198:    VM_Push  Data[esi]        //push key地址
0040319a:    VM_Pop   Data[0xe]        //保存 key 地址到 Data[0xe]中
0040319c:    VM_Pop   Data[0xa]        //XXXX掉
0040319e:    VM_Add                                        //ebp + 移位结果
0040319f:    VM_Pop   Data[0xa]
004031a1:    VM_DecodePoint_Dword        //选择跳向位置
004031a2:    VM_Jmp   ebp                        //进行跳转:0x00403099        //循环判断Key是否由数字组成的函数地址, 0x004031a3        //验证Key的函数地址
        123 这段函数与上面的非常相似。作用是判断当前key地址中的值是否为0,是的话就进行下一步的比较,不是的话就跳回去判断key的值是否为数字。
                --> 815 (OF | CF | AF | PF)


        Flag1 = 0 + ~key (的标志位)                --> 决定CF
        Flag2 = key - 0          (的标志位)                --> 决定ZF位。
        Flag = flag1 & 815 + flag2 & ~815
        如果flag ZF = 1,就跳向下一个验证的地方。
        于是,flag2 ZF必须等于0,于是 key = 0...


       接下来就要到验证key的值的地方了:

 楼主| currwin 发表于 2014-10-6 11:06
本帖最后由 currwin 于 2014-10-6 12:01 编辑

         然后,既然我们知道了这些了,那么就来一步一步的跟踪吧。。。。。。才不是呵。数据流中的数据长近1172个,就算单步跟保守估计也得跟10000步左右。所以还是早早放弃,写个程序来翻译一下吧。幸好各个Handle又已经判明白了,翻译起来不是难事。

         首先来进行一下翻译:代码请参考附带文档,翻译.cpp
        结果(部分)如下:
04.jpg
       很长。。。其余的都直接用文本。。。不,还是请你们自行阅读附带的文档。翻译结果.txt
       这里只是特别说几种组合

VM_Push  edi[0x8]
VM_Push  edi[0x8]
VM_OR_NON
这里push 了相同的两个数,然后求或非。这是啥?
来,我来计算一下,首先,或非这样来表示: ~a & ~b
对两个相同的数求或非就是:
~a & ~a = ~a
实际上就是求这个数的非了。

VM_Push  edi[0x8]                                //push a
VM_Push  edi[0x8]                                //push a
VM_Push  edi[0xa]                                //push b
VM_Push  edi[0xa]                                //push b
VM_OR_NON                                                // ~b = ~b & ~b
VM_Pop   edi[0x9]                                //把多余的flag数据pop
VM_Pop   edi[0x9]                                //暂时保存~b
VM_OR_NON                                                // ~a = ~a & ~a
VM_Pop   edi[0xb]                                //把多余的flag数据pop
VM_Push  edi[0x9]                                //取回刚才的数据 ~b
VM_OR_NON                                                // a & b = ~(~a) & ~(~b)
        最终的效果为求 a b 的与: a & b
        这里总共调用了3次或非门,分别用于求 ~a , ~b, 以及 ~a ~b 的或非。翻译一下:
        ~ ( ~a & ~a) & ~ ( ~b & ~b) = ~ (~a) & ~ (~b) = a & b
       这个就是用或非门组成求两数的与的结果了。

       讲到这里,我相信大家已经可以猜到我前面所说的或非门本身就是一个全集的意思了吧。没错,或非门之间的结合可以做到各种逻辑运算的效果。

       好了,有了这些基本的知识,最后还是得一步一步的去日这个VM
       虽然代码有点长,还是得慢慢的去看:
[Asm] 纯文本查看 复制代码
00403030:    VM_Pop   Data[flag]
00403032:    VM_Pop   Data[edi]
00403034:    VM_Pop   Data[esi]
00403036:    VM_Pop   Data[ebp]
00403038:    VM_Pop   Data[esp]
0040303a:    VM_Pop   Data[ebx]
0040303c:    VM_Pop   Data[edx]
0040303e:    VM_Pop   Data[ecx]
00403040:    VM_Pop   Data[eax]

    开始的时候把堆栈中保存的各个寄存器的值pop到数据区中。
[Asm] 纯文本查看 复制代码
00403042:    VM_Push  0x00403073                //退出VM的处理地址
00403047:    VM_Push  0x0040308a                //数据判断的处理地址
0040304c:    VM_Push_ebp                                //保存栈顶
--------------Junk Code 分割线开始------------------------Junk Code 分割线------------------------------
[Asm] 纯文本查看 复制代码
0040304d:    VM_Push  0xfffffeff                // ~0xfffffeff = 0x100
00403052:    VM_Push  Data[flag]
00403054:    VM_Push  Data[flag]
00403056:    VM_Or_Non                                //原标志位求非 = ~flag
00403057:    VM_Pop   Data[0xa]                //  --> 不要运算得到的flag标志
00403059:    VM_Or_Non                                // ~flag & ~0xfffffeff 
0040305a:    VM_Pop   Data[0xa]                //  保存运算得到的flag标志
0040305c:    VM_Pop   Data[0xc]                //  保存或非结果
0040305e:    VM_Push  Data[0xa]                //  入栈flag标志
00403060:    VM_Push  0xffffffbf                // ~0xffffffbf = 0x40
00403065:    VM_Or_Non                                // 求或非
00403066:    VM_Pop   Data[0xa]                // 清除标志位
00403068:    VM_Push  0x0004
0040306b:    VM_Div                                                // flag / 16 == 0
0040306c:    VM_Pop   Data[0xb]
0040306e:    VM_Add
0040306f:    VM_Pop   Data[0xb]                // ...
--------------Junk Code 分割线结束------------------------Junk Code 分割线--------------------------
[Asm] 纯文本查看 复制代码
00403071:    VM_DecodePoint_Dword
00403072:    VM_Jmp   ebp
     好了,虽然我上面浪费了许多空间写了一堆代码,但是仔细一看,在 Junk Code分割线内的代码是没有用的。它只是用一堆垃圾代码来迷惑人的视线而已。幸好堆栈还是平衡的。来抽取出有用的数据吧:
[Asm] 纯文本查看 复制代码
00403042:    VM_Push  0x00403073                //退出VM的处理地址
00403047:    VM_Push  0x0040308a                //数据判断的处理地址
0040304c:    VM_Push_ebp                                //保存栈顶
...
00403071:    VM_DecodePoint_Dword                //ss:[ebp] = 0x0040308a
00403072:    VM_Jmp   ebp                                //jmp 0040308a
      跳到0040308a处执行。顺带堆栈中还有两个地址:0040308a, 00403073。
      00403073 ~ 00403089 为退出VM的代码,这里暂时不展示
    继续跑到下一段
[Asm] 纯文本查看 复制代码
0040308a:    VM_Pop   Data[0xc]                //保存地址:0040308a
0040308c:    VM_Pop   Data[0xd]                //保存地址: 00403073
0040308e:    VM_Push  0x00403558                //push Key数据的地址
00403093:    VM_Pop   Data[0xe]                //保存Key数据的地址
--------------Junk Code 分割线开始------------------------Junk Code 分割线--------------------------
[Asm] 纯文本查看 复制代码
00403095:    VM_Push  Data[0x9]
00403097:    VM_Push  Data[0x9]
00403099:    VM_Pop   Data[0x9]
0040309b:    VM_Pop   Data[0x9]                //混乱一下视线
--------------Junk Code 分割线结束------------------------Junk Code 分割线--------------------------
[Asm] 纯文本查看 复制代码
0040309d:    VM_Push  Data[0xe]                //push Key数据的地址
      ----------区域处理 分割线①开始---------------------区域处理 分割线①----
0040309f:    VM_Push  0x00000001                //push 1
004030a4:    VM_Push  Data[0xe]                //push Key数据的地址
004030a6:    VM_Add                                                //key地址+1(取下一个数据)
004030a7:    VM_Pop   Data[0xa]                //add得到的flag不要
004030a9:    VM_Pop   Data[esi]                //把加后的地址放回esi中
      ----------区域处理 分割线①结束---------------------区域处理 分割线①----
004030ab:    VM_DecodePoint_Byte                //解析Key数据,取得key第一个字节的值
004030ac:    VM_Pop   Data[0xd]                //把该值保存在 Data[0xd]中

004030ae:    VM_Push  0x00000030                //push  0x30, 数字‘0’
004030b3:    VM_Push  Data[0xd]                //push key数据[0]
004030b5:    VM_Push  Data[0xd]                //push key数据[0]
004030b7:    VM_Or_Non                                //求得 ~ key[i]
004030b8:    VM_Pop   Data[0xf]                //不要 运算的flag标志
004030ba:    VM_Add                                                // 0x30 + ~key[i]
004030bb:    VM_Pop   Data[0xf]                //不要运算中的flag标志

004030bd:    VM_Push_ebp
004030be:    VM_DecodePoint_Dword                //复制 0x30 + ~key[i]的结果,保存在堆栈中
004030bf:    VM_Or_Non                                //求非。
     这里先总结一下:0x30 + ~key = a
     然后求 ~a & ~a = ~a。 也就是求 key - 0x30, ( o )算你狠。
[Asm] 纯文本查看 复制代码
004030c0:    VM_Pop   Data[0xf]		//运算得到的flag不要
004030c2:    VM_Pop   Data[0xc]		//保存key[i] - ‘0’的结果,下面记为dx

004030c4:    VM_Push  0x00000009		//push 9
004030c9:    VM_Push  Data[0xc]		//dx结果
004030cb:    VM_Push  Data[0xc]		//dx结果
004030cd:    VM_Or_Non				// ~dx & ~dx = ~dx
004030ce:    VM_Pop   Data[0xf]		//不要flag
004030d0:    VM_Add						// ~dx + 9
004030d1:    VM_Pop   Data[0xf]		//保存~dx + 9得到的flag1

004030d3:    VM_Push_ebp
004030d4:    VM_DecodePoint_Dword		//复制相加结果

004030d5:    VM_Or_Non				//得到 ~(~dx + 9) = dx - 9
004030d6:    VM_Pop   Data[0xe]		//保存dx - 9 得到的flag2
004030d8:    VM_Pop   Data[0x9]		//不要得到的结果
      ----------区域处理 分割线①开始---------------------区域处理 分割线①----
004030da:    VM_Push  Data[0xf]		//push ~dx+9 得到的flag1
004030dc:    VM_Push  Data[0xf]		//push ~dx+9 得到的flag1
004030de:    VM_Or_Non				//将求得 ~flag.

004030df:    VM_Pop   Data[0x9]		//XXX掉
004030e1:    VM_Push  0xfffff7ea		//~0xfffff7ea = 815
004030e6:    VM_Or_Non				//其实是求 ~dx+9 的 flag1 & 815(CF | PF | AF | OF)...后面将要用来判断是否非负,以验证key[i]是否为数字。
      ----------区域处理 分割线①结束---------------------区域处理 分割线①----
004030e7:    VM_Pop   Data[0x9]		//XXX掉

      ----------区域处理 分割线②开始---------------------区域处理 分割线②----
004030e9:    VM_Push  Data[0xe]		//dx - 9 的flag2。注意,这里与上面很相似
004030eb:    VM_Push_ebp
004030ec:    VM_DecodePoint_Dword		//复制dx - 9 的flag2
004030ed:    VM_Or_Non				//取 ~flag2
004030ee:    VM_Pop   Data[0x9]		//XXXX
004030f0:    VM_Push  0x00000815		//这里直接就取0x815了
004030f5:    VM_Or_Non				// ~(~flag2) & ~815 = flag2 & ~815
      ----------区域处理 分割线②结束---------------------区域处理 分割线②----
004030f6:    VM_Pop   Data[0x9]		//XXXX掉
      此时堆栈顶两个便是上面运算的两个flag了。也就是flag2 & ~815 flag1 & 815
             --> 下面待续。。。。

免费评分

参与人数 1热心值 +1 收起 理由
KONEG + 1 已答复!

查看全部评分

 楼主| currwin 发表于 2014-10-6 11:05
本帖最后由 currwin 于 2014-10-6 11:24 编辑

       首先,这个cm的验证分3部分,前两部分相对来说比较容易,只要认真的去看,肯定谁都能做出来的,所以就略去,直接从第三部分开始讲起。

       先给出一下相关的数据:
Key 地址:00403558   -> 存储输入进去的待验证数据
       这就足够了,开始吧。
      先看一下入口处理:
004014C8  /$  60           pushad           ;    -->push寄存器
004014C9  |.  F3:9C         rep pushfd        ;    -->push标志位
004014CB  |.  8BEC         mov ebp,esp                ;保存栈指针
004014CD  |.  83EC 60      sub esp,60        ; 新建空间(0x60,相当于0x18DWORD)
004014D0  |.  8BFC         mov edi,esp       ;    -->把栈顶给edi
004014D2  |. 8D35 30304000 lea esi,[403030]  ; VM_START esi = VM数据流
----------------------------------请叫我分割线-------------------------------------分割线-----------------------------------
004014D8  |>  33C0        xor eax,eax    ;注意这个跳入的箭头,下面每个函数执行完以后都会从这里继续。
004014DA  |.  AC           lods byte ptr [esi]  ;    --> 取一个数据
004014DB  |.  24 0F         and al,0F          ; 取个位序号
004014DD  |.  C1E0 02      shl eax,2          ; * 4 = 取地址
004014E0  |.  8B98 00304000 mov ebx,dword ptr [eax+403000]      ; 取地址表中地址
004014E6  |.  53           push ebx
004014E7  |.  C3           retn                                ;retnebx的地址中去,也就是到达刚才取出的地址处。


      这段代码挺清晰的,分割线上面进行一些初始化的工作。包括保存当前的寄存器消息,新建堆栈空间,保存堆栈指针,初始化VM的数据流等等。
      寄存器下面就是分派VM handle的地方。一目了然,来看看00403000中存储的数据就明白了。
01.jpg
这里存储了12个地址,分别对应于VM12handle,也可以理解为VMIAT表吧。总之上面就是通过对esi中的数据进行解析而得到地址的。对了,我好像还没有给出esi中的内容对吧,现在就给几个。 02.jpg
很多A3啥的。这是啥?我们来一步一步看:
004014D8  |>  33C0        xor eax,eax        ; --> eax = 0
004014DA  |.  AC           lods byte ptr [esi]  ; --> eax = A3
004014DB  |.  24 0F         and al,0F         ; -->.eax = A3 & 0F = 03  (序号)
004014DD  |.  C1E0 02      shl eax,2          ; --> eax = 03*4 = 0C    (偏移)
004014E0  |.  8B98 00304000 mov ebx,dword ptr [eax+403000 ; --> ebx = [00403000+0C] = [0040300C] = 004014E8
004014E6  |.  53           push ebx          ; --> ss:[esp] = 004014E8
004014E7  |.  C3           retn                        ; --> 返回到 SS:[esp] -> 004014E8
这样就可能比较容易理解了,其实这里就是一个函数的分派的地方。而分派到的函数则需要根据esi中的内容来决定!继续跟踪下去吧。到 004014E8 这个地方瞧瞧。

004014E8  \. /EB 16               jmp short 00401500
      先跳一个。到00401500去玩耍。
00401500  |> \33C0       xor eax,eax
00401502  |.  AC         lods byte ptr [esi]              ; 取序号
00401503  |.  C1E0 02    shl eax,2                      ; * 4 = 地址
00401506  |.  8B5D 00    mov ebx,dword ptr [ebp]       ; 取堆顶内容
00401509  |.  891C38    mov dword ptr [edi+eax],ebx    ; 移动到栈空间去
0040150C  |.  83C5 04   add ebp,4                     ; 移动VM帧指针
0040150F  \.^ EB C7     jmp short 004014D8            ; 跳回去VM函数分派地方,进行下一个分派

     接着进行解析:
00401500  |> \33C0       xor eax,eax                   ;--> eax = 0
00401502  |.  AC         lods byte ptr [esi]              ;--> eax = 08
00401503  |.  C1E0 02    shl eax,2                      ; -->* 4 = 偏移
00401506  |.  8B5D 00    mov ebx,dword ptr [ebp]       ;-->ebx = ss:[ebp] = flag寄存器的消息(刚才初始化VMpushfd的内容)
00401509  |.  891C38    mov dword ptr [edi+eax],ebx    ; -->(DWORD)edi[08] = flag
0040150C  |.  83C5 04   add ebp,4                     ;-->移动VM帧指针
0040150F  \.^ EB C7     jmp short 004014D8            ; -->跳回去VM函数分派地方,进行下一个分派

       做了什么?首先取了一个字节的内容,根据这个数据,把ebp顶的数据赋值给edi的空间中去。注意一下,前面还有这么一句:
004014CD  |.  83EC 60      sub esp,60        ; 新建空间(0x60,相当于0x18DWORD)
004014D0  |.  8BFC         mov edi,esp       ;    -->把栈顶给edi
      也就是说,这里把ebp的数据给esp的空间去了。然后又移动了ebp的指针。等等,不觉得这有点像某个语句么:( ﹁ ﹁ ) ~pop dword ptr [XXXXXXXX], 对,简直就像系统对堆栈的操作,不同的只是操作的对象换成帧指针而已。而且,edi(esi) 是保持不变的,自身维护了一个可以操作的空间。O(_)O~这样一来,ebp不就变成了堆栈,edi(esi)反而变成了数据区了么。实际上还真的就是那么一回事:它自身维护一个堆栈一个数据区,来进行代码的运算。

       这个操作Handle处理完以后,就马上跳回去刚才讲到的VM Handle 分派的地方,继续取esi中的数据(0xA3),也就是执行第3号的Handle,继续pop 一个数据到堆中 。然后不断的取数据,然后进行操作,如此反复,完成最终的目的。
      到了这里,我相信esi中数据的作用也就明朗了。这些数据是用来命令程序,驱动程序进行运算的。也就是说,这段VM使得程序变成了数据流的程序。

 楼主| currwin 发表于 2014-10-6 11:05
本帖最后由 currwin 于 2014-10-6 11:43 编辑

      恩恩,啰嗦了这么久,其实也就是把很少的东西讲来讲去而已:
      Esi 保存数据流。 Ebp变成堆栈指针。 Edi 0x60的空间保存数据。 程序使用数据流来驱动。

      好像懂了,又好像不懂对吧。前面我说了啥Handle的,高深得很,装逼装得太厉害了。其实并没有这么高大上。比如说,我前面讲到的3Handle,就完成了一个pop的操作,所以,我把它命名为 VM_POP_[Num],需要一个字节的数据标志出pop到的地方。
好,其他的handle也同样的来处理。

0号:
004015D2  /> \8B75 00   mov esi,dword ptr [ebp]            ; 移动数据流的指针
004015D5  |.  83C5 04   add ebp,4                         ; 移动堆指针
004015D8  \.^ E9 FBFEFFFF  jmp VM_Distribution     ;返回到VM分配
      根据对指针修改数据流指针。表达为: pop esi
命名:    VM_JMP_[ebp]
所需数据:0

1号:
004015A2  /> \8B5D 00   mov ebx,dword ptr [ebp]       ; 取栈数据①
004015A5  |.  8B55 04   mov edx,dword ptr [ebp+4]     ; 取栈数据②
004015A8  |.  F7D3      not ebx                       ; ①取反
004015AA  |.  F7D2      not edx                      ; ②取反
004015AC  |.  23DA      and ebx,edx                  ; 取反结果求与
004015AE  |.  895D 04   mov dword ptr [ebp+4],ebx    ; 最终结果写入栈数据②
004015B1  |.  9C        pushfd
004015B2  |.  8F45 00   pop dword ptr [ebp]     ; 取反导致的flag寄存器值写入栈数据①
004015B5  \.^ E9 1EFFFFFF         jmp VM_Distribution
       取了栈中数据①,②,取反求与,然后写入flag寄存器值以及求与后的值。这其实是或非门,自身就是一个全集了。什么,不懂?不要紧,下面会解析的
命名:    VM_Or_Non
所需数据:0


2号:
00401529  |> \33C0      xor eax,eax
0040152B  |.  AC        lods byte ptr [esi]               ; 取序号
0040152C  |.  C1E0 02   shl eax,2                       ; 取地址
0040152F  |.  83ED 04   sub ebp,4                      ; 移动堆栈指针
00401532  |.  8B1C38   mov ebx,dword ptr [edi+eax]     ; 从数据空间中取出数据
00401535  |.  895D 00   mov dword ptr [ebp],ebx        ; 进入堆栈中
00401538  \.^ EB 9E      jmp short VM_Distribution
     与最初介绍的pop恰好是相反的操作,从数据空间(edi)中取出数据,进入到堆栈(ebp)中。
命名:    VM_PUSH_[Num]
所需数据:1个。  (push 的数据的序号)


3号:
00401500  |> \33C0       xor eax,eax
00401502  |.  AC         lods byte ptr [esi]              ; 取序号
00401503  |.  C1E0 02    shl eax,2                      ; * 4 = 地址
00401506  |.  8B5D 00    mov ebx,dword ptr [ebp]       ; 取堆顶内容
00401509  |.  891C38     mov dword ptr [edi+eax],ebx   ; 移动到栈空间去
0040150C  |.  83C5 04    add ebp,4                     ; 移动VM堆指针
0040150F  \.^ EB C7     mp short VM_Distribution         ; 跳回去VM函数分派地方,进行下一个分派
     作用已经说明,是一个pop的操作
命名:   VM_POP_[Num]
所需数据:1个。   (pop 的数据的序号)


4:
0040154E  /> \33C0      xor eax,eax
00401550  |.  AD        lods dword ptr [esi]              ; DWORD 数据
00401551  |.  83ED 04   sub ebp,4                      ; 入栈
00401554  |.  8945 00   mov dword ptr [ebp],eax
00401557  \.^ E9 7CFFFFFF   jmp VM_Distribution
      依旧是对堆栈进行操作,直接push 一个4直接的数据。
命名:    VM_PUSH_DWORD
所需数据: 4个。   (push 所需的一个 DWORD 大小的立即数)


5:
00401574  |> \8B5D 00  mov ebx,dword ptr [ebp]          ; 取数据①
00401577  |.  8B55 04  mov edx,dword ptr [ebp+4]        ; 取数据②
0040157A  |.  03DA    add ebx,edx                      ; +
0040157C  |.  895D 04  mov dword ptr [ebp+4],ebx       ; 保存相加结果到②中
0040157F  |.  9C      pushfd
00401580  |.  8F45 00   pop dword ptr [ebp]           ; ①中存放相加得到的flag寄存器
00401583  \.^ E9 50FFFFFF  jmp VM_Distribution
     把堆栈中的数据相加。。。
命名:    VM_ADD
所需数据:0


6号:
004015F5  /> \33C0     xor eax,eax
004015F7  |.  66:AD    lods word ptr [esi]              ; WORD大小的数据
004015F9  |.  83ED 02  sub ebp,2                      ; 入堆栈
004015FC  |.  66:8945 00  mov word ptr [ebp],ax
00401600  \.^ E9 D3FEFFFF jmp VM_Distribution
     Push 一个WORD大小的数据进入堆栈。。。呵呵,这个堆栈还不是严格4字节对齐的。
命名:     VM_PUSH_WORD
所需数据:2(push 所需的一个WORD大小的立即数)


7号:
00401623  /> \60         pushad
00401624  |.  896C24 1C  mov dword ptr [esp+1C],ebp   ;  修改eax
00401628  |.  61         popad                        ; --> eax = ebp(堆指针)
00401629  |.  8A08      mov cl,byte ptr [eax]           ;    取除数①(BYTE -> WORD)
0040162B  |.  8B40 02   mov eax,dword ptr [eax+2]     ;    取被除数②(DWORD)
0040162E  |.  83ED 02   sub ebp,2                     ; 移动堆栈
00401631  |.  D3E8      shr eax,cl                     ;    /2^
00401633  |.  8945 04   mov dword ptr [ebp+4],eax     ; 除法结果写入②
00401636  |.  9C        pushfd
00401637  |.  8F45 00    pop dword ptr [ebp]          ; 标志位写入①
0040163A  \.^ E9 99FEFFFF  jmp VM_Distribution
       取堆栈中的两个数据,进行移位,移位结果写回堆栈。前后堆栈变化如下:
       除数①(WORD)      -->   flag(DWORD)
       被除数②(DWORD)   -->  结果②(DWORD)
      就结果来说堆栈大小变大了2个字节。那么我想大家可能已经明白了为啥前面会有push WORD 的操作了吧。
      这个是移位操作,但是理解为除法也是可以的。比如:② = / 2^
命名:   VM_DIV
所需数据:0


8号:
00401657  |> \8BC5       mov eax,ebp
00401659  |.  83ED 04    sub ebp,4
0040165C  |.  8945 00    mov dword ptr [ebp],eax        ; push 堆栈顶指针
0040165F  \.^ E9 74FEFFFF  jmp VM_Distribution
      Push 当前堆栈顶的指针。。。有啥用啊?
命名:    VM_PUSH_EBP
所需数据:0


9号:
004016A7  |> \83C4 60    add esp,60                ; 清除新建的空间
004016AA  |.  9D        popfd                      ; 还原标志flag
004016AB  |.  61        popad                      ; 还原寄存器
004016AC  \.  C3        retn
      与前面初始化VM的时候的操作是恰好相反的,结尾也是retn而不是跳回VM分派的地方。实际上,这里就是退出VM的地方了。
命名:  VM_RETN
所需数据:0


10:
00401682  /> \8B45 00     mov eax,dword ptr [ebp]     ; 取栈顶指针数据
00401685  |.  8B00       mov eax,dword ptr [eax]      ; 取指针指向的数据
00401687  |.  8945 00     mov dword ptr [ebp],eax     ; 写入栈顶
0040168A  \.^ E9 49FEFFFF  jmp VM_Distribution
      很简单的几行代码,只是把栈顶的数据当做一个指针,把它指向的DWORD的数据给还原出来而已。
      如果配合上面讲到的VM_PUSH_EBP 来使用的话,就相当于把栈顶的内容复制了一遍。
命名:    VM_DECODEPOINT_DWORD
所需数据:0


11:
004016AD  /.  33C0        xor eax,eax             ; VM_DECODEPOINT_BYTE
004016AF  |.  8B45 00        mov eax,dword ptr [ebp]
004016B2  |.  8A00           mov al,byte ptr [eax]
004016B4  |.  0FB6C0        movzx eax,al
004016B7  |.  8945 00        mov dword ptr [ebp],eax
004016BA  \.^ E9 19FEFFFF    jmp VM_Distribution
      与10号作用非常相似,只是他还原的目标对象为一个BYTE的数据而已。
命名:    VM_DECODEPOINT_BYTE
所需数据: 0

03.jpg
命名完后大概就是这样的感觉了。

点评

不错哦.  发表于 2014-10-6 11:43
1015274595 发表于 2014-10-6 11:06
分割线后咋就没了呢

点评

插♂得漂亮  发表于 2014-12-9 19:48
说吧,你是自己删,还是我去叫人删了  发表于 2014-10-6 11:11
 楼主| currwin 发表于 2014-10-6 11:07
本帖最后由 currwin 于 2014-10-6 12:16 编辑

[Asm] 纯文本查看 复制代码
004031a3:    VM_Pop   Data[0xa]        //pop 掉跳转地址
004031a5:    VM_Pop   Data[0xa]        //pop 掉跳转地址

004031a7:    VM_Push  0x00403558        //push key存放地址
004031ac:    VM_Push  Data[esi]        //push key结束地址
004031ae:    VM_Push_ebp
004031af:    VM_DecodePoint_Dword        //复制key结束地址
004031b0:    VM_Or_Non                        //求~key结束地址
004031b1:    VM_Pop   Data[0xa]        //XXXX掉
004031b3:    VM_Add                                        //求出 key[0] + ~key[end]
004031b4:    VM_Pop   Data[0xa]        //XXXX掉
004031b6:    VM_Push_ebp                        
004031b7:    VM_DecodePoint_Dword        //复制 key[0] + ~key[end]
004031b8:    VM_Or_Non                        //求反求出 key[end] - key[0] = key长度
004031b9:    VM_Pop   Data[0xa]        //XXXX掉
004031bb:    VM_Pop   Data[0x9]        //保存长度到data[9]中
004031bd:    VM_Push  0x00403073        //push 结束VM的地址

004031c2:    VM_Push  0x00000006        //push 需要比较的长度,6

004031c7:    VM_Push  Data[0x9]        //push key长度 
004031c9:    VM_Push  Data[0x9]        //push key长度 = keyLen
004031cb:    VM_Or_Non                        //求~keyLen
004031cc:    VM_Pop   Data[0xa]        //XXXX掉
004031ce:    VM_Add                                        //6 + ~keyLen
004031cf:    VM_Pop   Data[0xe]        //保存flag1
004031d1:    VM_Push_ebp        
004031d2:    VM_DecodePoint_Dword        //复制6+ ~keyLen
004031d3:    VM_Or_Non                        //计算 ~(6+~keyLen) = keyLen - 6

004031d4:    VM_Pop   Data[0xf]        //保存得到的flag2
004031d6:    VM_Pop   Data[0x9]        //XXXX掉
      ----------区域处理 分割线①开始---------------------区域处理 分割线①----
004031d8:    VM_Push  Data[0xe]        //push flag1
004031da:    VM_Push  Data[0xe]        //push flag1
004031dc:    VM_Or_Non                        //求 ~flag1
004031dd:    VM_Pop   Data[0xa]        //XXXX掉
004031df:    VM_Push  0xfffff7ea        //~fffff7ea = 815 (AF,OF,CF,PF)
004031e4:    VM_Or_Non                        //实际求 flag1 & 815
004031e5:    VM_Pop   Data[0xa]        //XXXX掉
      ----------区域处理 分割线①结束---------------------区域处理 分割线①----
004031e7:    VM_Push  Data[0xf]
004031e9:    VM_Push  Data[0xf]
004031eb:    VM_Or_Non
004031ec:    VM_Pop   Data[0xa]
004031ee:    VM_Push  0x00000815
004031f3:    VM_Or_Non                        //同理的求 flag2 & ~815
004031f4:    VM_Pop   Data[0xa]
     ----------省略不必要的说明-------------------------------
004031f6:    VM_Add                                        // flag = flag1 & 815 + flag2 & ~815
004031f7:    VM_Pop   Data[0xa]        // XXXX掉
004031f9:    VM_Pop   Data[0xc]        // 保存相加结果 flag

004031fb:    VM_Push  0x00403216        //下一处验证的地址
00403200:    VM_Push_ebp                        //push ebp

00403201:    VM_Push  0xffffffbf        //ffffffbf = 40(ZF)
00403206:    VM_Push  Data[0xc]        //push flag
00403208:    VM_Or_Non                        //求 ~flag & 40. 如果flag的ZF = 0,则得到0x40的数,如果 ZF = 1,则得到0

00403209:    VM_Pop   Data[0xa]        //XXXX掉
0040320b:    VM_Push  0x0004
0040320e:    VM_Div                                        //左移4位,0x40 >> 4 = 4. 0 >> 4 = 0
0040320f:    VM_Pop   Data[0xa]        //xxxx掉

00403211:    VM_Add                                        //上面push 的 ebp值与 移位的值相加
00403212:    VM_Pop   Data[0xa]        //来选择应该跳向的地址
00403214:    VM_DecodePoint_Dword        //解码出跳转地址
00403215:    VM_Jmp   ebp
       现在看起来应该是很简单了,就是取了字符串的长度,与6比较,相等的话就跳向下一处验证,失败就直接OUT了。
[Asm] 纯文本查看 复制代码
0040329b:    VM_Push  0x00000035        //key[1] == 0x35 (数字’5’)
...
0040330a:    VM_Push  0x00000034        //key[2] == 0x34 (数字’4’)
...
00403379:    VM_Push  0x00000034        //key[3] == 0x34 (数字’4’)
...
004033e8:    VM_Push  0x00000033        //key[4] == 0x33 (数字’3’)
...
00403457:    VM_Push  0x00000032        //key[5] == 0x32 (数字’2’)

当所有验证成功后
[Asm] 纯文本查看 复制代码
004034b7:    VM_Push  0x090a0b0c        
004034bc:    VM_Pop   Data[edx]        //给 edx = 0x090a0b0c        成功key
004034be:    VM_Push  0x00403073
004034c3:    VM_Jmp   ebp                        //跳到VM退出的地方
VM退出:
[Asm] 纯文本查看 复制代码
00403073:    VM_Pop   Data[0xc]
00403075:    VM_Pop   Data[0xd]
00403077:    VM_Push  Data[eax]
00403079:    VM_Push  Data[ecx]
0040307b:    VM_Push  Data[edx]
0040307d:    VM_Push  Data[ebx]
0040307f:    VM_Push  Data[esp]
00403081:    VM_Push  Data[ebp]
00403083:    VM_Push  Data[esi]
00403085:    VM_Push  Data[edi]
00403087:    VM_Push  Data[flag]
00403089:    VM_Retn
       与进入VM的时候恰好是相反的过程。。就不多说了。
       最终的key754432,这可是要找死人了呵。。。
      上面的注释中很多 XXXX 的字眼,这个可不是什么暗语呵,意思就只是这个数据不要。因为每一个运算的handle都会产生两个数据,而程序通常只需要其中的一个而已。自然的,另外一个就要被废弃了。哈哈。

      写了这么多,总算是弄完这个分析了吖。。。蛋疼的VM,弄得像是在写论文似的。这下我得去好好的休息一会儿了。如果这篇分析存在什么问题的话,还请各位大大指正。

520Kelly 发表于 2014-10-6 11:10
看到VM顿时吓尿了
mycc 发表于 2014-10-6 11:22
一看 VM 就头大 。。。。 牛 2
2314902431 发表于 2014-10-6 11:33
这种发帖方式很特别.主题在楼下.幸好楼层不高

点评

编辑的数据多了,浏览器容易奔溃。分开发是为了减少奔溃几率来着  发表于 2014-10-6 11:49
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-29 15:57

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表