看雪论坛
发新帖
2

[翻译]Windows平台下的Shellcode代码优化编写指引

木无聊偶 2017-5-9 22:23 1747

前言

    在构造一个Shellcode载荷时总是存在多种方法,特别是对于Windows平台来说。需要手工编写所有的汇编代码,或者说编译器能够有所帮助吗?需要直接使用syscall,还是需要在内存中搜索函数?因为构造载荷一般来说不会很简单,所以我决定写一篇文章来专门论述相关问题。我习惯于用C语言来完成所有的工作,并使用Visual Studio来对其进行编译:因为C语言的源代码更加优美,编译器能够更好地对其进行优化,并且如果需要的话可以使用LLVM框架实现自己的混淆器。

    在本例中,我将针对于x86架构下的Shellcode代码;当然,相关的分析完全可以应用于x86(64位)架构下的Shellcode代码或者别的处理器。

寻找基本DLL

简介

当一个Shellcode载荷加载到Windows系统中的时候,第一步就是要定位需要使用的函数,即搜索存储函数的动态链接库(DLL)。为此,我们需要用到以下各小节所描述的不同的结构。

线程环境块

Windows系统使用TEB结构来描述一个线程,每个线程通过使用FSx86平台)或GSx86,64位平台)寄存器来访问其自身的TEB结构。TEB结构具体如下:

0: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结构,只需要进行如下操作:

PEB* 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结构,如下所示:

0: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,其对应于如下结构:

0: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)的位置。每个链表成员具有如下结构:

0: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中的第一个成员就是可执行程序自身,在之后我们能够找到NTDLLKERNEL32;然而,我们并不确定在所有版本的Windows系统中都是这个次序,所以最好根据DLL名称(大写/小写)进行搜索。

DJB散列

    如前所述,我们并不信任DLL顺序而选择根据DLL名称进行搜索。然而在一段Shellcode代码中,使用ASCII字符串(或更糟的,UNICODE字符串)并不是一个好主意:这将使得我们的Shellcode代码过于臃肿!因此我建议,使用散列机制来比较DLL名称。由于其简洁有效性我选择使用DJB散列算法,具体代码如下所示:

DWORD djbHashW(wchar_t* str) {
    unsigned int hash = 5381;
    unsigned int i = 0;
 
    for (i = 0; str[i] != 0; i++) {
        hash = ((hash << 5) + hash) + str[i];
    }
 
    return hash;
}

    由于DLL名称可能大写也可能小写,因此在散列算法中最好满足如下等式关系:

djbHashW(L"ntdll.dll") == djbHashW(L"NTDLL.DLL")

代码

    现在我们已经讨论了如何完成以上工作,是时候来编码实现我们的想法了。具体代码如下所示:

typedef struct _LSA_UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
 
typedef struct _LDR_DATA_TABLE_ENTRY {
    LIST_ENTRY              InLoadOrderModuleList;
    LIST_ENTRY              InMemoryOrderModuleList;
    LIST_ENTRY              InInitializationOrderModuleList;
    PVOID                   BaseAddress;
    PVOID                   EntryPoint;
    ULONG                   SizeOfImage;
    UNICODE_STRING          FullDllName;
    UNICODE_STRING          BaseDllName;
    ULONG                   Flags;
    SHORT                   LoadCount;
    SHORT                   TlsIndex;
    LIST_ENTRY              HashTableEntry;
    ULONG                   TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
 
typedef struct _PEB_LDR_DATA {
    ULONG      Length;
    ULONG      Initialized;
    ULONG      SsHandle;
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
 
typedef struct _RTL_USER_PROCESS_PARAMETERS {
    BYTE           Reserved1[16];
    PVOID          Reserved2[10];
    UNICODE_STRING ImagePathName;
    UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
 
typedef struct _PEB {
    BYTE                          Reserved1[2];
    BYTE                          BeingDebugged;
    BYTE                          Reserved2[1];
    PVOID                         Reserved3[2];
    PPEB_LDR_DATA                 Ldr;
    PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
    BYTE                          Reserved4[104];
    PVOID                         Reserved5[52];
    PVOID                         PostProcessInitRoutine;
    BYTE                          Reserved6[128];
    PVOID                         Reserved7[1];
    ULONG                         SessionId;
} PEB, *PPEB;
 
DWORD getDllByName(DWORD dllHash) {
  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);
 
  return 0;
}

如果想要使用其他的DLL模块,只需要使用LoadLibrary()函数来将其加载。不要着急,我们将在user32.dll相关的Shellcode代码中进行该项工作。

函数地址

简介

现在我们已经找到DLL,下一步需要在DLL内存空间中搜寻所需的函数在哪儿。幸运的是,透彻理解PE文件头部结构的前提下这并不复杂。要牢记的是,当我们讨论PE文件头部时,提到的大部分地址都与可执行程序地址相关。

可移植的执行体头部

    执行体的开始位置是一个DOS头部,但它只是为了兼容(DOS系统)而存在。具体结构如下所示:

0: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头部具体结构如下所示:

0: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

    数据目录中包含了一些有趣成员的地址;而对我们来说,它包含了所有导出函数的地址。

0:000> dt nt!_IMAGE_DATA_DIRECTORY
    +0x000 VirtualAddress   : Uint4B
    +0x004 Size             : Uint4B

    因此,我们可以使用DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress来直接获取导出目录的地址:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     
    DWORD   AddressOfNames;         
    DWORD   AddressOfNameOrdinals;  
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

可以通过名字或者通过序号来导出一个函数;因此,以下三个数组保持更新:

·AddressOfFunctions,按照序号顺序依次保存函数的地址;

·AddressOfNames,保存函数名称;

·AddressOfNameOrdinals,保存序号,该数组与AddressOfNames数组次序相同。

    因此,如果想要根据名称来查找函数地址,我们需要浏览AddressOfNames数组中的所有名称,并将数组索引用于AddressOfNameOrdinals[index];而这将返回一个序号用于AddressOfFunctions[ordinal]。整个过程的伪代码如下所示:

int i = 0;
while (AddressOfNames[i] != searchedName) {
    i++;
}
 
return AddressOfFunctions[ AddressOfNamesOrdinals[i] ];

代码

    正如搜索DLL时所做的那样,我们将使用DJB散列算法(不过这次用ASCII字符串):

PVOID getFunctionAddr(DWORD dwModule, DWORD functionHash) {
    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) {
        return NULL;
    }
 
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwModule + dataDirectory->VirtualAddress);
    PDWORD ardwNames = (PDWORD)(dwModule + exportDirectory->AddressOfNames);
    PWORD arwNameOrdinals = (PWORD)(dwModule + exportDirectory->AddressOfNameOrdinals);
    PDWORD ardwAddressFunctions = (PDWORD)(dwModule + exportDirectory->AddressOfFunctions);
    char* szName = 0;
    WORD wOrdinal = 0;
 
    for (unsigned int i = 0; i < exportDirectory->NumberOfNames; i++) {
        szName = (char*)(dwModule + ardwNames[i]);
 
        if (djbHash(szName) == functionHash) {
            wOrdinal = arwNameOrdinals[i];
            return (PVOID)(dwModule + ardwAddressFunctions[wOrdinal]);
        }
    }
 
    return NULL;
}

编译

最终代码

    我们已经讨论了所用的重要结构和算法,下面看看如何生成Shellcode代码。

#pragma comment(linker, "/ENTRY:main")
 
#include "makestr.h"
#include "peb.h"
 
typedef HMODULE (WINAPI* _LoadLibraryA)(LPCSTR lpFileName);
typedef int (WINAPI* _MessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
 
int main();
DWORD getDllByName(DWORD dllHash);
PVOID getFunctionAddr(DWORD dwModule, DWORD functionHash);
DWORD djbHash(char* str);
DWORD djbHashW(wchar_t* str);
 
int main() {
    DWORD hashKernel32 = 0x6DDB9555; // djbHashW(L"KERNEL32.DLL");
    DWORD hKernel32 = getDllByName(hashKernel32);
    if (hKernel32 == 0) {
        return 1;
    }
 
    DWORD hashLoadLibraryA = 0x5FBFF0FB; // djbHash("LoadLibraryA");
    _LoadLibraryA xLoadLibraryA = getFunctionAddr(hKernel32, hashLoadLibraryA);
    if (xLoadLibraryA == NULL) {
        return 1;
    }
 
    char szUser32[] = MAKESTR("user32.dll", 10);
    DWORD hUser32 = xLoadLibraryA(szUser32);
    if (hUser32 == 0) {
        return 1;
    }
 
    DWORD hashMessageBoxA = 0x384F14B4; // djbHash("MessageBoxA");
    _MessageBoxA xMessageBoxA = getFunctionAddr(hUser32, hashMessageBoxA);
    if (xMessageBoxA == NULL) {
        return 1;
    }
 
    char szMessage[] = MAKESTR("Hello World", 11);
    char szTitle[] = MAKESTR(":)", 2);
    xMessageBoxA(0, szMessage, szTitle, MB_OK|MB_ICONINFORMATION);
 
    return 0;
}
 
inline PEB* getPeb() {
    __asm {
        mov eax, fs:[0x30];
    }
}
 
DWORD djbHash(char* str) {
    unsigned int hash = 5381;
    unsigned int i = 0;
 
    for (i = 0; str[i] != 0; i++) {
        hash = ((hash << 5) + hash) + str[i];
    }
 
    return hash;
}
DWORD djbHashW(wchar_t* str) {
    unsigned int hash = 5381;
    unsigned int i = 0;
 
    for (i = 0; str[i] != 0; i++) {
        hash = ((hash << 5) + hash) + str[i];
    }
 
    return hash;
}
 
DWORD getDllByName(DWORD dllHash) {
    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);
 
    return 0;
}
 
PVOID getFunctionAddr(DWORD dwModule, DWORD functionHash) {
    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) {
        return NULL;
    }
 
 
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwModule + dataDirectory->VirtualAddress);
    PDWORD ardwNames = (PDWORD)(dwModule + exportDirectory->AddressOfNames);
    PWORD arwNameOrdinals = (PWORD)(dwModule + exportDirectory->AddressOfNameOrdinals);
    PDWORD ardwAddressFunctions = (PDWORD)(dwModule + exportDirectory->AddressOfFunctions);
    char* szName = 0;
    WORD wOrdinal = 0;
 
    for (unsigned int i = 0; i < exportDirectory->NumberOfNames; i++) {
        szName = (char*)(dwModule + ardwNames[i]);
 
        if (djbHash(szName) == functionHash) {
            wOrdinal = arwNameOrdinals[i];
            return (PVOID)(dwModule + ardwAddressFunctions[wOrdinal]);
        }
    }
 
    return NULL;
}

    函数声明的次序非常重要:它将定义编译次序。因此,如果我们想要直接调用Shellcode代码,最好首先声明我们的main函数,因为它将是入口点。我不知道这是Visual Studio的特性,还是所有的编译器都这样处理。

    ASCII字符串使用宏定义MAKESTR像数组一样来声明字符串,该宏将字符串强制按照如下格式进行分配:

mov  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脚本生成,因为它是冗余的:

#pragma once
 
#define MAKESTR(s, length) MAKESTR_##length(s)
 
/*
for i in range(1,51):
    s = "#define MAKESTR_%d(s) {" % i
    for j in range(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,但我认为在其他版本中选项也是相同的:

C / 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代码是非常简单的,因为它仅仅弹出一个消息框,但你可以很容易地将其改造成比如一个下载器之类的程序。

#include <Windows.h>
 
typedef void(__stdcall* _function)();
 
char shellcode[] = 
    "\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";
 
int main() {
    char* payload = (char*) VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(payload, shellcode, sizeof(shellcode));
 
    _function function = (_function)payload;
    function();
 
    return 0;
}



原文链接:https://dimitrifourny.github.io/2017/04/28/optimized-windows-shellcode/

本文由 看雪翻译小组 木无聊偶 编译

转载请注明来源

本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (16)
jiaojcheng 2017-5-9 22:40
2
翻译小组真给力!
2
木无聊偶 2017-5-9 23:52
3
jiaojcheng 翻译小组真给力!
yy虫子yy 2017-5-10 00:44
4
翻译得不错,收藏了
2
木无聊偶 2017-5-10 06:50
5
yy虫子yy 翻译得不错,收藏了
能对大家有所帮助真是莫大的鼓励!我会继续努力的!
1
flamepeak 2017-5-10 10:23
6
文章很好,翻译的也很好。
2
木无聊偶 2017-5-10 11:05
7
flamepeak 文章很好,翻译的也很好。
谢谢谢谢
哆啦咪 2017-5-10 22:10
8
加油,期待更多优秀的译文
2
木无聊偶 2017-5-11 07:06
9
哆啦咪 加油,期待更多优秀的译文[em_13]
嗯嗯!我一定继续努力,绝不辜负组织的期待,哈哈哈!
1
RichardE 2017-5-11 07:22
10
谢谢分享
2
木无聊偶 2017-5-11 10:08
11
RichardE 谢谢分享[em_63]
互相交流,共同进步
formattings 2017-5-12 14:43
12
感谢分享,虽然我是一个码盲。
2
木无聊偶 2017-5-15 23:58
13
formattings 感谢分享,虽然我是一个码盲。
水平都是一点一点提高的,加油!
1
fyb波 2017-5-17 23:48
14
我想问下这个,  这个代码框里的  内容  是怎么  编辑的?  html  还是  编辑器  写的?
1
RichardE 2017-5-18 08:31
15
fyb波 我想问下这个, 这个代码框里的 内容 是怎么 编辑的? html 还是 编辑器 写的?

1
fyb波 2017-5-18 09:08
16
代码颜色怎么设置的
2
木无聊偶 2017-5-19 10:11
17
fyb波 代码颜色怎么设置的
代码颜色是根据你的选择自动设置的
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 知道创宇带宽支持 | 微信公众号:ikanxue
Time: 0.013, SQL: 9 / 京ICP备10040895号-17