Contents
  1. 1. 代码空白区添加shellcode
  2. 2. 手动添加shellcode
    1. 2.1. 寻找代码节空白区
    2. 2.2. 获取MessageBox地址,构造ShellCode代码
    3. 2.3. 计算修正E8 与 E9 后面的参数
    4. 2.4. 计算并修改OEP
  3. 3. 使用代码添加shellcode

本篇文章主要实现为记事本添加 空白弹窗,后续有时间写的话可以去写简单的文件感染的文章。

手动和编程实现,在代码空白区添加shellcode 、在数据空白区添加shellcode

代码空白区添加shellcode

适合有一定基础的萌新!

前言:

本篇文章主要实现为记事本添加 空白弹窗,后续有时间写的话可以去写简单的文件感染的文章。

手动和编程实现,在代码空白区添加shellcode 、在数据空白区添加shellcode

需要知识 :

PE

实验环境

windows xp

记事本

PETool

Uedit32

Visual c++ 6.0

手动添加shellcode

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

  • 寻找代码节空白区

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

  • 计算修正E8 与 E9 后面的参数

  • 计算并修改OEP

寻找代码节空白区

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

PETool打开记事本

记录如下需要用到的PE参数

1
2
3
4
5
6
7
8
9
10
11
12
IMAGE_OPTIONAL_HEADER32(可选PE头)
AddressEntryPint:0000739D (程序开始执行的位置)
ImageBase: 01000000 (内存基地址)
SectionAlignmeng:00001000 (内存对齐单位)
FileAlignment: 00000200 (文件对齐单位)

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

代码空白区的起始地址为:PointerToRawData + VirtualSize7748+400=7B48

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

获取MessageBox地址,构造ShellCode代码

用C++ 新建项目

1
2
3
4
5
6
7
8
9
#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

1
2
3
4
5
6
7
8
9
9:        MessageBox(0,0,0,0);
00401028 8B F4 mov esi,esp
0040102A 6A 00 push 0
0040102C 6A 00 push 0
0040102E 6A 00 push 0
00401030 6A 00 push 0
00401032 FF 15 8C 52 42 00 call dword ptr [__imp__MessageBoxA@16 (0042528c)]
00401038 3B F4 cmp esi,esp
0040103A E8 C1 00 00 00 call __chkesp (00401100)

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

1
77D5050B 8B FF                mov         edi,edi

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

编写ShellCode

1
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 00push 0 push 0 push 0 push 0 是调用MessageBox所传递的实参

将ShellCode添加进空白区中

1561436946735

计算修正E8 与 E9 后面的参数

FOA -> RVA

FOA:在文件中的偏移地址

RVA:相对偏移地址(内存中)

当程序中的文件对齐单位 和 内存对齐单位不一致时则需要进行FOA->RVA转换

1
2
SectionAlignmeng:00001000		(内存对齐单位)
FileAlignment: 00000200 (文件对齐单位)

FOA->RVA 转换公式

1
FOA_shellcodeAddr - PointerToRawData + VirtualAddress + ImageBase = RVA_shellcodeAddr

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

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

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

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

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

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

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

这里计算一定要用DWORD

将计算出的E8 E9 后面的值添加进文件中,注意小端显示

1561454221052

计算并修改OEP

计算公式

1
OEP = RVA_shellcodeAddr - ImageBase

1
100 8750 - 1000000 = 8750

修改OEP

1561453842765

保存后运行,可以看到弹窗

1561458172003

使用代码添加shellcode

需要实现功能如下

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

  • 保存文件

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// AddShellCode.cpp : Defines the entry point for the console application.
//

#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地址

文章首发于i春秋,版权所有,禁止转载!

Contents
  1. 1. 代码空白区添加shellcode
  2. 2. 手动添加shellcode
    1. 2.1. 寻找代码节空白区
    2. 2.2. 获取MessageBox地址,构造ShellCode代码
    3. 2.3. 计算修正E8 与 E9 后面的参数
    4. 2.4. 计算并修改OEP
  3. 3. 使用代码添加shellcode