Commit fc4c22f7 authored by jan.koester's avatar jan.koester
Browse files

research: add verified NETLOGON NTLM pass-through prototype

Standalone prototype (built and run against a real Samba AD DC inside the
FenrirOS build-chroot) of the NETLOGON secure-channel client for checking
domain users' NTLM responses: DCE/RPC + NDR + netsec AES schannel sign/seal
+ SEC_VT verification trailer + NetrLogonSamLogonEx, all hand-written on
libnetplus. Kept as the reference for the src/netlogon.* implementation.

Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 3ecdcf7e
Loading
Loading
Loading
Loading
+34 −0
Changes for research/netlogon/README.md: 34 added lines, 0 removed lines.
Original line number Diff line number Diff line
# NETLOGON NTLM pass-through — working prototype

`netlogon_proto.cpp` is a standalone, **verified-against-real-Samba** prototype of
the NETLOGON secure-channel client adconnect needs to check the NTLM responses of
domain users on a domain controller (for ksmbd/sharedd SMB logins that don't use
Kerberos). It was built and run inside the FenrirOS build-chroot, linked against
libnetplus (AES, HMAC-SHA256); the DCE/RPC, NDR, MD4/MD5/HMAC-MD5 and netsec
schannel sign+seal are hand-written here.

Proven flow against a real Samba 4.22 AD DC (strict schannel, no relaxation):
- DCE/RPC bind (ncacn_ip_tcp, no endpoint mapper — Samba's `rpc server port`)
- netr_ServerReqChallenge (opnum 4) + netr_ServerAuthenticate3 (opnum 26),
  AES session key = HMAC-SHA256(NThash, clientchal||serverchal)[:16],
  credential = AES-128-CFB8(sessionkey, challenge); server credential verified.
- netsec schannel alter_context with NL_AUTH_MESSAGE.
- netsec AES sign+seal per Samba libcli/auth/schannel_sign.c
  (see ref-samba-schannel_sign.c).
- DCE/RPC SEC_VT verification trailer (MS-RPCE 2.2.2.13); the BITMASK_1 value
  must be 0 (claiming header signing without signing the header is rejected).
- netr_LogonSamLogonEx (opnum 39) with a NetlogonNetworkInformation NTLMv2 logon
  -> NT_STATUS 0x00000000 (domain user validated).

Gotchas that cost real time (also in the project memory):
- netr_SchannelType is enum16 (2 bytes).
- NDR conformant/varying strings have no trailing padding; align before each field.
- The client challenge must be random (Samba's ZeroLogon/CVE-2020-1472 check).
- Samba requires real netlogon schannel for SamLogonEx (NTLMSSP/Kerberos-bound
  pipe is refused) -> netsec is mandatory.

Next: port into src/netlogon.{h,cpp} using netplus::tcp (not the raw socket used
here for debugging), extract the user session key from the SamInfo4 validation,
and expose an NTLM command on adconnectd's socket for sharedd. The NetworkInfo
NDR here reuses a captured reference stub with the names substituted; the real
client builds it field by field.
+309 −0
Changes for research/netlogon/netlogon_proto.cpp: 309 added lines, 0 removed lines.
Original line number Diff line number Diff line
// NETLOGON handshake milestone: DCE/RPC bind + ServerReqChallenge +
// ServerAuthenticate3 (AES session key), verify the server credential.
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <stdexcept>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <fcntl.h>
#include <cmath>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netplus/crypto/aes.h>
#include <netplus/crypto/sha.h>

using Bytes = std::string;
static void p16(Bytes&b,uint16_t v){b+=(char)(v&0xff);b+=(char)(v>>8);}
static void p32(Bytes&b,uint32_t v){for(int i=0;i<4;i++)b+=(char)(v>>(8*i));}
static uint16_t g16(const uint8_t*p){return p[0]|p[1]<<8;}
static uint32_t g32(const uint8_t*p){return p[0]|p[1]<<8|p[2]<<16|(uint32_t)p[3]<<24;}
static void align4(Bytes&b){while(b.size()%4)b+=(char)0;}

static Bytes utf16(const std::string&s){Bytes o;for(char c:s){o+=c;o+=(char)0;}return o;}

// [ref] conformant+varying UTF16 string with trailing NUL
static void ndrRefStr(Bytes&b,const std::string&s){
    align4(b); // conformant array: 4-align the max_count; strings get no trailing pad
    uint32_t n=(uint32_t)s.size()+1; // incl NUL
    p32(b,n);p32(b,0);p32(b,n);
    for(char ch:s){b+=ch;b+=(char)0;} b+=(char)0;b+=(char)0; // UTF16LE + NUL
}
// [unique] string, NULL -> referent 0
static void ndrUniqStr(Bytes&b,const std::string*s,uint32_t&refid){
    if(!s){p32(b,0);return;}
    p32(b,refid);refid+=4;
    ndrRefStr(b,*s);
}

// MD4 (NT hash)
static Bytes md4(const Bytes&msg){
    auto R=[](uint32_t x,int c){return (x<<c)|(x>>(32-c));};
    uint32_t a=0x67452301,b=0xefcdab89,c=0x98badcfe,d=0x10325476;
    Bytes m=msg;uint64_t bl=(uint64_t)msg.size()*8;m+=(char)0x80;while(m.size()%64!=56)m+=(char)0;
    for(int i=0;i<8;i++)m+=(char)(bl>>(8*i));
    for(size_t off=0;off<m.size();off+=64){
        uint32_t X[16];for(int i=0;i<16;i++)X[i]=g32((const uint8_t*)m.data()+off+i*4);
        uint32_t A=a,B=b,C=c,D=d;
        auto F=[&](uint32_t x,uint32_t y,uint32_t z){return (x&y)|(~x&z);};
        auto G=[&](uint32_t x,uint32_t y,uint32_t z){return (x&y)|(x&z)|(y&z);};
        auto H=[&](uint32_t x,uint32_t y,uint32_t z){return x^y^z;};
        int r1[]={3,7,11,19};
        for(int i=0;i<16;i++){int k=i;int s=r1[i%4];uint32_t t=A+F(B,C,D)+X[k];A=D;D=C;C=B;B=R(t,s);}
        int o2[]={0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15};int r2[]={3,5,9,13};
        for(int i=0;i<16;i++){int k=o2[i];int s=r2[i%4];uint32_t t=A+G(B,C,D)+X[k]+0x5a827999u;A=D;D=C;C=B;B=R(t,s);}
        int o3[]={0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15};int r3[]={3,9,11,15};
        for(int i=0;i<16;i++){int k=o3[i];int s=r3[i%4];uint32_t t=A+H(B,C,D)+X[k]+0x6ed9eba1u;A=D;D=C;C=B;B=R(t,s);}
        a+=A;b+=B;c+=C;d+=D;
    }
    Bytes o;for(uint32_t v:{a,b,c,d})p32(o,v);return o;
}

static std::vector<uint8_t> vb(const Bytes&s){return std::vector<uint8_t>(s.begin(),s.end());}
static Bytes sb(const std::vector<uint8_t>&v){return Bytes(v.begin(),v.end());}

// AES-128-CFB8 encrypt (IV 16x0), return same length as input
static Bytes aesCfb8(const Bytes&key16,const Bytes&in){
    netplus::aes128 c(vb(key16));
    uint8_t sr[16]; memset(sr,0,16);
    Bytes out;
    for(unsigned char pc:in){
        uint8_t ks[16]; c.encrypt_ecb(sr,ks);
        uint8_t cb=pc^ks[0];
        out+=(char)cb;
        memmove(sr,sr+1,15); sr[15]=cb;
    }
    return out;
}
static Bytes sessionKey(const Bytes&nthash,const Bytes&cc,const Bytes&sc){
    auto h=netplus::hmac_sha256(vb(nthash),vb(cc+sc));
    return sb(h).substr(0,16);
}
static Bytes credential(const Bytes&sk,const Bytes&chal8){return aesCfb8(sk,chal8).substr(0,8);}


// ---- MD5 + HMAC-MD5 (for NTLMv2) ----
static Bytes md5(const Bytes&msg){
    auto R=[](uint32_t x,int c){return (x<<c)|(x>>(32-c));};
    static const int S[]={7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
        4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21};
    static uint32_t K[64]; static bool ki=false;
    if(!ki){for(int i=0;i<64;i++)K[i]=(uint32_t)(fabs(sin(i+1.0))*4294967296.0);ki=true;}
    uint32_t a0=0x67452301,b0=0xefcdab89,c0=0x98badcfe,d0=0x10325476;
    Bytes m=msg; uint64_t bl=(uint64_t)msg.size()*8; m+=(char)0x80; while(m.size()%64!=56)m+=(char)0;
    for(int i=0;i<8;i++)m+=(char)(bl>>(8*i));
    for(size_t off=0;off<m.size();off+=64){
        uint32_t M[16]; for(int i=0;i<16;i++)M[i]=g32((const uint8_t*)m.data()+off+i*4);
        uint32_t A=a0,B=b0,C=c0,D=d0;
        for(int i=0;i<64;i++){uint32_t F;int g;
            if(i<16){F=(B&C)|(~B&D);g=i;}
            else if(i<32){F=(D&B)|(~D&C);g=(5*i+1)%16;}
            else if(i<48){F=B^C^D;g=(3*i+5)%16;}
            else{F=C^(B|~D);g=(7*i)%16;}
            F=F+A+K[i]+M[g]; A=D;D=C;C=B; B=B+R(F,S[i]);}
        a0+=A;b0+=B;c0+=C;d0+=D;
    }
    Bytes o; for(uint32_t v:{a0,b0,c0,d0})p32(o,v); return o;
}
static Bytes hmacMd5(const Bytes&key,const Bytes&msg){
    Bytes k=key; if(k.size()>64)k=md5(k); k.resize(64,0);
    Bytes o,i; for(int x=0;x<64;x++){o+=(char)(k[x]^0x5c);i+=(char)(k[x]^0x36);}
    return md5(o+md5(i+msg));
}
static Bytes utf16up(const std::string&s){Bytes o;for(char c:s){o+=(char)toupper((unsigned char)c);o+=(char)0;}return o;}

// ---- AES-128-CFB8 with IV ----
static void aesCfb8IV(const Bytes&key16,const uint8_t iv16[16],uint8_t*buf,size_t n,bool enc){
    netplus::aes128 c(vb(key16));
    uint8_t sr[16]; memcpy(sr,iv16,16);
    for(size_t i=0;i<n;i++){uint8_t ks[16];c.encrypt_ecb(sr,ks);
        uint8_t in=buf[i],out=(uint8_t)(in^ks[0]); uint8_t fb=enc?out:in;
        memmove(sr,sr+1,15); sr[15]=fb; buf[i]=out;}
}
// ---- netsec (schannel AES) sign+seal, per samba libcli/auth/schannel_sign.c ----
static void ssval(uint8_t*p,int o,uint16_t v){p[o]=(uint8_t)(v&0xff);p[o+1]=(uint8_t)(v>>8);}
static void netsecSign(const Bytes&sk,const uint8_t*conf,const uint8_t*data,size_t len,uint8_t header[8],uint8_t checksum[8]){
    ssval(header,0,0x0013); ssval(header,2,conf?0x001A:0xFFFF); ssval(header,4,0xFFFF); ssval(header,6,0x0000);
    Bytes m; m.append((char*)header,8); if(conf)m.append((char*)conf,8); m.append((char*)data,len);
    auto d=netplus::hmac_sha256(vb(sk),vb(m)); memcpy(checksum,d.data(),8);
}
// seal stub in place (forward), return 56-byte signature
static Bytes netsecSealOut(const Bytes&sk,uint64_t seq,uint8_t*stub,size_t len){
    uint8_t sq[8]; uint32_t s=(uint32_t)seq; sq[0]=s>>24;sq[1]=s>>16;sq[2]=s>>8;sq[3]=(uint8_t)s; sq[4]=0x80;sq[5]=0;sq[6]=0;sq[7]=0;
    uint8_t conf[8]; {int fd=::open("/dev/urandom",O_RDONLY);(void)!::read(fd,conf,8);::close(fd);}
    uint8_t header[8],checksum[8]; netsecSign(sk,conf,stub,len,header,checksum);
    Bytes key=sk; for(auto&ch:key)ch=(char)((unsigned char)ch^0xf0);
    uint8_t iv[16]; memcpy(iv,sq,8);memcpy(iv+8,sq,8);
    Bytes blob; blob.append((char*)conf,8); blob.append((char*)stub,len);
    aesCfb8IV(key,iv,(uint8_t*)blob.data(),blob.size(),true);
    memcpy(conf,blob.data(),8); memcpy(stub,blob.data()+8,len);
    uint8_t iv2[16]; memcpy(iv2,checksum,8);memcpy(iv2+8,checksum,8);
    aesCfb8IV(sk,iv2,sq,8,true);
    Bytes sig(56,'\0'); memcpy(&sig[0],header,8);memcpy(&sig[8],sq,8);memcpy(&sig[16],checksum,8);memcpy(&sig[24],conf,8);
    return sig;
}
static void netsecUnseal(const Bytes&sk,uint64_t seq,const uint8_t confWire[8],uint8_t*stub,size_t len){
    uint8_t sq[8]; uint32_t s=(uint32_t)seq; sq[0]=s>>24;sq[1]=s>>16;sq[2]=s>>8;sq[3]=(uint8_t)s; sq[4]=0;sq[5]=0;sq[6]=0;sq[7]=0;
    Bytes key=sk; for(auto&ch:key)ch=(char)((unsigned char)ch^0xf0);
    uint8_t iv[16]; memcpy(iv,sq,8);memcpy(iv+8,sq,8);
    Bytes blob; blob.append((char*)confWire,8); blob.append((char*)stub,len);
    aesCfb8IV(key,iv,(uint8_t*)blob.data(),blob.size(),false);
    memcpy(stub,blob.data()+8,len);
}

// ---- DCE/RPC ----
struct Rpc{
    uint32_t callid=1; int _fd=-1;
    void conn(const std::string&h,int port){
        _fd=::socket(AF_INET,SOCK_STREAM,0);
        sockaddr_in a{}; a.sin_family=AF_INET; a.sin_port=htons(port); inet_pton(AF_INET,h.c_str(),&a.sin_addr);
        if(::connect(_fd,(sockaddr*)&a,sizeof a)!=0) throw std::runtime_error("connect failed");
        struct timeval tv{15,0}; setsockopt(_fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof tv);
    }
    void sendAll(const Bytes&d){fprintf(stderr,"SEND %zu bytes: ",d.size());for(size_t i=0;i<d.size()&&i<16;i++)fprintf(stderr,"%02x",(unsigned char)d[i]);fprintf(stderr,"...\n");size_t off=0;while(off<d.size()){ssize_t n=::send(_fd,d.data()+off,d.size()-off,0);if(n<=0)throw std::runtime_error("send closed");off+=(size_t)n;}}
    Bytes _rx;
    Bytes recvN(size_t n){char b[16384];while(_rx.size()<n){ssize_t k=::recv(_fd,b,sizeof b,0);if(k<=0)throw std::runtime_error("recv closed/timeout");_rx.append(b,(size_t)k);}Bytes r=_rx.substr(0,n);_rx.erase(0,n);return r;}
    Bytes recvPdu(){
        Bytes hdr=recvN(16);
        uint16_t frag=g16((const uint8_t*)hdr.data()+8);
        Bytes rest=recvN(frag-16);
        return hdr+rest;
    }
    void bind(){ fprintf(stderr,"connecting done, sending bind\n");
        Bytes body;
        p16(body,5840);p16(body,5840);p32(body,0); // max xmit/recv, assoc group
        body+=(char)1;body+=(char)0;p16(body,0);    // n_ctx, reserved
        p16(body,0);body+=(char)1;body+=(char)0;     // ctx id 0, n_transfer 1, reserved
        // NETLOGON abstract syntax 12345678-1234-abcd-ef00-01234567cffb v1.0
        uint8_t ns[16]={0x78,0x56,0x34,0x12,0x34,0x12,0xcd,0xab,0xef,0x00,0x01,0x23,0x45,0x67,0xcf,0xfb};
        body.append((char*)ns,16);p32(body,1);
        // NDR transfer syntax 8a885d04-1ceb-11c9-9fe8-08002b104860 v2
        uint8_t ts[16]={0x04,0x5d,0x88,0x8a,0xeb,0x1c,0xc9,0x11,0x9f,0xe8,0x08,0x00,0x2b,0x10,0x48,0x60};
        body.append((char*)ts,16);p32(body,2);
        Bytes h; h+=(char)5;h+=(char)0;h+=(char)11;h+=(char)3; // ver,minor,ptype=bind,flags=first+last
        p32(h,0x00000010); p16(h,(uint16_t)(16+body.size()));p16(h,0);p32(h,callid++);
        sendAll(h+body);
        Bytes r=recvPdu();
        fprintf(stderr,"bind_ack %zu bytes ptype=%d\n",r.size(),(int)(uint8_t)r[2]); if((uint8_t)r[2]!=12) throw std::runtime_error("bind_ack expected, got ptype "+std::to_string((uint8_t)r[2]));
        assoc=g32((const uint8_t*)r.data()+20);
    }
    uint32_t assoc=0;
    void alterAuth(const Bytes&authval){
        Bytes body; p16(body,5840);p16(body,5840);p32(body,assoc);
        body+=(char)1;body+=(char)0;p16(body,0);
        p16(body,0);body+=(char)1;body+=(char)0;
        uint8_t ns[16]={0x78,0x56,0x34,0x12,0x34,0x12,0xcd,0xab,0xef,0x00,0x01,0x23,0x45,0x67,0xcf,0xfb};
        body.append((char*)ns,16);p32(body,1);
        uint8_t ts[16]={0x04,0x5d,0x88,0x8a,0xeb,0x1c,0xc9,0x11,0x9f,0xe8,0x08,0x00,0x2b,0x10,0x48,0x60};
        body.append((char*)ts,16);p32(body,2);
        Bytes sect; sect+=(char)68;sect+=(char)6;sect+=(char)0;sect+=(char)0;p32(sect,0);
        Bytes h; h+=(char)5;h+=(char)0;h+=(char)14;h+=(char)3; p32(h,0x00000010);
        p16(h,(uint16_t)(16+body.size()+8+authval.size())); p16(h,(uint16_t)authval.size()); p32(h,callid++);
        sendAll(h+body+sect+authval);
        Bytes r=recvPdu();
        if((uint8_t)r[2]!=15) throw std::runtime_error("alter_resp expected, got ptype "+std::to_string((uint8_t)r[2]));
    }
    Bytes callSealed(uint16_t opnum,Bytes stub,const Bytes&sk,uint64_t&seq){
        Bytes data=stub; while(data.size()%4)data+=(char)0;
        uint8_t padlen=(uint8_t)(data.size()-stub.size());
        Bytes sig=netsecSealOut(sk,seq++,(uint8_t*)data.data(),data.size());
        Bytes sect; sect+=(char)68;sect+=(char)6;sect+=(char)padlen;sect+=(char)0;p32(sect,0);
        Bytes h; h+=(char)5;h+=(char)0;h+=(char)0;h+=(char)3; p32(h,0x00000010);
        uint32_t fl=16+8+(uint32_t)data.size()+8+56; p16(h,(uint16_t)fl);p16(h,56);p32(h,callid++);
        Bytes req=h; p32(req,(uint32_t)stub.size());p16(req,0);p16(req,opnum); req+=data; req+=sect; req+=sig;
        sendAll(req);
        Bytes r=recvPdu();
        if((uint8_t)r[2]!=2){fprintf(stderr,"sealed resp ptype=%d: ",(int)(uint8_t)r[2]);for(unsigned char c:r)fprintf(stderr,"%02x",c);fprintf(stderr,"\n");if((uint8_t)r[2]==3&&r.size()>=28)fprintf(stderr,"fault 0x%08x\n",g32((const uint8_t*)r.data()+24));throw std::runtime_error("not a response(sealed)");}
        uint16_t al=g16((const uint8_t*)r.data()+10); size_t frag=r.size();
        size_t stubend=frag-8-al; Bytes rstub=r.substr(24,stubend-24);
        const uint8_t*rsig=(const uint8_t*)r.data()+frag-al;
        uint8_t padl=(uint8_t)r[frag-al-8+2];
        netsecUnseal(sk,seq++,rsig+24,(uint8_t*)rstub.data(),rstub.size());
        if(padl<=rstub.size()) rstub.resize(rstub.size()-padl);
        return rstub;
    }
    Bytes call(uint16_t opnum,const Bytes&stub){
        Bytes h; h+=(char)5;h+=(char)0;h+=(char)0;h+=(char)3; // ptype=request
        p32(h,0x00000010);
        uint32_t fl=16+8+(uint32_t)stub.size();
        p16(h,(uint16_t)fl);p16(h,0);p32(h,callid++);
        Bytes req=h; p32(req,(uint32_t)stub.size());p16(req,0);p16(req,opnum);req+=stub;
        sendAll(req);
        Bytes r=recvPdu();
        if((uint8_t)r[2]!=2){fprintf(stderr,"PDU ptype=%d len=%zu: ",(int)(uint8_t)r[2],r.size());for(unsigned char c:r)fprintf(stderr,"%02x",c);fprintf(stderr,"\n");if((uint8_t)r[2]==3&&r.size()>=28)fprintf(stderr,"fault status=0x%08x\n",g32((const uint8_t*)r.data()+24));throw std::runtime_error("not a response");}
        return r.substr(24); // skip 16 hdr + 8 response hdr
    }
};

int main(int argc,char**argv){ setvbuf(stdout,0,_IONBF,0);
    std::string host=argc>1?argv[1]:"127.0.0.1"; int port=argc>2?atoi(argv[2]):49152;
    std::string machinePw=argc>3?argv[3]:"Mach1ne.Pwd.Test";
    std::string computer="FENRIRT", account="FENRIRT$";
    Bytes nthash=md4(utf16(machinePw));
    printf("NT hash: "); for(unsigned char c:nthash)printf("%02x",c); printf("\n");
    Rpc r; r.conn(host,port); r.bind(); printf("bind ok\n");
    Bytes cc; {int fd=::open("/dev/urandom",O_RDONLY);char rb[8];(void)!::read(fd,rb,8);::close(fd);cc.assign(rb,8);}
    { Bytes s; uint32_t refid=0x00020000; ndrUniqStr(s,nullptr,refid); ndrRefStr(s,computer); s+=cc;
      Bytes out=r.call(4,s); // ServerReqChallenge opnum 4
      Bytes sc=out.substr(0,8); uint32_t st=g32((const uint8_t*)out.data()+out.size()-4);
      printf("ReqChallenge status=0x%08x servchal=",st); for(unsigned char c:sc)printf("%02x",c); printf("\n");
      Bytes sk=sessionKey(nthash,cc,sc);
      printf("session key: "); for(unsigned char c:sk)printf("%02x",c); printf("\n");
      Bytes clientCred=credential(sk,cc); fprintf(stderr,"CC=");for(unsigned char c:cc)fprintf(stderr,"%02x",c);fprintf(stderr," SC=");for(unsigned char c:sc)fprintf(stderr,"%02x",c);fprintf(stderr," CRED=");for(unsigned char c:clientCred)fprintf(stderr,"%02x",c);fprintf(stderr,"\n");
      // Authenticate3
      // NDR: fixed part first (server_name ref, secure_channel_type), then
      // deferred pointees (account string, computer string, credential, flags)
      Bytes a; uint32_t refid2=0x00040000; ndrUniqStr(a,nullptr,refid2);
      ndrRefStr(a,account);
      p16(a,2); // secure_channel_type enum16 = WorkstationSecureChannel
      ndrRefStr(a,computer);
      a+=clientCred;
      align4(a); uint32_t negflags=0x610fffff|0x01000000|0x00004000; p32(a,negflags);
      fprintf(stderr,"A3 stub %zu: ",a.size());for(unsigned char c:a)fprintf(stderr,"%02x",c);fprintf(stderr,"\n");
      Bytes o=r.call(26,a);
      Bytes servCred=o.substr(0,8);
      uint32_t gotflags=g32((const uint8_t*)o.data()+8);
      uint32_t rid=g32((const uint8_t*)o.data()+12);
      uint32_t st2=g32((const uint8_t*)o.data()+o.size()-4);
      printf("Authenticate3 status=0x%08x flags=0x%08x rid=%u\n",st2,gotflags,rid);
      printf("server cred: "); for(unsigned char c:servCred)printf("%02x",c); printf("\n");
      Bytes expect=credential(sk,sc);
      printf("expected   : "); for(unsigned char c:expect)printf("%02x",c); printf("\n");
      printf(servCred==expect?"MATCH: session key verified\n":"MISMATCH\n");
      if(servCred!=expect) return 1;

      // ---- schannel + SamLogonEx ----
      // alter_context with NL_AUTH_MESSAGE (MessageType 0, Flags OEM dom+comp)
      Bytes nlauth; p32(nlauth,0); p32(nlauth,0x00000003);
      nlauth+="TEST"; nlauth+=(char)0; nlauth+="FENRIRT"; nlauth+=(char)0;
      r.alterAuth(nlauth);
      printf("schannel alter_context ok\n");

      // SamLogonEx stub from the captured reference (same names), substitute challenge/nt/lm
      auto hx=[](const char*h){Bytes o;for(size_t i=0;h[i]&&h[i+1];i+=2){auto v=[&](char c){return c<='9'?c-'0':(c|32)-'a'+10;};o+=(char)(v(h[i])*16+v(h[i+1]));}return o;};
      Bytes stub=hx("00000200040000000000000004000000440043003100000004000200080000000000000008000000460045004e00520049005200540000000200020008000200080008000c00020000000000000000000000000016001600100002000e000e00140002001dd9b1ce912800d53400340018000200180018001c00020004000000000000000400000054004500530054000b000000000000000b0000006a0061006e002e006b006f00650073007400650072000000070000000000000007000000460045004e00520049005200540000003400000000000000340000001f67a90ee856a3961cb9125f90459fd2010100000000000000809351ce67d101db483202b8842125000000000000000000000000180000000000000018000000dff03b13569dc226f52254b798361d7edb483202b884212506000000000000008ae3137102f43671010004000000000002402800785634123412cdabef0001234567cffb01000000045d888aeb1cc9119fe808002b104860020000000000000000000000");
      Bytes blob=stub.substr(236,36);
      // NTLMv2 for jan.koester / TEST / Passw0rd.User1 against a random NTLM challenge
      Bytes jnt=md4(utf16("Passw0rd.User1"));
      Bytes v2=hmacMd5(jnt, utf16up("jan.koester")+utf16("TEST"));
      Bytes nchal; {int fd=::open("/dev/urandom",O_RDONLY);char rb[8];(void)!::read(fd,rb,8);::close(fd);nchal.assign(rb,8);}
      Bytes lchal=blob.substr(16,8);
      Bytes ntproof=hmacMd5(v2, nchal+blob);
      Bytes ntresp=ntproof+blob;            // 52
      Bytes lmresp=hmacMd5(v2, nchal+lchal)+lchal; // 24
      memcpy(&stub[100],nchal.data(),8);
      memcpy(&stub[220],ntresp.data(),52);
      memcpy(&stub[284],lmresp.data(),24);
      uint64_t seq=0;
      Bytes resp=r.callSealed(39,stub,sk,seq);
      printf("SamLogonEx response %zu bytes\n",resp.size());
      uint32_t status=g32((const uint8_t*)resp.data()+resp.size()-4);
      printf("SamLogonEx NT status = 0x%08x\n",status);
      if(status==0) printf("PASS: netsec schannel + SamLogonEx network logon OK\n");

    }
    return 0;
}
+367 −0

File added.

Preview size limit exceeded, changes collapsed.