Sandboxie循序渐进
2021-01-05 18:58:00 Author: mp.weixin.qq.com(查看原文) 阅读量:111 收藏

本文为看雪论优秀文章

看雪论坛作者ID:一半人生

Sandboxie注入方式回调处理数据发送至SbieSvc,Svc初始化Ldr,监控篇上描述了流程,下面分享一下处理细节。
Sandboxie并未直接再回调中处理,而是发送LPC注入信号至SbieSvC来完成,等待事件。
 
1. LowLevel是DLL项目,DriverAssist::InjectLow_InitHelper(ULONG *errlvl)初始化Helper将会获取资源DLL,保存节表,获取LdrInitializeThunk函数地址。
HRSRC hrsrc = FindResource(NULL, L"LOWLEVEL", RT_RCDATA); if (! hrsrc) return false; ULONG binsize = SizeofResource(NULL, hrsrc); if (! binsize) return false; HGLOBAL hglob = LoadResource(NULL, hrsrc);..................section = IMAGE_FIRST_SECTION(nt_hdrs);.................. m_LdrInitializeThunk = (ULONG_PTR) GetProcAddress(_Ntdll, "LdrInitializeThunk"); if (! m_LdrInitializeThunk) return false;x64 add#ifdef _WIN64 if (Dll_Windows >= 10) { unsigned char * code; code = (unsigned char *)m_LdrInitializeThunk; if (*(ULONG *)&code[0] == 0x24048b48 && code[0xa] == 0x48) { m_LdrInitializeThunk += 0xa; }}
2. InjectLow_InitHelper负责加载Lowlevel.dll数据初始化ldr函数地址。
DriverAssist::InjectLow_InitSyscalls()负责初始化sys_datavoid DriverAssist::InjectLow(void *_msg)是SvcInjectEntry入口点,填充lowdata结构体,Lowdata结构体包括下述 SBIELOW_DATA lowdata; memzero(&lowdata, sizeof(lowdata)); lowdata.ntdll_base = (ULONG64)(ULONG_PTR)_Ntdll; lowdata.is_wow64 = msg->is_wow64; lowdata.bHostInject = msg->bHostInject; lowdata.RealNtDeviceIoControlFile = (ULONG64) GetProcAddress((HMODULE) lowdata.ntdll_base,"NtDeviceIoControlFile"); void *remote_addr = InjectLow_CopyCode(hProcess, lowdata.is_wow64, lowdata.LdrInitializeThunk_tramp, sizeof(lowdata.LdrInitializeThunk_tramp)); if (!remote_addr) { errlvl = 0x33; goto finish; }

3. InjectLow_CopyCode负责填充lowdata.IdrInitalizeThunK_tramp。
lowdata.long_diff = TRUE; if (Has32BitJumpHorizon((void *)m_LdrInitializeThunk, remote_addr)) { lowdata.long_diff = FALSE; }#else lowdata.long_diff = FALSE;#endif1.InjectLow_SendHandle将SbieDrv句柄拷贝到目标进程 lowdata.api_device_handle = (ULONG64)(ULONG_PTR) InjectLow_SendHandle(hProcess); if (! lowdata.api_device_handle) { errlvl = 0x22; goto finish; } lowdata.api_sbiedrv_ctlcode = API_SBIEDRV_CTLCODE; lowdata.api_invoke_syscall = API_INVOKE_SYSCALL; memcpy(lowdata.NtDelayExecution_code, &m_syscall_data[2], (32 * 4));

4. tramp_remote_addr构造
#ifdef _WIN64 lowdata.Sbie64bitJumpTable = (SBIELOW_J_TABLE *) ((ULONG_PTR) remote_addr +m_sbielow_len+0x400); //(0x400 - (m_sbielow_len & 0x3ff))+ m_sbielow_len;#endif ULONG_PTR tramp_remote_addr = // calculate address in remote process (ULONG_PTR)remote_addr + m_sbielow_data_offset // offset of args area + FIELD_OFFSET(SBIELOW_DATA, LdrInitializeThunk_tramp);

5. LowDLl充当执行的shellcode,data_offset是初始化时候代码如下,这是获取的’zzzz’节表的入口点和数据大小:
targets = (MY_TARGETS *)& bindata[section[1].PointerToRawData]; m_sbielow_start_offset = (ULONG)targets->entry - section[0].VirtualAddress; m_sbielow_data_offset = (ULONG)targets->data - section[0].VirtualAddress;

6. 绑定
if (! InjectLow_BuildTramp(_msg, lowdata.long_diff, lowdata.LdrInitializeThunk_tramp, tramp_remote_addr)) { errlvl = 0x44; goto finish; } // // copy the syscall data buffer (m_syscall_data) to target process

7. 拷贝系统数据缓冲到目标进程
void *remote_syscall_data = InjectLow_CopySyscalls(hProcess); if (! remote_syscall_data) { errlvl = 0x55; goto finish; } lowdata.syscall_data = (ULONG64)(ULONG_PTR)remote_syscall_data;

8. 将低数据参数区域(包括转换后的蹦床代码)写入目标进程,并使其执行读取。
if (! InjectLow_CopyData(hProcess, remote_addr, &lowdata)) { errlvl = 0x66; goto finish; }

9. 覆盖LdrInitializeThunk的顶部以跳转到注入的代码,请注意,我们必须跳过8字节签名(.HEAD.00)删除了对(.HEAD.00)的硬编码依赖性。不再需要在remote_addr中添加8。
if (!InjectLow_WriteJump(hProcess, (UCHAR *)remote_addr + m_sbielow_start_offset, lowdata.long_diff, &lowdata)) { errlvl = 0x77; goto finish; } // // put process into a job for win32 restrictions //2. if (!msg->bHostInject) { if(! GuiServer::GetInstance()->InitProcess( hProcess, msg->process_id, msg->session_id, msg->add_to_job)) { errlvl = 0x88; goto finish; }} // // notify driver that we successfully injected the lowlevel code //if (SbieApi_CallOne(API_INJECT_COMPLETE, msg->process_id) == 0)

10. 上述过程描述细节狠清楚,利用这种方式实现了注入:
Sandboxie_HOOK_r3模板:
#define ANTSDLL_HOOK(pfx,proc) \ *(ULONG_PTR *)&__sys_##proc = (ULONG_PTR) \ ANTSDLL_Hook(#proc, proc, pfx##proc); \if (! __sys_##proc) return FALSE; #define GETPROCADDR_DEF(name) \ 获取函数地址 P_##name name = (P_##name) GetProcAddress(module, #name)
通用HOOK宏定义,入口SBIEDLL_HOOK函数实现,三要素:函数名/函数地址/HOOK函数地址。
_FX void *ANTSDLL_Hook(const char *SourceFuncName, void *SourceFunc, void *DetourFunc用法:GETPROCADDR_DEF(CoGetClassObject);ANTSDLL_HOOK(Com_, CoGetClassObject);

r3的就不多说了,基于三要素简答方便管理一些,ANTSDLL_Hook返回的是原函数地址。
_FX void *ANTSDLL_Hook(const char *SourceFuncName, void *SourceFunc, void *DetourFunc){UCHAR *tramp, *func;ULONG_PTR diff;ULONG_PTR target;ULONG prot, dummy_prot;void *orig_addr;#ifdef _WIN64long long delta;BOOLEAN CallInstruction64 = FALSE;#endif _WIN64 // validate parameter - 验证参数if (!SourceFunc) {return NULL;} // jmp xx 共两个字节if (*(UCHAR *)SourceFunc == 0xEB) {signed char offset = *((signed char *)SourceFunc + 1);SourceFunc = (UCHAR *)SourceFunc + offset + 2;} // jmp xx xx xx xx 后面 jmp current + diff + 5while (*(UCHAR *)SourceFunc == 0xE9) { diff = *(LONG *)((ULONG_PTR)SourceFunc + 1);target = (ULONG_PTR)SourceFunc + diff + 5;if (target == (ULONG_PTR)DetourFunc) {return NULL;} #ifdef _WIN64 SourceFunc = (void *)target; #else ! WIN_64 func = SbieDll_Hook_CheckChromeHook((void *)target);if (func != (void *)target) {SourceFunc = func;goto skip_e9_rewrite;} func = (UCHAR *)SourceFunc;diff = (UCHAR *)DetourFunc - (func + 5);++func;if (!VirtualProtect(func, 4, PAGE_EXECUTE_READWRITE, &prot)) {ULONG err = GetLastError();return NULL;}*(ULONG *)func = (ULONG)diff;VirtualProtect(func, 4, prot, &dummy_prot); return (void *)target; skip_e9_rewrite:; #endif _WIN64} #ifdef _WIN64// avast.snxhk64.dll compatibility nop+jmp (90,E9)if (*(USHORT *)SourceFunc == 0xE990) {diff = *(LONG *)((ULONG_PTR)SourceFunc + 2);target = (ULONG_PTR)SourceFunc + diff + 6;if (*(USHORT *)target == 0x25FF)SourceFunc = (void *)target;} // 如果 [x] 只替换地址// 0xff25 四个字节地址if(*(USHORT *)SourceFunc == 0x48 && *(USHORT *)((UCHAR *)SourceFunc) == 0x25FF) {// 4825ff and 25ff 一样 所以和ff25一起处理SourceFunc = (UCHAR *)SourceFunc + 1;} if (*(USHORT *)SourceFunc == 0x25FF) { orig_addr = NULL; // 首先取地址 +2是0xff25diff = *(LONG *)((ULONG_PTR)SourceFunc + 2); // jmp qword ptr + 地址 jmp到目标地址判断是否等于我们的挂钩函数target = (ULONG_PTR)SourceFunc + 6 + diff;orig_addr = (void *)*(ULONG_PTR *)target;if (orig_addr == DetourFunc) {return NULL;} func = (UCHAR *)target;if (!VirtualProtect(func, 8, PAGE_EXECUTE_READWRITE, &prot)) {ULONG err = GetLastError();return NULL;}// 进行挂钩HOOK*(ULONG_PTR *)target = (ULONG_PTR)DetourFunc;VirtualProtect(func, 8, prot, &dummy_prot); return orig_addr;} #endif _WIN64 #ifdef _WIN64// call qword ptr [0xaddress]if (*(USHORT *)SourceFunc == 0x15FF) { //// the call instruction pushes a qword into the stack, we need// to remove this qword before calling our detour function// UCHAR *NewDetour = (UCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 128); NewDetour[0] = 0x58; // pop raxNewDetour[1] = 0x48; // mov rax, DetourFuncNewDetour[2] = 0xB8;*(ULONG_PTR *)(&NewDetour[3]) = (ULONG_PTR)DetourFunc;NewDetour[11] = 0xFF; // jmp raxNewDetour[12] = 0xE0; DetourFunc = NewDetour; //// when our detour function calls the trampoline to invoke the// original code, we have to push the qword back into the stack,// because this is what the original code expects// NewDetour[16] = 0x48; // mov rax, SourceFunc+6NewDetour[17] = 0xB8;*(ULONG_PTR *)(&NewDetour[18]) = (ULONG_PTR)SourceFunc + 6;NewDetour[26] = 0x50; // push raxNewDetour[27] = 0x48; // mov rax, trampoline codeNewDetour[28] = 0xB8;*(ULONG_PTR *)(&NewDetour[29]) = 0;NewDetour[37] = 0xFF; // jmp raxNewDetour[38] = 0xE0; CallInstruction64 = TRUE; //// overwrite the code at the target of the call instruction// diff = *(LONG *)((ULONG_PTR)SourceFunc + 2);target = (ULONG_PTR)SourceFunc + 6 + diff;SourceFunc = (void *)*(ULONG_PTR *)target;// if is 0xff15, SourceFunc = jmp [current + offset + 6]} #endif _WIN64/*1. 0xFF25 jmp qword ptr xx xx xx xx R3进行inlinehook直接返回2. EB/E9/FF15/驱动挂钩处理3. 貌似没对E8做处理?*/// 调用驱动创建tramp __declspec(align(8)) ULONG64 parms[8];//tramp = (UCHAR*)VirtualAlloc(NULL, 128, MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);tramp = (UCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 128);//memset(tramp, 0, 128); parms[0] = 0;parms[1] = (ULONG64)(ULONG_PTR)SourceFunc;parms[2] = (ULONG64)(ULONG_PTR)tramp;Hook_Api_Tramp(parms); // NtDeviceIoControlFile --> API_HOOK_TRAMP --> \\Device\\SandboxieDriverApi 处理tramp//// drv.ApiInit() --> 初始化API_DEVICE_NAME端口 Api_SetFunction(API_HOOK_TRAMP, Hook_Api_Tramp);//// Hook_Api_Tramp() --> Hook_BuildTramp() 进行HOOK挂钩//// 先利用原dll导出函数// if (SbieApi_HookTramp(SourceFunc, tramp) != 0) {// return NULL;//} func = (UCHAR *)SourceFunc; if (!VirtualProtect(&func[-8], 20, PAGE_EXECUTE_READWRITE, &prot)) { ULONG err = GetLastError();return NULL;}

代码中有注释,主要对不同的机器码-call/jmp分别做了挂钩处理,对于跳转过去直接是汇编代码Hook,先申请一段内存,拷贝前几行指令到内存,Systemcall从申请的内存地址执行,跳转到挂钩的下一行即可。
DriverEntry:
 
1) Driver_CheckOsVersion
版本兼容,代码中对不同型号版本结构偏移纪录。
g_TrapFrameOffset = 0x90 and g_TrapFrameOffset = 0x1d8

2) Pool_Create
Pool_Alloc_Page封装了ExAllocatePoolWithTag申请内存池,最初申请了大小4096的内存池。
struct PAGE { LIST_ELEM list_elem; PAGE *next; POOL *pool; ULONG eyecatcher; USHORT num_free; // estimated, not accurate};

链表操作请看List.c文件,自实现的链表增删改查功能,这里主要是为页面bitmap初始化内存池。
 
3) Driver_InitPublicSecurity
代码初始化了Acl,申请128大小内存,初始化ACL,创建安全描述符,创建不同属性的安全描述符
Driver_PublicAcl = Mem_AllocEx(Driver_Pool, 128, TRUE); if (! Driver_PublicAcl) return FALSE; RtlCreateAcl(Driver_PublicAcl, 128, ACL_REVISION); static UCHAR AuthSid[12] = { 1, // Revision 1, // SubAuthorityCount 0,0,0,0,0,5, // SECURITY_NT_AUTHORITY // IdentifierAuthority SECURITY_AUTHENTICATED_USER_RID // SubAuthority }; static UCHAR WorldSid[12] = { 1, // Revision 1, // SubAuthorityCount 0,0,0,0,0,1, // SECURITY_WORLD_SID_AUTHORITY // IdentifierAuthority SECURITY_WORLD_RID // SubAuthority };

保存路径注册键值:
if (ok) { Driver_RegistryPath = Mem_AllocStringEx(Driver_Pool, RegistryPath->Buffer, TRUE); if (! Driver_RegistryPath) ok = FALSE; }

注册表获取路径,ObReferenceObjectByHandle获取FILE_OBJECT对象,查询标准路径,DOS/NT之间转换(obj.c里面实现了具体转换路径过程,包含了不同版本)。
4) Obj_Init()
初始化ObQueryNameInfo/ObGetObjectType函数地址。
 
5) Conf_Init()
Mem_GetLockResource申请内存,初始化资源变量。Conf_Init_User初始化事件EvEnt,设置API函数如下:
Api_SetFunction(API_SET_USER_NAME, Conf_Api_SetUserName);Api_SetFunction(API_IS_BOX_ENABLED, Conf_Api_IsBoxEnabled);

驱动初始化过程,还未安装SbieSvc服务,只是插入到对应的List中,等待SBieSvc启动调用。
DriverCoDe:API_SET_USER_NAME Api必须由sandboxie调用,做判断是否sandboxiepid进程调用:
if (proc || (PsGetCurrentProcessId() != Api_ServiceProcessId)) { return STATUS_ACCESS_DENIED;}DriverCode:API_IS_BOX_ENABLEDConf_Read()

IO系列函数创建Sandboxie.ini,读取Templates.ini,Conf_Read_Sections()检测参数调用Conf_Read_Settings()读取文件填充结构。
DLL_Init() List_Init(&Dll_List); Dll_List_Initialized = TRUE; if (! Dll_Load(Dll_NTDLL)) return FALSE; if (! Dll_Load(Dll_USER)) return FALSE; return TRUE;


1. 初始化NtDll和User32,定义了全局的static LIST Dll_List,初始化的DLL将会被链表全局记录。Dll_Load判断是否已初始化返回指针,反之Map映射。
ZwCreateFile(......,FILE_GENERIC_READ, \\SystemRoot\\System32\\xxx.dll,...);
 
ZwQueryInformationFile --> ZwCreateSection --> ZwMapViewOfSection
2. 获取DOS头和NT头,判断x64还是x32分别填充IMAGE_NT_HEADERS(32|64)结构体,保存DLL的Nt,入口点(IMageBase),大小(SizeOfImage),DataDirectory地址。Dll_RvaToAddr转换导出表地址,保存至dll->exports,插入DLL链表。
6) Syscall_Init();
全局链表
static LIST Syscall_List;static SYSCALL_ENTRY **Syscall_Table = NULL;static ULONG Syscall_MaxIndex = 0;static UCHAR *Syscall_NtdllSavedCode = NULL;static SYSCALL_ENTRY *Syscall_SetInformationThread = NULL;
Syscall_Init_List()
初始化全局List,Syscall_List.从全局DLL_List链表中获取NTDll.dll(Zw系列函数),通过Dll_GetNextProc来获取导出表的 IAT-IOD-INT,保存序号表索引和函数名。
ULONG *names = Dll_RvaToAddr(dll, dll->exports->AddressOfNames);ULONG *addrs = Dll_RvaToAddr(dll, dll->exports->AddressOfFunctions);USHORT *ordis = Dll_RvaToAddr(dll, dll->exports->AddressOfNameOrdinals);
 
过滤了特殊的函数如下:

分析每一个Zw导出找到索引,SySCall_GetKernelAddr(),关注点如何获取SSDT基地址,Sandboxie定位SSDT代码再Syscall_GetServiceTable()函数中:
if (syscall_index != -1) { Syscall_GetKernelAddr( syscall_index, &ntos_addr, ¶m_count); }

如果有导出函数可以使用,最为方便导出KeServiceDscriptorTable即可。
Sandboxie中Windows10寻找SSDT方式如下:
1. 查询获取MODULE_INFO对象,获取kernel_base:
2. 获取KeAddSystemServiceTable地址
3. 最后插入Syscall_List链表
Syscall_Init_Table()
 
7) Driver_FindMissingServices()
初始化session相关回调:
Api_SetFunction(API_SESSION_LEADER, Session_Api_Leader); Api_SetFunction(API_DISABLE_FORCE_PROCESS, Session_Api_DisableForce); Api_SetFunction(API_MONITOR_CONTROL, Session_Api_MonitorControl); Api_SetFunction(API_MONITOR_PUT, Session_Api_MonitorPut); Api_SetFunction(API_MONITOR_PUT2, Session_Api_MonitorPut2); Api_SetFunction(API_MONITOR_GET, Session_Api_MonitorGet);

8) Driver_FindMissingServices()
获取ZwSetInformationTOken,FIND_SEVICE进行Hook,后面在对Hook进行分析:

9) Process_Init()
PsSetCreateProcessNotifyRoutine&PsSetLoadImageNotifyRoutine
1. Process_NotifyProcess:
PspCreateThread中处理Process,Sandboxi回调初始化LdrInitializeThunk注入。具体代码可以看:
Process_NotifyImage:是在DbgKCreateThreaD中处理,负责Sandboxie空间初始化,有一个概念,它属于某一个沙盘内的进程。
if (!proc->bHostInject) { if (ok) ok = File_CreateBoxPath(proc); if (ok) ok = Ipc_CreateBoxPath(proc); if (ok) ok = Key_MountHive(proc); // // initialize the filtering components // if (ok) ok = File_InitProcess(proc); if (ok) ok = Key_InitProcess(proc); if (ok) ok = Ipc_InitProcess(proc); if (ok) ok = Gui_InitProcess(proc); if (ok) ok = Process_Low_InitConsole(proc); if (ok) ok = Token_ReplacePrimary(proc); if (ok) ok = Thread_InitProcess(proc);}

File_CreateBoxPath()
初始化路径:
RtlInitUnicodeString(&objname, proc->box->file_path); status = ZwCreateFile( &handle, FILE_GENERIC_READ | FILE_WRITE_ATTRIBUTES, &objattrs, &IoStatusBlock, NULL, // AllocationSize 0, // FileAttributes FILE_SHARE_VALID_FLAGS, // ShareAccess FILE_OPEN_IF, // CreateDisposition FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); // EaBuffer, EaLengthIpc_CreateBoxPath(proc)

循环创建对象:
while (retries < 64) { ++retries; RtlInitUnicodeString(&objname, proc->box->ipc_path); status = ZwCreateDirectoryObject( &handle, DIRECTORY_ALL_ACCESS, &objattrs); if (status == STATUS_OBJECT_PATH_NOT_FOUND) {
Key_Init()initialize the filtering components初始化过滤接口File_InitProcess()10) Thread_Init11) File_Init12) Key_Init13) Ipc_Init14) Gui_Init15) Api_Init16) Dll_Unload

- End -

看雪ID:一半人生

https://bbs.pediy.com/user-home-819685.htm

  *本文由看雪论坛 一半人生 原创,转载请注明来自看雪社区。

# 往期推荐

公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458378917&idx=1&sn=533008b3fc3dd17add05e5e00c3a8668&chksm=b180d22f86f75b3900410e401c1b9fbb512164a7cacb2f95c395643cb65bb7f1dcee1518795b#rd
如有侵权请联系:admin#unsafe.sh