Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simulator/encrypted-dns: new encrypted DNS module #41

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
wisdom,simulator/encdns: encrypted-dns re-write
Address comments in code review, namely:
- servers pulled from open-wisdom
- no protocol specified, run all protocols against a random set of
servers
Further enhancements and fixes
  • Loading branch information
kmroz committed Dec 13, 2021
commit 6ed3c5e1d28f9cfe7dca288d5482d6f289475e33
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Cheatsheet:
flightsim run c2 Simulate C2 traffic
flightsim run c2:trickbot Simulate C2 traffic for the TrickBot family
flightsim run ssh-transfer:1GB Simulate a 1GB SSH/SFTP file transfer
flightsim run encrypted-dns Simulate encrypted DNS traffic via DNS-over-HTTPS, DNS-over-TLS and DNSCrypt
flightsim run encrypted-dns:doh Simulate encrypted DNS traffic via a specific protocol (DNS-over-HTTPS in this case)

flightsim get families:c2 Get a list of all c2 families
```
Expand All @@ -58,7 +60,7 @@ To run all available modules, call:

Available modules:

c2, dga, imposter, miner, scan, sink, spambot, ssh-exfil, ssh-transfer, tunnel-dns, tunnel-icmp
c2, dga, encrypted-dns, imposter, miner, scan, sink, spambot, ssh-exfil, ssh-transfer, tunnel-dns, tunnel-icmp

Available flags:
-dry
Expand Down Expand Up @@ -138,16 +140,17 @@ All done!

The modules packaged with the utility are listed in the table below.

| Module | Description |
| ------------- | ----------------------------------------------------------------------------- |
| `c2` | Generates both DNS and IP traffic to a random list of known C2 destinations |
| `dga` | Simulates DGA traffic using random labels and top-level domains |
| `imposter` | Generates DNS traffic to a list of imposter domains |
| `miner` | Generates Stratum mining protocol traffic to known cryptomining pools |
| `scan` | Performs a port scan of random RFC 5737 addresses using common TCP ports |
| `sink` | Connects to known sinkholed destinations run by security researchers |
| `spambot` | Resolves and connects to random Internet SMTP servers to simulate a spam bot |
| `ssh-exfil` | Simulates an SSH file transfer to a service running on a non-standard SSH port|
| `ssh-transfer`| Simulates an SSH file transfer to a service running on an SSH port |
| `tunnel-dns` | Generates DNS tunneling requests to \*.sandbox.alphasoc.xyz |
| `tunnel-icmp` | Generates ICMP tunneling traffic to an Internet service operated by AlphaSOC |
| Module | Description |
| ------------- | ----------------------------------------------------------------------------- |
| `c2` | Generates both DNS and IP traffic to a random list of known C2 destinations |
| `dga` | Simulates DGA traffic using random labels and top-level domains |
| `encrypted-dns` | Simulates encrypted DNS traffic via DNS-over-HTTPS, DNS-over-TLS and/or DNSCrypt |
| `imposter` | Generates DNS traffic to a list of imposter domains |
| `miner` | Generates Stratum mining protocol traffic to known cryptomining pools |
| `scan` | Performs a port scan of random RFC 5737 addresses using common TCP ports |
| `sink` | Connects to known sinkholed destinations run by security researchers |
| `spambot` | Resolves and connects to random Internet SMTP servers to simulate a spam bot |
| `ssh-exfil` | Simulates an SSH file transfer to a service running on a non-standard SSH port |
| `ssh-transfer` | Simulates an SSH file transfer to a service running on an SSH port |
| `tunnel-dns` | Generates DNS tunneling requests to \*.sandbox.alphasoc.xyz |
| `tunnel-icmp` | Generates ICMP tunneling traffic to an Internet service operated by AlphaSOC |
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ Cheatsheet:
flightsim run c2 Simulate C2 traffic
flightsim run c2:trickbot Simulate C2 traffic for the TrickBot family
flightsim run ssh-transfer:1GB Simulate a 1GB SSH/SFTP file transfer
flightsim run encrypted-dns Simulate encrypted DNS traffic over random protocol (DoH, DoT, DNSCrypt)
flightsim run encrypted-dns:doh Simulate encrypted DNS-over-HTTPS traffic
flightsim run encrypted-dns Simulate encrypted DNS traffic via DNS-over-HTTPS, DNS-over-TLS and DNSCrypt
flightsim run encrypted-dns:doh Simulate encrypted DNS traffic via a specific protocol (DNS-over-HTTPS in this case)

flightsim get families:c2 Get a list of all c2 families
`
Expand Down
100 changes: 100 additions & 0 deletions simulator/encdns/dns/dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Package dns provides some basic DNS utilities.
package dns

import (
"fmt"
"math/rand"

"golang.org/x/net/dns/dnsmessage"
)

// NewTCPRequest creates a DNS wire request to be used over TCP. It returns the request
// as a byte slice along with an error.
func NewTCPRequest(domain string, t dnsmessage.Type) ([]byte, error) {
req, err := newRequest(domain, t)
if err != nil {
return nil, fmt.Errorf("failed creating DNS TCP request: %v", err)
}
lenReq := len(req) - 2
req[0] = byte(lenReq >> 8)
req[1] = byte(lenReq)
return req, nil
}

// NewUDPRequest creates a DNS wire request to be used over UDP. It returns the request
// as a byte slice along with an error.
func NewUDPRequest(domain string, t dnsmessage.Type) ([]byte, error) {
req, err := newRequest(domain, t)
if err != nil {
return nil, fmt.Errorf("failed creating DNS UDP request: %v", err)
}
return req[2:], nil
}

// newRequest creates a DNS wire request using the specified network protocol and returns
// the request as a byte slice along with an error
func newRequest(domain string, t dnsmessage.Type) ([]byte, error) {
name, err := dnsmessage.NewName(domain)
if err != nil {
return nil, err
}
q := dnsmessage.Question{
Name: name,
Type: t,
Class: dnsmessage.ClassINET,
}
// TODO: dnsclient_unix.go::tryOneName() does nice error handling
id := uint16(rand.Intn(256))
b := dnsmessage.NewBuilder(
make([]byte, 2, 514),
dnsmessage.Header{ID: id, RecursionDesired: true})
b.EnableCompression()
if err := b.StartQuestions(); err != nil {
return nil, err
}
if err := b.Question(q); err != nil {
return nil, err
}
req, err := b.Finish()
if err != nil {
return nil, err
}
return req, err
}

// ParseTXTResponse extracts TXT responses from a DNS message, and returns a slice of
// strings (the responses) and an error.
func ParseTXTResponse(msg []byte) ([]string, error) {
p := dnsmessage.Parser{}
_, err := p.Start(msg)
if err != nil {
return nil, err
}
err = p.SkipAllQuestions()
if err != nil {
return nil, err
}
var records []string
// Loop over the answers, extracting TXT records, until ErrSectionDone.
for {
hdr, err := p.AnswerHeader()
if err != nil {
// Done.
if err == dnsmessage.ErrSectionDone {
break
}
// Unexpected error.
return records, err
}
// Something other than a TXT record?
if hdr.Type != dnsmessage.TypeTXT {
return records, err
}
// Extract and append to the list of records.
txt, err := p.TXTResource()
if err == nil {
records = append(records, txt.TXT...)
}
}
return records, nil
}
141 changes: 100 additions & 41 deletions simulator/encdns/dnscrypt/dnscrypt.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,110 @@
// Package dnscrypt provides DNSCrypt functionality. It's pieced together from code found
// in the reference DNSCrypt implementation (https://github.com/DNSCrypt/dnscrypt-proxy),
// and in https://github.com/ameshkov/dnscrypt.git. The goal was the provide FlightSim with
// just enough DNSCrypt, without pulling in too many non-golang.org third party libs.
// Package dnscrypt provides a DNSCrypt Resolver for TXT lookups.
package dnscrypt

import (
"bytes"
"context"
"io"
"net"
"time"

"github.com/alphasoc/flightsim/simulator/encdns/dns"
"github.com/alphasoc/flightsim/simulator/encdns/dnscrypt/libdnscrypt"
"golang.org/x/net/dns/dnsmessage"
)

// IsValidResponse returns a boolean indicating if the *dnsmessage.Message carries a
// valid response.
func IsValidResponse(r *dnsmessage.Message) bool {
return r.RCode == dnsmessage.RCodeSuccess && len(r.Answers) > 0
type Resolver struct {
ctx context.Context
sdns string
bindIP net.IP
c *libdnscrypt.Client
d *net.Dialer
ri *libdnscrypt.ResolverInfo
}

// Error constants.
const (
// ErrEsVersion means that the cert contains unsupported es-version
ErrEsVersion = "unsupported es-version"

// ErrInvalidDNSStamp means an invalid DNS stamp
ErrInvalidDNSStamp = "invalid DNS stamp"

// ErrCertTooShort means that it failed to deserialize cert, too short
ErrCertTooShort = "cert is too short"

// ErrCertMagic means an invalid cert magic
ErrCertMagic = "invalid cert magic"

// ErrInvalidQuery means that it failed to decrypt a DNSCrypt query
ErrInvalidQuery = "DNSCrypt query is invalid and cannot be decrypted"

// ErrInvalidResponse means that it failed to decrypt a DNSCrypt response
ErrInvalidResponse = "DNSCrypt response is invalid and cannot be decrypted"

// ErrInvalidClientMagic means that client-magic does not match
ErrInvalidClientMagic = "DNSCrypt query contains invalid client magic"

// ErrInvalidPadding means that it failed to unpad a query
ErrInvalidPadding = "invalid padding"

// ErrQueryTooLarge means that the DNS query is larger than max allowed size
ErrQueryTooLarge = "DNSCrypt query is too large"
// NewResolver returns a pointer to a DNScrypt Resolver.
func NewResolver(ctx context.Context, network, sdns string, bindIP net.IP) *Resolver {
return &Resolver{
ctx: ctx,
sdns: sdns,
bindIP: bindIP,
c: &libdnscrypt.Client{Net: network}}
}

// ErrInvalidResolverMagic means that server-magic does not match
ErrInvalidResolverMagic = "DNSCrypt response contains invalid resolver magic"
// prepConnection grabs DNSCrypt resolver info and prepares the underlying connection.
func (r *Resolver) prepConnection() error {
// Connection already prepped.
if r.ri != nil {
return nil
}
ri, err := r.c.Dial(r.ctx, r.sdns)
if err != nil {
return err
}
r.ri = ri
d := net.Dialer{}
if r.bindIP != nil {
if r.c.Net == "udp" {
d.LocalAddr = &net.UDPAddr{IP: r.bindIP}
} else {
d.LocalAddr = &net.TCPAddr{IP: r.bindIP}
}
}
r.d = &d
return nil
}

// ErrInvalidDNSResponse is an invalid DNS reponse error.
ErrInvalidDNSResponse = "invalid DNS response"
)
// LookupTXT performs a DNSCrypt TXT lookup of host, returning TXT records as a slice of
// strings and an error.
func (r *Resolver) LookupTXT(ctx context.Context, host string) ([]string, error) {
// On an initial lookup, get resolver information and server certificate. Also,
// prepare the dialer.
var err error
err = r.prepConnection()
if err != nil {
return nil, err
}
// Dial the actual server address obtained in ResliverInfo.
conn, err := r.d.DialContext(r.ctx, r.c.Net, r.ri.ServerAddress)
if err != nil {
return nil, err
}
defer conn.Close()
var dnsReq []byte
if r.c.Net == "udp" {
dnsReq, err = dns.NewUDPRequest(host, dnsmessage.TypeTXT)
} else {
dnsReq, err = dns.NewTCPRequest(host, dnsmessage.TypeTXT)
}
if err != nil {
return nil, err
}
// Encrypt the DNS wire protocol packet, and send.
encryptedDnsReq, err := r.c.Encrypt(dnsReq, r.ri)
if err != nil {
return nil, err
}
_, err = conn.Write(encryptedDnsReq)
if err != nil {
return nil, err
}
// Set read deadline based on ctx.
if deadline, ok := ctx.Deadline(); ok {
conn.SetReadDeadline(deadline)
} else {
conn.SetReadDeadline(time.Time{})
}
// Read the response, decrypting, and extracting the TXT records.
var buf bytes.Buffer
n, err := io.Copy(&buf, conn)
// IO timeouts may be encountered. If we managed to read anything, try to decrypt.
if err != nil && n == 0 {
return nil, err
}
resp := make([]byte, buf.Len())
resp, err = r.c.Decrypt(buf.Bytes(), r.ri)
if err != nil {
return nil, err
}
return dns.ParseTXTResponse(resp)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dnscrypt
package libdnscrypt

import (
"bytes"
Expand Down