最近在阅读《0day:软件漏洞分析技术》,我也会经常提炼出一些东西分享在博客中。
PE文件格式
0x00 概述
pe文件格式把可执行文件分成若干个数据节(section),不同的资源被存放在不同的节中。
一个典型的pe文件中包含的节如下:
.text 由编译器产生,存放二进制的机器代码,也是我们反汇编和调试的对象
.data 初始化的数据块,如宏定义、全局变量、静态变量等
.idata 可执行文件所使用的动态链接库等外来函数与文件的信息
.rsrc 存放程序的资源,如图标、菜单等
此外,我们还有可能看到如.reloc、.edata等等
0x01 虚拟内存
Windows的内存可以分为两个层面:物理内存和虚拟内存。其中,物理内存比较复杂,需要进入Windows内核级别ring0才能看到。通常在用户模式下,我们用调试器看到的内存地址是虚拟内存。
0x02 pe文件与虚拟内存之间的映射
首先我们了解几个重要的概念:
1.文件偏移地址(File Offset):数据在pe文件中的地址叫文件偏移地址。
2.装载地址(Image Base):PE装入内存时的基地址。默认情况下,EXE文件在内存中的基地址是0x00400000,DLL文件在内存中的基地址是0x10000000。
3.虚拟内存地址(Virtual Address,VA):PE文件中的指令被装入内存的地址。
4.相对虚拟地址(Relative Virtual Address,RVA):相对虚拟地址是内存地址相对于映射基址的偏移量
虚拟内存地址、映射基址、相对虚拟内存地址的关系:
VA = Image Base + RVA
如下图所示,在默认情况下,一般PE文件的0字节将对映到虚拟内存的0x00400000的位置,这个地址就是所谓的装载基址。
但是pe文件在装载时会由于参考的数据标准不同,导致数据结构存在一定差异。
1.PE文件中的数据按照磁盘数据标准存放,以0x200字节为基本单位进行组织。当一个数据节(section)不足0x200字节时,不足的地方将被0x00填充;当一个数据节超过0x200字节时,下一个0x200块将分配给这个节使用。因此PE数据节的大小永远是0x200的整数倍。
2.当代码装入内存后,将按照内存数据标准存放,并以0x1000字节位基本单位进行组织。类似的,不足将被补全,若超出将分配下一个0x1000为其所用。因此,内存中的节总是0x1000的整数倍。
我们把这种由存储单元引起的节基址差称为节偏移。
节偏移=相对虚拟偏移-文件偏移
0x03 lordPE工具
PE头信息
当我们在调试中遇到虚拟内存中0x00404141处的一条指令,那么要换算出这条指令在文件中的偏移量,则有
文件偏移量 = 0x00404141 - 0x00400000 - (0x1000 - 0x400)= 0x3541
0x04 Crack示例
下面是一段用于密码验证的C代码
#include <stdio.h> #include <string.h> #define PASSWORD "123456" #define FLAG "flag{1_@m_c_program}" int verify_password (char *password) { int authenticated; authenticated = strcmp(password,PASSWORD); return authenticated; } void main() { int valid_flag = 0; char password[1024]; while(1) { printf("please input password:"); scanf("%s",password); valid_flag = verify_password(password); if(valid_flag) { printf("incorrect password!\n\n"); } else { printf("Congratulation!\n %s\n",FLAG); break; } } }只有当输入正确密码时,才能获取其中的flag。
除了反编译直接找flag,我们还可以通过动态调试找到关键判断逻辑,修改判断逻辑获取flag。