脱壳_01_虚拟机壳_VMP

写在前面的话:

上一篇文章中,带领大家一起分析了简单的压缩壳ASPACK,今天,就和大家一起来揭开VMP这道神秘的面纱;

【花指令】:扰乱调试器的,并不执行;

【混淆】:对原指令进行拆解或等价替换,会执行;

零、自己写个程序,加壳

 0、为方便我们对VMP壳有更清楚的认识,这里,我们先自己写个程序,然后,加壳;

通过分析对比压缩前后的程序,辅助大家进行更深一层的了解,测试代码如下:

#include <windows.h>

int main(int argc, char** argv) {
    MessageBox(0, L"VMP Simple Example", L"Reginald", 0);

    ExitProcess(0);

    return 0;
}

1、加壳

已经生成了,这里是问,要不要运行,点击YES,会运行我们加壳后的程序,我们看一下加壳后的程序:

加壳完成,下面,我们一起来分析下加壳后的程序;

 一、分析加壳后的程序:

在我们进行脱壳时,如果事先不知道这是什么种类的壳,方法只能是单步跟踪了;

这里,在分析前,我们可以从宏观上看下,OEP处有何变化;

加密后的OEP;

加密前的OEP(VS编译的OEP特征 call xxx; jmp yyy)

因为在上面加壳的时候,我们选择了对OEP处的第一条指令加保护,所以,只有第一条指令发生了变化;

我们单步跟踪下这部分的代码:

遇到call,F7跟进去;

我们来看下:

1、保存寄存器环境;

2、将0xB50000 压栈;

3、ESI = *(ESP + 0x2C);

4、EBP = ESP,ESP -= 0xC0;开辟栈帧

5、EDI = ESP,EDI指向新栈顶

6、ESI += *(EBP);ESI内容加上原栈顶内容——我们一会动态分析下;

      EAX = *(ESI);

      ESI++;

7、跳转到*(EAX*4 + 0xF661E9);

目前,好像就是在做这些操作,我们貌似看不出什么了,但这里不免有几个疑问:

A) 0xB50000 有什么含义;

B) 最后跳转的时候,那个0xF661E9又有什么含义;

我们带着疑问继续动态分析:

目前,我们已经了解到了一些有用的信息:

1、EBP保存原始栈顶

2、EDI保存新栈顶(老栈顶 - 0xC0)

3、ESI处经过运算,将两个无意义的值,变化为了一个有效的地址值【距离VMP段首偏移0x78A处】

4、EAX从ESI处取值,每次1B;

5、进行跳转时,加的常数也是位于VMP区段的地址值【距离VMP段首偏移0x1E9处】

那我们就在010里瞅瞅,0x78A和0x1E9里到底有什么东西;(VMP段内偏移值)

到现在,我们可以做出如下假设:

0x1E9处相当于函数表首地址;

0x78A处取出下标,根据下标进行定位,找出该地址,跳转到此地址;

这些地址,也都位于VMP段,这也符合情况,稍后会给出解释;(什么叫符合情况);
这些地址,都是需要重定位的;

继续分析,验证假设:

 

我们在010里,0x1E9开始,找出0x1C个地址(地址4B):

0xF50000 + (0x4160B0 - 0x400000) = 0xF660B0(重定位后的地址),那我们要跳转的地址,是这个吗,在OD里走下:

接下来,我们继续分析:

 为了方便我们的分析,希望大家将下面的栈图放在心中:

我们已经大致清楚了这部分的逻辑,那我们先把那些用到的下标,找出来,并且找出它所对应的地址,静态的先分析下:

1C 14 30 38 10 00 0C 34 18 3C 08 28
1C:0xF660B0
14:0xF660B0
30:0xF660B0
38:0xF660B0
10:0xF660B0
00:0xF660B0
0C:0xF660B0
34:0xF660B0
18:0xF660B0
3C:0xF660B0
08:0xF660B0
28:0xF660B0

这些都一样,我们瞅瞅是干嘛的:

0xF660B0:【ESP、EDI不变,*(EDI + EAX) = *EBP,EBP += 4】

执行完后:【ESP、EDI不变,EBP += 0x30】

再接着看下面的:

62 60 12 40 00

62:0xF666C0


【EBP -= 4,*EBP = 0x401260(原ESP + 0x2C = 0x401260)
【EBP VS (EDI + 0x50)】
【(原ESP + 0x2C) > (原ESP - 0x70)】end

这里,要注意这个地方:

1、原ESP + 0x2C,这个位置还记得么,就是最初进入VM的时候,push xxx; call yyy的那个push处的地方;

2、这个地址,0x401260,看着这么亲民,有什么意义吗;

这里,为了弄清楚这个地址的含义,我们先看看未VM保护前的程序,其实,这里不得不说单纯VM的缺陷了,

它只是在做特别厉害的混淆,但是,不论你怎么混淆,该做的,一定要做的,否则,程序就错了;

那它应该做什么呢?还记得,我们加VM保护的指令吗?

OEP:CALL xxx

OEP+5:jmp yyy

CALL xxx = push(OEP+5) && jmp xxx;

我们即使加了VM保护,但是,总要执行xxx处的代码吧,执行完后,又一定会回到OEP+5那吧;

我们如何得到xxx的地址呢,别慌,我们看下机器码,知道OEP的地址了,又有E8后的偏移,不是问题:

这是原来的OEP处的数据,E8 AD 03 00 00

第一:0xF5125B + 0x3AD + 5 = F5160D(执行函数)
第二:0xF5125B + 5 = F51260(返回地址)

到这一步,我们回过头来,再看看刚刚那个亲民的地址:0x401260

这么有感觉吧,像不像一个没有被污染(重定位)的VA,如果是的话,Offset就是0x1260;

再看看那个返回地址0xF51260-0xF50000(当前基址)=0x1260,因此,基本确定,这里就是在搞事情了;

把执行完原该执行的函数后,返回的地址,放到了原ESP+0x2C的位置;一切构造的是那么巧妙,现在知道为啥那个位置要来个push了吧,真是一箭双雕;

思路清晰的读者,也许心中还有一个疑问,别忙,接下来就帮你解答:

1E 2B 04 1E
1E:0xF66757

【0xFF660B0逆操作,EDI、ESP不变,EBP -= 4,*EBP = *(EDI + EAX)】
【每次EBP向反方向变化,都要进行如下比较】
【EBP VS EDI + 0x50】
【原ESP + 0x28 > 原ESP - 0x70】end

2B:0xF6619B(神来之笔)

【*(EBP + 4) += *EBP; *EBP = EFLAGS; EDI不变,ESP不变, EBP不变,前俩内容有变化】

这是在干嘛呢,我们一步步分析到这里的时候:

EBP = 原ESP + 0x28; 

*(原ESP+0x2C) = 0x401260(未受污染的返回地址OEP+5);

解释下,就是在执行:

*(ESP+0x2C) += *(EBP)

EBP的值哪里来的,就是上一步过来的,EAX=0x1E 时的那步逆操作,注意EAX & 0x3C后和EDI相加的,也就是0x1C

【0xFF660B0逆操作,EDI、ESP不变,EBP -= 4,*EBP = *(EDI + EAX)】

由于EDI一直未有变化,我们只需要找到什么时候往EDI+0x1C里存东西,就可以了,就是最初的时候:那个1C:

最初的时候,EBP就是原栈顶,1C的时候,执行*(EDI+0x1C) = *EBP,其实就是把当时栈顶的数,放进去了,那当时栈顶的数是什么呢;说出来,你会惊呼它的神奇:

还记得当时,我们认定无效的两个数么,一个是push xxx; call yyy进入虚拟机的;一个是最后push的那个0xB50000

也就是说,当时栈顶的数据,是当前基址-默认基址,现在那些有疑问的朋友,也许如拨云见日了吧;

*(原ESP+0x2C) = 0x401260(未受污染的返回地址OEP+5);

*(原ESP+0x2C) += 0xB50000;(进行重定位)

04:0xF660B0

1E:0xF66757 由于两个对EBP而言是互逆的,因此,不再分析了;EBP还是原ESP+0x28

接下来,我们继续分析,还有最后一处让人惊艳的(加法结合律)

D7 0D 16 40 00
D7:0xF666C0
【EBP -= 4,*EBP = 0x40160D 原ESP + 0x24 = 0x40160D
【EBP VS EDI + 0x50】
【原ESP + 0x24 > 原ESP - 0x70】end

这个套路,我们已经分析过了,继续走:

4A:0xF6619B

我们已经知道,这是修复重定位的,但是,现在存放该执行函数地址的,是原ESP+0x24的位置,返回地址位置是ESP+0x2C;

乌云啊,是我们分析错了吗,别急,我们也许忽略了一些常识,人最容易忽视的,其实是司空见惯的东西;自认为常识的东西;

重新审视修复重定位的函数:

它执行的操作是什么呢:

*(EBP+4) += *(EBP)

*EBP = EFLAGS;

现在*EBP存放的是0x40160D(有意义的地址);那*(EBP+4)里是啥呢,刚刚在分析上一个重定位的时候,已经知道了,这个位置,是(当前基址 - 默认基址) = 0xB50000

哦,原来如此,这不就是一个结合律的问题吗,谁先加谁的问题,一个主动的,一个被动的,我们把目光都放在了那个更有意义的点了,忽略了另外的也是有意义的;

至此,我们得出结论:

原ESP+0x2C <=> 应该返回的位置;

原ESP+0x28 <=> 应该执行的位置;

分析至此,可以欣慰以下了,接下来,继续走,既然准备工作基本完成,猜测,该退出VM了;

04 3E 1A 36 0E 02 12 3A 32 16 1E 

04:0xF660B0

3E:0xF66757 互逆

1A:0xF66757
【原ESP + 0x20 > 原ESP - 0x70】end
36:0xF66757
【原ESP + 0x1C > 原ESP - 0x70】end
0E:0xF66757
【原ESP + 0x18 > 原ESP - 0x70】end
02:0xF66757
【原ESP + 0x14 > 原ESP - 0x70】end
12:0xF66757
【原ESP + 0x10 > 原ESP - 0x70】end
3A:0xF66757
【原ESP + 0xC > 原ESP - 0x70】end
32:0xF66757
【原ESP + 0x8 > 原ESP - 0x70】end
16:0xF66757
【原ESP + 0x4 > 原ESP - 0x70】end
1E:0xF66757
【原ESP > 原ESP - 0x70】end

到这里,EBP就是原ESP了,见到曙光了,再接着走:

3B
3B:0xF66091

POP次,ESP的内容刚刚好,0x28(该执行的位置) && 0x2C(该返回的位置)

 如此,我们静态分析了调用过程,为了验证猜想,直接再这个地方下断,看下栈结构:

 二、总结:

0、单纯的VMP壳,只是在混淆代码(不妨试试,在IAT地址处搞一搞,就清楚了)

1、最初push xxx; call yyy; 这个push一方面和另外的基址之差定位到下标;另一方面,填充一个栈位(其实就是占位的),最终会被call 指令本应push的地址给替换;

2、进入里面后,最后一个push的值,是基址之差,用来修复call重定位的

3、VMP里,大多是混淆后,变着法的操作原堆栈,在操作完成后,就退出了;

4、退出OEP特征码:89 EC ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? C3

5、其实,经过这方分析后,感觉特征仍有不少,另外,静态分析,也变成了可能;知道VMP段的RVA后 x4 00,也能帮助定位;

6、对于CALL指令,VM保护,就是如上的,也可以分析别的指令,了解其处理方法,总体而言,单纯的VM,仍然离VM有些距离;

希望,能有所帮助;

转载请注明出处,TKS;

posted @ 2018-04-20 13:42  Reginald.S  阅读(1847)  评论(1编辑  收藏  举报