Drunkmars's Blog

shellcode编写探究

字数统计: 2.1k阅读时长: 10 min
2022/02/17

shellcode是不依赖环境,放到任何地方都可以执行的机器码。shellcode的应用场景很多,本文不研究shellcode的具体应用,而只是研究编写一个shellcode需要掌握哪些知识。

ShellCode编写原则

1、不能有全局变量

因为我们编写shellcode时,使用的全局变量是自己的进程里面的全局变量,注入到别的进程里,这个地址就没用了。

2、不能使用常量字符串

和第一点原因一样,字符串常量值也是全局变量,注入到别的进程里,根本没有这个字符串。

要使用字符串,请使用字符数组。

1
char s[] = {'1','2',0};

3、不能直接调用系统函数

调用系统函数的方式是间接调用(FF15),需要从IAT表里获取API地址,每个进程的IAT表位置不同,且对方的进程可能没有导入你需要调用的函数的DLL,那么你是不能调用这个系统函数的。

所以我们需要用到 LoadLibrary 和 GetProcAddress 这两个函数,来动态获取系统API的函数指针。

但是 LoadLibrary,GetProcAddress 本身就是系统函数,它们本身就依赖IAT表,咋办呢?

解决方案是这样的:通过FS:[0x30] 找到PEB,然后通过PEB里的LDR链表 [PEB+0x0C]找到 kernel32.dll 的地址,然后我们遍历它的 IAT表,找到 LoadLibrary 和 GetProcAddress 函数。

4、不能嵌套调用其他函数

和前两点道理是一样的,本进程里的函数地址,拿到别的进程的虚拟地址空间是无效的。

TEB/PEB

每个线程都有一个TEB结构来存储线程的一些属性结构,TEB的地址用fs:[0]来获取

image-20220214100731251

在0x30这个地址有一个指针指向PEB结构,PEB就是进程用来记录自己信息的一个结构

image-20220214100850094

完整结构如下

image-20220214101109806

在PEB的0x00c偏移有一个 Ldr _PEB_LDR_DATA结构跟进去

image-20220214101230643

可以得到3个结构如下所示

InLoadOrderModuleList:模块加载的顺序

InMemoryOrderModuleList:模块在内存的顺序

InInitializationOrderModuleList:模块初始化的顺序

image-20220217185640234

思路

我们一般使用api会直接使用LoadLibraryGetProcessAddress,但是这里肯定会依赖IAT表,所以这里我们就需要自己实现api所完成的功能

TEB -> PEB -> PEB + 0x0C -> Ldr _PEB_LDR_DATA -> InLoadOrderModuleList -> kernel32.dll -> 导出表定位GetProcessAddress -> 通过找到的GetProcessAddress实现LoadLibrary

实现过程

首先我们自己定义几个结构体,因为我们不依赖系统自己实现

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
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _PEB_LDR_DATA
{
DWORD Length;
bool Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA,*PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
UINT32 SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32 Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
UINT32 CheckSum;
UINT32 TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

typedef HMODULE (WINAPI * PLOADLIBRARY)(LPCSTR);
typedef DWORD (WINAPI * PGETPROCADDRESS)(HMODULE, LPCSTR);
typedef DWORD (WINAPI * PMESSAGEBOX)(HWND, LPCSTR,LPCSTR,UINT);

然后定义shellcode,这里因为kernel32.dll是unicode字符串所以用两字节存储

1
2
3
4
5
6
char szKernel32[] = {'k',0,'e',0,'r',0,'n',0,'e',0,'l',0,'3',0,'2',0,'.',0,'d',0,'l',0,'l',0,0,0}; // Unicode
char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
char szGetProcAddress[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
char szLoadLibrary[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0};
char szMessageBox[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
char szHelloShellCode[] = {'H','e','l','l','o','S','h','e','l','l','C','o','d','e',0};

找到InLoadOrderModuleList存入寄存器

1
2
3
4
5
6
7
8
9
__asm
{
mov eax,fs:[0x30] // PEB
mov eax,[eax+0x0C] // PEB->LDR
add eax,0x0C // LDR->InLoadOrderModuleList
mov pBeg,eax
mov eax,[eax]
mov pPLD,eax
}

找到kernel32.dll,通过遍历的方式来寻找,通过LDR指向DllBase获取基址

image-20220217112719053

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Find Kerner32.dll
while (pPLD != pBeg)
{
pLast = (WORD*)pPLD->BaseDllName.Buffer;
pFirst = (WORD*)szKernel32;

while (*pFirst && *pLast == *pFirst)
pFirst++,pLast++;

if (*pFirst == *pLast)
{
dwKernelBase = (DWORD)pPLD->DllBase;
break;
}
pPLD = (LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink;
}

然后通过指针定位到导出表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 通过指针定位到导出表
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernelBase;
PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(/images/shellcode/image_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)dwKernelBase + pOptionHeader->DataDirectory[0].VirtualAddress);

// 导出函数地址表RVA
DWORD *pAddOfFun_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfFunctions);
// 导出函数名称表RVA
WORD *pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNameOrdinals);
// 导出函数序号表RVA
DWORD *pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNames);

还是通过遍历找到GetProcessAddress,用指针指向这个地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DWORD dwCnt = 0;
char* pFinded = NULL, *pSrc = szGetProcAddress;

for (; dwCnt < pExportDirectory->NumberOfNames;dwCnt++)
{
pFinded = (char*)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]);

while (*pFinded && *pFinded == *pSrc)
pFinded++, pSrc++;

if (*pFinded == *pSrc)
{
pGetProcAddress = (PGETPROCADDRESS)(pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]] + (DWORD)dwKernelBase);
break;
}
pSrc = szGetProcAddress;
}

然后就可以使用pGetProcessAddress实现LoadLibraryMessageBox

1
2
3
4
5
// 通过pGetProcAddress进行调用
pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase, szLoadLibrary);
pMessageBox = (PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);

pMessageBox(NULL,szHelloShellCode,0,MB_OK);

完整代码如下

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
143
144
145
146
147
148
149
150
151
152
153
154
155
// shellcode.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _PEB_LDR_DATA
{
DWORD Length;
bool Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA,*PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
UINT32 SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32 Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
UINT32 CheckSum;
UINT32 TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

typedef HMODULE (WINAPI * PLOADLIBRARY)(LPCSTR);
typedef DWORD (WINAPI * PGETPROCADDRESS)(HMODULE, LPCSTR);
typedef DWORD (WINAPI * PMESSAGEBOX)(HWND, LPCSTR,LPCSTR,UINT);

DWORD WINAPI ShellCode();


int main(int argc, char* argv[])
{
ShellCode();

getchar();
return 0;
}

DWORD WINAPI ShellCode()
{
PGETPROCADDRESS pGetProcAddress = NULL;
PLOADLIBRARY pLoadLibrary = NULL;
PMESSAGEBOX pMessageBox = NULL;
PLDR_DATA_TABLE_ENTRY pPLD;
PLDR_DATA_TABLE_ENTRY pBeg;
WORD *pFirst = NULL;
WORD *pLast = NULL;
DWORD ret = 0, i = 0;
DWORD dwKernelBase = 0;


char szKernel32[] = {'k',0,'e',0,'r',0,'n',0,'e',0,'l',0,'3',0,'2',0,'.',0,'d',0,'l',0,'l',0,0,0}; // Unicode
char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
char szGetProcAddress[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
char szLoadLibrary[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0};
char szMessageBox[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
char szHelloShellCode[] = {'H','e','l','l','o','S','h','e','l','l','C','o','d','e',0};

__asm
{
mov eax,fs:[0x30] // PEB
mov eax,[eax+0x0C] // PEB->LDR
add eax,0x0C // LDR->InLoadOrderModuleList
mov pBeg,eax
mov eax,[eax]
mov pPLD,eax
}

// Find Kerner32.dll
while (pPLD != pBeg)
{
pLast = (WORD*)pPLD->BaseDllName.Buffer;
pFirst = (WORD*)szKernel32;

while (*pFirst && *pLast == *pFirst)
pFirst++,pLast++;

if (*pFirst == *pLast)
{
dwKernelBase = (DWORD)pPLD->DllBase;
break;
}
pPLD = (LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink;
}

// Kernel32.dll -> GetProcAddress
if (dwKernelBase != 0)
{
// 通过指针定位到导出表
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernelBase;
PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(/images/shellcode/image_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)dwKernelBase + pOptionHeader->DataDirectory[0].VirtualAddress);

// 导出函数地址表RVA
DWORD *pAddOfFun_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfFunctions);
// 导出函数名称表RVA
WORD *pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNameOrdinals);
// 导出函数序号表RVA
DWORD *pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNames);

DWORD dwCnt = 0;
char* pFinded = NULL, *pSrc = szGetProcAddress;

for (; dwCnt < pExportDirectory->NumberOfNames;dwCnt++)
{
pFinded = (char*)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]);

while (*pFinded && *pFinded == *pSrc)
pFinded++, pSrc++;

if (*pFinded == *pSrc)
{
pGetProcAddress = (PGETPROCADDRESS)(pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]] + (DWORD)dwKernelBase);
break;
}
pSrc = szGetProcAddress;
}

}

// 通过pGetProcAddress进行调用
pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase, szLoadLibrary);
pMessageBox = (PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);

pMessageBox(NULL,szHelloShellCode,0,MB_OK);


return 0;
}

成功弹窗

image-20220217113020990

这里我们进反汇编看一下,是有检测堆栈平衡的代码的

image-20220217113109918

在物理机里面查看也是有的

image-20220217113326459

这里关闭一下堆栈平衡的检测,默认情况如下

image-20220217113408424

修改为禁用安全检查

image-20220217113434792

即可生成没有检查堆栈平衡的代码

image-20220217113457999

CATALOG
  1. 1. ShellCode编写原则
  2. 2. TEB/PEB
  3. 3. 思路
  4. 4. 实现过程