mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73d14a32e2 |
No files matched your search
@@ -121,6 +121,7 @@ windows: clean
|
||||
.PHONY: pb
|
||||
pb:
|
||||
protoc -I protobuf/ protobuf/commonpb/common.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/dnspb/dns.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/sliverpb/sliver.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/clientpb/client.proto --go_out=paths=source_relative:protobuf/
|
||||
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=paths=source_relative:protobuf/ --go-grpc_out=protobuf/ --go-grpc_opt=paths=source_relative
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package encoders
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
)
|
||||
|
||||
// Base32EncoderID - EncoderID
|
||||
const Base32EncoderID = 65
|
||||
|
||||
// Base32 Encoder
|
||||
type Base32 struct{}
|
||||
|
||||
var base32Alphabet = "ab1c2d3e4f5g6h7j8k9m0npqrtuvwxyz"
|
||||
var sliverBase32 = base32.NewEncoding(base32Alphabet)
|
||||
|
||||
// Encode - Base32 Encode
|
||||
func (e Base32) Encode(data []byte) []byte {
|
||||
return []byte(sliverBase32.EncodeToString(data))
|
||||
}
|
||||
|
||||
// Decode - Base32 Decode
|
||||
func (e Base32) Decode(data []byte) ([]byte, error) {
|
||||
return sliverBase32.DecodeString(string(data))
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package encoders
|
||||
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Shawn Wang <[email protected]>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Modifications by moloch to work with Sliver
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
base = 62
|
||||
compactMask = 0x1E // 00011110
|
||||
mask5bits = 0x1F // 00011111
|
||||
)
|
||||
|
||||
// Base62EncoderID - EncoderID
|
||||
const Base62EncoderID = 43
|
||||
|
||||
// Base62 Encoder
|
||||
type Base62 struct{}
|
||||
|
||||
var base62Alphabet = "aBCDEFGHIJKLMNOPQRSTUVWXYZAbcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
// Encode - Base62 Encode
|
||||
func (e Base62) Encode(data []byte) []byte {
|
||||
return []byte(NewEncoding(base62Alphabet).EncodeToString(data))
|
||||
}
|
||||
|
||||
// Decode - Base62 Decode
|
||||
func (e Base62) Decode(data []byte) ([]byte, error) {
|
||||
return NewEncoding(base62Alphabet).DecodeString(string(data))
|
||||
}
|
||||
|
||||
type Base62Encoder struct {
|
||||
encode [base]byte
|
||||
decodeMap [256]byte
|
||||
}
|
||||
|
||||
func NewEncoding(encoder string) *Base62Encoder {
|
||||
if len(encoder) != base {
|
||||
panic("encoding alphabet is not 62-bytes long")
|
||||
}
|
||||
for i := 0; i < len(encoder); i++ {
|
||||
if encoder[i] == '\n' || encoder[i] == '\r' {
|
||||
panic("encoding alphabet contains newline character")
|
||||
}
|
||||
}
|
||||
|
||||
e := new(Base62Encoder)
|
||||
copy(e.encode[:], encoder)
|
||||
for i := 0; i < len(e.decodeMap); i++ {
|
||||
e.decodeMap[i] = 0xFF
|
||||
}
|
||||
for i := 0; i < len(encoder); i++ {
|
||||
e.decodeMap[encoder[i]] = byte(i)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (enc *Base62Encoder) Encode(src []byte) []byte {
|
||||
if len(src) == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
encoder := newEncoder(src)
|
||||
return encoder.encode(enc.encode[:])
|
||||
}
|
||||
|
||||
func (enc *Base62Encoder) EncodeToString(src []byte) string {
|
||||
ret := enc.Encode(src)
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
b []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
func newEncoder(src []byte) *encoder {
|
||||
return &encoder{
|
||||
b: src,
|
||||
pos: len(src) * 8,
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *encoder) next() (byte, bool) {
|
||||
var i, pos int
|
||||
var j, blen byte
|
||||
pos = enc.pos - 6
|
||||
if pos <= 0 {
|
||||
pos = 0
|
||||
blen = byte(enc.pos)
|
||||
} else {
|
||||
i = pos / 8
|
||||
j = byte(pos % 8)
|
||||
blen = byte((i+1)*8 - pos)
|
||||
if blen > 6 {
|
||||
blen = 6
|
||||
}
|
||||
}
|
||||
shift := 8 - j - blen
|
||||
b := enc.b[i] >> shift & (1<<blen - 1)
|
||||
|
||||
if blen < 6 && pos > 0 {
|
||||
blen1 := 6 - blen
|
||||
b = b<<blen1 | enc.b[i+1]>>(8-blen1)
|
||||
}
|
||||
if b&compactMask == compactMask {
|
||||
if pos > 0 || b > mask5bits {
|
||||
pos++
|
||||
}
|
||||
b &= mask5bits
|
||||
}
|
||||
enc.pos = pos
|
||||
|
||||
return b, pos > 0
|
||||
}
|
||||
|
||||
func (enc *encoder) encode(encTable []byte) []byte {
|
||||
ret := make([]byte, 0, len(enc.b)*8/5+1)
|
||||
x, hasMore := enc.next()
|
||||
for {
|
||||
ret = append(ret, encTable[x])
|
||||
if !hasMore {
|
||||
break
|
||||
}
|
||||
x, hasMore = enc.next()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type CorruptInputError int64
|
||||
|
||||
func (e CorruptInputError) Error() string {
|
||||
return "illegal base62 data at input byte " + strconv.FormatInt(int64(e), 10)
|
||||
}
|
||||
|
||||
func (enc *Base62Encoder) Decode(src []byte) ([]byte, error) {
|
||||
if len(src) == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
dec := decoder(src)
|
||||
return dec.decode(enc.decodeMap[:])
|
||||
}
|
||||
|
||||
func (enc *Base62Encoder) DecodeString(src string) ([]byte, error) {
|
||||
return enc.Decode([]byte(src))
|
||||
}
|
||||
|
||||
type decoder []byte
|
||||
|
||||
func (dec decoder) decode(decTable []byte) ([]byte, error) {
|
||||
ret := make([]byte, len(dec)*6/8+1)
|
||||
idx := len(ret)
|
||||
pos := byte(0)
|
||||
b := 0
|
||||
for i, c := range dec {
|
||||
x := decTable[c]
|
||||
if x == 0xFF {
|
||||
return nil, CorruptInputError(i)
|
||||
}
|
||||
if i == len(dec)-1 {
|
||||
b |= int(x) << pos
|
||||
pos += byte(bits.Len8(x))
|
||||
} else if x&compactMask == compactMask {
|
||||
b |= int(x) << pos
|
||||
pos += 5
|
||||
} else {
|
||||
b |= int(x) << pos
|
||||
pos += 6
|
||||
}
|
||||
if pos >= 8 {
|
||||
idx--
|
||||
ret[idx] = byte(b)
|
||||
pos %= 8
|
||||
b >>= 8
|
||||
}
|
||||
}
|
||||
if pos > 0 {
|
||||
idx--
|
||||
ret[idx] = byte(b)
|
||||
}
|
||||
|
||||
return ret[idx:], nil
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package encoders
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
@@ -20,6 +18,8 @@ import "encoding/base64"
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
// Base64EncoderID - EncoderID
|
||||
const Base64EncoderID = 13
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.17.3
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type DNSMessageType int32
|
||||
|
||||
const (
|
||||
DNSMessageType_NOP DNSMessageType = 0
|
||||
DNSMessageType_DOMAIN_KEY DNSMessageType = 1
|
||||
DNSMessageType_SESSION_INIT DNSMessageType = 2
|
||||
DNSMessageType_SESSION_POLL DNSMessageType = 3
|
||||
DNSMessageType_BLOCK_PREAMBLE DNSMessageType = 7
|
||||
DNSMessageType_BLOCK_DATA DNSMessageType = 8
|
||||
DNSMessageType_BLOCK_CLEAR DNSMessageType = 9
|
||||
)
|
||||
|
||||
// Enum value maps for DNSMessageType.
|
||||
var (
|
||||
DNSMessageType_name = map[int32]string{
|
||||
0: "NOP",
|
||||
1: "DOMAIN_KEY",
|
||||
2: "SESSION_INIT",
|
||||
3: "SESSION_POLL",
|
||||
7: "BLOCK_PREAMBLE",
|
||||
8: "BLOCK_DATA",
|
||||
9: "BLOCK_CLEAR",
|
||||
}
|
||||
DNSMessageType_value = map[string]int32{
|
||||
"NOP": 0,
|
||||
"DOMAIN_KEY": 1,
|
||||
"SESSION_INIT": 2,
|
||||
"SESSION_POLL": 3,
|
||||
"BLOCK_PREAMBLE": 7,
|
||||
"BLOCK_DATA": 8,
|
||||
"BLOCK_CLEAR": 9,
|
||||
}
|
||||
)
|
||||
|
||||
func (x DNSMessageType) Enum() *DNSMessageType {
|
||||
p := new(DNSMessageType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x DNSMessageType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (DNSMessageType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_dnspb_dns_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (DNSMessageType) Type() protoreflect.EnumType {
|
||||
return &file_dnspb_dns_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x DNSMessageType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DNSMessageType.Descriptor instead.
|
||||
func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_dnspb_dns_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type DNSMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Type DNSMessageType `protobuf:"varint,1,opt,name=Type,proto3,enum=dnspb.DNSMessageType" json:"Type,omitempty"`
|
||||
ID uint32 `protobuf:"varint,2,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
N uint32 `protobuf:"varint,3,opt,name=N,proto3" json:"N,omitempty"`
|
||||
Size uint32 `protobuf:"varint,4,opt,name=Size,proto3" json:"Size,omitempty"`
|
||||
Data []byte `protobuf:"bytes,5,opt,name=Data,proto3" json:"Data,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DNSMessage) Reset() {
|
||||
*x = DNSMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_dnspb_dns_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DNSMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DNSMessage) ProtoMessage() {}
|
||||
|
||||
func (x *DNSMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dnspb_dns_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DNSMessage.ProtoReflect.Descriptor instead.
|
||||
func (*DNSMessage) Descriptor() ([]byte, []int) {
|
||||
return file_dnspb_dns_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *DNSMessage) GetType() DNSMessageType {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return DNSMessageType_NOP
|
||||
}
|
||||
|
||||
func (x *DNSMessage) GetID() uint32 {
|
||||
if x != nil {
|
||||
return x.ID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DNSMessage) GetN() uint32 {
|
||||
if x != nil {
|
||||
return x.N
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DNSMessage) GetSize() uint32 {
|
||||
if x != nil {
|
||||
return x.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DNSMessage) GetData() []byte {
|
||||
if x != nil {
|
||||
return x.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_dnspb_dns_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_dnspb_dns_proto_rawDesc = []byte{
|
||||
0x0a, 0x0f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x2f, 0x64, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x12, 0x05, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x22, 0x7d, 0x0a, 0x0a, 0x44, 0x4e, 0x53, 0x4d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x2e, 0x44, 0x4e, 0x53,
|
||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70,
|
||||
0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49,
|
||||
0x44, 0x12, 0x0c, 0x0a, 0x01, 0x4e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x01, 0x4e, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53,
|
||||
0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x04, 0x44, 0x61, 0x74, 0x61, 0x2a, 0x82, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4e, 0x4f,
|
||||
0x50, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e, 0x5f, 0x4b, 0x45,
|
||||
0x59, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x49,
|
||||
0x4e, 0x49, 0x54, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e,
|
||||
0x5f, 0x50, 0x4f, 0x4c, 0x4c, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x4c, 0x4f, 0x43, 0x4b,
|
||||
0x5f, 0x50, 0x52, 0x45, 0x41, 0x4d, 0x42, 0x4c, 0x45, 0x10, 0x07, 0x12, 0x0e, 0x0a, 0x0a, 0x42,
|
||||
0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b, 0x42,
|
||||
0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x10, 0x09, 0x42, 0x2c, 0x5a, 0x2a,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f,
|
||||
0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_dnspb_dns_proto_rawDescOnce sync.Once
|
||||
file_dnspb_dns_proto_rawDescData = file_dnspb_dns_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_dnspb_dns_proto_rawDescGZIP() []byte {
|
||||
file_dnspb_dns_proto_rawDescOnce.Do(func() {
|
||||
file_dnspb_dns_proto_rawDescData = protoimpl.X.CompressGZIP(file_dnspb_dns_proto_rawDescData)
|
||||
})
|
||||
return file_dnspb_dns_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_dnspb_dns_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_dnspb_dns_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_dnspb_dns_proto_goTypes = []interface{}{
|
||||
(DNSMessageType)(0), // 0: dnspb.DNSMessageType
|
||||
(*DNSMessage)(nil), // 1: dnspb.DNSMessage
|
||||
}
|
||||
var file_dnspb_dns_proto_depIdxs = []int32{
|
||||
0, // 0: dnspb.DNSMessage.Type:type_name -> dnspb.DNSMessageType
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_dnspb_dns_proto_init() }
|
||||
func file_dnspb_dns_proto_init() {
|
||||
if File_dnspb_dns_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_dnspb_dns_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DNSMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_dnspb_dns_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_dnspb_dns_proto_goTypes,
|
||||
DependencyIndexes: file_dnspb_dns_proto_depIdxs,
|
||||
EnumInfos: file_dnspb_dns_proto_enumTypes,
|
||||
MessageInfos: file_dnspb_dns_proto_msgTypes,
|
||||
}.Build()
|
||||
File_dnspb_dns_proto = out.File
|
||||
file_dnspb_dns_proto_rawDesc = nil
|
||||
file_dnspb_dns_proto_goTypes = nil
|
||||
file_dnspb_dns_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
syntax = "proto3";
|
||||
package dnspb;
|
||||
option go_package = "github.com/bishopfox/sliver/protobuf/dnspb";
|
||||
|
||||
enum DNSMessageType {
|
||||
NOP = 0; // aka FINGERPRINT
|
||||
DOMAIN_KEY = 1;
|
||||
SESSION_INIT = 2;
|
||||
SESSION_POLL = 3;
|
||||
|
||||
BLOCK_PREAMBLE = 7;
|
||||
BLOCK_DATA = 8;
|
||||
BLOCK_CLEAR = 9;
|
||||
}
|
||||
|
||||
message DNSMessage {
|
||||
DNSMessageType Type = 1;
|
||||
uint32 ID = 2;
|
||||
uint32 N = 3;
|
||||
uint32 Size = 4;
|
||||
bytes Data = 5;
|
||||
}
|
||||
|
||||
+230
-527
@@ -16,58 +16,50 @@ package c2
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
------------------------------------------------------------------------
|
||||
|
||||
DNS command and control implementation
|
||||
|
||||
---
|
||||
DNS Tunnel Implementation
|
||||
*/
|
||||
|
||||
import (
|
||||
secureRand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"math"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
insecureRand "math/rand"
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
"github.com/bishopfox/sliver/protobuf/dnspb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/generate"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/pem"
|
||||
|
||||
secureRand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
insecureRand "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
serverHandlers "github.com/bishopfox/sliver/server/handlers"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
sessionIDSize = 12
|
||||
|
||||
domainKeyMsg = "_domainkey"
|
||||
blockReqMsg = "b"
|
||||
clearBlockMsg = "cb"
|
||||
|
||||
sessionInitMsg = "si"
|
||||
sessionPollingMsg = "sp"
|
||||
sessionEnvelopeMsg = "se"
|
||||
|
||||
// Max TXT record is 255, records are b64 so (n*8 + 5) / 6 = ~250
|
||||
byteBlockSize = 185 // Can be as high as n = 187, but we'll leave some slop
|
||||
blockIDSize = 6
|
||||
@@ -78,23 +70,59 @@ var (
|
||||
|
||||
dnsCharSet = []rune("abcdefghijklmnopqrstuvwxyz0123456789_")
|
||||
|
||||
sendBlocksMutex = &sync.RWMutex{}
|
||||
sendBlocks = &map[string]*SendBlock{}
|
||||
|
||||
dnsSessionsMutex = &sync.RWMutex{}
|
||||
dnsSessions = &map[string]*DNSSession{}
|
||||
dnsSessions = map[string]*DNSSession{}
|
||||
|
||||
blockReassemblerMutex = &sync.RWMutex{}
|
||||
blockReassembler = &map[string][][]byte{}
|
||||
sendBlocksMutex = &sync.RWMutex{}
|
||||
sendBlocks = map[uint32]*Block{}
|
||||
|
||||
dnsSegmentReassemblerMutex = &sync.RWMutex{}
|
||||
dnsSegmentReassembler = &map[string](*map[int][]string){}
|
||||
recvBlocksMutex = &sync.RWMutex{}
|
||||
recvBlocks = map[uint32]*Block{}
|
||||
)
|
||||
|
||||
// SendBlock - Data is encoded and split into `Blocks`
|
||||
type SendBlock struct {
|
||||
ID string
|
||||
Data []string
|
||||
// Block - A blob of data that we're sending or receiving, blocks of data
|
||||
// are split up into arrays of bytes (chunks) that are encoded per-query
|
||||
// the amount of data that can be encoded into a single request or response
|
||||
// varies depending on the type of query and the length of the parent domain.
|
||||
type Block struct {
|
||||
ID uint32
|
||||
data [][]byte
|
||||
Size int
|
||||
Started time.Time
|
||||
Mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (b *Block) AddData(index int, data []byte) (bool, error) {
|
||||
b.Mutex.Lock()
|
||||
defer b.Mutex.Unlock()
|
||||
if len(b.data) < index+1 {
|
||||
return false, errors.New("Data index out of bounds")
|
||||
}
|
||||
b.data[index] = data
|
||||
sum := 0
|
||||
for _, data := range b.data {
|
||||
sum += len(data)
|
||||
}
|
||||
return sum == b.Size, nil
|
||||
}
|
||||
|
||||
func (b *Block) GetData(index int) ([]byte, error) {
|
||||
b.Mutex.Lock()
|
||||
defer b.Mutex.Unlock()
|
||||
if len(b.data) < index+1 {
|
||||
return nil, errors.New("Data index out of bounds")
|
||||
}
|
||||
return b.data[index], nil
|
||||
}
|
||||
|
||||
func (b *Block) Reassemble() []byte {
|
||||
b.Mutex.RLock()
|
||||
defer b.Mutex.RUnlock()
|
||||
data := []byte{}
|
||||
for _, block := range b.data {
|
||||
data = append(data, block...)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// DNSSession - Holds DNS session information
|
||||
@@ -135,7 +163,10 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
|
||||
return server
|
||||
}
|
||||
|
||||
// DNSRequest -> C2 or canary?
|
||||
// ---------------------------
|
||||
// DNS Handler
|
||||
// ---------------------------
|
||||
// Handles all DNS queries, first we determine if the query is C2 or a canary
|
||||
func handleDNSRequest(domains []string, canaries bool, writer dns.ResponseWriter, req *dns.Msg) {
|
||||
if req == nil {
|
||||
dnsLog.Info("req can not be nil")
|
||||
@@ -158,7 +189,6 @@ func handleDNSRequest(domains []string, canaries bool, writer dns.ResponseWriter
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
// dnsLog.Debug(resp.String())
|
||||
writer.WriteMsg(resp)
|
||||
} else {
|
||||
dnsLog.Infof("Invalid query, no DNS response")
|
||||
@@ -177,7 +207,9 @@ func isC2SubDomain(domains []string, reqDomain string) (bool, string) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// C2 -> Record type?
|
||||
// The query is C2, pass to the appropriate record handler this is done
|
||||
// so the record handler can encode the response based on the type of
|
||||
// record that was requested
|
||||
func handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
subdomain := req.Question[0].Name[:len(req.Question[0].Name)-len(domain)]
|
||||
if strings.HasSuffix(subdomain, ".") {
|
||||
@@ -187,24 +219,26 @@ func handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
switch req.Question[0].Qtype {
|
||||
case dns.TypeTXT:
|
||||
return handleTXT(domain, subdomain, req)
|
||||
case dns.TypeA:
|
||||
return handleA(domain, subdomain, req)
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Canary Record Handler
|
||||
// ---------------------------
|
||||
// Canary -> valid? -> trigger alert event
|
||||
func handleCanary(req *dns.Msg) *dns.Msg {
|
||||
|
||||
reqDomain := strings.ToLower(req.Question[0].Name)
|
||||
if !strings.HasSuffix(reqDomain, ".") {
|
||||
reqDomain += "." // Ensure we have the FQDN
|
||||
}
|
||||
|
||||
canary, err := db.CanaryByDomain(reqDomain)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
if canary != nil {
|
||||
@@ -239,179 +273,173 @@ func handleCanary(req *dns.Msg) *dns.Msg {
|
||||
return resp
|
||||
}
|
||||
|
||||
// handles the c2 TXT record interactions, kind hacky this probably needs to get refactored at some point
|
||||
func randomIP() net.IP {
|
||||
randBuf := make([]byte, 4)
|
||||
secureRand.Read(randBuf)
|
||||
|
||||
// Ensure non-zeros with various bitmasks
|
||||
return net.IPv4(randBuf[0]|0x10, randBuf[1]|0x10, randBuf[2]|0x1, randBuf[3]|0x10)
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// C2 Record Handlers
|
||||
// ---------------------------
|
||||
func handleTXT(domain string, subdomain string, req *dns.Msg) *dns.Msg {
|
||||
|
||||
q := req.Question[0]
|
||||
fields := strings.Split(subdomain, ".")
|
||||
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
msgType := strings.ToLower(fields[len(fields)-1])
|
||||
|
||||
switch msgType {
|
||||
|
||||
case domainKeyMsg: // Send PubKey - _(nonce).(slivername).domainkey.example.com
|
||||
result, err := getDomainKeyFor(domain)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Error during session init: %v", err)
|
||||
}
|
||||
txt := &dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
|
||||
Txt: result,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, txt)
|
||||
|
||||
case blockReqMsg: // Get block: _(nonce).(start).(stop).(block id).b.example.com
|
||||
if len(fields) == 5 {
|
||||
startIndex := fields[1]
|
||||
stopIndex := fields[2]
|
||||
blockID := fields[3]
|
||||
txt := &dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
|
||||
Txt: dnsSendBlocks(blockID, startIndex, stopIndex),
|
||||
}
|
||||
resp.Answer = append(resp.Answer, txt)
|
||||
} else {
|
||||
dnsLog.Infof("Block request has invalid number of fields %d expected %d", len(fields), 5)
|
||||
}
|
||||
|
||||
case clearBlockMsg: // Clear block: _(nonce).(block id)._cb.example.com
|
||||
if len(fields) == 3 {
|
||||
result := 0
|
||||
if clearSendBlock(fields[1]) {
|
||||
result = 1
|
||||
}
|
||||
txt := &dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
|
||||
Txt: []string{fmt.Sprintf("%d", result)},
|
||||
}
|
||||
resp.Answer = append(resp.Answer, txt)
|
||||
}
|
||||
|
||||
case "_" + sessionInitMsg:
|
||||
fallthrough
|
||||
case sessionInitMsg: // Session init: (data)...(seq).(nonce).(_)si.example.com
|
||||
result, err := startDNSSession(domain, fields)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Error during session init: %v", err)
|
||||
}
|
||||
txt := &dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
|
||||
Txt: result,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, txt)
|
||||
|
||||
case "_" + sessionEnvelopeMsg:
|
||||
fallthrough
|
||||
case sessionEnvelopeMsg:
|
||||
result, err := dnsSessionEnvelope(domain, fields)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Error during session init: %v", err)
|
||||
}
|
||||
txt := &dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
|
||||
Txt: result,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, txt)
|
||||
|
||||
case sessionPollingMsg:
|
||||
result, err := dnsSessionPoll(domain, fields)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Error during session init: %v", err)
|
||||
}
|
||||
txt := &dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
|
||||
Txt: result,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, txt)
|
||||
|
||||
default:
|
||||
dnsLog.Infof("Unknown msg type '%s' in TXT req", fields[len(fields)-1])
|
||||
checksum, dnsMsg := parseC2Query(subdomain)
|
||||
if dnsMsg == nil {
|
||||
dnsLog.Errorf("Failed to parse TXT query")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute action based on message type
|
||||
|
||||
txtRecords := []string{}
|
||||
|
||||
switch dnsMsg.Type {
|
||||
case dnspb.DNSMessageType_DOMAIN_KEY:
|
||||
blockID := handleDomainKeyQuery(domain, dnsMsg)
|
||||
data, _ := proto.Marshal(&dnspb.DNSMessage{
|
||||
N: checksum,
|
||||
Data: blockID,
|
||||
})
|
||||
record := string(new(encoders.Base64).Encode(data))
|
||||
txtRecords = append(txtRecords, record)
|
||||
}
|
||||
|
||||
txt := &dns.TXT{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
|
||||
Txt: txtRecords,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, txt)
|
||||
return resp
|
||||
}
|
||||
|
||||
// --------------------------- FIELDS ---------------------------
|
||||
func handleA(domain string, subdomain string, req *dns.Msg) *dns.Msg {
|
||||
q := req.Question[0]
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
|
||||
func getFieldMsgType(fields []string) (string, error) {
|
||||
if len(fields) < 1 {
|
||||
return "", errors.New("Invalid number of fields in session init message (nonce)")
|
||||
checksum, dnsMsg := parseC2Query(subdomain)
|
||||
if dnsMsg == nil {
|
||||
dnsLog.Errorf("Failed to parse A query")
|
||||
return nil
|
||||
}
|
||||
return fields[len(fields)-1], nil
|
||||
|
||||
switch dnsMsg.Type {
|
||||
case dnspb.DNSMessageType_NOP:
|
||||
break
|
||||
}
|
||||
|
||||
checksumBuf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(checksumBuf, checksum)
|
||||
resp.Answer = append(resp.Answer, &dns.A{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0},
|
||||
A: checksumBuf,
|
||||
})
|
||||
return resp
|
||||
}
|
||||
|
||||
func getFieldSessionID(fields []string) (string, error) {
|
||||
if len(fields) < 2 {
|
||||
return "", errors.New("Invalid number of fields in session init message (session id)")
|
||||
// ---------------------------
|
||||
// C2 Handler
|
||||
// ---------------------------
|
||||
// This parses the C2 message and performs actions based on the request
|
||||
// the response is a CRC32 of the encoded data in the message
|
||||
func parseC2Query(subdomain string) (uint32, *dnspb.DNSMessage) {
|
||||
subdomainData := strings.Join(strings.Split(subdomain, "."), "")
|
||||
dnsLog.Debugf("Subdomain = %v, Data = %v", subdomain, subdomainData)
|
||||
dnsMsg := decodeMessage(subdomainData)
|
||||
if dnsMsg == nil {
|
||||
return 0, nil
|
||||
}
|
||||
sessionID := fields[len(fields)-2]
|
||||
if sessionID == "_" {
|
||||
return "", errors.New("Session ID is null")
|
||||
}
|
||||
return sessionID, nil
|
||||
checksum := crc32.Checksum(dnsMsg.Data, crc32.IEEETable)
|
||||
return checksum, dnsMsg
|
||||
}
|
||||
|
||||
func getFieldNonce(fields []string) (string, error) {
|
||||
if len(fields) < 3 {
|
||||
return "", errors.New("Invalid number of fields in session init message (nonce)")
|
||||
func decodeMessage(subdomainData string) *dnspb.DNSMessage {
|
||||
encoders := detectEncoding(subdomainData)
|
||||
query := &dnspb.DNSMessage{}
|
||||
for _, encoder := range encoders {
|
||||
dnsLog.Debugf("Attempting to decode subdomain data with %v", encoder)
|
||||
data, err := encoder.Decode([]byte(subdomainData))
|
||||
if err != nil {
|
||||
dnsLog.Debugf("Decode attempt failed %s, continue ...", err)
|
||||
continue
|
||||
}
|
||||
err = proto.Unmarshal(data, query)
|
||||
if err != nil {
|
||||
dnsLog.Debugf("Failed to parse pb %s, continue ...", err)
|
||||
continue
|
||||
}
|
||||
return query
|
||||
}
|
||||
return fields[len(fields)-3], nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFieldSeq(fields []string) (int, error) {
|
||||
if len(fields) < 4 {
|
||||
return -1, errors.New("Invalid number of fields in session init message (seq)")
|
||||
// This function attempts to detect the most likely encoder used by the implant
|
||||
// we return a list of encoders in order of most likely to least likely
|
||||
func detectEncoding(subdomainData string) []encoders.Encoder {
|
||||
|
||||
// Our version of base32 is missing chars: i, l, o, s
|
||||
// if any of these appear the message must be base62
|
||||
base62Chars := strings.ContainsAny(subdomainData, "silo") // "silo" simply appeases my spell checker
|
||||
if base62Chars {
|
||||
return []encoders.Encoder{new(encoders.Base62)}
|
||||
}
|
||||
rawSeq := fields[len(fields)-4]
|
||||
data, err := dnsDecodeString(rawSeq)
|
||||
|
||||
// Okay now we detect if the message only contains lower case letters
|
||||
// our base32 encoder only uses lower case but a resolver could have
|
||||
// messed with the domain letter cases on the way over to us. So we
|
||||
// say it's likely base62 if it contains a mix but fallback to base32
|
||||
if strings.ContainsAny(subdomainData, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
||||
return []encoders.Encoder{new(encoders.Base62), new(encoders.Base32)}
|
||||
} else {
|
||||
return []encoders.Encoder{new(encoders.Base32), new(encoders.Base64)}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------- DNS HANDLERS ---------------------------
|
||||
func encodeSendBlock(domain string, data []byte) *Block {
|
||||
encodedData := new(encoders.Base64).Encode(data)
|
||||
blockData := [][]byte{}
|
||||
for index := 0; index < len(encodedData); {
|
||||
end := index + 254
|
||||
if len(encodedData) < end {
|
||||
end = len(encodedData)
|
||||
}
|
||||
blockData = append(blockData, encodedData[index:end])
|
||||
index += 254
|
||||
}
|
||||
return &Block{
|
||||
ID: blockID(),
|
||||
Size: len(data),
|
||||
data: blockData,
|
||||
Started: time.Now(),
|
||||
Mutex: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func handleDomainKeyQuery(domain string, query *dnspb.DNSMessage) []byte {
|
||||
domainKey, err := getDomainKeyFor(domain)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Failed to decode seq field: %#v", rawSeq)
|
||||
return 0, err
|
||||
dnsLog.Errorf("Failed to find domain key for '%s' %s", domain, err)
|
||||
return nil
|
||||
}
|
||||
index := int(binary.LittleEndian.Uint32(data))
|
||||
|
||||
return index, nil
|
||||
block := encodeSendBlock(domain, domainKey)
|
||||
sendBlocksMutex.Lock()
|
||||
defer sendBlocksMutex.Unlock()
|
||||
sendBlocks[block.ID] = block
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
func getFieldSubdata(fields []string) ([]string, error) {
|
||||
if len(fields) < 5 {
|
||||
return []string{}, errors.New("Invalid number of fields in session init message (subdata)")
|
||||
}
|
||||
subdataFields := len(fields) - 4
|
||||
dnsLog.Infof("Domain contains %d subdata fields", subdataFields)
|
||||
return fields[:subdataFields], nil
|
||||
}
|
||||
|
||||
// --------------------------- DNS SESSION START ---------------------------
|
||||
|
||||
// Returns an confirmation value (e.g. exit code 0 non-0) and error
|
||||
func startDNSSession(domain string, fields []string) ([]string, error) {
|
||||
dnsLog.Infof("[start session] fields = %#v", fields)
|
||||
func startDNSSession(msgID uint32, domain string) ([]string, error) {
|
||||
|
||||
msgType, err := getFieldMsgType(fields)
|
||||
if err != nil {
|
||||
return []string{"1"}, err
|
||||
}
|
||||
|
||||
nonce, err := getFieldNonce(fields)
|
||||
if err != nil {
|
||||
return []string{"1"}, err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(msgType, "_") {
|
||||
return dnsSegment(fields)
|
||||
}
|
||||
dnsLog.Infof("Complete session init message received, reassembling ...")
|
||||
|
||||
// TODO: We don't have replay protection against the RSA-encrypt
|
||||
// sessionInit messages, but I don't think it's an issue ...
|
||||
encryptedSessionInit, err := dnsSegmentReassemble(nonce)
|
||||
if err != nil {
|
||||
return []string{"1"}, err
|
||||
}
|
||||
|
||||
publicKeyPEM, privateKeyPEM, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, domain)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Failed to fetch RSA private key")
|
||||
@@ -452,7 +480,7 @@ func startDNSSession(domain string, fields []string) ([]string, error) {
|
||||
sessionID := dnsSessionID()
|
||||
dnsLog.Infof("Starting new DNS session with id = %s", sessionID)
|
||||
dnsSessionsMutex.Lock()
|
||||
(*dnsSessions)[sessionID] = &DNSSession{
|
||||
dnsSessions[sessionID] = &DNSSession{
|
||||
ID: sessionID,
|
||||
Session: session,
|
||||
Key: aesKey,
|
||||
@@ -461,359 +489,16 @@ func startDNSSession(domain string, fields []string) ([]string, error) {
|
||||
dnsSessionsMutex.Unlock()
|
||||
|
||||
encryptedSessionID, _ := cryptography.GCMEncrypt(aesKey, []byte(sessionID))
|
||||
result, err := dnsSendOnce(encryptedSessionID)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Failed to encode message into single result %v", err)
|
||||
return []string{"1"}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// --------------------------- DNS SESSION RECV ---------------------------
|
||||
|
||||
func dnsSessionEnvelope(domain string, fields []string) ([]string, error) {
|
||||
dnsLog.Infof("[session envelope] fields = %#v", fields)
|
||||
|
||||
msgType, err := getFieldMsgType(fields)
|
||||
if err != nil {
|
||||
return []string{"1"}, err
|
||||
}
|
||||
|
||||
nonce, err := getFieldNonce(fields)
|
||||
if err != nil {
|
||||
return []string{"1"}, err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(msgType, "_") {
|
||||
return dnsSegment(fields)
|
||||
}
|
||||
dnsLog.Infof("Complete envelope received, reassembling ...")
|
||||
encryptedDNSEnvelope, err := dnsSegmentReassemble(nonce)
|
||||
if err != nil {
|
||||
return []string{"1"}, errors.New("Failed to reassemble segments")
|
||||
}
|
||||
|
||||
sessionID, err := getFieldSessionID(fields)
|
||||
if err != nil {
|
||||
return []string{"1"}, err
|
||||
}
|
||||
dnsSessionsMutex.Lock()
|
||||
defer dnsSessionsMutex.Unlock()
|
||||
|
||||
if dnsSession, ok := (*dnsSessions)[sessionID]; ok {
|
||||
dnsLog.Infof("Envelope has valid DNS session (%s)", dnsSession.ID)
|
||||
if dnsSession.isReplayAttack(encryptedDNSEnvelope) {
|
||||
dnsLog.Infof("WARNING: Replay attack detected, ignore request")
|
||||
return []string{"1"}, errors.New("Replay attack")
|
||||
}
|
||||
envelopeData, err := cryptography.GCMDecrypt(dnsSession.Key, encryptedDNSEnvelope)
|
||||
if err != nil {
|
||||
return []string{"1"}, errors.New("Failed to decrypt DNS envelope")
|
||||
}
|
||||
envelope := &sliverpb.Envelope{}
|
||||
proto.Unmarshal(envelopeData, envelope)
|
||||
|
||||
dnsLog.Infof("Envelope Type = %#v RespID = %#v", envelope.Type, envelope.ID)
|
||||
|
||||
dnsSession.Session.UpdateCheckin()
|
||||
|
||||
// Response Envelope or Handler
|
||||
handlers := serverHandlers.GetSessionHandlers()
|
||||
if envelope.ID != 0 {
|
||||
dnsSession.Session.RespMutex.Lock()
|
||||
defer dnsSession.Session.RespMutex.Unlock()
|
||||
if resp, ok := dnsSession.Session.Resp[envelope.ID]; ok {
|
||||
resp <- envelope
|
||||
}
|
||||
} else if handler, ok := handlers[envelope.Type]; ok {
|
||||
handler.(func(*core.Session, []byte))(dnsSession.Session, envelope.Data)
|
||||
}
|
||||
return []string{"0"}, nil
|
||||
}
|
||||
dnsLog.Infof("Invalid session id '%#v'", sessionID)
|
||||
return []string{"1"}, errors.New("Invalid session ID")
|
||||
}
|
||||
|
||||
// Client should have sent all of the data, attempt to reassemble segments
|
||||
func dnsSegmentReassemble(nonce string) ([]byte, error) {
|
||||
dnsSegmentReassemblerMutex.Lock()
|
||||
defer dnsSegmentReassemblerMutex.Unlock()
|
||||
if reasm, ok := (*dnsSegmentReassembler)[nonce]; ok {
|
||||
var keys []int
|
||||
for k := range *reasm {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Ints(keys)
|
||||
orderedSubdata := []string{}
|
||||
for _, k := range keys {
|
||||
orderedSubdata = append(orderedSubdata, (*reasm)[k]...)
|
||||
}
|
||||
data, err := dnsDecodeString(strings.Join(orderedSubdata, ""))
|
||||
if err != nil {
|
||||
dnsLog.Infof("Failed to decode session init: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
//BUG - in the event that the session init msg does not make it to the client, this will fail. This has been
|
||||
//observed consistently (Namely due to the race condition bug that is also being fixed in dnsSessionPoll).
|
||||
//Currently a memory leak. Consider tracking moving the cleanup code to after polling has
|
||||
//begun to ensure the initial handshake was successful.
|
||||
//
|
||||
//delete((*dnsSegmentReassembler), nonce)
|
||||
return data, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Invalid nonce '%#v' (session init reassembler)", nonce)
|
||||
}
|
||||
|
||||
// The domain is only a segment of the startDNSSession message, so we just store the data
|
||||
func dnsSegment(fields []string) ([]string, error) {
|
||||
dnsSegmentReassemblerMutex.Lock()
|
||||
defer dnsSegmentReassemblerMutex.Unlock()
|
||||
|
||||
nonce, _ := getFieldNonce(fields)
|
||||
index, err := getFieldSeq(fields)
|
||||
if err != nil {
|
||||
return []string{"1"}, err
|
||||
}
|
||||
subdata, err := getFieldSubdata(fields)
|
||||
if err != nil {
|
||||
return []string{"1"}, err
|
||||
}
|
||||
if _, ok := (*dnsSegmentReassembler)[nonce]; !ok {
|
||||
(*dnsSegmentReassembler)[nonce] = &map[int][]string{}
|
||||
}
|
||||
if reasm, ok := (*dnsSegmentReassembler)[nonce]; ok {
|
||||
(*reasm)[index] = subdata
|
||||
return []string{"0"}, nil
|
||||
}
|
||||
dnsLog.Infof("Invalid nonce (session segment): %#v", nonce)
|
||||
return []string{"1"}, errors.New("Invalid nonce (session segment)")
|
||||
}
|
||||
|
||||
// TODO: Avoid double-fetch
|
||||
func getDomainKeyFor(domain string) ([]string, error) {
|
||||
_, _, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, domain)
|
||||
if err != nil {
|
||||
certs.C2ServerGenerateRSACertificate(domain)
|
||||
}
|
||||
certPEM, _, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dnsSendOnce(certPEM)
|
||||
}
|
||||
|
||||
// --------------------------- DNS SESSION SEND ---------------------------
|
||||
|
||||
// Send all response data in a single TXT record, limited to 65535 bytes
|
||||
func dnsSendOnce(rawData []byte) ([]string, error) {
|
||||
if 65535 <= base64.RawStdEncoding.EncodedLen(len(rawData)) {
|
||||
return nil, errors.New("Response too large to encode into one TXT record")
|
||||
}
|
||||
data := base64.RawStdEncoding.EncodeToString(rawData)
|
||||
dnsLog.Infof("Encoding single resp: %#v", data)
|
||||
txts := []string{}
|
||||
size := int(math.Ceil(float64(len(data)) / 255.0))
|
||||
for index := 0; index < size; index++ {
|
||||
start := index * 255
|
||||
stop := start + 255
|
||||
if len(data) < stop {
|
||||
stop = len(data)
|
||||
}
|
||||
txts = append(txts, data[start:stop])
|
||||
}
|
||||
return txts, nil
|
||||
}
|
||||
|
||||
func dnsSessionPoll(domain string, fields []string) ([]string, error) {
|
||||
|
||||
sessionID, err := getFieldSessionID(fields)
|
||||
if err != nil {
|
||||
return []string{"1"}, errors.New("invalid session id (session poll)")
|
||||
}
|
||||
dnsSessionsMutex.Lock()
|
||||
dnsSession, found := (*dnsSessions)[sessionID]
|
||||
if !found {
|
||||
dnsSessionsMutex.Unlock()
|
||||
b64SessionId := base64.RawStdEncoding.EncodeToString([]byte(sessionID))
|
||||
return []string{b64SessionId}, errors.New("invalid session (nil pointer)")
|
||||
}
|
||||
dnsSessionsMutex.Unlock()
|
||||
|
||||
isDrained := false
|
||||
envelopes := []*sliverpb.Envelope{}
|
||||
for !isDrained {
|
||||
select {
|
||||
case envelope := <-dnsSession.Session.Send:
|
||||
dnsLog.Infof("New message from send channel ...")
|
||||
envelopes = append(envelopes, envelope)
|
||||
default:
|
||||
isDrained = true
|
||||
}
|
||||
}
|
||||
|
||||
dnsSession.Session.UpdateCheckin()
|
||||
|
||||
if 0 < len(envelopes) {
|
||||
dnsLog.Infof("%d new message(s) for session id %#v", len(envelopes), sessionID)
|
||||
dnsPoll := &sliverpb.DNSPoll{}
|
||||
for _, envelope := range envelopes {
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Failed to encode envelope %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
encryptedEnvelopeData, err := cryptography.GCMEncrypt(dnsSession.Key, data)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Failed to encrypt poll data %v", err)
|
||||
return []string{"1"}, errors.New("Failed to encrypt dns poll data")
|
||||
}
|
||||
|
||||
blockID, size := storeSendBlocks(encryptedEnvelopeData)
|
||||
dnsPoll.Blocks = append(dnsPoll.Blocks, &sliverpb.DNSBlockHeader{
|
||||
ID: blockID,
|
||||
Size: uint32(size),
|
||||
})
|
||||
}
|
||||
pollData, err := proto.Marshal(dnsPoll)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Failed to encode envelope %v", err)
|
||||
return []string{"1"}, errors.New("Failed to encode dns poll data")
|
||||
}
|
||||
encryptedPollData, err := cryptography.GCMEncrypt(dnsSession.Key, pollData)
|
||||
if err != nil {
|
||||
dnsLog.Infof("Failed to encrypt poll data %v", err)
|
||||
return []string{"1"}, errors.New("Failed to encrypt dns poll data")
|
||||
}
|
||||
return dnsSendOnce(encryptedPollData)
|
||||
}
|
||||
dnsLog.Infof("No new message for session id %#v", sessionID)
|
||||
return []string{"0"}, nil
|
||||
}
|
||||
|
||||
// Send blocks of data via multiple DNS TXT responses
|
||||
func dnsSendBlocks(blockID string, startIndex string, stopIndex string) []string {
|
||||
start, err := strconv.Atoi(startIndex)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
stop, err := strconv.Atoi(stopIndex)
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if stop < start {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
dnsLog.Infof("Send blocks %d to %d for ID %s", start, stop, blockID)
|
||||
|
||||
sendBlocksMutex.Lock()
|
||||
defer sendBlocksMutex.Unlock()
|
||||
respBlocks := []string{}
|
||||
if block, ok := (*sendBlocks)[blockID]; ok {
|
||||
for index := start; index < stop; index++ {
|
||||
if index < len(block.Data) {
|
||||
respBlocks = append(respBlocks, block.Data[index])
|
||||
}
|
||||
}
|
||||
dnsLog.Infof("Sending %d response block(s)", len(respBlocks))
|
||||
return respBlocks
|
||||
}
|
||||
dnsLog.Infof("Invalid block ID: %#v", blockID)
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Clear send blocks of data from memory
|
||||
func clearSendBlock(blockID string) bool {
|
||||
sendBlocksMutex.Lock()
|
||||
defer sendBlocksMutex.Unlock()
|
||||
if _, ok := (*sendBlocks)[blockID]; ok {
|
||||
delete(*sendBlocks, blockID)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Stores encoded blocks fo data into "sendBlocks"
|
||||
func storeSendBlocks(data []byte) (string, int) {
|
||||
blockID := generateBlockID()
|
||||
|
||||
sendBlock := &SendBlock{
|
||||
ID: blockID,
|
||||
Data: []string{},
|
||||
}
|
||||
for index := 0; index < len(data); index += byteBlockSize {
|
||||
start := index
|
||||
stop := index + byteBlockSize
|
||||
if len(data) < stop {
|
||||
stop = len(data)
|
||||
}
|
||||
encoded := base64.RawStdEncoding.EncodeToString(data[start:stop])
|
||||
dnsLog.Infof("Encoded block is %d bytes", len(encoded))
|
||||
sendBlock.Data = append(sendBlock.Data, encoded)
|
||||
}
|
||||
sendBlocksMutex.Lock()
|
||||
(*sendBlocks)[sendBlock.ID] = sendBlock
|
||||
sendBlocksMutex.Unlock()
|
||||
return sendBlock.ID, len(sendBlock.Data)
|
||||
}
|
||||
|
||||
// --------------------------- HELPERS ---------------------------
|
||||
|
||||
// Unique IDs, no need for secure random
|
||||
func generateBlockID() string {
|
||||
insecureRand.Seed(time.Now().UnixNano())
|
||||
blockID := []rune{}
|
||||
for i := 0; i < blockIDSize; i++ {
|
||||
index := insecureRand.Intn(len(dnsCharSet))
|
||||
blockID = append(blockID, dnsCharSet[index])
|
||||
}
|
||||
return string(blockID)
|
||||
}
|
||||
|
||||
func fingerprintSHA256(block *pem.Block) string {
|
||||
hash := sha256.Sum256(block.Bytes)
|
||||
b64hash := base64.RawStdEncoding.EncodeToString(hash[:])
|
||||
return strings.TrimRight(b64hash, "=")
|
||||
}
|
||||
|
||||
// TODO: Add a geofilter to make it look like we're in various regions of the world.
|
||||
// We don't really need to use srand but it's just easier to operate on bytes here.
|
||||
func randomIP() net.IP {
|
||||
randBuf := make([]byte, 4)
|
||||
secureRand.Read(randBuf)
|
||||
|
||||
// Ensure non-zeros with various bitmasks
|
||||
return net.IPv4(randBuf[0]|0x10, randBuf[1]|0x10, randBuf[2]|0x1, randBuf[3]|0x10)
|
||||
}
|
||||
|
||||
// --------------------------- ENCODER ---------------------------
|
||||
|
||||
var base32Alphabet = "ab1c2d3e4f5g6h7j8k9m0npqrtuvwxyz"
|
||||
var sliverBase32 = base32.NewEncoding(base32Alphabet)
|
||||
|
||||
// EncodeToString encodes the given byte slice in base32
|
||||
func dnsEncodeToString(input []byte) string {
|
||||
return strings.TrimRight(sliverBase32.EncodeToString(input), "=")
|
||||
}
|
||||
|
||||
// DecodeString decodes the given base32 encoded bytes
|
||||
func dnsDecodeString(raw string) ([]byte, error) {
|
||||
pad := 8 - (len(raw) % 8)
|
||||
padded := []byte(raw)
|
||||
if pad != 8 {
|
||||
padded = make([]byte, len(raw)+pad)
|
||||
copy(padded, raw)
|
||||
for index := 0; index < pad; index++ {
|
||||
padded[len(raw)+index] = '='
|
||||
}
|
||||
}
|
||||
// dnsLog.Infof("[base32] %#v", string(padded))
|
||||
return sliverBase32.DecodeString(string(padded))
|
||||
}
|
||||
|
||||
// SessionIDs are public parameters in this use case
|
||||
// so it's only important that they're unique
|
||||
func dnsSessionID() string {
|
||||
@@ -823,5 +508,23 @@ func dnsSessionID() string {
|
||||
index := insecureRand.Intn(len(dnsCharSet))
|
||||
sessionID = append(sessionID, dnsCharSet[index])
|
||||
}
|
||||
return "_" + string(sessionID)
|
||||
return string(sessionID)
|
||||
}
|
||||
|
||||
func blockID() uint32 {
|
||||
randBuf := make([]byte, 4)
|
||||
secureRand.Read(randBuf)
|
||||
return binary.LittleEndian.Uint32(randBuf)
|
||||
}
|
||||
|
||||
func getDomainKeyFor(domain string) ([]byte, error) {
|
||||
_, _, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, domain)
|
||||
if err != nil {
|
||||
certs.C2ServerGenerateRSACertificate(domain)
|
||||
}
|
||||
certPEM, _, err := certs.GetCertificate(certs.C2ServerCA, certs.RSAKey, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return certPEM, nil
|
||||
}
|
||||
@@ -27,6 +27,8 @@ var (
|
||||
srcFiles = []string{
|
||||
"constants/constants.go",
|
||||
|
||||
"encoders/base32.go",
|
||||
"encoders/base62.go",
|
||||
"encoders/base64.go",
|
||||
"encoders/combos.go",
|
||||
"encoders/encoders.go",
|
||||
@@ -165,6 +167,8 @@ var (
|
||||
genericSrcFiles = []string{
|
||||
"constants/constants.go",
|
||||
|
||||
"encoders/base32.go",
|
||||
"encoders/base62.go",
|
||||
"encoders/base64.go",
|
||||
"encoders/combos.go",
|
||||
"encoders/encoders.go",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package encoders
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
)
|
||||
|
||||
// Base32EncoderID - EncoderID
|
||||
const Base32EncoderID = 65
|
||||
|
||||
// Base32 Encoder
|
||||
type Base32 struct{}
|
||||
|
||||
// Missing chars: i, l, o, s
|
||||
const base32Alphabet = "ab1c2d3e4f5g6h7j8k9m0npqrtuvwxyz"
|
||||
|
||||
var sliverBase32 = base32.NewEncoding(base32Alphabet)
|
||||
|
||||
// Encode - Base32 Encode
|
||||
func (e Base32) Encode(data []byte) []byte {
|
||||
return []byte(sliverBase32.EncodeToString(data))
|
||||
}
|
||||
|
||||
// Decode - Base32 Decode
|
||||
func (e Base32) Decode(data []byte) ([]byte, error) {
|
||||
return sliverBase32.DecodeString(string(data))
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package encoders
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
func TestBase32(t *testing.T) {
|
||||
sample := randomData()
|
||||
|
||||
b32 := new(Base32)
|
||||
output := b32.Encode(sample)
|
||||
data, err := b32.Decode(output)
|
||||
if err != nil {
|
||||
t.Errorf("b32 decode returned an error %v", err)
|
||||
}
|
||||
if !bytes.Equal(sample, data) {
|
||||
t.Logf("sample = %#v", sample)
|
||||
t.Logf("output = %#v", output)
|
||||
t.Logf(" data = %#v", data)
|
||||
t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
|
||||
}
|
||||
|
||||
implantBase32 := new(implantEncoders.Base32)
|
||||
output2 := implantBase32.Encode(sample)
|
||||
data2, err := implantBase32.Decode(output2)
|
||||
if err != nil {
|
||||
t.Errorf("implant b32 decode returned an error %v", err)
|
||||
}
|
||||
if !bytes.Equal(sample, data2) {
|
||||
t.Logf("sample = %#v", sample)
|
||||
t.Logf("output2 = %#v", output2)
|
||||
t.Logf(" data2 = %#v", data2)
|
||||
t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
|
||||
}
|
||||
|
||||
output = b32.Encode(sample)
|
||||
data, err = implantBase32.Decode(output)
|
||||
if err != nil {
|
||||
t.Errorf("b32 decode returned an error %v", err)
|
||||
}
|
||||
if !bytes.Equal(sample, data) {
|
||||
t.Logf("sample = %#v", sample)
|
||||
t.Logf("output = %#v", output)
|
||||
t.Logf(" data = %#v", data)
|
||||
t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
|
||||
}
|
||||
|
||||
output = implantBase32.Encode(sample)
|
||||
data, err = b32.Decode(output)
|
||||
if err != nil {
|
||||
t.Errorf("b32 decode returned an error %v", err)
|
||||
}
|
||||
if !bytes.Equal(sample, data) {
|
||||
t.Logf("sample = %#v", sample)
|
||||
t.Logf("output = %#v", output)
|
||||
t.Logf(" data = %#v", data)
|
||||
t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package encoders
|
||||
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Shawn Wang <[email protected]>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Modifications by moloch to work with Sliver
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
base = 62
|
||||
compactMask = 0x1E // 00011110
|
||||
mask5bits = 0x1F // 00011111
|
||||
)
|
||||
|
||||
// Base62EncoderID - EncoderID
|
||||
const Base62EncoderID = 43
|
||||
|
||||
// Base62 Encoder
|
||||
type Base62 struct{}
|
||||
|
||||
const base62Alphabet = "aBCDEFGHIJKLMNOPQRSTUVWXYZAbcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
// Encode - Base62 Encode
|
||||
func (e Base62) Encode(data []byte) []byte {
|
||||
return []byte(NewEncoding(base62Alphabet).EncodeToString(data))
|
||||
}
|
||||
|
||||
// Decode - Base62 Decode
|
||||
func (e Base62) Decode(data []byte) ([]byte, error) {
|
||||
return NewEncoding(base62Alphabet).DecodeString(string(data))
|
||||
}
|
||||
|
||||
type Base62Encoder struct {
|
||||
encode [base]byte
|
||||
decodeMap [256]byte
|
||||
}
|
||||
|
||||
func NewEncoding(encoder string) *Base62Encoder {
|
||||
if len(encoder) != base {
|
||||
panic("encoding alphabet is not 62-bytes long")
|
||||
}
|
||||
for i := 0; i < len(encoder); i++ {
|
||||
if encoder[i] == '\n' || encoder[i] == '\r' {
|
||||
panic("encoding alphabet contains newline character")
|
||||
}
|
||||
}
|
||||
|
||||
e := new(Base62Encoder)
|
||||
copy(e.encode[:], encoder)
|
||||
for i := 0; i < len(e.decodeMap); i++ {
|
||||
e.decodeMap[i] = 0xFF
|
||||
}
|
||||
for i := 0; i < len(encoder); i++ {
|
||||
e.decodeMap[encoder[i]] = byte(i)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (enc *Base62Encoder) Encode(src []byte) []byte {
|
||||
if len(src) == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
encoder := newEncoder(src)
|
||||
return encoder.encode(enc.encode[:])
|
||||
}
|
||||
|
||||
func (enc *Base62Encoder) EncodeToString(src []byte) string {
|
||||
ret := enc.Encode(src)
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
b []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
func newEncoder(src []byte) *encoder {
|
||||
return &encoder{
|
||||
b: src,
|
||||
pos: len(src) * 8,
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *encoder) next() (byte, bool) {
|
||||
var i, pos int
|
||||
var j, blen byte
|
||||
pos = enc.pos - 6
|
||||
if pos <= 0 {
|
||||
pos = 0
|
||||
blen = byte(enc.pos)
|
||||
} else {
|
||||
i = pos / 8
|
||||
j = byte(pos % 8)
|
||||
blen = byte((i+1)*8 - pos)
|
||||
if blen > 6 {
|
||||
blen = 6
|
||||
}
|
||||
}
|
||||
shift := 8 - j - blen
|
||||
b := enc.b[i] >> shift & (1<<blen - 1)
|
||||
|
||||
if blen < 6 && pos > 0 {
|
||||
blen1 := 6 - blen
|
||||
b = b<<blen1 | enc.b[i+1]>>(8-blen1)
|
||||
}
|
||||
if b&compactMask == compactMask {
|
||||
if pos > 0 || b > mask5bits {
|
||||
pos++
|
||||
}
|
||||
b &= mask5bits
|
||||
}
|
||||
enc.pos = pos
|
||||
|
||||
return b, pos > 0
|
||||
}
|
||||
|
||||
func (enc *encoder) encode(encTable []byte) []byte {
|
||||
ret := make([]byte, 0, len(enc.b)*8/5+1)
|
||||
x, hasMore := enc.next()
|
||||
for {
|
||||
ret = append(ret, encTable[x])
|
||||
if !hasMore {
|
||||
break
|
||||
}
|
||||
x, hasMore = enc.next()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type CorruptInputError int64
|
||||
|
||||
func (e CorruptInputError) Error() string {
|
||||
return "illegal base62 data at input byte " + strconv.FormatInt(int64(e), 10)
|
||||
}
|
||||
|
||||
func (enc *Base62Encoder) Decode(src []byte) ([]byte, error) {
|
||||
if len(src) == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
dec := decoder(src)
|
||||
return dec.decode(enc.decodeMap[:])
|
||||
}
|
||||
|
||||
func (enc *Base62Encoder) DecodeString(src string) ([]byte, error) {
|
||||
return enc.Decode([]byte(src))
|
||||
}
|
||||
|
||||
type decoder []byte
|
||||
|
||||
func (dec decoder) decode(decTable []byte) ([]byte, error) {
|
||||
ret := make([]byte, len(dec)*6/8+1)
|
||||
idx := len(ret)
|
||||
pos := byte(0)
|
||||
b := 0
|
||||
for i, c := range dec {
|
||||
x := decTable[c]
|
||||
if x == 0xFF {
|
||||
return nil, CorruptInputError(i)
|
||||
}
|
||||
if i == len(dec)-1 {
|
||||
b |= int(x) << pos
|
||||
pos += byte(bits.Len8(x))
|
||||
} else if x&compactMask == compactMask {
|
||||
b |= int(x) << pos
|
||||
pos += 5
|
||||
} else {
|
||||
b |= int(x) << pos
|
||||
pos += 6
|
||||
}
|
||||
if pos >= 8 {
|
||||
idx--
|
||||
ret[idx] = byte(b)
|
||||
pos %= 8
|
||||
b >>= 8
|
||||
}
|
||||
}
|
||||
if pos > 0 {
|
||||
idx--
|
||||
ret[idx] = byte(b)
|
||||
}
|
||||
|
||||
return ret[idx:], nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package encoders
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
func TestBase62(t *testing.T) {
|
||||
sample := randomData()
|
||||
|
||||
b62 := new(Base62)
|
||||
output := b62.Encode(sample)
|
||||
data, err := b62.Decode(output)
|
||||
if err != nil {
|
||||
t.Errorf("b62 decode returned an error %v", err)
|
||||
}
|
||||
if !bytes.Equal(sample, data) {
|
||||
t.Logf("sample = %#v", sample)
|
||||
t.Logf("output = %#v", output)
|
||||
t.Logf(" data = %#v", data)
|
||||
t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
|
||||
}
|
||||
|
||||
implantBase62 := new(implantEncoders.Base62)
|
||||
output2 := implantBase62.Encode(sample)
|
||||
data2, err := implantBase62.Decode(output2)
|
||||
if err != nil {
|
||||
t.Errorf("implant b62 decode returned an error %v", err)
|
||||
}
|
||||
if !bytes.Equal(sample, data2) {
|
||||
t.Logf("sample = %#v", sample)
|
||||
t.Logf("output2 = %#v", output2)
|
||||
t.Logf(" data2 = %#v", data2)
|
||||
t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
|
||||
}
|
||||
|
||||
output = b62.Encode(sample)
|
||||
data, err = implantBase62.Decode(output)
|
||||
if err != nil {
|
||||
t.Errorf("b62 decode returned an error %v", err)
|
||||
}
|
||||
if !bytes.Equal(sample, data) {
|
||||
t.Logf("sample = %#v", sample)
|
||||
t.Logf("output = %#v", output)
|
||||
t.Logf(" data = %#v", data)
|
||||
t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
|
||||
}
|
||||
|
||||
output = implantBase62.Encode(sample)
|
||||
data, err = b62.Decode(output)
|
||||
if err != nil {
|
||||
t.Errorf("b62 decode returned an error %v", err)
|
||||
}
|
||||
if !bytes.Equal(sample, data) {
|
||||
t.Logf("sample = %#v", sample)
|
||||
t.Logf("output = %#v", output)
|
||||
t.Logf(" data = %#v", data)
|
||||
t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package encoders
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
@@ -20,6 +18,8 @@ import "encoding/base64"
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
// Base64EncoderID - EncoderID
|
||||
const Base64EncoderID = 13
|
||||
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package encoders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
@@ -25,6 +18,13 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
func TestBase64(t *testing.T) {
|
||||
sample := randomData()
|
||||
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package encoders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
@@ -25,6 +18,13 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
func TestGzipEnglish(t *testing.T) {
|
||||
sample := randomData()
|
||||
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package encoders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
@@ -25,6 +18,13 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
func TestEnglish(t *testing.T) {
|
||||
sample := randomData()
|
||||
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package encoders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
@@ -25,6 +18,13 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
func TestGzip(t *testing.T) {
|
||||
sample := randomData()
|
||||
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package encoders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
@@ -25,6 +18,13 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
|
||||
)
|
||||
|
||||
func TestHex(t *testing.T) {
|
||||
sample := randomData()
|
||||
|
||||
|
||||
Reference in new issue
Block a user