Skip to content

Commit 5f6cb3d

Browse files
Merge pull request #14 from shadowy-pycoder/udp
Udp support for transparent proxy with DNS queries sniffing
2 parents d6cd160 + 4616f8f commit 5f6cb3d

File tree

12 files changed

+1253
-207
lines changed

12 files changed

+1253
-207
lines changed

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- [Transparent proxy](#transparent-proxy)
2121
- [redirect (via NAT and SO_ORIGINAL_DST)](#redirect-via-nat-and-so_original_dst)
2222
- [tproxy (via MANGLE and IP_TRANSPARENT)](#tproxy-via-mangle-and-ip_transparent)
23+
- [UDP support](#udp-support)
2324
- [ARP spoofing](#arp-spoofing)
2425
- [Traffic sniffing](#traffic-sniffing)
2526
- [JSON format](#json-format)
@@ -62,8 +63,11 @@ Specify http server in proxy configuration of Postman
6263
- **Transparent proxy**\
6364
Supports `redirect` (SO_ORIGINAL_DST) and `tproxy` (IP_TRANSPARENT) modes
6465

66+
- **TCP and UDP Transparent proxy**\
67+
`tproxy` (IP_TRANSPARENT) handles TCP and UDP traffic
68+
6569
- **Traffic sniffing**\
66-
Proxy is able to parse HTTP headers and TLS handshake metadata
70+
Proxy is able to parse HTTP headers, TLS handshake, DNS messages and more
6771

6872
- **ARP spoofing**\
6973
Proxy entire subnets with ARP spoofing approach
@@ -101,7 +105,7 @@ You can download the binary for your platform from [Releases](https://github.com
101105
Example:
102106

103107
```shell
104-
GOHPTS_RELEASE=v1.9.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
108+
GOHPTS_RELEASE=v2.0.0; 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
105109
```
106110

107111
Alternatively, you can install it using `go install` command (requires Go [1.24](https://go.dev/doc/install) or later):
@@ -168,6 +172,7 @@ Options:
168172
TProxy:
169173
-t Address of transparent proxy server (it starts along with HTTP proxy server)
170174
-T Address of transparent proxy server (no HTTP)
175+
-Tu Address of transparent UDP proxy server
171176
-M Transparent proxy mode: (redirect, tproxy)
172177
-auto Automatically setup iptables for transparent proxy (requires elevated privileges)
173178
-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")
@@ -521,6 +526,30 @@ sudo bettercap -eval "net.probe on;net.recon on;set arp.spoof.fullduplex true;ar
521526
522527
Check proxy logs for traffic from other devices from your LAN
523528
529+
### UDP support
530+
531+
`GoHPTS` has UDP support that can be enabled in `tproxy` mode. For this setup to work you need to connect to a socks5 server capable of serving UDP connections (`UDP ASSOCIATE`). For example, you can use [https://github.com/wzshiming/socks5](https://github.com/wzshiming/socks5) to deploy UDP capable socks5 server on some remote or local machine. Once you have the server to connect to, run the following command:
532+
533+
```shell
534+
sudo env PATH=$PATH gohpts -s remote -Tu :8989 -M tproxy -auto -mark 100 -d
535+
```
536+
537+
This command will configure your operating system and setup server on `0.0.0.0:8989` address.
538+
539+
To test it locally, you can combine UDP transparent proxy with `-arpspoof` flag. For example:
540+
541+
1. Setup VM on your system with any Linux distributive that supports `tproxy` (Kali Linux, for instance).
542+
2. Enable `bridged` network so that VM could access your host machine.
543+
3. Move `gohpts` binary to VM (via `ssh`, for instance) or build it there in case of different OS/arch.
544+
4. On your VM run the following command:
545+
546+
```shell
547+
# Do not forget to replace <socks5 server> and <your host> with actual addresses
548+
sudo ./gohpts -s <socks5 server> -T 8888 -Tu :8989 -M tproxy -sniff -body -auto -mark 100 -d -arpspoof "targets <your host>;fullduplex true;debug false"
549+
```
550+
551+
4. Check connection on your host machine, the traffic should go through Kali machine.
552+
524553
## Traffic sniffing
525554
526555
[[Back]](#table-of-contents)

cmd/gohpts/cli.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const usageTproxy string = `
6262
TProxy:
6363
-t Address of transparent proxy server (it starts along with HTTP proxy server)
6464
-T Address of transparent proxy server (no HTTP)
65+
-Tu Address of transparent UDP proxy server
6566
-M Transparent proxy mode: (redirect, tproxy)
6667
-auto Automatically setup iptables for transparent proxy (requires elevated privileges)
6768
-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")
@@ -106,6 +107,7 @@ func root(args []string) error {
106107
if runtime.GOOS == tproxyOS {
107108
flags.StringVar(&conf.TProxy, "t", "", "Address of transparent proxy server (it starts along with HTTP proxy server)")
108109
flags.StringVar(&conf.TProxyOnly, "T", "", "Address of transparent proxy server (no HTTP)")
110+
flags.StringVar(&conf.TProxyUDP, "Tu", "", "Address of transparent UDP proxy server")
109111
flags.Func("M", fmt.Sprintf("Transparent proxy mode: %s", gohpts.SupportedTProxyModes), func(flagValue string) error {
110112
if !slices.Contains(gohpts.SupportedTProxyModes, flagValue) {
111113
fmt.Fprintf(os.Stderr, "%s: %s is not supported (type '%s -h' for help)\n", app, flagValue, app)
@@ -176,19 +178,27 @@ func root(args []string) error {
176178
return fmt.Errorf("transparent proxy mode is not provided: -M flag")
177179
}
178180
}
181+
if seen["Tu"] {
182+
if !seen["M"] {
183+
return fmt.Errorf("transparent proxy mode is not provided: -M flag")
184+
}
185+
if conf.TProxyMode != "tproxy" {
186+
return fmt.Errorf("transparent UDP proxy require tproxy mode")
187+
}
188+
}
179189
if seen["M"] {
180-
if !seen["t"] && !seen["T"] {
181-
return fmt.Errorf("transparent proxy mode requires -t or -T flag")
190+
if !seen["t"] && !seen["T"] && !seen["Tu"] {
191+
return fmt.Errorf("transparent proxy mode requires -t, -T or -Tu flag")
182192
}
183193
}
184194
if seen["auto"] {
185-
if !seen["t"] && !seen["T"] {
186-
return fmt.Errorf("-auto requires -t or -T flag")
195+
if !seen["t"] && !seen["T"] && !seen["Tu"] {
196+
return fmt.Errorf("-auto requires -t, -T or -Tu flag")
187197
}
188198
}
189199
if seen["mark"] {
190-
if !seen["t"] && !seen["T"] {
191-
return fmt.Errorf("-mark requires -t or -T flag")
200+
if !seen["t"] && !seen["T"] && !seen["Tu"] {
201+
return fmt.Errorf("-mark requires -t, -T or -Tu flag")
192202
}
193203
}
194204
if seen["f"] {

colorize.go

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import (
1818

1919
var (
2020
ipPortPattern = regexp.MustCompile(
21-
`\b(?:\d{1,3}\.){3}\d{1,3}(?::(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]?\d{1,4}))?\b`,
21+
`(?:\[(?:[0-9a-fA-F:.]+)\]|(?:\d{1,3}\.){3}\d{1,3})(?::(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]?\d{1,4}))?`,
2222
)
2323
domainPattern = regexp.MustCompile(
24-
`\b(?:[a-zA-Z0-9-]{1,63}\.)+(?:com|net|org|io|co|uk|ru|de|edu|gov|info|biz|dev|app|ai)(?::(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]?\d{1,4}))?\b`,
24+
`\b(?:[a-zA-Z0-9-]{1,63}\.)+(?:com|net|org|io|co|uk|ru|de|edu|gov|info|biz|dev|app|ai|tv)(?::(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]?\d{1,4}))?\b`,
2525
)
2626
jwtPattern = regexp.MustCompile(`\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b`)
2727
authPattern = regexp.MustCompile(
@@ -187,9 +187,9 @@ func colorizeHTTP(
187187

188188
func colorizeTLS(req *layers.TLSClientHello, resp *layers.TLSServerHello, id string, nocolor bool) string {
189189
var sb strings.Builder
190+
sb.WriteString(fmt.Sprintf("%s ", colorizeTimestamp(time.Now(), nocolor)))
191+
sb.WriteString(id)
190192
if nocolor {
191-
sb.WriteString(fmt.Sprintf("%s ", colorizeTimestamp(time.Now(), nocolor)))
192-
sb.WriteString(id)
193193
sb.WriteString(fmt.Sprintf(" %s ", req.TypeDesc))
194194
if req.Length > 0 {
195195
sb.WriteString(fmt.Sprintf(" Len: %d", req.Length))
@@ -224,8 +224,6 @@ func colorizeTLS(req *layers.TLSClientHello, resp *layers.TLSServerHello, id str
224224
sb.WriteString(fmt.Sprintf(" ExtLen: %d", resp.ExtensionLength))
225225
}
226226
} else {
227-
sb.WriteString(fmt.Sprintf("%s ", colorizeTimestamp(time.Now(), nocolor)))
228-
sb.WriteString(id)
229227
sb.WriteString(colors.Magenta(fmt.Sprintf(" %s ", req.TypeDesc)).Bold())
230228
if req.Length > 0 {
231229
sb.WriteString(colors.BeigeBg(fmt.Sprintf(" Len: %d", req.Length)).String())
@@ -263,6 +261,98 @@ func colorizeTLS(req *layers.TLSClientHello, resp *layers.TLSServerHello, id str
263261
return sb.String()
264262
}
265263

264+
func colorizeRData(rec *layers.ResourceRecord) string {
265+
var rdata string
266+
switch rd := rec.RData.(type) {
267+
case *layers.RDataA:
268+
case *layers.RDataAAAA:
269+
rdata = fmt.Sprintf("%s %s ", colors.LightBlue(rec.Type.Name), colors.Gray(rd.Address.String()))
270+
case *layers.RDataNS:
271+
rdata = fmt.Sprintf("%s %s ", colors.LightBlue(rec.Type.Name), colors.Gray(rd.NsdName))
272+
case *layers.RDataCNAME:
273+
rdata = fmt.Sprintf("%s %s ", colors.LightBlue(rec.Type.Name), colors.Gray(rd.CName))
274+
case *layers.RDataSOA:
275+
rdata = fmt.Sprintf("%s %s ", colors.LightBlue(rec.Type.Name), colors.Gray(rd.PrimaryNS))
276+
case *layers.RDataMX:
277+
rdata = fmt.Sprintf("%s %s %s ", colors.LightBlue(rec.Type.Name), colors.Gray(fmt.Sprintf("%d", rd.Preference)), colors.Gray(rd.Exchange))
278+
case *layers.RDataTXT:
279+
rdata = fmt.Sprintf("%s %s ", colors.LightBlue(rec.Type.Name), colors.Gray(rd.TxtData))
280+
default:
281+
rdata = fmt.Sprintf("%s ", colors.LightBlue(rec.Type.Name))
282+
}
283+
return rdata
284+
}
285+
286+
func colorizeDNS(req, resp *layers.DNSMessage, id string, nocolor bool) string {
287+
var sb strings.Builder
288+
sb.WriteString(fmt.Sprintf("%s ", colorizeTimestamp(time.Now(), nocolor)))
289+
sb.WriteString(id)
290+
if nocolor {
291+
sb.WriteString(fmt.Sprintf(" DNS %s (%s) %#04x ", req.Flags.OPCodeDesc, req.Flags.QRDesc, req.TransactionID))
292+
for _, rec := range req.Questions {
293+
sb.WriteString(fmt.Sprintf("%s %s ", rec.Type.Name, rec.Name))
294+
}
295+
for _, rec := range req.AnswerRRs {
296+
sb.WriteString(rec.Summary())
297+
}
298+
for _, rec := range req.AuthorityRRs {
299+
sb.WriteString(rec.Summary())
300+
}
301+
for _, rec := range req.AdditionalRRs {
302+
sb.WriteString(rec.Summary())
303+
}
304+
sb.WriteString("\n")
305+
sb.WriteString(fmt.Sprintf("%s ", colorizeTimestamp(time.Now(), nocolor)))
306+
sb.WriteString(id)
307+
sb.WriteString(fmt.Sprintf(" DNS %s (%s) %#04x ", resp.Flags.OPCodeDesc, resp.Flags.QRDesc, resp.TransactionID))
308+
for _, rec := range resp.Questions {
309+
sb.WriteString(fmt.Sprintf("%s %s ", rec.Type.Name, rec.Name))
310+
}
311+
for _, rec := range resp.AnswerRRs {
312+
sb.WriteString(rec.Summary())
313+
}
314+
for _, rec := range resp.AuthorityRRs {
315+
sb.WriteString(rec.Summary())
316+
}
317+
for _, rec := range resp.AdditionalRRs {
318+
sb.WriteString(rec.Summary())
319+
}
320+
} else {
321+
sb.WriteString(colors.Gray(fmt.Sprintf(" DNS %s (%s)", req.Flags.OPCodeDesc, req.Flags.QRDesc)).Bold())
322+
sb.WriteString(colors.Beige(fmt.Sprintf(" %#04x ", req.TransactionID)).String())
323+
for _, rec := range req.Questions {
324+
sb.WriteString(fmt.Sprintf("%s %s ", colors.LightBlue(rec.Type.Name), colors.Gray(rec.Name)))
325+
}
326+
for _, rec := range req.AnswerRRs {
327+
sb.WriteString(colorizeRData(rec))
328+
}
329+
for _, rec := range req.AuthorityRRs {
330+
sb.WriteString(colorizeRData(rec))
331+
}
332+
for _, rec := range req.AdditionalRRs {
333+
sb.WriteString(colorizeRData(rec))
334+
}
335+
sb.WriteString("\n")
336+
sb.WriteString(fmt.Sprintf("%s ", colorizeTimestamp(time.Now(), nocolor)))
337+
sb.WriteString(id)
338+
sb.WriteString(colors.Blue(fmt.Sprintf(" DNS %s (%s)", resp.Flags.OPCodeDesc, resp.Flags.QRDesc)).Bold())
339+
sb.WriteString(colors.Beige(fmt.Sprintf(" %#04x ", resp.TransactionID)).String())
340+
for _, rec := range resp.Questions {
341+
sb.WriteString(fmt.Sprintf("%s %s ", colors.LightBlue(rec.Type.Name), colors.Gray(rec.Name)))
342+
}
343+
for _, rec := range resp.AnswerRRs {
344+
sb.WriteString(colorizeRData(rec))
345+
}
346+
for _, rec := range resp.AuthorityRRs {
347+
sb.WriteString(colorizeRData(rec))
348+
}
349+
for _, rec := range resp.AdditionalRRs {
350+
sb.WriteString(colorizeRData(rec))
351+
}
352+
}
353+
return sb.String()
354+
}
355+
266356
func highlightPatterns(line string, nocolor bool) (string, bool) {
267357
matched := false
268358

@@ -377,7 +467,7 @@ func colorizeConnections(srcRemote, srcLocal, dstRemote, dstLocal net.Addr, id s
377467
}
378468

379469
func colorizeConnectionsTransparent(
380-
srcRemote, srcLocal, dstRemote, dstLocal net.Addr,
470+
srcRemote, srcLocal, dstLocal, dstRemote net.Addr,
381471
dst,
382472
id string,
383473
nocolor bool,

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/google/uuid v1.6.0
88
github.com/rs/zerolog v1.34.0
99
github.com/shadowy-pycoder/colors v0.0.1
10-
github.com/shadowy-pycoder/mshark v0.0.10
10+
github.com/shadowy-pycoder/mshark v0.0.13
1111
github.com/wzshiming/socks5 v0.5.2
1212
golang.org/x/sys v0.33.0
1313
golang.org/x/term v0.32.0
@@ -20,6 +20,7 @@ require (
2020
github.com/mattn/go-isatty v0.0.19 // indirect
2121
github.com/mdlayher/packet v1.1.2 // indirect
2222
github.com/mdlayher/socket v0.4.1 // indirect
23+
github.com/packetcap/go-pcap v0.0.0-20240528124601-8c87ecf5dbc5 // indirect
2324
github.com/pkg/errors v0.9.1 // indirect
2425
golang.org/x/net v0.40.0 // indirect
2526
golang.org/x/sync v0.16.0 // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
88
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
99
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
1010
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
11+
github.com/gopacket/gopacket v1.2.0 h1:eXbzFad7f73P1n2EJHQlsKuvIMJjVXK5tXoSca78I3A=
12+
github.com/gopacket/gopacket v1.2.0/go.mod h1:BrAKEy5EOGQ76LSqh7DMAr7z0NNPdczWm2GxCG7+I8M=
1113
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
1214
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
1315
github.com/malfunkt/iprange v0.9.0 h1:VCs0PKLUPotNVQTpVNszsut4lP7OCGNBwX+lOYBrnVQ=
@@ -21,6 +23,8 @@ github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY
2123
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
2224
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
2325
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
26+
github.com/packetcap/go-pcap v0.0.0-20240528124601-8c87ecf5dbc5 h1:p4VuaitqUAqSZSomd7Wb4BPV/Jj7Hno2/iqtfX7DZJI=
27+
github.com/packetcap/go-pcap v0.0.0-20240528124601-8c87ecf5dbc5/go.mod h1:zIAoVKeWP0mz4zXY50UYQt6NLg2uwKRswMDcGEqOms4=
2428
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
2529
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
2630
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -30,8 +34,8 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
3034
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
3135
github.com/shadowy-pycoder/colors v0.0.1 h1:weCj/YIOupqy4BSP8KuVzr20fC+cuAv/tArz7bhhkP4=
3236
github.com/shadowy-pycoder/colors v0.0.1/go.mod h1:lkrJS1PY2oVigNLTT6pkbF7B/v0YcU2LD5PZnss1Q4U=
33-
github.com/shadowy-pycoder/mshark v0.0.10 h1:pLMIsgfvnO0oKeBNdy0fTGQsx//6scCPT52g93CqyT4=
34-
github.com/shadowy-pycoder/mshark v0.0.10/go.mod h1:FqbHFdsx0zMnrZZH0+oPzaFcleP4O+tUWv8i5gxo87k=
37+
github.com/shadowy-pycoder/mshark v0.0.13 h1:ROEuey/Th4YAmfRg8Xc17aboMs5fknQho4mNBC9h+KE=
38+
github.com/shadowy-pycoder/mshark v0.0.13/go.mod h1:FqbHFdsx0zMnrZZH0+oPzaFcleP4O+tUWv8i5gxo87k=
3539
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
3640
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
3741
github.com/wzshiming/socks5 v0.5.2 h1:LtoowVNwAmkIQSkP1r1Wg435xUmC+tfRxorNW30KtnM=

0 commit comments

Comments
 (0)