原文首发:看雪论坛 http://bbs.pediy.com/thread-217513.htm
前言
在构造一个Shellcode载荷时总是存在多种方法,特别是对于Windows平台来说。需要手工编写所有的汇编代码,或者说编译器能够有所帮助吗?需要直接使用syscall,还是需要在内存中搜索函数?因为构造载荷一般来说不会很简单,所以我决定写一篇文章来专门论述相关问题。我习惯于用C语言来完成所有的工作,并使用Visual
Studio来对其进行编译:因为C语言的源代码更加优美,编译器能够更好地对其进行优化,并且如果需要的话可以使用LLVM框架实现自己的混淆器。
在本例中,我将针对于x86架构下的Shellcode代码;当然,相关的分析完全可以应用于x86(64位)架构下的Shellcode代码或者别的处理器。
寻找基本DLL
简介
当一个Shellcode载荷加载到Windows系统中的时候,第一步就是要定位需要使用的函数,即搜索存储函数的动态链接库(DLL)。为此,我们需要用到以下各小节所描述的不同的结构。
线程环境块
Windows系统使用TEB结构来描述一个线程,每个线程通过使用FS(x86平台)或GS(x86,64位平台)寄存器来访问其自身的TEB结构。TEB结构具体如下:
1
2
3
4
5
6
7
8
90:000> dt ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
...
+0xff0 EffectiveContainerId : _GUID
因此,若想要访问PEB结构,只需要进行如下操作:
1
2
3
4
5PEB* getPeb() {
__asm {
mov eax, fs:[0x30];
}
}
进程环境块
如果说TEB结构给出了一个线程的相关信息,那么PEB结构将告诉我们关于进程自身的信息,其中我们所需要的信息是基本DLL的位置。实际上,在Windows系统加载一个进程到内存中的时候,至少要映射两个DLL:
·ntdll.dll,其中包含执行syscall的函数,它们都以前缀Nt开头(形如Nt*),并调用内核中以Zw开头(形如Zw*)名称相同的函数;
·kernel32.dll,在更高层次上使用NTDLL中的函数。比如,kernel32!CreateFileA函数将调用ntdll!NtCreateFileW函数,而后者又将调用ntoskrnl!ZwCreateFileW函数。
在不同版本的Windows系统下,其他的DLL可能已经存在于内存中,但是是完全可移植的;因此,我们假设以上两个DLL是唯一加载的DLL模块。
让我们看一下PEB结构,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
180:000> dt nt!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
...
+0x25c WaitOnAddressHashTable : [128] Ptr32 Void
可以看到,其中一个成员名为PEB.BeingDebugged,可被IsDebuggerPresent()函数使用;而我们感兴趣的部分是成员PEB.Ldr,其对应于如下结构:
1
2
3
4
5
6
7
8
9
100:000> dt nt!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY
+0x014 InMemoryOrderModuleList : _LIST_ENTRY
+0x01c InInitializationOrderModuleList : _LIST_ENTRY
+0x024 EntryInProgress : Ptr32 Void
+0x028 ShutdownInProgress : UChar
+0x02c ShutdownThreadId : Ptr32 Void
顾名思义,成员PEB.Ldr->In*OrderModuleList是包含所有已加载到内存中的DLL模块的链表(LIST_ENTRY);三个表以不同的顺序指向相同的对象。我更倾向于使用InLoadOrderModuleList,因为可以像使用一个指向_LDR_DATA_TABLE_ENTRY的指针一样直接使用InLoadOrderModuleList.Flink。例如,如果使用InMemoryOrderModuleList,由于InMemoryOrderModuleList.Flink指针指向下一个InMemoryOrderModuleList,所以LDR_DATA_TABLE_ENTRY将位于(_InMemoryOrderModuleList.Flink–0x10)的位置。每个链表成员具有如下结构:
1
2
3
4
5
6
7
8
9
10
110:000> dt nt!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
...
+0x0a0 DependentLoadFlags : Uint4B
BaseDllName包含DLL模块的名称(比如,ntdll.dll),而DllBase包含DLL模块所加载到内存中的地址。通常,InLoadOrderModuleList中的第一个成员就是可执行程序自身,在之后我们能够找到NTDLL和KERNEL32;然而,我们并不确定在所有版本的Windows系统中都是这个次序,所以最好根据DLL名称(大写/小写)进行搜索。
DJB散列
如前所述,我们并不信任DLL顺序而选择根据DLL名称进行搜索。然而在一段Shellcode代码中,使用ASCII字符串(或更糟的,UNICODE字符串)并不是一个好主意:这将使得我们的Shellcode代码过于臃肿!因此我建议,使用散列机制来比较DLL名称。由于其简洁有效性我选择使用DJB散列算法,具体代码如下所示:
1
2
3
4
5
6
7
8
9
10DWORDdjbHashW(wchar_t* str) {
unsignedinthash = 5381;
unsignedinti = 0;
for(i = 0; str[i] != 0; i++) {
hash = ((hash << 5) + hash) + str[i];
}
returnhash;
}
由于DLL名称可能大写也可能小写,因此在散列算法中最好满足如下等式关系:
1
djbHashW(L"ntdll.dll") == djbHashW(L"NTDLL.DLL")
代码
现在我们已经讨论了如何完成以上工作,是时候来编码实现我们的想法了。具体代码如下所示:
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
74typedefstruct_LSA_UNICODE_STRING {
USHORTLength;
USHORTMaximumLength;
PWSTRBuffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
typedefstruct_LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOIDBaseAddress;
PVOIDEntryPoint;
ULONGSizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONGFlags;
SHORTLoadCount;
SHORTTlsIndex;
LIST_ENTRY HashTableEntry;
ULONGTimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedefstruct_PEB_LDR_DATA {
ULONGLength;
ULONGInitialized;
ULONGSsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
typedefstruct_RTL_USER_PROCESS_PARAMETERS {
BYTEReserved1[16];
PVOIDReserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
typedefstruct_PEB {
BYTEReserved1[2];
BYTEBeingDebugged;
BYTEReserved2[1];
PVOIDReserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
BYTEReserved4[104];
PVOIDReserved5[52];
PVOIDPostProcessInitRoutine;
BYTEReserved6[128];
PVOIDReserved7[1];
ULONGSessionId;
} PEB, *PPEB;
DWORDgetDllByName(DWORDdllHash) {
PEB* peb = getPeb();
PPEB_LDR_DATA Ldr = peb->Ldr;
PLDR_DATA_TABLE_ENTRY moduleList = (PLDR_DATA_TABLE_ENTRY)Ldr->InLoadOrderModuleList.Flink;
wchar_t* pBaseDllName = moduleList->BaseDllName.Buffer;
wchar_t* pFirstDllName = moduleList->BaseDllName.Buffer;
do{
if(pBaseDllName != NULL) {
if(djbHashW(pBaseDllName) == dllHash) {
return(DWORD)moduleList->BaseAddress;
}
}
moduleList = (PLDR_DATA_TABLE_ENTRY)moduleList->InLoadOrderModuleList.Flink;
pBaseDllName = moduleList->BaseDllName.Buffer;
}while(pBaseDllName != pFirstDllName);
return0;
}
如果想要使用其他的DLL模块,只需要使用LoadLibrary()函数来将其加载。不要着急,我们将在user32.dll相关的Shellcode代码中进行该项工作。
函数地址
简介
现在我们已经找到DLL,下一步需要在DLL内存空间中搜寻所需的函数在哪儿。幸运的是,透彻理解PE文件头部结构的前提下这并不复杂。要牢记的是,当我们讨论PE文件头部时,提到的大部分地址都与可执行程序地址相关。
可移植的执行体头部
执行体的开始位置是一个DOS头部,但它只是为了兼容(DOS系统)而存在。具体结构如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
200:000> dt nt!_IMAGE_DOS_HEADER
+0x000 e_magic : Uint2B
+0x002 e_cblp : Uint2B
+0x004 e_cp : Uint2B
+0x006 e_crlc : Uint2B
+0x008 e_cparhdr : Uint2B
+0x00a e_minalloc : Uint2B
+0x00c e_maxalloc : Uint2B
+0x00e e_ss : Uint2B
+0x010 e_sp : Uint2B
+0x012 e_csum : Uint2B
+0x014 e_ip : Uint2B
+0x016 e_cs : Uint2B
+0x018 e_lfarlc : Uint2B
+0x01a e_ovno : Uint2B
+0x01c e_res : [4] Uint2B
+0x024 e_oemid : Uint2B
+0x026 e_oeminfo : Uint2B
+0x028 e_res2 : [10] Uint2B
+0x03c e_lfanew : Int4B
成员e_lfanew将指示NT头部的位置。由于这是一个相对地址,所以需要进行pFile+e_lfanew操作。NT头部具体结构如下所示:
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
420:000> dt -r1 nt!_IMAGE_NT_HEADERS
+0x000 Signature : Uint4B
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x000 Machine : Uint2B
+0x002 NumberOfSections : Uint2B
+0x004 TimeDateStamp : Uint4B
+0x008 PointerToSymbolTable : Uint4B
+0x00c NumberOfSymbols : Uint4B
+0x010 SizeOfOptionalHeader : Uint2B
+0x012 Characteristics : Uint2B
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
+0x000 Magic : Uint2B
+0x002 MajorLinkerVersion : UChar
+0x003 MinorLinkerVersion : UChar
+0x004 SizeOfCode : Uint4B
+0x008 SizeOfInitializedData : Uint4B
+0x00c SizeOfUninitializedData : Uint4B
+0x010 AddressOfEntryPoint : Uint4B
+0x014 BaseOfCode : Uint4B
+0x018 BaseOfData : Uint4B
+0x01c ImageBase : Uint4B
+0x020 SectionAlignment : Uint4B
+0x024 FileAlignment : Uint4B
+0x028 MajorOperatingSystemVersion : Uint2B
+0x02a MinorOperatingSystemVersion : Uint2B
+0x02c MajorImageVersion : Uint2B
+0x02e MinorImageVersion : Uint2B
+0x030 MajorSubsystemVersion : Uint2B
+0x032 MinorSubsystemVersion : Uint2B
+0x034 Win32VersionValue : Uint4B
+0x038 SizeOfImage : Uint4B
+0x03c SizeOfHeaders : Uint4B
+0x040 CheckSum : Uint4B
+0x044 Subsystem : Uint2B
+0x046 DllCharacteristics : Uint2B
+0x048 SizeOfStackReserve : Uint4B
+0x04c SizeOfStackCommit : Uint4B
+0x050 SizeOfHeapReserve : Uint4B
+0x054 SizeOfHeapCommit : Uint4B
+0x058 LoaderFlags : Uint4B
+0x05c NumberOfRvaAndSizes : Uint4B
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
数据目录中包含了一些有趣成员的地址;而对我们来说,它包含了所有导出函数的地址。
1
2
30:000> dt nt!_IMAGE_DATA_DIRECTORY
+0x000 VirtualAddress : Uint4B
+0x004 Size : Uint4B
因此,我们可以使用DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress来直接获取导出目录的地址:
1
2
3
4
5
6
7
8
9
10
11
12
13typedefstruct_IMAGE_EXPORT_DIRECTORY {
DWORDCharacteristics;
DWORDTimeDateStamp;
WORDMajorVersion;
WORDMinorVersion;
DWORDName;
DWORDBase;
DWORDNumberOfFunctions;
DWORDNumberOfNames;
DWORDAddressOfFunctions;
DWORDAddressOfNames;
DWORDAddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
可以通过名字或者通过序号来导出一个函数;因此,以下三个数组保持更新:
·AddressOfFunctions,按照序号顺序依次保存函数的地址;
·AddressOfNames,保存函数名称;
·AddressOfNameOrdinals,保存序号,该数组与AddressOfNames数组次序相同。
因此,如果想要根据名称来查找函数地址,我们需要浏览AddressOfNames数组中的所有名称,并将数组索引用于AddressOfNameOrdinals[index];而这将返回一个序号用于AddressOfFunctions[ordinal]。整个过程的伪代码如下所示:
1
2
3
4
5
6inti = 0;
while(AddressOfNames[i] != searchedName) {
i++;
}
returnAddressOfFunctions[ AddressOfNamesOrdinals[i] ];
代码
正如搜索DLL时所做的那样,我们将使用DJB散列算法(不过这次用ASCII字符串):
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
26PVOIDgetFunctionAddr(DWORDdwModule,DWORDfunctionHash) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dwModule;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + dosHeader->e_lfanew);
PIMAGE_DATA_DIRECTORY dataDirectory = &ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if(dataDirectory->VirtualAddress == 0) {
returnNULL;
}
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwModule + dataDirectory->VirtualAddress);
PDWORD ardwNames = (PDWORD)(dwModule + exportDirectory->AddressOfNames);
PWORDarwNameOrdinals = (PWORD)(dwModule + exportDirectory->AddressOfNameOrdinals);
PDWORD ardwAddressFunctions = (PDWORD)(dwModule + exportDirectory->AddressOfFunctions);
char* szName = 0;
WORDwOrdinal = 0;
for(unsignedinti = 0; i < exportDirectory->NumberOfNames; i++) {
szName = (char*)(dwModule + ardwNames[i]);
if(djbHash(szName) == functionHash) {
wOrdinal = arwNameOrdinals[i];
return(PVOID)(dwModule + ardwAddressFunctions[wOrdinal]);
}
}
returnNULL;
}
编译
最终代码
我们已经讨论了所用的重要结构和算法,下面看看如何生成Shellcode代码。
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#pragma comment(linker, "/ENTRY:main")
#include "makestr.h"
#include "peb.h"
typedefHMODULE(WINAPI* _LoadLibraryA)(LPCSTRlpFileName);
typedefint(WINAPI* _MessageBoxA)(HWNDhWnd,LPCSTRlpText,LPCSTRlpCaption,UINTuType);
intmain();
DWORDgetDllByName(DWORDdllHash);
PVOIDgetFunctionAddr(DWORDdwModule,DWORDfunctionHash);
DWORDdjbHash(char* str);
DWORDdjbHashW(wchar_t* str);
intmain() {
DWORDhashKernel32 = 0x6DDB9555;// djbHashW(L"KERNEL32.DLL");
DWORDhKernel32 = getDllByName(hashKernel32);
if(hKernel32 == 0) {
return1;
}
DWORDhashLoadLibraryA = 0x5FBFF0FB;// djbHash("LoadLibraryA");
_LoadLibraryA xLoadLibraryA = getFunctionAddr(hKernel32, hashLoadLibraryA);
if(xLoadLibraryA == NULL) {
return1;
}
charszUser32[] = MAKESTR("user32.dll", 10);
DWORDhUser32 = xLoadLibraryA(szUser32);
if(hUser32 == 0) {
return1;
}
DWORDhashMessageBoxA = 0x384F14B4;// djbHash("MessageBoxA");
_MessageBoxA xMessageBoxA = getFunctionAddr(hUser32, hashMessageBoxA);
if(xMessageBoxA == NULL) {
return1;
}
charszMessage[] = MAKESTR("Hello World", 11);
charszTitle[] = MAKESTR(":)", 2);
xMessageBoxA(0, szMessage, szTitle, MB_OK|MB_ICONINFORMATION);
return0;
}
inlinePEB* getPeb() {
__asm {
mov eax, fs:[0x30];
}
}
DWORDdjbHash(char* str) {
unsignedinthash = 5381;
unsignedinti = 0;
for(i = 0; str[i] != 0; i++) {
hash = ((hash << 5) + hash) + str[i];
}
returnhash;
}
DWORDdjbHashW(wchar_t* str) {
unsignedinthash = 5381;
unsignedinti = 0;
for(i = 0; str[i] != 0; i++) {
hash = ((hash << 5) + hash) + str[i];
}
returnhash;
}
DWORDgetDllByName(DWORDdllHash) {
PEB* peb = getPeb();
PPEB_LDR_DATA Ldr = peb->Ldr;
PLDR_DATA_TABLE_ENTRY moduleList = (PLDR_DATA_TABLE_ENTRY)Ldr->InLoadOrderModuleList.Flink;
wchar_t* pBaseDllName = moduleList->BaseDllName.Buffer;
wchar_t* pFirstDllName = moduleList->BaseDllName.Buffer;
do{
if(pBaseDllName != NULL) {
if(djbHashW(pBaseDllName) == dllHash) {
return(DWORD)moduleList->BaseAddress;
}
}
moduleList = (PLDR_DATA_TABLE_ENTRY)moduleList->InLoadOrderModuleList.Flink;
pBaseDllName = moduleList->BaseDllName.Buffer;
}while(pBaseDllName != pFirstDllName);
return0;
}
PVOIDgetFunctionAddr(DWORDdwModule,DWORDfunctionHash) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dwModule;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + dosHeader->e_lfanew);
PIMAGE_DATA_DIRECTORY dataDirectory = &ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if(dataDirectory->VirtualAddress == 0) {
returnNULL;
}
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwModule + dataDirectory->VirtualAddress);
PDWORD ardwNames = (PDWORD)(dwModule + exportDirectory->AddressOfNames);
PWORDarwNameOrdinals = (PWORD)(dwModule + exportDirectory->AddressOfNameOrdinals);
PDWORD ardwAddressFunctions = (PDWORD)(dwModule + exportDirectory->AddressOfFunctions);
char* szName = 0;
WORDwOrdinal = 0;
for(unsignedinti = 0; i < exportDirectory->NumberOfNames; i++) {
szName = (char*)(dwModule + ardwNames[i]);
if(djbHash(szName) == functionHash) {
wOrdinal = arwNameOrdinals[i];
return(PVOID)(dwModule + ardwAddressFunctions[wOrdinal]);
}
}
returnNULL;
}
函数声明的次序非常重要:它将定义编译次序。因此,如果我们想要直接调用Shellcode代码,最好首先声明我们的main函数,因为它将是入口点。我不知道这是Visual Studio的特性,还是所有的编译器都这样处理。
ASCII字符串使用宏定义MAKESTR像数组一样来声明字符串,该宏将字符串强制按照如下格式进行分配:
1
2
3
4mov dword ptr [ebp+szUser32], 72657375h ; user
mov dword ptr [ebp+szUser32+4], 642E3233h ; 32.d
mov word ptr [ebp+szUser32+8], 6C6Ch ; ll
mov [ebp+szUser32+0Ah], 0 ;'\x00'
以上代码由Python脚本生成,因为它是冗余的:
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#pragma once
#define MAKESTR(s, length) MAKESTR_##length(s)
/*
foriinrange(1,51):
s="#define MAKESTR_%d(s) {"%i
forjinrange(i):
s+="s[%d],"%j
s+="0}"
print(s)
*/
#define MAKESTR_1(s) {s[0],0}
#define MAKESTR_2(s) {s[0],s[1],0}
#define MAKESTR_3(s) {s[0],s[1],s[2],0}
#define MAKESTR_4(s) {s[0],s[1],s[2],s[3],0}
#define MAKESTR_5(s) {s[0],s[1],s[2],s[3],s[4],0}
#define MAKESTR_6(s) {s[0],s[1],s[2],s[3],s[4],s[5],0}
#define MAKESTR_7(s) {s[0],s[1],s[2],s[3],s[4],s[5],s[6],0}
#define MAKESTR_8(s) {s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],0}
#define MAKESTR_9(s) {s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8],0}
#define MAKESTR_10(s) {s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8],s[9],0}
#define MAKESTR_11(s) {s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8],s[9],s[10],0}
编译配置
我所用的是Visual Studio 2017,但我认为在其他版本中选项也是相同的:
1
2
3
4
5
6
7
8
9C / C++
Optimisation
Reduce the size /O1
Smaller code /Os
Code generation
Disable security verifications /GS-
Linker
Entries
Ignore all the defaults libraries /NODEFAULTLIB
最终得到一个3KB大小的文件;一个简单的反汇编器即可从文件中提取Shellcode代码。
Shellcode代码
Shellcode代码有339字节大小,但其中最大的部分是函数加载和DLL模块搜索。因此,即使对于一个更大的程序,Shellcode代码也不会增大很多。我们的Shellcode代码是非常简单的,因为它仅仅弹出一个消息框,但你可以很容易地将其改造成比如一个下载器之类的程序。
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#include
typedefvoid(__stdcall* _function)();
charshellcode[] =
"\x55\x8B\xEC\x83\xEC\x1C\x53\x56\x57\x64\xA1\x30\x00\x00\x00\x8B"
"\x40\x0C\x8B\x50\x0C\x8B\x4A\x30\x8B\xD9\x85\xC9\x74\x29\x0F\xB7"
"\x01\x33\xFF\xBE\x05\x15\x00\x00\x66\x85\xC0\x74\x1A\x6B\xF6\x21"
"\x0F\xB7\xC0\x03\xF0\x47\x0F\xB7\x04\x79\x66\x85\xC0\x75\xEE\x81"
"\xFE\x55\x95\xDB\x6D\x74\x17\x8B\x12\x8B\x4A\x30\x3B\xCB\x75\xCA"
"\x33\xC9\x5F\x5E\x5B\x85\xC9\x75\x0A\x33\xC0\x40\xEB\x74\x8B\x4A"
"\x18\xEB\xEF\xBA\xFB\xF0\xBF\x5F\xE8\x69\x00\x00\x00\x85\xC0\x74"
"\xE8\x8D\x4D\xF0\xC7\x45\xF0\x75\x73\x65\x72\x51\xC7\x45\xF4\x33"
"\x32\x2E\x64\x66\xC7\x45\xF8\x6C\x6C\xC6\x45\xFA\x00\xFF\xD0\x85"
"\xC0\x74\xC6\xBA\xB4\x14\x4F\x38\x8B\xC8\xE8\x37\x00\x00\x00\x85"
"\xC0\x74\xB6\x6A\x40\x8D\x4D\xFC\xC7\x45\xE4\x48\x65\x6C\x6C\x51"
"\x8D\x4D\xE4\xC7\x45\xE8\x6F\x20\x57\x6F\x51\x6A\x00\xC7\x45\xEC"
"\x72\x6C\x64\x00\x66\xC7\x45\xFC\x3A\x29\xC6\x45\xFE\x00\xFF\xD0"
"\x33\xC0\x8B\xE5\x5D\xC3\x55\x8B\xEC\x83\xEC\x10\x8B\x41\x3C\x89"
"\x55\xFC\x8B\x44\x08\x78\x85\xC0\x74\x56\x8B\x54\x08\x1C\x53\x8B"
"\x5C\x08\x24\x03\xD1\x56\x8B\x74\x08\x20\x03\xD9\x8B\x44\x08\x18"
"\x03\xF1\x89\x55\xF0\x33\xD2\x89\x75\xF4\x89\x45\xF8\x57\x85\xC0"
"\x74\x29\x8B\x34\x96\xBF\x05\x15\x00\x00\x03\xF1\xEB\x09\x6B\xFF"
"\x21\x0F\xBE\xC0\x03\xF8\x46\x8A\x06\x84\xC0\x75\xF1\x3B\x7D\xFC"
"\x74\x12\x8B\x75\xF4\x42\x3B\x55\xF8\x72\xD7\x33\xC0\x5F\x5E\x5B"
"\x8B\xE5\x5D\xC3\x0F\xB7\x04\x53\x8B\x55\xF0\x8B\x04\x82\x03\xC1"
"\xEB\xEB";
intmain() {
char* payload = (char*) VirtualAlloc(NULL,sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(payload, shellcode,sizeof(shellcode));
_function function = (_function)payload;
function();
return0;
}
本文由 看雪翻译小组 木无聊偶 编译
转载请注明来源