Many moons ago I worked in a place that had an "incident" involving a network worm. It would hop from system to system, leaving behind itself as a payload before trying to find another machine to try logging into and repeating the spreading process.
I was reminded of this incident while troubleshooting a network issue for someone. What if something were on this user's network, poking around for access? How would she know?
What if I created a small application she could run on her Mac that would act as a kind of dumb honeypot; accepting connections and logging what the remote machine sent?
That's what set me on the path of using Go to create a simple program to do just that. I was vaguely familiar with the language and the resulting Go executable is statically linked; I can just send her the executable and it'll run, no need to make sure a set of libraries or framework was up to date before the program would run properly. And Go is also pretty fast, far faster than Python or Ruby.
I decided to chronicle the process of creating this application; perhaps it would be useful to a novice programmer wondering if anyone else created small programs with a similar process. I know there are times I would have found that kind of information useful. So...I'm leaving this here.
The first step was figuring out what I wanted the program to do. This was going to be a simple, small application. I wanted it to be easy enough that a kind of non-technical person could easily be talked through the steps of running the application. I wanted the program to listen to all the service ports, the ones commonly probed for vulnerabilities, and accept TCP connections and then log whatever was sent to those ports (passwords or commands, for example) to a text file.
I started off by making a simple "skeleton" of the application, modeling certain behaviors I could use as the bones of the program.
package main import "fmt" import "strconv" import "time" func main() { var i uint16 for i = 1; i < 1025; i++ { go ListenToPort(i) } // This sleep and print is just a debugging/testing thing. The for loop using // "go ListenToPort" is asynchronous...so we have to wait or it just finishes. time.Sleep(2 * time.Second) fmt.Println("Done!\n") } func ListenToPort(port uint16) { fmt.Println("Listening to port " + strconv.FormatInt(int64(port), 10)) }
Package main -> this tells the compiler this is the "main" application file with the main() function, instead of a package that is treated like a library to be compiled in the workspace pkg directory.
Import -> fmt, strconv and time are built-in libraries to the language. Fmt handles string handling and formatting, strconv handles converting strings to and from other types, and time lets us play with sleep.
main -> This function consists of just a for loop to run from 1 to 1024 and increment by one each time. Each iteration of the loop spawns a goroutine call to ListenToPort with the current iterator value, which later will be used to listen to the network ports. For now it just models the calling behavior validating that my looping logic will work in the earliest stages.
The "go" keyword means "this is to be run as a goroutine without returning a value or even waiting for any feedback." The application will spit out 1,024 asynchronous routines like cards shooting from the hands of a magician playing 1,024 card pickup before finishing the loop. The timing of these routines, separately scheduled, will finish at unpredictable moments later and may not even finish in order.
time.Sleep(2 * time.Second) and the fmt.Println-> Remember how I said the "go" keyword spawns go processes without regard for returning values or waiting for things to complete? Yeah, without this "let's wait" thing the system will almost immediately spit out "Done!" This means the application will finish before the goroutines have a chance to do anything.
This sleeping pause just gives the application some time to show some output. This is a quick-and-dirty thing...don't judge me! I'm simply forcing main() to give some breathing room to validate my loop logic.
The Main() function is closed up, and I then define a placeholder for a useful ListenToPort function with ListenToPort(port uint16); func means this is a function, ListenToPort is the name of that function, and "port" is the name of the variable I want to work with and uint16 means unsigned 16 bit integer since that should be enough to hold the number of ports I could want to iterate through.
The Println call is kind of complicated for the purposes of a simple test taking shape, but basically it is saying "Print 'Listening to port" and append the port number passed to me." Because it's an integer, I have to convert the uint16 into a string or a type error will be spat out by the compiler.
There doesn't seem to be a straightforward way of changing the variable into a string but FormatInt will do the conversion if the variable is a 64 bit int; so I cast port to a 64 bit integer using int64. The 10 is because FormatInt takes a "base number" argument and I'm using base 10 counting.
This source code compiles and spits out numbers...almost in order...to the command prompt, and after three seconds pass it says, "Done!"
Pretty simple, eh?
I'll end part one here...
No comments:
Post a Comment