发新话题
打印

[转帖]SMB会话劫持专题

[转帖]SMB会话劫持专题

SMB系列(5)--LM NTLM验证机制
作者:小四
主页http://www.nsfocus.com
日期:2002-12-02

目录:

☆ 概述
☆ 挑战/响应模式
☆ L0pht文档
☆ Windows NT身份验证机制的脆弱性
☆ str_to_key()函数
☆ 如何从明文口令生成LM Hash
☆ 标准DES加密
☆ 如何从明文口令生成NTLM Hash
☆ 标准MD4单向哈希
☆ SMB报文中使用的是DES LM Hash和DES NTLM Hash
☆ 观察一个实例
☆ negotiate response解码
☆ session_setup_andx request解码
☆ 小结
☆ 参考资源

--------------------------------------------------------------------------

☆ 概述

早期SMB协议在网络上传输明文口令。后来出现"LAN Manager Challenge/Response"
验证机制,简称LM,它是如此简单以至很容易被破解。微软提出了WindowsNT挑战/响
应验证机制,称之为NTLM。现在已经有了更新的NTLMv2以及Kerberos验证体系。

微软承认LM Hash的固有特性极大损害了安全性,但他们认为这是最初设计者IBM之过。

☆ 挑战/响应模式

使用明文口令模式时,网络上传输的就是明文口令本身,这很容易被Sniffer捕获。
挑战/响应模式的企图不泄露明文口令本身就能证明客户机确实拥有正确的口令:

1. 服务器随机产生一个8字节的挑战,送往客户机。

2. 服务器、客户机各自使用源自明文口令的DESKEY分别对8字节挑战进行加密。客户
机将计算结果送往服务器,这就是所谓响应(分成三组,总共24字节)。

response = DES( key derived from plaintext password, challenge )

这里使用的就是标准DES算法。任何知道key的人都可以将reponse解密,从而获取
challenge。

3. 如果响应与服务器的计算结果匹配,服务器认为客户机拥有正确的明文口令。

☆ L0pht文档

1997年7月12日,L0pht的Mudge 对外发布了一份关于SMB通信中身
份验证的文档(参考资源[1])。

+----------------------------+
│ 14bytes Plaintext Password │
+----------------------------+

+--------------------------------------------------------------------------+
│ first 7bytes of Plaintext Password │ second 7bytes of Plaintext Password │
+--------------------------------------------------------------------------+

+-----------------+
│ 16bytes LM Hash │
+-----------------+

+----------------------------------------------------+
│ first 8bytes of LM Hash │ second 8bytes of LM Hash │
+----------------------------------------------------+

+-------------------------+
│ 16bytes NTLM Hash (MD4) │
+-------------------------+

+------------------+
│ 8bytes Challenge │
+------------------+

+------------------+
│ 24bytes Response │
+------------------+

LM Hash的前8字节源自对明文口令前7字节的运算,LM Hash的后8字节源自对明文口
令后7字节的运算。如果明文口令最多7字节,则第二部分LM Hash总是"AA D3 B4 35
B5 14 04 EE"(以后解释这里)。比如以"WELCOME"做为明文口令,则对应的LM Hash是
"C23413A8A1E7665FAAD3B435B51404EE"。

假设服务器B向客户机A发送了一个8字节挑战"0001020304050607"

Server B -- 8bytes Challenge --> Client A

A现在有LM Hash,C23413A8A1E7665FAAD3B435B51404EE,在其后增加5个0x00变成
"C23413A8A1E7665FAAD3B435B51404EE0000000000",然后划分成三组,每组7字节

+----------------+----------------+----------------+
│ C23413A8A1E766 │ 5FAAD3B435B514 │ 04EE0000000000 │
+----------------+----------------+----------------+

每组7字节做为形参传递给str_to_key()函数,最终得到三组DESKEY,每组8字节

+--------------------------------------------------------+
│ 8bytes DESKEY1 │ 8bytes DESKEY2 │ 8bytes DESKEY3 │
+------------------+------------------+------------------+
│ C21A04748A0E9CCC │ 5ED4B47642ACD428 │ 0476800000000000 │
+--------------------------------------------------------+

分别用三组DESKEY对8字节挑战"0001020304050607"进行标准DES加密后得到

C21A04748A0E9CCC -对0001020304050607进行标准DES加密-> CA1200723C41D577
5ED4B47642ACD428 -对0001020304050607进行标准DES加密-> AB18C764C6DEF34F
0476800000000000 -对0001020304050607进行标准DES加密-> A61BFA0671EA5FC8

最终获得一个24字节响应"CA1200723C41D577AB18C764C6DEF34FA61BFA0671EA5FC8",
送往服务器B。在服务器B上进行同样的计算,并将计算结果与来自A的响应进行比较,
如果匹配则身份验证通过。

考虑明文口令不超过7字节的情况。

首先检查明文口令是否少于8字节。用str_to_key()函数处理"04EE0000000000",得
到8字节的DESKEY,"0476800000000000",用它对挑战"0001020304050607"进行标准
DES加密,如果结果与网络上传输的"A61BFA0671EA5FC8"相符,明文口令很可能少于8
字节,当然不能绝对肯定。

接下来用str_to_key()函数处理"??AAD3B435B514",得到8字节DESKEY,用它对挑战
进行标准DES加密,如果与网络上传输的"AB18C764C6DEF34F"相符,则找到匹配,此
时我们可以绝对肯定明文口令少于8字节。这里"??"由循环产生,穷举256种可能。

由于LM Hash的一些固有特性,穷举运算量已大幅下降。

考虑明文口令为8字节或更多,假设最后的LM Hash如下

+----------------+----------------+----------------+
│ C23413A8A1E766 │ AC435F2DD90417 │ CCD60000000000 │
+----------------+----------------+----------------+

首先检查明文口令是否少于8字节。用str_to_key()函数处理"04EE0000000000",得
到8字节的DESKEY,"0476800000000000",用它对挑战进行标准DES加密,如果结果与
网络上传输的内容不相符,明文口令必然大于等于8字节。

接下来用str_to_key()函数处理"????0000000000",得到8字节DESKEY,用它对挑战
进行标准DES加密,如果与网络上传输的内容相符,则找到匹配。这里"????"由循环
产生,穷举65536种可能。

上面实际在介绍如何根据Challenge/Response暴力破解获取LM Hash。

即使到了NT4 SP3,DES LM Hash Response还是与DES NTLM Hash Response一起发送,
这种情况下NTLM Hash强度再高也是没有意义的。

如果禁用DES LM Hash Response,Windows 95无法与NT进行正常的SMB通信。

如果你所使用的Windows系统支持口令超过14字节,就尽量使用超过14字节的口令。

☆ Windows NT身份验证机制的脆弱性

1997年2月6日,Dominique Brezinski 对外
发布了一份关于Windows NT身份验证机制脆弱性的文档(参考资源[8])。

假设有主机B与A

(1) A向B发起连接请求

(2) B向A发送挑战(一组随机数据)

(3) A用源自明文口令的DESKEY对挑战进行标准DES加密得到响应,并发往B

(4) B从SAM中获取A的LM Hash、NTLM Hash,计算出DESKEY,并对前面发往A的挑战进
行标准DES加密

(5) 如果(4)中计算结果与A送过来的响应匹配,A被允许访问B

现在假设一个攻击者C卷入其中

(1) C向B发起连接请求

(2) B向C发送挑战D(一组随机数据)

(3) C等待A向B发起连接请求

(4) 当A向B发起连接请求时,C伪造成B向A发送挑战D

(5) A用源自明文口令的DESKEY对挑战D进行标准DES加密得到响应E,并发往B

(6) C截获到响应E,将它做为针对(2)中挑战D的响应发往B,并声称自己是A

(7) B从SAM中获取A的LM Hash、NTLM Hash,计算出DESKEY,并对挑战D进行标准DES
加密

(8) 如果(7)中计算结果与C送过来的响应匹配,C被允许以A的身份访问B

下面我们详细分析一下这个过程。攻击者C卷入A与B的通信中,C向B建立NBT会话并发
送SMB_COM_NEGOTIATE(0x72)请求报文,指定使用"NT LM 0.12" dialect。在用户级
共享(与之相对的是共享级共享)中"NT LM 0.12"是首选SMB dialect。B将在响应报文
的encryption key(其实应该叫Challenge)字段中返回8字节的挑战。C保存这8字节的
挑战并开始等待,如果B因为超时终止了这次连接,C必须重复前面的步骤。当A试图
连接B时,也会建立NBT会话并发送SMB_COM_NEGOTIATE(0x72)请求报文,就dialect进
行协商。一般最终协商结果都是使用"NT LM 0.12" dialect。C注意到这个协商请求,
于是伪装成B向A发送响应报文,encryption key字段中设置成前面保存下来的挑战。
这个响应报文的源IP设置成B的IP地址,需要分析A送往B的SMB_COM_NEGOTIATE(0x72)
请求报文以设置响应报文的th_ack字段。这个伪造的响应报文必须抢在B的正常响应
报文之前到达A。如果C本来就扮演着A与B之间路由器一类的角色,这不成问题。来自
B的正常响应报文做为重复数据而被丢弃。此时A生成两组24字节响应,向B发送
SMB_COM_SESSION_SETUP_ANDX(0x73)请求报文。C注意到这个请求,获取了A生成的两
组24字节响应,然后C也构造一个SMB_COM_SESSION_SETUP_ANDX(0x73)请求报文,用
这两组24字节响应分别设置CaseInsensitivePassword、CaseSensitivePassword字段。
同时在AccountName字段设置A的用户名。C将这样一个伪造的0x73请求报文通过最初
建立的NBT会话发往B。至此C将获取一条到B的SMB会话,拥有A用户的权限。

如果C扮演着A与B之间路由器一类的角色,整个攻击过程将大大简化。

☆ str_to_key()函数

前面我们多次提到str_to_key()函数,却未解释这个函数如何实现的,因为用自然语
言描述它比较困难,还是先来看它的C语言描述吧。

--------------------------------------------------------------------------
/*
* For x86/FreeBSD 4.5-RELEASE
* gcc -Wall -pipe -O3 -o str_to_key_test str_to_key_test.c
*
* C23413A8A1E7665FAAD3B435B51404EE0000000000
* C21A04748A0E9CCC5ED4B47642ACD4280476800000000000
*/

#include
#include
#include

/*
* 读取形如"AABBCCDDEEFF"这样的16进制数字串,主调者自己进行形参的边界检查
*/
static void readhexstring ( const unsigned char *src, unsigned char *dst, unsigned int len )
{
unsigned int i;
unsigned char str[3];

str[2] = '\0';
for ( i = 0; i < len; i++ )
{
str[0] = src[ i * 2 ];
str[1] = src[ i * 2 + 1 ];
dst = ( unsigned char )strtoul( str, NULL, 16 );
}
return;
} /* end of readhexstring */

/*
* from The Samba Team&#39;s source/libsmb/smbdes.c
*/
static void str_to_key ( const unsigned char *str, unsigned char *key )
{
unsigned int i;

key[0] = str[0] >> 1;
key[1] = ( ( str[0] & 0x01 ) << 6 ) │ ( str[1] >> 2 );
key[2] = ( ( str[1] & 0x03 ) << 5 ) │ ( str[2] >> 3 );
key[3] = ( ( str[2] & 0x07 ) << 4 ) │ ( str[3] >> 4 );
key[4] = ( ( str[3] & 0x0F ) << 3 ) │ ( str[4] >> 5 );
key[5] = ( ( str[4] & 0x1F ) << 2 ) │ ( str[5] >> 6 );
key[6] = ( ( str[5] & 0x3F ) << 1 ) │ ( str[6] >> 7 );
key[7] = str[6] & 0x7F;
for ( i = 0; i < 8; i++ )
{
key = ( key << 1 );
}
return;
} /* end of str_to_key */

int main ( int argc, char * argv[] )
{
unsigned int i;
unsigned char buf_0[21];
unsigned char buf_1[24];

if ( argc != 2 )
{
fprintf( stderr, "Usage: %s \n", argv[0] );
return( EXIT_FAILURE );
}
memset( buf_0, 0, sizeof( buf_0 ) );
memset( buf_1, 0, sizeof( buf_1 ) );
i = strlen( argv[1] ) / 2;
readhexstring( argv[1], buf_0, i );
for ( i = 0; i < sizeof( buf_0 ); i++ )
{
fprintf( stderr, "%02X", buf_0 );
}
fprintf( stderr, "\n" );
str_to_key( buf_0, buf_1 );
str_to_key( buf_0 + 7, buf_1 + 8 );
str_to_key( buf_0 + 14, buf_1 + 16 );
for ( i = 0; i < sizeof( buf_1 ); i++ )
{
fprintf( stderr, "%02X", buf_1 );
}
fprintf( stderr, "\n" );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

☆ 如何从明文口令生成LM Hash

假设明文口令是"Welcome",首先全部转换成大写,再做如下变换

"WELCOME" -> 57454C434F4D4500000000000000

也就是说在明文口令不足14字节的情况下,后面添加0x00补足14字节。有些书上介绍
添加空格(0x20)补足14字节,这是错误的,我不清楚是原作者写错了,还是译者的问
题。

然后切割成两组7字节的数据,分别经str_to_key()函数处理得到两组8字节数据

57454C434F4D45 -str_to_key()-> 56A25288347A348A
00000000000000 -str_to_key()-> 0000000000000000

这两组8字节数据将做为DESKEY对魔术字符串"KGS!@#$%"进行标准DES加密

"KGS!@#$%" -> 4B47532140232425

56A25288347A348A -对4B47532140232425进行标准DES加密-> C23413A8A1E7665F
0000000000000000 -对4B47532140232425进行标准DES加密-> AAD3B435B51404EE

将加密后的这两组数据简单拼接,就得到了最后的LM Hash

LM Hash: C23413A8A1E7665FAAD3B435B51404EE

显然,由于明文口令一开始就全部转换成大写,导致多个明文口令对应一个LM Hash。
反过来,在穷举破解LM Hash时,得到的有可能不是原始口令,因为不可能确定大小
写。仔细观察前述SMB身份验证过程,即使这里得到的不是原始口令(大小写有差别),
同样可以通过SMB身份验证。这种转换成大写的行为减小了穷举破解强度。

另一个弱点,当明文口令小于8字节时,LM Hash后8字节的计算过程总是这样的

00000000000000 -str_to_key()-> 0000000000000000 -对4B47532140232425进行标准DES加密-> AAD3B435B51404EE

这也将减小穷举破解强度。

IBM设计了这个LM Hash算法,魔术字符串"KGS!@#$%"的意义无从考证。这个算法称之
为"哈希"不怎么妥当,由于是标准DES加密,完全是可逆的。当然,由于要穷举的是
DESKEY本身,与传统所说的可逆有区别。

☆ 标准DES加密

这里不会重复DES的历史,Bruce Schneier所著<<应用密码学(第二版)>>值得一看。
事实上我们在自己编程时使用了书中附录里的DES例子代码。该书英文电子版未在互
联网上正式流传,尤其包含附录代码的配套磁盘被限制在北美地区以外出现,google
还是留下了它们。tombkeeper从google网页快照中恢复了该书英文版全部章节并制做
了一份chm文件,包括所有源代码,只是少了图。我不得不佩服这个变态的精力旺盛,
尽管后来他又从苏联人的地下站点上找到了该书更完整的内容,比如图。

本篇不打算涉及DES算法的C语言描述,microcat(lgx)为我写了这个临时测试程序用
于验证某些概念

--------------------------------------------------------------------------
/*
* For x86/FreeBSD 4.5-RELEASE
* gcc -Wall -pipe -O3 -o des_test des_test.c -lcrypto
*
* key : 01 23 45 67 89 AB CD EF
* cipher : C9 57 44 25 6A 5E D3 1D
* plain : 01 23 45 67 89 AB CD E7
*/
#include
#include
#include

int main ( int argc, char * argv[] )
{
unsigned int i;
des_key_schedule ks;
/*
* typedef unsigned char des_cblock[8];
* typedef unsigned char const_des_cblock[8];
*/
const_des_cblock key =
{
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
};
const_des_cblock plain_0 =
{
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xE7,
};
des_cblock plain_1;
des_cblock cipher;

printf( "key : " );
for ( i = 0; i < 8; i++ )
{
printf( "%02X%c", key, i == 7 ? &#39;\n&#39; : &#39; &#39; );
}
des_set_key_unchecked( &key, ks );
des_ecb_encrypt( &plain_0, &cipher, ks, DES_ENCRYPT );
printf( "cipher : " );
for ( i = 0; i < 8; i++ )
{
printf( "%02X%c", cipher, i == 7 ? &#39;\n&#39; : &#39; &#39; );
}
des_ecb_encrypt( &cipher, &plain_1, ks, DES_DECRYPT );
printf( "plain : " );
for ( i = 0; i < 8; i++ )
{
printf( "%02X%c", plain_1, i == 7 ? &#39;\n&#39; : &#39; &#39; );
}

return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

☆ 如何从明文口令生成NTLM Hash

IBM设计的LM Hash算法存在几个弱点,微软在保持向后兼容性的同时提出了自己的挑
战响应机制,NTLM Hash应运而生。

假设明文口令是"123456",首先转换成Unicode字符串,与LM Hash算法不同,这次不
需要添加0x00补足14字节

"123456" -> 310032003300340035003600

从ASCII串转换成Unicode串时,使用little-endian序,微软在设计整个SMB协议时就
没考虑过big-endian序,ntoh*()、hton*()函数不宜用在SMB报文解码中。0x80之前
的标准ASCII码转换成Unicode码,就是简单地从0x??变成0x00??。此类标准ASCII串
按little-endian序转换成Unicode串,就是简单地在原有每个字节之后添加0x00。

对所获取的Unicode串进行标准MD4单向哈希,无论数据源有多少字节,MD4固定产生
128-bit的哈希值,16字节

310032003300340035003600 -进行标准MD4单向哈希-> 32ED87BDB5FDC5E9CBA88547376818D4

就得到了最后的NTLM Hash

NTLM Hash: 32ED87BDB5FDC5E9CBA88547376818D4

与LM Hash算法相比,明文口令大小写敏感,无法根据NTLM Hash判断原始明文口令是
否小于8字节,摆脱了魔术字符串"KGS!@#$%"。

MD4是真正的单向哈希函数,穷举做为数据源出现的明文,难度较大。

问题在于,微软一味强调NTLM Hash的强度高,却避而不谈一个事实,为了保持向后
兼容性,NTLMHash缺省总是与LM Hash一起使用的。这意味着NTLM Hash强调再高也是
无助于安全的,相反潜在损害着安全性。增加NTLM Hash后,首先利用LM Hash的弱点
穷举出原始明文口令的大小写不敏感版本,再利用NTLM Hash修正出原始明文口令的
大小写敏感版本。

Phrack Magazine 50-08(参考资源[10])中提供的源程序根据NTLM Hash挂字典穷举明
文口令。我们推荐LC4。

☆ 标准MD4单向哈希

Ron Rivest设计的MD4算法固定产生128-bit的哈希值。尽管其理论上存在某些弱点,
但那是密码学家、数学家眼中的弱点,或者是NSA(美国国家安全局)所拥有计算能力
下的弱点,当然在分布式计算逐渐盛行的今天,也许还存在其它角度的弱点。除此三
者之外,MD4用来生成NTLM Hash,强度足够了。遗憾的是算法本身的强度不能保证整
个NTLM Hash验证机制的安全性。

Bruce Schneier所著<<应用密码学(第二版)>>中对MD4做了介绍。我们在自己编程时
使用了1999年挖自Tripwire Project的MD4实现代码。本篇不打算涉及MD4算法的C语
言描述,microcat(lgx)为我写了这个临时测试程序用于验证某些概念

--------------------------------------------------------------------------
/*
* For x86/FreeBSD 4.5-RELEASE
* gcc -Wall -pipe -O3 -o md4_test md4_test.c -lcrypto
*
* plain : 31 00 32 00 33 00 34 00 35 00 36 00
* md4 : 32 ED 87 BD B5 FD C5 E9 CB A8 85 47 37 68 18 D4
*/
#include
#include
#include

int main ( int argc, char * argv[] )
{
unsigned int i;
unsigned char md4[16];
unsigned char plain[] =
{
0x31, 0x00, 0x32, 0x00, 0x33, 0x00,
0x34, 0x00, 0x35, 0x00, 0x36, 0x00,
};

printf( "plain : " );
for ( i = 0; i < sizeof( plain ); i++ )
{
printf( "%02X%c", plain, i == ( sizeof( plain ) - 1 ) ? &#39;\n&#39; : &#39; &#39; );
}
MD4( plain, sizeof( plain), md4 );
printf( "md4 : " );
for ( i = 0; i < sizeof( md4 ); i++ )
{
printf( "%02X%c", md4, i == ( sizeof( md4 ) - 1 ) ? &#39;\n&#39; : &#39; &#39; );
}

return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

☆ SMB报文中使用的是DES LM Hash和DES NTLM Hash

大量中英文档在转述L0pht的资料,LC4系列在GUI上没有区分LM Hash、NTLM Hash与
DES LM Hash、DES NTLM Hash,加上转述者表达方式各不相同,引起一些概念上的混
淆。SMB报文中并不直接使用原始的LM Hash、NTLM Hash,而是对之进行了标准DES加
密。

假设有如下原始数据,虽然生成算法不同,但两个Hash值都是16字节长。挑战来自网
络传输,一般出现在SMB_COM_NEGOTIATE(0x72)响应报文的encryption key字段,固
定长8字节。

LM Hash -> 44EFCE164AB921CAAAD3B435B51404EE
NTLM Hash -> 32ED87BDB5FDC5E9CBA88547376818D4
Challenge -> E6010BA38D389439

在LM Hash后添加五个0x00补足21个字节,再用str_to_key()处理成24字节,每8字节
一切割,得到三组DESKEY。利用这三组DESKEY分别对8字节挑战进行标准DES加密

44EFCE164AB921CAAAD3B435B51404EE0000000000

str_to_key()

V
4476F2C26454E442CA54B47642ACD4280476800000000000

DESKEY1 -> 4476F2C26454E442 -对E6010BA38D389439进行标准DES加密-> 6DA05CC936C0D9B9
DESKEY2 -> CA54B47642ACD428 -对E6010BA38D389439进行标准DES加密-> 37DE29821E728894
DESKEY3 -> 0476800000000000 -对E6010BA38D389439进行标准DES加密-> 368728E66F38669E

将所得三组数据简单拼接就得到了24字节的DES LM Hash(响应)

DES LM Hash -> 6DA05CC936C0D9B937DE29821E728894368728E66F38669E

出现在SMB_COM_SESSION_SETUP_ANDX(0x73)请求报文的CaseInsensitivePassword字
段中。

在NTLM Hash后添加五个0x00补足21个字节,再用str_to_key()处理成24字节,每8字
节一切割,得到三组DESKEY。利用这三组DESKEY分别对8字节挑战进行标准DES加密

32ED87BDB5FDC5E9CBA88547376818D40000000000

str_to_key()

V
327660F6DAAEF68AE8E4EA105438DCD0186A000000000000

DESKEY1 -> 327660F6DAAEF68A -对E6010BA38D389439进行标准DES加密-> 19C4336ACBBB62DD
DESKEY2 -> E8E4EA105438DCD0 -对E6010BA38D389439进行标准DES加密-> 37802C62AD5C6903
DESKEY3 -> 186A000000000000 -对E6010BA38D389439进行标准DES加密-> 4B68998B807F8A9D

将所得三组数据简单拼接就得到了24字节的DES NTLM Hash(响应)

DES NTLM Hash -> 19C4336ACBBB62DD37802C62AD5C69034B68998B807F8A9D

出现在SMB_COM_SESSION_SETUP_ANDX(0x73)请求报文的CaseSensitivePassword字段
中。
益友网吧联盟  http://www.96-7.com

TOP

发新话题