Skip to content

Commit 86c195f

Browse files
added basic logic for packet capture
1 parent 298fb34 commit 86c195f

12 files changed

Lines changed: 178 additions & 13 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ models/**/*
3333
*.vocab
3434
*.yaml
3535
rules.sh
36+
*.txt
37+
*.pcap*
3638

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ test:
1414
.PHONY: clean
1515
clean:
1616
find ./bin ! -name '.gitignore' -type f -exec rm -vrf {} +
17+
rm -vf *.pcap*
18+
rm -vf *.txt
1719

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- [ARP spoofing](#arp-spoofing)
3232
- [NDP spoofing](#ndp-spoofing)
3333
- [DNS spoofing](#dns-spoofing)
34+
- [Packet Capture](#packet-capture)
3435
- [Links](#links)
3536
- [Contributing](#contributing)
3637
- [License](#license)
@@ -84,6 +85,9 @@ Specify http server in proxy configuration of Postman
8485
- **DNS spoofing**\
8586
Redirect clients to arbitrary domains using DNS records manipulation
8687

88+
- **Packet Capture**\
89+
Capture traffic into txt/pcap/pcapng files and analyze with Wireshark
90+
8791
- **DNS Leak Protection**\
8892
DNS resolution occurs on SOCKS5 server side.
8993

@@ -121,7 +125,7 @@ yay -S gohpts
121125
- Download the binary for your platform from [Releases](https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases) page:
122126

123127
```shell
124-
GOHPTS_RELEASE=v1.13.3; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$GOHPTS_RELEASE/gohpts-$GOHPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$GOHPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h
128+
GOHPTS_RELEASE=v1.13.4; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$GOHPTS_RELEASE/gohpts-$GOHPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$GOHPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h
125129
```
126130

127131
- Install using `go install` command (requires Go [1.26](https://go.dev/doc/install) or later):
@@ -997,6 +1001,35 @@ sudo ./gohpts -f ./gohpts_dns_spoof.yaml
9971001
9981002
More information can be found here: [https://en.wikipedia.org/wiki/DNS_spoofing](https://en.wikipedia.org/wiki/DNS_spoofing)
9991003
1004+
### Packet Capture
1005+
1006+
[[Back]](#table-of-contents)
1007+
1008+
Traffic can be captured into pcap, pcapng or custom txt formats and later analyzed with tools like Wireshark, tcpdump and many others.
1009+
1010+
First, make sure `GoHPTS` executable has elevated privileges to be able to capture raw packets, you have two options:
1011+
1012+
- Run `sudo setcap cap_net_raw+ep ~/go/bin/gohpts` one time to give proxy raw traffic access
1013+
- Run proxy with `sudo` when you need to specify `-pcap` flag in CLI or `pcap.enabled` in file configuration.
1014+
1015+
Configure proxy using CLI:
1016+
1017+
```shell
1018+
gohpts -pcap "promisc true;timeout 10s;exts txt,pcap,pcapng"
1019+
```
1020+
1021+
Configuration file:
1022+
1023+
```yaml
1024+
pcap:
1025+
enabled: true
1026+
settings: "promisc true;expr ip proto tcp;snaplen 65535;timeout 10s;packet_count 100;packet_buffer 8192;exts txt,pcap,pcapng"
1027+
```
1028+
1029+
These commands produce three packet capture files with corresponding formats that later can be analyzed by various tools.
1030+
1031+
For more information about pcap options see `gohpts -h` and [https://github.com/shadowy-pycoder/mshark](https://github.com/shadowy-pycoder/mshark)
1032+
10001033
## Links
10011034
10021035
[[Back]](#table-of-contents)

cmd/gohpts/cli.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import (
1212
"golang.org/x/term"
1313
)
1414

15-
const app string = "gohpts"
16-
1715
const usagePrefix string = ` _____ _ _ _____ _______ _____
1816
/ ____| | | | | __ \__ __/ ____|
1917
| | __ ___ | |__| | |__) | | | | (___
@@ -73,11 +71,14 @@ const usageTproxy string = `
7371
Spoofing:
7472
-arpspoof Enable ARP spoof proxy for selected targets (Example: "targets 10.0.0.1,10.0.0.5-10,192.168.1.*,192.168.10.0/24;fullduplex false;debug true;interval 10s")
7573
-ndpspoof Enable NDP spoof proxy for selected targets (Example: "ra true;na true;targets fe80::3a1c:7bff:fe22:91a4;fullduplex false;debug true;interval 10s")
74+
75+
Packet Capture:
76+
-pcap Enable packet capture (Example: "promisc true;expr ip proto tcp;snaplen 65535;timeout 10s;packet_count 100;packet_buffer 8192;exts txt,pcap,pcapng")
7677
`
7778

7879
func root(args []string) error {
7980
conf := gohpts.Config{}
80-
flags := flag.NewFlagSet(app, flag.ExitOnError)
81+
flags := flag.NewFlagSet(gohpts.App, flag.ExitOnError)
8182
addrSocks := flags.String("s", "", "")
8283
userSocks := flags.String("u", "", "")
8384
passSocks := ""
@@ -90,7 +91,7 @@ func root(args []string) error {
9091
flags.StringVar(&conf.Interface, "i", "", "")
9192
flags.BoolFunc("I", "", func(flagValue string) error {
9293
if err := network.DisplayInterfaces(false); err != nil {
93-
fmt.Fprintf(os.Stderr, "%s: %v\n", app, err)
94+
fmt.Fprintf(os.Stderr, "%s: %v\n", gohpts.App, err)
9495
os.Exit(2)
9596
}
9697
os.Exit(0)
@@ -102,7 +103,7 @@ func root(args []string) error {
102103
flags.StringVar(&conf.TProxyUDP, "Tu", "", "")
103104
flags.Func("M", "", func(flagValue string) error {
104105
if !slices.Contains(gohpts.SupportedTProxyModes, flagValue) {
105-
fmt.Fprintf(os.Stderr, "%s: %s is not supported (type '%s -h' for help)\n", app, flagValue, app)
106+
fmt.Fprintf(os.Stderr, "%s: %s is not supported (type '%s -h' for help)\n", gohpts.App, flagValue, gohpts.App)
106107
os.Exit(2)
107108
}
108109
conf.TProxyMode = flagValue
@@ -115,6 +116,7 @@ func root(args []string) error {
115116
flags.UintVar(&conf.Mark, "mark", 0, "")
116117
flags.StringVar(&conf.ARPSpoof, "arpspoof", "", "")
117118
flags.StringVar(&conf.NDPSpoof, "ndpspoof", "", "")
119+
flags.StringVar(&conf.Pcap, "pcap", "", "")
118120
flags.StringVar(&conf.IgnoredPorts, "P", "", "")
119121
flags.BoolVar(&conf.Dump, "dump", false, "")
120122
}
@@ -262,7 +264,7 @@ func root(args []string) error {
262264
if err != nil {
263265
return err
264266
}
265-
fmt.Printf("%s pid: %d\n", app, process.Pid)
267+
fmt.Printf("%s pid: %d\n", gohpts.App, process.Pid)
266268
process.Release()
267269
os.Exit(0)
268270
}

cmd/gohpts/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package main
33
import (
44
"fmt"
55
"os"
6+
7+
gohpts "github.com/shadowy-pycoder/go-http-proxy-to-socks"
68
)
79

810
func main() {
911
if err := root(os.Args[1:]); err != nil {
10-
fmt.Fprintf(os.Stderr, "%s: %v (type '%s -h' for help)\n", app, err, app)
12+
fmt.Fprintf(os.Stderr, "%s: %v (type '%s -h' for help)\n", gohpts.App, err, gohpts.App)
1113
os.Exit(2)
1214
}
1315
}

config.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ type Config struct {
5757

5858
// DNSFilter filters
5959
DNSFilter DNSFilterLists
60+
61+
// packet capture
62+
Pcap string
6063
}
6164

6265
type ProxyEntry struct {
@@ -134,6 +137,10 @@ type yamlConfig struct {
134137
Settings string `yaml:"settings"`
135138
} `yaml:"ndpspoof"`
136139
DNSFilter DNSFilterLists `yaml:"dns_filter"`
140+
Pcap struct {
141+
Enabled bool `yaml:"enabled"`
142+
Settings string `yaml:"settings"`
143+
} `yaml:"pcap"`
137144
}
138145

139146
func createConfigFromPath(path string) (*Config, error) {
@@ -201,6 +208,9 @@ func createConfigFromPath(path string) (*Config, error) {
201208
if sconf.DNSFilter.Enabled {
202209
conf.DNSFilter = sconf.DNSFilter
203210
}
211+
if sconf.Pcap.Enabled {
212+
conf.Pcap = sconf.Pcap.Settings
213+
}
204214
return &conf, nil
205215
}
206216

@@ -335,6 +345,10 @@ func parseConfig(conf *Config) error {
335345
conf.NDPSpoof = yamlConf.NDPSpoof
336346
}
337347

348+
if conf.Pcap == "" {
349+
conf.Pcap = yamlConf.Pcap
350+
}
351+
338352
if conf.IgnoredPorts == "" {
339353
conf.IgnoredPorts = yamlConf.IgnoredPorts
340354
}
@@ -382,6 +396,9 @@ func parseConfig(conf *Config) error {
382396
if conf.NDPSpoof != "" {
383397
return fmt.Errorf("option `NDPSpoof` is available only on linux/android systems")
384398
}
399+
if conf.Pcap != "" {
400+
return fmt.Errorf("option `Pcap` is available only on linux/android systems")
401+
}
385402
if conf.IgnoredPorts != "" {
386403
return fmt.Errorf("option `IgnoredPorts` is available only on linux/android systems")
387404
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ module github.com/shadowy-pycoder/go-http-proxy-to-socks
22

33
go 1.26.3
44

5+
56
require (
67
github.com/goccy/go-yaml v1.18.0
78
github.com/google/uuid v1.6.0
89
github.com/rs/zerolog v1.34.0
910
github.com/shadowy-pycoder/arpspoof v0.0.4
1011
github.com/shadowy-pycoder/colors v0.0.2
11-
github.com/shadowy-pycoder/mshark v0.0.25
12+
github.com/shadowy-pycoder/mshark v0.0.26
1213
github.com/shadowy-pycoder/ndpspoof v0.0.8
1314
github.com/stretchr/testify v1.9.0
1415
github.com/wzshiming/socks5 v0.5.2

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ github.com/shadowy-pycoder/arpspoof v0.0.4 h1:Hs2Rr23eNnW3vIn/U2b0n7bNIp2b027+PK
3636
github.com/shadowy-pycoder/arpspoof v0.0.4/go.mod h1:KRIpFgIXyxXC3/2vwOW2uHEFX4ghEGgepPLd6H2fgYA=
3737
github.com/shadowy-pycoder/colors v0.0.2 h1:l4zvGOTPS92oxPIo6g+6t8tbzhh/v+6yT5A/3TQp8rc=
3838
github.com/shadowy-pycoder/colors v0.0.2/go.mod h1:lkrJS1PY2oVigNLTT6pkbF7B/v0YcU2LD5PZnss1Q4U=
39-
github.com/shadowy-pycoder/mshark v0.0.25 h1:46m4RroyKzZIYwDguL/Qt64DYbJkTeZVhmySG6brCxw=
40-
github.com/shadowy-pycoder/mshark v0.0.25/go.mod h1:ouUxERdr+Ih1kNlbJBda0nbrt3o/JkO5amR7HjD7tBk=
4139
github.com/shadowy-pycoder/ndpspoof v0.0.8 h1:fdOE06d071lZfHTN9BxgT+t960gSriv6MrOB69y+7aU=
4240
github.com/shadowy-pycoder/ndpspoof v0.0.8/go.mod h1:Bv6s1r+YZNLSnKNbGEW9ciR4ycsaUSRjF8jTXHiDW38=
4341
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=

gohpts.go

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,17 @@ import (
3232

3333
"github.com/rs/zerolog"
3434
"github.com/shadowy-pycoder/arpspoof"
35+
"github.com/shadowy-pycoder/mshark"
3536
"github.com/shadowy-pycoder/mshark/layers"
37+
"github.com/shadowy-pycoder/mshark/mpcap"
38+
"github.com/shadowy-pycoder/mshark/mpcapng"
3639
"github.com/shadowy-pycoder/mshark/network"
3740
"github.com/shadowy-pycoder/ndpspoof"
3841
"github.com/wzshiming/socks5"
3942
)
4043

4144
const (
45+
App string = "gohpts"
4246
addrSOCKS string = "127.0.0.1:1080"
4347
addrHTTP string = "127.0.0.1:8080"
4448
readTimeout time.Duration = 30 * time.Second
@@ -120,6 +124,8 @@ type proxyapp struct {
120124
dump strings.Builder
121125
closeConn chan bool
122126
filter *dnsFilter
127+
pcapConf *mshark.Config
128+
pcapW []mshark.PacketWriter
123129

124130
mu sync.RWMutex
125131
availProxyList []ProxyEntry
@@ -531,6 +537,71 @@ func New(conf *Config) (*proxyapp, error) {
531537
return nil, fmt.Errorf("failed creating ndp spoofer: %v", err)
532538
}
533539
}
540+
// configure packet capture
541+
if conf.Pcap != "" {
542+
if p.iface == nil {
543+
return nil, fmt.Errorf("failed getting network interface")
544+
}
545+
pcc, err := mshark.NewConfig(conf.Pcap)
546+
if err != nil {
547+
return nil, fmt.Errorf("failed configuring packet capture: %v", err)
548+
}
549+
pcc.Device = p.iface
550+
pcc.Promisc = true
551+
for _, ext := range pcc.Exts {
552+
switch ext {
553+
case "txt":
554+
f, err := createPcapFile(App, ext)
555+
if err != nil {
556+
return nil, err
557+
}
558+
w := mshark.NewWriter(f, true)
559+
if err := w.WriteHeader(pcc); err != nil {
560+
w.Close()
561+
return nil, err
562+
}
563+
p.pcapW = append(p.pcapW, w)
564+
case "pcap":
565+
f, err := createPcapFile(App, ext)
566+
if err != nil {
567+
return nil, err
568+
}
569+
w := mpcap.NewWriter(f)
570+
if err := w.WriteHeader(pcc.Snaplen); err != nil {
571+
w.Close()
572+
return nil, err
573+
}
574+
p.pcapW = append(p.pcapW, w)
575+
case "pcapng":
576+
f, err := createPcapFile(App, ext)
577+
if err != nil {
578+
return nil, err
579+
}
580+
w := mpcapng.NewWriter(f)
581+
if err := w.WriteHeader(App, pcc.Device, pcc.Expr, pcc.Snaplen); err != nil {
582+
w.Close()
583+
return nil, err
584+
}
585+
p.pcapW = append(p.pcapW, w)
586+
default:
587+
return nil, fmt.Errorf("unsupported file format: %s", ext)
588+
}
589+
}
590+
if len(p.pcapW) == 0 {
591+
f, err := createPcapFile(App, "pcapng")
592+
if err != nil {
593+
return nil, err
594+
}
595+
defer f.Close()
596+
w := mpcapng.NewWriter(f)
597+
if err := w.WriteHeader(App, pcc.Device, pcc.Expr, pcc.Snaplen); err != nil {
598+
return nil, err
599+
}
600+
p.pcapW = append(p.pcapW, w)
601+
602+
}
603+
p.pcapConf = pcc
604+
}
534605
// configuring DNS
535606
if p.arpspoofer != nil {
536607
gw := p.arpspoofer.GatewayIP()
@@ -618,6 +689,15 @@ func (p *proxyapp) Run() {
618689
if p.ndpspoofer != nil {
619690
go p.ndpspoofer.Start()
620691
}
692+
693+
var pcapWg sync.WaitGroup
694+
if p.pcapConf != nil {
695+
pcapWg.Go(func() {
696+
if err := mshark.OpenLive(p.pcapConf, p.pcapW...); err != nil {
697+
p.logger.Error().Err(err).Msg("Failed capturing packets")
698+
}
699+
})
700+
}
621701
tproxyEnabled := p.tproxyAddr != ""
622702
tproxyServers := make([]*tproxyServer, p.tproxyWorkers)
623703
opts := make(map[string]string, 20)
@@ -668,6 +748,10 @@ func (p *proxyapp) Run() {
668748
p.logger.Error().Err(err).Msg("Failed stopping ndp spoofer")
669749
}
670750
}
751+
if p.pcapConf != nil {
752+
p.logger.Info().Msg("Shutting down packet capture..")
753+
pcapWg.Wait()
754+
}
671755
close(p.closeConn)
672756
var wg sync.WaitGroup
673757
if tproxyEnabled {
@@ -762,6 +846,10 @@ func (p *proxyapp) Run() {
762846
p.logger.Error().Err(err).Msg("Failed stopping ndp spoofer")
763847
}
764848
}
849+
if p.pcapConf != nil {
850+
p.logger.Info().Msg("Shutting down packet capture..")
851+
pcapWg.Wait()
852+
}
765853
close(p.closeConn)
766854
var wg sync.WaitGroup
767855
if tproxyEnabled {
@@ -855,7 +943,11 @@ func (p *proxyapp) getResolver() *net.UDPAddr {
855943
if r.IsLinkLocalUnicast() {
856944
zone = p.iface.Name
857945
}
858-
return &net.UDPAddr{IP: net.ParseIP(r.String()), Port: 53, Zone: zone}
946+
ip := net.ParseIP(r.String())
947+
if ip == nil {
948+
continue
949+
}
950+
return &net.UDPAddr{IP: ip, Port: 53, Zone: zone}
859951
}
860952
}
861953
}

0 commit comments

Comments
 (0)