VMP1.22以上的版本都没有看过,周末几天有幸看了海风月影兄弟发的1.6x加的Notepad,刚拿到手时候,根本没有任何头绪,仔细琢磨一下,还是有规律可寻的。
为了方便起见,我拿这个和1.22的比较,所以看这篇冬冬的话,最好还是要对1.22有所了解,首先我说一下,这篇的目的,是把1.6x版本的VM能够转化成1.22版本的结构,因为在1.22下,本人做了些尝试,可以比较完整的跟踪出所有的PCode,以及当时的相关环境,在完成这一步的基础上,通过人工识别的方法(工作量非常大),可以还原一部分代码出来,所以,我这里的目的,就是希望能够把1.6x降级,降级到1.22,再进行分析。
对比:
1.
跟过1.22(包括1.22)以前版本的都知道,VMP是通过自身堆栈来进行运算的,程序进入VM前,相关的寄存器以及VM所需的一些数据保存在一个全局数组中,对自身堆栈的操作是通过esp来完成。
在1.6x中,情况发生变化了,VMP同样是通过堆栈进行运算,但是这里出现了两个层面上的堆栈,一个是esp维护的堆栈,一个是ebp维护的堆栈,虽然都是在一个地址区域里面,但是作用却不一样,esp维护的堆栈是用来迷惑跟踪者的,出了一些pushfd有用之外,其余的一切在这个堆栈上的操作都是垃圾代码,真正的用来执行的是ebp指向的堆栈,这个堆栈,就相当于1.22的esp指向的堆栈,所以看dispatch_code的关键代码,关键留意操作ebp的代码,esp的几乎全是垃圾代码,并且它在退出dispatch_code时候,会平衡掉自己。在1.6x里面,如果我没有特别说明,我所说的vm_esp指的都是ebp。
2.
VM_Data的获取:
和1.22一样,进入vm循环后,esi指向的是VM_Data,不同的地方是,1.6x的esi不单单是朝着esi增大的方向进行,很多时候是朝着减少的方向进行。
3.
VM_Data地址的保护
1.22版本,VM_Data的地址是明文传进去的
push offset VM_Data
jmp VM_Init
这是最常见的情况,到了1.6x,不在是这样的
push keydata
jmp VM_init
经过解密运算,会把keydata变成,一个真实的地址。
4.
dispatch_table的保护
dispatch_table里面256的地址,全部加密,通过固定算法解密,这里有我写的程序,解密所有的dispatch_code_addr。
5.
vm前环境变量的保护
1.22是保护在一个全局变量中,1.6x是保护在堆栈里面,地址在ebp的上面,大小为0x50,同样都是通过edi指向的,如果进行进栈操作的话,ebp地址要小于edi+0x50的话,会重新引发分配保存vm的环境变量
这个在很多dispatch_code中都有体现,如果有进栈操作,都会跳到jmp VM_CHECK_STACK_OVERFLOW,进行检查。
6.
1.6x的dispatch_code不像1.22那样干净了,里面有大量的垃圾代码,jcc,fake call,jcc的话,走哪个分支都一样,fake_call跟进去就行,这些都会在出dispatch_code时候,被平衡掉,有一个原则就是,看操做
esi,ebx,ebp,edi的代码,以及与之相关的代码,这些代码是关键,
esi指向当前vm_data
edi指向vm前的环境
ebx用来计算dispatch_no的
ebp是运算堆栈。
主要差别好像就这些,如果有不足的,希望大家补充。
下面介绍一下进入VM的流程
newlaos1:0102C346 start: ; keydata
newlaos1:0102C346 push 0C9440AF1h ;这个就是上面说的keydata
newlaos1:0102C34B call fake_call
newlaos1:0103ACEB fake_call proc near ; CODE XREF: newlaos1:0102C34Bp
newlaos1:0103ACEB jmp VM_Init ; 前面
newlaos1:0103ACEB fake_call endp
newlaos1:0103995C VM_Init proc near ; CODE XREF: fake_callj
newlaos1:0103995C
newlaos1:0103995C push ebp ; *
newlaos1:0103995D pusha
newlaos1:0103995E pusha
newlaos1:0103995F push esi
newlaos1:01039960 mov [esp+40h], ecx ; fuck掉最初的eax
newlaos1:01039964 call Fake_01
newlaos1:01039F03
newlaos1:01039F03 pushf
newlaos1:01039F04 push ebp
newlaos1:01039F05 mov [esp+48h], ebx ; *
newlaos1:01039F09 pushf
newlaos1:01039F0A pushf
newlaos1:01039F0B mov [esp+4Ch], edi ; *
newlaos1:01039F0F jmp loc_103AB16
newlaos1:01038E2C mov [esp+54h], edx ; *
newlaos1:01038E30 call fake_call_33
newlaos1:01039A6E mov [esp+54h], esi ; *
newlaos1:01039A72 mov [esp+4], si
newlaos1:01039B1E mov [esp+4], ah
newlaos1:01039B22 mov [esp+54h], ebx ; *
newlaos1:01039B26 mov [esp+8], bl
newlaos1:01039B2A mov word ptr [esp+4], 0A44Bh
newlaos1:01039B31 mov word ptr [esp+0], 4D52h
newlaos1:01039B37 lea esp, [esp+54h] ; 平衡堆栈
newlaos1:01039B3B jmp loc_1039991 ;
newlaos1:01039B3B sub_1039B1E endp ; sp-analysis failed ;
newlaos1:01039B3B ; 0007FF78 7C930738 ntdll.7C930738
newlaos1:01039B3B ; 0007FF7C FFFFFFFF
newlaos1:01039B3B ; 0007FF80 0007FFF0
newlaos1:01039B3B ; 0007FF84 0007FF98
newlaos1:01039B3B ; 0007FF88 7FFDE000
newlaos1:01039B3B ; 0007FF8C 7C92EB94 ntdll.KiFastSystemCallRet
newlaos1:01039B3B ; 0007FF90 0007FFB0
newlaos1:01039B3B ; 0007FF94 00000000
newlaos1:01039B3B ;
newlaos1:01039B3B ; 0007FFA0 7FFDE000 ebx
newlaos1:01039B3B ; 0007FFA4 FFFFFFFF esi
newlaos1:01039B3B ; 0007FFA8 7C92EB94 ntdll.KiFastSystemCallRet edx
newlaos1:01039B3B ; 0007FFAC 7C930738 ntdll.7C930738 edi
newlaos1:01039B3B ; 0007FFB0 7FFDE000 ebx
newlaos1:01039B3B ; 0007FFB4 0007FFB0 ecx
newlaos1:01039B3B ; 0007FFB8 0007FFF0 ebp
newlaos1:01039B3B ;
newlaos1:01039B3B ; eax没有保存 直接在寄存器中
带星号的都是关键代码,上面都是在寄存器进栈操作。
newlaos1:01039991 push 411EC39Ah
newlaos1:01039996 xchg si, bp
newlaos1:01039999 not dh
newlaos1:0103999B mov [esp], eax ; * 这里保存eax了
newlaos1:0103999B ; 前面都保存了别的regs了
newlaos1:0103999E lea esi, [ecx+1243303Ch]
newlaos1:010399A4 pushf ; 保存eflags
newlaos1:010399A5 inc si
newlaos1:010399A8 neg ebp
newlaos1:010399AA push 0 ; 这个0应该是ImageDelta
newlaos1:0103A3B4 xor bp, 0E390h
newlaos1:0103A3B9 rol al, 4
newlaos1:0103A3BC and di, 0C24Ch
//下面开始取keydata 计算vm_data地址了
newlaos1:0103A3C1 mov esi, [esp+30h] ; keydata
newlaos1:0103A3C5 bsf di, dx
newlaos1:0103A3C9 sub bp, 2EBAh
newlaos1:0103A3CE lea edi, [edx*8+0E847F6A0h]
newlaos1:0103A3D5 not esi ; not keydata
newlaos1:0103A3D7 rcl di, 9
newlaos1:0103A3DB shl dl, 1
newlaos1:0103A3DD bswap esi ; bswap keydata
newlaos1:0103A3DF neg bp
newlaos1:0103A3E2 dec bh
newlaos1:0103A3E4 add esp, 4 ; 平衡了堆栈
newlaos1:0103A3E7 jb fake_jxx_01 ;这里是个假跳
newlaos1:0103A3ED add esi, 0E0B43246h ; add E0B43246h
newlaos1:0103A3F3 xchg edi, ebp
newlaos1:0103A3F5 clc
newlaos1:0103A3F6 rol esi, 0Dh
newlaos1:0103A3F9 rol di, 6
newlaos1:0103A3FD neg esi
newlaos1:0103A3FF shr bx, cl
newlaos1:0103A402 neg ebp
newlaos1:0103A404 shr bp, 4
newlaos1:0103A408 sal bl, cl
newlaos1:0103A40A add esi, 3EB2B05Bh
newlaos1:0103A410 or ebp, esi
newlaos1:0103A412 pushf
newlaos1:0103A413 clc
newlaos1:0103A414 lea ebp, [esp+4] ; ebp指向ImageDelta
newlaos1:0103A414 ; ImageDelta
newlaos1:0103A414 ; Eflags
newlaos1:0103A414 ; 8个regs
newlaos1:0103A414 ; addr_no_use
newlaos1:0103A414 ; Keydata
//这里就很清楚的表明了ebp是指向参与真实运算的堆栈
newlaos1:0103A418 pusha
newlaos1:0103A419 bts edi, eax
newlaos1:0103A41C inc edi
newlaos1:0103A41D sub bh, bl
newlaos1:0103A41F sub esp, 9Ch ;这是再开辟一个空间 用来保存vm前环境
newlaos1:0103A425 xchg dh, bl
newlaos1:0103A427 sar bx, 1
newlaos1:0103A42A mov edi, esp ;把这个地址给edi
newlaos1:0103A42C VM_Pre_Start: ; CODE XREF: newlaos1:0103A3AFj
newlaos1:0103A42C clc
newlaos1:0103A42D mov ebx, esi ; 用来计算PCode的
newlaos1:0103A42F not al
newlaos1:0103A431 add esi, [ebp+0] ; + ImageDelta
newlaos1:0103A431 ; ebx VM_Data Org
newlaos1:0103A431 ; esi VM_Data
newlaos1:0103A431 ; edi 指向VM_Struct
newlaos1:0103A431 ; ebp 指向ImageDelta
下面就是VM的循环了:
newlaos1:0103A434 VM_Start: ; CODE XREF: fake_04-FADj
newlaos1:0103A434 ; newlaos1:010390CDj ...
newlaos1:0103A434 cmp cx, bp
newlaos1:0103A437 shr dh, cl
newlaos1:0103A439 pusha
newlaos1:0103A43A mov al, [esi-1] ; 用来计算dispatch_no了
newlaos1:0103A43D shl dx, 2
newlaos1:0103A441 or dx, 0B38Dh
newlaos1:0103A446 xor al, bl ; * 运算
newlaos1:0103A448 movsx dx, al
newlaos1:0103A44C mov [esp], al
newlaos1:0103A44F bsr dx, dx
newlaos1:0103A453 pushf
newlaos1:0103A454 not al ; * 运算
newlaos1:0103A456 movsx edx, dl
newlaos1:0103A459 pop edx
newlaos1:0103A45A db 66h
newlaos1:0103A45A bswap edx
newlaos1:0103A45D inc al ; * 运算
newlaos1:0103A45F dec dh
newlaos1:0103A461 shr dh, 1
newlaos1:0103A463 jmp loc_103AA7F
newlaos1:0103AA7F btc dx, ax
newlaos1:0103AA83 xor al, 76h ; * 运算
newlaos1:0103AA85 shrd edx, esp, 5
newlaos1:0103AA89 not edx
newlaos1:0103AA8B sub al, 22h ; * 运算
newlaos1:0103AA8D cmp al, 7Eh
newlaos1:0103AA8F cmc
newlaos1:0103AA90 shrd dx, ax, cl
newlaos1:0103AA94 xor bl, al ; * 修改ebx
newlaos1:0103AA96 stc
newlaos1:0103AA97 setns dh
newlaos1:0103AA9A bt ax, 0Ch
newlaos1:0103AA9F rcr dx, cl
newlaos1:0103AAA2 movzx eax, al ; 取得dispatch_no
newlaos1:01039A24 mov edx, ds:dispath_table[eax*4] ; * 取加密的dipatch_code数据
newlaos1:01039A2B pushf
newlaos1:01039A2C bt cx, sp
newlaos1:01039A30 ror edx, 19h ; * 运算
newlaos1:01039A33 cmp bl, bh
newlaos1:01039A35 bt si, 0Dh
newlaos1:01039BBA test si, sp
newlaos1:01039BBD lea esi, [esi-1] ;可以明显看出esi是朝着递减的方向进行的
newlaos1:01039BC0 jmp loc_103AC32
newlaos1:0103AC32 clc
newlaos1:0103AC33 stc
newlaos1:0103AC34 neg edx ; *运算
newlaos1:0103AC36 clc
newlaos1:0103AC37 jmp loc_103A944
newlaos1:0103A944 clc
newlaos1:0103A945 rol edx, 7 ; *运算
newlaos1:0103937F mov dword ptr [esp+4], 0DC426B8Ah
newlaos1:01039387 mov word ptr [esp+0Ch], 74B5h
newlaos1:0103938E not edx ; * 运算
newlaos1:01039390 mov byte ptr [esp+0], 29h
newlaos1:01039394 push 9CB0A2BBh
newlaos1:01039399 mov [esp+34h], edx ; dispath_code地址
newlaos1:0103939D pushf
newlaos1:0103939E pushf
newlaos1:0103939F push 0C56A1E5Ch
newlaos1:010393A4 pushf
newlaos1:010393A5 push dword ptr [esp+44h] ; dispath_code地址
newlaos1:010393A9 retn 48h ; 平衡堆栈
然后进入对于的dispatch_code
256个dispatch_no,一共识别出来了44条dispatch_code,和1.22很接近,指令几乎一样,这也为降级成1.22提供了一个条件。识别出来的地址,都可以参考附件。
到这里,基本上就和1.22的东西对应起来了,可以模拟的写出程序,来跟踪这些代码,不过工作量还是很大:(
大概就这些,欢迎大家补充。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)