Loading plugin/smb/smb.cpp +17 −4 Changes for plugin/smb/smb.cpp: 17 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ #include "crypto/crypto.h" #include "crypto/gss.h" #include "rpc/ndr.h" #include "rpc/services.h" #include "smb.h" namespace fenrirds { Loading Loading @@ -72,10 +73,20 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) { } std::vector<uint8_t> RpcPipe::dispatch(const rpc::RequestPdu &req) { /* the lsarpc/samr/netlogon opnum handlers plug in here (next step). * Until then every call faults with nca_op_rng_error so the transport is * exercised end to end. */ return rpc::buildFault(req.callId, req.contextId, 0x1C010002 /* nca_op_rng_error */); rpc::DispatchResult res; if (rpc::isLsarpc(_boundAbstract.uuid)) res = rpc::lsarpcDispatch(req.opnum, req.stub, _sessionKey); else if (rpc::isSamr(_boundAbstract.uuid)) res = rpc::samrDispatch(req.opnum, req.stub, _sessionKey); else if (rpc::isNetlogon(_boundAbstract.uuid)) res = rpc::netlogonDispatch(req.opnum, req.stub, _sessionKey); else { res.fault = true; res.faultCode = 0x1C010003; /* nca_unk_if */ } if (res.fault) return rpc::buildFault(req.callId, req.contextId, res.faultCode); return rpc::buildResponse(req.callId, req.contextId, res.stub); } /***************************************************************************** Loading Loading @@ -251,6 +262,7 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t c.authenticated = true; c.clientPrincipal = res.clientPrincipal; c.sessionKey = res.sessionKey; if (!c.sessionId) { auto r = crypto::randomBytes(8); c.sessionId = (rd64(r.data()) | 1); Loading Loading @@ -320,6 +332,7 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t return errorResponse(req, smb::STATUS_OBJECT_NAME_NOT_FOUND); uint64_t fid = c.nextFid++; c.pipes[fid] = new RpcPipe(pname); c.pipes[fid]->setSessionKey(c.sessionKey); smb::Smb2Header h; h.command=req.command; h.messageId=req.messageId; h.sessionId=c.sessionId; h.treeId=req.treeId; h.status=smb::STATUS_SUCCESS; h.creditReqResp=1; auto resp = smb::buildHeader(h); Loading plugin/smb/smb.h +3 −0 Changes for plugin/smb/smb.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ namespace fenrirds { public: explicit RpcPipe(const std::string &name) : _name(name) {} const std::string &name() const { return _name; } void setSessionKey(const std::vector<uint8_t> &k) { _sessionKey = k; } /* feed bytes written to the pipe; produce any complete PDU responses */ void write(const uint8_t *data, size_t len); Loading @@ -50,6 +51,7 @@ namespace fenrirds { size_t _outPos = 0; rpc::SyntaxId _boundAbstract; /* the interface bound on this pipe */ bool _bound = false; std::vector<uint8_t> _sessionKey; }; /*************************************************************************** Loading Loading @@ -88,6 +90,7 @@ namespace fenrirds { std::string clientPrincipal; smb::Smb3Keys keys; bool haveKeys = false; std::vector<uint8_t> sessionKey; /* GSS session key for SAMR */ uint32_t treeIpc = 0; /* IPC$ tree id (0 = none) */ std::map<uint64_t, RpcPipe*> pipes; /* volatile file id -> pipe */ uint64_t nextFid = 1; Loading src/CMakeLists.txt +4 −0 Changes for src/CMakeLists.txt: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -34,6 +34,10 @@ list(APPEND fenrirdsSrcs dsdb/pac.cpp rpc/ndr.cpp rpc/dcerpc.cpp rpc/services.cpp rpc/lsarpc.cpp rpc/samr.cpp rpc/netlogon.cpp smb/smb2.cpp ) Loading src/rpc/lsarpc.cpp 0 → 100644 +145 −0 Changes for src/rpc/lsarpc.cpp: 145 added lines, 0 removed lines. Original line number Diff line number Diff line #include <cstring> #include "crypto/crypto.h" #include "domains.h" #include "ndr.h" #include "services.h" namespace fenrirds { namespace rpc { /* ---- shared: resolve the primary domain ---- */ DomainInfo primaryDomain() { DomainInfo di; auto doms = DomainRegistry::instance().list(); if (doms.empty()) return di; auto &d = doms.front(); AuthBackend *bck = DomainRegistry::instance().get(d.id); if (!bck) return di; dsdb::DomainMeta meta; meta.dnsName = d.name; auto dot = d.name.find('.'); meta.netbios = dsdb::upper(dot == std::string::npos ? d.name : d.name.substr(0, dot)); meta.baseDn = dsdb::dnFromDomain(d.name); meta.realm = dsdb::upper(d.name); dsdb::Directory dir(*bck, meta); auto head = dir.byDn(meta.baseDn); if (!head) return di; di.ok = true; di.dnsName = d.name; di.netbios = meta.netbios; di.forest = d.name; di.sid = head->objectSid; std::memcpy(di.guid, head->objectGuid.value, 16); di.baseDn = meta.baseDn; di.domainId = d.id; return di; } namespace { /* lsarpc UUID 12345778-1234-abcd-ef00-0123456789ab (NDR LE field order) */ const uint8_t LSA_UUID[16] = {0x78,0x57,0x34,0x12,0x34,0x12,0xcd,0xab,0xef,0x00,0x01,0x23,0x45,0x67,0x89,0xab}; enum { LSA_CLOSE = 0, LSA_QUERYINFOPOLICY = 7, LSA_OPENPOLICY2 = 44, LSA_QUERYINFOPOLICY2 = 46 }; /* an lsa_String / lsa_StringLarge: Length, Size, [ref]Buffer (conformant * varying wchar). lsa_String Size = Length; lsa_StringLarge Size = Length+2. */ void pushLsaString(NdrPush &p, const std::string &s, bool large) { auto u = crypto::utf16le(s); uint16_t len = uint16_t(u.size()); p.u16(len); p.u16(large ? uint16_t(len + 2) : len); p.ref(true); } void pushLsaStringBuffer(NdrPush &p, const std::string &s, bool large) { auto u = crypto::utf16le(s); uint32_t chars = uint32_t(u.size() / 2); uint32_t maxc = large ? chars + 1 : chars; p.align(4); p.u32(maxc); p.u32(0); p.u32(chars); p.bytes(u.data(), u.size()); } } bool isLsarpc(const uint8_t uuid[16]) { return std::memcmp(uuid, LSA_UUID, 16) == 0; } DispatchResult lsarpcDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, const std::vector<uint8_t> &) { DispatchResult r; switch (opnum) { case LSA_OPENPOLICY2: case 6 /* LSA_OPENPOLICY */: { /* return a fresh policy handle + success */ NdrPush p; PolicyHandle h; h.type = 0; auto rnd = crypto::randomBytes(16); std::memcpy(h.uuid, rnd.data(), 16); p.handle(h); p.u32(0); /* NTSTATUS success */ r.stub = p.buf; return r; } case LSA_CLOSE: { NdrPush p; PolicyHandle h; /* null handle on close */ p.handle(h); p.u32(0); r.stub = p.buf; return r; } case LSA_QUERYINFOPOLICY: case LSA_QUERYINFOPOLICY2: { /* request: policy handle + u16 level */ NdrPull in(stub); in.handle(); uint16_t level = in.u16(); DomainInfo di = primaryDomain(); if (!di.ok) { r.status = 0xC0000034; /* object name not found */ NdrPush p; p.u32(0); p.u32(r.status); r.stub = p.buf; return r; } NdrPush p; p.ref(true); /* [out,ref] **info -> the union pointer */ p.u16(level); /* union discriminant */ if (level == 5) { /* PolicyAccountDomainInformation */ p.align(4); pushLsaString(p, di.netbios, false); p.ref(true); /* sid pointer */ pushLsaStringBuffer(p, di.netbios, false); p.sid(di.sid); } else if (level == 12) { /* PolicyDnsDomainInformation */ p.align(4); pushLsaString(p, di.netbios, true); /* name (NetBIOS) */ pushLsaString(p, di.dnsName, true); /* dns_domain */ pushLsaString(p, di.forest, true); /* dns_forest */ p.bytes(di.guid, 16); /* domain_guid */ p.ref(true); /* sid pointer */ pushLsaStringBuffer(p, di.netbios, true); pushLsaStringBuffer(p, di.dnsName, true); pushLsaStringBuffer(p, di.forest, true); p.sid(di.sid); } else { /* unsupported level: return a null pointer + invalid info class */ p.buf.clear(); p.ref(false); p.u32(0xC00000DD); /* STATUS_INVALID_INFO_CLASS */ r.stub = p.buf; return r; } p.u32(0); /* NTSTATUS success */ r.stub = p.buf; return r; } default: r.fault = true; r.faultCode = 0x1C010002; /* nca_op_rng_error */ return r; } } } } src/rpc/ndr.cpp +2 −2 Changes for src/rpc/ndr.cpp: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ void NdrPush::unistrBuffer(const std::string &s, bool nul) { u32(actual); /* ActualCount */ bytes(u.data(), u.size()); if (nul) { u8(0); u8(0); } align(4); } void NdrPush::sid(const dsdb::Sid &s) { Loading Loading @@ -61,7 +60,8 @@ std::string NdrPull::unistrBuffer() { throw NdrError("unistr offset != 0"); auto raw = takeVec(size_t(actual) * 2); std::string out = fromUtf16(raw.data(), raw.size()); align(4); /* NDR aligns at the START of each field, not after; the next read aligns * itself. (A trailing align here would eat the following u16's bytes.) */ /* strip a trailing NUL if present */ if (!out.empty() && out.back() == '\0') out.pop_back(); Loading Loading
plugin/smb/smb.cpp +17 −4 Changes for plugin/smb/smb.cpp: 17 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ #include "crypto/crypto.h" #include "crypto/gss.h" #include "rpc/ndr.h" #include "rpc/services.h" #include "smb.h" namespace fenrirds { Loading Loading @@ -72,10 +73,20 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) { } std::vector<uint8_t> RpcPipe::dispatch(const rpc::RequestPdu &req) { /* the lsarpc/samr/netlogon opnum handlers plug in here (next step). * Until then every call faults with nca_op_rng_error so the transport is * exercised end to end. */ return rpc::buildFault(req.callId, req.contextId, 0x1C010002 /* nca_op_rng_error */); rpc::DispatchResult res; if (rpc::isLsarpc(_boundAbstract.uuid)) res = rpc::lsarpcDispatch(req.opnum, req.stub, _sessionKey); else if (rpc::isSamr(_boundAbstract.uuid)) res = rpc::samrDispatch(req.opnum, req.stub, _sessionKey); else if (rpc::isNetlogon(_boundAbstract.uuid)) res = rpc::netlogonDispatch(req.opnum, req.stub, _sessionKey); else { res.fault = true; res.faultCode = 0x1C010003; /* nca_unk_if */ } if (res.fault) return rpc::buildFault(req.callId, req.contextId, res.faultCode); return rpc::buildResponse(req.callId, req.contextId, res.stub); } /***************************************************************************** Loading Loading @@ -251,6 +262,7 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t c.authenticated = true; c.clientPrincipal = res.clientPrincipal; c.sessionKey = res.sessionKey; if (!c.sessionId) { auto r = crypto::randomBytes(8); c.sessionId = (rd64(r.data()) | 1); Loading Loading @@ -320,6 +332,7 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t return errorResponse(req, smb::STATUS_OBJECT_NAME_NOT_FOUND); uint64_t fid = c.nextFid++; c.pipes[fid] = new RpcPipe(pname); c.pipes[fid]->setSessionKey(c.sessionKey); smb::Smb2Header h; h.command=req.command; h.messageId=req.messageId; h.sessionId=c.sessionId; h.treeId=req.treeId; h.status=smb::STATUS_SUCCESS; h.creditReqResp=1; auto resp = smb::buildHeader(h); Loading
plugin/smb/smb.h +3 −0 Changes for plugin/smb/smb.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ namespace fenrirds { public: explicit RpcPipe(const std::string &name) : _name(name) {} const std::string &name() const { return _name; } void setSessionKey(const std::vector<uint8_t> &k) { _sessionKey = k; } /* feed bytes written to the pipe; produce any complete PDU responses */ void write(const uint8_t *data, size_t len); Loading @@ -50,6 +51,7 @@ namespace fenrirds { size_t _outPos = 0; rpc::SyntaxId _boundAbstract; /* the interface bound on this pipe */ bool _bound = false; std::vector<uint8_t> _sessionKey; }; /*************************************************************************** Loading Loading @@ -88,6 +90,7 @@ namespace fenrirds { std::string clientPrincipal; smb::Smb3Keys keys; bool haveKeys = false; std::vector<uint8_t> sessionKey; /* GSS session key for SAMR */ uint32_t treeIpc = 0; /* IPC$ tree id (0 = none) */ std::map<uint64_t, RpcPipe*> pipes; /* volatile file id -> pipe */ uint64_t nextFid = 1; Loading
src/CMakeLists.txt +4 −0 Changes for src/CMakeLists.txt: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -34,6 +34,10 @@ list(APPEND fenrirdsSrcs dsdb/pac.cpp rpc/ndr.cpp rpc/dcerpc.cpp rpc/services.cpp rpc/lsarpc.cpp rpc/samr.cpp rpc/netlogon.cpp smb/smb2.cpp ) Loading
src/rpc/lsarpc.cpp 0 → 100644 +145 −0 Changes for src/rpc/lsarpc.cpp: 145 added lines, 0 removed lines. Original line number Diff line number Diff line #include <cstring> #include "crypto/crypto.h" #include "domains.h" #include "ndr.h" #include "services.h" namespace fenrirds { namespace rpc { /* ---- shared: resolve the primary domain ---- */ DomainInfo primaryDomain() { DomainInfo di; auto doms = DomainRegistry::instance().list(); if (doms.empty()) return di; auto &d = doms.front(); AuthBackend *bck = DomainRegistry::instance().get(d.id); if (!bck) return di; dsdb::DomainMeta meta; meta.dnsName = d.name; auto dot = d.name.find('.'); meta.netbios = dsdb::upper(dot == std::string::npos ? d.name : d.name.substr(0, dot)); meta.baseDn = dsdb::dnFromDomain(d.name); meta.realm = dsdb::upper(d.name); dsdb::Directory dir(*bck, meta); auto head = dir.byDn(meta.baseDn); if (!head) return di; di.ok = true; di.dnsName = d.name; di.netbios = meta.netbios; di.forest = d.name; di.sid = head->objectSid; std::memcpy(di.guid, head->objectGuid.value, 16); di.baseDn = meta.baseDn; di.domainId = d.id; return di; } namespace { /* lsarpc UUID 12345778-1234-abcd-ef00-0123456789ab (NDR LE field order) */ const uint8_t LSA_UUID[16] = {0x78,0x57,0x34,0x12,0x34,0x12,0xcd,0xab,0xef,0x00,0x01,0x23,0x45,0x67,0x89,0xab}; enum { LSA_CLOSE = 0, LSA_QUERYINFOPOLICY = 7, LSA_OPENPOLICY2 = 44, LSA_QUERYINFOPOLICY2 = 46 }; /* an lsa_String / lsa_StringLarge: Length, Size, [ref]Buffer (conformant * varying wchar). lsa_String Size = Length; lsa_StringLarge Size = Length+2. */ void pushLsaString(NdrPush &p, const std::string &s, bool large) { auto u = crypto::utf16le(s); uint16_t len = uint16_t(u.size()); p.u16(len); p.u16(large ? uint16_t(len + 2) : len); p.ref(true); } void pushLsaStringBuffer(NdrPush &p, const std::string &s, bool large) { auto u = crypto::utf16le(s); uint32_t chars = uint32_t(u.size() / 2); uint32_t maxc = large ? chars + 1 : chars; p.align(4); p.u32(maxc); p.u32(0); p.u32(chars); p.bytes(u.data(), u.size()); } } bool isLsarpc(const uint8_t uuid[16]) { return std::memcmp(uuid, LSA_UUID, 16) == 0; } DispatchResult lsarpcDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, const std::vector<uint8_t> &) { DispatchResult r; switch (opnum) { case LSA_OPENPOLICY2: case 6 /* LSA_OPENPOLICY */: { /* return a fresh policy handle + success */ NdrPush p; PolicyHandle h; h.type = 0; auto rnd = crypto::randomBytes(16); std::memcpy(h.uuid, rnd.data(), 16); p.handle(h); p.u32(0); /* NTSTATUS success */ r.stub = p.buf; return r; } case LSA_CLOSE: { NdrPush p; PolicyHandle h; /* null handle on close */ p.handle(h); p.u32(0); r.stub = p.buf; return r; } case LSA_QUERYINFOPOLICY: case LSA_QUERYINFOPOLICY2: { /* request: policy handle + u16 level */ NdrPull in(stub); in.handle(); uint16_t level = in.u16(); DomainInfo di = primaryDomain(); if (!di.ok) { r.status = 0xC0000034; /* object name not found */ NdrPush p; p.u32(0); p.u32(r.status); r.stub = p.buf; return r; } NdrPush p; p.ref(true); /* [out,ref] **info -> the union pointer */ p.u16(level); /* union discriminant */ if (level == 5) { /* PolicyAccountDomainInformation */ p.align(4); pushLsaString(p, di.netbios, false); p.ref(true); /* sid pointer */ pushLsaStringBuffer(p, di.netbios, false); p.sid(di.sid); } else if (level == 12) { /* PolicyDnsDomainInformation */ p.align(4); pushLsaString(p, di.netbios, true); /* name (NetBIOS) */ pushLsaString(p, di.dnsName, true); /* dns_domain */ pushLsaString(p, di.forest, true); /* dns_forest */ p.bytes(di.guid, 16); /* domain_guid */ p.ref(true); /* sid pointer */ pushLsaStringBuffer(p, di.netbios, true); pushLsaStringBuffer(p, di.dnsName, true); pushLsaStringBuffer(p, di.forest, true); p.sid(di.sid); } else { /* unsupported level: return a null pointer + invalid info class */ p.buf.clear(); p.ref(false); p.u32(0xC00000DD); /* STATUS_INVALID_INFO_CLASS */ r.stub = p.buf; return r; } p.u32(0); /* NTSTATUS success */ r.stub = p.buf; return r; } default: r.fault = true; r.faultCode = 0x1C010002; /* nca_op_rng_error */ return r; } } } }
src/rpc/ndr.cpp +2 −2 Changes for src/rpc/ndr.cpp: 2 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ void NdrPush::unistrBuffer(const std::string &s, bool nul) { u32(actual); /* ActualCount */ bytes(u.data(), u.size()); if (nul) { u8(0); u8(0); } align(4); } void NdrPush::sid(const dsdb::Sid &s) { Loading Loading @@ -61,7 +60,8 @@ std::string NdrPull::unistrBuffer() { throw NdrError("unistr offset != 0"); auto raw = takeVec(size_t(actual) * 2); std::string out = fromUtf16(raw.data(), raw.size()); align(4); /* NDR aligns at the START of each field, not after; the next read aligns * itself. (A trailing align here would eat the following u16's bytes.) */ /* strip a trailing NUL if present */ if (!out.empty() && out.back() == '\0') out.pop_back(); Loading