Compare commits
22 Commits
3ff47dfa19
...
0.0.2
Author | SHA1 | Date | |
---|---|---|---|
4ec16513ac | |||
0d113b4e8b | |||
8cd537cd79 | |||
80043a571d | |||
998c9e217c | |||
977aef9ee2 | |||
5f10c27b0f | |||
0b876665d5 | |||
d422724556 | |||
a73854e040 | |||
d40147d61c | |||
3cbd6eace2 | |||
42e5679570 | |||
54159e2e5e | |||
e197990185 | |||
f3d84f09fd | |||
0382892f73 | |||
cb20f91223 | |||
2efe3344b4 | |||
0ad3e2b0d4 | |||
3ae568639e | |||
ead7578544 |
89
.drone.yml
Normal file
89
.drone.yml
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
workspace:
|
||||||
|
base: /go
|
||||||
|
path: src/deadbeef.codes/steven/hyp
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: create build environment
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
repo: registry.deadbeef.codes/hyp-build
|
||||||
|
when:
|
||||||
|
target:
|
||||||
|
include:
|
||||||
|
- environment
|
||||||
|
|
||||||
|
|
||||||
|
- name: build hyp (client) linux-amd64
|
||||||
|
image: registry.deadbeef.codes/hyp-build:latest
|
||||||
|
pull: always
|
||||||
|
volumes:
|
||||||
|
- name: publicrelease
|
||||||
|
path: /dist
|
||||||
|
environment:
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: amd64
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
commands:
|
||||||
|
- . /root/.profile
|
||||||
|
- cd hyp
|
||||||
|
- go build -o /dist/hyp-linux-amd64 .
|
||||||
|
|
||||||
|
|
||||||
|
- name: build hypd (server) linux-amd64
|
||||||
|
image: registry.deadbeef.codes/hyp-build:latest
|
||||||
|
pull: always
|
||||||
|
volumes:
|
||||||
|
- name: publicrelease
|
||||||
|
path: /dist
|
||||||
|
environment:
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: amd64
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
commands:
|
||||||
|
- . /root/.profile
|
||||||
|
- cd hypd/server
|
||||||
|
- go generate
|
||||||
|
- cd ..
|
||||||
|
- go build -o /dist/hypd-linux-amd64 .
|
||||||
|
|
||||||
|
|
||||||
|
- name: build hyp (client) windows-amd64
|
||||||
|
image: registry.deadbeef.codes/hyp-build:latest
|
||||||
|
pull: always
|
||||||
|
volumes:
|
||||||
|
- name: publicrelease
|
||||||
|
path: /dist
|
||||||
|
environment:
|
||||||
|
GOOS: windows
|
||||||
|
GOARCH: amd64
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
commands:
|
||||||
|
- . /root/.profile
|
||||||
|
- cd hyp
|
||||||
|
- go build -o /dist/hyp-windows-amd64.exe .
|
||||||
|
|
||||||
|
|
||||||
|
- name: release
|
||||||
|
image: plugins/gitea-release
|
||||||
|
pull: always
|
||||||
|
volumes:
|
||||||
|
- name: publicrelease
|
||||||
|
path: /dist
|
||||||
|
settings:
|
||||||
|
api_key:
|
||||||
|
from_secret: drone_token
|
||||||
|
base_url: https://deadbeef.codes
|
||||||
|
files: /dist/*
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- tag
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: publicrelease
|
||||||
|
host:
|
||||||
|
path: /data/public/build/hyp
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ hyp.secret
|
|||||||
*.exe
|
*.exe
|
||||||
hypd/hypd
|
hypd/hypd
|
||||||
hyp/hyp
|
hyp/hyp
|
||||||
|
hypd/server/hyp_bpf_bpfe*
|
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Build environment container
|
||||||
|
# THIS CONTAINER IS NOT FOR RUNNING HYP, IT IS FOR BUILDING IT FROM SOURCE
|
||||||
|
|
||||||
|
FROM debian:stable
|
||||||
|
LABEL maintainer="himself@stevenpolley.net"
|
||||||
|
|
||||||
|
# Install build
|
||||||
|
RUN apt update -y && \
|
||||||
|
apt upgrade -y && \
|
||||||
|
apt install -y wget git clang linux-headers-amd64 libbpf-dev
|
||||||
|
|
||||||
|
# Create a few symlinks
|
||||||
|
RUN ln -s /usr/bin/llvm-strip-14 /usr/bin/llvm-strip && \
|
||||||
|
ln -s /usr/include/x86_64-linux-gnu/asm /usr/include/asm
|
||||||
|
|
||||||
|
# Install golang - Latest at: https://go.dev/dl/
|
||||||
|
RUN wget https://go.dev/dl/go1.22.2.linux-amd64.tar.gz && \
|
||||||
|
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.2.linux-amd64.tar.gz && \
|
||||||
|
rm -rf *.tar.gz && \
|
||||||
|
echo "export PATH=$PATH:/usr/local/go/bin" >> /root/.profile && . /root/.profile
|
@ -1,5 +1,7 @@
|
|||||||
# hyp | Hide Your Ports
|
# hyp | Hide Your Ports
|
||||||
|
|
||||||
|
[](https://drone.deadbeef.codes/steven/hyp)
|
||||||
|
|
||||||
hyp is a [port knocking](https://www.youtube.com/watch?v=a7VJZEJVhD0) implementation written in Go, using spread-spectrum UDP as an authentication mechanism. It enables trusted devices to access services over the internet, wherever they are, and without the service being publicly accessible. The benefit is that the ports are not open publicly on the internet, they won't show in a port scan and are therefore less likely to be attacked by a threat actor.
|
hyp is a [port knocking](https://www.youtube.com/watch?v=a7VJZEJVhD0) implementation written in Go, using spread-spectrum UDP as an authentication mechanism. It enables trusted devices to access services over the internet, wherever they are, and without the service being publicly accessible. The benefit is that the ports are not open publicly on the internet, they won't show in a port scan and are therefore less likely to be attacked by a threat actor.
|
||||||
|
|
||||||
hyp provides security through obscurity. Security through obscurity tends to have a negative connotation, at least in the IT world. I don't agree with this, but it's prescribed as being bad. My belief is security through obscurity is a "further step" one can take to eliminate a certain class of threats. It by no means should be the only mechanism of protection, but instead should be incorporated only as part of a layered defense.
|
hyp provides security through obscurity. Security through obscurity tends to have a negative connotation, at least in the IT world. I don't agree with this, but it's prescribed as being bad. My belief is security through obscurity is a "further step" one can take to eliminate a certain class of threats. It by no means should be the only mechanism of protection, but instead should be incorporated only as part of a layered defense.
|
||||||
|
6
go.mod
6
go.mod
@ -3,13 +3,13 @@ module deadbeef.codes/steven/hyp
|
|||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/cilium/ebpf v0.14.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
)
|
)
|
||||||
|
34
go.sum
34
go.sum
@ -1,28 +1,30 @@
|
|||||||
|
github.com/cilium/ebpf v0.14.0 h1:0PsxAjO6EjI1rcT+rkp6WcCnE0ZvfkXBYiMedJtrSUs=
|
||||||
|
github.com/cilium/ebpf v0.14.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
|
||||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -31,8 +31,14 @@ Example usage:
|
|||||||
`,
|
`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
// load secret and generate ports using secret and current time
|
// load secret and generate ports using secret and current time
|
||||||
secretBytes, err := os.ReadFile("hyp.secret")
|
secretFilePath, err := cmd.Flags().GetString("secret")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to parse command flag 'secret': %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
secretBytes, err := os.ReadFile(secretFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to read file 'hyp.secret': %v", err)
|
log.Fatalf("failed to read file 'hyp.secret': %v", err)
|
||||||
}
|
}
|
||||||
@ -56,4 +62,6 @@ Example usage:
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(knockCmd)
|
rootCmd.AddCommand(knockCmd)
|
||||||
|
|
||||||
|
knockCmd.PersistentFlags().String("secret", "hyp.secret", "Path to the file containing the hyp secret.")
|
||||||
}
|
}
|
||||||
|
100
hypd/server/bpf_endian.h
Normal file
100
hypd/server/bpf_endian.h
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Code lifted from the folks at Cilium from ebpf-go repo
|
||||||
|
|
||||||
|
#ifndef __BPF_ENDIAN__
|
||||||
|
#define __BPF_ENDIAN__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Isolate byte #n and put it into byte #m, for __u##b type.
|
||||||
|
* E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64:
|
||||||
|
* 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx
|
||||||
|
* 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000
|
||||||
|
* 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn
|
||||||
|
* 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000
|
||||||
|
*/
|
||||||
|
#define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8))
|
||||||
|
|
||||||
|
#define ___bpf_swab16(x) ((__u16)( \
|
||||||
|
___bpf_mvb(x, 16, 0, 1) | \
|
||||||
|
___bpf_mvb(x, 16, 1, 0)))
|
||||||
|
|
||||||
|
#define ___bpf_swab32(x) ((__u32)( \
|
||||||
|
___bpf_mvb(x, 32, 0, 3) | \
|
||||||
|
___bpf_mvb(x, 32, 1, 2) | \
|
||||||
|
___bpf_mvb(x, 32, 2, 1) | \
|
||||||
|
___bpf_mvb(x, 32, 3, 0)))
|
||||||
|
|
||||||
|
#define ___bpf_swab64(x) ((__u64)( \
|
||||||
|
___bpf_mvb(x, 64, 0, 7) | \
|
||||||
|
___bpf_mvb(x, 64, 1, 6) | \
|
||||||
|
___bpf_mvb(x, 64, 2, 5) | \
|
||||||
|
___bpf_mvb(x, 64, 3, 4) | \
|
||||||
|
___bpf_mvb(x, 64, 4, 3) | \
|
||||||
|
___bpf_mvb(x, 64, 5, 2) | \
|
||||||
|
___bpf_mvb(x, 64, 6, 1) | \
|
||||||
|
___bpf_mvb(x, 64, 7, 0)))
|
||||||
|
|
||||||
|
/* LLVM's BPF target selects the endianness of the CPU
|
||||||
|
* it compiles on, or the user specifies (bpfel/bpfeb),
|
||||||
|
* respectively. The used __BYTE_ORDER__ is defined by
|
||||||
|
* the compiler, we cannot rely on __BYTE_ORDER from
|
||||||
|
* libc headers, since it doesn't reflect the actual
|
||||||
|
* requested byte order.
|
||||||
|
*
|
||||||
|
* Note, LLVM's BPF target has different __builtin_bswapX()
|
||||||
|
* semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
|
||||||
|
* in bpfel and bpfeb case, which means below, that we map
|
||||||
|
* to cpu_to_be16(). We could use it unconditionally in BPF
|
||||||
|
* case, but better not rely on it, so that this header here
|
||||||
|
* can be used from application and BPF program side, which
|
||||||
|
* use different targets.
|
||||||
|
*/
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define __bpf_ntohs(x) __builtin_bswap16(x)
|
||||||
|
# define __bpf_htons(x) __builtin_bswap16(x)
|
||||||
|
# define __bpf_constant_ntohs(x) ___bpf_swab16(x)
|
||||||
|
# define __bpf_constant_htons(x) ___bpf_swab16(x)
|
||||||
|
# define __bpf_ntohl(x) __builtin_bswap32(x)
|
||||||
|
# define __bpf_htonl(x) __builtin_bswap32(x)
|
||||||
|
# define __bpf_constant_ntohl(x) ___bpf_swab32(x)
|
||||||
|
# define __bpf_constant_htonl(x) ___bpf_swab32(x)
|
||||||
|
# define __bpf_be64_to_cpu(x) __builtin_bswap64(x)
|
||||||
|
# define __bpf_cpu_to_be64(x) __builtin_bswap64(x)
|
||||||
|
# define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x)
|
||||||
|
# define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x)
|
||||||
|
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||||
|
# define __bpf_ntohs(x) (x)
|
||||||
|
# define __bpf_htons(x) (x)
|
||||||
|
# define __bpf_constant_ntohs(x) (x)
|
||||||
|
# define __bpf_constant_htons(x) (x)
|
||||||
|
# define __bpf_ntohl(x) (x)
|
||||||
|
# define __bpf_htonl(x) (x)
|
||||||
|
# define __bpf_constant_ntohl(x) (x)
|
||||||
|
# define __bpf_constant_htonl(x) (x)
|
||||||
|
# define __bpf_be64_to_cpu(x) (x)
|
||||||
|
# define __bpf_cpu_to_be64(x) (x)
|
||||||
|
# define __bpf_constant_be64_to_cpu(x) (x)
|
||||||
|
# define __bpf_constant_cpu_to_be64(x) (x)
|
||||||
|
#else
|
||||||
|
# error "Fix your compiler's __BYTE_ORDER__?!"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define bpf_htons(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_htons(x) : __bpf_htons(x))
|
||||||
|
#define bpf_ntohs(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_ntohs(x) : __bpf_ntohs(x))
|
||||||
|
#define bpf_htonl(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_htonl(x) : __bpf_htonl(x))
|
||||||
|
#define bpf_ntohl(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_ntohl(x) : __bpf_ntohl(x))
|
||||||
|
#define bpf_cpu_to_be64(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x))
|
||||||
|
#define bpf_be64_to_cpu(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x))
|
||||||
|
|
||||||
|
#endif /* __BPF_ENDIAN__ */
|
71
hypd/server/hyp_bpf.c
Normal file
71
hypd/server/hyp_bpf.c
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2024 Steven Polley <himself@stevenpolley.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
//go:build ignore
|
||||||
|
#include "vmlinux.h"
|
||||||
|
#include "bpf_endian.h"
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
|
||||||
|
|
||||||
|
char __license[] SEC("license") = "BSD";
|
||||||
|
|
||||||
|
#define ETH_P_IP 0x0800
|
||||||
|
#define IP_FRAGMENTED 65343
|
||||||
|
|
||||||
|
// representation of knock data that gets sent to userspace
|
||||||
|
struct knock_data {
|
||||||
|
__u32 srcip; // 4 bytes
|
||||||
|
__u16 dstport; // 2 bytes
|
||||||
|
__u16 pad; // required padding - struct must be multiple of 4 bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
// ring buffer used to send data to userspace
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||||
|
__uint(max_entries, 1 << 24);
|
||||||
|
} rb SEC(".maps");
|
||||||
|
|
||||||
|
// force emitting struct event into the ELF
|
||||||
|
const struct knock_data *unused __attribute__((unused));
|
||||||
|
|
||||||
|
// hook into xpress data path attach point
|
||||||
|
SEC("xdp")
|
||||||
|
int xdp_prog_func(struct xdp_md *ctx) {
|
||||||
|
|
||||||
|
// xdp gives us the raw frame with no structures, it must be parsed
|
||||||
|
void *data = (void *)(long)ctx->data;
|
||||||
|
void *data_end = (void *)(long)ctx->data_end;
|
||||||
|
|
||||||
|
// A knock should not contain any data
|
||||||
|
if (data_end - data > 60) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse ethernet header
|
||||||
|
struct ethhdr *eth = data;
|
||||||
|
|
||||||
|
if ((void *)eth + sizeof(*eth) <= data_end) {
|
||||||
|
// parse IP header
|
||||||
|
struct iphdr *ip = data + sizeof(*eth);
|
||||||
|
if ((void *)ip + sizeof(*ip) <= data_end) {
|
||||||
|
if (ip->protocol == IPPROTO_UDP) {
|
||||||
|
// parse UDP header
|
||||||
|
struct udphdr *udp = (void *)ip + sizeof(*ip);
|
||||||
|
if ((void *)udp + sizeof(*udp) <= data_end)
|
||||||
|
{
|
||||||
|
// pack into knock structure and send to userspace
|
||||||
|
struct knock_data knock = {
|
||||||
|
.srcip = bpf_ntohl(ip->saddr),
|
||||||
|
.dstport = bpf_htons(udp->dest),
|
||||||
|
.pad = 0
|
||||||
|
};
|
||||||
|
bpf_ringbuf_output(&rb, &knock, sizeof(knock), BPF_RB_FORCE_WAKEUP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
// We send everything to XDP_PASS
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
@ -5,23 +5,28 @@ Copyright © 2024 Steven Polley <himself@stevenpolley.net>
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"deadbeef.codes/steven/hyp/otphyp"
|
"deadbeef.codes/steven/hyp/otphyp"
|
||||||
"github.com/google/gopacket"
|
"github.com/cilium/ebpf/link"
|
||||||
"github.com/google/gopacket/pcap"
|
"github.com/cilium/ebpf/ringbuf"
|
||||||
|
"github.com/cilium/ebpf/rlimit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go --type knock_data hyp_bpf hyp_bpf.c
|
||||||
|
|
||||||
// Client is used to keep track of a client attempting to perform an authentic knock sequence
|
// Client is used to keep track of a client attempting to perform an authentic knock sequence
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Progress int // index of current progress in sequence. Value of 1 means first port has been matched
|
Progress int // index of current progress in sequence. Value of 1 means first port has been matched
|
||||||
Sequence [4]uint16 // stores the knock sequence the current client is attempting. It's set and tracked here to prevent race conditions during a knock sequence being received and key rotations
|
Sequence [4]uint16 // stores the knock sequence the current client is attempting. It's set and tracked here to prevent race conditions during a knock sequence being received and key rotations
|
||||||
LastUpdated time.Time // The last time the client sent a correct packet in the sequence
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// KnockSequence is used keep track of an ordered knock sequence and whether it's been marked for use (to prevent replay attacks)
|
// KnockSequence is used keep track of an ordered knock sequence and whether it's been marked for use (to prevent replay attacks)
|
||||||
@ -30,57 +35,112 @@ type KnockSequence struct {
|
|||||||
PortSequence [4]uint16 // Each knock sequence is four ports long
|
PortSequence [4]uint16 // Each knock sequence is four ports long
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
KnockSequenceTimeout = 3 // TBD: Make this a configurable value
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
clients map[string]*Client // Contains a map of clients
|
clients map[uint32]*Client // Contains a map of clients, key is IPv4 address
|
||||||
knockSequences []KnockSequence // We have 3 valid knock sequences at any time to account for clock skew
|
knockSequences []KnockSequence // We have 3 valid knock sequences at any time to account for clock skew
|
||||||
sharedSecret string // base32 encoded shared secret used for totp
|
sharedSecret string // base32 encoded shared secret used for totp
|
||||||
)
|
)
|
||||||
|
|
||||||
// packetServer is the main function when operating in server mode
|
// PacketServer is the main function when operating in server mode
|
||||||
// it sets up the pcap on the capture device and starts a goroutine
|
// it sets up the pcap on the capture device and starts a goroutine
|
||||||
// to rotate the knock sequence
|
// to rotate the knock sequence
|
||||||
func PacketServer(captureDevice string) error {
|
func PacketServer(captureDevice string) error {
|
||||||
|
|
||||||
|
iface, err := net.InterfaceByName(captureDevice)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("lookup network iface %q: %v", captureDevice, err)
|
||||||
|
}
|
||||||
|
|
||||||
secretBytes, err := os.ReadFile("hyp.secret")
|
secretBytes, err := os.ReadFile("hyp.secret")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to read file 'hyp.secret': %v", err)
|
log.Fatalf("failed to read file 'hyp.secret': %v", err)
|
||||||
}
|
}
|
||||||
sharedSecret = string(secretBytes)
|
sharedSecret = string(secretBytes)
|
||||||
|
|
||||||
clients = make(map[string]*Client, 0)
|
clients = make(map[uint32]*Client, 0)
|
||||||
knockSequences = []KnockSequence{}
|
knockSequences = []KnockSequence{}
|
||||||
|
|
||||||
// Open pcap handle on device
|
|
||||||
handle, err := pcap.OpenLive(captureDevice, 1600, true, pcap.BlockForever)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open pcap on capture device: %w", err)
|
|
||||||
}
|
|
||||||
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
|
||||||
|
|
||||||
// Setup a goroutine to periodically rotate the authentic knock sequence
|
// Setup a goroutine to periodically rotate the authentic knock sequence
|
||||||
go rotateSequence(handle)
|
go rotateSequence()
|
||||||
|
|
||||||
// Read from the pcap handle until we exit
|
////////////////////////////////////
|
||||||
for packet := range packetSource.Packets() {
|
|
||||||
handlePacket(packet) // Do something with a packet here.
|
// Allow the current process to lock memory for eBPF resources.
|
||||||
|
if err := rlimit.RemoveMemlock(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// Load pre-compiled programs into the kernel.
|
||||||
|
objs := hyp_bpfObjects{}
|
||||||
|
if err := loadHyp_bpfObjects(&objs, nil); err != nil {
|
||||||
|
log.Fatalf("loading objects: %v", err)
|
||||||
|
}
|
||||||
|
defer objs.Close()
|
||||||
|
|
||||||
|
// Attach the program.
|
||||||
|
l, err := link.AttachXDP(link.XDPOptions{
|
||||||
|
Program: objs.XdpProgFunc,
|
||||||
|
Interface: iface.Index,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not attach XDP program: %v", err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
log.Printf("Attached XDP program to iface %q (index %d)", iface.Name, iface.Index)
|
||||||
|
log.Printf("Press Ctrl-C to exit and remove the program")
|
||||||
|
|
||||||
|
rd, err := ringbuf.NewReader(objs.Rb)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not open ring buffer reader: %v", err)
|
||||||
|
}
|
||||||
|
defer rd.Close()
|
||||||
|
|
||||||
|
var event hyp_bpfKnockData
|
||||||
|
for {
|
||||||
|
record, err := rd.Read()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ringbuf.ErrClosed) {
|
||||||
|
log.Println("eBPF ring buffer closed, exiting...")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Printf("error reading from ring buffer reader: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
|
||||||
|
log.Printf("error parsing ringbuf event: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handleKnock(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// intToIP converts IPv4 number to net.IP
|
||||||
|
func intToIP(ipNum uint32) net.IP {
|
||||||
|
ip := make(net.IP, 4)
|
||||||
|
binary.BigEndian.PutUint32(ip, ipNum)
|
||||||
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
// packets that match the BPF filter get passed to handlePacket
|
// packets that match the BPF filter get passed to handlePacket
|
||||||
func handlePacket(packet gopacket.Packet) {
|
func handleKnock(knockEvent hyp_bpfKnockData) {
|
||||||
port := binary.BigEndian.Uint16(packet.TransportLayer().TransportFlow().Dst().Raw())
|
|
||||||
srcip := packet.NetworkLayer().NetworkFlow().Src().String()
|
|
||||||
|
|
||||||
client, ok := clients[srcip]
|
client, ok := clients[knockEvent.Srcip]
|
||||||
if !ok { // client doesn't exist yet
|
if !ok { // client doesn't exist yet
|
||||||
for i, knockSequence := range knockSequences { // identify which of the 3 authentic knock sequences is matched
|
for i, knockSequence := range knockSequences { // identify which of the 3 authentic knock sequences is matched
|
||||||
if knockSequence.Used { // skip over sequences that are already used to prevent replay attack
|
if knockSequence.Used { // skip over sequences that are already used to prevent replay attack
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if port == knockSequence.PortSequence[0] {
|
if knockEvent.Dstport == knockSequence.PortSequence[0] {
|
||||||
// Create the client and mark the knock sequence as used
|
// Create the client and mark the knock sequence as used
|
||||||
clients[srcip] = &Client{Progress: 1, Sequence: knockSequence.PortSequence}
|
clients[knockEvent.Srcip] = &Client{Progress: 1, Sequence: knockSequence.PortSequence}
|
||||||
knockSequences[i].Used = true
|
knockSequences[i].Used = true
|
||||||
|
go timeoutKnockSequence(knockEvent.Srcip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -89,25 +149,36 @@ func handlePacket(packet gopacket.Packet) {
|
|||||||
// if it's wrong, reset progress
|
// if it's wrong, reset progress
|
||||||
// TBD: vulnerable to sweep attack - this won't be triggered if a wrong packet doesn't match BPF filter
|
// TBD: vulnerable to sweep attack - this won't be triggered if a wrong packet doesn't match BPF filter
|
||||||
// TBD: make the sweep attack fix on by default, but configurable to be off to allow for limited BPF filter for extremely low overhead as compromise.
|
// TBD: make the sweep attack fix on by default, but configurable to be off to allow for limited BPF filter for extremely low overhead as compromise.
|
||||||
if port != client.Sequence[client.Progress] {
|
if knockEvent.Dstport != client.Sequence[client.Progress] {
|
||||||
delete(clients, srcip)
|
delete(clients, knockEvent.Srcip)
|
||||||
fmt.Printf("port '%d' is in sequence, but came at unexpected order - resetting progress", port)
|
fmt.Printf("port '%d' is in sequence, but came at unexpected order - resetting progress", knockEvent.Dstport)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client increases progress through sequence and checks if sequence is completed
|
// Client increases progress through sequence and checks if sequence is completed
|
||||||
client.Progress++
|
client.Progress++
|
||||||
if client.Progress >= len(client.Sequence) {
|
if client.Progress >= len(client.Sequence) {
|
||||||
delete(clients, srcip)
|
delete(clients, knockEvent.Srcip)
|
||||||
handleSuccess(srcip) // The magic function, the knock is completed
|
handleSuccess(intToIP(knockEvent.Srcip)) // The magic function, the knock is completed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to rotate the authentic port knock sequence
|
// Remove the client after the timeout value has elapsed. This prevents a client from
|
||||||
func rotateSequence(handle *pcap.Handle) {
|
// being indefinitely stuck part way through an old knock sequence. It's also helpful
|
||||||
for {
|
// in preventing sweep attacks as the authentic knock sequence must be correctly entered
|
||||||
|
// within the timeout value from start to finish.
|
||||||
|
func timeoutKnockSequence(srcip uint32) {
|
||||||
|
time.Sleep(time.Second * KnockSequenceTimeout)
|
||||||
|
_, ok := clients[srcip]
|
||||||
|
if ok {
|
||||||
|
delete(clients, srcip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to rotate the authentic port knock sequence
|
||||||
|
func rotateSequence() {
|
||||||
|
for {
|
||||||
// Generate new knock sequences with time skew support
|
// Generate new knock sequences with time skew support
|
||||||
t := time.Now().Add(time.Second * -30)
|
t := time.Now().Add(time.Second * -30)
|
||||||
for i := len(knockSequences); i < 3; i++ {
|
for i := len(knockSequences); i < 3; i++ {
|
||||||
@ -120,12 +191,6 @@ func rotateSequence(handle *pcap.Handle) {
|
|||||||
}
|
}
|
||||||
fmt.Println("New sequences:", knockSequences)
|
fmt.Println("New sequences:", knockSequences)
|
||||||
|
|
||||||
// Set BPF filter
|
|
||||||
err := setPacketFilter(handle)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("failed to change packet filter: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep until next 30 second offset
|
// Sleep until next 30 second offset
|
||||||
time.Sleep(time.Until(time.Now().Truncate(time.Second * 30).Add(time.Second * 30)))
|
time.Sleep(time.Until(time.Now().Truncate(time.Second * 30).Add(time.Second * 30)))
|
||||||
|
|
||||||
@ -134,31 +199,10 @@ func rotateSequence(handle *pcap.Handle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a pcap handle and list of authentic port knock sequences, configures a BPF filter
|
|
||||||
func setPacketFilter(handle *pcap.Handle) error {
|
|
||||||
filter := "udp && ("
|
|
||||||
for i, knockSequence := range knockSequences {
|
|
||||||
for j, port := range knockSequence.PortSequence {
|
|
||||||
if i == 0 && j == 0 {
|
|
||||||
filter += fmt.Sprint("port ", port)
|
|
||||||
} else {
|
|
||||||
filter += fmt.Sprint(" || port ", port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filter += ")"
|
|
||||||
err := handle.SetBPFFilter(filter)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set BPF filter '%s': %v", filter, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TBD: Implement - this is a temporary routine to demonstrate an application
|
// TBD: Implement - this is a temporary routine to demonstrate an application
|
||||||
func handleSuccess(srcip string) {
|
func handleSuccess(srcip net.IP) {
|
||||||
fmt.Println("Success for ", srcip)
|
fmt.Println("Success for ", srcip)
|
||||||
|
cmd := exec.Command("iptables", "-A", "INPUT", "-p", "tcp", "-s", fmt.Sprint(srcip), "--dport", "22", "-j", "ACCEPT")
|
||||||
cmd := exec.Command("iptables", "-A", "INPUT", "-p", "tcp", "-s", srcip, "--dport", "22", "-j", "ACCEPT")
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to execute iptables command for '%s': %v", srcip, err)
|
log.Printf("failed to execute iptables command for '%s': %v", srcip, err)
|
||||||
|
112576
hypd/server/vmlinux.h
Normal file
112576
hypd/server/vmlinux.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user