首页
社区
课程
招聘
[原创]学写压缩壳心得系列之一 熟悉概念,未雨绸缪
发表于: 2012-2-1 01:32 15100

[原创]学写压缩壳心得系列之一 熟悉概念,未雨绸缪

2012-2-1 01:32
15100

这是我对之前学习写一个简单压缩壳的总结。期间查阅了很多的资料,借鉴了很多的代码,做了很多的笔记,有一些小心得和体会,希望与大家共同的交流和学习。再一次的感谢各位前辈们提供的无数资料。
   
     文章分为了4个部分,从最基本的概念,到最后学习实践的原理和相关代码,都在这个系列之中。文章包括:

1.数据与指令,以及加载前期相关概念(已完成)
2.pe文件的结构解析(已完成)
3.pe文件load的过程(已完成)
4.壳的处理(已完成)

     因为小弟才疏学浅,肯定有不足和错误之处,希望各位大虾能多多的指点。希望为学习写压缩壳的朋友提供一点参考,哪怕是一点点,就很知足了:)

1.数据与指令

     冯诺依曼计算机体系中,数据和指令本质上如同棋盒里的棋子和棋盘上对弈的棋子一样。数据与指令从广义的定义上来说是一样的。在某个时候,我们很难去分清出什么是指令,什么是数据。借王爽《汇编语言》中的一个比方,将数据比喻成是棋盘里面的棋子。比赛时的棋子才按照棋盘的规律一个一个的排好,比赛后,所有的棋子都装在棋盒内。不同的时候,棋子比赛中和比赛后的状态,就可以去对应数据和指令的状态。当进行比赛的时候,根据比赛的规则,棋子要放在格子里,而且格子有大小,一个格子只能放一个,不能叠加。而不进行比赛的时候,一大把的棋子任意的放在盒子里面。棋子始终是棋子,促使他们之间状态的不同的是比赛的规则。就如同数据和指令一样,本质上来说是相同的,放在磁盘里保存的时候,就按照了磁盘保存的规则,载入内存运行的时候,就按照了内存载入的规则。

     那么,怎么去看这个规则呢?没有比赛的时候,棋子可以是按照装在棋盒子的“规则”(就随意的倒进去,只要还有空隙就可以塞进去了,直到塞不进,我就用另一个棋盘装),也可以按棋盘的“规则”(一个一个的放在棋盘线的交叉点上),但是充其量也只能放361个,因为一个棋盘只有361个交叉点,如果又买了一副棋,放第362粒,就要拿一另外一个棋盘了。通过不同的装棋规则,我们可以知道什么时候是在下棋,什么时候是在休息。由此,我们引入了映射的概念。

映射关系:

在了解pe装载的过程中,我们假象要载入内存,就是要进行比赛。任何的比赛是有其规矩的,这样棋子就不能像装载棋盒里那样紧凑,必须按照棋盘的格子一格一格的对应。同样,程序从硬盘载入的到内存也是一样。系统规定了程序载入的时候必须按照某一个尺寸来对齐,而这个尺寸和硬盘的对齐尺寸是不一样的。这就是映射的根本。如图:可以看到,程序在硬盘和内存中的大小是不一样的,期间,为了满足内存装载的规则,就必须对原本没有的地方,进行填充。其实,说到底,就是一切要按规则办事情:)



    例如,我要把一个程序载入到内存中,内存中规定,装载的最小单位就是1000字节。我只有1个字节,也要用到1000字节,因为一个最小单位是1000。1001个字节,就要用到2000字节的空间,也是因为一个最小单位是1000,要装剩下的1个字节必须要动用下一个最小单元的1000字节,尽管只装1字节。

    回到图。这里在磁盘中的对齐大小是200h,在磁盘中装入数据的最小对齐单位就是200h,而内存中对齐的是1000h,也就是说内存中装入数据的最小对齐单位就是1000h。

既然磁盘文件映射到了内存,就会产生出一个映像。我们称之为内存映像。这里的“像”对应的实体就是磁盘文件,我们不能说他是原本复制,因为载入到了内存,就要按照内存的载入规矩。载入规则的不同,导致“像”的内容相同,形态大小不同。

这样,根据不同的规则,磁盘文件实体和内存映像虽然是衍生的关系,由于装载对齐的粒度(即装载数据最小对齐单位)不同,成的“像”即不同。

PE加载准备
    PE loader要想从磁盘装载一个程序到内存中去,必须要知道从哪里装载,装载到哪里去。你写一个PE文件,就要告诉loader要怎么去装载,这就是pe文件各个部分所提供信息的根源。如同给loader一个地图指南,告诉loader一个个关于PE的信息,定位到磁盘中的文件,装载到内存中去。

    抛开具体的数据不谈,其实整个PE结构里面许多结构都是通知loader要怎么去加载和在哪里加载它本身。所以,当看到PE结构一大堆结构的时候,不要头疼,细想一下,不就是一堆地址嘛:)

    另外,PE中有的信息并不是loader时候必须的,有的仅仅是校验而已。只要正确的按照loader的形式和在loader容错范围内,PE文件都是可以被正常加载的:)。

值得注意的是,由于加载粒度的不同,对于多出来的空余加载地址,PE Loader都会填0.

ImageBase、RVA 和 FileOffset

    一个pe放在内存中去,不可能给你一个绝对的地址。因为loader不能确定这个地址一定可用或可被加载。在这样一个前提下,能提供给loader的是许多相对于基址(IMAGEBASE)的偏移(RVA),根据PE加载的基址(IMAGEBASE),所有的要加载的绝对地址VA(IMAGEBASE + RVA),都根据这个偏移即可计算出来。

    RVA 和 FileOffset这两个名词一个是相对于内存基址的偏移,一个是相对于磁盘基址的偏移。根据装载规则的不同,就有两套偏移。

    IMAGEBASE、RVA 和 FileOffset这三者结合的寻址方式使loader的时候更加轻松,加载时只需要得到IMAGEBASE,便可以根据PE内部结构中的RVA 和 offset按图索骥了。

RVA 和 FileOffset的转换

    内存偏移和磁盘偏移结果不同,最根本的原因是,对齐粒度的不同。那么要进行转换,必须得过渡到统一的粒度标准上来。通常,在首先判断要转换的RVA在哪一个区段中,然后进行再进行粒度的统一。

    回到图中:假设我们要求00401010h的磁盘偏移,我们发现该地址在.text段中,而.text起始内存映像加载绝对地址是401000h,IMAGEBASE为4000000h,两者相减得出相对加载地址为1010h。然后再减去.text根据内存对齐粒度对齐后的偏移1000h,加上.text磁盘偏移地址得出了410h,即为所求。

    再例如,我们要求出00403030h的磁盘偏移。该地址在 .data中,.data起始内存映像加载绝对地址是403000h,IMAGEBASE为4000000h,两者相减得出相对加载地址为3030h。再减去.data根据内存对齐粒度对齐后的偏移3000h,加上.data磁盘偏移地址得出了410h,即为所求。

   这样,我们可以统一出通用转换公式:

FileOffset = VA - IMAGEBASE - 该地址所在节内存起始偏移 + 该地址所在节磁盘起始偏移

很多解析工具里面转换公式还有一个版本的写法:

k = 该地址所在节内存起始偏移 - 该地址所在节磁盘起始偏移
FileOffset = VA - IMAGEBASE - k

其实意思是一样,个人认为第一个版本更加的直观些,任君选择:)

微软为此提供了一个函数ImageRvaToVa,注意这里有个名称上的不同,即后面的VA是指的文件中的offset,我么来看看这个函数在MSDN上的说明


LPVOID ImageRvaToVa(
IN PIMAGE_NT_HEADERS NtHeaders,
// NT头
IN LPVOID Base, // // MapViewOfFile载入磁盘基址
IN DWORD Rva, // 待转Rva
IN OUT PIMAGE_SECTION_HEADER *LastRvaSection //最后一个节地址,可置NULL
);

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 6
支持
分享
最新回复 (18)
雪    币: 94
活跃值: (475)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢,写的很全面。请问还会有下一个系列么?我也想学
2012-2-1 08:07
0
雪    币: 1981
活跃值: (771)
能力值: ( LV13,RANK:420 )
在线值:
发帖
回帖
粉丝
3
VirtualAddress>pSH->VirtualAddress

应该有 = 的
畸形文件很多,你上面只考虑了一种,
例如
计算test1的 RVA = 0x1000时的offset
计算test2的 RVA = 0x15CD时的offset
上传的附件:
2012-2-1 09:57
0
雪    币: 9
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
MARK
2012-2-1 10:38
0
雪    币: 408
活跃值: (156)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
5
[QUOTE=demoscene;1041276]
VirtualAddress>pSH->VirtualAddress

应该有 = 的
畸形文件很多,你上面只考虑了一种,
例如
计算test1的 RVA = 0x1000时的offset
计算test2的 RVA = 0x15CD时的offset[/QUOTE]

恩,感谢你的提醒,边界的判断忘记考虑到:)已修正,当初考虑到某些变形PE会把数据放在表头,ms的那个ImageRvaToVa老回出错,所以针对那种情况写了上面那个函数

test1的 RVA = 0x1000时,offset是第一个节头,如果磁盘文件有数据映射的话,PointerToRawData
就是要求的偏移了,第一个映射的节内对齐的颗粒已经是统一的了,只要有数据可以印映射可以直接返回PointerToRawData,test2用上述算法RVA = 0x15CD可以定位到offsetdemoscene还有更好的方法欢迎指点:)
2012-2-1 12:14
0
雪    币: 408
活跃值: (156)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
6
恩,几个月前就想写一下心得了,构想了好久,不知道从哪里下笔,水平有限,涉及的东西有太多了,我会尽力描述清楚的:)欢迎多多指导
2012-2-1 12:27
0
雪    币: 230
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
mk一下吧……
2012-2-1 12:40
0
雪    币: 1981
活跃值: (771)
能力值: ( LV13,RANK:420 )
在线值:
发帖
回帖
粉丝
8
test2用上述算法RVA = 0x15CD可以定位到offset

定位到的是多少,正确的应该是0x9CD,你可以用OD载入和用winhex载入对比看看
2012-2-1 14:39
0
雪    币: 408
活跃值: (156)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
9
[QUOTE=demoscene;1041382]
test2用上述算法RVA = 0x15CD可以定位到offset

定位到的是多少,正确的应该是0x9CD,你可以用OD载入和用winhex载入对比看看[/QUOTE]

恩,开始没注意,刚看了,test2 PointerToRawData被指定到了0x450h,但是loader在检查的时候是按照0x400h计算的,会导致0x50h偏移,记得以前linxer有篇文章写过了,loader装载的PointerToRawData按照:
PointerToRawData = PointerToRawData & (0xffffffff ^ FileAlignmentMask),所以还要考虑PointerToRawData的取值:)
2012-2-1 17:19
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
学习壳的第一步
2012-2-1 17:29
0
雪    币: 1163
活跃值: (137)
能力值: ( LV12,RANK:230 )
在线值:
发帖
回帖
粉丝
11
不错的科普文。我先标记"关注",期待你后续的文章,到时整合在一起。
2012-2-2 21:43
0
雪    币: 408
活跃值: (156)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
12
恩,感谢铅笔牛的赞赏,我会加倍努力完成这个系列的:)
2012-2-3 00:28
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
[QUOTE=小小的心;1041244] 载入规则的不同,导致“像”的内容相同,形态大小不同。

[QUOTE]

仔细看了一遍,这里有没有点问题?
2012-2-6 00:18
0
雪    币: 408
活跃值: (156)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
14
[QUOTE=mumaren;1042450][QUOTE=小小的心;1041244] 载入规则的不同,导致“像”的内容相同,形态大小不同。




仔细看了一遍,这里有没有点问题?


呵呵,这里的“像”就是映像文件的意思,类比如镜子中的“像”,是一个虚指的概念,实体是磁盘中的文件,因为映射到了虚拟地址空间,所以称为“像”。“像”的内容相同表示的是其自身同实体是一样,大小不同时因为按照不同的对齐粒度,产生了不同的大小:)
2012-2-6 00:26
0
雪    币: 1482
活跃值: (2553)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
先mark 便于查找。
2012-2-16 17:30
0
雪    币: 70
活跃值: (55)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
好久不来看雪了,继续开始学习了
2012-2-17 10:24
0
雪    币: 34
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
17
FileOffset = VA - IMAGEBASE - 该地址所在节内存起始偏移 + 该地址所在节磁盘起始偏移

FileOffset - 该地址所在节磁盘起始偏移 = VA - IMAGEBASE - 该地址所在节内存起始偏移

FileOffset - 该地址所在节磁盘起始偏移 = VA -(IMAGEBASE + 该地址所在节内存起始偏移)

FileOffset - 地址节头所在磁盘偏移 = VA - 地址节头所在内存偏移

是不是我太蠢了点 这个公式看了好久才看明白。。。。
这个公式的意思是不是就是 不管在磁盘或者内存中PE文件是以多少字节对齐的 同一个节内任何地方的地址相对于节头偏移总是相等的 也就是加载到内存的时候磁盘上面同一个连续的节 在内存中也一定是连续的 不知我这样想是不是对的
2012-2-19 19:30
0
雪    币: 77
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
精简。简练。初学的经典
2012-2-24 12:04
0
雪    币: 408
活跃值: (156)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
19
差值就是利用的相对节头偏移:)
2012-2-24 20:54
0
游客
登录 | 注册 方可回帖
返回
//