首页
社区
课程
招聘
[原创]干货分享丨如何在代码空白区添加ShellCode
发表于: 2020-4-3 14:06 2085

[原创]干货分享丨如何在代码空白区添加ShellCode

2020-4-3 14:06
2085

本文需要的知识:

PE

实验环境:

Windows XP

记事本:

PETool

Uedit32

Visual c++ 6.0


手动添加shellcode

手动添加ShellCode实验有以下几个步骤:

  • 寻找代码节空白区;
  • 获取MessageBox地址,构造ShellCode代码;
  • 计算修正E8与E9后面的参数;
  • 计算并修改OEP。


寻找代码节空白区

代码节空白区一般指第一个节区的VirualSize(对齐前的节区大小)与第一个节区的SizeOfRawData(对齐后的节区大小)之间的空白范围。


用PETool打开记事本,记录如下需要用到的PE参数:

AddressEntryPint:0000739D        (程序开始执行的位置)
ImageBase:         01000000       (内存基地址)
SectionAlignmeng:00001000        (内存对齐单位)
FileAlignment:     00000200        (文件对齐单位)

IMAGE_SECTION_HEADER(节表)
VirtualSize:     00007748        (对齐前的长度)
VirtualAddress:     00001000         (内存中的偏移)
SizeOfRawData:     00007800        (对齐后的长度)
PointerToRawData:00000400        (文件中的偏移)
Characteristics: 60000020        (节区属性)


代码空白区的起始地址为:

PointerToRawData + VirtualSize  即 7748+400=7B48

用Uedit打开记事本,找到空白区。

获取MessageBox地址,构造ShellCode代码。

用C++ 新建项目

include "stdafx.h"include "windows.h"int main(int argc, char* argv[]){MessageBox(0,0,0,0);printf("Hello World!\n");return 0;}

按F9在MessageBox前下断点,运行后右键菜单->Go To Disassembly。

9:        MessageBox(0,0,0,0);00401028 8B F4                mov         esi,esp0040102A 6A 00                push        00040102C 6A 00                push        00040102E 6A 00                push        000401030 6A 00                push        000401032 FF 15 8C 52 42 00    call        dword ptr [impMessageBoxA@16 (0042528c)]00401038 3B F4                cmp         esi,esp0040103A E8 C1 00 00 00       call        __chkesp (00401100)

按F11单步步入调试进入MessageBox函数内部。

77D5050B 8B FF                mov         edi,edi

在汇编中,call内存地址对应的硬编码为E8 00 00 00 00,jmp 内存地址对应的硬编码为E9 00 00 00 00。


编写ShellCode


6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00
6A 00 6A 00 6A 00 6A 00是push 0 push 0 push 0 push 0是调用MessageBox所传递的实参
将ShellCode添加进空白区中:


计算修正E8与E9后面的参数
FOA -> RVA
FOA:在文件中的偏移地址
RVA:相对偏移地址(内存中)
当程序中的文件对齐单位和内存对齐单位不一致时则需要进行FOA->RVA转换
SectionAlignmeng:00001000        (内存对齐单位)

FileAlignment:     00000200        (文件对齐单位)

FOA->RVA 转换公式

FOA_shellcodeAddr - PointerToRawData + VirtualAddress + ImageBase = RVA_shellcodeAddr

即7b50 - 400 + 1000 + 01000000 = 100 8750

如果一致的话就不需要转换,直接使用即可。

用转换后的地址进行计算E8、E9后面的偏移。

E8 后的地址 = MessageBox的地址 - E8的下一条指令的地址。

即77D5050B -   (100 8750 +D) = 76D4 7DAE‬

E9 后的地址 = ImageBase + AddressEntryPoint - E9的下一条指令的地址。

(01000000 + 739D) - (100 8750 + 12) = FFFF EC3B


这里计算一定要用DWORD,将计算出的E8 E9后面的值添加进文件中。


计算并修改OEP

计算公式

OEP = RVA_shellcodeAddr - ImageBase


即100 8750 - 1000000 = 8750

修改OEP


使用代码添加ShellCode

需要实现功能如下:

  • 读取exe到内存中
  • 解析PE格式
  • 判断代码节空白区大小
  • 添加shellcode代码
  • FOA->RVA
  • 修正E8
  • 修正E9
  • 修正OEP
  • 保存文件


#include "stdafx.h"#include "windows.h"#include "stdlib.h"

#define FILEPATH_IN "C:\\test.exe"#define FILEPATH_OUT "C:\\t.exe"#define SHELLCODELENGTH 0X12#define MESSAGEBOXADDR 0x77D5050B

BYTE shellCode[] ={ 0x6A,0x00,0x6A,0x00,0x6A,0x00,0x6A,0x00, 0xE8,0x00,0x00,0x00,0x00, 0xE9,0x00,0x00,0x00,0x00};

int FileLength(FILE* fp){ int fileSize = 0; fseek(fp,0,SEEK_END); fileSize = ftell(fp); fseek(fp,0,SEEK_SET); //指针归位 return fileSize;}

DWORD ReadPEFile(IN LPSTR lpszFile,OUT LPVOID* pFileBuffer){ FILE* pFile = NULL; DWORD fileSize = 0;

//打开文件 pFile = fopen(lpszFile,"rb+"); if(!pFile) { printf("无法打开EXE文件"); return NULL; } //读取文件大小 fileSize = FileLength(pFile);

//分配缓冲区大小 *pFileBuffer = malloc(fileSize);

if(!pFileBuffer) { printf(" 分配空间失败!"); fclose(pFile); return NULL; } //将文件数据读取到缓冲区 size_t n = fread(*pFileBuffer,fileSize,1,pFile);

if(!n) { printf(" 读取数据失败!"); free(*pFileBuffer); fclose(pFile);

return NULL; } //关闭文件 fclose(pFile); return fileSize;}

VOID addShellCode(){ LPVOID pFileBuffer=NULL; PIMAGE_DOS_HEADER pDosHeader = NULL;//声明DOS头结构体变量 PIMAGE_NT_HEADERS pNTHeader = NULL;//声明NT头结构体变量 PIMAGE_FILE_HEADER pPEHeader = NULL;//声明PE头结构体变量 PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;//声明可选PE头结构体变量 PIMAGE_SECTION_HEADER pSectionHeader = NULL;//声明节表 结构体 变量 DWORD RVA_codeBegin = 0; //将exe文件读取入内存 size_t size = ReadPEFile(FILEPATH_IN,&pFileBuffer); //解析PE pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;//Dos头 pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);//NT头 pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);//PE头 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);//可选PE头 pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);//节表

//判断代码空闲区空间 if((pSectionHeader->SizeOfRawData - pSectionHeader->Misc.VirtualSize) < SHELLCODELENGTH) { printf("代码区空闲空间不足"); free(pFileBuffer); exit(0); } //添加shellcode代码 PBYTE codeBegin = (PBYTE)((DWORD)pFileBuffer+pSectionHeader->PointerToRawData +pSectionHeader->Misc.VirtualSize +8); printf("codeBegin:%x\n",codeBegin); memcpy(codeBegin,shellCode,SHELLCODELENGTH);

//FOA->RVA if(pOptionHeader->SectionAlignment != pOptionHeader->FileAlignment) { RVA_codeBegin = (DWORD)codeBegin - pSectionHeader->PointerToRawData + pSectionHeader->VirtualAddress + pOptionHeader->ImageBase - (DWORD)pFileBuffer; }else { RVA_codeBegin = (DWORD)codeBegin - (DWORD)pFileBuffer; }

//修正E8 DWORD callAddr = MESSAGEBOXADDR - (RVA_codeBegin+0xD); printf("callAddr:%x\n",callAddr); *(PDWORD)(codeBegin+0x9) = callAddr;

//修正E9 DWORD jmpAddr = (pOptionHeader->ImageBase + pOptionHeader->AddressOfEntryPoint) - (RVA_codeBegin+0x12); printf("ImageBase:%x\nAddressEntryPoint:%x\n",pOptionHeader->ImageBase,pOptionHeader->AddressOfEntryPoint); *(PDWORD)(codeBegin+0xE) = jmpAddr; printf("jmpAddr:%x\n",jmpAddr);

//修正OEP pOptionHeader->AddressOfEntryPoint = RVA_codeBegin-pOptionHeader->ImageBase; printf("OEP:%x\n",RVA_codeBegin-pOptionHeader->ImageBase);

//存入文件 FILE* fp = fopen(FILEPATH_OUT,"wb+"); fwrite(pFileBuffer,size,1,fp); fclose(fp);

}

int main(int argc, char* argv[]){ addShellCode(); system("pause"); return 0;}


注意:每个系统的MessageBox的地址可能都不相同,替换代码中的MessageBox地址。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回