欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

PE节load时的对齐问题

PE节load时的对齐问题

【  标题  】 PE节load时的对齐问题
【  作者  】 linxer
【  环境  】 xp sp2
【  Q Q   】 3568599
【  声明  】 俺系初级选手,高手略过。失误之处敬请诸位大侠赐教!

本问题的研究源至upack 0.32 load后虚拟脱壳,发现自己写的load程序有很大问题,upack 0.32加壳后程序虚拟机中跑不起来,连入口处反汇编出来的代码都不对......后来,发现是section load不对。经过本人反复改写节表头信息(IMAGE_SECTION_HEADER),然后用OD加载,看其怎么load,发现以下规律,以前曾在论坛上发过一段PE load的代码,当时,对节表对齐属性该怎么效验也不清楚,那个时候,在load FSG1.33时,发现有些节属性是不对齐的,但windows却能让这个程序跑起来,心中甚是不解,后来没有办法,干脆把节对齐属性效验部分给屏蔽了(其实那个时候是自己的程序写的不对,效验太严格了)
前几天,在expressor v1.2虚拟脱壳后dump时,发现dump后程序windows无法load,我用OllyDump插件把OD中脱壳后程序dump出来也有这样的问题,后来发现是:expressor v1.2加壳后程序,其SizeOfImage不为SectionAlignment整数倍,我写的那个dump是在相对PE头SizeOfImage处建新节的(从以前看的OllyDump插件源码学的,不过,目前最新的源码好像修正这个bug了,可能是我用的插件比较老吧,才load不起来),这样就出现了最后一个节没对齐,windows load不起来。
下面结合我用upack v0.32加壳后程序说明之,高手请飘过,内容很简单。
以下示例中对齐属性如下:
00000078    00100000    DD 00001000          ;  SectionAlignment = 1000
0000007C    00020000    DD 00000200          ;  FileAlignment = 200
说下FileAlignment字段吧,这个字段在有些程序可以看到是0x1000等,我通过试验,好像发现这个字段不管是什么值,系统都用0x200,不过它必须大于等于0x200,小于等于0x1000,并为0x200整数倍
为了说明问题,这里定义两个变量:
SectionAlignmentMask = SectionAlignment - 1;
FileAlignmentMask = 0x200 - 1;
如不加说明,以下几个示例均非最后一个节
示例1:
节表信息如下:
00000138    2E 55 70 61>ASCII ".Upack"       ; SECTION
00000140    00800000    DD 00008000          ;  VirtualSize = 8000 (32768.)
00000144    00100000    DD 00001000          ;  VirtualAddress = 1000    //按一般书上说,这个要对齐
00000148    B7000000    DD 000000B7          ;  SizeOfRawData = B7 (183.)   //按一般书上说,这个也要对齐
0000014C    11000000    DD 00000011          ;  PointerToRawData = 11
00000150    00000000    DD 00000000          ;  PointerToRelocations = 0
00000154    00000000    DD 00000000          ;  PointerToLineNumbers = 0
00000158    0000        DW 0000              ;  NumberOfRelocations = 0
0000015A    0000        DW 0000              ;  NumberOfLineNumbers = 0
0000015C    600000E0    DD E0000060          ;  Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE
上面这个节,貌似:将磁盘中偏移0x11,大小0xb7的内容拷到内存中VirtualAddress=0x1000处,当然这个节在内存中大小是0x8000,未填充部分全填0
但,其实不是这样的,windows在load的时候是将磁盘中偏移0x00,大小0x200的内容拷到内存中去了,出乎我这个菜鸟的意料了

示例2:
节表信息如下:
00000138    2E 55 70 61>ASCII ".Upack"       ; SECTION
00000140    00800000    DD 00008000          ;  VirtualSize = 8000 (32768.)
00000144    00100000    DD 00001000          ;  VirtualAddress = 1000
00000148    B7020000    DD 000002B7          ;  SizeOfRawData = 2B7 (695.)
0000014C    f1000000    DD 000000f1          ;  PointerToRawData = f1
00000150    00000000    DD 00000000          ;  PointerToRelocations = 0
00000154    00000000    DD 00000000          ;  PointerToLineNumbers = 0
00000158    0000        DW 0000              ;  NumberOfRelocations = 0
0000015A    0000        DW 0000              ;  NumberOfLineNumbers = 0
0000015C    600000E0    DD E0000060          ;  Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE
看下这个win怎么load到内存的,它将磁盘中偏移0x00,大小0x400的内容拷到内存中去了
好了,看了上面两个例子,你应该知道它是怎么从磁盘中拷数据出去了吧,其思路用代码表示如下:
SizeOfRawData = (SizeOfRawData + FileAlignmentMask) & (0xffffffff ^ FileAlignmentMask);
PointerToRawData = PointerToRawData & (0xffffffff ^ FileAlignmentMask);

示例3:
节表信息如下:
00000138    2E 55 70 61>ASCII ".Upack"       ; SECTION
00000140    00810000    DD 00008100          ;  VirtualSize = 8100 (33024.)
00000144    00100000    DD 00001000          ;  VirtualAddress = 1000
00000148    B7020000    DD 000002B7          ;  SizeOfRawData = 2B7 (695.)
0000014C    f1000000    DD 000000f1          ;  PointerToRawData = f1
00000150    00000000    DD 00000000          ;  PointerToRelocations = 0
00000154    00000000    DD 00000000          ;  PointerToLineNumbers = 0
00000158    0000        DW 0000              ;  NumberOfRelocations = 0
0000015A    0000        DW 0000              ;  NumberOfLineNumbers = 0
0000015C    600000E0    DD E0000060          ;  Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE
上面这个例子把VirtualSize改成没有对齐了,发现程序图标也没了,windows还load不起来了,这个地方为什么不能运行是因为我们把VirtualSize调大了,占了第二个节的地方了,如果调小的话,则可以运行,可以看出这个字段的效验是不跨越其它节地界,这个字段只要不跨越其它节地界,为任何值都可以,0也不例外,这个值除了用来效验,没有其它用

示例4:
节表信息如下:
00000138    2E 55 70 61>ASCII ".Upack"       ; SECTION
00000140    00800000    DD 00008000          ;  VirtualSize = 8000 (32768.)
00000144    00110000    DD 00001100          ;  VirtualAddress = 1100
00000148    B7000000    DD 000000B7          ;  SizeOfRawData = B7 (183.)
0000014C    11000000    DD 00000011          ;  PointerToRawData = 11
00000150    00000000    DD 00000000          ;  PointerToRelocations = 0
00000154    00000000    DD 00000000          ;  PointerToLineNumbers = 0
00000158    0000        DW 0000              ;  NumberOfRelocations = 0
0000015A    0000        DW 0000              ;  NumberOfLineNumbers = 0
0000015C    600000E0    DD E0000060          ;  Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE
这个示例跟示例3一样,图标没了,windows也load不起来了,VirtualAddress这个字段也是要严格效验的

好了,下面来看最后一个节的情况(以下均为最后一个节了)

示例5:
节表信息如下:
00000160    2E 72 73 72>ASCII ".rsrc"        ; SECTION
00000168    00500000    DD 00005000          ;  VirtualSize = 5000 (20480.)
0000016C    00900000    DD 00009000          ;  VirtualAddress = 9000
00000170    48320000    DD 00003248          ;  SizeOfRawData = 3248 (12872.)
00000174    00020000    DD 00000200          ;  PointerToRawData = 200
00000178    00000000    DD 00000000          ;  PointerToRelocations = 0
0000017C    00000000    DD 00000000          ;  PointerToLineNumbers = 0
00000180    0000        DW 0000              ;  NumberOfRelocations = 0
00000182    0000        DW 0000              ;  NumberOfLineNumbers = 0
00000184    600000E0    DD E0000060          ;  Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE
这个节是upack v0.32加壳后程序的最后一个节,是好的,可见SizeOfRawData也没有对齐,这说明最后一个节SizeOfRawData可以不对齐

示例6:这个示例我们让PointerToRawData不对齐看看
节表信息如下:
00000160    2E 72 73 72>ASCII ".rsrc"        ; SECTION
00000168    00500000    DD 00005000          ;  VirtualSize = 5000 (20480.)
0000016C    00900000    DD 00009000          ;  VirtualAddress = 9000
00000170    48320000    DD 00003248          ;  SizeOfRawData = 3248 (12872.)
00000174    10020000    DD 00000210          ;  PointerToRawData = 210
00000178    00000000    DD 00000000          ;  PointerToRelocations = 0
0000017C    00000000    DD 00000000          ;  PointerToLineNumbers = 0
00000180    0000        DW 0000              ;  NumberOfRelocations = 0
00000182    0000        DW 0000              ;  NumberOfLineNumbers = 0
00000184    600000E0    DD E0000060          ;  Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE
这么一改,程序图标又没了,windows也load不起来了,这是因为我们加大了PointerToRawData,导致SizeOfRawData + PointerToRawData > 文件长度了,所以windows不让load了,如果将其改小的话,可以load,但是会出现什么错误视具体程序定

下面还是冗余的改下VirtualSize和VirtualAddress字段吧,看看如何
示例7:
节表信息如下:
00000160    2E 72 73 72>ASCII ".rsrc"        ; SECTION
00000168    00500000    DD 00005000          ;  VirtualSize = 5000 (20480.)
0000016C    00910000    DD 00009100          ;  VirtualAddress = 9100
00000170    48320000    DD 00003248          ;  SizeOfRawData = 3248 (12872.)
00000174    00020000    DD 00000200          ;  PointerToRawData = 200
00000178    00000000    DD 00000000          ;  PointerToRelocations = 0
0000017C    00000000    DD 00000000          ;  PointerToLineNumbers = 0
00000180    0000        DW 0000              ;  NumberOfRelocations = 0
00000182    0000        DW 0000              ;  NumberOfLineNumbers = 0
00000184    600000E0    DD E0000060          ;  Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE
让VirtualAddress不对齐后,程序图标又没了,windows也load不起来了,可见最后一个节的VirtualAddress也有严格的效验

示例8:
节表信息如下:
00000160    2E 72 73 72>ASCII ".rsrc"        ; SECTION
00000168    10500000    DD 00005010          ;  VirtualSize = 5010 (20496.)
0000016C    00900000    DD 00009000          ;  VirtualAddress = 9000
00000170    48320000    DD 00003248          ;  SizeOfRawData = 3248 (12872.)
00000174    00020000    DD 00000200          ;  PointerToRawData = 200
00000178    00000000    DD 00000000          ;  PointerToRelocations = 0
0000017C    00000000    DD 00000000          ;  PointerToLineNumbers = 0
00000180    0000        DW 0000              ;  NumberOfRelocations = 0
00000182    0000        DW 0000              ;  NumberOfLineNumbers = 0
00000184    600000E0    DD E0000060          ;  Characteristics = CODE|INITIALIZED_DATA|EXECUTE|READ|WRITE
让VirtualSize不对齐后,程序图标还有(我们是加大了VirtualSize,如果减小,估计就会没了,555,失算了,把它改成0,还有图标),但是windows load不起来了,如果把它改成0x4500也可以运行,这里调大了,导致最后一个节的VirtualAddress + VirtualSize > SizeOfImage了,因此load不起来,这种情况windows也是要效验的

下面给出这些属性的效验代码:
//pe check函数
//合法返回0,否则-1
//lpExePEBuff是pe文件地址
//nExePELen是pe文件长度
long pe_check(char *lpExePEBuff, unsigned long nExePELen)
{
  unsigned long lSectionNum;
  unsigned long lFileAlign;
  unsigned long lFileAlignMask;
  unsigned long lSectionAlignMask;
  unsigned long nPointerToRawData;
  unsigned long nSizeOfRawData;
  unsigned long nSizeOfImage;
  unsigned long nIndex;
  IMAGE_DOS_HEADER *imDos_Headers;
  IMAGE_NT_HEADERS * pINH;
  
  IMAGE_SECTION_HEADER *pISH;
  IMAGE_SECTION_HEADER * pOldEndISH;
  //MZ PE标志效验省去
  imDos_Headers = (IMAGE_DOS_HEADER *)lpExePEBuff;
  pINH = (IMAGE_NT_HEADERS *)((char *)lpExePEBuff + imDos_Headers->e_lfanew);//NT头指针地址
  
  //取PE文件的节数量
  lSectionNum = pINH->FileHeader.NumberOfSections;
  
  pISH = (IMAGE_SECTION_HEADER *)((char *)pINH + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER) + pINH->FileHeader.SizeOfOptionalHeader);
  pOldEndISH = pISH + lSectionNum - 1;

  //SizeOfImage按对齐属性向上取整
  nSizeOfImage = pINH->OptionalHeader.SizeOfImage;
  //各节在磁盘中的对齐掩码
  lSectionAlignMask = pINH->OptionalHeader.SectionAlignment - 1;  //各节在load后内存中的对齐掩码
  nSizeOfImage = (nSizeOfImage + lSectionAlignMask) & (0xffffffff ^ lSectionAlignMask);
  //效验最后节VirtualSize是否越界SizeOfImage
  if(pOldEndISH->VirtualAddress + pOldEndISH->Misc.VirtualSize > nSizeOfImage)
  {
    return -1;
  }
  //貌似FileAlignment无用,系统好像是用0x200,只要是0x200整数倍,不大于0x1000,程序都可以运行
  lFileAlign = g_pINH->OptionalHeader.FileAlignment;
  if(lFileAlign < 0x200 && lFileAlign > 0x1000)
  {
    return -1;
  }
  if(lFileAlign % 0x200)
  {
    return -1;
  }
  lFileAlign = 0x200;
  
  lFileAlignMask = lFileAlign - 1;                  
  for(nIndex = 0; nIndex < lSectionNum; nIndex++, pISH++)
  {
    //效验VirtualAddress对齐属性
    if(pISH->VirtualAddress & lSectionAlignMask)
    {
      //出现非法节
      return -1;
    }
    //效验要装载磁盘数据是否超出文件大小
    if(pISH->PointerToRawData + pISH->SizeOfRawData > nExePELen)
    {
      return -1;
    }
    //效验VirtualSize是否跨入下一节地界
    if(pISH != pOldEndISH)
    {
      if(pISH->VirtualAddress + pISH->Misc.VirtualSize > (pISH + 1)->VirtualAddress)
      {
        //出现非法节
        return -1;
      }
    }
    //不管对不对齐均重算PE节磁盘地址,磁盘中大小信息
    nPointerToRawData = pISH->PointerToRawData & (0xffffffff ^ lFileAlignMask);
    nSizeOfRawData = (pISH->SizeOfRawData + lFileAlignMask) & (0xffffffff ^ lFileAlignMask);
    //nSizeOfRawData要跟磁盘中剩余长度比较,取小者
    if(nSizeOfRawData > nExePELen - nPointerToRawData)
    {
      nSizeOfRawData = nExePELen - nPointerToRawData;
    }
    if(pISH != pOldEndISH)
    {
      //发现这个VirtualSize没有用,用0程序也可以运行,这里效验如下
      nVirtualSize = (pISH + 1)->VirtualAddress - pISH->VirtualAddress;
      //nSizeOfRawData大于nVirtualSize也不行
      if(nSizeOfRawData > nVirtualSize)
      {
        return -1;
      }
    }
    //如果要load的话,是用nSizeOfRawData/nPointerToRawData
  }
  
  return 0;
}

【  总结  】
1.VirtualSize可以不对齐,但是它表示的值不能跨到其它节地界
2.VirtualAddress在任何节中也都要对齐
3.SizeOfRawData没有对齐要求,如不对齐,按这个公式重算:SizeOfRawData = (SizeOfRawData + FileAlignmentMask) & (0xffffffff ^ FileAlignmentMask);
4.PointerToRawData没有对齐要求,如不对齐,按这个公式重算:PointerToRawData = PointerToRawData & (0xffffffff ^ FileAlignmentMask);

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功