Lance师傅的Do not lie to me 的vm部分分析
Lance师傅的Do not lie to me 的vm部分分析菜鸟在此,大牛飞过~~~~~~~
以上仅为个人的一点小小的心得,有什么不对的地方还请各位指出。 大家好,我是F8。很久没有做cm分析了。这次看到了一个非常有趣的cm,所以就来弄一个简单的分析。请各位大牛们不要喷。 首先感谢Lance师傅弄了个这么有趣的cm出来,让我学到了不少。这个cm的难度挺高的,当中的vm部分更是让人兴奋不已,特别是对于我这种第一次接触vm的人来说,简直就是最好的教材。所以在这里做一下简单的记录。不对的地方请大家指正。 个人喜欢热心,也喜欢CB,大家如果觉得好的话还请多多投一下CB啊 那么,下面正式开始:
----------------------------------请叫我分割线-------------------------------------分割线-----------------------------------割线--------------------------------------线------------------------------ 附件已给出。内含这篇文章里面的所有分析以及分析代码。。如果觉得网页排版太乱的,或者觉得需要原分析代码的,请自行下载。 收费1cb,应该不贵。。。哈哈 本帖最后由 currwin 于 2014-10-6 12:11 编辑
004030f8: VM_Add //两个结果相加
004030f9: VM_Pop Data //XXXX掉
004030fb: VM_Pop Data //保存一下相加得到的flag
004030fd: VM_Push0x0040313c //下一个判断地址
00403102: VM_Push0x00403073 //VM退出地址
00403107: VM_Push_ebp
----------区域处理 分割线③开始---------------------区域处理 分割线③----
00403108: VM_PushData //push flag
0040310a: VM_PushData
0040310c: VM_Or_Non //求得 ~flag
0040310d: VM_Pop Data //XXXX掉
0040310f: VM_Push0xffffffbe //~0xffffffbe = 0x41 (CF | ZF)
00403114: VM_Or_Non //实际上求 flag & 0x41
----------区域处理 分割线③结束---------------------区域处理 分割线③----
00403115: VM_Pop Data //保存或非得到的 flag3
00403117: VM_Pop Data //XXXX掉
00403119: VM_PushData //push flag3
0040311b: VM_PushData //push flag3
0040311d: VM_Or_Non //求得 ~flag3
0040311e: VM_Pop Data //XXXX掉
00403120: VM_Push_ebp
00403121: VM_DecodePoint_Dword //复制 ~flag3
00403122: VM_Pop Data
00403124: VM_PushData //mov Data, ~flag3
00403126: VM_Or_Non //求回 ~~flag3 = flag3
00403127: VM_Pop Data //XXXX掉
00403129: VM_Push0xffffffbf //~ 0xffffffbf = 0x40 (ZF)
0040312e: VM_Or_Non //求得 ~flag3 & 0x40
0040312f: VM_Pop Data //XXXX掉
00403131: VM_Push0x0004
00403134: VM_Div // >>4位,
00403135: VM_Pop Data //XXXX掉
00403137: VM_Add //结果与前面的push ebp的值相加()
00403138: VM_Pop Data //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 是由flag1与flag2对应的位组成的。由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 的值。 这样,如果flag3的ZF = 0,那么 temp = 4, 如果 flag3的ZF = 1,那么temp = 0. 最后进行跳跃jmp 。 这里,temp = 0 的话就直接跳到VM_Retn, temp = 4 的话就跳到 下一个处理
好了,这段VM的功能就只是用来判断key 是否在 ‘0’ ~ ‘9’ 之间而已。虽然很长呵。下面也是类似的。0040313c: VM_Pop Data //消掉地址: 0x0040313c
0040313e: VM_Pop Data //消掉地址: 0x00403073
这样堆栈就平衡了。
00403140: VM_Push0x004031a3 //验证Key的函数地址
00403145: VM_Push0x00403099 //循环判断Key是否由数字组成的函数地址
0040314a: VM_Push_ebp
0040314b: VM_Push0x00000000
00403150: VM_PushData //取回前面保存的key地址
00403152: VM_DecodePoint_Byte //从Key中读取一个数据 key
00403153: VM_Push_ebp
00403154: VM_DecodePoint_Dword //复制这个数据key
00403155: VM_Or_Non //求非 ~key
00403156: VM_Pop Data //XXXX掉
00403158: VM_Add //0+~key = ~key
00403159: VM_Pop Data //保存flag1 ~key
0040315b: VM_Push_ebp
0040315c: VM_DecodePoint_Dword //复制上面相加的结果 ~key
0040315d: VM_Or_Non //求回 key
0040315e: VM_Pop Data //保存flag2 key
00403160: VM_Pop Data //XXXX掉
----------区域处理 分割线①开始---------------------区域处理 分割线①----
00403162: VM_PushData //push flag1
00403164: VM_PushData //push flag1
00403166: VM_Or_Non //求 ~flag1
00403167: VM_Pop Data //XXXX掉
00403169: VM_Push0xfffff7ea // ~0xfffff7ea = 0x815 (OF | CF | AF | PF)
0040316e: VM_Or_Non //求flag1 & 0x815.
0040316f: VM_Pop Data //XXXX掉
----------区域处理 分割线①结束---------------------区域处理 分割线①----
----------区域处理 分割线②开始---------------------区域处理 分割线②----
00403171: VM_PushData //push flag2
00403173: VM_Push_ebp
00403174: VM_DecodePoint_Dword //复制 flag2
00403175: VM_Or_Non //求 ~flag2
00403176: VM_Pop Data //XXXX掉
00403178: VM_Push0x00000815
0040317d: VM_Or_Non //求 flag2 & ~815
0040317e: VM_Pop Data //XXXX掉
----------区域处理 分割线②结束---------------------区域处理 分割线②----
00403180: VM_Add //flag1 & 815 + flag2 & ~815 = a
00403181: VM_Pop Data //XXXX掉
00403183: VM_Pop Data //保存相加结果 a
----------区域处理 分割线③开始---------------------区域处理 分割线③----
00403185: VM_PushData //push a
00403187: VM_PushData //push a
00403189: VM_Or_Non //求 ~a
0040318a: VM_Pop Data //XXXX掉
0040318c: VM_Push0xffffffbf //~ffffffbf = 0x40(ZF)
00403191: VM_Or_Non // 实际上求 a & 0x40,判断ZF标志
----------区域处理 分割线③结束---------------------区域处理 分割线③----
00403192: VM_Pop Data //XXXX掉
00403194: VM_Push0x0004
00403197: VM_Div //左移4位,如果ZF = 1,那么移动结果为4,ZF = 0,则移动结果为 0
00403198: VM_PushData //push key地址
0040319a: VM_Pop Data //保存 key 地址到 Data中
0040319c: VM_Pop Data //XXXX掉
0040319e: VM_Add //ebp + 移位结果
0040319f: VM_Pop Data
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 12:01 编辑
然后,既然我们知道了这些了,那么就来一步一步的跟踪吧。。。。。。才不是呵。数据流中的数据长近1172个,就算单步跟保守估计也得跟10000步左右。所以还是早早放弃,写个程序来翻译一下吧。幸好各个Handle又已经判明白了,翻译起来不是难事。
首先来进行一下翻译:代码请参考附带文档,翻译.cpp 结果(部分)如下: 很长。。。其余的都直接用文本。。。不,还是请你们自行阅读附带的文档。翻译结果.txt 这里只是特别说几种组合
①VM_Pushedi[0x8]VM_Pushedi[0x8]VM_OR_NON这里push 了相同的两个数,然后求或非。这是啥?来,我来计算一下,首先,或非这样来表示: ~a & ~b对两个相同的数求或非就是:~a & ~a = ~a。实际上就是求这个数的非了。
②VM_Pushedi[0x8] //push aVM_Pushedi[0x8] //push aVM_Pushedi[0xa] //push bVM_Pushedi[0xa] //push bVM_OR_NON // ~b = ~b & ~b VM_Pop edi[0x9] //把多余的flag数据pop掉VM_Pop edi[0x9] //暂时保存~bVM_OR_NON // ~a = ~a & ~aVM_Pop edi[0xb] //把多余的flag数据pop掉VM_Pushedi[0x9] //取回刚才的数据 ~bVM_OR_NON // a & b = ~(~a) & ~(~b) 最终的效果为求 a 与 b 的与: a & b。 这里总共调用了3次或非门,分别用于求 ~a , ~b, 以及 ~a 与 ~b 的或非。翻译一下: ~ ( ~a & ~a) & ~ ( ~b & ~b) = ~ (~a) & ~ (~b) = a & b 这个就是用或非门组成求两数的与的结果了。
讲到这里,我相信大家已经可以猜到我前面所说的或非门本身就是一个全集的意思了吧。没错,或非门之间的结合可以做到各种逻辑运算的效果。
好了,有了这些基本的知识,最后还是得一步一步的去日这个VM。 虽然代码有点长,还是得慢慢的去看:00403030: VM_Pop Data
00403032: VM_Pop Data
00403034: VM_Pop Data
00403036: VM_Pop Data
00403038: VM_Pop Data
0040303a: VM_Pop Data
0040303c: VM_Pop Data
0040303e: VM_Pop Data
00403040: VM_Pop Data
开始的时候把堆栈中保存的各个寄存器的值pop到数据区中。00403042: VM_Push0x00403073 //退出VM的处理地址
00403047: VM_Push0x0040308a //数据判断的处理地址
0040304c: VM_Push_ebp //保存栈顶--------------Junk Code 分割线开始------------------------Junk Code 分割线------------------------------0040304d: VM_Push0xfffffeff // ~0xfffffeff = 0x100
00403052: VM_PushData
00403054: VM_PushData
00403056: VM_Or_Non //原标志位求非 = ~flag
00403057: VM_Pop Data //--> 不要运算得到的flag标志
00403059: VM_Or_Non // ~flag & ~0xfffffeff
0040305a: VM_Pop Data //保存运算得到的flag标志
0040305c: VM_Pop Data //保存或非结果
0040305e: VM_PushData //入栈flag标志
00403060: VM_Push0xffffffbf // ~0xffffffbf = 0x40
00403065: VM_Or_Non // 求或非
00403066: VM_Pop Data // 清除标志位
00403068: VM_Push0x0004
0040306b: VM_Div // flag / 16 == 0
0040306c: VM_Pop Data
0040306e: VM_Add
0040306f: VM_Pop Data // ...--------------Junk Code 分割线结束------------------------Junk Code 分割线--------------------------00403071: VM_DecodePoint_Dword
00403072: VM_Jmp ebp 好了,虽然我上面浪费了许多空间写了一堆代码,但是仔细一看,在 Junk Code分割线内的代码是没有用的。它只是用一堆垃圾代码来迷惑人的视线而已。幸好堆栈还是平衡的。来抽取出有用的数据吧:00403042: VM_Push0x00403073 //退出VM的处理地址
00403047: VM_Push0x0040308a //数据判断的处理地址
0040304c: VM_Push_ebp //保存栈顶
...
00403071: VM_DecodePoint_Dword //ss: = 0x0040308a
00403072: VM_Jmp ebp //jmp 0040308a 跳到0040308a处执行。顺带堆栈中还有两个地址:0040308a, 00403073。 00403073 ~ 00403089 为退出VM的代码,这里暂时不展示 继续跑到下一段0040308a: VM_Pop Data //保存地址:0040308a
0040308c: VM_Pop Data //保存地址: 00403073
0040308e: VM_Push0x00403558 //push Key数据的地址
00403093: VM_Pop Data //保存Key数据的地址--------------Junk Code 分割线开始------------------------Junk Code 分割线--------------------------00403095: VM_PushData
00403097: VM_PushData
00403099: VM_Pop Data
0040309b: VM_Pop Data //混乱一下视线--------------Junk Code 分割线结束------------------------Junk Code 分割线--------------------------0040309d: VM_PushData //push Key数据的地址
----------区域处理 分割线①开始---------------------区域处理 分割线①----
0040309f: VM_Push0x00000001 //push 1
004030a4: VM_PushData //push Key数据的地址
004030a6: VM_Add //key地址+1(取下一个数据)
004030a7: VM_Pop Data //add得到的flag不要
004030a9: VM_Pop Data //把加后的地址放回esi中
----------区域处理 分割线①结束---------------------区域处理 分割线①----
004030ab: VM_DecodePoint_Byte //解析Key数据,取得key第一个字节的值
004030ac: VM_Pop Data //把该值保存在 Data中
004030ae: VM_Push0x00000030 //push0x30, 数字‘0’
004030b3: VM_PushData //push key数据
004030b5: VM_PushData //push key数据
004030b7: VM_Or_Non //求得 ~ key
004030b8: VM_Pop Data //不要 运算的flag标志
004030ba: VM_Add // 0x30 + ~key
004030bb: VM_Pop Data //不要运算中的flag标志
004030bd: VM_Push_ebp
004030be: VM_DecodePoint_Dword //复制 0x30 + ~key的结果,保存在堆栈中
004030bf: VM_Or_Non //求非。 这里先总结一下:0x30 + ~key = a 然后求 ~a & ~a = ~a。 也就是求 key - 0x30, ( ⊙ o ⊙ )算你狠。004030c0: VM_Pop Data //运算得到的flag不要
004030c2: VM_Pop Data //保存key - ‘0’的结果,下面记为dx
004030c4: VM_Push0x00000009 //push 9
004030c9: VM_PushData //dx结果
004030cb: VM_PushData //dx结果
004030cd: VM_Or_Non // ~dx & ~dx = ~dx
004030ce: VM_Pop Data //不要flag
004030d0: VM_Add // ~dx + 9
004030d1: VM_Pop Data //保存~dx + 9得到的flag1
004030d3: VM_Push_ebp
004030d4: VM_DecodePoint_Dword //复制相加结果
004030d5: VM_Or_Non //得到 ~(~dx + 9) = dx - 9
004030d6: VM_Pop Data //保存dx - 9 得到的flag2
004030d8: VM_Pop Data //不要得到的结果
----------区域处理 分割线①开始---------------------区域处理 分割线①----
004030da: VM_PushData //push ~dx+9 得到的flag1
004030dc: VM_PushData //push ~dx+9 得到的flag1
004030de: VM_Or_Non //将求得 ~flag.
004030df: VM_Pop Data //XXX掉
004030e1: VM_Push0xfffff7ea //~0xfffff7ea = 815
004030e6: VM_Or_Non //其实是求 ~dx+9 的 flag1 & 815(CF | PF | AF | OF)...后面将要用来判断是否非负,以验证key是否为数字。
----------区域处理 分割线①结束---------------------区域处理 分割线①----
004030e7: VM_Pop Data //XXX掉
----------区域处理 分割线②开始---------------------区域处理 分割线②----
004030e9: VM_PushData //dx - 9 的flag2。注意,这里与上面很相似
004030eb: VM_Push_ebp
004030ec: VM_DecodePoint_Dword //复制dx - 9 的flag2
004030ed: VM_Or_Non //取 ~flag2
004030ee: VM_Pop Data //XXXX
004030f0: VM_Push0x00000815 //这里直接就取0x815了
004030f5: VM_Or_Non // ~(~flag2) & ~815 = flag2 & ~815
----------区域处理 分割线②结束---------------------区域处理 分割线②----
004030f6: VM_Pop Data //XXXX掉 此时堆栈顶两个便是上面运算的两个flag了。也就是flag2 & ~815 和 flag1 & 815 --> 下面待续。。。。 本帖最后由 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,相当于0x18个DWORD)004014D0|.8BFC mov edi,esp ; -->把栈顶给edi004014D2|. 8D35 30304000 lea esi,; VM_START esi = VM数据流----------------------------------请叫我分割线-------------------------------------分割线-----------------------------------004014D8|>33C0 xor eax,eax ;注意这个跳入的箭头,下面每个函数执行完以后都会从这里继续。004014DA|.AC lods byte ptr ; --> 取一个数据004014DB|.24 0F and al,0F ; 取个位序号004014DD|.C1E0 02 shl eax,2 ; * 4 = 取地址004014E0|.8B98 00304000 mov ebx,dword ptr +403000] ; 取地址表中地址004014E6|.53 push ebx004014E7|.C3 retn ;retn到ebx的地址中去,也就是到达刚才取出的地址处。
这段代码挺清晰的,分割线上面进行一些初始化的工作。包括保存当前的寄存器消息,新建堆栈空间,保存堆栈指针,初始化VM的数据流等等。 寄存器下面就是分派VM handle的地方。一目了然,来看看00403000中存储的数据就明白了。这里存储了12个地址,分别对应于VM的12个handle,也可以理解为VM的IAT表吧。总之上面就是通过对esi中的数据进行解析而得到地址的。对了,我好像还没有给出esi中的内容对吧,现在就给几个。很多A3啥的。这是啥?我们来一步一步看:004014D8|>33C0 xor eax,eax ; --> eax = 0004014DA|.AC lods byte ptr ; --> eax = A3004014DB|.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 403000 ; --> ebx = = = 004014E8004014E6|.53 push ebx ; --> ss: = 004014E8004014E7|.C3 retn ; --> 返回到 SS: -> 004014E8去这样就可能比较容易理解了,其实这里就是一个函数的分派的地方。而分派到的函数则需要根据esi中的内容来决定!继续跟踪下去吧。到 004014E8 这个地方瞧瞧。
004014E8\. /EB 16 jmp short 00401500 先跳一个。到00401500去玩耍。00401500|> \33C0 xor eax,eax00401502|.AC lods byte ptr ; 取序号00401503|.C1E0 02 shl eax,2 ; * 4 = 地址00401506|.8B5D 00 mov ebx,dword ptr ; 取堆顶内容00401509|.891C38 mov dword ptr ,ebx ; 移动到栈空间去0040150C|.83C5 04 add ebp,4 ; 移动VM帧指针0040150F\.^ EB C7 jmp short 004014D8 ; 跳回去VM函数分派地方,进行下一个分派
接着进行解析:00401500|> \33C0 xor eax,eax ;--> eax = 000401502|.AC lods byte ptr ;--> eax = 0800401503|.C1E0 02 shl eax,2 ; -->* 4 = 偏移00401506|.8B5D 00 mov ebx,dword ptr ;-->ebx = ss: = flag寄存器的消息(刚才初始化VM时pushfd的内容)00401509|.891C38 mov dword ptr ,ebx ; -->(DWORD)edi = flag0040150C|.83C5 04 add ebp,4 ;-->移动VM帧指针0040150F\.^ EB C7 jmp short 004014D8 ; -->跳回去VM函数分派地方,进行下一个分派
做了什么?首先取了一个字节的内容,根据这个数据,把ebp顶的数据赋值给edi的空间中去。注意一下,前面还有这么一句:004014CD|.83EC 60 sub esp,60 ; 新建空间(0x60,相当于0x18个DWORD)004014D0|.8BFC mov edi,esp ; -->把栈顶给edi 也就是说,这里把ebp的数据给esp的空间去了。然后又移动了ebp的指针。等等,不觉得这有点像某个语句么:( ﹁ ﹁ ) ~→ pop dword ptr , 对,简直就像系统对堆栈的操作,不同的只是操作的对象换成帧指针而已。而且,edi(esi) 是保持不变的,自身维护了一个可以操作的空间。O(∩_∩)O~这样一来,ebp不就变成了堆栈,edi(esi)反而变成了数据区了么。实际上还真的就是那么一回事:它自身维护一个堆栈与一个数据区,来进行代码的运算。
这个操作Handle处理完以后,就马上跳回去刚才讲到的VM Handle 分派的地方,继续取esi中的数据(0xA3),也就是执行第3号的Handle,继续pop 一个数据到堆中 。然后不断的取数据,然后进行操作,如此反复,完成最终的目的。 到了这里,我相信esi中数据的作用也就明朗了。这些数据是用来命令程序,驱动程序进行运算的。也就是说,这段VM使得程序变成了数据流的程序。
本帖最后由 currwin 于 2014-10-6 11:43 编辑
恩恩,啰嗦了这么久,其实也就是把很少的东西讲来讲去而已: Esi 保存数据流。 Ebp变成堆栈指针。 Edi 有0x60的空间保存数据。 程序使用数据流来驱动。
好像懂了,又好像不懂对吧。前面我说了啥Handle的,高深得很,装逼装得太厉害了。其实并没有这么高大上。比如说,我前面讲到的3号Handle,就完成了一个pop的操作,所以,我把它命名为 VM_POP_,需要一个字节的数据标志出pop到的地方。好,其他的handle也同样的来处理。
0号:004015D2/> \8B75 00 mov esi,dword ptr ; 移动数据流的指针004015D5|.83C5 04 add ebp,4 ; 移动堆指针004015D8\.^ E9 FBFEFFFFjmp VM_Distribution ;返回到VM分配 根据对指针修改数据流指针。表达为: pop esi命名: VM_JMP_所需数据:0个
1号:004015A2/> \8B5D 00 mov ebx,dword ptr ; 取栈数据①004015A5|.8B55 04 mov edx,dword ptr ; 取栈数据②004015A8|.F7D3 not ebx ; ①取反004015AA|.F7D2 not edx ; ②取反004015AC|.23DA and ebx,edx ; 取反结果求与004015AE|.895D 04 mov dword ptr ,ebx ; 最终结果写入栈数据②004015B1|.9C pushfd004015B2|.8F45 00 pop dword ptr ; 取反导致的flag寄存器值写入栈数据①004015B5\.^ E9 1EFFFFFF jmp VM_Distribution 取了栈中数据①,②,取反求与,然后写入flag寄存器值以及求与后的值。这其实是或非门,自身就是一个全集了。什么,不懂?不要紧,下面会解析的命名: VM_Or_Non所需数据:0个
2号:00401529|> \33C0 xor eax,eax0040152B|.AC lods byte ptr ; 取序号0040152C|.C1E0 02 shl eax,2 ; 取地址0040152F|.83ED 04 sub ebp,4 ; 移动堆栈指针00401532|.8B1C38 mov ebx,dword ptr ; 从数据空间中取出数据00401535|.895D 00 mov dword ptr ,ebx ; 进入堆栈中00401538\.^ EB 9E jmp short VM_Distribution 与最初介绍的pop恰好是相反的操作,从数据空间(edi)中取出数据,进入到堆栈(ebp)中。命名: VM_PUSH_所需数据:1个。(push 的数据的序号)
3号:00401500|> \33C0 xor eax,eax00401502|.AC lods byte ptr ; 取序号00401503|.C1E0 02 shl eax,2 ; * 4 = 地址00401506|.8B5D 00 mov ebx,dword ptr ; 取堆顶内容00401509|.891C38 mov dword ptr ,ebx ; 移动到栈空间去0040150C|.83C5 04 add ebp,4 ; 移动VM堆指针0040150F\.^ EB C7 mp short VM_Distribution ; 跳回去VM函数分派地方,进行下一个分派 作用已经说明,是一个pop的操作命名: VM_POP_所需数据:1个。 (pop 的数据的序号)
4号:0040154E/> \33C0 xor eax,eax00401550|.AD lods dword ptr ; 取 DWORD 数据00401551|.83ED 04 sub ebp,4 ; 入栈00401554|.8945 00 mov dword ptr ,eax00401557\.^ E9 7CFFFFFF jmp VM_Distribution 依旧是对堆栈进行操作,直接push 一个4直接的数据。命名: VM_PUSH_DWORD所需数据: 4个。 (push 所需的一个 DWORD 大小的立即数)
5号:00401574|> \8B5D 00mov ebx,dword ptr ; 取数据①00401577|.8B55 04mov edx,dword ptr ; 取数据②0040157A|.03DA add ebx,edx ; ①+②0040157C|.895D 04mov dword ptr ,ebx ; 保存相加结果到②中0040157F|.9C pushfd00401580|.8F45 00 pop dword ptr ; ①中存放相加得到的flag寄存器00401583\.^ E9 50FFFFFFjmp VM_Distribution 把堆栈中的数据相加。。。命名: VM_ADD所需数据:0个
6号:004015F5/> \33C0 xor eax,eax004015F7|.66:AD lods word ptr ; 取WORD大小的数据004015F9|.83ED 02sub ebp,2 ; 入堆栈004015FC|.66:8945 00mov word ptr ,ax00401600\.^ E9 D3FEFFFF jmp VM_Distribution Push 一个WORD大小的数据进入堆栈。。。呵呵,这个堆栈还不是严格4字节对齐的。命名: VM_PUSH_WORD所需数据:2个 (push 所需的一个WORD大小的立即数)
7号:00401623/> \60 pushad00401624|.896C24 1Cmov dword ptr ,ebp ;修改eax00401628|.61 popad ; --> eax = ebp(堆指针)00401629|.8A08 mov cl,byte ptr ; 取除数①(BYTE -> WORD)0040162B|.8B40 02 mov eax,dword ptr ; 取被除数②(DWORD)0040162E|.83ED 02 sub ebp,2 ; 移动堆栈00401631|.D3E8 shr eax,cl ; ②/2^①00401633|.8945 04 mov dword ptr ,eax ; 除法结果写入②00401636|.9C pushfd00401637|.8F45 00 pop dword ptr ; 标志位写入①0040163A\.^ E9 99FEFFFFjmp VM_Distribution 取堆栈中的两个数据,进行移位,移位结果写回堆栈。前后堆栈变化如下: 除数①(WORD) --> flag①(DWORD) 被除数②(DWORD) -->结果②(DWORD) 就结果来说堆栈大小变大了2个字节。那么我想大家可能已经明白了为啥前面会有push WORD 的操作了吧。 这个是移位操作,但是理解为除法也是可以的。比如:② = ② / 2^①命名: VM_DIV所需数据:0个
8号:00401657|> \8BC5 mov eax,ebp00401659|.83ED 04 sub ebp,40040165C|.8945 00 mov dword ptr ,eax ; push 堆栈顶指针0040165F\.^ E9 74FEFFFFjmp VM_Distribution Push 当前堆栈顶的指针。。。有啥用啊?命名: VM_PUSH_EBP所需数据:0个
9号:004016A7|> \83C4 60 add esp,60 ; 清除新建的空间004016AA|.9D popfd ; 还原标志flag004016AB|.61 popad ; 还原寄存器004016AC\.C3 retn 与前面初始化VM的时候的操作是恰好相反的,结尾也是retn而不是跳回VM分派的地方。实际上,这里就是退出VM的地方了。命名: VM_RETN所需数据:0个
10号:00401682/> \8B45 00 mov eax,dword ptr ; 取栈顶指针数据00401685|.8B00 mov eax,dword ptr ; 取指针指向的数据00401687|.8945 00 mov dword ptr ,eax ; 写入栈顶0040168A\.^ E9 49FEFFFFjmp VM_Distribution 很简单的几行代码,只是把栈顶的数据当做一个指针,把它指向的DWORD的数据给还原出来而已。 如果配合上面讲到的VM_PUSH_EBP 来使用的话,就相当于把栈顶的内容复制了一遍。命名: VM_DECODEPOINT_DWORD所需数据:0个
11号:004016AD/.33C0 xor eax,eax ; VM_DECODEPOINT_BYTE004016AF|.8B45 00 mov eax,dword ptr 004016B2|.8A00 mov al,byte ptr 004016B4|.0FB6C0 movzx eax,al004016B7|.8945 00 mov dword ptr ,eax004016BA\.^ E9 19FEFFFF jmp VM_Distribution 与10号作用非常相似,只是他还原的目标对象为一个BYTE的数据而已。命名: VM_DECODEPOINT_BYTE所需数据: 0个
命名完后大概就是这样的感觉了。 {:301_1002:}分割线后咋就没了呢 本帖最后由 currwin 于 2014-10-6 12:16 编辑
004031a3: VM_Pop Data //pop 掉跳转地址
004031a5: VM_Pop Data //pop 掉跳转地址
004031a7: VM_Push0x00403558 //push key存放地址
004031ac: VM_PushData //push key结束地址
004031ae: VM_Push_ebp
004031af: VM_DecodePoint_Dword //复制key结束地址
004031b0: VM_Or_Non //求~key结束地址
004031b1: VM_Pop Data //XXXX掉
004031b3: VM_Add //求出 key + ~key
004031b4: VM_Pop Data //XXXX掉
004031b6: VM_Push_ebp
004031b7: VM_DecodePoint_Dword //复制 key + ~key
004031b8: VM_Or_Non //求反求出 key - key = key长度
004031b9: VM_Pop Data //XXXX掉
004031bb: VM_Pop Data //保存长度到data中
004031bd: VM_Push0x00403073 //push 结束VM的地址
004031c2: VM_Push0x00000006 //push 需要比较的长度,6
004031c7: VM_PushData //push key长度
004031c9: VM_PushData //push key长度 = keyLen
004031cb: VM_Or_Non //求~keyLen
004031cc: VM_Pop Data //XXXX掉
004031ce: VM_Add //6 + ~keyLen
004031cf: VM_Pop Data //保存flag1
004031d1: VM_Push_ebp
004031d2: VM_DecodePoint_Dword //复制6+ ~keyLen
004031d3: VM_Or_Non //计算 ~(6+~keyLen) = keyLen - 6
004031d4: VM_Pop Data //保存得到的flag2
004031d6: VM_Pop Data //XXXX掉
----------区域处理 分割线①开始---------------------区域处理 分割线①----
004031d8: VM_PushData //push flag1
004031da: VM_PushData //push flag1
004031dc: VM_Or_Non //求 ~flag1
004031dd: VM_Pop Data //XXXX掉
004031df: VM_Push0xfffff7ea //~fffff7ea = 815 (AF,OF,CF,PF)
004031e4: VM_Or_Non //实际求 flag1 & 815
004031e5: VM_Pop Data //XXXX掉
----------区域处理 分割线①结束---------------------区域处理 分割线①----
004031e7: VM_PushData
004031e9: VM_PushData
004031eb: VM_Or_Non
004031ec: VM_Pop Data
004031ee: VM_Push0x00000815
004031f3: VM_Or_Non //同理的求 flag2 & ~815
004031f4: VM_Pop Data
----------省略不必要的说明-------------------------------
004031f6: VM_Add // flag = flag1 & 815 + flag2 & ~815
004031f7: VM_Pop Data // XXXX掉
004031f9: VM_Pop Data // 保存相加结果 flag
004031fb: VM_Push0x00403216 //下一处验证的地址
00403200: VM_Push_ebp //push ebp
00403201: VM_Push0xffffffbf //ffffffbf = 40(ZF)
00403206: VM_PushData //push flag
00403208: VM_Or_Non //求 ~flag & 40. 如果flag的ZF = 0,则得到0x40的数,如果 ZF = 1,则得到0
00403209: VM_Pop Data //XXXX掉
0040320b: VM_Push0x0004
0040320e: VM_Div //左移4位,0x40 >> 4 = 4. 0 >> 4 = 0
0040320f: VM_Pop Data //xxxx掉
00403211: VM_Add //上面push 的 ebp值与 移位的值相加
00403212: VM_Pop Data //来选择应该跳向的地址
00403214: VM_DecodePoint_Dword //解码出跳转地址
00403215: VM_Jmp ebp 现在看起来应该是很简单了,就是取了字符串的长度,与6比较,相等的话就跳向下一处验证,失败就直接OUT了。0040329b: VM_Push0x00000035 //key == 0x35 (数字’5’)
...
0040330a: VM_Push0x00000034 //key == 0x34 (数字’4’)
...
00403379: VM_Push0x00000034 //key == 0x34 (数字’4’)
...
004033e8: VM_Push0x00000033 //key == 0x33 (数字’3’)
...
00403457: VM_Push0x00000032 //key == 0x32 (数字’2’)
当所有验证成功后004034b7: VM_Push0x090a0b0c
004034bc: VM_Pop Data //给 edx = 0x090a0b0c 成功key
004034be: VM_Push0x00403073
004034c3: VM_Jmp ebp //跳到VM退出的地方VM退出:00403073: VM_Pop Data
00403075: VM_Pop Data
00403077: VM_PushData
00403079: VM_PushData
0040307b: VM_PushData
0040307d: VM_PushData
0040307f: VM_PushData
00403081: VM_PushData
00403083: VM_PushData
00403085: VM_PushData
00403087: VM_PushData
00403089: VM_Retn 与进入VM的时候恰好是相反的过程。。就不多说了。 最终的key为754432,这可是要找死人了呵。。。 上面的注释中很多 XXXX掉 的字眼,这个可不是什么暗语呵,意思就只是这个数据不要。因为每一个运算的handle都会产生两个数据,而程序通常只需要其中的一个而已。自然的,另外一个就要被废弃了。哈哈。
写了这么多,总算是弄完这个分析了吖。。。蛋疼的VM,弄得像是在写论文似的。这下我得去好好的休息一会儿了。如果这篇分析存在什么问题的话,还请各位大大指正。
看到VM顿时吓尿了 一看 VM 就头大 。。。。 牛 2 这种发帖方式很特别.主题在楼下.幸好楼层不高