Added experimental banner grabber implemented in Go.

(Caution: This is my first attempt at writing Go.)
This commit is contained in:
Alex Halderman 2013-08-29 15:00:34 -04:00
parent 853524c2ae
commit aa5580ff1a
3 changed files with 199 additions and 0 deletions

View File

@ -0,0 +1,36 @@
TCP banner grabber implemented in Go (experimental)
======
This program will make TCP connections to IP addresses provide on
stdin, optionally send them a short message, and wait for their
responses. Each response is printed to stdout, along with the
responding host's IP address. Status messages appear on stderr.
USING:
-----
go build banner.go
zmap -p 80 -N 1000 -o - | ./banner -port 80 -concurrent 100 -data http-req > banners.out
OPTIONS:
-----
-concurrent Number of connections that can be going on at once.
This, combined with timeouts, will decide the maximum
rate at which banners are grabbed. If this value
is set higher than 1000, you should use
`ulimit -SHn 1000000` and `ulimit -SSn 1000000` to
avoid running out of file descriptors (typically capped
at 1024). Default: 100.
-port The port which to connect to hosts on. Default: 80.
-timeout Connection timeout (seconds). Give up on a host if grabber
has not completed by this time. Default: 4 seconds.
-format Format to output banner responses. One of 'hex', 'ascii',
or 'base64'. Default: ascii.
-d, --data Optional data file. This data will be sent to each host
upon successful connection. Occurrences of the
string '%s' will be replaced with the current
target host's address.

View File

@ -0,0 +1,160 @@
/*
TCP banner grabber, implemented in go
This program will make TCP connections to IP addresses provide on
stdin, optionally send them a short message, and wait for their
responses. Each response is printed to stdout, along with the
responding host's IP address. Status messages appear on stderr.
*/
package main
/*
* banner.go Copyright 2013 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import (
"bufio"
"encoding/base64"
"encoding/hex"
"flag"
"fmt"
"io"
"net"
"os"
"strings"
"syscall"
"time"
)
var (
nConnectFlag = flag.Int("concurrent", 100, "Number of concurrent connections")
portFlag = flag.String("port", "80", "Destination port")
formatFlag = flag.String("format", "ascii", "Output format for responses ('ascii', 'hex', or 'base64')")
timeoutFlag = flag.Int("timeout", 4, "Seconds to wait for each host to respond")
dataFileFlag = flag.String("data", "", "File containing message to send to responsive hosts ('%s' will be replaced with host IP)")
)
var messageData = make([]byte, 0) // data read from file specified with dataFile flag
// Before running main, parse flags and load message data, if applicable
func init() {
flag.Parse()
if *dataFileFlag != "" {
fi, err := os.Open(*dataFileFlag)
if err != nil {
panic(err)
}
buf := make([]byte, 1024)
n, err := fi.Read(buf)
messageData = buf[0:n]
if err != nil && err != io.EOF {
panic(err)
}
fi.Close()
}
// Increase file descriptor limit
rlimit := syscall.Rlimit{Max: uint64(*nConnectFlag + 4), Cur: uint64(*nConnectFlag + 4)}
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
fmt.Fprintf(os.Stderr, "Error setting rlimit: %s", err)
}
}
type resultStruct struct {
addr string // address of remote host
data []byte // data returned from the host, if successful
err error // error, if any
}
// Read addresses from addrChan and grab banners from these hosts.
// Sends resultStructs to resultChan. Writes to doneChan when complete.
func grabber(addrChan chan string, resultChan chan resultStruct, doneChan chan int) {
for addr := range addrChan {
deadline := time.Now().Add(time.Duration(*timeoutFlag) * time.Second)
dialer := net.Dialer{Deadline: deadline}
conn, err := dialer.Dial("tcp", net.JoinHostPort(addr, *portFlag))
if err != nil {
resultChan <- resultStruct{addr, nil, err}
continue
}
conn.SetDeadline(deadline)
if len(messageData) > 0 {
s := strings.Replace(string(messageData), "%s", addr, -1)
if _, err := conn.Write([]byte(s)); err != nil {
conn.Close()
resultChan <- resultStruct{addr, nil, err}
continue
}
}
var buf [1024]byte
n, err := conn.Read(buf[:])
conn.Close()
if err != nil && (err != io.EOF || n == 0) {
resultChan <- resultStruct{addr, nil, err}
continue
}
resultChan <- resultStruct{addr, buf[0:n], nil}
}
doneChan <- 1
}
// Read resultStructs from resultChan, print output, and maintain
// status counters. Writes to doneChan when complete.
func output(resultChan chan resultStruct, doneChan chan int) {
ok, timeout, error := 0, 0, 0
for result := range resultChan {
if result.err == nil {
switch *formatFlag {
case "hex":
fmt.Printf("%s: %s\n", result.addr,
hex.EncodeToString(result.data))
case "base64":
fmt.Printf("%s: %s\n", result.addr,
base64.StdEncoding.EncodeToString(result.data))
default:
fmt.Printf("%s: %s\n", result.addr,
string(result.data))
}
ok++
} else if nerr, ok := result.err.(net.Error); ok && nerr.Timeout() {
fmt.Fprintf(os.Stderr, "%s: Timeout\n", result.addr)
timeout++
} else {
fmt.Fprintf(os.Stderr, "%s: Error %s\n", result.addr, result.err)
error++
}
}
fmt.Fprintf(os.Stderr, "Complete (OK=%d, timeout=%d, error=%d)\n",
ok, timeout, error)
doneChan <- 1
}
func main() {
addrChan := make(chan string, *nConnectFlag) // pass addresses to grabbers
resultChan := make(chan resultStruct, *nConnectFlag) // grabbers send results to output
doneChan := make(chan int, *nConnectFlag) // let grabbers signal completion
// Start grabbers and output thread
go output(resultChan, doneChan)
for i := 0; i < *nConnectFlag; i++ {
go grabber(addrChan, resultChan, doneChan)
}
// Read addresses from stdin and pass to grabbers
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
addrChan <- scanner.Text()
}
close(addrChan)
// Wait for completion
for i := 0; i < *nConnectFlag; i++ {
<-doneChan
}
close(resultChan)
<-doneChan
}

View File

@ -0,0 +1,3 @@
GET / HTTP/1.1
Host: %s