内存读写复习
想当年也是做pwn题的,又看了看视频,发现有些东西还是忘记了。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 byte 8 位WORD 16 位DWORD 32 位 取值MOV EAX ,DWORD PTR DS :[0x123FC2 ] 取内存地址 编号LEA EAX ,DWORD PTR DS :[0x123FC2 ] 内存前的DS ES SS DS 是个数 ESP EBP : SS EDI :ES ESP 栈顶EBP 栈底push 入栈pop 出栈 栈高往低走push 相当lea esp ,dword ptr ss :[esp -4 ]mov dword ptr ss [esp ],eax 栈顶拔高ECX 值决定REP 指令重复次数 stos指令,它的功能是将eax 中的数据放入的edi 所指的地址中,同时,edi 会增加4 个字节,rep 使指令重复执行ecx 中填写的次数。 方括弧表示存储器,这个地址实际上是edi 的内容所指向的地址。这里的stos其实对应的是stosd ,其它的还有CALL : EIP 改为函数地址,返回地址压栈RET 返回时改EIP (弹栈,esp 加)
pe头解析
内容
以下内容有参考 x复制✓[原创]归纳PE结构基础知识,顺便手撕个PE-编程技术-看雪论坛-安全社区|安全招聘|bbs.pediy.com
内存对齐和硬盘对齐
就是一个pe文件在硬盘上的数据可能和硬盘中的不一样
因为老软件硬盘对齐是200h
内存对齐是1000h
后来就对齐一样了
使用分节结构原因:
节省内存,多开的时候只需要
DOS头
1 2 3 4 5 6 7 typedef struct _IMAE_DOS_HEADER { WORD e_magic; ... ... ... ... LONG e_lfanew; } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
重要:
1.WORD e_magic (0x00处)
● 对应PE文件的开头,是PE文件DOS头的标识符"MZ"→0x5A4D
○ 对应Winnt.h头文件中宏定义: #define IMAGE_DOS_SIGNATURE 0x4D5A // MZ
2.LONG e_lfanew (0x3C处)
● 对应PE文件0x3C处指向NT头在文件中的偏移(默认0xB0),即32位及以上系统文件头在文件中真正的偏移
2.NT头
(1)DWORD Signature:"PE\0\0"→0x00004550
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; 32 位:#define IMAGE_FILE_MACHINE_I386, 0x014c 64 位:#define IMAGE_FILE_MACHINE_AMD64, 0x8664 节表紧跟在IMAGE_NT_HEADERS后面,此字段决定了节表中元素的个数,即节的个数 遍历节表经验:根据此处的个数拿对应的节表数据 定位节表位置=选项头地址+选项头大小#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 #define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 #define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 #define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 #define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 #define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 #define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 #define IMAGE_FILE_32BIT_MACHINE 0x0100 #define IMAGE_FILE_DEBUG_STRIPPED 0x0200 #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 #define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 #define IMAGE_FILE_SYSTEM 0x1000 #define IMAGE_FILE_DLL 0x2000 #define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 #define IMAGE_FILE_BYTES_REVERSED_HI 0x8000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; // * PE标志字:32 位(0 x10B),64 位(0 x20B) BYTE MajorLinkerVersion; // 主链接器版本号 BYTE MinorLinkerVersion; // 副链接器版本号 DWORD SizeOfCode; // 代码所占空间大小(代码节大小) DWORD SizeOfInitializedData; // 已初始化数据所占空间大小 DWORD SizeOfUninitializedData; // 未初始化数据所占空间大小 DWORD AddressOfEntryPoint; // * 程序执行入口RVA,(w)(Win)mainCRTStartup:即0 D首次断下来的自进程地址 DWORD BaseOfCode; // 代码段基址 DWORD BaseOfData; // 数据段基址 DWORD ImageBase; // * 内存加载基址,exe默认0 x400000,dll默认0 x10000000 DWORD SectionAlignment; // * 节区数据在内存中的对齐值,一定是4 的倍数,一般是0 x1000(4096 =4 K) DWORD FileAlignment; // * 节区数据在文件中的对齐值,一般是0 x200(磁盘扇区大小512 ) WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号 WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号 WORD MajorImageVersion; // 可运行于操作系统的主版本号 WORD MinorImageVersion; // 可运行于操作系统的次版本号 WORD MajorSubsystemVersion; // 主子系统版本号:不可修改 WORD MinorSubsystemVersion; // 副子系统版本号 DWORD Win32VersionValue; // 版本号:不被病毒利用的话一般为0 ,XP中不可修改 DWORD SizeOfImage; // * PE文件在进程内存中的总大小,与SectionAlignment对齐 DWORD SizeOfHeaders; // * PE文件头部在文件中的按照文件对齐后的总大小(所有头 + 节表) DWORD CheckSum; // 对文件做校验,判断文件是否被修改:3 环无用,MapFileAndCheckSum获取 WORD Subsystem; // 子系统,与连接选项/system相关:1=驱动程序,2=图形界面,3=控制台/ Dll WORD DllCharacteristics; // 文件特性 DWORD SizeOfStackReserve; // 初始化时保留的栈大小 DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小 DWORD SizeOfHeapReserve; // 初始化时保留的堆大小 DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小 DWORD LoaderFlags; // 已废弃,与调试有关,默认为 0 DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,此字段自Windows NT发布以来,一直是16 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// 数据目录表 } IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32; // * 字段6 :AddressOfEntryPoint 表 程序入口RVA,即OEP: EOP:程序入口点,壳相关概念 OEP:原本的程序入口点(实际为偏移,+模块基址=实际入口点) EP: 被加工后的入口点// * 字段9 :ImageBase 表 模块加载基地址,exe默认0 x400000,dll默认0 x10000000 建议装载地址:exe映射加载到内存中的首地址= PE 0 处,即实例句柄hInstance 一般而言,exe文件可遵从装载地址建议,但dll文件无法满足// * 尾字段:DataDirectory 表 数据目录表,用来定义多种不通用处的数据块。 存储了PE中各个表的位置,详情参考IMAGE_DIRECTORY_ENTRY...系列宏
3.节表
(1)节表总概
节表:描述PE文件与内存之间的映射关系,由一系列的IMAGE_SECTION_HEADER结构排列而成,每个结构用来描述一个节(每个节占用0x28h),说明PE文件的指定内容拷贝至内存的哪个位置、拷贝大小及内存属性的设置。结构的排列顺序和它们描述的节在文件中的排列顺序是一致的。全部有效结构的最后以一个空的IMAGE_SECTION_HEADER结构作为结束,所以节表中总的IMAGE_SECTION_HEADER结构数量等于节的数量加一。节表总是被存放在紧接在PE文件头的地方。
节表大小 = FileHeader.NumberOfSections(节数量)* IMAGE_SECTION_HEADER 结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
节属性常用位含义
节表置于选项头之后,节表首地址 计算方法:
选项头的地址 + 选项头的大小;
(2) e_lfanew+4+0x14(文件头大小)+0xE0(32位选项头大小)64位是0xF0。
FileBuffer(文件里的数据状态)与ImageBuffer(内存里的数据状态)
1 SizeOfRawData不一定比Misc大,因为Misc在文件中,可能是在文件里未初始化未进行分配的值(记录的是在内存中的大小)
1 2 3 4 5 写代码的坑 vs fopen_s 函数: 第一个参数是FILE *指针 第二个文件名字 第三个是方法
call jmp
1 2 3 4 jmp = E8jmp 后面的值x = jmp 后一条指令地址(运行时内存中的地址)+要跳转的地址call = E9call 后面的值同上,均是运行时内存中的地址
指针
1 2 3 (byte *)转byte 型指针 (dword )要用指针算地址的时候转的dword 类型 都是强制类型转换
任意空白区添加代码
修改vs编译的程序,遇到的问题:
1.要是修改vs编译出来的程序要跳过.textbss这个节表,这个表的内存地址和文件地址都为0x000000
2.vs编译的程序已经开启ALSR(地址随机化),要想修改成功需要关闭这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 LPVOID changeimagebuffer (LPVOID imagebuffer) { PIMAGE_DOS_HEADER pDosHeader = NULL ; DWORD SizeOfImage = 0 ; DWORD SizeOfHeaders = 0 ; WORD NumberOfSections = 0 ; PIMAGE_SECTION_HEADER pSectionHeaderBase = NULL ; PIMAGE_NT_HEADERS pNtHeaders = NULL ; LPVOID pImageBuffer = NULL ; PBYTE codebegin = NULL ; pDosHeader = (PIMAGE_DOS_HEADER)imagebuffer; pNtHeaders = FileToNtHeader (imagebuffer); pSectionHeaderBase = LocateSectionBase (imagebuffer); if (((pSectionHeaderBase->SizeOfRawData) - (pSectionHeaderBase->Misc.VirtualSize)) < sizeof (shellcode)) { printf ("代码空闲区不足" ); free (imagebuffer); return NULL ; } codebegin = (PBYTE)((DWORD)imagebuffer + pSectionHeaderBase->VirtualAddress + pSectionHeaderBase->Misc.VirtualSize)+14 ; memcpy (codebegin, shellcode, sizeof (shellcode)); DWORD e8addr = (DWORD)(MESSAGE_BOX_ADDRESS - (pNtHeaders->OptionalHeader.ImageBase + ((DWORD)codebegin +(DWORD)0x0D - (DWORD)imagebuffer))); *(PDWORD)(codebegin + 9 ) = e8addr; DWORD e9addr = (DWORD)((pNtHeaders->OptionalHeader.ImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint) - (pNtHeaders->OptionalHeader.ImageBase + ((DWORD)codebegin + (DWORD)0x12 - (DWORD)imagebuffer))); *(PDWORD)(codebegin + 0xE ) = e9addr; pNtHeaders->OptionalHeader.AddressOfEntryPoint = (DWORD)codebegin - (DWORD)imagebuffer; return imagebuffer; }
新增加一个节
需要进行的操作
1)添加一个新的节(可以copy一份)
2)在新增节后面填充一个节大小的000
3)修改PE头中节的数量
4)修改sizeofimage的大小
5)再原有数据的最后,新增一个节的数据(内存对齐的整数倍).
6)修正新增节表的属性
注意:SizeOfHeader - (DOS +垃圾数据+ PE标记+标准PE头+可选PE头+己存在节表)= 2个节表的大小
空间不够可以把PE头往上移动(占DOS头的垃圾数据)
代码遇到的问题
1.imagebuffer开辟内存空间太小了,解决:重新申请一个足够大的空间然后拷贝内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 LPVOID NEWSetion (LPVOID pImageBuffer) { PIMAGE_DOS_HEADER pDosHeader = NULL ; DWORD SizeOfImage = 0 ; DWORD SizeOfHeaders = 0 ; WORD NumberOfSections = 0 ; DWORD pointtonew = 0 ; PIMAGE_SECTION_HEADER pSectionHeaderBase = NULL ; PIMAGE_SECTION_HEADER pnewSectionHeaderBase = NULL ; PIMAGE_NT_HEADERS pNtHeaders = NULL ; PBYTE codebegin = NULL ; pNtHeaders = FileToNtHeader (pImageBuffer); const DWORD size = pNtHeaders->OptionalHeader.SectionAlignment; LPVOID npFileBuffer = malloc (pNtHeaders->OptionalHeader.SizeOfImage + size); memset (npFileBuffer,0x00 , pNtHeaders->OptionalHeader.SizeOfImage + size); memcpy (npFileBuffer, pImageBuffer, pNtHeaders->OptionalHeader.SizeOfImage); pImageBuffer = npFileBuffer; pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer; pNtHeaders = FileToNtHeader (pImageBuffer); DWORD freesp = pNtHeaders->OptionalHeader.SizeOfHeaders - (pDosHeader->e_lfanew + 0x4 + 0x14 + pNtHeaders->FileHeader.SizeOfOptionalHeader + (pNtHeaders->FileHeader.NumberOfSections * 0x28 )); if (freesp < 0x28 * 2 ) { printf ("空间不足够添加节表,添加失败" ); return pImageBuffer; }; pSectionHeaderBase = LocateSectionBase (pImageBuffer); LPVOID addsectionstart = (BYTE*)pSectionHeaderBase + (DWORD)(0x28 * (pNtHeaders->FileHeader.NumberOfSections)); memcpy (addsectionstart, pSectionHeaderBase, 0x28 ); LPVOID endexe = (BYTE*)pImageBuffer + pNtHeaders->OptionalHeader.SizeOfImage ; BYTE* tian1 = new BYTE[size]; memset (tian1, 0x00 , size); memcpy (endexe, tian1 , size); pNtHeaders->OptionalHeader.SizeOfImage = pNtHeaders->OptionalHeader.SizeOfImage + size; pnewSectionHeaderBase =(PIMAGE_SECTION_HEADER) addsectionstart; pnewSectionHeaderBase->Misc.VirtualSize = size; pnewSectionHeaderBase->VirtualAddress = (DWORD) endexe - (DWORD) pImageBuffer; pnewSectionHeaderBase->SizeOfRawData = size; PIMAGE_SECTION_HEADER presec = PIMAGE_SECTION_HEADER ((BYTE*)pnewSectionHeaderBase - 0x28 ); if (presec->SizeOfRawData > presec->Misc.VirtualSize) { pointtonew = presec->SizeOfRawData + presec->PointerToRawData; } else { return 0 ; } pnewSectionHeaderBase->PointerToRawData = pointtonew; pNtHeaders->FileHeader.NumberOfSections = pNtHeaders->FileHeader.NumberOfSections + 1 ; return pImageBuffer; }
扩大节:
1、拉伸到内存
2.分配一块新的空间: Size0fImage + Ex
3、将最后一个节的Si zeOfRawData和VirtualSize改成N
SizeOfRawData = VirtualSize = N
N = (SizeOfRawData或者VirtualSize 内存对齐后的值) + Ex (这两个值取最大的)
4、修改SizeOfImage大小
Size0fImage = Size0fImage + Ex
合并节
1、拉伸到内存
2、将第一个节的内存大小、文件大小改成一样
Max = SizeOfRawData>VirtualSize?SizeOfRawData:VirtualSize
SizeOfRawData = VirtualSize = 最后一个节的VirtualAddress(23000h) + Max (4000h)- SizeOfHeaders内存对齐后的大小(1000h)
3、将第一个节的属性改为包含所有节的属性
DWORD Characteristics; 进行或运算即可
4、修改节的数量为1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 LPVOID Setiontoone (LPVOID pImageBuffer) { PIMAGE_DOS_HEADER pDosHeader = NULL ; DWORD SizeOfImage = 0 ; DWORD SizeOfHeaders = 0 ; DWORD SizeOfRawData = 0 ; DWORD VirtualSize = 0 ; DWORD MAX = 0 ; WORD NumberOfSections = 0 ; DWORD pointtonew = 0 ; PIMAGE_SECTION_HEADER pSectionHeaderBase = NULL ; PIMAGE_SECTION_HEADER pnewSectionHeaderBase = NULL ; PIMAGE_NT_HEADERS pNtHeaders = NULL ; pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer; pNtHeaders = FileToNtHeader (pImageBuffer); pSectionHeaderBase = LocateSectionBase (pImageBuffer); DWORD SizeOfHeadersmem = pSectionHeaderBase->VirtualAddress; PIMAGE_SECTION_HEADER pLaswSectionHeaderBase = PIMAGE_SECTION_HEADER ((BYTE*)pSectionHeaderBase + (DWORD)(0x28 * (pNtHeaders->FileHeader.NumberOfSections-1 ))); if (pLaswSectionHeaderBase->SizeOfRawData > pLaswSectionHeaderBase->Misc.VirtualSize) { MAX = pLaswSectionHeaderBase->SizeOfRawData; } else { MAX = pLaswSectionHeaderBase->Misc.VirtualSize; } SizeOfRawData = pLaswSectionHeaderBase->VirtualAddress + MAX - SizeOfHeadersmem; for (int x = 1 ; x < pNtHeaders->FileHeader.NumberOfSections ; x++) { PIMAGE_SECTION_HEADER pSectiontime = (PIMAGE_SECTION_HEADER)((BYTE*)pSectionHeaderBase +x* 0x28 ); pSectionHeaderBase->Characteristics = pSectiontime->Characteristics | pSectionHeaderBase->Characteristics; } pNtHeaders->FileHeader.NumberOfSections = 1 ; pSectionHeaderBase->SizeOfRawData = SizeOfRawData; pSectionHeaderBase->Misc.VirtualSize = SizeOfRawData; return pImageBuffer;
代码遇到的问题
1.忘记修改节表属性了
动态链接库 静态链接库
静态链接库:直接把函数编译到exe里,但是如果需要修改函数就得重新编译exe
动态链接库:修改时直接编译dll即可,exe不用重新生成
导出表
在可选PE头里的数据目录第一项
数据目录结构
1 2 3 4 5 // 数据目录 _IMAGE_DATA_DIRECTORY结构体 typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress ; /**指向某个数据的相对虚拟地址 RAV 偏移0x00**/ DWORD Size ; /**某个数据块的大小 偏移0x04**/ } IMAGE_DATA_DIRECTORY , *PIMAGE_DATA_DIRECTORY ;
要在文件中找到需要RVA转FOA查看文件中的位置
导出表结构
*不一定是导出的函数地址长度最大,有可能几个函数指向同一个的地址
numberoffunctions,不一定就是真正的导出函数个数(用序号的最后一个减去开始的序号然后加1)