/*
* Beware the LM hash is easy to crack (google for l0phtCrack)
* and though NTLM is more secure it is still breakable.
* Ntlmv2 is better and seen as good enough by the Windows community.
* For real security use Kerberos.
*/
#include <u.h>
#include <libc.h>
#include <mp.h>
#include <auth.h>
#include <libsec.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "cifs.h"
#define NTLMV2_TEST 1
#define DEF_AUTH "ntlmv2"
static enum {
MACkeylen = 40, /* MAC key len */
MAClen = 8, /* signature length */
MACoff = 14, /* sign. offset from start of SMB (not netbios) pkt */
Bliplen = 8, /* size of LMv2 client nonce */
};
static void
dmp(char *s, int seq, void *buf, int n)
{
int i;
char *p = buf;
print("%s %3d ", s, seq);
while(n > 0){
for(i = 0; i < 16 && n > 0; i++, n--)
print("%02x ", *p++ & 0xff);
if(n > 0)
print("\n");
}
print("\n");
}
static Auth *
auth_plain(char *windom, char *keyp, uchar *chal, int len)
{
UserPasswd *up;
static Auth *ap;
USED(chal, len);
up = auth_getuserpasswd(auth_getkey, "windom=%s proto=pass service=cifs %s",
windom, keyp);
if(! up)
sysfatal("cannot get key - %r");
ap = emalloc9p(sizeof(Auth));
memset(ap, 0, sizeof(ap));
ap->user = estrdup9p(up->user);
ap->windom = estrdup9p(windom);
ap->resp[0] = estrdup9p(up->passwd);
ap->len[0] = strlen(up->passwd);
memset(up->passwd, 0, strlen(up->passwd));
free(up);
return ap;
}
static Auth *
auth_lm_and_ntlm(char *windom, char *keyp, uchar *chal, int len)
{
int err;
Auth *ap;
char user[64];
MSchapreply mcr;
err = auth_respond(chal, len, user, sizeof user, &mcr, sizeof mcr,
auth_getkey, "windom=%s proto=mschap role=client service=cifs %s",
windom, keyp);
if(err == -1)
sysfatal("cannot get key - %r");
ap = emalloc9p(sizeof(Auth));
memset(ap, 0, sizeof(ap));
ap->user = estrdup9p(user);
ap->windom = estrdup9p(windom);
/* LM response */
ap->len[0] = sizeof(mcr.LMresp);
ap->resp[0] = emalloc9p(ap->len[0]);
memcpy(ap->resp[0], mcr.LMresp, ap->len[0]);
/* NTLM response */
ap->len[1] = sizeof(mcr.NTresp);
ap->resp[1] = emalloc9p(ap->len[1]);
memcpy(ap->resp[1], mcr.NTresp, ap->len[1]);
return ap;
}
/*
* NTLM response only, the LM response is a just
* copy of the NTLM one. We do this because the lm
* response is easily reversed - Google for l0pht for more info.
*/
static Auth *
auth_ntlm(char *windom, char *keyp, uchar *chal, int len)
{
Auth *ap;
if((ap = auth_lm_and_ntlm(windom, keyp, chal, len)) == nil)
return nil;
free(ap->resp[0]);
ap->len[0] = ap->len[1];
ap->resp[0] = emalloc9p(ap->len[0]);
memcpy(ap->resp[0], ap->resp[1], ap->len[0]);
return ap;
}
/*
* This is not really nescessary as all fields hmac_md5'ed
* in the ntlmv2 protocol are less than 64 bytes long, however
* I still do this for completeness.
*/
static DigestState *
hmac_t64(uchar *data, ulong dlen, uchar *key, ulong klen, uchar *digest,
DigestState *state)
{
if(klen > 64)
klen = 64;
return hmac_md5(data, dlen, key, klen, digest, state);
}
static int
ntv2_blob(uchar *blob, int len, char *windom)
{
uvlong t;
uchar *p;
enum { /* name types */
Beof, /* end of name list */
Bhost, /* Netbios host name */
Bdomain, /* Windows Domain name (NT) */
Bdnshost, /* DNS host name */
Bdnsdomain, /* DNS domain name */
};
p = blob;
*p++ = 1; /* 8bit: response type */
*p++ = 1; /* 8bit: max response type understood by client */
*p++ = 0; /* 16bit: reserved */
*p++ = 0;
*p++ = 0; /* 32bit: unknown */
*p++ = 0;
*p++ = 0;
*p++ = 0;
#ifdef NTLMV2_TEST
*p++ = 0xf0;
*p++ = 0x20;
*p++ = 0xd0;
*p++ = 0xb6;
*p++ = 0xc2;
*p++ = 0x92;
*p++ = 0xbe;
*p++ = 0x01;
#else
t = time(nil); /* 64bit: time in NT format */
t += 11644473600LL;
t *= 10000000LL;
*p++ = t;
*p++ = t >> 8;
*p++ = t >> 16;
*p++ = t >> 24;
*p++ = t >> 32;
*p++ = t >> 40;
*p++ = t >> 48;
*p++ = t >> 56;
#endif
#ifdef NTLMV2_TEST
*p++ = 0x05;
*p++ = 0x83;
*p++ = 0x32;
*p++ = 0xec;
*p++ = 0xfa;
*p++ = 0xe4;
*p++ = 0xf3;
*p++ = 0x6d;
#else
genrandom(p, 8);
p += 8; /* 64bit: client nonce */
#endif
*p++ = 0; /* 32bit: unknown data */
*p++ = 0;
*p++ = 0;
*p++ = 0;
p += putname(p, len - (p-blob), windom, Bdomain);
p += putname(p, len - (p-blob), "", Beof);
return p - blob;
}
static Auth *
auth_ntlmv2(char *windom, char *keyp, uchar *chal, int len)
{
int i, n;
Rune r;
char *p, *u;
uchar c, lm_hmac[MD5dlen], nt_hmac[MD5dlen], nt_sesskey[MD5dlen];
uchar lm_sesskey[MD5dlen];
uchar v1hash[MD5dlen], blip[Bliplen], blob[1024], v2hash[MD5dlen];
DigestState *ds;
UserPasswd *up;
static Auth *ap;
up = auth_getuserpasswd(auth_getkey, "windom=%s proto=pass service=cifs-ntlmv2 %s",
windom, keyp);
if(! up)
sysfatal("cannot get key - %r");
#ifdef NTLMV2_TEST
{
static uchar srvchal[] = { 0x52, 0xaa, 0xc8, 0xe8, 0x2c, 0x06, 0x7f, 0xa1 };
up->user = "ADMINISTRATOR";
windom = "rocknroll";
chal = srvchal;
}
#endif
ap = emalloc9p(sizeof(Auth));
memset(ap, 0, sizeof(ap));
/* Standard says unlimited length, experience says 128 max */
if((n = strlen(up->passwd)) > 128)
n = 128;
ds = md4(nil, 0, nil, nil);
for(i = 0, p = up->passwd; i < n; i++) {
p += chartorune(&r, p);
c = r;
md4(&c, 1, nil, ds);
c = r >> 8;
md4(&c, 1, nil, ds);
}
md4(nil, 0, v1hash, ds);
#ifdef NTLMV2_TEST
{
uchar v1[] = {
0x0c, 0xb6, 0x94, 0x88, 0x05, 0xf7, 0x97, 0xbf,
0x2a, 0x82, 0x80, 0x79, 0x73, 0xb8, 0x95, 0x37
;
memcpy(v1hash, v1, sizeof(v1));
}
#endif
/*
* Some documentation insists that the username must be forced to
* uppercase, but the domain name should not be. Other shows both
* being forced to uppercase. I am pretty sure this is irrevevant as
* the domain name passed from the remote server always seems to be in
* uppercase already.
*/
ds = hmac_t64(nil, 0, v1hash, MD5dlen, nil, nil);
u = up->user;
while(*u){
u += chartorune(&r, u);
r = toupperrune(r);
c = r & 0xff;
hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
c = r >> 8;
hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
}
u = windom;
while(*u){
u += chartorune(&r, u);
c = r;
hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
c = r >> 8;
hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
}
hmac_t64(nil, 0, v1hash, MD5dlen, v2hash, ds);
#ifdef NTLMV2_TEST
print("want: 40 e1 b3 24...\n");
dmp("v2hash==kr", 0, v2hash, MD5dlen);
#endif
ap->user = estrdup9p(up->user);
ap->windom = estrdup9p(windom);
/* LM v2 */
genrandom(blip, Bliplen);
#ifdef NTLMV2_TEST
{
uchar t[] = { 0x05, 0x83, 0x32, 0xec, 0xfa, 0xe4, 0xf3, 0x6d };
memcpy(blip, t, 8);
}
#endif
ds = hmac_t64(chal, len, v2hash, MD5dlen, nil, nil);
hmac_t64(blip, Bliplen, v2hash, MD5dlen, lm_hmac, ds);
ap->len[0] = MD5dlen+Bliplen;
ap->resp[0] = emalloc9p(ap->len[0]);
memcpy(ap->resp[0], lm_hmac, MD5dlen);
memcpy(ap->resp[0]+MD5dlen, blip, Bliplen);
#ifdef NTLMV2_TEST
print("want: 38 6b ae...\n");
dmp("lmv2 resp ", 0, lm_hmac, MD5dlen);
#endif
/* LM v2 session key */
hmac_t64(lm_hmac, MD5dlen, v2hash, MD5dlen, lm_sesskey, nil);
/* LM v2 MAC key */
ap->mackey[0] = emalloc9p(MACkeylen);
memcpy(ap->mackey[0], lm_sesskey, MD5dlen);
memcpy(ap->mackey[0]+MD5dlen, ap->resp[0], MACkeylen-MD5dlen);
/* NTLM v2 */
n = ntv2_blob(blob, sizeof(blob), windom);
ds = hmac_t64(chal, len, v2hash, MD5dlen, nil, nil);
hmac_t64(blob, n, v2hash, MD5dlen, nt_hmac, ds);
ap->len[1] = MD5dlen+n;
ap->resp[1] = emalloc9p(ap->len[1]);
memcpy(ap->resp[1], nt_hmac, MD5dlen);
memcpy(ap->resp[1]+MD5dlen, blob, n);
#ifdef NTLMV2_TEST
print("want: 1a ad 55...\n");
dmp("ntv2 resp ", 0, nt_hmac, MD5dlen);
#endif
/* NTLM v2 session key */
hmac_t64(nt_hmac, MD5dlen, v2hash, MD5dlen, nt_sesskey, nil);
/* NTLM v2 MAC key */
ap->mackey[1] = emalloc9p(MACkeylen);
memcpy(ap->mackey[1], nt_sesskey, MD5dlen);
memcpy(ap->mackey[1]+MD5dlen, ap->resp[1], MACkeylen-MD5dlen);
free(up);
return ap;
}
struct {
char *name;
Auth *(*func)(char *, char *, uchar *, int);
} methods[] = {
{ "plain", auth_plain },
{ "lm+ntlm", auth_lm_and_ntlm },
{ "ntlm", auth_ntlm },
{ "ntlmv2", auth_ntlmv2 },
// { "kerberos", auth_kerberos },
};
void
autherr(void)
{
int i;
fprint(2, "supported auth methods:\t");
for(i = 0; i < nelem(methods); i++)
fprint(2, "%s ", methods[i].name);
fprint(2, "\n");
exits("usage");
}
Auth *
getauth(char *name, char *windom, char *keyp, int secmode, uchar *chal, int len)
{
int i;
Auth *ap;
if(name == nil){
name = DEF_AUTH;
if((secmode & SECMODE_PW_ENCRYPT) == 0)
sysfatal("plaintext authentication required, use '-a plain'");
}
ap = nil;
for(i = 0; i < nelem(methods); i++)
if(strcmp(methods[i].name, name) == 0){
ap = methods[i].func(windom, keyp, chal, len);
break;
}
if(! ap){
fprint(2, "%s: %s - unknown auth method\n", argv0, name);
autherr(); /* never returns */
}
return ap;
}
static int
genmac(uchar *buf, int len, int seq, uchar key[MACkeylen], uchar mine[MAClen])
{
DigestState *ds;
uchar *sig, digest[MD5dlen], their[MAClen];
sig = buf+MACoff;
memcpy(their, sig, MAClen);
memset(sig, 0, MAClen);
sig[0] = seq;
sig[1] = seq >> 8;
sig[2] = seq >> 16;
sig[3] = seq >> 24;
ds = md5(key, MACkeylen, nil, nil);
md5(buf, len, nil, ds);
md5(nil, 0, digest, ds);
memcpy(mine, digest, MAClen);
return memcmp(their, mine, MAClen);
}
int
macsign(Pkt *p)
{
int i, len;
uchar *sig, *buf, mac[MAClen], zeros[MACkeylen];
sig = p->buf + NBHDRLEN + MACoff;
buf = p->buf + NBHDRLEN;
len = (p->pos - p->buf) - NBHDRLEN;
for(i = -3; i < 4; i++){
memset(zeros, 0, sizeof(zeros));
if(genmac(buf, len, p->seq+i, zeros, mac) == 0){
dmp("got", 0, buf, len);
dmp("Zero OK", p->seq, mac, MAClen);
return 0;
}
if(genmac(buf, len, p->seq+i, p->s->auth->mackey[0], mac) == 0){
dmp("got", 0, buf, len);
dmp("LM-hash OK", p->seq, mac, MAClen);
return 0;
}
if(genmac(buf, len, p->seq+i, p->s->auth->mackey[1], mac) == 0){
dmp("got", 0, buf, len);
dmp("NT-hash OK", p->seq, mac, MAClen);
return 0;
}
}
genmac(buf, len, p->seq, p->s->auth->mackey[0], mac);
memcpy(sig, mac, MAClen);
return -1;
}
|