从0到1编写shellcode
ming9 发表于 山东 二进制安全 1705浏览 · 2024-05-21 03:25

前言

在c语言中我们使用的MessageBoxA函数,其实存放在User32.dll动态链接库中,它是一个windows API函数

当c语言在链接时,它会将源文件中的代码、库函数等链接在一起,生成最终的可执行代码,形成可执行程序

当程序运行时,操作系统会加载程序所需的动态链接库到内存中,而User32.dll中的MessageBoxA函数也被放在了内存中的一个位置,供给程序使用

硬编码shellcode

硬编码shellcode是直接通过地址调用相应API函数的一段16进制机器代码

例如:User32.dll中的MessageBoxA函数的内存地址我们知道,直接使用,这就是硬编码

先使用C语言编写一段弹窗程序,并修改visual stdio配置,能够让程序可以在xp的电脑上运行

c语言弹窗

#include <Windows.h>

int main() {
    MessageBoxA(NULL, "ming", "test", MB_OK | MB_ICONINFORMATION);
    return 0;
}

visual stdio设置

当在xp环境中执行的时候,发现可以弹窗,程序正常执行

ollydbg定位MessageBoxA函数内存地址

通过查找字符串功能选项寻找对应的汇编语言命令位置

通过call命令可以发现MessageBoxA函数地址为0x77D5050B

也就是说内存中0x77D5050B的位置存放着windows操作系统User32.dll文件中的MessageBoxA函数

编写内联汇编代码

#include<windows.h>
void main()
{
    LoadLibrary(L"user32.dll");
    __asm
    {
        xor ebx, ebx            //ebx置0
        push ebx                       
        push 0x74736574; test   //这些push会将数据压入栈中,而esp对应的为当前栈顶的地址
        mov esi, esp            //mov指令会将esp,当前栈顶的地址放入到eax
        push ebx                //push ebx的作用为插入一个空字节,隔开两个数据
        push 0x676e696d; ming   //将ming字符串压入到栈中
        mov ecx, esp            //将当前栈顶的地址放入到ecx,现在eax与ecx分别存放着test、ming数据的内存地址
        push ebx
        push esi                //将eax压入栈中,为后续MessageBoxA函数执行提供参数
        push ecx                //将ecx压入栈中,为后续MessageBoxA函数执行提供参数
        push ebx
        mov esi, 0x77D5050B
        call esi                //调用MessageBoxA函数,实现弹窗
    }
}

编译为可执行程序之后放入到xp中运行弹窗

生成shellcode

IDA反汇编

这些就是对应的shellcode

33DB5368746573748BC453686D696E678BCC53505153BE0B05D577FFD6

通过反汇编网站转换一下格式

https://defuse.ca/online-x86-assembler.htm

通过shellcode加载器执行shellcode

#include<windows.h>
unsigned char shellcode[] = "\x33\xDB\x53\x68\x74\x65\x73\x74\x8B\xC4\x53\x68\x6D\x69\x6E\x67\x8B\xCC\x53\x50\x51\x53\xBE\x0B\x05\xD5\x77\xFF\xD6";

void main(int arc,char **argv)
{
    LoadLibrary(L"user32.dll");
    __asm
    {
        lea eax,shellcode
        push eax
        ret
    }
}

可是不同的操作系统对应的api函数位置不同

以下是在windows10操作系统下使用x32dbg调试得到的MessageBoxA函数地址,与xp下的0x77D5050B不同

修改代码重新编译才可以执行

所以,因为不同操作系统下api函数加载的内存位置不同导致了硬编码shellcode没有可移植性,在xp下使用,但是不能在windows10上使用。

函数动态获取的shellcode

编写逻辑

所有的windows32程序都会加载kerner32.dll基础的动态链接库,而GetProcAddress函数就在kernel32.dll中

GetProcAddress这个函数的作用是从指定的动态链接库 (DLL) 检索导出函数 (也称为过程) 或变量的地址

使用GetProcAddress获取到kernel32.dll中LoadLibraryA的地址,在通过LoadLibraryA加载User32.dll动态链接库

也就能找到MessageBoxA函数,也就能实现弹窗

定位kernel32.dll地址

#include "windows.h"
#include "stdio.h"

int main()
{
    int kernel32 = 0;
    __asm {
        mov ebx, fs: [0x30]                //通过FS寄存器访问PEB结构
        mov ebx, [ebx + 0xc]               //获取PEB结构中Ldr字段的值
        mov ebx, [ebx + 0x14]              //获取PEB结构中InMemoryOrderModuleList字段的值
        mov ebx, [ebx]; ntdll              //获取InMemoryOrderModuleList第一个模块的基址
        mov ebx, [ebx]; kernel
        mov ebx, [ebx + 0x10]; DllBase     //获取kernel32.dll模块的基址
        mov kernel32, ebx
    }
    printf("kernel32=0x%x", kernel32);
    return 0;
}

使用windbg查询kernel32.dll的基址

为了验证内联汇编程序得到的kerenl32.dll的基址位置正确,我们可以使用windbg运行一个32位程序查看kerenl32.dll的基址

dt _PEB @$peb

dt _PEB_LDR_DATA 0x777ddb00

dt _LIST_ENTRY 0x777ddb00+0x014
dt _LDR_DATA_TABLE_ENTRY 0x1153eb8

dt _LDR_DATA_TABLE_ENTRY 0x11543c8

使用x32dbg查询kernel32.dll的基址

程序运行的结果与windbg、x32dbg查询的对应

找出kernel32.dll导出表的地址

dt _IMAGE_DOS_HEADER 75f30000

dt _IMAGE_NT_HEADERS  kernel32+0n248

dt _IMAGE_OPTIONAL_HEADER kernel32+0n248+0x018

dt _IMAGE_DATA_DIRECTORY kernel32+0n248+0x018+0x060

导出表的地址为75f30000+0x93e40,及0x75fc3e40

汇编语言定位导出表地址

#include "windows.h"
#include "stdio.h"

int main()
{
    int kernel32 = 0;
    int Export_table = 0;
    __asm {
        mov ebx, fs: [0x30]                //通过FS寄存器访问PEB结构
        mov ebx, [ebx + 0xc]               //获取PEB结构中Ldr字段的值
        mov ebx, [ebx + 0x14]              //获取PEB结构中InMemoryOrderModuleList字段的值
        mov ebx, [ebx]; ntdll              //获取InMemoryOrderModuleList第一个模块的基址
        mov ebx, [ebx]; kernel
        mov ebx, [ebx + 0x10]; DllBase     //获取kernel32.dll模块的基址
        mov kernel32, ebx
        mov edx, [ebx + 0x3c]
        add edx, ebx
        mov edx, [edx + 0x78]
        add edx, ebx
        mov Export_table, edx
    }
    printf("Export=0x%x", Export_table);
    return 0;
}

遍历地址,找出GetProcaddress

导出表地址偏移0x20处为AddressOfNames,而AddressOfNames的结构是一个数组指针,每个机器位(4字节)都指向一个函数名的字符串

通过比较函数名字符串的方式确定GetProcAress函数的索引值

Get_Function:                                             
inc ecx
lodsd
add eax, ebx                                 
cmp dword ptr[eax], 0x50746547               // GetP
jnz Get_Function
cmp dword ptr[eax + 0x4], 0x41636f72         // rocA
jnz Get_Function

这样就找到了GetProcAddress函数的索引值

内联汇编代码

然后在寻找GetProcAddress函数的绝对地址,使用GetProcAddress获取到kernel32.dll中LoadLibraryA的地址,在通过LoadLibraryA加载User32.dll动态链接库

就能找到MessageBoxA函数,就能实现弹窗

#include "windows.h"
#include "stdio.h"

void main()
{
    int kernel32 = 0;
    int Export_table = 0;
    __asm {
        mov ebx, fs: [0x30]                          //通过FS寄存器访问PEB结构
        mov ebx, [ebx + 0xc]                         //获取PEB结构中Ldr字段的值
        mov ebx, [ebx + 0x14]                        //获取PEB结构中InMemoryOrderModuleList字段的值
        mov ebx, [ebx]; ntdll                        //获取InMemoryOrderModuleList第一个模块的基址
        mov ebx, [ebx]; kernel
        mov ebx, [ebx + 0x10]; DllBase               //获取kernel32.dll模块的基址
        mov kernel32, ebx
        mov edx, [ebx + 0x3c]
        add edx, ebx
        mov edx, [edx + 0x78]       
        add edx, ebx                                 //获取导出表的地址
        mov esi, [edx + 0x20]            
        add esi, ebx                                 //获取AddressOfNames
        xor ecx, ecx

        Get_Function:                               // 搜索GetProcAddress,在kernel32.dll的导出函数中,GetProcA就能确认到
        inc ecx
        lodsd
        add eax, ebx                                 
        cmp dword ptr[eax], 0x50746547               // GetP
        jnz Get_Function
        cmp dword ptr[eax + 0x4], 0x41636f72         // rocA
        jnz Get_Function
        mov esi, [edx + 0x24]
        add esi, ebx
        mov cx, [esi + ecx * 2]
        dec ecx
        mov esi, [edx + 0x1c]
        add esi, ebx
        mov edx, [esi + ecx * 4]
        add edx, ebx                                // EDX = GetProcAddress

        xor ecx, ecx; ECX = 0
        push ebx                                    // Kernel32.dll的基地址
        push edx                                    // GetProcAddress的基址
        push ecx; 0
        push 0x41797261; aryA
        push 0x7262694c; Libr
        push 0x64616f4c; Load
        push esp; "LoadLibrary"
        push ebx;
        call edx                                   // GerProcAddress(Kernel32,"LoadLibraryA") 使用GetProcAddress加载loadlibraryA- 

        add esp, 0xc; pop "LoadLibrary"
        pop ecx; ECX = 0
        push eax; EAX = LoadLibrary
        push ecx
        mov cx, 0x6c6c; ll
        push ecx
        push 0x642e3233; d.23
        push 0x72657355; resU
        push esp; "User32.dll" 
        call eax; LoadLibrary("User32.dll")

        add esp, 0x10                            // Clean stack
        mov edx, [esp + 0x4]                     // EDX = GetProcAddress
        xor ecx, ecx
        push ecx
        mov  ecx, 0x6c41786f; obAl
        push ecx
        sub dword ptr[esp + 0x3], 0x6c; Remove l
        push 0x42656761; Bega
        push 0x7373654d; sseM
        push esp; MessageBoxA
        push eax; User32.dll address
        call edx; GetProc(MessageBoxA)

        add esp, 0x18; Cleanup stack
        push ebp
        xor ebx, ebx            //ebx置0
        push ebx
        push 0x74736574; test   //这些push会将数据压入栈中,而esp对应的为当前栈顶的地址
        mov esi, esp            //mov指令会将esp,当前栈顶的地址放入到eax
        push ebx                //push ebx的作用为插入一个空字节,隔开两个数据
        push 0x676e696d; ming   //将ming字符串压入到栈中
        mov ecx, esp            //将当前栈顶的地址放入到ecx,现在eax与ecx分别存放着test、ming数据的内存地址
        push ebx
        push esi                //将eax压入栈中,为后续MessageBoxA函数执行提供参数
        push ecx                //将ecx压入栈中,为后续MessageBoxA函数执行提供参数
        push ebx
        call eax                //调用MessageBoxA函数,实现弹窗

    }
}

对应的shellcode

IDA反汇编exe程序,将对应的16进制代码取出

拿到反汇编网站上转换一下

https://defuse.ca/online-x86-assembler.htm

\x64\x8B\x1D\x30\x00\x00\x00\x8B\x5B\x0C\x8B\x5B\x14\x8B\x1B\x8B\x1B\x8B\x5B\x10\x89\x5D\xF4\x8B\x53\x3C\x03\xD3\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\x63\x41\x75\xEB\x8B\x72\x24\x03\xF3\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x03\xF3\x8B\x14\x8E\x03\xD3\x33\xC9\x53\x52\x51\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x83\xC4\x0C\x59\x50\x51\x66\xB9\x6C\x6C\x51\x68\x33\x32\x2E\x64\x68\x55\x73\x65\x72\x54\xFF\xD0\x83\xC4\x10\x8B\x54\x24\x04\x33\xC9\x51\xB9\x6F\x78\x41\x6C\x51\x83\x6C\x24\x03\x6C\x68\x61\x67\x65\x42\x68\x4D\x65\x73\x73\x54\x50\xFF\xD2\x83\xC4\x18\x55\x33\xDB\x53\x68\x74\x65\x73\x74\x8B\xF4\x53\x68\x6D\x69\x6E\x67\x8B\xCC\x53\x56\x51\x53\xFF\xD0

shellcode效果

使用shellcode加载器加载到内存执行

#include <stdio.h>
#include <windows.h>
using namespace std;
int main()
{
    char shellcode[] = "\x64\x8B\x1D\x30\x00\x00\x00\x8B\x5B\x0C\x8B\x5B\x14\x8B\x1B\x8B\x1B\x8B\x5B\x10\x89\x5D\xF4\x8B\x53\x3C\x03\xD3\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F\x63\x41\x75\xEB\x8B\x72\x24\x03\xF3\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x03\xF3\x8B\x14\x8E\x03\xD3\x33\xC9\x53\x52\x51\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x53\xFF\xD2\x83\xC4\x0C\x59\x50\x51\x66\xB9\x6C\x6C\x51\x68\x33\x32\x2E\x64\x68\x55\x73\x65\x72\x54\xFF\xD0\x83\xC4\x10\x8B\x54\x24\x04\x33\xC9\x51\xB9\x6F\x78\x41\x6C\x51\x83\x6C\x24\x03\x6C\x68\x61\x67\x65\x42\x68\x4D\x65\x73\x73\x54\x50\xFF\xD2\x83\xC4\x18\x55\x33\xDB\x53\x68\x74\x65\x73\x74\x8B\xF4\x53\x68\x6D\x69\x6E\x67\x8B\xCC\x53\x56\x51\x53\xFF\xD0";
    LPVOID lpAlloc = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(lpAlloc, shellcode, sizeof shellcode);
    ((void(*)())lpAlloc)();
    return 0;
}

参考链接

https://xz.aliyun.com/t/2108

https://migraine-sudo.github.io/2019/12/21/Shellcode-Write/

https://blog.csdn.net/mdp1234/article/details/110287856?spm=1001.2014.3001.5502

https://www.youtube.com/watch?v=mN9LopGgkjk

0 条评论
某人
表情
可输入 255