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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://b0ldfrev.gitbook.io/note/windows_operating_system/windows-xia-tong-yong-shellcode-yuan-li.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
