Window中的shellcode编写框架(入门篇)

Posted Thresh|的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Window中的shellcode编写框架(入门篇)相关的知识,希望对你有一定的参考价值。

Shellcode

  • 定义

  是一段可注入的指令(opcode),可以在被攻击的程序内运行。

  • 特点

  短小精悍,灵活多变,独立存在,无需任何文件格式的包装,因为shellcode直接操作寄存器和函数,所以opcode必须是16进制形式。因此也不能用高级语言编写shellcode。在内存中运行,无需运行在固定的宿主进程上。

  • Shellcode的利用原理

  将shellcode注入缓冲区,然后欺骗目标程序执行它。而将shellcode注入缓冲区最常用的方法是利用目标系统上的缓冲区溢出漏洞。

Shellcode生成方法

  • 编程语言编写:汇编语言,C语言
  • shellcode生成器

Shellcode编写原则

  •  杜绝双引号字符串的直接使用

    关闭VS自动优化没有使用到的变量

    自定义函数入口

  •   动态获取函数的地址

    GetProAddress 从dll中获取函数的地址

    参数1:调用dll的句柄,参数2:函数名

    Bug:error C2760: 语法错误: 意外的令牌“标识符”,预期的令牌为“类型说明符”

    打开项目工程-> 属性 -> c/c++ --> 语言 -> 符合模式 修改成否即可 如果这样设置将无法使用c函数。

    这个比较关键,否则使用printf就直接崩溃或者是编译报错

    最佳方案是:修改平台工具集

  • 通过获得Kernel32基址来获取GetProcAddres基址
  • 避免全局变量的使用

    因为vs会将全局变量编译在其他区段中 结果就是一个绝对的地址不能使用static定义变量(变量放到内部函数使用)

  •  确保已加载使用API的动态链接库

编写shellcode前的准备

  • 修改程序入口点:链接器-高级。作用:去除自动生成的多余的exe代码
  • 关闭缓冲区安全检查,属性->C/C++ ->代码生成->安全检查禁用
  • 设置工程兼容window XP :代码生成 ->运行库 选择 debug MTD  release MT
  • 清除资源:链接器->调试->清单文件
  • 关闭调试功能

如下:

 

属性->常规->平台工具集 选择xp版本

 

 

 

 

C/C++->代码生成->运行库选择MT

安全检查禁用

 

 

 

 

 

链接器->高级->入口点修改为EntryMain

 

 

 

 

 

函数动态链接调用

  在编写shellcode时,所有用到的函数都需要动态调用,通过LoadLibrary函数加载动态链接库,GetProAddress获取动态链接库中函数的地址。所以获取到GetProAddress和LoadLibrary地址时非常重要的。通过GetProAddress和LoadLibrary,可以获取到已加载的动态链接库的地址。

 

动态获取Kernel32.dll基址和GetProAddress 地址

  在正常情况下,我们不知道LoadLibraryA的地址,所以不能直接使用该函数。GetProAddress是动态链接库Kernel32.dll中的函数。每个进程内部加载都会加载Kernel32.dll,获取到加载Kernel32.dll地址就可以获取到GetProAddress函数的地址。通过汇编内嵌获取到Kernel32的基址。获取到GetProAddress地址后,就可以获取到所有需要的函数的地址。

 

pE文件运行时加载的链接库

 

 

 

 

代码如下:

#include <windows.h>
#include <stdio.h>

//内嵌汇编获取Kernel32的地址
__declspec(naked) DWORD getKernel32()
{
    __asm
    {
        mov eax,fs:[30h]
        mov eax,[eax+0ch]
        mov eax,[eax+14h]
        mov eax,[eax]
        mov eax,[eax]
        mov eax,[eax+10h]
        ret
    }
}

//通过kernel32基址获取GetProcAddress的地址
FARPROC _GetProcAddress(HMODULE hModuleBase) 
{
    PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
    PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size){
        return NULL;
    }
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
        return NULL;
    }
    PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
    PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);

    DWORD dwLoop = 0;
    FARPROC pRet = NULL;
    for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
        char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);

        if (pFunName[0] == \'G\'&&
            pFunName[1] == \'e\'&&
            pFunName[2] == \'t\'&&
            pFunName[3] == \'P\'&&
            pFunName[4] == \'r\'&&
            pFunName[5] == \'o\'&&
            pFunName[6] == \'c\'&&
            pFunName[7] == \'A\'&&
            pFunName[8] == \'d\'&&
            pFunName[9] == \'d\'&&
            pFunName[10] == \'r\'&&
            pFunName[11] == \'e\'&&
            pFunName[12] == \'s\'&&
            pFunName[13] == \'s\')
        {
            pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
            break;
        }
    }
    return pRet;
}

int main()
{
    //kernel32.dll 基址的动态获取
    HMODULE hLoadLibrary = LoadLibraryA("kernel32.dll");
    //使用内嵌汇编来获取基址
    HMODULE _hLoadLibrary = (HMODULE)getKernel32();
    //效果是一样的
    printf("LoadLibraryA动态获取的地址: 0x%x\\n", hLoadLibrary);
    printf("内嵌汇编获取的地址: 0x%x\\n", _hLoadLibrary);

    //声明定义,先转到到原函数定义,然后重新定义
    typedef FARPROC(WINAPI *FN_GetProcAddress)(
            _In_ HMODULE hModule,
            _In_ LPCSTR lpProcName
        );

    FN_GetProcAddress fn_GetProcAddress;
    fn_GetProcAddress = (FN_GetProcAddress)_GetProcAddress(_hLoadLibrary);

    printf("动态获取GetProcAddress地址: 0x%x\\n",fn_GetProcAddress);
    printf("内置函数获取: 0x%x\\n",GetProcAddress);
}

 

可以看到,动态获取的地址和系统函数获取的是一致的

 

 

 

 

Shellcode框架(一)

引用前面kernel32.dll和GetProcaddress的获取地址。对于每一个要引用的函数,通过查看定义来声明定义函数,通过GetProcaddress动态获取地址。

 

 

具体代码如下:

原C代码:

#include <windows.h>
#include <stdio.h>
#include <windows.h>
int EntryMain()
{
CreateFileA(“1.txt”, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
MessageBoxA(NULL, “hello world”, tip, MB_OK);
return 0;
}

Shellcode编写:

#include <windows.h>
#include <stdio.h>

FARPROC  getProcAddress(HMODULE hModuleBase);
DWORD getKernel32();

int EntryMain()
{
    //声明定义GetProcAddress
    typedef FARPROC(WINAPI *FN_GetProcAddress)(
        _In_ HMODULE hModule,
        _In_ LPCSTR lpProcName
        );

    //获取GetProcAddress真实地址
    FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());


    //声明定义CreateFileA
    typedef HANDLE(WINAPI *FN_CreateFileA)(
            __in     LPCSTR lpFileName,
            __in     DWORD dwDesiredAccess,
            __in     DWORD dwShareMode,
            __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
            __in     DWORD dwCreationDisposition,
            __in     DWORD dwFlagsAndAttributes,
            __in_opt HANDLE hTemplateFile
        );
    //将来的替换,地址全部动态获取
    //FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)GetProcAddress(LoadLibrary("kernel32.dll"), "CreateFileA");
    //带引号的字符串打散处理
    char xyCreateFile[] = { \'C\',\'r\',\'e\',\'a\',\'t\',\'e\',\'F\',\'i\',\'l\',\'e\',\'A\',0 };
    //动态获取CreateFile的地址
    FN_CreateFileA fn_CreateFileA = (FN_CreateFileA)fn_GetProcAddress((HMODULE)getKernel32(), xyCreateFile);
    char xyNewFile[] = { \'1\',\'.\',\'t\',\'x\',\'t\',\'\\0\'};
    fn_CreateFileA(xyNewFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);


    //定义LoadLibraryA
    typedef HMODULE(WINAPI *FN_LoadLibraryA)(
            __in LPCSTR lpLibFileName
        );
    char xyLoadLibraryA[] = { \'L\',\'o\',\'a\',\'d\',\'L\',\'i\',\'b\',\'r\',\'a\',\'r\',\'y\',\'A\',0};
    //动态获取LoadLibraryA的地址
    FN_LoadLibraryA fn_LoadLibraryA = (FN_LoadLibraryA)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryA);


    //定义MessageBoxA
    typedef int (WINAPI *FN_MessageBoxA)(
            __in_opt HWND hWnd,
            __in_opt LPCSTR lpText,
            __in_opt LPCSTR lpCaption,
            __in UINT uType);

            //原来的:MessageBoxA(NULL, "Hello world", "tip", MB_OK);
            char xy_user32[] = { \'u\',\'s\',\'e\',\'r\',\'3\',\'2\',\'.\',\'d\',\'l\',\'l\',0 };
            char xy_MessageBoxA[] = { \'M\',\'e\',\'s\',\'s\',\'a\',\'g\',\'e\',\'B\',\'o\',\'x\',\'A\',0 };
            FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress(fn_LoadLibraryA(xy_user32), xy_MessageBoxA);
            char xy_Hello[] = { \'H\',\'e\',\'l\',\'l\',\'o\',\' \',\'w\',\'o\',\'r\',\'l\',\'d\',0 };
            char xy_tip[] = { \'t\',\'i\',\'p\' };
            fn_MessageBoxA(NULL, xy_Hello, xy_tip, MB_OK);
            return 0;
}

//内嵌汇编获取Kernel32的地址
__declspec(naked) DWORD getKernel32()
{
    __asm
    {
        mov eax, fs:[30h]
        mov eax, [eax + 0ch]
        mov eax, [eax + 14h]
        mov eax, [eax]
        mov eax, [eax]
        mov eax, [eax + 10h]
        ret
    }
}

//获取GetProcAddress的地址
FARPROC getProcAddress(HMODULE hModuleBase)
{
    PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
    PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) {
        return NULL;
    }
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
        return NULL;
    }
    PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
    PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);

    DWORD dwLoop = 0;
    FARPROC pRet = NULL;
    for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
        char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);

        if (pFunName[0] == \'G\'&&
            pFunName[1] == \'e\'&&
            pFunName[2] == \'t\'&&
            pFunName[3] == \'P\'&&
            pFunName[4] == \'r\'&&
            pFunName[5] == \'o\'&&
            pFunName[6] == \'c\'&&
            pFunName[7] == \'A\'&&
            pFunName[8] == \'d\'&&
            pFunName[9] == \'d\'&&
            pFunName[10] == \'r\'&&
            pFunName[11] == \'e\'&&
            pFunName[12] == \'s\'&&
            pFunName[13] == \'s\')
        {
            pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
            break;
        }
    }
    return pRet;
}

  结果:运行exe文件创建了1.txt文件,并出现弹框。

 

 

 

 

 

函数生成的位置规律

  1. 单文件函数生成的位置规律

  规律:单文件函数的生成规律,与函数实现的先后顺序有关,而与函数的定义顺序无关。

  例如:

 

#include<windows.h>
#include<stdio.h>

int FuncA(int a, int b)
{
    puts("AAAA");
    return a + b;
}

int FuncB(int a, int b)
{
    puts("BBB");
    return a + b;
}
int main()
{
    FuncA(1, 2);
    FuncB(2, 3);
    return 0;
}

 

  结果:在IDA中看到生成的exe文件中的函数顺序为FuncA,FuncB,main,与函数实现的先后顺序有关。通过函数的位置,可以得到两个函数之间的空间大小。

  2.多文件函数生成的位置规律

    规律:与包含文件的位置无关,与实际调用的顺序有关

  //A.h
#include<stdio.h>
void A()
{
  puts("AAA");
}

//B.h
#include<stdio.h>
void B()
{
  puts("BBB");
}

//main
#include"A.h"
#include"B.h"
int main()

{ 
  A(); B();
return 0; }

 

  结果:

   

 

 

 

  工程下的后缀名为vcxproj文件

   

 

 

 

  修改顺序

   

 

 

 

  可以看到生成的顺序发生了改变

  

Shellcode框架(二)

  Shellcode代码执行过程

  ShellcodeStart-> ShellcodeEntry-> ShellcodeEnd

   工程文件:

  api.h文件:存放定义的的函数

  header.h文件:存放定义的功能函数

  0.entry.cpp文件:shellcode的入口点

  a.start.cpp文件:shellcode执行(实现逻辑功能)

  b.work.cpp文件:存放具体功能实现

  z.end.cpp文件:shellcode结束

 

  a.start.cpp文件和b.work.cpp文件分别管理逻辑功能和具体实现,更方便管理。

  生成的文件顺序:

 

 

   

 

 

   代码:

  api.h

#pragma once

#include<windows.h>

//声明定义GetProcAddress
typedef FARPROC(WINAPI *FN_GetProcAddress)(
    _In_ HMODULE hModule,
    _In_ LPCSTR lpProcName
    );
//定义LoadLibraryA
typedef HMODULE(WINAPI *FN_LoadLibraryA)(
    __in LPCSTR lpLibFileName
    );

//定义MessageBoxA
typedef int (WINAPI *FN_MessageBoxA)(
    __in_opt HWND hWnd,
    __in_opt LPCSTR lpText,
    __in_opt LPCSTR lpCaption,
    __in UINT uType);

//定义CreateFileA
typedef HANDLE(WINAPI *FN_CreateFileA)(
    __in     LPCSTR lpFileName,
    __in     DWORD dwDesiredAccess,
    __in     DWORD dwShareMode,
    __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    __in     DWORD dwCreationDisposition,
    __in     DWORD dwFlagsAndAttributes,
    __in_opt HANDLE hTemplateFile
    );

//定义一个指针
typedef struct _FUNCTIONS
{
    FN_GetProcAddress fn_GetProcAddress;
    FN_LoadLibraryA fn_LoadLibraryA;
    FN_MessageBoxA fn_MessageBoxA;
    FN_CreateFileA fn_CreateFileA;
}FUNCTIONS,*PFUNCTIONS;

  header.h

#pragma once
#include<windows.h>
#include<stdio.h>
#include"api.h"
void ShellcodeStart();
void ShellcodeEntry();
void ShellcodeEnd();
void CreateShellcode();
void InitFunctions(PFUNCTIONS pFn);
void CreateConfigFile(PFUNCTIONS pFn);

  

  0.entry.cpp

  

#include "header.h"
int EntryMain()
{
    CreateShellcode();

    return 0;
}

void CreateShellcode() 
{

    HMODULE hMsvcrt = LoadLibraryA("msvcrt.dll");
    //定义printf
    typedef int (__CRTDECL *FN_printf)(
        _In_z_ _Printf_format_string_ char const* const _Format,
        ...);
    FN_printf fn_printf = (FN_printf)GetProcAddress(hMsvcrt,"printf");

    HANDLE hBin = CreateFileA("sh.bin", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, NULL);

    if (hBin == INVALID_HANDLE_VALUE)
    {

        //这里不能使用printf函数,因为修改了函数入口,找不到printf的地址,所有需要动态调用printf函数
        //printf("create file error:%d\\n", GetLastError());
        fn_printf("create file error:%d\\n", GetLastError());
        return ;
    }
    DWORD dwSize = (DWORD)ShellcodeEnd - (DWORD)ShellcodeStart;
    DWORD dwWrite;
    WriteFile(hBin, ShellcodeStart, dwSize, &dwWrite, NULL);
    CloseHandle(hBin);

}

 a.start.cpp文件

#include "header.h"
#include "api.h"
__declspec(naked) void ShellcodeStart()
{
    __asm 
    {
        jmp ShellcodeEntry
    }
}
//内嵌汇编获取Kernel32的地址
__declspec(naked) DWORD getKernel32()
{
    __asm
    {
        mov eax, fs:[30h];
        test eax, eax;
        js finished;
        mov eax, [eax + 0ch];
        mov eax, [eax + 14h];
        mov eax, [eax];
        mov eax, [eax]
        mov eax, [eax + 10h]
    finished:
        ret
    }
}

//获取GetProcAddress的地址
FARPROC getProcAddress(HMODULE hModuleBase)
{
    PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;
    PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) {
        return NULL;
    }
    if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) {
        return NULL;
    }
    PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);
    PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);
    PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);

    DWORD dwLoop = 0;
    FARPROC pRet = NULL;
    for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++) {
        char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);


        if (pFunName[0] == \'G\'&&
            pFunName[1] == \'e\'&&
            pFunName[2] == \'t\'&&
            pFunName[3] == \'P\'&&
            pFunName[4] == \'r\'&&
            pFunName[5] == \'o\'&&
            pFunName[6] == \'c\'&&
            pFunName[7] == \'A\'&&
            pFunName[8] == \'d\'&&
            pFunName[9] == \'d\'&&
            pFunName[10] == \'r\'&&
            pFunName[11] == \'e\'&&
            pFunName[12] == \'s\'&&
            pFunName[13] == \'s\')
        {
            pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);
            break;
        }
    }
    return pRet;
}
//动态调用地址
void InitFunctions(PFUNCTIONS pFn) 
{
    //获取GetProcAddress真实地址
    pFn->fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());
    //动态获取LoadLibraryA的地址
    char xyLoadLibraryA[] = { \'L\',\'o\',\'a\',\'d\',\'L\',\'i\',\'b\',\'r\',\'a\',\'r\',\'y\',\'A\',0 };
    pFn->fn_LoadLibraryA = (FN_LoadLibraryA)pFn->fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryA);
    
    //动态获取MessageBoxA的地址
    char xy_user32[] = { \'u\',\'s\',\'e\',\'r\',\'3\',\'2\',\'.\',\'d\',\'l\',\'l\',0 };
    char xy_MessageBoxA[] = { \'M\',\'e\',\'s\',\'s\',\'a\',\'g\',\'e\',\'B\',\'o\',\'x\',\'A\',0 };
    pFn->fn_MessageBoxA = (FN_MessageBoxA)pFn->fn_GetProcAddress(pFn->fn_LoadLibraryA(xy_user32), xy_MessageBoxA);

    //动态获取CreateFile的地址
    char xyCreateFile[] = { \'C\',\'r\',\'e\',\'a\',\'t\',\'F\',\'i\',\'l\',\'e\',\'A\',0 };
    pFn->fn_CreateFileA = (FN_CreateFileA)pFn->以上是关于Window中的shellcode编写框架(入门篇)的主要内容,如果未能解决你的问题,请参考以下文章

PWN 菜鸡入门之 shellcode编写 及exploid-db用法示例

mybatis入门篇1 --- 编写入门小程序

为啥shellcode分析很难

浅析缓冲区溢出漏洞的利用与Shellcode编写

Shellcode的编写

浅析缓冲区溢出漏洞的利用与Shellcode编写