1。客户端申请挑战
安全BLOB的结构如下:
{
CHAR DESC[8]=“NTLMSSP”;
DWORD NO=1;
//顺序号
DWORD FLAG;
WORD DOMAINNAMELEN;
//域名长度
WORD DOMAINNAMEMAXLEN;//域名最大长度
DWORD DOMAINNAMEOFFSET;//域名便移
WORD HOSTNAMELEN;
//主机名长度
WORD HOSTNAMEMAXLEN;
//主机名最大长度
DWORD HOSTNAMEOFFSET;
//主机名便移
CHAR HOSTNAME[HOSTNAMELEN];//主机名
CHAR DOMAINNAME[DOMAINNAMELEN];//域名
}
2。服务器返回挑战,这个挑战是一个8字节的随机数
安全BLOB的结构如下:
{
CHAR DESC[8]=“NTLMSSP”;
DWORD NO=2;
//顺序号,必须为2
WORD DOMAINNAMELEN;
//帐户域名长度
WORD DOMAINNAMEMAXLEN;
//帐户域名最大长度
DWORD DOMAINNAMEOFFSET;////帐户域名偏移
DWORD FLAG;
BYTE CHALLAGE[8];
//8字节挑战
BYTE RE[8];
//保留8字节,必须为0
WORD HOSTNAMELISTLEN;
//主机名列表长度
WORD HOSTNAMELISTMAXLEN;//主机名列表最大长度
DWORD HOSTNAMELISTOFFSET;//主机名列表偏移
CHAR DOMAINNAME[DOMAINNAMELEN];
CHAR HOSTLISTNAME[HOSTNAMELISTLEN];
//这里是多个结构组成
//每个结构如下:
//WORD ITEMNO;索引
//WORD HOSTNAMELEN;主机名长度
//BYTE HOSTNAME[HOSTNAMELEN]:主机名
}
3。客户端加密密码散列
其实这个过程还是很复杂的
如果是明文口令,先将此口令转化成散列,如果是系统用户则直接根据令牌从SESSION表中取出散列。
这下面就是小四的文章中没提到的过程
客户端先生成一个随机KEY,然后和服务器端的挑战使用MD5算法生成16字节的散列,但MS只取前8字节做为加密KEY,其实后面的散列加密并非是使用挑战直接加密的,而是使用此加密KEY进行加密的。
这个算法的代码如下:romkey就是客户端生成的一个随机KEY,challage就是服务器挑战
void challagetorkey(unsigned char * romkey,unsigned char * challage,unsigned char * enkey)
{
unsigned char LM[0x58];
md5init(LM);
*(DWORD *)LM=0x200;
memcpy(LM+0X18,challage,8);
memcpy(LM+0X20,romkey,8);
*(DWORD *)(LM+0x28)=0x80;
*(DWORD *)(LM+0x50)=0x80;
md5final(LM);
memcpy(enkey,LM+8,8);
}
然后再使用生成的加密KEY加密MD4生成的NTLM散列,具体的过程在下四的文章中有详细介绍,这里就不再讲了,后面给出了完全实现的代码。
同时还会先选择一个RC4的KEY,对MD4生成的NTLM散列用MD4再生成一个散列,再用HMACMD5算法,加密KEY,这个散列计算出一个0X10字节的散列,用此散列和rc4_key函数生成rc4的key表,最后用这个KEY表和先选择的RC4的KEY生成一个0X10字节的RC的结果出来。
但要注意的是:通常情况下,这个RC4的结果并没用于认证。
安全BLOB的结构如下:
{
CHAR DESC[8]=“NTLMSSP”;
DWORD NO=3;
//顺序号,必须为3
WORD RANDOMKEYLEN;
//客户端随机KEY长度,固定为0X18
WORD RANDOMKEYMAXLEN;
//客户端随机KEY最大长度,固定为0X18
//其实客户端随机KEY只有8字节,但是后面补0添满0X18字节
DWORD RANDOMKEYOFFSET;////客户端随机KEY偏移
WORD ENNTLMLEN;
//加密的NTLM散列长度,固定为0X18
WORD ENNTLMMAXLEN;
//加密的NTLM散列最大长度,固定为0X18
DWORD ENNTLMOFFSET;
//加密的NTLM散列偏移
WORD HOSTNAMELEN;
//主机名长度
WORD HOSTNAMEMAXLEN;//主机名最大长度
DWORD HOSTNAMEOFFSET;//主机名偏移
WORD USERNAMELEN;
//用户名长度
WORD USERNAMEMAXLEN;//用户名最大长度
DWORD USERNAMEOFFSET;//用户名偏移
WORD USERDOMAINLEN;
//帐户域名长度
WORD USERDOMAINMAXLEN;//帐户域名最大长度
DWORD USERDOMAINOFFSET;//帐户域名偏移
WORD ENNTLMRC4LEN;
//加密的RC4 NTLM散列长度,固定为0X10
WORD ENNTLMRC4MAXLEN;
//加密的RC4 NTLM散列最大长度,固定为0X10
DWORD ENNTLMRC4OFFSET;
//加密的RC4 NTLM散列偏移
DWORD FLAG;
CHAR HOSTNAME[HOSTNAMELEN];//主机名
CHAR USERNAME[HOSTNAMELEN];//用户名
CHAR USERDOMAINNAME[HOSTNAMELEN];//帐户域名
RANDOMKEY[0X18];//其实客户端随机KEY只有8字节,但是后面补0添满0X18字节
ENNTLM[0X18];
ENRC4NTLM[0X10];
}
4。服务器认证
服务器获得客户端随即KEY以后,也和挑战运算获得此加密KEY,然后计算加密的NTLM散列做对比进行判断。
5。一些思考
主要是关于重放攻击的,其实真正的加密KEY是挑战和客户端随机KEY做MD5运算的结果,那么如果我们嗅探出了一个挑战,随机KEY就能计算出加密KEY,如果我们发起一个请求获得一个挑战2,如果能计算一个随即KEY2(这个可由我们自己控制),满足MD5(挑战2,随即KEY2)=加密KEY,那么我们就能发送我们修探获得的安全BLOB中的加密散列的内容来获得认证。
当然MD5的算法本事就是要防碰撞的,但是在MS的SMB实现中,由于MD5生成的是16字节的散列,但是用来做加密KEY的只是前8个字节,那么对于寻找只满足部分(前8字节)碰撞的算法复杂度就会大大的降低,当然我们在MD5中唯一能控制的东西就是随即KEY,但是挑战KEY已知,其实针对加密算发是常量(因为加密内容固定为0X1O字节,其他的内容可以常量),应该来说还是很有希望的。不过就我的数学水平而已,尝试了一下就被难到了,希望对MD5碰撞有研究的人给予指教。下面就是给出的一个针对其挑战,随即KEY到加密KEY的替代函数:
void challagetorkey(unsigned long c1,unsigned long c2,unsigned long r1,unsigned long r2)
{
//我们把挑战看做2个unsigned long的数C1,C2,客户端随即KEY也看做2个unsigned long的数R1,R2
//最后生成的是B1,D1,S1,D2,但其实用做加密KEY的只是B1,D1而已
//如果我们知道B1,D1,C1,C2,是否能找过一个R1,R2,使得满足这个函数呢?
//按照概率是针对每个B1,D1都有可能找到对应的R1,R2,因为MD5的算发要求是每个字节的变化都应该平均分布的
//我们可控的是256的8次方,结果也是256的8次方
//即使不是完全平均分布的,在测试100个挑战中总会有满足的吧?
//而算法复杂度对于16字节的256的16次方应该降低了不少
//那么再退一步说,如果我们知道B1,D1,C1,C2,R1,如果能解出R2也行,我们对R1实行穷举
//R1是256的4次方,4亿左右的穷举对现在的机器不应该是负担吧?当然真正实现工具由于认证要求,可能要求在10分钟以内
//但是如果我们在2天内能找到结果,也能说明很多问题
DWORD a1,d1,d2,s1;
DWORD b1;
b1=(((0x10325476^0x98badcfe)&0xefcdab89)^0x10325476)+c1;
b1=b1+0x67452301+0xd76aa478;
b1=((b1<<0x7)|(b10x19))+0xefcdab89;
//第一轮
a1=(((0x98badcfe^0xefcdab89)&b1)^0x98badcfe)+0xe8c7b756;
d2=0x10325476+c2+a1;
d2=((d2<<0xc)|(d20x14))+b1;
a1=(((0xefcdab89^b1)&d2)^0xefcdab89)+0x242070db;
s1=0x98badcfe+r1+a1;
s1=((s1<<0x11)|(s10xf))+d2;
a1=(((d2^b1)&s1)^b1)+0xc1bdceee;
d1=0xefcdab89+r2+a1;
d1=((d1<<0x16)|(d10xa))+s1;
a1=(((d2^s1)&d1)^d2)+0xf57c0faf;
b1=b1+0x80+a1;
b1=((b1<<0x7)|(b10x19))+d1;
a1=(((s1^d1)&b1)^s1)+0x4787C62A;
d2=d2+a1;
d2=((d2<<0xc)|(d20x14))+b1;
a1=(((d1^b1)&d2)^d1)+0xA8304613;
s1=s1+a1;
s1=((s1<<0x11)|(s10xf))+d2;
a1=(((d2^b1)&s1)^b1)+0xFD469501;
d1=d1+a1;
d1=((d1<<0x16)|(d10xa))+s1;
a1=(((d2^s1)&d1)^d2)+0x698098D8;
b1=b1+a1;
b1=((b1<<0x7)|(b10x19))+d1;
a1=(((s1^d1)&b1)^s1)+0x8B44F7AF;
d2=d2+a1;
d2=((d2<<0xc)|(d20x14))+b1;
a1=(((d1^b1)&d2)^d1)+0xFFFF5BB1;
s1=s1+a1;
s1=(