zoukankan      html  css  js  c++  java
  • 脱壳_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;

  • 相关阅读:
    Kafka Shell基本命令(包括topic的增删改查)
    thefuck的安装和使用
    Linux运维利器之ClusterShell
    MySQL数据库的10大经典错误案例
    Mysql 常用操作
    Git 忽略特定文件或文件夹
    为什么需要拷贝构造函数
    C语言编译过程
    设计模式之建造者模式
    设计模式之工厂/抽象工厂模式
  • 原文地址:https://www.cnblogs.com/Reginald-S/p/8886515.html
Copyright © 2011-2022 走看看