├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd └── udpx │ └── main.go ├── go.mod ├── pkg ├── colors │ └── color_output.go ├── probes │ ├── print_probes.go │ └── probes.go ├── scan │ └── scanner.go └── utils │ ├── helpers.go │ └── opts_parser.go └── screenshots ├── showcase.png └── udpx_logo.png /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Release 5 | 6 | on: 7 | push: 8 | # Sequence of patterns matched against refs/tags 9 | tags: 10 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | goosarch: 19 | - 'linux/amd64' 20 | - 'linux/386' 21 | - 'darwin/amd64' 22 | - 'darwin/arm64' 23 | - 'windows/amd64' 24 | - 'windows/386' 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v2 28 | with: 29 | fetch-depth: 0 30 | - uses: actions/setup-go@v2 31 | with: 32 | go-version: '1.17' 33 | - name: Get OS and arch info 34 | run: | 35 | GOOSARCH=${{matrix.goosarch}} 36 | GOOS=${GOOSARCH%/*} 37 | GOARCH=${GOOSARCH#*/} 38 | BINARY_NAME=${{github.event.repository.name}}-$GOOS-$GOARCH 39 | echo "BINARY_NAME=$BINARY_NAME" >> $GITHUB_ENV 40 | echo "GOOS=$GOOS" >> $GITHUB_ENV 41 | echo "GOARCH=$GOARCH" >> $GITHUB_ENV 42 | - name: Build 43 | run: | 44 | go build -o "$BINARY_NAME" -v ./cmd/udpx 45 | 46 | - name: Zip bin 47 | run: zip -r ${{env.BINARY_NAME}}.zip ${{env.BINARY_NAME}} 48 | 49 | #- name: Release Notes 50 | # run: 51 | # git log $(git describe HEAD~ --tags --abbrev=0)..HEAD --pretty='format:* %h %s%n * %an <%ae>' --no-merges >> ".github/RELEASE-TEMPLATE.md" 52 | - name: Release with Notes 53 | uses: softprops/action-gh-release@v1 54 | with: 55 | tag_name: ${{ github.ref }} 56 | release_name: Release ${{ github.ref }} 57 | #body_path: ".github/RELEASE-TEMPLATE.md" 58 | draft: true 59 | files: ${{env.BINARY_NAME}}.zip 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | udpx 2 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 nullt3r@bugdelivery.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Alt text](screenshots/udpx_logo.png) 2 | # 3 | Fast and lightweight, UDPX is a single-packet UDP scanner written in Go that supports the discovery of over 45 services with the ability to add custom ones. It is easy to use and portable, and can be run on Linux, Mac OS, and Windows. Unlike internet-wide scanners like zgrab2 and zmap, UDPX is designed for portability and ease of use. 4 | 5 | * It is fast. It can scan whole /16 network in ~20 seconds for a single service. 6 | * You don't need to instal libpcap or any other dependencies. 7 | * Can run on Linux, Mac Os, Windows. Or your Nethunter if you built it for Arm. 8 | * Customizable. You can add your probes and test for even more protocols. 9 | * Stores results in JSONL format. 10 | * Scans also domain names. 11 | 12 | ## How it works 13 | Scanning UDP ports is very different than scanning TCP - you may, or may not get any result back from probing an UDP port as UDP is a connectionless protocol. UDPX implements a single-packet based approach. A protocol-specific packet is sent to the defined service (port) and waits for a response. The limit is set to 500 ms by default and can be changed by `-w` flag. If the service sends a packet back within this time, it is certain that it is indeed listening on that port and is reported as open. 14 | 15 | A typical technique is to send 0 byte UDP packets to each port on the target machine. If we receive an "ICMP Port Unreachable" message, then the port is closed. If an UDP response is received to the probe (unusual), the port is open. If we get no response at all, the state is open or filtered, meaning that the port is either open or packet filters are blocking the communication. This method is not implemented as there is no added value (UDPX tests only for specific protocols). 16 | 17 | ## Usage 18 | 19 | ![Alt text](screenshots/showcase.png) 20 | 21 | 22 | > :warning: **Concurrency:** By default, concurrency is set to 32 connections only (so you don't crash anything). If you have a lot of hosts to scan, you can set it to 128 or 256 connections. Based on your hardware, connection stability, and ulimit (on *nix), you can run 512 or more concurrent connections, but this is not recommended. 23 | 24 | To scan a single IP: 25 | ``` 26 | udpx -t 1.1.1.1 27 | ``` 28 | 29 | To scan a CIDR with maximum of 128 connections and timeout of 1000 ms: 30 | ``` 31 | udpx -t 1.2.3.4/24 -c 128 -w 1000 32 | ``` 33 | 34 | To scan targets from file with maximum of 128 connections for only specific service: 35 | ``` 36 | udpx -tf targets.txt -c 128 -s ipmi 37 | ``` 38 | 39 | Target can be: 40 | * IP address 41 | * CIDR 42 | * Domain 43 | 44 | IPv6 is supported. 45 | 46 | If you want to store the results, use flag `-o [filename]`. Output is in JSONL format, as can be seen bellow: 47 | 48 | ```jsonl 49 | {"address":"45.33.32.156","hostname":"scanme.nmap.org","port":123,"service":"ntp","response_data":"JAME6QAAAEoAAA56LU9vp+d2ZPwOYIyDxU8jS3GxUvM="} 50 | ``` 51 | 52 | ## Options 53 | ``` 54 | 55 | __ ______ ____ _ __ 56 | / / / / __ \/ __ \ |/ / 57 | / / / / / / / /_/ / / 58 | / /_/ / /_/ / ____/ | 59 | \____/_____/_/ /_/|_| 60 | v1.0.2-beta, by @nullt3r 61 | 62 | Usage of ./udpx-linux-amd64: 63 | -c int 64 | Maximum number of concurrent connections (default 32) 65 | -nr 66 | Do not randomize addresses 67 | -o string 68 | Output file to write results 69 | -s string 70 | Scan only for a specific service, one of: ard, bacnet, bacnet_rpm, chargen, citrix, coap, db, db, digi1, digi2, digi3, dns, ipmi, ldap, mdns, memcache, mssql, nat_port_mapping, natpmp, netbios, netis, ntp, ntp_monlist, openvpn, pca_nq, pca_st, pcanywhere, portmap, qotd, rdp, ripv, sentinel, sip, snmp1, snmp2, snmp3, ssdp, tftp, ubiquiti, ubiquiti_discovery_v1, ubiquiti_discovery_v2, upnp, valve, wdbrpc, wsd, wsd_malformed, xdmcp, kerberos, ike 71 | -sp 72 | Show received packets (only first 32 bytes) 73 | -t string 74 | IP/CIDR to scan 75 | -tf string 76 | File containing IPs/CIDRs to scan 77 | -w int 78 | Maximum time to wait for a response (socket timeout) in ms (default 500) 79 | ``` 80 | 81 | ## Building 82 | You can grab prebuilt binaries in the release section. If you want to build UDPX from source, follow these steps: 83 | 84 | From git: 85 | ``` 86 | git clone https://github.com/nullt3r/udpx 87 | cd udpx 88 | go build ./cmd/udpx 89 | ``` 90 | You can find the binary in the current directory. 91 | 92 | Or via go: 93 | ``` 94 | go install -v github.com/nullt3r/udpx/cmd/udpx@latest 95 | ``` 96 | 97 | After that, you can find the binary in `$HOME/go/bin/udpx`. If you want, move binary to `/usr/local/bin/` so you can call it directly. 98 | 99 | ## Supported services 100 | The UDPX supports more then 45 services. The most interesting are: 101 | * ipmi 102 | * snmp 103 | * ike 104 | * tftp 105 | * openvpn 106 | * kerberos 107 | * ldap 108 | 109 | The complete list of supported services: 110 | * ard 111 | * bacnet 112 | * bacnet_rpm 113 | * chargen 114 | * citrix 115 | * coap 116 | * db 117 | * db 118 | * digi1 119 | * digi2 120 | * digi3 121 | * dns 122 | * ipmi 123 | * ldap 124 | * mdns 125 | * memcache 126 | * mssql 127 | * nat_port_mapping 128 | * natpmp 129 | * netbios 130 | * netis 131 | * ntp 132 | * ntp_monlist 133 | * openvpn 134 | * pca_nq 135 | * pca_st 136 | * pcanywhere 137 | * portmap 138 | * qotd 139 | * rdp 140 | * ripv 141 | * sentinel 142 | * sip 143 | * snmp1 144 | * snmp2 145 | * snmp3 146 | * ssdp 147 | * tftp 148 | * ubiquiti 149 | * ubiquiti_discovery_v1 150 | * ubiquiti_discovery_v2 151 | * upnp 152 | * valve 153 | * wdbrpc 154 | * wsd 155 | * wsd_malformed 156 | * xdmcp 157 | * kerberos 158 | * ike 159 | 160 | ## How to add your own probe? 161 | Please send a feature request with protocol name and port and I will make it happen. Or add it on your own, the file `pkg/probes/probes.go` contains all available payloads. Specify the protocol name, port and packet data (hex-encoded). 162 | 163 | ```go 164 | { 165 | Name: "ike", 166 | Payloads: []string{"5b5e64c03e99b51100000000000000000110020000000000000001500000013400000001000000010000012801010008030000240101"}, 167 | Port: []int{500, 4500}, 168 | }, 169 | ``` 170 | 171 | ## Credits 172 | * [Nmap](https://nmap.org/) 173 | * [UDP Hunter](https://github.com/NotSoSecure/udp-hunter) 174 | * [ZGrab2](https://github.com/zmap/zgrab2) 175 | * [ZMap](https://github.com/zmap/zmap) 176 | 177 | ## Disclaimer 178 | I am not responsible for any damages. You are responsible for your own actions. Scanning or attacking targets without prior mutual consent can be illegal. 179 | 180 | ## License 181 | UDPX is distributed under [MIT License](https://raw.githubusercontent.com/nullt3r/udpx/main/LICENSE). 182 | -------------------------------------------------------------------------------- /cmd/udpx/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "os" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/nullt3r/udpx/pkg/colors" 15 | "github.com/nullt3r/udpx/pkg/probes" 16 | "github.com/nullt3r/udpx/pkg/scan" 17 | "github.com/nullt3r/udpx/pkg/utils" 18 | ) 19 | 20 | func main() { 21 | fmt.Printf(`%s 22 | __ ______ ____ _ __ 23 | / / / / __ \/ __ \ |/ / 24 | / / / / / / / /_/ / / 25 | / /_/ / /_/ / ____/ | 26 | \____/_____/_/ /_/|_| 27 | v1.0.7, by @nullt3r 28 | 29 | %s`, colors.SetColor().Cyan, colors.SetColor().Reset) 30 | 31 | opts := utils.ParseOptions() 32 | 33 | var targets []string 34 | var toscan []string 35 | 36 | if len(opts.Arg_t) == 0 && len(opts.Arg_tf) == 0 { 37 | log.Fatalf("%s[!]%s Error, argument -t or -tf is required\n", colors.SetColor().Red, colors.SetColor().Reset) 38 | } 39 | 40 | if len(opts.Arg_tf) != 0 { 41 | val, err := utils.ReadFile(opts.Arg_tf) 42 | if err != nil { 43 | log.Fatalf("%s[!]%s Error while loading targets from file: %s", colors.SetColor().Red, colors.SetColor().Reset, err) 44 | } 45 | targets = val 46 | } else if len(opts.Arg_t) != 0 { 47 | targets = []string{opts.Arg_t} 48 | } 49 | 50 | for _, target := range targets { 51 | if strings.Contains(target, "/") { 52 | val, err := utils.IpsFromCidr(target) 53 | 54 | if err != nil { 55 | log.Fatalf("%s[!]%s Error parsing IP range: %s", colors.SetColor().Red, colors.SetColor().Reset, err) 56 | } 57 | 58 | toscan = append(toscan, val...) 59 | } else { 60 | toscan = append(toscan, target) 61 | } 62 | } 63 | 64 | if len(opts.Arg_s) != 0 { 65 | for n, probe := range probes.Probes { 66 | if probe.Name == opts.Arg_s { 67 | probes.Probes = []probes.Probe{probe} 68 | break 69 | } 70 | if n == len(probes.Probes)-1 { 71 | log.Fatalf("%s[!]%s Service '%s' is not supported", colors.SetColor().Red, colors.SetColor().Reset, opts.Arg_s) 72 | } 73 | } 74 | } 75 | 76 | toscan = utils.Deduplicate(toscan) 77 | toscan_count := len(toscan) 78 | 79 | if !opts.Arg_nr { 80 | rand.Seed(time.Now().UnixNano()) 81 | rand.Shuffle(toscan_count, func(i, j int) { toscan[i], toscan[j] = toscan[j], toscan[i] }) 82 | } 83 | 84 | goroutineLimit := opts.Arg_c 85 | guard := make(chan struct{}, goroutineLimit) 86 | 87 | var wg sync.WaitGroup 88 | 89 | log.Printf("[+] Starting UDP scan on %d target(s)", toscan_count) 90 | 91 | wg.Add(toscan_count) 92 | 93 | comm := make(chan scan.Message, 10) 94 | 95 | go func() { 96 | for _, t := range toscan { 97 | guard <- struct{}{} 98 | go func(t string) { 99 | defer wg.Done() 100 | scanner := scan.Scanner{Target: t, Probes: probes.Probes, Arg_st: opts.Arg_st, Arg_sp: opts.Arg_sp, Channel: comm} 101 | scanner.Run() 102 | <-guard 103 | }(t) 104 | } 105 | 106 | }() 107 | 108 | go func() { 109 | wg.Wait() 110 | close(comm) 111 | }() 112 | 113 | if len(opts.Arg_o) != 0 { 114 | f, err := os.Create(opts.Arg_o) 115 | 116 | if err != nil { 117 | log.Fatalf("%s[!]%s Error creating output file: %s", colors.SetColor().Red, colors.SetColor().Reset, err) 118 | } 119 | 120 | defer f.Close() 121 | 122 | log.Printf("[+] Results will be written to: %s", opts.Arg_o) 123 | } 124 | 125 | for message := range comm { 126 | log.Printf("%s[*]%s %s:%d (%s)", colors.SetColor().Cyan, colors.SetColor().Reset, message.Address, message.Port, message.Service) 127 | 128 | if opts.Arg_sp { 129 | log.Printf("[+] Received packet: %s%s%s...", colors.SetColor().Yellow, hex.EncodeToString(message.ResponseData), colors.SetColor().Reset) 130 | } 131 | 132 | if len(opts.Arg_o) != 0 { 133 | json, err := json.Marshal(&message) 134 | 135 | if err != nil { 136 | log.Fatalf("%s[!]%s Error: %s", colors.SetColor().Red, colors.SetColor().Reset, err) 137 | } 138 | 139 | f, err := os.OpenFile(opts.Arg_o, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) 140 | 141 | if err != nil { 142 | log.Fatalf("%s[!]%s Error opening output file: %s", colors.SetColor().Red, colors.SetColor().Reset, err) 143 | } 144 | 145 | if _, err = f.WriteString(string(json) + "\n"); err != nil { 146 | log.Fatalf("%s[!]%s Error writing output file: %s", colors.SetColor().Red, colors.SetColor().Reset, err) 147 | } 148 | 149 | } 150 | } 151 | 152 | <-comm 153 | 154 | log.Print("[+] Scan completed") 155 | } 156 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nullt3r/udpx 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /pkg/colors/color_output.go: -------------------------------------------------------------------------------- 1 | package colors 2 | 3 | import "runtime" 4 | 5 | type colors struct { 6 | Cyan string 7 | Yellow string 8 | Red string 9 | Reset string 10 | } 11 | 12 | func SetColor() *colors { 13 | c := &colors{} 14 | 15 | if runtime.GOOS == "windows" { 16 | c.Cyan = "" 17 | c.Yellow = "" 18 | c.Red = "" 19 | c.Reset = "" 20 | } else { 21 | c.Cyan = "\033[36m" 22 | c.Yellow = "\033[33m" 23 | c.Red = "\033[1;31m" 24 | c.Reset = "\033[0m" 25 | } 26 | 27 | return c 28 | 29 | } 30 | -------------------------------------------------------------------------------- /pkg/probes/print_probes.go: -------------------------------------------------------------------------------- 1 | package probes 2 | 3 | func GetProbeNames() string { 4 | var avail_probes string 5 | 6 | for i := range Probes { 7 | avail_probes += Probes[i].Name 8 | if i != len(Probes)-1 { 9 | avail_probes += ", " 10 | } 11 | } 12 | 13 | return avail_probes 14 | } 15 | -------------------------------------------------------------------------------- /pkg/probes/probes.go: -------------------------------------------------------------------------------- 1 | package probes 2 | 3 | type Probe struct { 4 | Name string 5 | Payloads []string 6 | Port []int 7 | } 8 | 9 | var Probes = []Probe{ 10 | { 11 | Name: "ard", 12 | Payloads: []string{"0014000103"}, 13 | Port: []int{3283}, 14 | }, 15 | { 16 | Name: "bacnet", 17 | Payloads: []string{"810A001101040005D60C0C023FFFFF194B4C", "810A002501040205010E0C020000001E090C091C092C09380939093A0946094D097809791F"}, 18 | Port: []int{47808}, 19 | }, 20 | { 21 | Name: "chargen", 22 | Payloads: []string{"01"}, 23 | Port: []int{19}, 24 | }, 25 | { 26 | Name: "citrix", 27 | Payloads: []string{"1E00013002FDA8E300000000000000000000000000000000000000000000"}, 28 | Port: []int{1604}, 29 | }, 30 | { 31 | Name: "coap", 32 | Payloads: []string{"40017D70BB2E77656C6C2D6B6E6F776E04636F7265"}, 33 | Port: []int{5683}, 34 | }, 35 | { 36 | Name: "db", 37 | Payloads: []string{"444232474554414444520053514C303930313000", "444232474554414444520053514C303530303000"}, 38 | Port: []int{523}, 39 | }, 40 | { 41 | Name: "digi", 42 | Payloads: []string{"4449474900010006FFFFFFFFFFFF", "44564B5400010006FFFFFFFFFFFF", "4447445000010006FFFFFFFFFFFF"}, 43 | Port: []int{2362}, 44 | }, 45 | { 46 | Name: "dns", 47 | Payloads: []string{"34EF010000010000000000000756455253494F4E0442494E440000100003", "AE0D010000010000000000000377777706676F6F676C6503636F6D0000010001"}, 48 | Port: []int{53}, 49 | }, 50 | { 51 | Name: "ipmi", 52 | Payloads: []string{"0600FF07000000000000000000092018C88100388E04B5"}, 53 | Port: []int{623}, 54 | }, 55 | { 56 | Name: "ldap", 57 | Payloads: []string{"30840000002D02010163840000002404000A01000A0100020100020100010100870B6F626A656374636C617373308400000000000A"}, 58 | Port: []int{389}, 59 | }, 60 | { 61 | Name: "mdns", 62 | Payloads: []string{"000000000001000000000000095F7365727669636573075F646E732D7364045F756470056C6F63616C00000C0001"}, 63 | Port: []int{5353}, 64 | }, 65 | { 66 | Name: "memcache", 67 | Payloads: []string{"5A4D0000000100007374617473206974656D730D0A"}, 68 | Port: []int{11211}, 69 | }, 70 | { 71 | Name: "mssql", 72 | Payloads: []string{"02"}, 73 | Port: []int{1434}, 74 | }, 75 | { 76 | Name: "nat", 77 | Payloads: []string{"0000000000000000000000000000E3B3E483", "00000000"}, 78 | Port: []int{5351}, 79 | }, 80 | { 81 | Name: "netbios", 82 | Payloads: []string{"E5D80000000100000000000020434B4141414141414141414141414141414141414141414141414141414141410000210001"}, 83 | Port: []int{137}, 84 | }, 85 | { 86 | Name: "netis", 87 | Payloads: []string{"0A000000000000000000000000009E21BDAD"}, 88 | Port: []int{53413}, 89 | }, 90 | { 91 | Name: "ntp", 92 | Payloads: []string{"E30004FA000100000001000000000000000000000000000000000000000000000000000000000000C54F234B71B152F3", "1700032A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, 93 | Port: []int{123}, 94 | }, 95 | { 96 | Name: "openvpn", 97 | Payloads: []string{"381212121212121212000000000038B126DE"}, 98 | Port: []int{1194}, 99 | }, 100 | { 101 | Name: "pca", 102 | Payloads: []string{"4E51", "5354", "4E5100000000000000000000000041F4BFA6"}, 103 | Port: []int{5632}, 104 | }, 105 | { 106 | Name: "portmap", 107 | Payloads: []string{"1AA9FFE10000000000000002000186A0000000020000000400000000000000000000000000000000"}, 108 | Port: []int{111}, 109 | }, 110 | { 111 | Name: "qotd", 112 | Payloads: []string{"0D0A"}, 113 | Port: []int{17}, 114 | }, 115 | { 116 | Name: "rdp", 117 | Payloads: []string{"00000000000000FF00000000000000005406"}, 118 | Port: []int{3389}, 119 | }, 120 | { 121 | Name: "ripv", 122 | Payloads: []string{"010100000000000000000000000000000000000000001000"}, 123 | Port: []int{520}, 124 | }, 125 | { 126 | Name: "sentinel", 127 | Payloads: []string{"7A0000000000"}, 128 | Port: []int{5093}, 129 | }, 130 | { 131 | Name: "sip", 132 | Payloads: []string{"4F5054494F4E53"}, 133 | Port: []int{5060}, 134 | }, 135 | { 136 | Name: "snmp", 137 | Payloads: []string{"302902010004067075626C6963A01C0204565ADC5D020100020100300E300C06082B060102010101000500", "302602010104067075626C6963A1190204DC63C29A020100020100300B300906052B060102010500", "303A020103300F02024A69020300FFE30401040201030410300E0400020100020100040004000400301204000400A00C020237F00201000201003000"}, 138 | Port: []int{161}, 139 | }, 140 | { 141 | Name: "ssdp", 142 | Payloads: []string{"4D2D534541524348202A20485454502F312E310D0A484F53543A3233392E3235352E3235352E3235303A313930300D0A53543A737364703A616C6C0D0A4D414E3A22737364703A646973636F766572220D0A0D0A"}, 143 | Port: []int{1900}, 144 | }, 145 | { 146 | Name: "tftp", 147 | Payloads: []string{"00012F61006E6574617363696900"}, 148 | Port: []int{69}, 149 | }, 150 | { 151 | Name: "ubiquiti", 152 | Payloads: []string{"01000000000000000000000000001F7FA366", "01000000", "02080000"}, 153 | Port: []int{10001}, 154 | }, 155 | { 156 | Name: "upnp", 157 | Payloads: []string{"4D2D534541524348202A20485454502F312E310D0A486F73743A3233392E3235352E3235352E3235303A313930300D0A53543A75706E703A726F6F746465766963650D0A4D616E3A22737364703A646973636F766572220D0A4D583A330D0A0D0A0D0A"}, 158 | Port: []int{1900}, 159 | }, 160 | { 161 | Name: "valve", 162 | Payloads: []string{"FFFFFFFF54536F7572636520456E67696E6520517565727900"}, 163 | Port: []int{27015}, 164 | }, 165 | { 166 | Name: "wdbrpc", 167 | Payloads: []string{"1A09FABA000000000000000255555555000000010000000100000000000000000000000000000000FFFF55120000003C00000001000000020000000000000000"}, 168 | Port: []int{17185}, 169 | }, 170 | { 171 | Name: "wsd", 172 | Payloads: []string{"3C3A2F3E0A", "3C3F786D6C2076657273696F6E3D22312E302220656E636F64696E673D227574662D38223F3E0A3C736F61703A456E76656C6F706520786D6C6E733A736F61703D22687474703A2F2F7777772E77332E6F72672F323030332F30352F736F61702D656E76656C6F70652220786D6C6E733A7773613D22687474703A2F2F736368656D61732E786D6C736F61702E6F72672F77732F323030342F30382F61646472657373696E672220786D6C6E733A7773643D22687474703A2F2F736368656D61732E786D6C736F61702E6F72672F77732F323030352F30342F646973636F766572792220786D6C6E733A777364703D22687474703A2F2F736368656D61732E786D6C736F61702E6F72672F77732F323030362F30322F64657670726F66223E0A3C736F61703A4865616465723E3C7773613A546F3E75726E3A736368656D61732D786D6C736F61702D6F72673A77733A323030353A30343A646973636F766572793C2F7773613A546F3E3C7773613A416374696F6E3E687474703A2F2F736368656D61732E786D6C736F61702E6F72672F77732F323030352F30342F646973636F766572792F50726F62653C2F7773613A416374696F6E3E3C7773613A4D65737361676549443E75726E3A757569643A63653034646164302D356432632D343032362D393134362D3161616266633165343131313C2F7773613A4D65737361676549443E3C2F736F61703A4865616465723E3C736F61703A426F64793E3C7773643A50726F62653E3C7773643A54797065733E777364703A4465766963653C2F7773643A54797065733E3C2F7773643A50726F62653E3C2F736F61703A426F64793E3C2F736F61703A456E76656C6F70653E0A"}, 173 | Port: []int{3702}, 174 | }, 175 | { 176 | Name: "xdmcp", 177 | Payloads: []string{"00010002000100"}, 178 | Port: []int{177}, 179 | }, 180 | { 181 | Name: "kerberos", 182 | Payloads: []string{"6A7A3078A103020105A20302010AA46C306AA00703050040000000A111300FA003020101A10830061B046E6D6170A2061B0474657374A3193017A003020102A110300E1B066B72627467741B0474657374A511180F32303232313131333231343530325AA7060204094A7681A80E300C020112020111020110020117"}, 183 | Port: []int{88}, 184 | }, 185 | { 186 | Name: "ike", 187 | Payloads: []string{"5b5e64c03e99b51100000000000000000110020000000000000001500000013400000001000000010000012801010008030000240101"}, 188 | Port: []int{500, 4500}, 189 | }, 190 | { 191 | Name: "radius", 192 | Payloads: []string{"0167005740b664dbf5d681b2adbd1769515118c8010773746576650212dbc6c4b758be14f005b3877c9e2fb6010406c0a8001c05060000007b50125f0f8647e8c89bd881364268fcd045324f0c0266000a017374657665"}, 193 | Port: []int{1645, 1812}, 194 | }, 195 | { 196 | Name: "dtls", 197 | Payloads: []string{"0d31323334353637385139393900", "16feff00000000000000000036", "0100002a000000000000002a", "fefd", "0000", "0002002f", "0100"}, 198 | Port: []int{80,443,853,3391,4433,4740,5349,5684,5868,6514,6636,8232,10161,10162,12346,12446,12546,12646,12746,12846,12946,13046}, 199 | }, 200 | } 201 | 202 | -------------------------------------------------------------------------------- /pkg/scan/scanner.go: -------------------------------------------------------------------------------- 1 | package scan 2 | 3 | import ( 4 | "bufio" 5 | "encoding/hex" 6 | "fmt" 7 | "log" 8 | "net" 9 | "strings" 10 | "time" 11 | 12 | "github.com/nullt3r/udpx/pkg/colors" 13 | "github.com/nullt3r/udpx/pkg/probes" 14 | ) 15 | 16 | type Scanner struct { 17 | Target string 18 | Probes []probes.Probe 19 | Arg_st int 20 | Arg_sp bool 21 | Channel chan Message 22 | } 23 | 24 | type Message struct { 25 | Address string `json:"address"` 26 | Hostname string `json:"hostname"` 27 | Port int `json:"port"` 28 | Service string `json:"service"` 29 | ResponseData []byte `json:"response_data"` 30 | Timestamp int64 `json:"timestamp"` 31 | } 32 | 33 | func (s Scanner) Run() { 34 | socketTimeout := time.Duration(s.Arg_st) * time.Millisecond 35 | target := s.Target 36 | 37 | // Check if input is a domain 38 | if net.ParseIP(target) == nil { 39 | // Resolve domain to IP 40 | ips, err := net.LookupIP(target) 41 | if err != nil { 42 | log.Printf("%s[!]%s Error resolving domain '%s': %s", colors.SetColor().Red, colors.SetColor().Reset, target, err) 43 | return 44 | } 45 | domain := target 46 | 47 | // Dial for each IP of domain 48 | for _, ip := range ips { 49 | ip := ip.String() 50 | // If IP is IPv6 51 | if strings.Contains(ip, ":") { 52 | ip = "[" + ip + "]" 53 | } 54 | for _, probe := range probes.Probes { 55 | for _, port := range probe.Port { 56 | func() { 57 | 58 | for _, payload := range probe.Payloads { 59 | recv_Data := make([]byte, 32) 60 | 61 | c, err := net.Dial("udp", fmt.Sprint(ip, ":", port)) 62 | 63 | if err != nil { 64 | log.Printf("%s[!]%s [%s] Error connecting to host '%s': %s", colors.SetColor().Red, colors.SetColor().Reset, probe.Name, ip, err) 65 | return 66 | } 67 | 68 | defer c.Close() 69 | 70 | Data, err := hex.DecodeString(payload) 71 | 72 | if err != nil { 73 | log.Fatalf("%s[!]%s Error in decoding payload. Problem probe: '%s'", colors.SetColor().Red, colors.SetColor().Reset, probe.Name) 74 | } 75 | 76 | _, err = c.Write([]byte(Data)) 77 | 78 | if err != nil { 79 | return 80 | } 81 | 82 | c.SetReadDeadline(time.Now().Add(socketTimeout)) 83 | 84 | recv_length, err := bufio.NewReader(c).Read(recv_Data) 85 | 86 | if err != nil { 87 | return 88 | } 89 | 90 | if recv_length != 0 { 91 | s.Channel <- Message{Address: ip, Hostname: domain, Port: port, Service: probe.Name, ResponseData: recv_Data} 92 | return 93 | } 94 | } 95 | }() 96 | } 97 | } 98 | } 99 | } else { 100 | // Dial for a single IP 101 | ip := target 102 | // If IP is IPv6 103 | if strings.Contains(ip, ":") { 104 | ip = "[" + ip + "]" 105 | } 106 | for _, probe := range probes.Probes { 107 | for _, port := range probe.Port { 108 | func() { 109 | for _, payload := range probe.Payloads { 110 | recv_Data := make([]byte, 32) 111 | 112 | now := time.Now() 113 | 114 | c, err := net.Dial("udp", fmt.Sprint(ip, ":", port)) 115 | 116 | if err != nil { 117 | log.Printf("%s[!]%s [%s] Error connecting to host '%s': %s", colors.SetColor().Red, colors.SetColor().Reset, probe.Name, ip, err) 118 | return 119 | } 120 | 121 | defer c.Close() 122 | 123 | Data, err := hex.DecodeString(payload) 124 | 125 | if err != nil { 126 | log.Fatalf("%s[!]%s Error in decoding payload. Problem probe: '%s'", colors.SetColor().Red, colors.SetColor().Reset, probe.Name) 127 | } 128 | 129 | _, err = c.Write([]byte(Data)) 130 | 131 | if err != nil { 132 | return 133 | } 134 | 135 | c.SetReadDeadline(time.Now().Add(socketTimeout)) 136 | 137 | recv_length, err := bufio.NewReader(c).Read(recv_Data) 138 | 139 | if err != nil { 140 | return 141 | } 142 | 143 | if recv_length != 0 { 144 | s.Channel <- Message{Address: ip, Port: port, Service: probe.Name, ResponseData: recv_Data, Timestamp: now.Unix()} 145 | return 146 | } 147 | } 148 | }() 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /pkg/utils/helpers.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "os" 8 | ) 9 | 10 | func EscapeByteArray(message []byte) []byte { 11 | var result []byte 12 | for _, b := range message { 13 | if b > 127 || b == '"' || b == '\n' || b == '\t' || (b <= ' ' && b >= 0) { 14 | result = append(result, []byte(fmt.Sprintf("\\x%02x", b))...) 15 | } else { 16 | result = append(result, b) 17 | } 18 | } 19 | return result 20 | } 21 | 22 | func IpsFromCidr(cidr string) ([]string, error) { 23 | inc := func(ip net.IP) { 24 | for j := len(ip) - 1; j >= 0; j-- { 25 | ip[j]++ 26 | if ip[j] > 0 { 27 | break 28 | } 29 | } 30 | } 31 | 32 | ip, ipnet, err := net.ParseCIDR(cidr) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | var ips []string 38 | 39 | for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) { 40 | ips = append(ips, ip.String()) 41 | } 42 | 43 | // If mask is /32 or /31 44 | //if len(ips) <= 2 { 45 | // return ips, nil 46 | //} 47 | 48 | // remove network address and broadcast address 49 | //return ips[1 : len(ips)-1], nil 50 | 51 | return ips, nil 52 | } 53 | 54 | func ReadFile(path string) ([]string, error) { 55 | file, err := os.Open(path) 56 | if err != nil { 57 | return nil, err 58 | } 59 | defer file.Close() 60 | 61 | var lines []string 62 | scanner := bufio.NewScanner(file) 63 | for scanner.Scan() { 64 | lines = append(lines, scanner.Text()) 65 | } 66 | return lines, scanner.Err() 67 | } 68 | 69 | func WriteChannel(lines chan string, path string) error { 70 | file, err := os.Create(path) 71 | if err != nil { 72 | return err 73 | } 74 | defer file.Close() 75 | 76 | w := bufio.NewWriter(file) 77 | for line := range lines { 78 | fmt.Fprintln(w, line) 79 | } 80 | return w.Flush() 81 | } 82 | 83 | 84 | func Deduplicate(stringSlice []string) []string { 85 | keys := make(map[string]bool) 86 | list := []string{} 87 | 88 | // If the key(values of the slice) is not equal 89 | // to the already present value in new slice (list) 90 | // then we append it. else we jump on another element. 91 | for _, entry := range stringSlice { 92 | if _, value := keys[entry]; !value { 93 | keys[entry] = true 94 | list = append(list, entry) 95 | } 96 | } 97 | return list 98 | } 99 | -------------------------------------------------------------------------------- /pkg/utils/opts_parser.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/nullt3r/udpx/pkg/probes" 8 | ) 9 | 10 | type Options struct { 11 | Arg_t string 12 | Arg_tf string 13 | Arg_o string 14 | Arg_c int 15 | Arg_nr bool 16 | Arg_st int 17 | Arg_sp bool 18 | Arg_s string 19 | } 20 | 21 | func ParseOptions() *Options { 22 | opts := &Options{} 23 | flag.StringVar(&opts.Arg_t, "t", "", "IP/CIDR to scan") 24 | flag.StringVar(&opts.Arg_tf, "tf", "", "File containing IPs/CIDRs to scan") 25 | flag.StringVar(&opts.Arg_o, "o", "", "Output file to write results") 26 | flag.StringVar(&opts.Arg_s, "s", "", fmt.Sprintf("Scan only for a specific service, one of: %s", probes.GetProbeNames())) 27 | flag.IntVar(&opts.Arg_c, "c", 32, "Maximum number of concurrent connections") 28 | flag.BoolVar(&opts.Arg_nr, "nr", false, "Do not randomize addresses") 29 | flag.IntVar(&opts.Arg_st, "w", 500, "Maximum time to wait for a response (socket timeout) in ms") 30 | flag.BoolVar(&opts.Arg_sp, "sp", false, "Show received packets (only first 32 bytes)") 31 | 32 | flag.Parse() 33 | 34 | return opts 35 | } 36 | -------------------------------------------------------------------------------- /screenshots/showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullt3r/udpx/7e6ea01359d6f154f35e42bd2c44c26df86d28bc/screenshots/showcase.png -------------------------------------------------------------------------------- /screenshots/udpx_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullt3r/udpx/7e6ea01359d6f154f35e42bd2c44c26df86d28bc/screenshots/udpx_logo.png --------------------------------------------------------------------------------