upack是一个压缩器,用于压缩PE文件,跟随逆核一起深入学习PE文件结构

特征

PE文件经过upack之后有以下特点:

重叠文件头

  • 把MZ文件头(dos头)和PE头重合在一起

  • 原理是dos头中的e_lfanew指向NT头但是并没有说NT头不能和DOS头重合,一般情况下:

  • e_fanew = MZ文件大小(40h)+DOS存根=E0
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    - 但是Upack后e_fanew值是10h,所以hex方式打开后,第一排是4D5A下一排就是5045


    ## 在可选头中插入解码代码

    - 修改SizeOfOpionalHeader的值增加至148h,这样在节区头和可选头中间就可以插入解码代码,此时节区头的起始偏移是

    - ```
    170h=28h(可选头起始偏移)+148h
  • (SizeOfOpionalHeader的作用是想要根据PE文件形态更换和插入其它可选头形态的结构体)

在文件头插入解码代码

  • 可选头中的NumberOfRvaAndSizes由10h变为Ah,使得DataDirectory只有前10个元素,,并把后面6个元素的位置改为自己的解码代码

在节区头中记录自身数据和重叠节区

  • 节区头中类似重定位节区是不需要的,所以这种节区可以用于记录自身数据
  • 整个PE文件分为4个部分:PE头,第一节区,第二节区,第三节区.有以下几个特性:
  • 内存中,PE头,第一节区,第三节区均由同一区域映射而来,即SizeOfRawData和RawOffset相同,但是VirtulSize不同
    • 源文件压缩于第二节区
    • 第一节区的SizeOfImage与原PE文件相同,即原PE文件会解压到第一节区

RVA to RAW

标准操作是:

RAW=RVA-VA+PointerToRawData

但是经过upack后,第一节区的PointerToRawData值为10,然而不为FileAlignment(200h)的倍数,因此PE装载器强制识别为其整倍数(确保指向一页),令其等于0

所以正确值是:

RAW=RVA-VA

导入表

  • 通过存在在节区的末尾,省略最后的null结构体,映射到内存时,后面自然有null填充,这样可以节约空间

导入地址表

  • 在DOS存根中存放导入DLL的名称,查看FirstThunk可以找到对应加载的函数.

调试过程

  • 先是解码循环,再是设置IAT(和UPX差不多?),但是设置IAT的方法是用导入的函数LoadLibraryA和GetProcAddress一边循环一遍构建(先获取导入函数实际地址,再写入原函数)IAT