mirror of
https://github.com/bettercap/bettercap.git
synced 2024-11-03 04:00:07 -08:00
238 lines
7.0 KiB
Go
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"
|
|
}
|