Windows下通用ShellCode原理

用 C 汇编实现,编译环境VC6.0, 实测 Win all x86 x64 系统可行

0x00 原理简述

  • 利用fs寄存器,找到TEB的地址->PEB的地址。PEB的0xC偏移处为一个指向PEB_LDR_DATA结构体的指针Ldr,PEB_LDR_DATA的0xC的偏移处为一个指向LIST_ENTRY结构体的指针InLoadOrderModuleList,这是一个按加载顺序构成的双向模块链表。同时LIST_ENTRY的父结构体为LDR_DATA_TABLE_ENTRY,该结构体里有俩有用信息->0x18 DLLBase(模块基址), ->0x2c BaseDllName(指向UNICODE_STRING结构体 模块名字为unicode类型)

  • 利用上述InLoadOrderModuleList双向链表查找kernel32.dll加载到内存的位置,找到其导出表,定位kernel32.dll导出的GetProcAddress函数,使用GetProcAddress函数获取LoadLibrary的函数地址,使用LoadLibrary函数加载user32.dll动态链接库,获取user32.dll中MessageBox的函数地址,调用MessageBox函数。

  • PEB结构图片如下:

0x01 代码实现

1, 分别为GetProcAddress,MessageBox(演示),Loadlibrary 定义函数指针

typedef DWORD (WINAPI *PGETPROCADDRESS) (HMODULE hModule , LPCSTR lpProcName);
typedef int (WINAPI * PMESSAGEBOX) (HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType);
typedef HMODULE (WINAPI * PLOADLIBRARY) (LPCTSTR lpFileName);

2, 定义UNICODE_STRING , PEB_LDR_DATA ,LDR_DATA_TABLE_ENTRY结构体

typedef struct UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;

}UNICODE_STRING;

typedef struct PEB_LDR_DATA{
    DWORD Length;
    BYTE initialized;
    PVOID SsHandle;
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    VOID * EntryInProgress;

}PEB_LDR_DATA;

typedef struct LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    void* DllBase;
    void* EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    SHORT LoadCount;
    SHORT TlsIndex;
    HANDLE SectionHandle;
    ULONG CheckSum;
    ULONG TimeDateStamp;

}LDR_DATA_TABLE_ENTRY;

3, 你在C/C++代码中定义一个全局变量,一个取值为“Hello world”的字符串,或直接把该字符串作为参数传递给某个函数。但是,编译器会把字符串放置在一个特定的Section中(如.rdata或.data)。所以定义模块和函数名的字符串的时候,为了使变量存在与栈中,因使用位置无关代码

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};
char szUser32[]={'U','S','E','R','3','2','.','d','l','l',0};
char szGetProcAddr[]={'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};

注意Dll模块名称为Unicode格式。

4, 内联汇编,找到指向InLoadOrderModuleList头的指针pBeg,pPLD指向下一个模块。

__asm{
        mov eax,fs:[0x30]
        mov eax,[eax+0x0c]
        add eax,0x0c
        mov pBeg,eax
        mov eax,[eax]
        mov pPLD,eax 
}

5, 遍历双向链表,找到kernel32.dll。由于有些系统中的kernel32.dll大小写不一样,所以这里在遍历时考虑大小写不同的情况。

while(pPLD!=pBeg)
{
    pLast=(WORD*)pPLD->BaseDllName.Buffer;
    pFirst=(WORD*)szKernel32;
    while(*pFirst && (*pFirst-32==*pLast||*pFirst==*pLast))
        {pFirst++,pLast++;}
    if(*pFirst==*pLast)
        {
        dwKernelBase=(DWORD)pPLD->DllBase;
        break;
        }
    pPLD=(LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderModuleList.Flink;
}

6, PE操作,遍历kernel32.dll的导出表,根据找到GetProcAddr函数地址。(AddressOfNames的偏移号 对应AddressOfNameOrdinals的偏移,找到的序号为AddressOfFunctions表的偏移)

IMAGE_DOS_HEADER *pIDH=(IMAGE_DOS_HEADER *)dwKernelBase; 
IMAGE_NT_HEADERS *pINGS=(IMAGE_NT_HEADERS *)((DWORD)dwKernelBase+pIDH->e_lfanew);
IMAGE_EXPORT_DIRECTORY *pIED=(IMAGE_EXPORT_DIRECTORY*)((DWORD)dwKernelBase+pINGS
->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

DWORD *pAddOfFun_Raw=(DWORD*)((DWORD)dwKernelBase+pIED->AddressOfFunctions);
WORD *pAddOfOrd_Raw=(WORD*)((DWORD)dwKernelBase+pIED->AddressOfNameOrdinals);
DWORD *pAddOfNames_Raw=(DWORD*)((DWORD)dwKernelBase+pIED->AddressOfNames);
DWORD dwCnt=0;

char *pFinded=NULL,*pSrc=szGetProcAddr;
for(;dwCnt<pIED->NumberOfNames;dwCnt++)
{
    pFinded=(char *)((DWORD)dwKernelBase+pAddOfNames_Raw[dwCnt]);
    while(*pFinded &&*pFinded==*pSrc) 
    {pFinded++;pSrc++;}
    if(*pFinded == *pSrc)
        {
        pGetProcAddress=(PGETPROCADDRESS)((DWORD)dwKernelBase+pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]]);
        break;
        }
    pSrc=szGetProcAddr;
}

7, 现在有了GetProcAddr的函数地址,我们可以用LoadLibrary获得任何api的函数地址,并调用。

pLoadLibrary=(PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase,szLoadLibrary);
pMessageBox=(PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);
char szTitle[]={'S','h','e','l','l','C','o','d','e',0};
char szContent[]={0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x20,0x21,0};
pMessageBox(NULL,szContent,szTitle,0);

0x02 运行结果

这里Hello World弹窗仅供测试,要实现更多的功能的话还需要你自己去挖掘噢 ╮( ̄▽  ̄)╭

0x03 完整代码

#include<windows.h>

typedef DWORD (WINAPI *PGETPROCADDRESS) (HMODULE hModule,LPCSTR lpProcName);
typedef int (WINAPI * PMESSAGEBOX) (HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType);
typedef HMODULE (WINAPI * PLOADLIBRARY) (LPCTSTR lpFileName);

typedef struct UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
}UNICODE_STRING;

typedef struct PEB_LDR_DATA{
    DWORD Length;
    BYTE initialized;
    PVOID SsHandle;
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    VOID * EntryInProgress;
}PEB_LDR_DATA;

typedef struct LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    void* DllBase;
    void* EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    SHORT LoadCount;
    SHORT TlsIndex;
    HANDLE SectionHandle;
    ULONG CheckSum;
    ULONG TimeDateStamp;
}LDR_DATA_TABLE_ENTRY;

void ShellCode()
{
    LDR_DATA_TABLE_ENTRY *pPLD=NULL,*pBeg=NULL;
    PGETPROCADDRESS pGetProcAddress=NULL;
    PMESSAGEBOX pMessageBox=NULL;
    PLOADLIBRARY pLoadLibrary=NULL;
    WORD *pFirst =NULL,*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};
    char szUser32[]={'U','S','E','R','3','2','.','d','l','l',0};
    char szGetProcAddr[]={'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};

    __asm{
        mov eax,fs:[0x30]
            mov eax,[eax+0x0c]
            add eax,0x0c
            mov pBeg,eax
            mov eax,[eax]
            mov pPLD,eax 
    }
    // 遍历找到kernel32.dll
    while(pPLD!=pBeg)
    {
        pLast=(WORD*)pPLD->BaseDllName.Buffer;
        pFirst=(WORD*)szKernel32;
        while(*pFirst && (*pFirst-32==*pLast||*pFirst==*pLast))
        {    pFirst++,pLast++;}
        if(*pFirst==*pLast)
        {
            dwKernelBase=(DWORD)pPLD->DllBase;
            break;
        }
        pPLD=(LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderModuleList.Flink;
    }

    // 遍历kernel32.dll的导出表,找到GetProcAddr函数地址

    IMAGE_DOS_HEADER *pIDH=(IMAGE_DOS_HEADER *)dwKernelBase; 
    IMAGE_NT_HEADERS *pINGS=(IMAGE_NT_HEADERS *)((DWORD)dwKernelBase+pIDH->e_lfanew);
    IMAGE_EXPORT_DIRECTORY *pIED=(IMAGE_EXPORT_DIRECTORY*)((DWORD)dwKernelBase+
        pINGS->OptionalHeader.
        DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    DWORD *pAddOfFun_Raw=(DWORD*)((DWORD)dwKernelBase+pIED->AddressOfFunctions);
    WORD *pAddOfOrd_Raw=(WORD*)((DWORD)dwKernelBase+pIED->AddressOfNameOrdinals);
    DWORD *pAddOfNames_Raw=(DWORD*)((DWORD)dwKernelBase+pIED->AddressOfNames);
    DWORD dwCnt=0;

    char *pFinded=NULL,*pSrc=szGetProcAddr;
    for(;dwCnt<pIED->NumberOfNames;dwCnt++)
    {
        pFinded=(char *)((DWORD)dwKernelBase+pAddOfNames_Raw[dwCnt]);
        while(*pFinded &&*pFinded==*pSrc) 
        {pFinded++;pSrc++;}
        if(*pFinded == *pSrc)
        {
            pGetProcAddress=(PGETPROCADDRESS)((DWORD)dwKernelBase+pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]]);
            break;
        }
        pSrc=szGetProcAddr;
    }
    // 有了GetProcAddr 可以获得任何api
    pLoadLibrary=(PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase,szLoadLibrary);
    pMessageBox=(PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);

    // 使用函数
    char szTitle[]={'S','h','e','l','l','C','o','d','e',0};
    char szContent[]={0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x20,0x21,0};
    pMessageBox(NULL,szContent,szTitle,0);

}


int main()
{
    ShellCode();
    return 0;
}

0x04 总结感悟

接下来研究的内容:

  • Hash API

  • 编码方式

  • 简单免杀

Last updated