Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsteele committed Apr 25, 2014
1 parent af6895d commit c26e2a1
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 0 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,36 @@ cookiescan
==========

TCP port scanning through SYN flood protected networks

##Download##
Binary packages for Linux and OS X are available [here](http://github.com/tomsteele/cookiescan/releases/latest). If you need to build for another operating system see the go documentation or get in contact with me and I'll see what I can do.

##Install##
After installing libpcap-dev:
```
$ unzip cookiescan*.zip
$ ./cookiescan
```

##Usage##
```
$ cookiescan -h
Usage:
cookiescan [options] <target>
cookiescan -h | --help
cookiescan -v | --version
Required Arguments:
target: IP Address or Hostname
Options:
-h --help Show this message.
-v --version Show version.
-p <port ranges> Ex: -p 22; -p 1-65535, -p 80,443. [default: 1-1024]
-g <int> Amount of goroutines to spread connection attempts across. [default: 1000]
-c <int> Minimum confidence level to flag port as open. [default: 1]
-i <interface> Network interface to listen on.
-t <timeout> Timeout in Milliseconds to wait for a connection. [default: 400]
-j Output JSON.
```
56 changes: 56 additions & 0 deletions explode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"errors"
"strconv"
"strings"
)

// Turns a string of ports separated by '-' or ',' and returns a slice of Ints.
func explode(s string) ([]int, error) {
const errmsg = "Invalid port specification"
ports := []int{}
switch {
case strings.Contains(s, "-"):
sp := strings.Split(s, "-")
if len(sp) != 2 {
return ports, errors.New(errmsg)
}
start, err := strconv.Atoi(sp[0])
if err != nil {
return ports, errors.New(errmsg)
}
end, err := strconv.Atoi(sp[1])
if err != nil {
return ports, errors.New(errmsg)
}
if start > end || start < 1 || end > 65535 {
return ports, errors.New(errmsg)
}
for ; start <= end; start++ {
ports = append(ports, start)
}
case strings.Contains(s, ","):
sp := strings.Split(s, ",")
for _, p := range sp {
i, err := strconv.Atoi(p)
if err != nil {
return ports, errors.New(errmsg)
}
if i < 1 || i > 65535 {
return ports, errors.New(errmsg)
}
ports = append(ports, i)
}
default:
i, err := strconv.Atoi(s)
if err != nil {
return ports, errors.New(errmsg)
}
if i < 1 || i > 65535 {
return ports, errors.New(errmsg)
}
ports = append(ports, i)
}
return ports, nil
}
150 changes: 150 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package main

import (
"encoding/json"
"fmt"
"github.com/docopt/docopt.go"
"github.com/miekg/pcap"
"github.com/tomsteele/cookiescan/result"
"log"
"net"
"os"
"sort"
"strconv"
"text/tabwriter"
"time"
)

type empty struct{}

func main() {
args, err := docopt.Parse(usage, nil, true, "cookiescan 0.1", false)
if err != nil {
log.Fatal("Error parsing usage. Error: ", err.Error())
}
host := args["<target>"].(string)
ports, err := explode(args["-p"].(string))
if err != nil {
log.Fatal(err.Error())
}

var ip string
if net.ParseIP(host) == nil {
ips, err := net.LookupIP(host)
if err != nil {
log.Fatal("Could not resolve hostname. Error: ", err.Error())
}
ip = ips[0].String()
} else {
ip = host
}

minc, err := strconv.Atoi(args["-c"].(string))
if err != nil {
log.Fatal("Invalid argument for -c.")
}
concurrency, err := strconv.Atoi(args["-g"].(string))
if err != nil {
log.Fatal("Invalid argument for -g.")
}
ti, err := strconv.Atoi(args["-t"].(string))
if err != nil {
log.Fatal("Invalid argument for -t.")
}
timeout := time.Duration(ti) * time.Millisecond

filter := fmt.Sprintf("src %s and ((tcp[13] == 0x11) or (tcp[13] == 0x10) or (tcp[13] == 0x18))", ip)
var device string
if args["-i"] != nil {
device = args["-i"].(string)
}
if device == "" {
devs, err := pcap.FindAllDevs()
if err != "" {
log.Fatal("Error finding interfaces. Error: ", err)
}
if len(devs) == 0 {
log.Fatal("No interfaces found. Are you not running as root?")
}
device = devs[0].Name
}

h, err := pcap.OpenLive(device, int32(320), true, 500)
if err != nil {
log.Fatal(err.Error())
}
if err = h.SetFilter(filter); err != nil {
log.Fatal(err.Error())
}

res := make(map[uint16][]string)
tasks := make(chan int, concurrency)
track := make(chan empty)

go func() {
for pkt, r := h.NextEx(); r >= 0; pkt, r = h.NextEx() {
select {
case <-track:
break
default:
if r == 0 {
continue
}
pkt.Decode()
t := pkt.Headers[1].(*pcap.Tcphdr)
f := t.FlagsString()
res[t.SrcPort] = append(res[t.SrcPort], f)
}
}
}()

for i := 0; i < concurrency; i++ {
go func() {
for p := range tasks {
c, err := net.DialTimeout("tcp", ip+":"+strconv.Itoa(p), timeout)
if err != nil {
continue
}
c.Close()
}
}()
}

log.Printf("Staring scan of %s.\n", ip)
for _, p := range ports {
tasks <- p
}
close(tasks)
time.Sleep(time.Duration(2 * time.Second))
track <- empty{}
h.Close()
log.Println("Scan complete.")

services, _ := buildServices()
results := cookiescan.Result{Host: ip}
for k, v := range res {
conf := len(v)
if conf < minc {
continue
}
service := "unknown"
if s, ok := services[int(k)]; ok {
service = s
}
p := cookiescan.Port{Port: int(k), Service: service, State: "open", Confidence: conf, Reason: v}
results.Ports = append(results.Ports, p)
}
sort.Sort(results.Ports)

if args["-j"].(bool) {
j, _ := json.MarshalIndent(results, "", " ")
fmt.Println(string(j))
} else {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 4, ' ', 0)
fmt.Fprintln(w, "Port\tState\tService\tConfidence\tReason")
for _, p := range results.Ports {
fmt.Fprintf(w, "%d\t%s\t%s\t%d\t%s\n", p.Port, p.State, p.Service, p.Confidence, p.Reason)
}
w.Flush()
}
}
20 changes: 20 additions & 0 deletions result/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cookiescan

type Result struct {
Host string `json:"host"`
Ports Ports `json:"ports"`
}

type Port struct {
Port int `json:"port"`
Service string `json:"service"`
State string `json:"state"`
Confidence int `json:"confidence"`
Reason []string `json:"reason"`
}

type Ports []Port

func (p Ports) Len() int { return len(p) }
func (p Ports) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Ports) Less(i, j int) bool { return p[i].Port < p[j].Port }
34 changes: 34 additions & 0 deletions service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"bufio"
"os"
"regexp"
"strconv"
)

// Reads '/etc/services' and creates a port[service] lookup table
func buildServices() (map[int]string, error) {
services := make(map[int]string)
f, err := os.Open("/etc/services")
if err != nil {
return services, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
re := regexp.MustCompile("([^\\s]+)\\s+([0-9]+)/(tcp)")
for scanner.Scan() {
result := re.FindStringSubmatch(scanner.Text())
if len(result) == 4 {
port, err := strconv.Atoi(result[2])
if err != nil {
continue
}
services[port] = result[1]
}
}
if err := scanner.Err(); err != nil {
return services, err
}
return services, nil
}
23 changes: 23 additions & 0 deletions usage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

const usage = `
Usage:
cookiescan [options] <target>
cookiescan -h | --help
cookiescan -v | --version
Required Arguments:
target: IP Address or Hostname
Options:
-h --help Show this message.
-v --version Show version.
-p <port ranges> Ex: -p 22; -p 1-65535, -p 80,443. [default: 1-1024]
-g <int> Amount of goroutines to spread connection attempts across. [default: 1000]
-c <int> Minimum confidence level to flag port as open. [default: 1]
-i <interface> Network interface to listen on.
-t <timeout> Timeout in Milliseconds to wait for a connection. [default: 400]
-j Output JSON.
`

0 comments on commit c26e2a1

Please sign in to comment.