本人学习《Windows黑客编程技术详解》所做的学习笔记
- 简介:远线程注入即,一个进程在目标进程中创建一个线程来执行注入操作。
- 大致思路:自身进程权限提升->打开目标进程->在目标进程虚拟地址空间分配内存->将数据写入内存->创建线程并执行注入
1,自身进程权限提升
原因及目的:在枚举访问系统进程中,会出现权限不足的情况,于是这个时候我们便需要去调用函数提升自身权限
GetCurrentProcess()/** 获取当前进程句柄**/
这时获取了进程句柄后,我们要将进程的权限设置为SE_DEBUG_NAME
接下来我们将要调用OpenProcessToken,LookupPrivilegeValue,AdjustTokenPrivileges三个WINDOW API函数进行提权
1, OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken); 得到进程的令牌句柄
TOKEN_ADJUST_PRIVILEGES 修改令牌的访问权限
BOOL OpenProcessToken(
HANDLE ProcessHandle, //进程句柄
DWORD DesiredAccess, //操作类型
PHANDLE TokenHandle //访问令牌的指针
);
2, LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue); 查看TOKEN获取LUID
BOOL WINAPI LookupPrivilegeValue(
__in_opt LPCTSTR lpSystemName, //系统名称,本系统就NULL
__in LPCTSTR lpName, //查看的特权名字
__out PLUID lpLuid //返回的LUID
);
3, AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL); 修改进程权限
BOOL AdjustTokenPrivileges(
HANDLE TokenHandle, //令牌句柄
BOOL DisableAllPrivileges, //决定是进行权限修改还是除能(Disable)所有权限
PTOKEN_PRIVILEGES NewState, //指明要修改的权限
DWORD BufferLength, //PreviousState的长度,PreviousState为空,该参数应为NULL
PTOKEN_PRIVILEGES PreviousState, //指向TOKEN_PRIVILEGES结构的指针,存放修改前访问权限的信息,可空;
PDWORD ReturnLength //实际PreviousState结构返回的大小
);
typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount; //数组长度
LUID_AND_ATTRIBUTES Privileges[];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid; //权限的类型,是一个LUID的值
DWORD Attributes;//进行的操作类型,有三个可选项: SE_PRIVILEGE_ENABLED、SE_PRIVILEGE_ENABLED_BY_DEFAULT、SE_PRIVILEGE_USED_FOR_ACCESS
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES
完整提取代码
//头文件
EnbalePrivileges(::GetCurrentProcess(), SE_DEBUG_NAME);
BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName)
{
HANDLE hToken = NULL;
LUID luidValue = { 0 };
TOKEN_PRIVILEGES tokenPrivileges = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;
// 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
if (FALSE == bRet)
{
std::cout << "OpenProcessToken";
return FALSE;
}
// 获取本地系统的 pszPrivilegesName 特权的LUID值
bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
if (FALSE == bRet)
{
std::cout<< "LookupPrivilegeValue";
return FALSE;
}
// 设置提升权限信息
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Luid = luidValue;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// 提升进程令牌访问权限
bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
if (FALSE == bRet)
{
std::cout<<"AdjustTokenPrivileges";
return FALSE;
}
else
{
// 根据错误码判断是否特权都设置成功
dwRet = ::GetLastError();
if (ERROR_SUCCESS == dwRet)
{
return TRUE;
}
else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
{
std::cout<<"ERROR_NOT_ALL_ASSIGNED";
return FALSE;
}
}
return FALSE;
}
2, 远线程注入操作
在实现完进程提权后,我们就可以编写线程注入代码了。
接下来我们将要调用OpenProcess,VirtualAllocEx,WriteProcessMemory,GetProcAddress,CreateRemoteThread 五个Windows API函数进行注入
远线程注入的原理
最重要的既是CreateRemoteThread函数,他获取目标进程空间中的多线程函数地址以及多线程参数,来创建一个新的线程并调用LoadLibrary函数
我们要怎么获取目标函数地址呢?在进程加载DLL时会要调用一个LoadLibrary的函数,而函数参数只有一个即DLL的路径字符串,即使有ASLR基址随机化回随机DLL的加载基址,但是在系统kernel32.dll的加载基址基本上是不同的,所以函数地址不会改变,这样我们就可以通过CreateRemoteThread函数获取LoadLibrary的函数地址。
通过VirtualAllocEx在目标进程中分配虚拟内存,WriteProcessMemory将我们要注入的DLL路径写入进去即可实现远线程DLL注入!
1, OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId) 打开本地现有进程
HANDLE WINAPI OpenProcess{
_In_ DWORD dwDesiredAccess, //此参数代表着进程访问权限
_In_ BOOL bInheritHandle, //为True则此进程创建的进程将继承该句柄
_In_ DWORD dwProcessId, //打开本地进程PID
}
2, VirtualAllocEx(PROCESS_ALL_ACCESS, FALSE, dwProcessId) 在指定进程的虚拟地址空间中保留,提交,更改内存状态
句柄必须拥有PROCESS_VM_OPERATION权限
LPVOID WINAPI VirtualAllocEx{
_In_ HANDLE hProcess, //句柄
_In_opt_ LPVOID lpAddress, //分配页面所需的起始地址,若为NULL则自动分配
_In_ SIZE_T dwSize, //大小
_In_ DWORD flAllocationType,//内存放分配的类型
_In_ DWORD flProtect //要分配页面区域的内存保护
}
3, WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))将数据写入进程
4,CreateRemoteThread(hProcess, NULL, 0,(LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
HANDLE CreateRemoteThread(
HANDLE hProcess, // 创建线程的进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 指定新线程的安全描述符
SIZE_T dwStackSize, // 堆栈大小,0则为新线程使用可执行文件的默认大小
LPTHREAD_START_ROUTINE lpStartAddress, // 函数地址,该函数必须在该进程中存在
LPVOID lpParameter, // 参数,变量
DWORD dwCreationFlags, // 控制线程创建的标准,若为0,则表示线程创建后立即执行
LPDWORD lpThreadId // 指向接受线程标识符的变量指针
);
完整代码
// 使用 CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjectDll(DWORD dwProcessId,const char *pszDllFileName)
{
HANDLE hProcess = NULL;
SIZE_T dwSize = 0;
LPVOID pDllAddr = NULL;
FARPROC pFuncProcAddr = NULL;
// 打开注入进程,获取进程句柄
hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == hProcess)
{
std::cout<<"OpenProcess" << std::endl;
return FALSE;
}
// 在注入进程中申请内存
dwSize = 1 + ::lstrlen(pszDllFileName);
pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL == pDllAddr)
{
std::cout<<"VirtualAllocEx" << std::endl;
return FALSE;
}
// 向申请的内存中写入数据
if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
{
std::cout<<"WriteProcessMemory" << std::endl;
return FALSE;
}
// 获取LoadLibraryA函数地址
pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr)
{
std::cout<<"GetProcAddress_LoadLibraryA"<< std::endl;
return FALSE;
}
// 使用 CreateRemoteThread 创建远线程, 实现 DLL 注入
HANDLE hRemoteThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
if (NULL == hRemoteThread)
{
std::cout<<"CreateRemoteThread"<< std::endl;
return FALSE;
}
std::cout << hRemoteThread;
// 关闭句柄
::CloseHandle(hProcess);
return TRUE;
}
3, 学习中出现的问题
上述工作完成之后,即可选择dll以及进程进行注入了。
提权失败
问题原因:在提权过程中呢,会出现提权失败的过程,这是因为在Win7系统中,分配给普通用户的权利变少了,无法进行DEBUG,只有Admin才是真正的老大
问题解决:以管理员的身份进行运行才能提权成功
系统进程注入不成功
问题原因:由于系统进程的SESSION 0 会话隔离的安全机制,这种注入并不能突破SESSION 0隔离
问题解决:SESSION 0隔离的远线程注入,还在学。。。