欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-15 16:18:42
Shellcode 定义 是一段可注入的指令(opcode),可以在被攻击的程序内运行。 特点 短小精悍,灵活多变,独立存在,无需任何文件格式的包装,因为shellcode直接操作寄存器和函数,所以opcode必须是16进制形式。因此也不能用高级语言编写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版本

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

 

 

 

c/c++->代码生成->运行库选择mt

安全检查禁用

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

 

 

 

 

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

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

 

 

 

 

函数动态链接调用

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

 

动态获取kernel32.dll基址和getproaddress 地址

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

 

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

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

 

 

 

代码如下:

#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);
}

 

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

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

 

 

 

 

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文件,并出现弹框。

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

 

 

 

 

函数生成的位置规律

  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; }

 

  结果:

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

 

 

 

  工程下的后缀名为vcxproj文件

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

 

 

 

  修改顺序

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

 

 

 

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

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

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文件分别管理逻辑功能和具体实现,更方便管理。

  生成的文件顺序:

 

 

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

 

 

   代码:

  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->fn_getprocaddress((hmodule)getkernel32(), xycreatefile);

}
//入口点
void shellcodeentry()
{
    char xy_hello[] = { 'h','e','l','l','o',' ','w','o','r','l','d',0 };
    char xy_tip[] = { 't','i','p' };
    functions fn;
    initfunctions(&fn);
    createconfigfile(&fn);
}

  b.work.cpp

#include"api.h"
#include<stdio.h>
//存放功能
void createconfigfile(pfunctions pfn)
{
    char xynewfile[] = { '1','.','t','x','t','\0' };
    pfn->fn_createfilea(xynewfile, generic_write, 0, null, create_always, 0, null);
}

  z.end.cpp

#include "header.h"
void shellcodeend()
{

}

shellcode加载器

用来运行提取出来的shellcode代码,保存在sh.bin文件,将sb.bin文件拖入加载器,即可执行。

#include<stdio.h>
#include<windows.h>
int main(int argc,char* argv[])
{
    //打开文件
    handle hfile = createfilea(argv[1], generic_read, 0, null, open_always, 0, null);
    if (hfile == invalid_handle_value)
    {
        printf("oen file error:%d\n", getlasterror);
        return -1;
    }
    dword dwsize;
    dwsize = getfilesize(hfile, null);

    lpvoid lpaddress = virtualalloc(null, dwsize, mem_commit, page_execute_readwrite);
    //内存分配是否成功
    if (lpaddress ==null)
    {
        printf("virtualalloc error:%d\n", getlasterror);
        closehandle(hfile);
        return -1;
    }

    dword dwread;
    readfile(hfile,lpaddress,dwsize,&dwread, 0);

    //内嵌汇编
    __asm 
    {
        call lpaddress
    }
    _flushall();
    system("pause");
    return 0;
}