luketowne

实现PROXY穿越

实现PROXY穿越(1):流程和NTLM算法
最近想实现一个通过PROXY穿越的网络编程,将相关的内容进行一下汇总。很多东西来自网络共产主义,也应该为共产主义有所回馈。
HTTP PROXY穿越的流程
     从第一个HTTP请求到200 Connected established,一共有6个步骤『PROXY需要校验,如果Proxy不需要验证,非常简单,直接供步骤一跳到步骤六,不再做描述』。下面以HTTP的CONNECT方法为例子,GET和POST的实现是类似的。

步骤一:
    C --》 P : HTTP/1.1 CONNECT …… Connect:keep-Alive

步骤二:
  C 《--  P :HTTP 407 Connect: close
  在这里,PROXY给出三个Proxy-Auchenticate参数。Negotiate, Kerberos、NTLM的算法要求,其中NTLM根据后面交互的flags,有v1和v2两种算法。
  Negotiate是一个ssp,它会根据用户的环境选择其它合适的具体SSP(更像是一个重定向器,协商):NTLM或者kerberos。 NTLM 是一个比较老的SSP,但支持广泛,Kerberos是新的比较好的SSP,但不支持NT4.0以及之前版本。用户一般不直接调用NTLM或者 Kerberos,而是调用Negotiate。Negotiate的策略是,如果用户系统不支持Kerberos,或者没有提供支持Kerberos的信息,则Negotiate会默认返回NTLM给客户端。
  根据Connect的字段,客户端需要Close TCP连接,重新建立一条新的连接,发送步骤三的数据包。

步骤三:
  C --》 P : HTTP/1.1 CONNECT NTLMSSP …… Connect:keep-Alive
  选择NTLM算法,向P发送经过base64处理的type1 message,带有用户的HOST名字『机器名字』和域名,例如我的机器HOST为YJY-WEI,域名为GDCTC。这些通过获取计算机的信息得到。如果在LINUX或者其他操作系统,可以填为NULL、NULL。

步骤四
  C 《--  P :HTTP 407 Connect: Keep-Alive
  P通过经过base64加密的信息给出type2 Message,其中包含nonce,提供给客户端计算,并给出NTLM算法的特性,这个特定由步骤三给出的NTLM flags,P来进行选择,可以指定使用NTLM使用v1还是v2。

步骤五:
  C --》 P : HTTP/1.1 CONNECT NTLMSSP …… Connect:keep-Alive
  C根据NTLM算法计算nonce、user、passwd,给出type3Message,并经过base64扰码给P送去。

步骤六:
  C --》 P : HTTP/1.1  200
  Connection建立,可以传递非HTML格式的内容 。

相关算法:NTLM
    如果通过网络抓包,一般采用NTLM和kerberos。我们的第一个目的是通过NTLM实现通信。需现对NTLM算法有所了解。

  windows的登录。按从弱至强可使用LM、NTLM、NTLM v2,kerberos。
  • LM网络认证协议是很多年前Microsoft和IBM合作开发操作系统是开发的,比较薄弱,经常被用作攻击工具获得windows的密码。他的薄弱基于以下原因:
  • LM无法辨别大小写字母,在密码哈希将所有字母都被转换成大写字谜,但包含不同类型字符的密码是难破译的。
  • LM将长于7为的密码分为两块,分别处理。攻击者可分别攻击LMmima的两个部分。因此创建一个长于7位的密码不能是密码更安全。
  • LM密码不能长于14个字符,但是长密码是更安全的密码。
  • LM协议使用DES,DES是所有加密协议中较弱的一个协议。

默认情况下,Windows Server 2003钱的操作系统会创建、贮存一个LM hash和一个NTLM hash。此外,网络认证是传送一个LM喝NTLM版本的相应。这些因素使得LM较容易攻击,破解,通过破解LM hash值得到账户密码。通过消除LM hash的存贮,有很多方法可用于加强密码安全。
  Windows NT引入NT LAN Manager认证协议。和LM不同,NTLM能辨别大小写字母。NTLM为整个密码生成一个MD4哈希(不分为7个字符的块)。虽然NTLM支持更长的字符,但是Window NT用户 界面不支持超过14位字符的密码,因此实际上这也是NT密码的长度。Windows 2000以后的版本能支持更长的密码,知道最到128位字符。
  NTLMv2支持信息的保密性和完整性。用独立密钥和HMAC-MD5算法,NTLMv能提供128位加密和NTLMv会话安全性。当配置NTLMv作为唯一允许协议时,LM的响应将不会被发送。
  计算机升级到Windows 2000及以上版本,更常用Kerberos认证协议。Kerberos协议被认为是专家级的强协议。协议的很多部分是专门针对那些有名的认证攻击而开发。区别Kerberos协议和NTLM的一个办法就是,NTLM是为应用于可信任网络而开发的,而Kerberos是为了应用于不可信任网络而开发。但是需要注意如果驱动器是由服务器IP地址而不是计算机名映射,Kerberos不可用,LM或NTLM可用。



实现PROXY穿越(2):Base64算法
  如果我们进行抓包,在步骤二、步骤三、步骤四中,传递相关信息,在WWW-Authenticate中,使用了Base64算法进行扰码。
  按照RFC2045的定义,Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The Base64 Content-Transfer-Encoding is designed  to represent arbitrary sequences of octets in a form that need not be humanly readable.)
  BASE64的原理可以在RFC中查阅,或者青参考网络共产主义:http://www.5dmail.net/html/2004-1-30/200413084348.htm。我们在下面给出相关的程序代码。

// *********************************  Base64 算法 begin *****************************
// Translation Table as described in RFC1113
static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static unsigned char db64[256] = {
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  62,  255,  255,  255,  63,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 
    255,  255,  255,  0,  255,  255,  255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 
    11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  255,  255, 
    255,  255,  255,  255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37, 
    38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255,  255, 
    255,  255,  255,  255,  255};

// encodeblock
// encode 3 8-bit binary bytes as 4 '6-bit' characters
static void encodeblock( unsigned char in[3], unsigned char out[4], int len )
{
    out[0] = cb64[ in[0] >> 2 ];
    out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
    out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
    out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
}

void encode_base64(OUT char *dst, IN const char *src, IN int sz )
{
    unsigned char in[3], *out = (unsigned char *) dst;
    int i, len;

    while (sz > 0){
        len = 0;
        for (i = 0; i < 3; i++, sz--){
            if (sz > 0){
                len++;
                in[i] = src[i];
            } else {
                in[i] = 0;
            }
        }
        src += 3;
        if (len){
            encodeblock(in, out, len);
            out += 4;
        }
    }
    *out = '\0';
}

void decode_base64(IN char *src, IN int src_len, OUT char *dst,OUT int * dst_len)
{
    int j = 0, i = 0;
    for ( i = 0; i < src_len; i += 4) {
        dst[j++] = db64[src[i]] << 2 | db64[src[i + 1]] >> 4;
        dst[j++] = db64[src[i + 1]] << 4 | db64[src[i + 2]] >> 2;
        dst[j++] = db64[src[i + 2]] << 6 | db64[src[i + 3]];
    }
    dst[j] = '\0';
    * dst_len = j-1;
}
// end of part1: Base64 算法 end


实现PROXY穿越(3):DES算法之一
    在NTLM中使用DES算法生成LM-HASH和NTLM-HASH(以后在介绍)。我们现讨论DES算法的实现。 DES是Data Encryption Standard(数据加密标准)的缩写。它是由IBM公司研制的一种加密算法,美国国家标准局于1977年公布把它作为非机要部门使用的数据加密标准,二十年来,它一直活跃在国际保密通信的舞台上,扮演了十分重要的角色。而DES算法本身称自己为DEA(Data Encryption Althorithm),因此DES和DEA实际是同一个东东。
  DES输入64bit的数据源,通过56bits的key(由8bytes去掉CRC校验位获得,输入也可以看作是64bits,至少在程序代码中是这样体现),生成一个64bits的结果,DES属于对称加密方式。一共分为三个步骤。
  在实现的过程中,最为郁闷的是网上有很多代码和例子,也有一些检测工具,例如Crytotool1.2,但是很奇怪,他们给出的结果不一样,因此有必要认真了解一下整个DES的过程。

步骤一:源和key的初始化序列改动(Initail permutation)
通过对比特的序列移动,生成新的源'和key’,用于后续的算法计算。

A:对源的处理
  8字节,共64bits,从1到64按下面左阵列的摆列方式,经过序列移动,成规下面右阵列的排列方式,实际上新的第一个8bit就是左阵列的第2列,从下到上的顺序,后面的7组8比特分别为第4、6、8、1、3、5、7列从下到上的排序。这种方式,我想对于芯片实现是很便利的,可惜C编程语言不等直接按列处理,只好写段代码来实现。
| 1  8|    |58 50 42 34 26 18 10 2|
| 9 10 11 12 13 14 15 16|    |60 52 44 36 28 20 12 4|
|17 18 19 20 21 22 23 24|    |62 54 46 38 30 22 14 6|
|25 26 27 28 29 30 31 32|    |64 56 48 40 32 24 16 8|
|33 34 35 36 37 38 39 40| -> |57 49 41 33 25 17  9 1|
|41 42 43 44 45 46 47 48|    |59 51 43 35 27 19 11 3|
|49 50 51 52 53 54 55 56|    |61 53 45 37 29 21 13 5|
|57 58 59 60 61 62 63 64|    |63 55 47 39 31 23 15 7|
 实现代码:
//变换序列
static int ip_data_seq[] = {
    58,50,42,34,26,18,10,2,
    60,52,44,36,28,20,12,4,
    62,54,46,38,30,22,14,6,
    64,56,48,40,32,24,16,8,
    57,49,41,33,25,17,9,1 ,
    59,51,43,35,27,19,11,3,
    61,53,45,37,29,21,13,5,
    63,55,47,39,31,23,15,7};

//由于后面存在大量的比特操作,而C程序中一般以字节为单位,因此我们将字节中的比特分别
//存贮在8个字节中,以方便后面的大量运算
static void storebit(IN unsigned char * data, IN int data_len, OUT unsigned char * dst){
    int i = 0;
    for(i = 0 ; i < data_len;i ++){
        dst[i*8] = getbit(data[i],7);
        dst[i*8 + 1] = getbit(data[i],6);
        dst[i*8 + 2] = getbit(data[i],5);
        dst[i*8 + 3] = getbit(data[i],4);
        dst[i*8 + 4] = getbit(data[i],3);
        dst[i*8 + 5] = getbit(data[i],2);
        dst[i*8 + 6] = getbit(data[i],1);
        dst[i*8 + 7] = getbit(data[i],0);
    }
}
//这是storebit得反操作。
static void parsebit(IN unsigned char * data,OUT unsigned char * dst,IN int dst_len){
    int i = 0;
    for(i = 0 ; i < dst_len ; i ++){
        dst[i] = data[8*i] * 0x80 +
            data[8*i + 1] * 0x40 +
            data[8*i + 2] * 0x20 +
            data[8*i + 3] * 0x10 +
            data[8*i + 4] * 0x8 +
            data[8*i + 5] * 0x4 +
            data[8*i + 6] * 0x2 +
            data[8*i + 7];
    }
}

//移位操作函数
static void initail_permutation(IN unsigned char * data,IN int * schedule, IN int num,
                                OUT unsigned char * dst){
    int i = 0;
    unsigned char * temp;
    temp = (unsigned char *)malloc(num);

    for(i = 0 ; i < num; i ++){
        temp[i] = data[schedule[i] - 1];
    }
    memcpy(dst,temp,num);
    free(temp);
}

//算法主函数
void algorithm_des(IN unsigned char * src, IN unsigned char * secrect,
                   OUT unsigned char * dst){
    unsigned char s[64],key[64],L[32],R[32],K[48],E[48];
    int i = 0;

    //步骤1
    storebit(src,8,s);
    initail_permutation(s,ip_data_seq,64,s);
}

B:对key的处理
  对于输入的8字节的key,每个字节去除其CRC校验位(第8位),然后经过类似的序列移位生成了56bit的新的key。从这个处理,我们也可以看出DES算法的古老,采用流的概念,而CRC校验位用于初期比较古老的误码率高的通信中。
  这种以通信以流的方式传递,需要注意和我们程序中的顺序问题。如果我们使用byte(unsinged char)来放置一8bit的信息,那么第一位是我们byte的高位。这与我们网络编程中碰到的little_endian和big_endian的情况有点类似。闲话少谈,移动位的方式如下:
| 1  8|    |57 49 41 33 25 17  9|    |57 49 41 33 25 17  1|
| 9 10 11 12 13 14 15 16|    | 1 58 50 42 34 26 18|    |58 50 42 34 26 18 10  2|
|17 18 19 20 21 22 23 24|    |10  2 59 51 43 35 27|    |59 51 43 35 27 19 11  3|
|25 26 27 28 29 30 31 32|    |19 11  3 60 52 44 36|    |60 52 44 36            |
|33 34 35 36 37 38 39 40| -> |63 55 47 39 31 23 15| -> |63 55 47 39 31 23 15  7|
|41 42 43 44 45 46 47 48|    | 7 62 54 46 38 30 22|    |62 54 46 38 30 22 14  6|
|49 50 51 52 53 54 55 56|    |14  6 61 53 45 37 29|    |61 53 45 37 29 21 13  5|
|57 58 58 60 61 62 63 64|    |21 13  5 28 20 12  4|    |28 20 12            |
  中间和右图是一样的,只是我们采用7bit一组还是按照8bit一组的方式存放,我们希望通过右图给出一个有规律的处理。
static int ip_key_seq[] ={
    57,49,41,33,25,17,9,
    1,58,50,42,34,26,18,
    10,2,59,51,43,35,27,
    19,11,3,60,52,44,36,
    63,55,47,39,31,23,15,
    7,62,54,46,38,30,22,
    14,6,61,53,45,37,29,
    21,13,5,28,20,12,4};

//算法主函数
void algorithm_des(IN unsigned char * src, IN unsigned char * secrect,
                   OUT unsigned char * dst){
    unsigned char s[64],key[64],L[32],R[32],K[48],E[48];
    int i = 0;

    //步骤1
    storebit(src,8,s);
    storebit(secrect,8,key);
    initail_permutation(s,ip_data_seq,64,s);
    initail_permutation(key,ip_key_seq,56,key);
}

OK,终于完成了第一步,得到了s[64]和key[56]为第二步算法的输入。

实现PROXY穿越(4):DES算法之二
步骤二:16次计算(16 interations)
 
    在步骤一中,我们获取了64bits的s,以及56bits的key。s分为2个32bit的序列,分布成为L和R,根据第一步计算,我们有了L0、R0,以及K0,进行如下的操作:
L1=R0
R1=L0 XOR F(R0,K1)
  我们得到了新的L1和R1,重复操作,直至得到L16和R16,操作公式为:
Li = R i-1
Ri = L i-1 XOR F(Ri-1,Ki)

A:Ki的获取
  在这个过程中,我们每次运算的操作需要得到新的ki进行参与。下面将介绍如何从K0依次16个获取Ki(K1-K16)。K0是一个56bits的数据,我们将其存贮56个字节的key数组中。这56个比特均分为2组,第一组0-27,第二组28-55。
   左移偏移量:
   1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
对于Ki,从左移偏移量选择对应的偏移量,例如K1=1,K4=2。将两组28位的比特分别左移指定的便宜量。例如对于K1,经过左移后,第一组为1-27,0,第二组为29-55,28。得到一个新的56比特的数据,请保留这个新的序列,记做K1',这个数据根据下面进行序列重排:
    |14 17 11 24  5|
    | 3 28 15  6 21 10|
    |23 19 12  4 26  8|
    |16  7 27 20 13  2|
    |41 52 31 37 47 55|
    |30 40 51 45 33 48|
    |44 49 39 56 34 53|
    |46 42 50 36 29 32|
  经过重新排序后,我们得到48比特的数据,这个就是K1。由此我们获得了第一个参与运算的K1。
对于Ki,将Ki-1',例如计算K2,将上次我们得到的K1',分为两组,进行相应的左移操作,得到Ki’,例如计算K2是,我们将K1’分为两组,每组左移1为,得到K2’,将Ki’进行同样排序,得到Ki。
  通过这样的操作,我们依次得到16个Ki,代码如下:
static int key_offset[] = {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};
static int ip_key[] ={
    14,17,11,24,1,5,
    3,28,15,6,21,10,
    23,19,12,4,26,8,
    16,7,27,20,13,2,
    41,52,31,37,47,55,
    30,40,51,45,33,48,
    44,49,39,56,34,53,
    46,42,50,36,29,32};

//左移操作
static void getkey(IN OUT unsigned char * key,int offset){
    unsigned char temp[28];

    memcpy(temp,key + offset,28-offset);
    memcpy(temp + 28 - offset, key , offset);
    memcpy(key,temp,28);

    memcpy(temp,key + 28 + offset,28-offset);
    memcpy(temp + 28 - offset, key + 28 , offset);
    memcpy(key + 28,temp,28);
}

void algorithm_des(IN unsigned char * src, IN unsigned char * secrect,
                   OUT unsigned char * dst){
    ......
    //步骤二:
    //获取原始的L0和R0
    memcpy(L,s,32);
    memcpy(R,s+32,32);
   
    //进行16次计算
    for(i = 0; i < 16 ; i++){
        //获取Ki'仍然放置在key中
        getkey(key,key_offset[i]);
        //获取Ki,放置在K中
        initail_permutation(key,ip_key,48,K);
    }
}


B:F计算

  我们已经有R0,对于每次计算Ri,都将有Ri-1,根据A的步骤,我们已经有了K1,对于每次计算,我们也有Ki。在这个步骤中,我们将实现F(Ri-1,Ki)。

step 1:
对于Ri-1进行序列交换,生成一个48比特的数据,排序方式如下:
   | 32  5 |
   9 |
   9 10 11 12 13 |
   | 12 13 14 15 16 17 |
   | 16 17 18 19 20 21 |
   | 20 21 22 23 24 25 |
   | 24 25 26 27 28 29 |
   | 28 29 30 31 32  1 |
这组48比特的数据和Ki进行异或(XOR),得到一组新的48bite的数据,我们暂时记做E.

step 2: S box的运算
  48比特的数据均分为8份,每份6比特,我们分别根据S-box的变换,每份产生4比特的数据,生成一个32bite的数据。
1、从6比特的数据中获取一个行号m和一个列号n。
  m=b0b5,n=b1b2b3b4
  例如6比特的数据为100101,则m=11(3),n=0010(2)
2、根据行号和列号,在S-box的序列中,查到相应的数值。48比特分为8组,每组查询的S-box是不一样的,分别为S1, S2, S3, S4, S5, S6, S7,S8具体如下:
S1:   0列 1列 2列 3列 4列 5列 6列 7列 8列 9列 A列 B列 C列 D列 E列 F列
0行    14   13    15  11    10   12     7
1行    0   15    14   13  1   10  6   12  11  9   5   3   8
2行     1   14   13  6   2   11  15  12  9   7   3   10  5   0
3行    15  12  8    4   9   1   7    11  3   14  10   6   13
S2:
    15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
    3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
    0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
    13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
S3:
    10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
    13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
    13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
    1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
S4
    7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
    13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
    10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
    3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
S5
    2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
    14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
    4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
    11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
S6
    12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
    10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
    9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
    4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
S7
    4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
    13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
    1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
    6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
S8
    13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
    1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
    7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
    2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
 我们仍以100101为例子,假设它的第一组6比特,得到m=3,n=2,我们查询S1,在第3行,第2列中查得8,将其翻译为二进制,则转换为4比特的数据1000,这样我们得到了新的第一组4比特,如此类推,我们得到了8组4比特的数据,按顺序组合成一个32比特的数据,暂时记为E'。

step3:更换顺序
将32比特的E’根据下面进行序列更换,得到新的32位数据。
   |16 7  20 21|
   |29 12 28 17|
   |1  15 23 26|
   |5  18 31 10|
   |2  24 14|
   |32 27 3  9 |
   |19 13 30 6 |
   |22 11 4  25|
至此我们完成了F运算。

C:获得新的Ri和Li
这步骤比较简单,如下
Li = R i-1
Ri = L i-1 XOR F(Ri-1,Ki)

D:重复16次计算,得到L16和R16

static int ip_e[] = {
    32,1,2,3,4,5,
    4,5,6,7,8,9,
    8,9,10,11,12,13,
    12,13,14,15,16,17,
    16,17,18,19,20,21,
    20,21,22,23,24,25,
    24,25,26,27,28,29,
    28,29,30,31,32,1};
static int ip_p[] = {
    16,7,20,21,29,12,28,17,
    1,15,23,26,5,18,31,10,
    2,8,24,14,32,27,3,9,
    19,13,30,6,22,11,4,25};

static unsigned char s1[64] = {
    14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
    0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
    4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
    15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 };

static unsigned char s2[64] = {
    15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
    3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
    0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
    13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 };

static unsigned char s3[64] = {
    10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
    13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
    13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
    1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 };

static unsigned char s4[64] = {
    7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
    13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
    10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
    3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 };

static unsigned char s5[64] = {
    2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
    14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
    4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
    11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 };

static unsigned char s6[64] = {
    12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
    10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
    9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
    4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 };

static unsigned char s7[64] = {
    4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
    13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
    1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
    6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 };

static unsigned char s8[64] = {
    13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
    1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
    7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
    2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 };

static void s_box_function(IN unsigned char * data,IN unsigned char * sbox,
                           OUT unsigned char * dst){
    int m = data[0] * 2 + data[5];
    int n = data[1] * 8 + data[2] * 4 + data[3] * 2 + data[4];
    unsigned char c = sbox[m* 16 + n];
    if(c >= 8){
        dst[0] = 1;
        c = c-8;
    }else{
        dst[0] = 0;
    }
    if(c >= 4){
        dst[1] = 1;
        c = c-4;
    }else{
        dst[1] = 0;
    }
    if(c >= 2){
        dst[2] = 1;
        c = c-2;
    }else{
        dst[2] = 0;
    }
    dst[3] = c;
}

void algorithm_des(IN unsigned char * src, IN unsigned char * secrect,
                   OUT unsigned char * dst){
    ......
    //步骤二:
    //获取原始的L0和R0
    memcpy(L,s,32);
    memcpy(R,s+32,32);
   
    //进行16次计算
    for(i = 0; i < 16 ; i++){
        //获取Ki
        getkey(key,key_offset[i]);
        initail_permutation(key,ip_key,48,K);

        //F计算
        initail_permutation(R,ip_e,48,E);
        xorbit(E,K,48,E);
        s_box_function(E,s1,E);
        s_box_function(E + 6,s2,E + 4);
        s_box_function(E + 12,s3,E + 8);
        s_box_function(E + 18,s4,E + 12);
        s_box_function(E + 24,s5,E + 16);
        s_box_function(E + 30,s6,E + 20);
        s_box_function(E + 36,s7,E + 24);
        s_box_function(E + 42,s8,E + 28);
        initail_permutation(E,ip_p,32,E);

        //更换序列
        xorbit(E,L,32,E);
        memcpy(L,R,32);
        memcpy(R,E,32);
    }
}
实现PROXY穿越(5):DES算法之三
步骤三:获取最后的加密结果
    我们经过了前面的多个步骤,得到了L16和R16,进入我们的最后的步骤:获取最终结果。
A:32-bit swap
    将L16和R16,按R16L16的顺序组成一个新的64比特的数据流。
B:Invers initial permutation
   执行反向的序列重排,按下面的序列进行顺序重排
   |40,8,48,16,56,24,64,32|
   |39,7,47,15,55,23,63,31|
   |38,6,46,14,54,22,62,30|
   |37,5,45,13,53,21,61,29|
   |36,4,44,12,52,20,60,28|
   |35,3,43,11,51,19,59,27|
   |34,2,42,10,50,18,58,26|
   |33,1,41,9 ,49,17,57,25|
  经过重新排序后得到的64比特的数据就是DES最终的加密数据。
 static int inverse_ip_p[64] = {
    40,8,48,16,56,24,64,32,
    39,7,47,15,55,23,63,31,
    38,6,46,14,54,22,62,30,
    37,5,45,13,53,21,61,29,
    36,4,44,12,52,20,60,28,
    35,3,43,11,51,19,59,27,
    34,2,42,10,50,18,58,26,
    33,1,41,9,49,17,57,25};

void algorithm_des(IN unsigned char * src, IN unsigned char * secrect,
                   OUT unsigned char * dst){
    …… ……
    //步骤三
    memcpy(s,R,32);
    memcpy(s+32,L,32);
    initail_permutation(s,inverse_ip_p,64,s);
    parsebit(s,dst,8);
}

我们给出几组验证数据:
组一:
key:56 a2 52 88 34 7a 34 8a
plain text:KGS!@#$%
DES加密结果:c2 34 13 a8 a1 e7 66 5f
组二:
key :31 31 31 31 31 31 31 31 ("11111111")
text:30 30 30 30 30 30 30 30 ("00000000")
DES: f4 7b b4 62 73 b1 5e b5
组三:
key :"00000000"
text:"11111111"
DES: 65 5e a6 28 cf 62 58 5f
组四:
key:“abcdefgh"
text: "12345678"
DES: 21 c6 0d a5 34 24 8b ce

后感:
  我在最后给出四组校验的结果,是因为网上有人反应,而我自己也发现,不同网页给出的结果或者不同小工具运算出来的结果不一样,这对于程序员是很郁闷的。因为需要校验。
    我使用了C而不是C++,对于算法,没有什么对象的概念,而且C的通用性比C++好,可以直接用在C#中,也可以包装一下成为C#。
  DES算法很折腾,换来换去的,然则,DES算法却并不安全,容易破解。仔细想想,有两群很无聊的人,一群人努力地捣腾进行加密,一群人费尽心思进行破解,搞得加密算法越来越复杂。由于DES的不安全性,它只是ntlm中的一环,实现NTLM还有漫漫长路。

实现PROXY穿越(6):LM-Hash的实现
  经过了对DES的努力,终于可以实现LM-HASH,不管离开NTLM还有多远,至少前进了一步。现从网上共产主义来点HASH科普『参考:http://tech.ddvip.com/2008-12/122818571796653.htm』,然后再介绍LM-HASH的实现。

  Hash,一般翻译为“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。HASH主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128位的编码里,叫做HASH值。也可以说,Hash就是找到一种数据内容和数据存放地址之间的映射关系。Hash算法在信息安全方面的应用主要体现在以下的3个方面:
  (1) 文件校验:
  我们比较熟悉的校验算法有奇偶校验和CRC校验,这2种校验并没有抗数据篡改的能力,它们一定程度上能检测并纠正数据传输中的信道误码,但却不能防止对数据的恶意破坏。 MD5 Hash算法的“数字指纹”特性,使它成为目前应用最广泛的一种文件完整性校验和(Checksum)算法,不少Unix系统有提供计算md5 checksum的命令。
  (2)数字签名
  Hash 算法也是现代密码体系中的一个重要组成部分。由于非对称算法的运算速度较慢,所以在数字签名协议中,单向散列函数扮演了一个重要的角色。对Hash值,又称“数字摘要”进行数字签名,在统计上可以认为与对文件本身进行数字签名是等效的。而且这样的协议还有其他的优点。
  (3)鉴权协议
  鉴权协议又被称作挑战--认证模式:在传输信道是可被侦听,但不可被篡改的情况下,这是一种简单而安全的方法。

    言归正传,LM HASH是在DES上面实现的,因此它的安全性也如DES的安全性一样收到置疑。LM-HASH的实现分为6个步骤,在我们解决DES算法的基础上,很容易实现。在下面我们以Welcome为例子进行描述:

步骤一:转换为大写
  将需要加密的明文中所有的小写改为大写,即WELCOME。

步骤二:定长14字节
  如果明文得长度超过14字节,将截取前面14字节的数据,如果不足14字节,则以0x00来补齐,即得到二进制:5745 4C43 4F4D 4500 0000 0000 0000

步骤三:分为2组7字节的数据
  将14字节均分为两组,即:
组一:57 45 4C 43 4F 4D 45
组二:00 00 00 00 00 00 00

步骤四:分别将2个7字节的数据,各生成64比特的DES的key。
组一:56 A2 52 88 34 7A 34 8A
组二:00 00 00 00 00 00 00 00

步骤五:以步骤四得到的作为DES的key,以“KGS!@#$%”作为DES的明文,进行DES加密,分别得到两组64比特的数据。
组一:C2 34 13 A8 A1 E7 66 5F
组二:AA D3 B4 35 B5 14 04 EE

步骤六:将两组64比特的数据连结为16字节的数据,这就是LM Hash的最后的值。
LM-HASH=C23413A8A1E7665FAAD3B435B51404EE

//KGS!@#$%的二进制表示
static unsigned char lm_magic[] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };

//from The Samba Team's source/libsmb/smbdes.c,用于步骤四的实现

static void str_to_key ( IN const unsigned char *str, OUT 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[i] = ( key[i] << 1 );
    }
    return;
}

//获取16字节的lm-hash内容
void lm_hash(IN char * src, OUT unsigned char * dst, OUT int * dst_len){
    int i = 0;
    unsigned char lm_src[14];

    //步骤一和步骤二
    if(strlen(src) >= 14){
        memcpy(lm_src,src,14);
    }else{
        memset(lm_src,0,14);
        memcpy(lm_src,src,strlen(src));
    }
    for(i = 0 ;i < 14; i ++){
      lm_src[i] = chrtoupper(lm_src[i]);
    }

    //步骤三和步骤四
    str_to_key(lm_src,dst);
    str_to_key(lm_src + 7, dst + 8);

    //步骤五和步骤六
    algorithm_des(lm_magic, dst,dst);
    algorithm_des(lm_magic, dst + 8,dst + 8);
    if(dst_len != NULL)
        * dst_len = 16;
}//LM-HASH end

检验例子二:Administrator的LM-HASH = 6a 98 eb 0f b8 8a 44 9c be 6f ab fd 82 5b ca 61
实现PROXY穿越(7):MD4和MD5
  LM-HASH不能提供足够的安全,使用NTLM-HASH,在这个算法中,使用了MD4。MD4和MD5都是非常常用的Hash算法。算法非常复杂,但是很容易实现,因为RFC1320:MD4 Message-Digest Algorithm和RFC1321:MD5 Message-Digest Algorithm在附录中都给出了算法的C代码,可以直接抄。在JAVA中,有相关类提供。
  我们从RFC1320/1321中抄录了MD4.c和MD5.c,在里面增加两个函数,提供MD4和MD5的调用。

void MD4String (IN char * string,IN int len,OUT unsigned char * digest);
void MD5String (IN char * string,IN int len,OUT unsigned char * digest);

void MD4String (string,len,digest)
char *string;
int len;
unsigned char *digest;
{
  MD4_CTX context;

  MD4Init (&context);
  MD4Update (&context, string, len);
  MD4Final (digest, &context);
}

void MD5String (string,len,digest)
char *string;
int len;
unsigned char * digest;
{
  MD5_CTX context;

  MD5Init (&context);
  MD5Update (&context, string, len);
  MD5Final (digest, &context);
}
实现PROXY穿越(8):NT-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: 32ED87BDB5FDC5E9CBA88547376818D4。

static void unicode(IN char * src, IN int src_len, OUT char * dst, OUT int * dst_len){
    int i ;
    for( i = 0 ; i < src_len; i ++){
        dst[2*i] = src[i];
        dst[2*i +1] = 0;
    }
    if(dst_len != NULL)
        * dst_len = src_len * 2;
}

void nt_hash(IN char * src, IN int is_unicode,OUT unsigned char * dst, OUT int * dst_len){
    char * source = NULL;
    int len = strlen(src);
    if(!is_unicode){
        source = (char * ) malloc(len *2 );
        unicode(src,len,source,&len);
    }else{
        source = src;
    }

    MD4String (source,len,dst);
    if(dst_len != NULL)
        * dst_len = 16;

    if(!is_unicode)
        free(source);
}

调用例子:
unsigned char dst[16];
nt_hash("123456",0,dst,NULL);
实现PROXY穿越(9):NTLMv1 response
  经过了前面的准备,可以进入计算NTLM的响应。NTLM响应分为NTLM v1,NTLMv2,NTLM session v2三种。具体使用哪种进行,由客户端和proxy进行协商确定,在讨论client和proxy的交互过程中,我们需能计算不同的response。
   服务器(Proxy)提供一个8-byte的challenge. 客户端用于进行LM response和NT response的计算,服务器自己也根据密码进行计算,校验和Client送过来的数值。LM and NT responses都是24bytes。
对于NTLM Response,也就是我们常说的NTLMv1的方式,可以表述如下:

  服务器发送一个8字节的随机数作为challenge。
  客户端返回48字节的response。下面描述NTLMv1的步骤:

步骤一:获取挑战值
从服务器中获取8字节的challenge:
C = 8-byte server challenge, random

步骤二:计算LM-HASH
根据用户密码计算LM-HASH,获得16字节的hash值,在后面加上5个空字节,总共21个字节,将21字节平均分割为三个7字节,然后进行DES计算:
K1 | K2 | K3 = LM-Hash | 5-bytes-0
R1 = DES(K1,C) | DES(K2,C) | DES(K3,C)

步骤三:计算NT-HASH
根据用户密码计算NT-HASH,获得16字节的hash值,在后面加上5个空字节,总共21个字节,将21字节平均分割为三个7字节,然后进行DES计算:
K1 | K2 | K3 = NT-Hash | 5-bytes-0
R2 = DES(K1,C) | DES(K2,C) | DES(K3,C)

步骤四:获取NTLMv1 response
response = R1 | R2

  我们在DES的算法中,key的输入是64比特,将这8个字节分别去掉CRC校验位(第8位),生成56比特的key。现在我们直接输入为56字节比特,可以通过LM-Hash中介绍的使用str_to_key函数,将这56比特的key变为64比特的key作为DES算法的输入,或者我们直接在DES算法中增加一个函数用于处理直接56比特key的输入。如下:
static int ip_56key_seq[] ={
    50,43,36,29,22,15,8,
    1, 51,44,37,30,23,16,
    9, 2, 52,45,38,31,24,
    17,10,3, 53,46,39,32,
    56,49,42,35,28,21,14,
    7, 55,48,41,34,27,20,
    13, 6,54,47,40,33,26,
    19,12,5, 25,18,11,4};

void algorithm_des_56key(IN unsigned char * src, IN unsigned char * secrect,
                         OUT unsigned char * dst){
    unsigned char s[64],key[64],L[32],R[32],K[48],E[48];
    int i = 0;

    storebit(src,8,s);
    storebit(secrect,8,key);
    //step1: initial permutation src and key
    initail_permutation(s,ip_data_seq,64,s);
    initail_permutation(key,ip_56key_seq,56,key);
   

    //step2:16次计算
    //获取原始的L0和R0
    memcpy(L,s,32);
    memcpy(R,s+32,32);

    //进行16次计算
    for(i = 0; i < 16 ; i++){
        //获取K
        getkey(key,key_offset[i]);
        initail_permutation(key,ip_key,48,K);

        //F计算
        initail_permutation(R,ip_e,48,E);
        xorbit(E,K,48,E);
        s_box_function(E,s1,E);
        s_box_function(E + 6,s2,E + 4);
        s_box_function(E + 12,s3,E + 8);
        s_box_function(E + 18,s4,E + 12);
        s_box_function(E + 24,s5,E + 16);
        s_box_function(E + 30,s6,E + 20);
        s_box_function(E + 36,s7,E + 24);
        s_box_function(E + 42,s8,E + 28);

        initail_permutation(E,ip_p,32,E);
        xorbit(E,L,32,E);
        memcpy(L,R,32);
        memcpy(R,E,32);
    }

    memcpy(s,R,32);
    memcpy(s+32,L,32);
    initail_permutation(s,inverse_ip_p,64,s);
    parsebit(s,dst,8);
}

下面,我们给出NTLM v1 reponse的程序:
void ntlmv1_response(IN char * passwd, IN int passwd_len,IN unsigned char * chanllenge,
                     OUT unsigned char * dst, OUT int * dst_len){
    unsigned char hash[21];

    //K1 | K2 | K3 = LM-Hash | 5-bytes-0
    //R1 = DES(K1,C) | DES(K2,C) | DES(K3,C)
    lm_hash(passwd, hash,NULL);
    memset(hash + 16,0,5);
    algorithm_des_56key(chanllenge, hash,dst);
    algorithm_des_56key(chanllenge, hash+7,dst+8);
    algorithm_des_56key(chanllenge, hash+14,dst+16);

    //K1 | K2 | K3 = NT-Hash | 5-bytes-0
    //R2 = DES(K1,C) | DES(K2,C) | DES(K3,C)
    nt_hash(passwd, 0,hash,NULL);
    memset(hash + 16,0,5);
    algorithm_des_56key(chanllenge,hash,dst+24);
    algorithm_des_56key(chanllenge,hash+7,dst+32);
    algorithm_des_56key(chanllenge,hash+14,dst+40);

    if(dst_len != NULL)
        * dst_len = 48;
}

  NTLM v1响应实际上是将LM响应和NT响应合并发送,这是最容易计算的。LM是用于最古老的客户端,是最原始的响应类型;NT响应,也叫做NTLM响应,用于基于NT的客户端,例如Windows 2000和XP。如果我们进行抓包,大部分Proxy穿越不使用NTLMv1这个古老的方式,当然如果自己编写的客户端使用这种方式,并成功和Proxy协商,穿越没有问题。
实现PROXY穿越(10):NTLMv2 response
  NTLMv1由两部分组成,LM response和NTLM response。NTLMv2也一样,分为LMv2 response和NTLMv2 response。这里使用了一个HMAC-MD5的计算,需要从RFC2104的附录中获得算法的C代码。需要注意两点:
  • 函数参数类型caddr_t实际是unsigned char *
  • bzero和bcopy在VC中需要使用memset和memcpy来代替,其中memcpy和bcopy的参数src和dst的位置是颠倒的,需要特别注意。我就将顺序搞反了,导致down掉。
  对于NTLM的详细说明可以参考http://davenport.sourceforge.net/ntlm.html,虽然是英文、洋文、鸡肠文,还是很值得读一读。NTLMv2的输入参数比较多:
  1. 密码: passwd,假设密码为:SecREt01
  2. 用户名:user_name,假设为user
  3. 域名:domain,假设为DOMAIN
  4. 从server中获得的challenge,固定长度为8,假设为0x0123456789abcdef
  5. 从server中获得的target_info,长度不固定,因此需要一个表示长度的target_info_len,假设为:0x02000c0044004f00 4d00410049004e00 01000c0053004500 5200560045005200 0400140064006f00 6d00610069006e00 2e0063006f006d00 0300220073006500 7200760065007200 2e0064006f006d00 610069006e002e00 63006f006d000000 0000
  6. 客户端自己生成的8位client_nonce,假设为0xff ff ff 00 11 22 33 44
  输出如下:
  1. NTLMv2 response,长度不固定,需要给出len
  2. LMv2 response,长度为24
void ntlmv2_response(IN char * passwd, IN char * user_name,IN char * domain,
                     IN unsigned char * chanllenge, IN unsigned char * target_info,
                     IN int target_info_len,IN unsigned char * client_nonce,
                     OUT unsigned char * ntlm_response, OUT int * ntlm_response_len,
                     OUT unsigned char * lm_response,   OUT int * lm_response_len){
    unsigned char hash[16],digest[16];
    unsigned char * name;
    int len ;
    long cur_time = 0;
    __int64 t;
}

步骤一:v2-Hash = HMAC-MD5(NT-Hash, user name, domain name)
step1:获取NT-HASH,存放在digest中
nt_hash(passwd, 0,digest,NULL);
digest = 0xcd 06 ca 7c 7e 10 c9 9b 1d 33 b7 48 5a 2e d8 08
step2:获取v2-Hash
将user和domain合并然后全部改为大写换为unicode的测试,例如user=“user”,domain=“domain”,合并为USERDOMAIN为此我们提供了一个函数:
static void ntlmv2_unicode(IN char * user_name, IN char * domain,
                                       OUT char * user){
    char * temp;
    int len1 = strlen(user_name);
    int len2 = strlen(domain);

    temp = ( char *) malloc(len1 + len2 + 1);
    strcpy(temp,user_name);
    strcat(temp,domain);
    strtoupper(temp);
    unicode(temp,len1 + len2,user,NULL);
    free(temp);
}
得到的值使用step1计算出来的digest作为key进行HMAC_MD5计算,得到的值放置在hash中。程序如下:
    len = (strlen(user_name) + strlen(domain)) * 2;
    name = (unsigned char *) malloc(len);
    ntlmv2_unicode(user_name,domain,(char *)name);
    HMAC_MD5(name, len, digest,16,hash);
    free(name);
得到hash=0x04 b8 e0 ba 74 28 9c c5 40 82 6b ab 1d ee 63 ae

步骤二:计算NTLMv2 response:NTv2 = HMAC-MD5(v2-Hash, CS, CC*)
step1:我们需要根据系统时间、challenge、client_nonce、target_info获得一个blob的数据,用于后面的HMAC_MD5计算,及CC* = (X, time, CC, domain name)
blob的格式为:1-8字节为chanllenge;9-12字节为0x010100,17-24字节为根据当前时间获得一个数值;25-32字节为client_nonce,第37字节开始为target_info,在target_info后补充4个空字节。没有提及的字节填写0。
时间值的计算如下:当前时间(1970年1月1日开始计算的秒)+11644473600,成为1601年1月1日开始计算的秒,然后乘以10000000(1E+7)。获得的64比特的整数。要注意long的长度为32比特,不足以存放,我们使用__int64来解决这个问题。
cur_time = time(NULL);
t = (cur_time + 11644473600) * 10000000;
blob的获取如下:
len = 40 + target_info_len ;
name = (unsigned char *)malloc(len);
memset(name,0,len);
memcpy(name,chanllenge,8);
name[8] = 0x01;
name[9] = 0x01;
memcpy(name + 16, &t , 8);
memcpy(name + 24,client_nonce,8);
memcpy(name + 36,target_info,target_info_len);
以我们提供的例子,得到的blob长度为138,cur_time=1229419943,为:
 01 23 45 67 89 ab cd ef 01 01 00 00 00 00 00 00
 80 2d 11 33 61 5f c9 01 ff ff ff 00 11 22 33 44
 00 00 00 00 02 00 0c 00 44 00 4f 00 4d 00 41 00
 49 00 4e 00 01 00 0c 00 53 00 45 00 52 00 56 00
 45 00 52 00 04 00 14 00 64 00 6f 00 6d 00 61 00
 69 00 6e 00 2e 00 63 00 6f 00 6d 00 03 00 22 00
 73 00 65 00 72 00 76 00 65 00 72 00 2e 00 64 00
 6f 00 6d 00 61 00 69 00 6e 00 2e 00 63 00 6f 00
 6d 00 00 00 00 00 00 00 00 00
step2:计算ntlmv2 response
HMAC_MD5(name, len, hash,16,ntlm_response);
获得16字节的数据:0x3f 7e 49 5e 5c 39 c1 46 34 28 54 00 4f a4 5a 26,用其代替blob原来0-8字节的数据数据,就是我们最后得到的ntlmv2_response;
    memcpy(ntlm_response + 16,name + 8,len - 8);
    if(ntlm_response_len != NULL)
        * ntlm_response_len = len + 8;
    free(name);

步骤三:计算LMv2的response,LMv2 = HMAC-MD5(v2-Hash, CS, CC)
  计算LMv2 response比较简单,固定长度为24字节。
step1:将挑战值和client_nonce合并,使用步骤一的hash作为key进行HMAC_MD5计算
step2:将步骤一得到的16字节长度加上8字节的client_nonce就是LMv2的response。
    name = (unsigned char *) malloc(16);
    memcpy(name,chanllenge,8);
    memcpy(name + 8,client_nonce,8);
    HMAC_MD5(name, 16, hash,16,lm_response);
    memcpy(lm_response + 16,client_nonce,8);
    if(lm_response_len != NULL)
        * lm_response_len = 24;
    free(name);
根据我们的输入值,得到24字节的结果:0xd6 e6 15 2e a2 5d 03 b7 c6 ba 66 29 c2 d6 aa f0 ff ff ff 00 11 22 33 44

^_^,终于完成了NTLMv2应答值的计算,擦把汗,发现还有NTLM v2 session的方式,这个世界就是这样,有人在不断地加密,有人在不断地解密,将事情搞得越来越复杂。因为大家都很无聊,就搞出更多无聊的事情,也就有更多的人可以混饭吃。

现PROXY穿越(11):NTLMv2 session response
  终于到来分析NTLMv2session response了。解决这个问题,我们就完成对PROXY认证中相关的NTLM算法的编写,可以进入PROXY认证交换过程,实现我们的网络编程。数一下,捣腾这个问题可以快半个月,主要在DES的算法上,在网上也看到有人很绝望地呼喊为什么不同工具算出来的结果不一样。而且一直没有下定决心自己写一个。原来想在网上找一个现成的代码,例如openssl,但是挖个萝卜带框泥,就拿DES算法,我已经在project中导入十数个openssl的源代码,发现还不行,还有函数没导入,于是乎搞得源码乱糟糟,看来还是自己弄一个算了,这样才开始了NTLM的大学习。今天终于完成了NTLM v2 session response的计算,算法阶段可以竣工。至于kerberos,我想在NTLM认证通过后,再作考虑。
  NTLMv2 session比NTLMv2要简单,输入参数为:
  • client的password,为了配合验证,我们给出例子:SecREt01
  • 来自server的挑战值chanllenge,例如0x01 23 45 67 89 ab cd ef
  • client自己生成的8字节的nonce,记做client_nonce,例如0xff ff ff 00 11 22 33 44
   输出有两个:ntlm response和lm response,长度据为24字节。函数定义为:
void ntlmv2_session_response(IN char * passwd, IN unsigned char * chanllenge,
                   IN unsigned char * client_nonce,
                   OUT unsigned char * ntlm_response, OUT int * ntlm_response_len,
                   OUT unsigned char * lm_response,OUT int * lm_response_len);

步骤一:LM response的获取
这步骤很简单,比NTLMv1的LM响应值都简单。在8字节的client_nonce后面,补充16个0字节作为pad,生成一个24字节的数据,即为所求。
    memset(lm_response,0,24);
    memcpy(lm_response,client_nonce,8);
    if(lm_response_len != NULL)
        * lm_response_len = 24;
即:0xff   ff   ff  00 11 22 33 44
          00 00 00 00 00 00 00 00
          00 00 00 00 00 00 00 00

步骤二:NTLM response的获取
step1:计算NTLMv2 session的hash值,MD5(chanllenge+client_nonce)
将server给的8位挑战值和客户端自己生成的8位nonce值,合并为一个16字节的数据,通过MD5,生成一个新的16字节数据,截取前面的8字节,就是NTLMv2 session的hash值。存放在hash[16]中。
    memcpy(buf,chanllenge,8);
    memcpy(buf+8,client_nonce,8);
    MD5String((char*)buf,16,hash);
即:0xbe ac 9a 1b c5 a9 86 7c,后面8字节的数据不参与后面的运算
step2:对password进行加密
将password转换为unicode的方式,然后对它进行MD4的处理,所得的16字节数据为所求,存放在buf[21]中
    len = strlen(passwd);
    c = (unsigned char *) malloc(len * 2);
    unicode(passwd, len, (char *)c, NULL);
    memset(buf,0,21);
    MD4String((char*)c,2 * len ,buf);
    free(c);
即:0xcd 06 ca 7c 7e 10 c9 9b 1d 33 b7 48 5a 2e d8 08
step3:通过DES计算响应值
这个步骤和NTLMv1的计算有些类似,在step2中我们根据passwd得到了16字节的数据,放置在buf中,增加5个null的pad,获得21字节的数据。这些我们在step2中已经进行了处理。将这21字节的数据,均分为3份,每份7字节56比特,作为DES加密中的key,分布对step1计算出来的hash的前8字节数据进行DES加密,然后组合起来,就是最终的NTLMv2 response值。
    algorithm_des_56key(hash,buf,ntlm_response);
    algorithm_des_56key(hash,buf +7 ,ntlm_response+8);
    algorithm_des_56key(hash,buf +14 ,ntlm_response+16);
    if(ntlm_response_len != NULL)
        * ntlm_response_len = 24;
0x10 d5 50 83 2d 12 b2 cc
  b7 9d 5a d1 f4 ee d3 df
  82 ac a4 c3 68 1d d4 55

posted on 2009-05-20 16:44 露露 阅读(551) 评论(0)  编辑 收藏 引用