# 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类型）  &#x20;
* 利用上述InLoadOrderModuleList双向链表查找kernel32.dll加载到内存的位置，找到其导出表，定位kernel32.dll导出的GetProcAddress函数，使用GetProcAddress函数获取LoadLibrary的函数地址，使用LoadLibrary函数加载user32.dll动态链接库，获取user32.dll中MessageBox的函数地址,调用MessageBox函数。
* PEB结构图片如下：

![](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LckC1dmB2Mb2F1Bgx_H%2Fuploads%2Fde3lnxqqhpzRvo6npXiG%2Ffile.png?alt=media)

![](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LckC1dmB2Mb2F1Bgx_H%2Fuploads%2FgXHwSRMvzizJ6fGXwFln%2Ffile.png?alt=media)

## 0x01 代码实现

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

```c
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结构体

```c
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）。所以定义模块和函数名的字符串的时候，为了使变量存在与栈中，因使用位置无关代码

```c
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大小写不一样，所以这里在遍历时考虑大小写不同的情况。

```c
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表的偏移）

```c
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的函数地址，并调用。

```c
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弹窗仅供测试,要实现更多的功能的话还需要你自己去挖掘噢 ╮(￣▽ ￣)╭

![](https://firebasestorage.googleapis.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LckC1dmB2Mb2F1Bgx_H%2Fuploads%2FcG4jXMuVwAhQRWmA3D4u%2Ffile.png?alt=media)

## 0x03 完整代码

```c
#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
* 编码方式
* 简单免杀
