bettercap/packets/ntlm.go
2018-04-24 18:26:16 +02:00

238 lines
7.0 KiB
Go

package packets
import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
"errors"
"strings"
"sync"
"unsafe"
)
const (
NTLM_SIG_OFFSET = 0
NTLM_TYPE_OFFSET = 8
NTLM_TYPE1_FLAGS_OFFSET = 12
NTLM_TYPE1_DOMAIN_OFFSET = 16
NTLM_TYPE1_WORKSTN_OFFSET = 24
NTLM_TYPE1_DATA_OFFSET = 32
NTLM_TYPE1_MINSIZE = 16
NTLM_TYPE2_TARGET_OFFSET = 12
NTLM_TYPE2_FLAGS_OFFSET = 20
NTLM_TYPE2_CHALLENGE_OFFSET = 24
NTLM_TYPE2_CONTEXT_OFFSET = 32
NTLM_TYPE2_TARGETINFO_OFFSET = 40
NTLM_TYPE2_DATA_OFFSET = 48
NTLM_TYPE2_MINSIZE = 32
NTLM_TYPE3_LMRESP_OFFSET = 12
NTLM_TYPE3_NTRESP_OFFSET = 20
NTLM_TYPE3_DOMAIN_OFFSET = 28
NTLM_TYPE3_USER_OFFSET = 36
NTLM_TYPE3_WORKSTN_OFFSET = 44
NTLM_TYPE3_SESSIONKEY_OFFSET = 52
NTLM_TYPE3_FLAGS_OFFSET = 60
NTLM_TYPE3_DATA_OFFSET = 64
NTLM_TYPE3_MINSIZE = 52
NTLM_BUFFER_LEN_OFFSET = 0
NTLM_BUFFER_MAXLEN_OFFSET = 2
NTLM_BUFFER_OFFSET_OFFSET = 4
NTLM_BUFFER_SIZE = 8
NtlmV1 = 1
NtlmV2 = 2
)
type NTLMChallengeResponse struct {
Challenge string
Response string
}
type NTLMChallengeResponseParsed struct {
Type int
ServerChallenge string
User string
Domain string
LmHash string
NtHashOne string
NtHashTwo string
}
type NTLMResponseHeader struct {
Sig string
Type uint32
LmLen uint16
LmMax uint16
LmOffset uint16
NtLen uint16
NtMax uint16
NtOffset uint16
DomainLen uint16
DomainMax uint16
DomainOffset uint16
UserLen uint16
UserMax uint16
UserOffset uint16
HostLen uint16
HostMax uint16
HostOffset uint16
}
type NTLMState struct {
sync.Mutex
Responses map[uint32]string
Pairs []NTLMChallengeResponse
}
func (s *NTLMState) AddServerResponse(key uint32, value string) {
s.Lock()
defer s.Unlock()
s.Responses[key] = value
}
func (s *NTLMState) AddClientResponse(seq uint32, value string, cb func(data NTLMChallengeResponseParsed)) {
s.Lock()
defer s.Unlock()
if chall, found := s.Responses[seq]; found {
pair := NTLMChallengeResponse{
Challenge: chall,
Response: value,
}
s.Pairs = append(s.Pairs, pair)
if data, err := pair.Parsed(); err == nil {
cb(data)
}
}
}
func NewNTLMState() *NTLMState {
return &NTLMState{
Responses: make(map[uint32]string),
Pairs: make([]NTLMChallengeResponse, 0),
}
}
func (sr NTLMChallengeResponse) getServerChallenge() string {
dataCallenge := sr.getChallengeBytes()
//offset to the challenge and the challenge is 8 bytes long
return hex.EncodeToString(dataCallenge[NTLM_TYPE2_CHALLENGE_OFFSET : NTLM_TYPE2_CHALLENGE_OFFSET+8])
}
func (sr NTLMChallengeResponse) getChallengeBytes() []byte {
dataCallenge, _ := base64.StdEncoding.DecodeString(sr.Challenge)
return dataCallenge
}
func (sr NTLMChallengeResponse) getResponseBytes() []byte {
dataResponse, _ := base64.StdEncoding.DecodeString(sr.Response)
return dataResponse
}
func (sr *NTLMChallengeResponse) Parsed() (NTLMChallengeResponseParsed, error) {
if sr.isNtlmV1() {
return sr.ParsedNtLMv1()
}
return sr.ParsedNtLMv2()
}
func (sr *NTLMChallengeResponse) ParsedNtLMv2() (NTLMChallengeResponseParsed, error) {
r := sr.getResponseHeader()
if r.UserLen == 0 {
return NTLMChallengeResponseParsed{}, errors.New("No repsponse data")
}
b := sr.getResponseBytes()
nthash := b[r.NtOffset : r.NtOffset+r.NtLen]
// each char in user and domain is null terminated
return NTLMChallengeResponseParsed{
Type: NtlmV2,
ServerChallenge: sr.getServerChallenge(),
User: strings.Replace(string(b[r.UserOffset:r.UserOffset+r.UserLen]), "\x00", "", -1),
Domain: strings.Replace(string(b[r.DomainOffset:r.DomainOffset+r.DomainLen]), "\x00", "", -1),
NtHashOne: hex.EncodeToString(nthash[:16]), // first part of the hash is 16 bytes
NtHashTwo: hex.EncodeToString(nthash[16:]),
}, nil
}
func (sr NTLMChallengeResponse) isNtlmV1() bool {
headerValues := sr.getResponseHeader()
return headerValues.NtLen == 24
}
func (sr NTLMChallengeResponse) ParsedNtLMv1() (NTLMChallengeResponseParsed, error) {
r := sr.getResponseHeader()
if r.UserLen == 0 {
return NTLMChallengeResponseParsed{}, errors.New("No repsponse data")
}
b := sr.getResponseBytes()
// each char user and domain is null terminated
return NTLMChallengeResponseParsed{
Type: NtlmV1,
ServerChallenge: sr.getServerChallenge(),
User: strings.Replace(string(b[r.UserOffset:r.UserOffset+r.UserLen]), "\x00", "", -1),
Domain: strings.Replace(string(b[r.DomainOffset:r.DomainOffset+r.DomainLen]), "\x00", "", -1),
LmHash: hex.EncodeToString(b[r.LmOffset : r.LmOffset+r.LmLen]),
}, nil
}
func is_le() bool {
var i int32 = 0x01020304
u := unsafe.Pointer(&i)
pb := (*byte)(u)
b := *pb
return (b == 0x04)
}
func _uint32(b []byte, start, end int) uint32 {
if is_le() {
return binary.LittleEndian.Uint32(b[start:end])
}
return binary.BigEndian.Uint32(b[start:end])
}
func _uint16(b []byte, start, end int) uint16 {
if is_le() {
return binary.LittleEndian.Uint16(b[start:end])
}
return binary.BigEndian.Uint16(b[start:end])
}
func (sr NTLMChallengeResponse) getResponseHeader() NTLMResponseHeader {
b := sr.getResponseBytes()
if len(b) == 0 {
return NTLMResponseHeader{}
}
return NTLMResponseHeader{
Sig: strings.Replace(string(b[NTLM_SIG_OFFSET:NTLM_SIG_OFFSET+8]), "\x00", "", -1),
Type: _uint32(b, NTLM_TYPE_OFFSET, NTLM_TYPE_OFFSET+4),
LmLen: _uint16(b, NTLM_TYPE3_LMRESP_OFFSET, NTLM_TYPE3_LMRESP_OFFSET+2),
LmMax: _uint16(b, NTLM_TYPE3_LMRESP_OFFSET+2, NTLM_TYPE3_LMRESP_OFFSET+4),
LmOffset: _uint16(b, NTLM_TYPE3_LMRESP_OFFSET+4, NTLM_TYPE3_LMRESP_OFFSET+6),
NtLen: _uint16(b, NTLM_TYPE3_NTRESP_OFFSET, NTLM_TYPE3_NTRESP_OFFSET+2),
NtMax: _uint16(b, NTLM_TYPE3_NTRESP_OFFSET+2, NTLM_TYPE3_NTRESP_OFFSET+4),
NtOffset: _uint16(b, NTLM_TYPE3_NTRESP_OFFSET+4, NTLM_TYPE3_NTRESP_OFFSET+6),
DomainLen: _uint16(b, NTLM_TYPE3_DOMAIN_OFFSET, NTLM_TYPE3_DOMAIN_OFFSET+2),
DomainMax: _uint16(b, NTLM_TYPE3_DOMAIN_OFFSET+2, NTLM_TYPE3_DOMAIN_OFFSET+4),
DomainOffset: _uint16(b, NTLM_TYPE3_DOMAIN_OFFSET+4, NTLM_TYPE3_DOMAIN_OFFSET+6),
UserLen: _uint16(b, NTLM_TYPE3_USER_OFFSET, NTLM_TYPE3_USER_OFFSET+2),
UserMax: _uint16(b, NTLM_TYPE3_USER_OFFSET+2, NTLM_TYPE3_USER_OFFSET+4),
UserOffset: _uint16(b, NTLM_TYPE3_USER_OFFSET+4, NTLM_TYPE3_USER_OFFSET+6),
HostLen: _uint16(b, NTLM_TYPE3_WORKSTN_OFFSET, NTLM_TYPE3_WORKSTN_OFFSET+2),
HostMax: _uint16(b, NTLM_TYPE3_WORKSTN_OFFSET+2, NTLM_TYPE3_WORKSTN_OFFSET+4),
HostOffset: _uint16(b, NTLM_TYPE3_WORKSTN_OFFSET+4, NTLM_TYPE3_WORKSTN_OFFSET+6),
}
}
func (data NTLMChallengeResponseParsed) LcString() string {
// NTLM v1 in .lc format
if data.Type == NtlmV1 {
return data.User + "::" + data.Domain + ":" + data.LmHash + ":" + data.ServerChallenge + "\n"
}
return data.User + "::" + data.Domain + ":" + data.ServerChallenge + ":" + data.NtHashOne + ":" + data.NtHashTwo + "\n"
}