Last updated on October 7, 2023 pm
PE练习,修改OEP执行shellcode
回顾
在可选PE头OptionalHeader中有这样一个成员AddressOfEntryPoint
,这个成员记录整个exe程序开始入口地址,而且是一个RVA,即虚拟相对地址,需要将其加上程序装载的基址即ImageBase才是在内存中真正运行的地址。
那么自然就有一种想法,能否通过修改这个OEP,在程序执行之前先执行其他代码,然后再执行源代码?
练习
要求
1.通过修改OEP,在程序执行前执行一个弹窗
2.弹窗后恢复正常执行流
需要解决的问题
Q1:弹窗函数在哪里
A1:可以先使用OD等调试工具装载exe文件,在调试工具里面找到弹窗函数MessageBoxA
函数原型
1
| int MessageBoxA( HWND hWnd,LPCTSTR lpText, LPCTSTR lpCaption = NULL, UINT nType = MB_OK );
|
参数说明
hWnd:表示窗口句柄,指定该对话框的所有者窗口;如果该参数为空(0/NULL),则该对话框不属于任何窗口
lpText:字符串,指显示在对话框中的内容
lpCaption:字符串,指对话框的标题;如果此参数为空,则默认使用“错误”作为标题
nType:指定显示按钮的数目及形式,表名使用的图标样式、缺省按钮是什么、以及消息框的强制回应等
Q2:如何调用
A2:MessageBoxA有四个参数,通过栈来传递参数,简单起见,只需要连续的四个push 0
即可,在push完后,就应该使用call指令调用这个函数,类似于call x
。然而注意的是,这里的x并不是在调试工具里找到MessageBoxA的地址,而是找到的地址减去call指令的下一条地址。
举个例子,比如call指令地址是0x1000,下一条指令地址是0x1005(call指令占5个字节长度),需要call的函数地址位置为0x1105,那么就应该写为call 0x100
,这里的100就是0x1105 - 0x1005得到。
由于是在文件里面进行修改,这里修改的就是机器码,push 0
的机器码是6A 00
,call x
的机器码是e8 x
,其中x占四个字节,e8即为call的操作码
Q3:如何返回
A3:在call完MessageBoxA后,就应该返回原来的正常执行流,那么可以在call指令下一条使用jmp指令直接跳回原来的OEP,格式为jmp x
,同样的,这里的x也是jmp下一条指令地址减去要跳转的指令地址,x也为四字节,机器码格式为e9 x
,e9即为jmp的操作码
Q4:在哪里插入shellcode
A4:理想的插入位置就是Code区或者Text区,因为这两个地方存放的就是代码片段,实际上任何地方都可以(得是空白段,即不能影响原来的代码),只要相应的地方拥有执行权限即可。
根据Q2,Q3,Q4可以插入对应的shellcode雏形
Q5:如何修改原来的OEP,使其执行shellcode
A5:OEP存储的是RVA,也就是在内存中偏移,那么就需要从文件偏移计算内存偏移,也就是涉及到RVA和FOA的互相转化。值得一提的是,这里的RVA和FOA其实只会对节区以后的数据有影响,所有的头其实都是不变的,因为这些头之间都是紧紧挨在一起的,中间没有填充。
那么给出一个FOA,如何计算它的RVA呢。实际上只需要确定它在哪一个节区,减去这个节区的起始地址,算出它在节区中的偏移offset,再加上节区在内存中的起始地址即可算出在内存中的地址。确定节区也好办,只需要大于此节区开始地址VirtualAddress
,小于下一个节区开始地址,就可以确定它是在此节区中。
从RVA计算FOA也是一样的,确定节区,算节区偏移,加上节区在文件中起始地址PointerToRawData
,给出代码实现
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
| #include<stdio.h> #include<malloc.h> #include<windows.h>
DWORD Size(FILE* fp){ DWORD size = 0; fseek(fp,0,SEEK_END); size = ftell(fp); fseek(fp,0,SEEK_SET); return size; }
DWORD FOA_TO_RVA(DWORD FOA,char* Buffer){ PIMAGE_DOS_HEADER DosHeader = NULL; PIMAGE_NT_HEADERS NTHeader = NULL; PIMAGE_FILE_HEADER FileHeader= NULL; PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL; PIMAGE_SECTION_HEADER SectionHeader = NULL;
DosHeader = (PIMAGE_DOS_HEADER)Buffer; FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4); OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + DosHeader->e_lfanew + 4 +IMAGE_SIZEOF_FILE_HEADER); SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 +IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);
if (FOA < OptionalHeader->SizeOfHeaders) { return FOA; }
for(int i =0;i<FileHeader->NumberOfSections-1;i++){ if(FOA >= SectionHeader->PointerToRawData&& FOA < (SectionHeader +1)->PointerToRawData){ DWORD offset = FOA - SectionHeader->PointerToRawData; return SectionHeader->VirtualAddress + offset; } else{ SectionHeader += 1; } } DWORD offset = FOA - SectionHeader->PointerToRawData; return SectionHeader->VirtualAddress + offset; }
DWORD RVA_TO_FOA(DWORD RVA,char* Buffer){ PIMAGE_DOS_HEADER DosHeader = NULL; PIMAGE_NT_HEADERS NTHeader = NULL; PIMAGE_FILE_HEADER FileHeader = NULL; PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL; PIMAGE_SECTION_HEADER SectionHeader = NULL;
DosHeader = (PIMAGE_DOS_HEADER)Buffer; FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4); OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER); SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader); if (RVA < OptionalHeader->SizeOfHeaders) { return RVA; }
for(int i=0;i<FileHeader->NumberOfSections-1;i++){ if(RVA >= SectionHeader->VirtualAddress && RVA < (SectionHeader+1)->VirtualAddress){ DWORD offset = RVA - SectionHeader->VirtualAddress; return SectionHeader->PointerToRawData + offset; } else{ SectionHeader +=1; } }
DWORD offset = RVA - SectionHeader->VirtualAddress; return SectionHeader->PointerToRawData + offset; }
int main(){ char* buffer;
FILE* fp1 = NULL; errno_t err_1 = fopen_s(&fp1, "C:\\Users\\yongrin\\Desktop\\PE_info.exe", "rb");
DWORD size = Size(fp1); buffer = (char*)malloc(size); fread(buffer,size,1,fp1);
printf("%X",FOA_TO_RVA(0x5d380,buffer)); printf("%X",RVA_TO_FOA(0x5df80,buffer)); return 0; }
|
计算地址
真正ImageBase
由于设备等原因,程序并不一定会加载到ImageBase的地方,这时就需要判断程序会加载到什么基址上。最好的办法就是使用调试工具,程序就会自动断在OEP处。
此时的OEP地址为0x6DEFC,是用PE查看器或者自己手动找文件中的OEP,就是可选头的AddressOfEntryPoint
字段可得0x5DEFC,那么很显然,真正的ImageBase就是0x10000。
修改OEP
shellcode在文件中起始地址是0x5D380,转化为RVA可得0x5DF80,那么只需要把OEP改写为0x5DF80即可,修改完再拖入调试器应该是这样的,可以从中看见插入的shellcode,下一步就是计算偏移
函数MessageBoxA在内存中地址是0x75B23D90(可以通过符号查看等方式找,此函数在user32.dll里,不同设备这个值也会不同),那么计算call的地址就是要跳转的地址 - 下一条指令地址,具体计算就是0x75B23D90 - 0x6DF8D = 0x75AB5E03
,同理jmp的地址0x6DEFC - 0x6DF92 =0xffffff6a
(要jmp的地址是原来的OEP)
因此在文件中改为
在调试器中为,成功显示相关函数!
此时也能按照预期的方式去运行,即先弹窗再正常执行
代码实现
用到了以前的几个函数
定义了ImageBase 的宏 以及 MessageBox的宏
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 96 97 98 99 100 101 102 103 104 105 106 107 108
| #include<stdio.h> #include<malloc.h> #include<windows.h>
#define ImgaeBase 0x10000 #define MessageBox_Addr 0x75C93D90 BYTE Shell[] = {0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00, 0xE8,0x00,0x00,0x00,0x00, 0xe9,0x00,0x00,0x00,0x00};
DWORD Size(FILE* fp){ DWORD size = 0; fseek(fp,0,SEEK_END); size = ftell(fp); fseek(fp,0,SEEK_SET); return size; }
DWORD FOA_TO_RVA(DWORD FOA,char* Buffer){ PIMAGE_DOS_HEADER DosHeader = NULL; PIMAGE_NT_HEADERS NTHeader = NULL; PIMAGE_FILE_HEADER FileHeader= NULL; PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL; PIMAGE_SECTION_HEADER SectionHeader = NULL;
DosHeader = (PIMAGE_DOS_HEADER)Buffer; FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4); OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + DosHeader->e_lfanew + 4 +IMAGE_SIZEOF_FILE_HEADER); SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 +IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);
if (FOA < OptionalHeader->SizeOfHeaders) { return FOA; }
for(int i =0;i<FileHeader->NumberOfSections-1;i++){ if(FOA >= SectionHeader->PointerToRawData&& FOA < (SectionHeader +1)->PointerToRawData){ DWORD offset = FOA - SectionHeader->PointerToRawData; return SectionHeader->VirtualAddress + offset; } else{ SectionHeader += 1; } } SectionHeader +=1; DWORD offset = FOA - SectionHeader->PointerToRawData; return SectionHeader->VirtualAddress + offset; }
void Shell_add(char* buffer){ PIMAGE_DOS_HEADER DosHeader = NULL; PIMAGE_NT_HEADERS NTHeader = NULL; PIMAGE_FILE_HEADER FileHeader = NULL; PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL; PIMAGE_SECTION_HEADER SectionHeader = NULL;
DosHeader = (PIMAGE_DOS_HEADER)buffer; NTHeader = (PIMAGE_NT_HEADERS)(buffer + DosHeader->e_lfanew); FileHeader = (PIMAGE_FILE_HEADER)(buffer + DosHeader->e_lfanew + 4); OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER); SectionHeader = (PIMAGE_SECTION_HEADER)(buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);
char* shell_begin = buffer + SectionHeader->PointerToRawData + SectionHeader->Misc.VirtualSize ;
DWORD begin_FOA = SectionHeader->PointerToRawData + SectionHeader->Misc.VirtualSize;
DWORD e8_FOA = begin_FOA + 8; DWORD e9_FOA = e8_FOA + 5;
DWORD begin_RVA = FOA_TO_RVA(begin_FOA,buffer); DWORD e8_x_addr = MessageBox_Addr - (FOA_TO_RVA(e9_FOA,buffer)+ImgaeBase); DWORD e9_x_addr = OptionalHeader->AddressOfEntryPoint - (FOA_TO_RVA(e9_FOA,buffer)+5);
OptionalHeader->AddressOfEntryPoint = FOA_TO_RVA(begin_FOA,buffer);
memcpy(shell_begin,Shell,18);
DWORD* e8 = (DWORD*)(shell_begin + 9); DWORD* e9 = (DWORD*)(shell_begin + 14); memcpy(e8,&e8_x_addr,4); memcpy(e9,&e9_x_addr,4); } int main(){ char* buffer; char* new_buffer;
FILE* fp1 = NULL; FILE* fp2 = NULL; errno_t err_1 = fopen_s(&fp1, "C:\\Users\\yongrin\\Desktop\\PE_info.exe", "rb"); errno_t err_2 = fopen_s(&fp2, "C:\\Users\\yongrin\\Desktop\\PE_info_shell.exe", "wb");
DWORD size = Size(fp1); buffer = (char*)malloc(size); fread(buffer,size,1,fp1);
Shell_add(buffer); fwrite(buffer,size,1,fp2);
return 0; }
|
后记
1.也可以直接拉伸成ImageBuffer后再patch,都是一样
2.也可以在IDA里面直接patch,构造几个简单ROP即可