【shellcode】Windows shellcode的理解与编写

虚拟内存空间

整个虚拟内存空间分为两个相关部分: 为用户进程预留的虚拟内存空间和为系统进程预留的虚拟内存空间。
在这里插入图片描述
每个进程都有自己的私有虚拟地址空间,其中“内核空间”是一种“共享环境”,意味着每个内核进程可以在任何它想要的地方读写虚拟内存。

在这里插入图片描述

其中:

HEAP(堆) : 这是存储所有动态局部变量的地方。(通常alloc()或类似的系统调用)
STACK(栈): 分配每个静态局部变量的位置。

RWX-Hunter执行:
搜索已被标记为读(R),写(W)和执行(X)的内存部分。
遍历进程的虚拟空间内存,搜索标记为RWX的区域。

shellcode过程

    1. PE文件结构
    1. 位置无关
* 不能对字符串使用直接偏移,必须将字符串存储在堆栈中
* dll中的函数寻址,由于 ASLR 不会每次都在同一个地址中加载,
* 可以通过 PEB.PEB_LDR_DATA 找到加载模块调用其导出的函数,或加载新 dll。
* 避免空字节

有这么一个信息,就是所有的win32程序都会加载ntdll.dll和kernel32.dll这两个基础的动态链接库。所以只要找到LoadLibraryA函数,就能加载动态链接库,并调用其他函数。

在这里插入图片描述

typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24


我们看到,在PEB_LDR_DATA结构中,又包含三个LIST_ENTRY结构体分别命名为:


InLoadOrderModuleList;                模块加载顺序
InMemoryOrderModuleList;              模块在内存中的顺序
InInitializationOrderModuleList;     模块初始化装载顺序


LIST_ENTRY其结构定义如下:


typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

逐步解析:

SEH结构: Windows 结构化异常处理。

  1. Win32 寻找TEB: FS :[0] 指向进程中的TEB结构首。
  2. TEB线程环境块偏移位置为0x30的地方存放着指向进程环境块PEB的指针。
  3. 在进程环境块PEB中便宜为0x0c的地方存放着指向PEB_LDR_DATA结构体的指针。其中存放着已经被进程装载的动态链接库的信息。
  4. PEB_LDR_DATA 偏移位置为0x0c的地方存放着指向模块加载顺序连表的头指针inLoadOrderModuleList、偏移位置为0x14的位置存放InMemoryOrderModuleList,0x1c存放InInitializationOrderModuleList
  5. 利用InMemoryOrderModuleList 等结构体获取链表节点,InMemoryOrderModuleList第二个节点是ntdll.dll,第三个是kernel32.dll。找到链表结构后,当前链表(相对于链表首地址偏移46个字节位之后)便宜44 0xf字节位之后就是基地址。
  6. 获取kernel32.dll的基地址之后,在偏移0x3c的地方就是其PE头。
  7. PE头偏移0x78的地方存放着指向函数导出表的指针。
  8. 导出表0x1c处的指针指向存储导出函数,函数名的列表,函数的RVA地址和名字按照顺序存放在上述两个列表中,可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA。
  9. 获得RVA后,再加上前面已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址。

在这里插入图片描述

在 Vista 之前的 Windows 版本中,InInitializationOrderModuleList 中的前两个DLL是 ntdll.dll和kernel32.dll,但对于 Vista 及以后的版本,第二个DLL更改为kernelbase.dll。
InMemoryOrderModuleList 中的第一个 calc.exe(可执行文件),第二个是ntdll.dll,第三个是kernel32.dll,目前这适用于所有 Windows 版本是首选方法。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

PEB及LDR链

https://www.cnblogs.com/bokernb/p/6404795.html
https://blog.csdn.net/iamsongyu/article/details/101451588

shellcode 例子

一个可以弹框的shellcode例子:

_asm
    {       // 将要调用的函数hash值入栈保存
            CLD                             // 清空标志位DF
            push 0x1e380a6a                    // 压入 MessageBoxA 字符串的hash
            push 0x4fd18963                    // 压入 ExitProcess 字符串的hash
            push 0x0c917432                 // 压入 LoadLibraryA 字符串的hash
            mov esi, esp                    // 指向栈中存放LoadLibraryA的 hash 地址
            lea edi, [esi - 0xc]            // 用于存放后边找到的 三个函数地址
            // 开辟0x400大小的栈空间
            xor ebx, ebx
            mov bh, 0x04
            sub esp, ebx
            // 将user32.dll入栈
            mov bx, 0x3233
            push ebx                        // 压入字符'32'
            push 0x72657375                 // 压入字符 'user'
            push esp
            xor edx, edx
            // 查找 kernel32.dll 的基地址
            mov ebx, fs:[edx + 0x30]        // FS得到当前线程环境块TEB TEB+0x30 是进程环境块 PEB
            mov ecx, [ebx + 0x0c]            // PEB+0x0c 是PEB_LDR_DATA结构体指针 存放这已经被进程加载的动态链接库的信息
            mov ecx, [ecx + 0x1c]            // PEB_LDR_DATA+0x1c 指向模块初始化链表的头指针 InInitalizationOrderModuleList
            mov ecx, [ecx]                    // 进入链表第一个就是ntdll.dll
            mov ebp, [ecx + 0x08]            // ebp 即kernel32.dll基地址
            
            // 与 hash 的查找相关
        find_lib_funcs :
            lodsd                            // 将[esi]中的4字节 传到eax中
            cmp eax, 0x1e380a6a             // 比较 MessageBoxA 字符串的hash值
            jne find_funcs                  // 如果不相等则继续查找
            xchg eax, ebp                    // 记录当前hash值
            call[edi - 0x8]
            xchg eax, ebp                    // 还原当前hash值 并且把exa基地址更新为 user32.dll的基地址
        
            // 在PE文件中查找相应的API函数
        find_funcs :
            pushad                            // 保存寄存器环境
            mov eax, [ebp + 0x3c]            // 指向PE头
            mov ecx, [ebp + eax + 0x78]        // 得到导出表的指针
            add ecx, ebp                    // 得到导出函数表内存虚拟地址(VA)
            mov ebx, [ecx + 0x20]            // 得到导出函数名称表(RVA)
            add ebx, ebp                    // 得到导出函数名称表内存虚拟地址(VA)
            xor edi, edi                    // 初始化计数器
            // 循环读取导出表函数
        next_func_loop :
            inc edi                            // 函数计数器+1
            mov esi, [ebx + edi * 4]        // 得到 当前函数名的地址(RVA)
            add esi, ebp                    // 得到 当前函数名的内存虚拟地址(VA)
            cdq;                            
                                            
            // 计算hash值
        hash_loop:                            // 循环得到当前函数名的hash
            movsx eax, byte ptr[esi]        // 得到当前函数名称 第esi的一个字母
            cmp al, ah                        // 比较到达函数名最后的0没有
            jz compare_hash                    // 函数名hash 计算完毕后跳到 下一个流程
            ror edx, 7                        // 循环右移7位
            add edx, eax                    // 累加得到hash
            inc esi                            // 计数+1 得到函数名的下一个字母
            jmp hash_loop                    // 循环跳到 hash_loop
            // hash值的比较
        compare_hash :                        
            cmp edx, [esp + 0x1c]            // 比较 目标函数名hash 和 当前函数名的hash
            jnz next_func_loop                // 如果 不等于 继续下一个函数名
            mov ebx, [ecx + 0x24]            // 得到 PE导出表中的 函数序号列表的 相对位置
            add ebx, ebp                    // 得到 PE导出表中的 函数序号列表的 绝对位置
            mov di, [ebx + 2 * edi]            // 得到 PE导出表中的 当前函数的序号
            mov ebx, [ecx + 0x1c]            // 得到 PE导出表中的 函数地址列表的 相对位置
            add ebx, ebp                    // 得到 PE导出表中的 函数地址列表的 绝对位置
            add ebp, [ebx + 4 * edi]        // 得到 PE导出表中的 当前函数的绝对地址
                                            // 循环依次得到kernel32.dll中的 LoadLibraryA  ExitProcess
                                            // 和user32.dll中的 MessageBoxA
            xchg eax, ebp                    // 把函数地址放入eax中
            pop edi                            // pushad中最后一个压入的是edi 正好是开始预留 用于存放的三个函数地址 的栈空间
            stosd                            // 把找到函数地址出入 edi对应的栈空间
            push edi                        // 继续压栈 平衡栈
            popad                            // 还原环境
            cmp eax, 0x1e380a6a                // 比较是否是 MessageBoxA 函数 如果是说明全部函数已经找齐 可以调用函数执行功能
            jne find_lib_funcs
            
            // 下方的代码,就是弹窗
        func_call :            
            xor ebx,ebx        // 将 ebx 清0
            push ebx
            push 0x20343332    // 注意数据大小端问题
            push 0x3170646D // 标题“mdp1234”
            mov eax,esp        // 把标题赋值给 eax
            push ebx        
            push 0x2020206f    // 再push一个hello当做内容
            push 0x6c6c6568 // push的时候不够了要用0x20填充为空
            mov ecx,esp        // 把内容 hello 赋值给 ecx
            
            // 下面就是将MessageBox的参数压栈
            push ebx        // messageBox 第四个参数
            push eax        // messageBox 第三个参数
            push ecx        // messageBox 第二个参数
            push ebx        // messageBox 第一个参数
            
            call[edi - 0x04]                // 调用    MessageBoxA
            push ebx
            call[edi - 0x08]                // 调用 ExitProcess
            nop
            nop
            nop
            nop
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c    模块加载顺序



 LIST_ENTRY InMemoryOrderModuleList; // +0x14   模块在内存中的顺序



 LIST_ENTRY InInitializationOrderModuleList;// +0x1c   模块初始化装载顺序
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
其中的每个link指针指向一个LDR_DATA_TABLE_ENTRY结构体

typedef struct _LDR_DATA_TABLE_ENTRY
{
     LIST_ENTRY InLoadOrderLinks;
     LIST_ENTRY InMemoryOrderLinks;
     LIST_ENTRY InInitializationOrderLinks;
     PVOID DllBase;
     PVOID EntryPoint;
     ULONG SizeOfImage;
     UNICODE_STRING FullDllName;
     UNICODE_STRING BaseDllName;
     ULONG Flags;
     WORD LoadCount;
     WORD TlsIndex;
     union
     {
          LIST_ENTRY HashLinks;
          struct
          {
               PVOID SectionPointer;
               ULONG CheckSum;
          };
     };
     union
     {
          ULONG TimeDateStamp;
          PVOID LoadedImports;
     };
     _ACTIVATION_CONTEXT * EntryPointActivationContext;
     PVOID PatchInformation;
     LIST_ENTRY ForwarderLinks;
     LIST_ENTRY ServiceTagLinks;
     LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

自己做了一个潦草的图:

在这里插入图片描述

每一个_LDR_DATA_TABLE_ENTRY都是加载的module的描述。

欢迎各位大佬一起来免费知识星球学习: 一起探究安全原理,探索更多攻击方法。
https://t.zsxq.com/2rjA6yn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值