|
[原创]VMProtect2.04加壳程序从入门到精通
奥斯丁发射点法说的 |
|
[原创]VMProtect2.04加壳程序从入门到精通
在所有的原程序API获取出来后,还有更多的hash值检测 这个部分的检测和前面的一部分基本相同,只有在循环计数器的地方还有一些不同 这里的计数器是给出一个值(实际是所有结构的大小),然后每次减去C 再接上ZF位检测+跳转 最有意思的循环,我觉得当属给各个段.text .data等解码数据的循环,双数据AL与DL控制,外带CL。主要是双数据控制循环方式非常有趣呀。都找不到计数器。。 |
|
[原创]VMProtect2.04加壳程序从入门到精通
恩。就是这个了。我命名为VM_HASH |
|
[分享]猛壳
哈哈哈哈。。多么轻描淡写的一句话呀。。膜拜。。 |
|
[原创]VMProtect2.04加壳程序从入门到精通
这个。2.06的我也没有接触过。我看的是2.04的。 在2.04中。调试器检测的最后createfile打开自身哪里,VM本来是要打开自身的,我是修改了文件名。让他去打开自身的一个复本。这样。不管他怎么算都是非常正确的 hash值分块检测那里,表面非常难看懂,因为中间掺杂了很多标志位的检测,而有些是假的,有些是真的,要进行详细的分辨,相当于把先写出一个带有花指令的循环,然后再把它用VM的伪指令表现出来,但是真正看懂了。就会觉得漏洞非常大 vm_hash是计算hash值的那个函数,它有2个参数,分别是计算的 开始地址和计数器 分块检测部分是按照结构排列的,每个结构包括3个数据,每个数据都是dword的,当然它都是加密的,使用的时候要解密出来 第一个dword解密出来的是vm_hash检测的块的内存偏移地址,出来后加上00400000这样的基地址就是hash函数的第一个参数 开始地址 第二个dword解密出来的是vm_hash检测的第二个参数 计数器 比如说100 2000这样的 解出2个参数后,VM就调用vm_hash,得到hash值xxxx 接着就是第三个dword,我觉得最大的弱点就在这里,第三个dword的解密相当的简单,而它就是正确的hash值YYYY, 接着就是YYYY和XXXX比较,这里就很巧妙的运用了标志位寄存器中ZF位的位置,这个在后面详述,反正就是比较值要相同,不相同就会留下一个01的标记,到后面的结果就是,弹出对话框,告诉你,被修改了。 这样就结束了一个结构快的比较 接着就是结构地址+C,跳到下一个地址,循环的计数器减1,并相应的进行ZF位测试+跳转,看看循环计数器是不是已经为0了,重新开始这个循环,直到所有的块结束 我觉得最搞笑的地方就在于第三个dword,这就相当于一个明码的正确hash值嘛,根本不用去管VM的vm_hash函数计算结果是多少,直接在比较的时候,改成第三个dword就可以了。就相当于明码注册码比较一样。 第三个dword还有有一点点加密的,但是太弱了,和明码差不多 我用的是OD2.0,不带任何的插件等等东西,每次走过了或者其他的情况等等要重来时。都是手动跑着去的,后面的hash值这里,把所有的int3断点隐藏,在vm_hash这里下个硬件断点。也是一样的,他怎么算都是正确的, |
|
[原创]VMProtect2.04加壳程序从入门到精通
是呀。。就是我的随笔了。。说了是草稿了 。。正式的文档还要在后的。。将就着看吧。。想看的。。 高手就绕过好了。。反正VMP早就已经被很多高手蹂躏过了。。 想看又觉得乱的。。等过久再来吧。。到时候我会换上正式的文章。。 一个VMP跟了一个多月了。。大哥。。我也不容易呀。。又赶上中秋国庆放假。。停停歇歇的。。调试器的这个部分。。觉得乱的。。直接看后面我的总结好了。。 |
|
[原创]VMProtect2.04加壳程序从入门到精通
哈哈哈哈,与其说是你刺激,倒不如说是,我昨晚才知道,1st已经发布了VMP2.06的程序了。 我非常的震精呀。。本来就落后一个版本。。现在倒好直接落后2个版本 擦。。要速度了。。 昨天今天一直卡在VM的hash值分块检验的算法的那个大循环里面上,刚刚弄清楚 发现,除了VM的外壳伪指令中有很多的废操作,首先要能够找到各个伪指令的真正指令,这一步早就解决了,上个月开始后不久就已经可以看着伪指令的名字知道它的操作了,而这几天在分块hash值检测部分才发现,真正的花指令是VM内部的有些运算其实就是垃圾运算。。这个才是真正的内部花指令。在纸上演算了几遍才弄清楚。真假标志位ZF检测混合在一起接跳转。真的是太绕了 |
|
|
|
[原创]VMProtect2.04加壳程序从入门到精通
怎么没有注意到。什么时候变成 关注 了。。 先把 VMP加密程序 调试器检测 的草稿部分放出来一些。。关于 VM的结构。堆栈空间的划分。伪指令的命名和解析。各个逻辑运算。JMP指令。比较指令等等内容。。估计要等整理出来再放。。 跳过了N多的指令 一下的所有记录都将是跳跃式的 一切初始化、计算、移动等无聊部分都被略过 当然如果关键操作 而我也没有看到的也将被跳过 将就着看吧 代码是不发了 VM_MOVdw_[EBP]_EBP+4这是一条跨堆栈的指令,他可以把数据随意的存到其他地方,VM就是用这条指令把下面的DLL的名字存入到0013FF8C,下面的堆栈空间被当做是一个字符串存储的数据空间 CPU Stack Locked Value ASCII Comments 0013F790 7C80B741 A| ; kernel32.GetModuleHandleA 0013F794 0013FF8C _. ; ASCII "SbieDll.dll" VM_APIJMP_EBPSTACK ;这个指令首先给ECX放入API参数的个数,循环读取参数,最后跳转进系统空间 ;第一个APIJMP检测的是 沙盘(sandboxie) 我没有用沙盘 ;跳转地址:004363EA ;解码出第二个要检测的函数kernel32.IsDebuggerPresent ;还有一个地址0013F6C8 004393BC C. ; Entry point of procedure CPU Stack Locked Value ASCII Comments 0013F698 7C813133 31| ; kernel32.IsDebuggerPresent ;下一个要检测的是IsDebuggerPresent函数,但是在进入之前,先来一个CC码检测,看看有没有下断 CPU Stack Locked Value ASCII Comments 0013F790 3133 0013F794 00CC7C81 |. VM_EBPSTACKw_MEMb 0013F794 00CC0064 d.. ;接下来就是一系列的检查和比较跳转 这里不再复述 ;我肯定是没有下断点洒 最后计算好、解密完的地址是0043C2D4 VM_JMP ;走起 0013F698 7C813133 31| ; kernel32.IsDebuggerPresent VM_APIJMP_EBPSTACK ;这里就直接该返回值了,返回值肯定是1洒,毫不犹豫的改为0 ;接着就是检测返回值了 毫无疑问 VM检测不出来^_^ ;最后的比较跳转地址:0043929C VM_JMP ;走起 0013F794 7C85AAF2 | ; kernel32.CheckRemoteDebuggerPresent ;我们的第三位登场的角色就是它了 ;在开始正常的操作之前 VM都是会搞一下NOT(A) NOT(A) 在后面VM会检测ZF看函数地址是不是0 这个无聊的操作就是为了能确定有没有正常的获得地址 比较跳转地址:0043A72E VM_JMP ;走起 ;CC码检测是必须的 VM_EBPSTACKw_MEMb 0013F794 00CC008B .. ;最后的比较+跳转时刻的堆栈图: 0013F780 FFBF 0013F784 FD6CFFFF l 0013F788 0004FFFF _. 0013F78C 0013F790 _. 0013F790 7681CB89 藖v 0013F794 768003D3 _v ;我肯定是没有下断点洒 最后计算好、解密完的地址是0043A82B VM_JMP ;走起 0013F78C 7C85AAF2 | ; kernel32.CheckRemoteDebuggerPresent 0013F790 FFFFFFFF 0013F794 0013FF98 _. VM_APIJMP_EBPSTACK ;执行去了 ;返回值修改为0 ;接着还是一系列的比较,ZF位检测,最后的跳转地址:00428F50 ;我们如果不修改,被查到你开了调试器,跑去了另外一路的结局就是: 0013F794 00423E60 `>B. ; ASCII "A debugger has been found running in your system. Please, unload it from memory and restart your program." VM_APIJMP_EBPSTACK 0013F788 7C80B56F o| ; kernel32.GetModuleFileNameA 0013F78C 00400000 ..@. ; OFFSET NOTEPAD ;最后的结局就是消息框里"嘣"的弹出上面的话给你 VM_JMP ;走起 0013F794 7C809BE7 鐩€| ; kernel32.CloseHandle ;下一个是这位 0013F784 7C809BE7 鐩€| ; kernel32.CloseHandle 0013F788 DEADC0DE ;这叫什么东西! 0013F78C 0013FFE0 _. ; Pointer to next SEH record 0013F790 00427A04 _zB. ; SE handler 0013F794 0013FF98 _. VM_APIJMP_EBPSTACK ;执行 ;返回为0 也不用去异常 这是一个关闭无效句柄的检测 DEADCODE本身就不是什么东西 ;把C0000008异常刚出现就给为0000000返回,貌似就可以跳过了。跳转地址:0042357C VM_JMP ;走起 ;这次去的地方并不是新的函数,而是释放掉在CloseHandle函数那里构建的SEH 0013F794 7C863FCA ?| ; kernel32.UnhandledExceptionFilter ;首先依然是检测CC码,跳转地址:00422E3E VM_JMP ;走起 0013F794 7C809BE7 鐩€| ; kernel32.CloseHandle ;这个也一起出现,跳转地址:004262DC VM_JMP ;走起 0013F76C 0013FF8C _. ; ASCII "sice.sys" ;这个也出现,跳转地址:00422D4C 0013F68C 00425E64 d^B. 0013F690 0043EEB7 C. ; RETURN from NOTEPAD.0043D111 to NOTEPAD.0043EEB7 0013F694 0013FF8C _. ; ASCII "sice.sys" VM_JMP ;走起 0013F794 0013FF8C _. ; ASCII "siwvid.sys" ;字符串进一步解码为这个 0013F790 00425E64 d^B. 0013F794 0013FF8C _. ; ASCII "siwvid.sys" VM_APIJMP_EBPSTACK ;命名的有点问题,这个handler并不只是可以跳去API,它是有一个分支的,这里它就跳去了一个VM的call里 ;SoftIce都停止开发了,我也没有用洒,不怕它检测 0013F634 7C92D92E .賿| ; ntdll.NtQuerySystemInformation ;呼呼 好绕呀,最后进入的是这个函数 0013F66C /0042999D B. ; RETURN from NOTEPAD.004235BA to NOTEPAD.0042999D 0013F670 |00000000 .... 0013F674 |00010C80 ._. 0013F62C /7C809A2D -| ; kernel32.LocalAlloc ;分配的句柄是:00157D28 0013F61C 7C92D92E .賿| ; ntdll.NtQuerySystemInformation ;最后获得了系统当前的运行进程保存到00157D28: ;每个中间还有很多的空间 把他们去除后,类似于这个样子: ;这里中间掉了一点。下面有补充 CPU Dump Address Hex dump ASCII 00157D28 79 00 00 00|0D F0 AD BA|00 00 00 00|00 80 4D 80| y.........M 00157D38 00 D0 20 00|00 40 00 0C|00 00 00 00|01 00 12 00| . ..@......_._. 00157D48 5C 57 49 4E|44 4F 57 53|5C 73 79 73|74 65 6D 33| \WINDOWS\system3 00157D58 32 5C 6E 74|6B 72 6E 6C|70 61 2E 65|78 65 00 BA| 2\ntkrnlpa.exe. ............... 00157E48 0D F0 AD BA|00 00 00 00|00 50 6E 80|00 0D 02 00| ......Pn.._. 00157E58 00 40 00 0C|01 00 00 00|01 00 12 00|5C 57 49 4E| .@.._..._._.\WIN 00157E68 44 4F 57 53|5C 73 79 73|74 65 6D 33|32 5C 68 61| DOWS\system32\ha 00157E78 6C 2E 64 6C|6C 00 AD BA|0D F0 AD BA|0D F0 AD BA| l.dll... ;还有很多的了其中就包括了这个:(我前几天刚装了SoftIce) 00159A08 00 00 00 00|00 10 CD B9|00 70 02 00|00 40 00 09| ....._凸.p_..@.. 00159A18 1A 00 00 00|01 00 00 00|53 69 77 76|69 64 2E 73| _..._...Siwvid.s 00159A28 79 73 00 BA|0D F0 AD BA|0D F0 AD BA|0D F0 AD BA| ys.... ;接下来就是开始比较,一个一个的读出来和siwvid.sys比较 ;对这些数据结构有了一些认识 第一个79 00 00 00 这个dword是表示表里有多少个程序 要读取这些数据,就要00157D28+4=00157D2C从这里开始读取, 0041EC14 83C1 FF ADD ECX,-1 00427D2E ^\0F84 3DE3FFFF JE 00426071 ;循环的计数就是00000079,结构的第一个dword 00427D36 81C2 1C010000 ADD EDX,11C 通过这一句说明,每个结构式11C的大小,执行后就去到下一个结构那里了 004262FC 0FB772 1A MOVZX ESI,WORD PTR DS:[EDX+1A] 在到达下一个结构比如00157E48后,通过这条指令就可以获得其中的数字0012 00426307 8D7416 1C LEA ESI,[EDX+ESI+1C] ; * 接着来这条指令,OK,我们就可以获得hal.dll 00425364 8B7D 08 MOV EDI,DWORD PTR SS:[EBP+8] ; * ;这里是获得EBPSTACK中存着的siwvid.sys 0041E9CA 8A07 MOV AL,BYTE PTR DS:[EDI] ; * ;获得要比较的Siwvid.sys的第一个数字S=73 00435F96 3C 41 CMP AL,41 ;73和41比较用"S"和"A"比较 00422AA4 /0F82 18000000 JB 00422AC2 ; * ;应该是大于"A" 00422AAB 3C 5A CMP AL,5A ; * A--Z ;73和41比较用"S"和"Z"比较 00422AB6 /0F87 06000000 JA 00422AC2 00422AC0 04 20 ADD AL,20 ;比较的就是说,进行一下检测,得到的应该在字母A--Z之间,否则就跳转了,小于A的我们不去管它,只看这个分支 ;小于Z的,就不跳转继续执行第二条指令 加20 可以把大写转换为小写。说明它是用小写进行比较 00422AC7 8A26 MOV AH,BYTE PTR DS:[ESI] ; * ;获得hal.dll的第一个字符"h"=71 0043790C 80FC 41 CMP AH,41 ; * A 00429178 /0F82 1B000000 JB 00429199 00429186 80FC 5A CMP AH,5A ; * A--Z ;这里都是和前面的处理一样的,看看是不是大于A,和如果是大写就转化为小写。"h"这个字符 00424C76 38E0 CMP AL,AH ; * ;这里比较 ;他要进入另外一个分支 0043DE10 RETN 38 0013F690 0043F143 CC. 0013F694 0013FF8C . ; ASCII "siwvid.sys" 0013F698 7C809BE7 鐩€| ; kernel32.CloseHandle ;记录一下代码 00425E6C 9C PUSHFD ; * 00425E6D 872C24 XCHG DWORD PTR SS:[ESP],EBP ; *保存EBP指针 00425E70 E9 EE290000 JMP 00428863 ; * 00428863 \89E5 MOV EBP,ESP ; *更新EBP指针到ESP 0042886E 83EC 08 SUB ESP,8 ; * 0013F684 0043EF77 wC. ;ESP 0013F688 00000000 .... 0013F68C 0013F790 . ;EBP 0013F690 0043F143 CC. 0013F694 0013FF8C . ; ASCII "siwvid.sys" 0013F698 7C809BE7 鐩€| ; kernel32.CloseHandle 004259BA 891C24 MOV DWORD PTR SS:[ESP],EBX ; * 004259BF 56 PUSH ESI ; * 004259C5 57 PUSH EDI ; * ;前面已经放了两个数据,后面的这些一起就是保存了当前的context结构 ;这是在保存VM的寄存器数据,我们要简单回到现实中一下 ;ESP指针是局部保存的寄存器值 004259C8 31DB XOR EBX,EBX ; *初始化EBX 004259CD 8D45 F8 LEA EAX,[EBP-8] ; * 00428FF9 \8918 MOV DWORD PTR DS:[EAX],EBX ; * 0043546C FF3424 PUSH DWORD PTR SS:[ESP] ; * 0043546F 874424 04 XCHG DWORD PTR SS:[ESP+4],EAX ; * 00435481 8D45 FC LEA EAX,[EBP-4] ; * 00439AC9 \FF30 PUSH DWORD PTR DS:[EAX] ; *ntdll.NtQuerySystemInformation 0013F660 7C92D92E .賿| ; ntdll.NtQuerySystemInformation 0013F664 0041E1ED A. ; RETURN from NOTEPAD.00439ADB to NOTEPAD.0041E1ED 0013F668 0000000B ;第一个参数:系统信息类型 0013F66C 0013F688 . ;返回系统句柄内存空间指针 0013F670 00000000 .... ;内存空间大小 byte 0013F674 0013F684 . ;成功调用返回0 ;这次调用B类型,貌似和上面查阅的资料有所不同,他在第四个参数的指针位置0013F684 0013F684 00008640 @.. ;在后面这个位置会成为获得内存分配空间的大小依据 00428BD7 D1E0 SHL EAX,1 ; * ;把EAX中00008640*2=00010C80 00438C21 8945 FC MOV DWORD PTR SS:[EBP-4],EAX ; * 00423E1B 874424 2C XCHG DWORD PTR SS:[ESP+2C],EAX ; * 00425A5F C74424 2C 00000 MOV DWORD PTR SS:[ESP+2C],0 ; * 00425A71 8D05 E87D4300 LEA EAX,[<&KERNEL32.LocalAlloc>] ; * ;前面计算好大小,然后开始放置参数,到这里获得函数 00428DB0 C74424 2C 9D994 MOV DWORD PTR SS:[ESP+2C],0042999D ; * 00423849 FF30 PUSH DWORD PTR DS:[EAX] ; * 00423854 C2 3C00 RETN 3C ; APIJMP RETN 3C 0013F668 7C809A2D -| ; kernel32.LocalAlloc 0013F66C 0042999D B. ; RETURN from NOTEPAD.004235BA to NOTEPAD.0042999D 0013F670 00000000 .... ;If zero is specified, the default is the LMEM_FIXED flag 0013F674 00010C80 .. ;number of bytes to allocate ;EAX=00157D28 004299A5 09C0 OR EAX,EAX ; * 004299AC ^\0F84 BFC6FFFF JE 00426071 ; * ;判断返回值是否为零 ;跳过一些初始化指令 0013F660 7C92D92E .賿| ; ntdll.NtQuerySystemInformation 0013F664 0043A352 RC. 0013F668 0000000B 0013F66C 00157D28 (}. 0013F670 00010C80 .. 0013F674 0013F684 . ;到这里就回到了上面检测的地方 ;详细记录一下检测的进程,按照顺序分别是: 1)siwvid.sys ;第一个我就有,⊙﹏⊙b汗,先跳过再说 ,刚才手按太快没有跳过,重来后发现第一个是:sice.sys,不管他,反正我全部记录下来 ;搜了一圈发现没有搜到,原以为接着就会载入下一个黑名单,但是它先调用 00423815 8D05 EC7D4300 LEA EAX,[<&KERNEL32.LocalFree>] 1)sice.sys 2)siwvid.sys 3)ntice.sys 4)iceext.sys 5)syser.sys ;到这里就结束了,回VM去了 ;回VM后会接着来一个返回值检测,就是看有没有查到调试器 ;接着就解码出ntdll.ZwQueryInformationProcess 0013F794 7C92D7FE 讙| ; ntdll.ZwQueryInformationProcess 0013F798 230A53C4 S.# ;接下来是老规矩,CC码检测,有没有int3断点在函数开头 ;进入ntdll.ZwQueryInformationProcess函数 0013F67C 7C92D7FE 讙| ; ntdll.ZwQueryInformationProcess 0013F680 0043EEB7 C. ; RETURN from NOTEPAD.0043D111 to NOTEPAD.0043EEB7 0013F684 FFFFFFFF ;ProcessHandle 0013F688 0000001E ... ;ProcessInformationClass 0013F68C 0013FF98 . ;ProcessInformation 0013F690 00000004 ... ;ProcessInformationLength 0013F694 00000000 .... ;ReturnLength ;这个真的要贴一下,不然会忘记的,连另一个一起写下来 ;00000001E和0000001F是未公开的类名 ;00000001E是当有调试活动开始,系统会创建一个debug object,而这个类名就是用来查询这个句柄 ;00000001F这个类名更直接,根据系统中是否有调试器存在,返回true和false 我的系统里面是有Softice的,所以这个函数后: EAX=00000000 0013FF98那里返回了00000068 VMP只检测了EAX就判断我有调试器了,所以这个函数返回00000000就是调用成功,直接就可以判断 让我们重头来过,把返回值更改为00000001 ;在走了N步,跳过了N条伪指令后,终于EBPSTACK里出现了一点貌似有意思的东西 0013F78C 0013FFE0 . ; Pointer to next SEH record 0013F790 00436302 cC. ; SE handler 0013F794 0013FF98 . 0013F798 230A53C4 S.# 获取2个常量: 0013F6A0 00004647 GF.. 0013F784 4A4D0000 接下来进入VM_EXIT,我照常来一个F9 程序停下来了,当前的各种数据如下: 004394FA 9D POPFD ;int3异常 004394FB 60 PUSHAD EAX 00000000 ECX 0000E39D EDX BFEBFBFF EBX 00000000 ESP 0013F788 EBP 0013FF98 ESI 00004647 EDI 00004A4D 0013F78C 0013FFE0 . ; Pointer to next SEH record 0013F790 00436302 cC. ; SE handler ;还在检测Softice,真不知道如果我不装它,能够少多少麻烦!看了半天资料也没看出要怎么过,直接在SEH下断,飞过去了,貌似没有什么问题,不管它继续走。 ;也不知道,上面有没有检测了什么东西,不管了,赶时间呀,一定要尽快跟完整个程序,反正都是检测调试器部分,要是检测到,早爆发了,没检测掉的,错过了也不管了 ;接下来就是堆栈跑了老远的距离呀,然后连着来VM_EXIT,慢慢的退下去了 ;FS寄存器又登场了 VM_FS:[EBPSTACK] ;EBPSTACK=00000000 ;清除了前面设置的SEH ;继续刷过一大堆的伪指令 VM_EBPSTACKdw_MEMdw ;获取的是0043D289的数据 ;然后解码出下一个函数 0013F794 7C92DCAE 軖| ; ntdll.NtSetInformationThread ;接CC码检测 ;先来一段理论: NtSetInformationThread()的参数列表如下。要实现这一功能,ThreadHideFromDebugger(0x11)被当作ThreadInformationClass参数传递,ThreadHandle通常设为当前线程的句柄(0xFFFFFFFE): NTSTATUS NTAPI NtSetInformationThread( HANDLE ThreadHandle, THREAD_INFORMATION_CLASS ThreadInformaitonClass, PVOID ThreadInformation, ULONG ThreadInformationLength ); ThreadHideFromDebugger内部设置内核结构ETHREAD16的HideThreadFromDebugger成员。一旦这个成员设置以后,主要用来向调试器发送事件的内核函数_DbgkpSendApiMessage()将不再被调用。 示例 调用NtSetInformationThread()的一个典型示例: push 0 ;InformationLength push NULL ;ThreadInformation push ThreadHideFromDebugger ;0x11 push 0xfffffffe ;GetCurrentThread() call [NtSetInformationThread] 0013F67C 7C92DCAE 軖| ; ntdll.NtSetInformationThread 0013F680 0043EEB7 C. ; RETURN from NOTEPAD.0043D111 to NOTEPAD.0043EEB7 0013F684 FFFFFFFE 0013F688 00000011 ... 0013F68C 00000000 .... 0013F690 00000000 .... ;不解释。。想想怎么过吧 ;GetCurrentProcess 返回值是-1. GetCurrentThread返回值是-2.这是个假的句柄,仅仅用来代表当前线程,还有当前进程用-1表示。 ;把FFFFFFFE改为FFFFFFFD,然后把返回值EAX=C0000008改为00000000 ;程序回到VM中执行,解码出的下一个有用的东西是: ;接着出来的东西也要贴下来。怎么说也有点用处了: 0013F788 00437DF0 }C. ; <&KERNEL32.GetModuleFileNameA> 0013F78C 00400000 ..@. ; 0013F790 0013FB98 . ; PTR to UNICODE "kernel32.dll" 0013F794 00000104 .. VM_EBPSTACKdw_MEMdw ;取出GetModuleFileNameA函数的地址 0013F788 7C80B56F o| ; kernel32.GetModuleFileNameA 0013F78C 00400000 ..@. ; OFFSET NOTEPAD.B 0013F790 0013FB98 . ; PTR to UNICODE "kernel32.dll" 0013F794 00000104 .. VM_APIJMP_EBPSTACK DWORD GetModuleFileName( HMODULE hModule, // handle to module to find filename for LPTSTR lpFilename, // pointer to buffer for module path DWORD nSize // size of buffer, in characters ); ;参数也贴贴 ;获得了路径 0013F68C 0013FB98 . ; ASCII "D:\JKylin\VMProtect Ultimate2.04\NotePad\NOTEPAD.exe" ;EAX中返回长度00000034 ;VM接着就检测一下返回的是不是0,看看有没有执行成功 ;你以为检测完了,实际还没有完,我们还在路上 ;下一个函数是它: 0013F69C 7C801A28 (| ; kernel32.CreateFileA 0013F778 7C801A28 (| ; kernel32.CreateFileA 0013F77C 0013FB98 . ; ASCII "D:\JKylin\VMProtect Ultimate2.04\NotePad\NOTEPAD.exe" 0013F780 80000000 ... 0013F784 00000003 ... 0013F788 00000000 .... 0013F78C 00000003 ... 0013F790 00000080 ... 0013F794 00000000 .... VM_APIJMP_EBPSTACK ;打开自身来看看有没有调试器 HANDLE CreateFile( LPCTSTR lpFileName, // pointer to name of the file DWORD dwDesiredAccess, // access (read-write) mode DWORD dwShareMode, // share mode LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes DWORD dwCreationDistribution, // how to create DWORD dwFlagsAndAttributes, // file attributes HANDLE hTemplateFile // handle to file with attributes to copy ); ;我决定玩玩这样,我在路径里放上了win98的原始记事本,命名为NOTEPAC.exe。然后修改函数的参数为NOTEPAC.exe ;返回了0000006C,看看VMP到底只是比较一下就过了,还是他要做其他的事情 ;VMP检测返回值是否为0,肯定不为洒,我已经改了。 ;果然VMP没有那么轻易放过这个句柄,早知道我应该放上加壳记事本的复本,算了,到后面改跳转好了 ;下一个函数是:GetFileSize ;接着我们就来到了这里: 0013F78C 7C810B17 | ; kernel32.GetFileSize 0013F790 0000006C l... 0013F794 00000000 .... DWORD GetFileSize( HANDLE hFile, // handle of file to get size of LPDWORD lpFileSizeHigh // address of high-order word for file size ); ;还是贴贴函数参数比较人性化,^_^,可惜前面的没有贴 ;得到的大小是EAX=0000D000 ;毫无疑问,我相信VMP肯定已经提前计算好了文件值,现在要看看是不是一样了 VM_EBPSTACKdw_MEMdw ;估摸着这里得到的应该就加密的原程序大小了 0013F794 A5B56CFF l ;解密解密 0013F794 F48003FF ;0043DE51地址,出来一个新的伪指令 0013F78C 03FF 0013F790 03FFF480 0013F794 000AF480 .. 0043DE5D 8B45 00 MOV EAX,DWORD PTR SS:[EBP] ; * 0043DE69 8B55 04 MOV EDX,DWORD PTR SS:[EBP+4] ; * 0043DE6E 8A4D 08 MOV CL,BYTE PTR SS:[EBP+8] ; * 0043DE7A 0FADD0 SHRD EAX,EDX,CL ; * 0043D38F 8945 04 MOV DWORD PTR SS:[EBP+4],EAX ; * ;非常清晰,这个伪指令就是读取EBPSTACK的数据,然后移位,我把它命名为VM_SHRw_EBPSTACK ;F48003FF解密成了FFFD2000 ;继续解密解密,貌似这里贴的太细了。早知道直接贴结果 0013F794 0002E000 .. ;这个应该是结果了 ;果然接下来就是比较大小是否一致,EBPSTACK和EDISTACK中暂存的所有0000D000都修改为0002E000 0013F790 0002E000 .. ;这里是修改的 0013F794 0002E000 .. ;检测是否相同,跳转地址为004259B3 ;下面解密出场的函数是:CreateFileMappingA 0013F77C 7C80950A .| ; kernel32.CreateFileMappingA 0013F780 0000006C l... 0013F784 00000000 .... 0013F788 00000002 ... 0013F78C 00000000 .... 0013F790 00000000 .... 0013F794 00000000 .... HANDLE CreateFileMapping( HANDLE hFile, // handle to file to map LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // optional security attributes DWORD flProtect, // protection for mapping object DWORD dwMaximumSizeHigh, // high-order 32 bits of object size DWORD dwMaximumSizeLow, // low-order 32 bits of object size LPCTSTR lpName // name of file-mapping object ); ;这个玩笑开大了,刚才真的应该放一个加壳记事本的复本呀 ;函数调用完后,EAX返回的句柄是00000070 VM检测返回值是否为0 ;很多套路都是一样的,每个函数解密后必定要看看是否为0,接着CC码检测,每个函数完成后,都是检测返回值,然后跳转到相应的位置,然后又做点事,又跳转到另外一个地方。 ;这次的跳转地址是:00427D2A,其实根本没有用处,随便写写记记。⊙﹏⊙b汗 ;我用0D2.0 无插件又装着SoftIce,开着程序就不敢关闭呀。重装一次壳程序,就要重新手动跑到原来的地方。超级麻烦。 大量的放入函数参数,都是以明码常量放入的,这么多参数不可能VMP每个都去加密洒。 函数倒是都是加密的洒 废话少说,下一个解密函数是:MapViewOfFile 0013F780 7C80B9A5 | ; kernel32.MapViewOfFile 0013F784 00000070 p... 0013F788 00000004 ... 0013F78C 00000000 .... 0013F790 00000000 .... 0013F794 00000000 .... LPVOID MapViewOfFile( HANDLE hFileMappingObject, // file-mapping object to map into address space DWORD dwDesiredAccess, // access mode DWORD dwFileOffsetHigh, // high-order 32 bits of file offset DWORD dwFileOffsetLow, // low-order 32 bits of file offset DWORD dwNumberOfBytesToMap // number of bytes to map ); 函数执行完毕,返回的映射地址是00A00000,不管三七二十一先在数据窗口里面跟踪着00A00000,同时开了PEID把加壳记事本的程序也打开,Hex Viewer打开,先做点准备工作,如果它要搞什么比较之类的东西我也好看着点,谁叫我当时放了不同的程序给它打开 VM照例先检测一下返回值是否为0并跳转:0043BE55 哗哗哗的跳过了一些内容,后面发现悲剧了 VM算了120,然后加到了映射地址, 现在00A00000+120=00A00120,我们还是来理理PE文件格式,看看120是PE中的什么地方 0043CE89,哦哦,有新的伪指令出现。里面的重要的一条指令就是: 0043D7EF \3202 XOR AL,BYTE PTR DS:[EDX] ; * [EDX]中的就是[00A00120],EAX在前面被清0了 由于VMP是在打开自身的程序。所以原程序的一切内容它事先都是知道的。根本就不通过慢慢的定位。直奔主题去了 120是PE数据表目录中的重定位表目录 0013F790 00A00120 . 0013F794 0001EF2B ). VM进入了一个循环,他从00A00120开始,不断的循环计算里面的值。0001EF2B就是里面的计数器。这是在计算指定大小数据块的一个值。我们来贴一下完整的循环代码,看看到底是什么算法: 0043DCC0 89C1 MOV ECX,EAX ; *结果放到ECX中保存 0043E6FA C1E0 07 SHL EAX,7 ; 0043E701 C1E9 19 SHR ECX,19 ; * 0043D2BD /09C8 OR EAX,ECX ; * 0043D7EF \3202 XOR AL,BYTE PTR DS:[EDX] ; * 0043D7F2 42 INC EDX ; *数据地址指针 0043DD12 FF4D 00 DEC DWORD PTR SS:[EBP] ; *循环计数器 求值的数据块大小 0043F023 ^\0F85 7FDEFFFF JNE 0043CEA8 ;*计数器为0,就跳出循环 ;一个简单的hash值计算,不管他是什么东西,直接要结果,肯定是要比较撒 ;哈哈 由于给他加载的程序太小,所以根本就没有算完,程序就异常了。读不到数据了。重头来过了。这次让他加载自己的复本 ;0043CE89命名为VM_HASH ;这次好了,让他打开加壳记事本的复本。随便他怎么算都可以 计算结束后得到的HASH值是:EAX=E3E4A8CC ;0043E9CA,新的伪指令出现,命名为VM_MOVw_EBPSTACK_[EBPSTACK] 比较测试结果,然后VM继续 ;数据一致有从00436057的地方出来 开始继续动映射程序的脑子: 0013F790 0001F07B {. 0013F794 00A30000 ... VM_ADDdw_EBPSTACK ;太多的操作了,绕来绕去。想睡觉了。看不过来。直接跳过了。 0013F6B8 7C80BA14 | ; kernel32.UnmapViewOfFile 释放了空间 0013F790 7C809BE7 鐩€| ; kernel32.CloseHandle 0013F794 00000070 p... 关闭句柄 0013F790 7C809BE7 鐩€| ; kernel32.CloseHandle 0013F794 0000006C l... 彻底关闭 到这里VMP的调试器检测告一段落 前面VMP还有关于VMware的检测,就在这里说一下了 CPU - main thread, module NOTEPAD EAX 564D5868 ECX 0000000A EDX 00005658 EBX 00000000 ESP 0013F78C EBP 0013FF98 ESI 0013FF8C ASCII "ntdll.dll" EDI 0013FF70 EIP 0042536C NOTEPAD.0042536C VMware的后门指令检测。这个么不用问为什么。这个就是后门 mov eax, 564D5868h mov ebx, 00000000h mov ecx, 0000000Ah mov edx, 00005658h in eax, dx 这个检测很普通,由于只有在VMware下这条指令才有返回值,否则就是一次异常。而我们是在真实环境下的。这个太普通了。我根本没有用什么虚拟机。所以直接去ESP(注意,现在是真实空间,和普通一样,我们就是要ESP)找到SEH异常地址。跳过去就OK了。 简单的总结一下 1)VMware检测 2)SbieDll.dll沙盘检测 3)IsDebuggerPresent 4)CheckRemoteDebuggerPresent 5)closehandle非法句柄 6)NtQuerySystemInformation进程名比较: a)sice.sys b)siwvid.sys c)ntice.sys d)iceext.sys e)syser.sys 7)ZwQueryInformationProcess 0000001E类检测 8)4647 4A4D int3异常 SoftIce后门检测 9)NtSetInformationThread 00000011类检测 10)CreateFileA打开自身检测 a)CreateFileA打开自身 b)GetFileSize得到文件大小检测 c)CreateFileMappingA与MapViewOfFile把打开的自身映射进程序中 d)使用自身的一个简单的XOR+位移的hash伪指令,计算出打开自身的hash值并比较检测 e)这个非常非常的重要,也非常非常的可惜,我太想睡了,同时实在受不了了,哗哗哗的按过了一大堆伪指令。O(∩_∩)O哈哈~。不知道VMP做了什么 f)调用UnmapViewOfFile、CloseHandle、CloseHandle函数退出打开的自身。 |
|
|
|
[原创]VMProtect2.04加壳程序从入门到精通
O(∩_∩)O哈哈~。。那天刚好看这里,就把它贴出来了。 到今天已经看了近一个月的VMP加密程序了,所以就把抽了几条代码贴出来,要是像我前面发的那样,把所有的VMP外壳代码都贴出来,可以粘贴得一片山远了。 嘿嘿。等看完了,好好的写一份分析报告。 以前刚碰到你的时候,看得忒艰难呀。一条代码一条代码的扫着去。 现在感觉好多了。 |
|
[原创]VMProtect2.04加壳程序从入门到精通
以万变应不变。。O(∩_∩)O哈哈~ |
|
[求助]手动跳过closehandle检测调试器要怎么弄了?
把C0000008异常刚出现就改为00000000返回貌似就可以了 |
|
|
|
[原创]VMProtect加密程序解析
还不起来 更待何时! |
|
[分享]虚拟机完整分析
其他的看不过来。。看了前面的结构。。就是一个简化的虚拟机。。堆栈划分就比VMP的虚拟机有了不同。。指令的构造与读取数据的偏移量也是直接获取。。也比较简单。。非常适合学习。。收藏了 |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
能力排名:
No.{{ rank_num }}
等 级:
LV{{ rank_lv-100 }}
活跃值:
在线值:
浏览人数:{{ visits }}
最近活跃:{{ last_active_time }}
注册时间:{{ user_info.create_date_jsonfmt }}
勋章
兑换勋章
证书
证书查询 >
能力值