Extending Dionaea

Even though there is little action on tcp/3306 I choose MySQL as a protocol to show how to extend dionaea.
Over the next lines, we'll implement parts of the MySQL wire protocol for a MySQL service using scapy.

MySQL

First, get the protocol documentation 1), in most cases the wire documentation is written sloppy and overall inaccurate and hard to understand but it is the first to start with. After reading the documentation, grab pcaps and see what wireshark makes of it, for MySQL there is a pcap in the wireshark wiki 2). Now you may have an idea what the protocol is about and already identified interesting values.

Protocol Basics

In case of MySQL there are some constants defined, as constants always help, we start with defining the constants for later use in scapy.

CLIENT_LONG_PASSWORD		= 0x00001	# new more secure passwords 
CLIENT_FOUND_ROWS			= 0x00002	# Found instead of affected rows 
CLIENT_LONG_FLAG			= 0x00004	# Get all column flags 
CLIENT_CONNECT_WITH_DB		= 0x00008	# One can specify db on connect 
CLIENT_NO_SCHEMA			= 0x00010	# Don't allow database.table.column 
CLIENT_COMPRESS				= 0x00020	# Can use compression protocol 
CLIENT_ODBC					= 0x00040	# Odbc client 
CLIENT_LOCAL_FILES			= 0x00080	# Can use LOAD DATA LOCAL 
CLIENT_IGNORE_SPACE			= 0x00100	# Ignore spaces before '(' 
CLIENT_PROTOCOL_41			= 0x00200	# New 4.1 protocol 
CLIENT_INTERACTIVE			= 0x00400	# This is an interactive client 
CLIENT_SSL					= 0x00800	# Switch to SSL after handshake 
CLIENT_IGNORE_SIGPIPE		= 0x01000	# IGNORE sigpipes 
CLIENT_TRANSACTIONS			= 0x02000	# Client knows about transactions 
CLIENT_RESERVED				= 0x04000	# Old flag for 4.1 protocol  
CLIENT_SECURE_CONNECTION	= 0x08000	# New 4.1 authentication 
 
MySQL_Capabilities = {
	CLIENT_LONG_PASSWORD		: "CLIENT_LONG_PASSWORD",
	CLIENT_FOUND_ROWS			: "CLIENT_FOUND_ROWS",
	CLIENT_LONG_FLAG			: "CLIENT_LONG_FLAG",
	CLIENT_CONNECT_WITH_DB		: "CLIENT_CONNECT_WITH_DB",
	CLIENT_NO_SCHEMA			: "CLIENT_NO_SCHEMA",
	CLIENT_COMPRESS				: "CLIENT_COMPRESS",
	CLIENT_ODBC					: "CLIENT_ODBC",
	CLIENT_LOCAL_FILES			: "CLIENT_LOCAL_FILES",
	CLIENT_IGNORE_SPACE			: "CLIENT_IGNORE_SPACE",
	CLIENT_PROTOCOL_41			: "CLIENT_PROTOCOL_41",
	CLIENT_INTERACTIVE			: "CLIENT_INTERACTIVE",
	CLIENT_SSL					: "CLIENT_SSL",
	CLIENT_IGNORE_SIGPIPE		: "CLIENT_IGNORE_SIGPIPE",
	CLIENT_TRANSACTIONS			: "CLIENT_TRANSACTIONS",
	CLIENT_RESERVED				: "CLIENT_RESERVED",
	CLIENT_SECURE_CONNECTION	: "CLIENT_SECURE_CONNECTION",
}
 
CLIENT_MULTI_STATEMENTS		= 0x01	# Enable/disable multi-stmt support 
CLIENT_MULTI_RESULTS		= 0x02	# Enable/disable multi-results 
 
MySQL_Extended_Capabilities = {
	CLIENT_MULTI_STATEMENTS		: "CLIENT_MULTI_STATEMENTS",
	CLIENT_MULTI_RESULTS		: "CLIENT_MULTI_RESULTS"
}
 
COM_SLEEP				= 0x00	# (none, this is an internal thread state)
COM_QUIT				= 0x01	# mysql_close
COM_INIT_DB				= 0x02	# mysql_select_db 
COM_QUERY				= 0x03	# mysql_real_query
COM_FIELD_LIST			= 0x04	# mysql_list_fields
COM_CREATE_DB			= 0x05	# mysql_create_db (deprecated)
COM_DROP_DB				= 0x06	# mysql_drop_db (deprecated)
COM_REFRESH				= 0x07	# mysql_refresh
COM_SHUTDOWN			= 0x08	# mysql_shutdown
COM_STATISTICS			= 0x09	# mysql_stat
COM_PROCESS_INFO		= 0x0a	# mysql_list_processes
COM_CONNECT				= 0x0b	# (none, this is an internal thread state)
COM_PROCESS_KILL		= 0x0c	# mysql_kill
COM_DEBUG				= 0x0d	# mysql_dump_debug_info
COM_PING				= 0x0e	# mysql_ping
COM_TIME				= 0x0f	# (none, this is an internal thread state)
COM_DELAYED_INSERT		= 0x10	# (none, this is an internal thread state)
COM_CHANGE_USER			= 0x11	# mysql_change_user
COM_BINLOG_DUMP			= 0x12	# sent by the slave IO thread to request a binlog
COM_TABLE_DUMP			= 0x13	# LOAD TABLE ... FROM MASTER (deprecated)
COM_CONNECT_OUT			= 0x14	# (none, this is an internal thread state)
COM_REGISTER_SLAVE		= 0x15	# sent by the slave to register with the master (optional)
COM_STMT_PREPARE		= 0x16	# mysql_stmt_prepare
COM_STMT_EXECUTE		= 0x17	# mysql_stmt_execute
COM_STMT_SEND_LONG_DATA	= 0x18	# mysql_stmt_send_long_data
COM_STMT_CLOSE			= 0x19	# mysql_stmt_close
COM_STMT_RESET			= 0x1a	# mysql_stmt_reset
COM_SET_OPTION			= 0x1b	# mysql_set_server_option
COM_STMT_FETCH			= 0x1c	# mysql_stmt_fetch
 
MySQL_Commands	=	{
	COM_SLEEP				: "COM_SLEEP",
	COM_QUIT				: "COM_QUIT",
	COM_INIT_DB				: "COM_INIT_DB",
	COM_QUERY				: "COM_QUERY",
	COM_FIELD_LIST			: "COM_FIELD_LIST",
	COM_CREATE_DB			: "COM_CREATE_DB",
	COM_DROP_DB				: "COM_DROP_DB",
	COM_REFRESH				: "COM_REFRESH",
	COM_SHUTDOWN			: "COM_SHUTDOWN",
	COM_STATISTICS			: "COM_STATISTICS",
	COM_PROCESS_INFO		: "COM_PROCESS_INFO",
	COM_CONNECT				: "COM_CONNECT",
	COM_PROCESS_KILL		: "COM_PROCESS_KILL",
	COM_DEBUG				: "COM_DEBUG",
	COM_PING				: "COM_PING",
	COM_TIME				: "COM_TIME",
	COM_DELAYED_INSERT		: "COM_DELAYED_INSERT",
	COM_CHANGE_USER			: "COM_CHANGE_USER",
	COM_BINLOG_DUMP			: "COM_BINLOG_DUMP",
	COM_TABLE_DUMP			: "COM_TABLE_DUMP",
	COM_CONNECT_OUT			: "COM_CONNECT_OUT",
	COM_REGISTER_SLAVE		: "COM_REGISTER_SLAVE",
	COM_STMT_PREPARE		: "COM_STMT_PREPARE",
	COM_STMT_EXECUTE		: "COM_STMT_EXECUTE",
	COM_STMT_SEND_LONG_DATA	: "COM_STMT_SEND_LONG_DATA",
	COM_STMT_CLOSE			: "COM_STMT_CLOSE",
	COM_STMT_RESET			: "COM_STMT_RESET",
	COM_SET_OPTION			: "COM_SET_OPTION",
	COM_STMT_FETCH			: "COM_STMT_FETCH"
}

The documentation mentions some SERVER_STATUS_ flags, which are not part of the documentation, so we look into the source of the current version of the mysql daemon to gather some more constants.

# mysql-5.5.12/include/mysql_com.h
SERVER_STATUS_IN_TRANS				= 0x0001	# 
SERVER_STATUS_AUTOCOMMIT			= 0x0002	# Server in auto_commit mode
SERVER_MORE_RESULTS_EXISTS			= 0x0008	# Multi query - next query exists
SERVER_QUERY_NO_GOOD_INDEX_USED		= 0x0010	# 
SERVER_QUERY_NO_INDEX_USED			= 0x0020	# 
SERVER_STATUS_CURSOR_EXISTS			= 0x0040	# 
SERVER_STATUS_LAST_ROW_SENT			= 0x0080	# 
SERVER_STATUS_DB_DROPPED			= 0x0100	# A database was dropped
SERVER_STATUS_NO_BACKSLASH_ESCAPES	= 0x0200	# 
SERVER_STATUS_METADATA_CHANGED		= 0x0400	# 
SERVER_QUERY_WAS_SLOW				= 0x0800	# 
SERVER_PS_OUT_PARAMS				= 0x1000	# 
 
MySQL_Server_Status = {
	SERVER_STATUS_IN_TRANS				: "SERVER_STATUS_IN_TRANS",
	SERVER_STATUS_AUTOCOMMIT			: "SERVER_STATUS_AUTOCOMMIT",
	SERVER_MORE_RESULTS_EXISTS			: "SERVER_MORE_RESULTS_EXISTS",
	SERVER_QUERY_NO_GOOD_INDEX_USED		: "SERVER_QUERY_NO_GOOD_INDEX_USED",
	SERVER_QUERY_NO_INDEX_USED			: "SERVER_QUERY_NO_INDEX_USED",
	SERVER_STATUS_CURSOR_EXISTS			: "SERVER_STATUS_CURSOR_EXISTS",
	SERVER_STATUS_LAST_ROW_SENT			: "SERVER_STATUS_LAST_ROW_SENT",
	SERVER_STATUS_DB_DROPPED			: "SERVER_STATUS_DB_DROPPED",
	SERVER_STATUS_NO_BACKSLASH_ESCAPES	: "SERVER_STATUS_NO_BACKSLASH_ESCAPES",
	SERVER_STATUS_METADATA_CHANGED		: "SERVER_STATUS_METADATA_CHANGED",
	SERVER_QUERY_WAS_SLOW				: "SERVER_QUERY_WAS_SLOW",
	SERVER_PS_OUT_PARAMS				: "SERVER_PS_OUT_PARAMS"
}

The documentation mentions Elements3) which are Null-Terminated Strings or Length Coded Binary and Length Coded Strings.
We have to create scapy Fields for those.

class LengthCodedIntField(IntField):
	def __init__(self, name, default):
		Field.__init__(self,name,default,fmt="H")
	def i2len(self, pkt, i):
		return len(self.i2m(pkt,i))
	def i2m(self, pkt, y):
		if y is None:
			y = 0
		l = b''
		if y < 250:
			l = struct.pack("<B", y)
		elif y < 2**16:
			l = struct.pack("<BH", 252, y)
		elif y < 2**32:
			l = struct.pack("<BI", 253, y)
		else:
			l = struct.pack("<BQ", 254, y)
		return l
	def m2i(self, pkt, x):
		(l,o,s) = self._los(x)
		return l
	def addfield(self, pkt, s, val):
		m = self.i2m(pkt, val)
		return s+m
	def getfield(self, pkt, d):
		(l,o,s) = self._los(d)
		return d[s+o:],self.m2i(pkt, d)
	def size(self, pkt, val):
		return len(self.i2m(pkt, val))
	def _los(self, d):
		l = d[0]
		o = 1
		s = 1
		if l<=250 or l == 251:
			o = 0
		elif l == 252:
			s = 2
			(l,) = struct.unpack("<H", d[o:o+s])
		elif l == 253:
			s = 4
			(l,) = struct.unpack("<I", d[o:o+s])
		elif l == 254:
			s = 8
			(l,) = struct.unpack("<Q", d[o:o+s])
		return (l,o,s)
 
class LengthCodedBinaryField(StrField):
	def __init__(self, name, default):
		Field.__init__(self,name,default,fmt="H")
	def i2len(self, pkt, i):
		return len(self.i2m(pkt,i))
	def i2m(self, pkt, x):
		if x is None:
			y = None
		elif type(x) is str:
			x = x.encode('ascii')
		elif type(x) is not bytes:
			x = str(x).encode('ascii')
		y=len(x)
		l = b''
		if y is None:
			l = struct.pack("<B", 251)
			x = b''
		elif y > 0 and y < 250:
			l = struct.pack("<B", y)
		elif y < 2**16:
			l = struct.pack("<BH", 252, y)
		elif y < 2**32:
			l = struct.pack("<BI", 253, y)
		else:
			l = struct.pack("<BQ", 254, y)
		return l+x
	def m2i(self, pkt, x):
		(l,o,s) = self._los(x)
		return x[o+s:o+s+l]
	def addfield(self, pkt, s, val):
		m = self.i2m(pkt, val)
		return s+m
	def getfield(self, pkt, d):
		(l,o,s) = self._los(d)
		return d[s+o+l:],self.m2i(pkt, d)
	def size(self, pkt, val):
		return len(self.i2m(pkt, val))
	def _los(self, d):
		l = d[0]
		o = 1
		s = 1
		if l<=250 or l == 251:
			o = 0
		elif l == 252:
			s = 2
			(l,) = struct.unpack("<H", d[o:o+s])
		elif l == 253:
			s = 4
			(l,) = struct.unpack("<I", d[o:o+s])
		elif l == 254:
			s = 8
			(l,) = struct.unpack("<Q", d[o:o+s])
		return (l,o,s)

Now, we look on the first MySQL packets.
Every packet is prefixed by a Packet Header in MySQL, consisting of the Length and the Number of the packet. As the Length is a 3 Bytes integer, we have to define another scapy Field type - the Int24Field.

class Int24Field(IntField):
	def __init__(self, name, default):
		IntField.__init__(self,name,default)
	def i2len(self, pkt, i):
		return 3
	def i2m(self, pkt, y):
		return struct.pack("<BBB", y&0xff, (y&0xff00) >> 8, (y&0xff0000) >> 16)
	def m2i(self, pkt, x):
		(l,m,h) = struct.unpack("<BBB", x[:3])
		return h * 2**16 + m * 2**8 + l
	def addfield(self, pkt, s, val):
		m = self.i2m(pkt, val)
		return s+m
	def getfield(self, pkt, d):
		return d[3:],self.m2i(pkt, d)
	def size(self, pkt, val):
		return 3

Now, we can define the Packet Header:

class MySQL_Packet_Header(Packet):
	name="MySQL Packet Header"
	fields_desc = [
		Int24Field("Length",0),
		ByteField("Number",0)
	]
	def post_build(self, p, pay):
		self.Length = len(pay)
		p = self.do_build()
		return p+pay

and our Server Greeting Packet:

class MySQL_Server_Greeting(Packet):
	name="MySQL Server Greeting"
	fields_desc = [
		ByteField("ProtocolVersion",10),
		StrNullField("ServerVersion","5.0.54"),
		IntField("ThreadID",4711),
		StrFixedLenField("ScrambleBuffer","a"*8,8),
		ByteField("Filler0",0),
		FlagsField("ServerCapabilities", CLIENT_LONG_FLAG|CLIENT_CONNECT_WITH_DB|CLIENT_COMPRESS+CLIENT_PROTOCOL_41|CLIENT_TRANSACTIONS|CLIENT_SECURE_CONNECTION, -16, MySQL_Capabilities),
		ByteField("ServerLanguage",33),
		LEShortEnumField("ServerStatus", SERVER_STATUS_AUTOCOMMIT, MySQL_Server_Status),
		StrFixedLenField("Unused",b"",13),
		StrNullField("Salt"," "*12)
	]

this packet already uses the previously defined constants MySQL_Capabilities and MySQL_Server_Status.

Now we have something to start with, we can extract the hex value of the payload from our wireshark pcap and verify our parsing works:

>>> d = MySQL_Server_Greeting(bytes.fromhex('0a352e302e3534005e0000003e7e24347574682c002ca2210200000000000000000000000000003e36313249575a3e6668575800'))                                  
>>> d.show()                                                                                                                                                                              
# ###[ MySQL Server Greeting sizeof(52) ]### 
#   ProtocolVersion     = 10              sizeof(  1) off=  0 goff=  0
#   ServerVersion       = b'5.0.54\x00'   sizeof(  7) off=  1 goff=  1
#   ThreadID            = 1577058304      sizeof(  4) off=  8 goff=  8
#   ScrambleBuffer      = b'>~$4uth,'     sizeof(  8) off= 12 goff= 12
#   Filler0             = 0               sizeof(  1) off= 20 goff= 20
#   ServerCapabilities  = CLIENT_LONG_FLAG+CLIENT_CONNECT_WITH_DB+CLIENT_COMPRESS+CLIENT_PROTOCOL_41+CLIENT_TRANSACTIONS+CLIENT_SECURE_CONNECTION sizeof(  2) off= 21 goff= 21
#   ServerLanguage      = 33              sizeof(  1) off= 23 goff= 23
#   ServerStatus        = SERVER_STATUS_AUTOCOMMIT sizeof(  2) off= 24 goff= 24
#   Unused              = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' sizeof( 13) off= 26 goff= 26
#   Salt                = b'>612IWZ>fhWX\x00' sizeof( 13) off= 39 goff= 39

Now, lets see if we can create valid Greeting packets as well. To do so, we dump the parsed packet, parse the dump and print it - it should match the original packet:

MySQL_Server_Greeting(d.build()).show()
# ###[ MySQL Server Greeting sizeof(52) ]### 
#   ProtocolVersion     = 10              sizeof(  1) off=  0 goff=  0
#   ServerVersion       = b'5.0.54\x00'   sizeof(  7) off=  1 goff=  1
#   ThreadID            = 1577058304      sizeof(  4) off=  8 goff=  8
#   ScrambleBuffer      = b'>~$4uth,'     sizeof(  8) off= 12 goff= 12
#   Filler0             = 0               sizeof(  1) off= 20 goff= 20
#   ServerCapabilities  = CLIENT_LONG_FLAG+CLIENT_CONNECT_WITH_DB+CLIENT_COMPRESS+CLIENT_PROTOCOL_41+CLIENT_TRANSACTIONS+CLIENT_SECURE_CONNECTION sizeof(  2) off= 21 goff= 21
#   ServerLanguage      = 33              sizeof(  1) off= 23 goff= 23
#   ServerStatus        = SERVER_STATUS_AUTOCOMMIT sizeof(  2) off= 24 goff= 24
#   Unused              = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' sizeof( 13) off= 26 goff= 26
#   Salt                = b'>612IWZ>fhWX\x00' sizeof( 13) off= 39 goff= 39
#

So, we can create MySQL Greeting packets. Let's see if the calculation of the Packet Header works as well:

>>> a = MySQL_Packet_Header() / MySQL_Server_Greeting(bytes.fromhex('0a352e302e3534005e0000003e7e24347574682c002ca2210200000000000000000000000000003e36313249575a3e6668575800'))
>>> MySQL_Packet_Header(a.build()).show()
# ###[ MySQL Packet Header sizeof(4) ]### 
#   Length              = 52              sizeof(  3) off=  0 goff=  0
#   Number              = 0               sizeof(  1) off=  3 goff=  3
# ###[ Raw sizeof(52) ]### 
#      load                = b'\n5.0.54\x00^\x00\x00\x00>~$4uth,\x00,\xa2!\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>612IWZ>fhWX\x00' sizeof( 52) off=  0 goff=  4

The Length is set correctly. It is a pitty there is no useable way to use scapy's great chaining for MySQL packets, but thats a limitation of MySQL's poor wire protocol - if the Packet Header included a 'command' field to identify the sublayer accordingly, parsing the protocol would be much more comfortable. The way it is, you have to know the state of the connection to be able to know which packet to expect and parse.

Nevertheless, next packet to expect during client authentication is the Client Authentication Packet.

class MySQL_Client_Authentication(Packet):
	name="MySQL Client Authentication"
	fields_desc = [
		FlagsField("ClientCapabilities", 0, -16, MySQL_Capabilities),
		FlagsField("ClientExCapabilities", 0, -16, MySQL_Extended_Capabilities),
		LEIntField("MaxPacketSize",0),
		ByteField("CharSetNumber",0),
		StrFixedLenField("Filler",b"",23),
		StrNullField("User","bob"),
		LengthCodedBinaryField("ScrambleBuffer",b""),
		StrNullField("DatabaseName",b"")
	]

Again we verify this using the hexdata gathered from wireshark and compare to wireshark and itself:

>>> a = MySQL_Client_Authentication(bytes.fromhex('85a603000000000121000000000000000000000000000000000000000000000074666f65727374650014eefd6d5562851bc5966a0b41236ae3f2315efcc4'))      
>>> a.show()                                                                                                                                                                            
# ###[ MySQL Client Authentication sizeof(63) ]### 
#   ClientCapabilities  = CLIENT_LONG_PASSWORD+CLIENT_LONG_FLAG+CLIENT_LOCAL_FILES+CLIENT_PROTOCOL_41+CLIENT_INTERACTIVE+CLIENT_TRANSACTIONS+CLIENT_SECURE_CONNECTION sizeof(  2) off=  0 goff=  0
#   ClientExCapabilities= CLIENT_MULTI_STATEMENTS+CLIENT_MULTI_RESULTS sizeof(  2) off=  2 goff=  2
#   MaxPacketSize       = 16777216        sizeof(  4) off=  4 goff=  4
#   CharSetNumber       = 33              sizeof(  1) off=  8 goff=  8
#   Filler              = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' sizeof( 23) off=  9 goff=  9
#   User                = b'tfoerste\x00' sizeof(  9) off= 32 goff= 32
#   ScrambleBuffer      = b'\xee\xfdmUb\x85\x1b\xc5\x96j\x0bA#j\xe3\xf21^\xfc\xc4' sizeof( 21) off= 41 goff= 41
#   DatabaseName        = '\x00'          sizeof(  1) off= 62 goff= 62
 
>>> MySQL_Client_Authentication(a.build()).show()                                                                                                                                       
# ###[ MySQL Client Authentication sizeof(63) ]### 
#   ClientCapabilities  = CLIENT_LONG_PASSWORD+CLIENT_LONG_FLAG+CLIENT_LOCAL_FILES+CLIENT_PROTOCOL_41+CLIENT_INTERACTIVE+CLIENT_TRANSACTIONS+CLIENT_SECURE_CONNECTION sizeof(  2) off=  0 goff=  0
#   ClientExCapabilities= CLIENT_MULTI_STATEMENTS+CLIENT_MULTI_RESULTS sizeof(  2) off=  2 goff=  2
#   MaxPacketSize       = 16777216        sizeof(  4) off=  4 goff=  4
#   CharSetNumber       = 33              sizeof(  1) off=  8 goff=  8
#   Filler              = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' sizeof( 23) off=  9 goff=  9
#   User                = b'tfoerste\x00' sizeof(  9) off= 32 goff= 32
#   ScrambleBuffer      = b'\xee\xfdmUb\x85\x1b\xc5\x96j\x0bA#j\xe3\xf21^\xfc\xc4' sizeof( 21) off= 41 goff= 41
#   DatabaseName        = b'\x00'         sizeof(  1) off= 62 goff= 62

This packet does not match the documentation, the ClientExCapabilities are not listed in the documentation, and the DatabaseName seems to be optional, as we do not want to send client side packets anyway, we can live with DatabaseName not matching the expectations.

To acknowledge a client trying to authenticate, we have to sent a Result OK packet:

class MySQL_Result_OK(Packet):
	name="MySQL Result OK"
	fields_desc = [
		ByteField("ResultMarker", 0x00),
		LengthCodedIntField("AffectedRows",0),
		LengthCodedIntField("InsertID",0),
		LEShortEnumField("ServerStatus", SERVER_STATUS_AUTOCOMMIT, MySQL_Server_Status),
		LEShortField("WarningCount",0),
		StrField("Message",b'')
	]

Dionaea comes into play

Now, we have all packets to have a client connect a MySQL server successfully, lets create the server code then.

class mysqld(connection):
	def __init__ (self):
		connection.__init__(self,"tcp")
 
	def handle_established(self):
		self.state = 'greeting'
		a = MySQL_Packet_Header(Number=0) / MySQL_Server_Greeting()
		a.show()
		self.send(a.build())
 
	def handle_io_in(self,data):
		_l = len(data)
		offset = 0
		while len(data) - offset >= 4:
			h = MySQL_Packet_Header(data[offset:offset+4])
			r = p = None
			if len(data)-offset < h.Length+4:
				break
			if self.state == 'greeting':
				self.state = 'online'
				p = MySQL_Client_Authentication(data[offset+4:offset+4+h.Length])
				r = MySQL_Result_OK()
			else:
				pass
			if p is not None:
				h = h / p
			h.show()
 
			if r is not None:
				r = MySQL_Packet_Header(Number=h.Number+1+i) / r
				r.show()
				self.send(r.build())
			offset += 4 + h.Length
		return offset

handle_established sends the new connected client the Server Greeting, once the remote responds, handle_io_in takes care. The logic to handle incoming data is somewhat complex, as we can receive multiple MySQL packets in a single iteration, so we parse the data we received, until the is not enough data left to parse the next packet.

  • verify we have at least 4 bytes of data
  • splice the 4 byte chunk we need for the header from the data received
  • parse the header
  • verify we got enough data to parse the remaining packet
  • if we are in state 'greeting' - parse a Client Authentication Packet and prepare a Result OK
  • if we have a result, prepend the Packet Header and send the response

Now, we have to add mysql to the services, so it can get started during dionaeas startup:

class mysqlservice(service):
	def start(self, addr,  iface=None):
		daemon = mysql.mysqld()
		daemon.bind(addr, 3306, iface=iface)
		daemon.listen()
		return daemon
	def stop(self, daemon):
		daemon.close()
#...
def new():
# ...
	if "mysql" in g_dionaea.config()['modules']['python']['services']['serve']:
		g_slave.services.append(mysqlservice)

The auto* thing needs some information to copy the files to the appropriate place:

PYSCRIPTS += mysql/__init__.py           
PYSCRIPTS += mysql/mysql.py              
PYSCRIPTS += mysql/include/packets.py    
PYSCRIPTS += mysql/include/fields.py     
PYSCRIPTS += mysql/include/__init__.py   

First run

Now, after adding “mysql” to modules.python.services.serve in your config, running make install and restarting dionaea there should be a MySQL service listening.

sudo lsof -c dionaea | grep mysql
dionaea 1342 root   18u  IPv4           18615371       0t0       TCP localhost:mysql (LISTEN)
dionaea 1342 root   27u  IPv6           18615380       0t0       TCP ip6-localhost:mysql (LISTEN)

Running mysql -h 127.0.0.1 gives this:

OUT
###[ MySQL Packet Header sizeof(4) ]### 
  Length              = 0               sizeof(  3) off=  0 goff=  0
  Number              = 0               sizeof(  1) off=  3 goff=  3
###[ MySQL Server Greeting sizeof(52) ]### 
     ProtocolVersion     = 10              sizeof(  1) off=  0 goff=  4
     ServerVersion       = '5.0.54\x00'    sizeof(  7) off=  1 goff=  5
     ThreadID            = 4711            sizeof(  4) off=  8 goff= 12
     ScrambleBuffer      = 'aaaaaaaa'      sizeof(  8) off= 12 goff= 16
     Filler0             = 0               sizeof(  1) off= 20 goff= 24
     ServerCapabilities  = CLIENT_LONG_FLAG+CLIENT_CONNECT_WITH_DB+CLIENT_COMPRESS+CLIENT_PROTOCOL_41+CLIENT_TRANSACTIONS+CLIENT_SECURE_CONNECTION sizeof(  2) off= 21 goff= 25
     ServerLanguage      = 33              sizeof(  1) off= 23 goff= 27
     ServerStatus        = SERVER_STATUS_AUTOCOMMIT sizeof(  2) off= 24 goff= 28
     Unused              = b''             sizeof( 13) off= 26 goff= 30
     Salt                = '            \x00' sizeof( 13) off= 39 goff= 43

IN
###[ MySQL Packet Header sizeof(4) ]### 
  Length              = 40              sizeof(  3) off=  0 goff=  0
  Number              = 1               sizeof(  1) off=  3 goff=  3
###[ MySQL Client Authentication sizeof(43) ]### 
     ClientCapabilities  = CLIENT_LONG_PASSWORD+CLIENT_LONG_FLAG+CLIENT_LOCAL_FILES+CLIENT_PROTOCOL_41+CLIENT_INTERACTIVE+CLIENT_TRANSACTIONS+CLIENT_SECURE_CONNECTION sizeof(  2) off=  0 goff=  4
     ClientExCapabilities= CLIENT_MULTI_STATEMENTS+CLIENT_MULTI_RESULTS sizeof(  2) off=  2 goff=  6
     MaxPacketSize       = 16777216        sizeof(  4) off=  4 goff=  8
     CharSetNumber       = 8               sizeof(  1) off=  8 goff= 12
     Filler              = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' sizeof( 23) off=  9 goff= 13
     User                = b'common\x00'   sizeof(  7) off= 32 goff= 36
     ScrambleBuffer      = b''             sizeof(  3) off= 39 goff= 43
     DatabaseName        = '\x00'          sizeof(  1) off= 42 goff= 46

OUT
###[ MySQL Packet Header sizeof(4) ]### 
  Length              = 0               sizeof(  3) off=  0 goff=  0
  Number              = 2               sizeof(  1) off=  3 goff=  3
###[ MySQL Result OK sizeof(7) ]### 
     ResultMarker        = 0               sizeof(  1) off=  0 goff=  4
     AffectedRows        = 0               sizeof(  1) off=  1 goff=  5
     InsertID            = 0               sizeof(  1) off=  2 goff=  6
     ServerStatus        = SERVER_STATUS_AUTOCOMMIT sizeof(  2) off=  3 goff=  7
     WarningCount        = 0               sizeof(  2) off=  5 goff=  9
     Message             = b''             sizeof(  0) off=  7 goff= 11

and the mysql cli hangs as dionaea does not respond to a MySQL Command Packet.

Extending - Command Packets

Wireshark revealed the packet in question was a MySQL Command Packet of type COM_QUERY, so let's add the packet definition and the login to reply to this type of packet accordingly:

class MySQL_Command_Header(Packet):
	name="MySQL Command Header"
	fields_desc = [
		XByteEnumField("Command",0,MySQL_Commands),
	]
 
class MySQL_COM_QUERY(Packet):
	name="MySQL Command QUERY"
	fields_desc = [
		StrNullField("Query",b"")
	]
 
bind_bottom_up(MySQL_Command_Header, MySQL_COM_QUERY, Command=lambda x: x==COM_QUERY)

The values for the response packets were gathered from the wireshark packets:

	def _handle_COM_QUERY(self, p):
		r = None
		if p.Query == b'select @@version_comment limit 1':
			r = [MySQL_Result_Header(FieldCount=1),
 
				MySQL_Result_Field(Catalog='def',
				Name='@@version_comment',
				CharSet=33,
				Length=75,
				Type=253,
				Flags=0x1,
				Decimals=31),
				MySQL_Result_EOF(ServerStatus=0x002),
 
				MySQL_Result_Row_Data(ColumnValue='Gentoo Linux mysql-5.0.54\0'),
				MySQL_Result_EOF(ServerStatus=0x002)]
		return r

A COM_QUERY results in multiple packets which have to be sent, so we return a list, the code in handle_io_in gets adjusted to pass Command Packets to _handle_COM_xxx and deal with r being a list or not.

	def handle_io_in(self,data):
		_l = len(data)
		offset = 0
		while len(data) - offset >= 4:
			h = MySQL_Packet_Header(data[offset:offset+4])
			r = p = None
			if len(data)-offset < h.Length+4:
				break
			if self.state == 'greeting':
				self.state = 'online'
				p = MySQL_Client_Authentication(data[offset+4:offset+4+h.Length])
				r = MySQL_Result_OK()
			elif self.state == 'online':
				p = MySQL_Command_Header(data[offset+4:offset+4+h.Length])
				cmd = MySQL_Commands[p.Command]
				m = getattr(self, "_handle_" + cmd, None)
				if m is not None:
					r = m(p.payload)
			if p is not None:
				h = h / p
			h.show()
 
			if r is not None:
				if type(r) is not list:
					r = [r]
				buf = b''
				for i in range(len(r)):
					rp = r[i]
					rp = MySQL_Packet_Header(Number=h.Number+1+i) / rp
					rp.show()
					buf += rp.build()
				self.send(buf)
			offset += 4 + h.Length
		return offset

Second run - SELECT something

Now, mysql cli can login properly, and we have implemented a query which will work:

mysql -h 127.0.0.1
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1729232896
Server version: 5.0.54 Gentoo Linux mysql-5.0.54
 
mysql> select @@version_comment limit 1;
+----------------------------+
| @@version_comment          |
+----------------------------+
| Gentoo Linux mysql-5.0.54  |
+----------------------------+
1 row in set (0.01 sec)

Extend - use database

Running

use database;

hangs mysql cli again, as there is no reply. So handle the query run by use.

	def _handle_COM_QUERY(self, p):
		r = None
		if p.Query == b'select @@version_comment limit 1':
# ...
		elif p.Query == b'SELECT DATABASE()':
			r = [MySQL_Result_Header(FieldCount=1),
 
				MySQL_Result_Field(Catalog='def',
				Name=b'DATABASE()',
				CharSet=33,
				Length=75,
				Type=253,
				Flags=0x1,
				Decimals=31),
				MySQL_Result_EOF(ServerStatus=0x002),
 
				MySQL_Result_Row_Data(ColumnValue=''),
				MySQL_Result_EOF(ServerStatus=0x002)]
		return r

After the query, a command of type COM_INIT_DB is sent by the mysql cli.

###[ MySQL Packet Header sizeof(4) ]### 
  Length              = 9               sizeof(  3) off=  0 goff=  0
  Number              = 0               sizeof(  1) off=  3 goff=  3
###[ MySQL Command Header sizeof(1) ]### 
     Command             = COM_INIT_DB     sizeof(  1) off=  0 goff=  4
###[ Raw sizeof(8) ]### 
        load                = b'database'     sizeof(  8) off=  0 goff=  5

So we add the Packet and handle the command accordingly:

class MySQL_COM_INIT_DB(Packet):
	name="MySQL Command INIT DB"
	fields_desc = [
		StrField("Database",b"")
	]
bind_bottom_up(MySQL_Command_Header, MySQL_COM_INIT_DB, Command=lambda x: x==COM_INIT_DB)

and add the appropriate handler to the service:

	def _handle_COM_INIT_DB(self, p):
		return MySQL_Result_OK()

While this works fine:

###[ MySQL Packet Header sizeof(4) ]### 
  Length              = 5               sizeof(  3) off=  0 goff=  0
  Number              = 0               sizeof(  1) off=  3 goff=  3
###[ MySQL Command Header sizeof(1) ]### 
     Command             = COM_INIT_DB     sizeof(  1) off=  0 goff=  4
###[ MySQL Command INIT DB sizeof(4) ]### 
        Database            = b'test'         sizeof(  4) off=  0 goff=  5

and our response is well formatted too,

###[ MySQL Packet Header sizeof(4) ]### 
  Length              = 0               sizeof(  3) off=  0 goff=  0
  Number              = 1               sizeof(  1) off=  3 goff=  3
###[ MySQL Result OK sizeof(7) ]### 
     ResultMarker        = 0               sizeof(  1) off=  0 goff=  4
     AffectedRows        = 0               sizeof(  1) off=  1 goff=  5
     InsertID            = 0               sizeof(  1) off=  2 goff=  6
     ServerStatus        = SERVER_STATUS_AUTOCOMMIT sizeof(  2) off=  3 goff=  7
     WarningCount        = 0               sizeof(  2) off=  5 goff=  9
     Message             = b''             sizeof(  0) off=  7 goff= 11

Now, just to keep us busy adding code, the cli will run the queries “show databases”, “show tables”

	def _handle_COM_QUERY(self, p):
		...
		elif p.Query == b'show databases':
			r = [MySQL_Result_Header(FieldCount=1),
 
				MySQL_Result_Field(Catalog='def',
					Table=b'SCHEMATA',
					Name=b'Database',
					CharSet=33,
					Length=192,
					Type=253,
					Flags=0x1,
					Decimals=0),
				MySQL_Result_EOF(ServerStatus=0x002),
 
				MySQL_Result_Row_Data(ColumnValues=['information_schema']),
				MySQL_Result_EOF(ServerStatus=0x002)]
		elif p.Query == b'show tables':
			r = [MySQL_Result_Header(FieldCount=1),
 
				MySQL_Result_Field(Catalog='def',
					Table=b'TABLE_NAMES',
					Name=b'Tables_in_test',
					CharSet=33,
					Length=192,
					Type=253,
					Flags=0x1,
					Decimals=0),
				MySQL_Result_EOF(ServerStatus=0x002),
 
				MySQL_Result_Row_Data(ColumnValues=['agent']),
				MySQL_Result_EOF(ServerStatus=0x002)]

and the Command COM_FIELD_LIST for the table 'agent' we pretended to have.

	def _handle_COM_FIELD_LIST(self, p):
		r = None
		if p.Table == b'agent\x00':
			r = [MySQL_Result_Field(Catalog='def',
					Database=b"test",
					Table=b'agent',
					Name=b'id',
					CharSet=63,
					Length=20,
					Type=8,
					Flags=0x4203,
					Decimals=0),
				 MySQL_Result_EOF(ServerStatus=0x002)
				]
		return r

Ready?

Now, we have a basic working MySQL stack, but there is very little value in it - there is simply no database. Though I do not want to implement a database, not even to talk about re-implementing MySQL, a database which allows queries would be 'it'.

So, lets embed SQLite then.

Open a self contained memory database

	def handle_established(self):
# ...
		self.dbh = sqlite3.connect(":memory:")
		self.cursor = self.dbh.cursor()

and forward all queries to this database. In case the query is valid, create a resultset from the data retrieved. To keep things simple, the only data type used is VARCHAR.

	def _handle_COM_QUERY(self, p):
		r = None
		if p.Query == b'select @@version_comment limit 1':
# ...
		else:
			p.show()
			try:
				result = self.cursor.execute(p.Query.decode('utf-8'))
				names = [result.description[x][0] for x in range(len(result.description))]
				result = [dict(zip(names, i)) for i in result]
				r = [MySQL_Result_Header(FieldCount=len(names))]
				for name in names:
					r.append(MySQL_Result_Field(Catalog='def',
	#					Table=b'SCHEMATA',
						Name=name,
						CharSet=33,
						Length=255,
						Type=253,
						Flags=0x1,
						Decimals=0))
				r.append(MySQL_Result_EOF(ServerStatus=0x002))
				for res in result:
					x = MySQL_Result_Row_Data(ColumnValues=[res[name] for name in names])
					x.show()
					print(x.build())
					r.extend(x),
				r.append(MySQL_Result_EOF(ServerStatus=0x002))
			except Exception as e:
				logger.warn("SQL ERROR %s" % e)
				r = MySQL_Result_Error(Message="Learn SQL!")
		return r

Let's see how this works out:

mysql> select 1*1,2*2,3*3,4*4,5*5;
+------+------+------+------+------+
| 1*1  | 2*2  | 3*3  | 4*4  | 5*5  |
+------+------+------+------+------+
|    1 |    4 |    9 |   16 |   25 |
+------+------+------+------+------+
1 row in set (0.03 sec)

and in case the query is invalid:

mysql> seloct;
ERROR: 
Learn SQL!
mysql> 

Before going ahead, lets replace some more constants.

FIELD_TYPE_DECIMAL		= 0x00
FIELD_TYPE_TINY			= 0x01
FIELD_TYPE_SHORT		= 0x02
FIELD_TYPE_LONG			= 0x03
FIELD_TYPE_FLOAT		= 0x04
FIELD_TYPE_DOUBLE		= 0x05
FIELD_TYPE_NULL			= 0x06
FIELD_TYPE_TIMESTAMP	= 0x07
FIELD_TYPE_LONGLONG		= 0x08
FIELD_TYPE_INT24		= 0x09
FIELD_TYPE_DATE			= 0x0a
FIELD_TYPE_TIME			= 0x0b
FIELD_TYPE_DATETIME		= 0x0c
FIELD_TYPE_YEAR			= 0x0d
FIELD_TYPE_NEWDATE		= 0x0e
FIELD_TYPE_VARCHAR		= 0x0f # (new in MySQL 5.0)
FIELD_TYPE_BIT			= 0x10 # (new in MySQL 5.0)
FIELD_TYPE_NEWDECIMAL	= 0xf6 # (new in MYSQL 5.0)
FIELD_TYPE_ENUM			= 0xf7
FIELD_TYPE_SET			= 0xf8
FIELD_TYPE_TINY_BLOB	= 0xf9
FIELD_TYPE_MEDIUM_BLOB	= 0xfa
FIELD_TYPE_LONG_BLOB	= 0xfb
FIELD_TYPE_BLOB			= 0xfc
FIELD_TYPE_VAR_STRING	= 0xfd
FIELD_TYPE_STRING		= 0xfe
FIELD_TYPE_GEOMETRY		= 0xff
 
MySQL_Field_Types = {
	FIELD_TYPE_DECIMAL		:"FIELD_TYPE_DECIMAL",
	FIELD_TYPE_TINY			:"FIELD_TYPE_TINY",
	FIELD_TYPE_SHORT		:"FIELD_TYPE_SHORT",
	FIELD_TYPE_LONG			:"FIELD_TYPE_LONG",
	FIELD_TYPE_FLOAT		:"FIELD_TYPE_FLOAT",
	FIELD_TYPE_DOUBLE		:"FIELD_TYPE_DOUBLE",
	FIELD_TYPE_NULL			:"FIELD_TYPE_NULL",
	FIELD_TYPE_TIMESTAMP	:"FIELD_TYPE_TIMESTAMP",
	FIELD_TYPE_LONGLONG		:"FIELD_TYPE_LONGLONG",
	FIELD_TYPE_INT24		:"FIELD_TYPE_INT24",
	FIELD_TYPE_DATE			:"FIELD_TYPE_DATE",
	FIELD_TYPE_TIME			:"FIELD_TYPE_TIME",
	FIELD_TYPE_DATETIME		:"FIELD_TYPE_DATETIME",
	FIELD_TYPE_YEAR			:"FIELD_TYPE_YEAR",
	FIELD_TYPE_NEWDATE		:"FIELD_TYPE_NEWDATE",
	FIELD_TYPE_VARCHAR		:"FIELD_TYPE_VARCHAR",
	FIELD_TYPE_BIT			:"FIELD_TYPE_BIT",
	FIELD_TYPE_NEWDECIMAL	:"FIELD_TYPE_NEWDECIMAL",
	FIELD_TYPE_ENUM			:"FIELD_TYPE_ENUM",
	FIELD_TYPE_SET			:"FIELD_TYPE_SET",
	FIELD_TYPE_TINY_BLOB	:"FIELD_TYPE_TINY_BLOB",
	FIELD_TYPE_MEDIUM_BLOB	:"FIELD_TYPE_MEDIUM_BLOB",
	FIELD_TYPE_LONG_BLOB	:"FIELD_TYPE_LONG_BLOB",
	FIELD_TYPE_BLOB			:"FIELD_TYPE_BLOB",
	FIELD_TYPE_VAR_STRING	:"FIELD_TYPE_VAR_STRING",
	FIELD_TYPE_STRING		:"FIELD_TYPE_STRING",
	FIELD_TYPE_GEOMETRY		:"FIELD_TYPE_GEOMETRY",
}
 
FLAG_NOT_NULL   	= 0x0001
FLAG_PRI_KEY		= 0x0002
FLAG_UNIQUE_KEY		= 0x0004
FLAG_MULTIPLE_KEY	= 0x0008
FLAG_BLOB			= 0x0010
FLAG_UNSIGNED		= 0x0020
FLAG_ZEROFILL   	= 0x0040
FLAG_BINARY			= 0x0080
FLAG_ENUM			= 0x0100
FLAG_AUTO_INCREMENT = 0x0200
FLAG_TIMESTAMP  	= 0x0400
FLAG_SET			= 0x0800
 
MySQL_Field_Flags = {
	FLAG_NOT_NULL		:"FLAG_NOT_NULL",
	FLAG_PRI_KEY		:"FLAG_PRI_KEY",
	FLAG_UNIQUE_KEY		:"FLAG_UNIQUE_KEY",
	FLAG_MULTIPLE_KEY	:"FLAG_MULTIPLE_KEY",
	FLAG_BLOB			:"FLAG_BLOB",
	FLAG_UNSIGNED		:"FLAG_UNSIGNED",
	FLAG_ZEROFILL		:"FLAG_ZEROFILL",
	FLAG_BINARY			:"FLAG_BINARY",
	FLAG_ENUM			:"FLAG_ENUM",
	FLAG_AUTO_INCREMENT	:"FLAG_AUTO_INCREMENT",
	FLAG_TIMESTAMP		:"FLAG_TIMESTAMP",
	FLAG_SET			:"FLAG_SET",
}

and use them:

class MySQL_Result_Field(Packet):
	name="MySQL Result Field"
	fields_desc = [
		LengthCodedBinaryField("Catalog",b''),
		LengthCodedBinaryField("Database",b''),
		LengthCodedBinaryField("Table",b''),
		LengthCodedBinaryField("ORGTable",b''),
		LengthCodedBinaryField("Name",b''),
		ByteField("Filler", 0),
		ByteField("XFiller", 0),
		LEShortField("CharSet", 0),
		LEIntField("Length", 0),
		XByteEnumField("Type",0,MySQL_Field_Types),
		FlagsField("Flags", 0, -16, MySQL_Field_Flags),
		ByteField("Decimals", 0),
#		LEShortField("Filler2",0),
#		LengthCodedBinaryField("Default",b''),
	]

as a result, the scapy types look much better, proving more information:

###[ MySQL Result Field sizeof(33) ]### 
     Catalog             = 'def'           sizeof(  4) off=  0 goff=  4
     Database            = b'test'         sizeof(  5) off=  4 goff=  8
     Table               = b'agent'        sizeof(  6) off=  9 goff= 13
     ORGTable            = b''             sizeof(  3) off= 15 goff= 19
     Name                = b'id'           sizeof(  3) off= 18 goff= 22
     Filler              = 0               sizeof(  1) off= 21 goff= 25
     XFiller             = 0               sizeof(  1) off= 22 goff= 26
     CharSet             = 63              sizeof(  2) off= 23 goff= 27
     Length              = 20              sizeof(  4) off= 25 goff= 29
     Type                = FIELD_TYPE_LONGLONG sizeof(  1) off= 29 goff= 33
     Flags               = FLAG_NOT_NULL+FLAG_PRI_KEY+FLAG_AUTO_INCREMENT+0x4000 sizeof(  2) off= 30 goff= 34
     Decimals            = 0               sizeof(  1) off= 32 goff= 36

Dynamic output

To get over the static replies for “show databases” we allow multiple databases to be configured in the configuration:

On connect we copy the information of the databases available,

	def handle_established(self):
		self.config = g_dionaea.config()['modules']['python']['mysql']['databases']
		self.state = 'greeting'
 
		a = MySQL_Packet_Header(Number=0) / MySQL_Server_Greeting()
		a.show()
		self.send(a.build())
 
		self.database = 'memory'
		self.dbh = sqlite3.connect(":memory:")
		self.cursor = self.dbh.cursor()

we open the database in _handle_COM_INIT_DB instead:

	def _handle_COM_INIT_DB(self, p):
		Database = p.Database.decode('utf-8')
		if self._open_db(Database) == True:
			return MySQL_Result_OK()
		else:
			return MySQL_Result_Error(Message="No such database")

COM_FIELD_LIST gets adjusted to work with the sqlite information as well:

	def _handle_COM_FIELD_LIST(self, p):
		logger.warn("%s" % p)
		Table = p.Table
		r = []
		query = "PRAGMA table_info(%s);" % Table.decode('ascii')[:-1]
		result = self.cursor.execute(query) # FIXME sqlite does not allow ? for PRAGMA?
		names = [result.description[x][0] for x in range(len(result.description))]
		result = [dict(zip(names, i)) for i in result]
		for res in result:
			x = MySQL_Result_Field(Catalog='def',
				Database=self.database,
				Table=p.Table[:-1],
				ORGTable=p.Table[:-1],
				Name=res['name'].encode('ascii'),
				ORGName=res['name'].encode('ascii'),
				CharSet=33,
				Length=20,
				Type=FIELD_TYPE_VAR_STRING,
				Flags=0, #0x4203,
				Decimals=0,
				Default='0')
			r.append(x)
			break
 
		r.append(MySQL_Result_EOF(ServerStatus=0x002))
		return r

Debugging the documentation

Then we'll hit some border, as mysql cli will see “Malformed packet” and mess things up:

   3: | | >mysql_list_fields
   3: | | | enter: table: 'dcerpcbinds'  wild: ''
   4: | | | >free_old_query
   5: | | | | >init_alloc_root
   5: | | | | | enter: root: 0x92bd40
   5: | | | | <init_alloc_root
   4: | | | <free_old_query
   4: | | | >cli_advanced_command
   5: | | | | >net_clear
   5: | | | | <net_clear
   5: | | | | >net_write_command
   5: | | | | | enter: length: 12
   6: | | | | | >net_flush
   7: | | | | | | >vio_is_blocking
   7: | | | | | | | exit: 1
   7: | | | | | | <vio_is_blocking
   7: | | | | | | >net_real_write
   8: | | | | | | | >vio_write
   8: | | | | | | | | enter: sd: 4  buf: 0x64041c0  size: 17
   8: | | | | | | | | exit: 17
   8: | | | | | | | <vio_write
   7: | | | | | | <net_real_write
   6: | | | | | <net_flush
   5: | | | | <net_write_command
   4: | | | | exit: result: 0
   4: | | | <cli_advanced_command
   4: | | | >cli_read_rows
   5: | | | | >vio_is_blocking
   5: | | | | | exit: 1
   5: | | | | <vio_is_blocking
   5: | | | | >vio_read_buff
   5: | | | | | enter: sd: 4  buf: 0x64041c0  size: 4
   6: | | | | | >vio_read
   6: | | | | | | enter: sd: 4  buf: 0x6400180  size: 16384
   6: | | | | | | exit: 83
   6: | | | | | <vio_read
   5: | | | | <vio_read_buff
   4: | | | | packet_header: Memory: 0x64041c0  Bytes: (4)
46 00 00 01 
   5: | | | | >vio_read_buff
   5: | | | | | enter: sd: 4  buf: 0x64041c0  size: 70
   5: | | | | <vio_read_buff
   5: | | | | >my_malloc
   5: | | | | | my: size: 96  my_flags: 48
   5: | | | | | exit: ptr: 0x64e4ac0
   5: | | | | <my_malloc
   5: | | | | >init_alloc_root
   5: | | | | | enter: root: 0x64e4ad0
   5: | | | | <init_alloc_root
   5: | | | | >alloc_root
   5: | | | | | enter: root: 0x64e4ad0
   6: | | | | | >my_malloc
   6: | | | | | | my: size: 8160  my_flags: 1040
   6: | | | | | | exit: ptr: 0x64e4b60
   6: | | | | | <my_malloc
   5: | | | | | exit: ptr: 0x64e4b70
   5: | | | | <alloc_root
   5: | | | | >alloc_root
   5: | | | | | enter: root: 0x64e4ad0
   5: | | | | | exit: ptr: 0x64e4b88
   5: | | | | <alloc_root
   5: | | | | >free_root
   5: | | | | | enter: root: 0x64e4ad0  flags: 0
   6: | | | | | >my_free
   6: | | | | | | my: ptr: 0x64e4b60
   6: | | | | | <my_free
   5: | | | | <free_root
   5: | | | | >my_free
   5: | | | | | my: ptr: 0x64e4ac0
   5: | | | | <my_free
   5: | | | | >set_mysql_error
   5: | | | | | enter: error :2027 'Malformed packet'
   5: | | | | <set_mysql_error
   4: | | | <cli_read_rows
   3: | | <mysql_list_fields

After some fixes, things will get worse, and mysql cli will segfault:

==23728== Invalid read of size 1
==23728==    at 0x4217BC: unpack_fields (client.c:1474)
==23728==    by 0x426706: cli_read_query_result (client.c:3856)
==23728==    by 0x426900: mysql_real_query (client.c:3890)
==23728==    by 0x4168FC: mysql_query (libmysql.c:699)
==23728==    by 0x411F36: server_version_string(st_mysql*) (mysql.cc:4521)
==23728==    by 0x409D80: main (mysql.cc:1175)
==23728==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

Finally, we'll remember documentation is nothing to rely on, and hit the code.

Turns out the not documented filler in Field Packet has to be 0xc

 VERSION 4.1
 Bytes                      Name
 -----                      ----
 n (Length Coded String)    catalog
 n (Length Coded String)    db
 n (Length Coded String)    table
 n (Length Coded String)    org_table
 n (Length Coded String)    name
 n (Length Coded String)    org_name
 1                          (filler)
 2                          charsetnr
 4                          length
 1                          type
 2                          flags
 1                          decimals
 2                          (filler), always 0x00
 n (Length Coded Binary)    default

The idea of this is, they parse the whole packet using 8 Length Coded Strings, filler is the 7th, a filler of 0xc makes the data for the filler 13 bytes, so aligning it with the Length Coded Binary 'default'. Using 0xfb for filler segfaults mysql cli reliable for me with above mentioned backtrace.

Thats the type of bugs which take some time to resolve properly.

But, after this is resolved we can have some fun with our new working database …

mysql -h 192.168.53.20
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1729232896
Server version: 5.0.54 Gentoo Linux mysql-5.0.54

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL v2 license

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| test               |
| information_schema |
+--------------------+
2 rows in set (0.01 sec)

mysql> use test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> show tables;
+--------------------+
| Tables_in_test     |
+--------------------+
| dcerpcbinds        |
| dcerpcrequests     |
| dcerpcservices     |
| dcerpcserviceops   |
| emu_profiles       |
| emu_services_old   |
| offers             |
| downloads          |
| resolves           |
| p0fs               |
| emu_services       |
| logins             |
| mssql_fingerprints |
| mssql_commands     |
| connections        |
| sqlite_sequence    |
| virustotals        |
| virustotalscans    |
+--------------------+
18 rows in set (0.03 sec)

mysql> SELECT COUNT(*),offers.offer_url FROM offers GROUP BY offer_url ORDER BY COUNT(*) DESC LIMIT 10;
+----------+-------------------------------------------------------+
| COUNT(*) | offer_url                                             |
+----------+-------------------------------------------------------+
| 817      | tftp://0.0.0.0/ssms.exe                               |
| 51       | smb://200.109.251.194/sql\query                       |
| 32       | ftp://1:1@ftpserroco.mine.nu:21/doc.exe               |
| 29       | http://76.73.7.138/63xx.exe                           |
| 28       | http://174.120.251.130/63.exe                         |
| 26       | tftp://92.52.33.198/ssms.exe                          |
| 22       | ftp://upload:upload@wootftp.l1qu1d.net:8989//woot.exe |
| 22       | tftp://93.93.15.138/ssms.exe                          |
| 20       | tftp://92.55.128.226/ssms.exe                         |
| 19       | http://208.53.183.163/63.exe                          |
+----------+-------------------------------------------------------+
10 rows in set (1.76 sec)

mysql> create table test5 ( NUMBER oid );
Query OK, 0 rows affected (0.00 sec)

You may have noticed I used a historic dionaea logsql database to play with.

Due to the working COM_FIELD_LIST the mysql cli autocompletition even works:

mysql> select conn
connect                           connection                        connection_date
connection_parent                 connection_protocol               connection_root
connection_timestamp              connection_transport              connection_type
connections                       connections.connection            connections.connection_date
connections.connection_parent     connections.connection_protocol   connections.connection_root
connections.connection_timestamp  connections.connection_transport  connections.connection_type
connections.local_host            connections.local_port            connections.remote_host
connections.remote_hostname       connections.remote_port

Now there is something basic to play with, of course it is rather easy to detect as not a real mysql database, but basics work already. One may want to add real mysql error messages later on, volunteers accepted - I'm not into mysql error codes that much. Permission denied for information schema would be a start. Given we have something working already, lets go ahead and make sure the gathered information is stored in an accessible way.

Code

Logging

Besides the information about who is the remote, which gets logged by dionaea by default, we may be interested in authentication credentials used to gain access and commands run on the database. Contrary to mssql, mysql does not transfer the password plaintext, so logging credentials would only reveal some hashes for the passwords and we'd have to live with the username. For commands, there are multiple commands, and these commands have a dynamic number of arguments.

incidents

So we add two incidents which report the login and commands and the arguments.

For the login we leave out the password, as it is hashed and does not provide any value anyway.

@@ -217,12 +217,32 @@ class mysqld(connection):
                                else:
                                        r = MySQL_Result_OK()
 
+                               i = incident("dionaea.modules.python.mysql.login")
+                               i.con = self
+                               i.username = p.User
+                               i.password = ""
+                               i.report()

For commands we retrieve the arguments - if possible.

                        elif self.state == 'online':
                                p = MySQL_Command_Header(data[offset+4:offset+4+h.Length])
                                cmd = MySQL_Commands[p.Command]
                                m = getattr(self, "_handle_" + cmd, None)
+                               args = None
                                if m is not None:
+                                       args = []
+                                       for f in p.payload.fields_desc:
+                                               if f.name in p.payload.fields:
+                                                       args.append(p.payload.fields[f.name])
                                        r = m(p.payload)
+
+                               i = incident("dionaea.modules.python.mysql.command")
+                               i.con = self
+                               i.command = p.Command
+                               if args is not None:
+                                       i.args = args
+                               i.dump()
+                               i.report()
+
                        if p is not None:
                                h = h / p
                        h.show()

commit

logsql

The incidents just report something happened, so we still have to log it somewhere. My first target is logsql, as it is pretty easy to extend.

We define the database tables we want to use to store the data, as a table to store authentication credentials already exists, we only need to add some tables specific to our new mysql code:

		self.cursor.execute("""CREATE TABLE IF NOT EXISTS
			mysql_commands (
				mysql_command INTEGER PRIMARY KEY,
				connection INTEGER,
				mysql_command_cmd NUMBER NOT NULL
				-- CONSTRAINT mysql_commands_connection_fkey FOREIGN KEY (connection) REFERENCES connections (connection)
			)""")
 
		self.cursor.execute("""CREATE TABLE IF NOT EXISTS
			mysql_command_args (
				mysql_command_arg INTEGER PRIMARY KEY,
				mysql_command INTEGER,
				mysql_command_arg_index NUMBER NOT NULL,
				mysql_command_arg_data TEXT NOT NULL
				-- CONSTRAINT mysql_commands_connection_fkey FOREIGN KEY (connection) REFERENCES connections (connection)
			)""")

And a convenience table to provide some better visualisation of the information - a mapping from COM_ integers to names.

		from dionaea.mysql.include.packets import MySQL_Commands
		logger.info("Setting MySQL Command Ops")
		for num,name in MySQL_Commands.items():
			try:
				self.cursor.execute("INSERT INTO mysql_command_ops (mysql_command_cmd, mysql_command_op_name) VALUES (?,?)",
							(num, name))
			except:
				pass

Once we have the tables, we can add the code to store the information into the tables, starting with the code to store the login information:

	def handle_incident_dionaea_modules_python_mysql_login(self, icd):
		con = icd.con
		if con in self.attacks:
			attackid = self.attacks[con][1]
			self.cursor.execute("INSERT INTO logins (connection, login_username, login_password) VALUES (?,?,?)",
				(attackid, icd.username, icd.password))
			self.dbh.commit()

For commands, store the arguments to the commands in a seperate table, so we do not need a table for every command, depending on the number of arguments:

	def handle_incident_dionaea_modules_python_mysql_command(self, icd):
		con = icd.con
		if con in self.attacks:
			attackid = self.attacks[con][1]
			self.cursor.execute("INSERT INTO mysql_commands (connection, mysql_command_cmd) VALUES (?,?)",
				(attackid, icd.command))
			cmdid = self.cursor.lastrowid
			args = icd.args
 
			for i in range(len(args)):
				arg = args[i]
				self.cursor.execute("INSERT INTO mysql_command_args (mysql_command, mysql_command_arg_index, mysql_command_arg_data) VALUES (?,?,?)",
					(cmdid, i, arg))
			self.dbh.commit()

commit

readlogsqltree

Once there is data, one may want to have a look on it, for logsql dionaea ships readlogsqltree, so we have to extend readlogsqltree to print the information from the mysql related tables:

def print_mysql_commands(cursor, connection, indent):
	r = cursor.execute("""
		SELECT
			mysql_command,
			mysql_command_cmd,
			mysql_command_op_name
		FROM 
			mysql_commands
			LEFT OUTER JOIN mysql_command_ops USING(mysql_command_cmd)
		WHERE 
			connection = ?""", (connection, ))
	commands = resolve_result(r)
	for cmd in commands:
		print("{:s} mysql command (0x{:02x}) {:s}".format(
			' ' * indent,
			cmd['mysql_command_cmd'],
			cmd['mysql_command_op_name']
			), end='')
		# args
		r = cursor.execute("""
		SELECT
			mysql_command_arg_data
		FROM
			mysql_command_args
		WHERE
			mysql_command = ?
		ORDER BY
			mysql_command_arg_index ASC """, (cmd['mysql_command'], ))
		args = resolve_result(r)
		print("({:s})".format(",".join([ "'%s'" % arg['mysql_command_arg_data'] for arg in args])))

and call this function for the accepted connections:

 def print_connection(c, indent):
        indentStr = ' ' * (indent + 1)
 
@@ -301,6 +333,7 @@ WHERE
                        print_logins(cursor, c['connection'], 2)
                        print_mssql_fingerprints(cursor, c['connection'], 2)
                        print_mssql_commands(cursor, c['connection'], 2)
+                       print_mysql_commands(cursor, c['connection'], 2)
                        recursive_print(cursor, c['connection'], 2)

commit

Now we can verify this actually works:

2011-05-19 10:52:40
  connection 1624 mysqld tcp accept 127.0.0.1:3306 <- 127.0.0.1:40716 (1624 None)
   login - user:'nobody' password:''
   mysql command (0x02) COM_INIT_DB('test')
   mysql command (0x03) COM_QUERY('show databases')
   mysql command (0x03) COM_QUERY('show tables')
   mysql command (0x04) COM_FIELD_LIST('connections')
   mysql command (0x04) COM_FIELD_LIST('dcerpcbinds')
   mysql command (0x04) COM_FIELD_LIST('dcerpcrequests')
   mysql command (0x04) COM_FIELD_LIST('dcerpcservices')
   mysql command (0x04) COM_FIELD_LIST('dcerpcserviceops')
   mysql command (0x04) COM_FIELD_LIST('emu_profiles')
   mysql command (0x04) COM_FIELD_LIST('emu_services_old')
   mysql command (0x04) COM_FIELD_LIST('offers')
   mysql command (0x04) COM_FIELD_LIST('downloads')
   mysql command (0x04) COM_FIELD_LIST('resolves')
   mysql command (0x04) COM_FIELD_LIST('p0fs')
   mysql command (0x04) COM_FIELD_LIST('logins')
   mysql command (0x04) COM_FIELD_LIST('mssql_fingerprints')
   mysql command (0x04) COM_FIELD_LIST('mssql_commands')
   mysql command (0x04) COM_FIELD_LIST('virustotals')
   mysql command (0x04) COM_FIELD_LIST('virustotalscans')
   mysql command (0x04) COM_FIELD_LIST('emu_services')
   mysql command (0x04) COM_FIELD_LIST('sqlite_stat1')
   mysql command (0x04) COM_FIELD_LIST('mysql_commands')
   mysql command (0x04) COM_FIELD_LIST('mysql_command_args')
   mysql command (0x04) COM_FIELD_LIST('mysql_command_ops')
   mysql command (0x03) COM_QUERY('select * from x limit 1')

logxmpp

For distributed setups dionaea ships logxmpp, so lets extend logxmpp to take care of the mysql incidents:

Once again, lets start with the login information:

	def handle_incident_dionaea_modules_python_mysql_login(self, i):
		n = etree.Element('mysqllogin', attrib={
			'username' : i.username,
			'password' : i.password,
			'ref' : str(i.con.__hash__())})
		self.broadcast(i, n)

On the wire, the login information will look like this

<message to="anon-events@dionaea.sensors.carnivore.it" type="groupchat" xml:lang="en">
  <body>
    <dionaea incident="dionaea.modules.python.mysql.login" xmlns="http://dionaea.carnivore.it">
      <mysqllogin username="nobody" password="" ref="35840352"/>
    </dionaea>
  </body>
  <nick xmlns="http://jabber.org/protocol/nick">anonymous-LNghoDxP</nick>
</message>

Afterwards, the command information needs to be serialized as well:

	def handle_incident_dionaea_modules_python_mysql_command(self, i):
		n = etree.Element('mysqlcommand', attrib={
			'cmd' : str(i.command),
			'ref' : str(i.con.__hash__())})
		if hasattr(i,'args'):
			args = etree.Element('args')
			for j in range(len(i.args)):
				arg = etree.Element('arg', attrib={
					'index' : str(j)})
				arg.text = i.args[j]
				args.append(arg)
			n.append(args)
		self.broadcast(i, n)

The data sent will look like this:

<message to="anon-events@dionaea.sensors.carnivore.it" type="groupchat" xml:lang="en">
  <body>
    <dionaea incident="dionaea.modules.python.mysql.command" xmlns="http://dionaea.carnivore.it">
      <mysqlcommand cmd="3" ref="35840352">
        <args>
          <arg index="0">select * from x limit 1</arg>
        </args>
      </mysqlcommand>
    </dionaea>
  </body>
  <nick xmlns="http://jabber.org/protocol/nick">anonymous-LNghoDxP</nick>
</message>

commit

pg_backend

To be able to store the information collected by logxmpp pg_backend needs to be extended, adding the tables to the database4) and provide the appropriate handlers to deal with the new messages:

	def handle_incident_dionaea_modules_python_mysql_login(self, user, xmlobj):
		try:
			ref = xmlobj.hasProp('ref').content
			ref = int(ref)
			username = xmlobj.hasProp('username').content
			password = xmlobj.hasProp('password').content
		except Exception as e:
			print(e)
			return
		if dbh is not None and ref in user.attacks:
			attackid = user.attacks[ref][1]
			cursor.execute("INSERT INTO dionaea.logins (connection, login_username, login_password) VALUES (%s,%s,%s)",
				(attackid, username, password))
		print("[%s] mysqllogin ref %i: %s %s" % (user.room_jid.as_unicode(), ref, username, password))
	def handle_incident_dionaea_modules_python_mysql_command(self, user, xmlobj):
		try:
			ref = xmlobj.hasProp('ref').content
			ref = int(ref)
			cmd = int(xmlobj.hasProp('cmd').content)
			args = []
			child = xmlobj.children
			r = xpath_eval(xmlobj, './dionaea:args/dionaea:arg', namespaces=dionaea_ns)
			for i in r:
				args.append((i.hasProp('index').content, i.content))
		except Exception as e:
			print(e)
			return
		if dbh is not None and ref in user.attacks:
			attackid = user.attacks[ref][1]
			cursor.execute("INSERT INTO dionaea.mysql_commands (connection, mysql_command_cmd) VALUES (%s,%s)",
				(attackid, cmd))
			r = cursor.execute("""SELECT CURRVAL('dionaea.mysql_commands_mysql_command_seq')""")
			command = cursor.fetchall()[0][0]
 
			for i in args:
				cursor.execute("INSERT INTO dionaea.mysql_command_args (mysql_command, mysql_command_arg_data, mysql_command_arg_index) VALUES (%s,%s,%s)",
					(command, i[1], i[0]))
 
		print("[%s] mysqlcommand ref %i: %i %s" % (user.room_jid.as_unicode(), ref, cmd, args))

commit

carniwwwhore

Last step for logxmpp is extending The Ore to present the information. First The Ore needs to know about the new tables:

class mysql_command(models.Model):
	mysql_command = models.AutoField(primary_key=True)
	connection = models.ForeignKey(connection, to_field='connection',db_column='connection')
	mysql_command_cmd = models.IntegerField()
	class Meta:
		managed = False
		db_table = 'dionaea\".\"mysql_commands'
 
class mysql_command_arg(models.Model):
	mysql_command_arg = models.AutoField(primary_key=True)
	mysql_command = models.ForeignKey(mysql_command, to_field='mysql_command',db_column='mysql_command')
	mysql_command_arg_index = models.IntegerField()
	mysql_command_arg_data = models.TextField()
	class Meta:
		managed = False
		db_table = 'dionaea\".\"mysql_command_args'

second, add the required bits to render the information to the templates:

+                       {% comment %} mysql section {% endcomment %}
+                       {% for d in child.mysql_command_set.all %}
+                       <li>
+                               {{ d.mysql_command_cmd }}
+                               {% for c in d.mysql_command_arg_set.all %}
+                                       '{{ c.mysql_command_arg_data }}'
+                               {% endfor %}
+                       </li>
+                       {% endfor %}
+

commit

Bait

Accessing an empty database is boring, one should put in some value. Learning from the best, I remember cc information is a nice gift for those seeking something which seems valuable, so if you feel challenged to write some script which creates 500-1000 human names and random cc information, I'd appreciate it.
Of course we can't compete with the major players in loosing customer data here, but it is pretty difficult to serve 100.000.000 records with sqlite - the transaction would block the rest of the process.
I already made up my mind where to gather human names, got to be somehow like a telephone book, so the facebook dump might be an option, simply gather the 10k most popular first and last names, the number of permutations should be way more than enough.

fakenamegenerator

Create a mass order at fakenamegenerator, opt in for the following fields:

  • Gender
  • Given name
  • Surname
  • Street address
  • City
  • State
  • Postal code
  • Country abbreviation
  • Credit card type
  • Credit card number
  • Credit card CVV2/CVC
  • Credit card expiration

You'll receive a mail with the subject “Your ordered Fake Name Generator identities”. Unpack the attached zip file and rename the csv file to customers.csv.

Create a sqlite database of the csv file using 5)

./csv2sqlite.py --database mysql_bait.sqlite customers.csv --primary-key customer
Processing File customers.csv
Using column names Gender GivenName Surname StreetAddress City State ZipCode Country CCType CCNumber CVV2 CCExpires

Now, update the CC numbers in the database to be valid numbers using 6):

./updateccs.py --table ccs mysql_bait.sqlite --type-col CCType --num-col CCNumber
UPDATE ccs SET CCNumber=CAST(gencc(CCType) AS INTEGER)
updated the ccs for 12345 rows

The database is complete, lets teach dionaea about it.
First copy the file somewhere appropriate:

cp mysql_bait.sqlite /opt/dionaea/var/dionaea/

Then update the dionaea config:

                mysql = {
                        databases = {
                                information_schema = {
                                        path = ":memory:"
                                }
                                tmp_customers = { 
                                        path = "/opt/dionaea/var/dionaea/mysql_bait.sqlite" 
                                }

                        }
                }

restart dionaea and test it:

mysql -u common -h 127.0.0.1
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1729232896
Server version: 5.0.54 Gentoo Linux mysql-5.0.54

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL v2 license

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| tmp_customers      |
| information_schema |
+--------------------+
2 rows in set (0.01 sec)

mysql> use tmp_customers;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| customers      |
+----------------+
1 rows in set (0.01 sec)

mysql> use customers;
ERROR: 
No such database
mysql> select * from customers limit 1;
+----------+--------+-----------+---------+---------------------------+-----------------+-------+---------+---------+------------+------------------+------+-----------+
| customer | Gender | GivenName | Surname | StreetAddress             | City            | State | ZipCode | Country | CCType     | CCNumber         | CVV2 | CCExpires |
+----------+--------+-----------+---------+---------------------------+-----------------+-------+---------+---------+------------+------------------+------+-----------+
| 1        | female | Kimberly  | Cooper  | 3916 West Virginia Avenue | North Greenbush | NY    | 12144   | US      | MasterCard | 5208326038753446 | 322  | 1/2015    |
+----------+--------+-----------+---------+---------------------------+-----------------+-------+---------+---------+------------+------------------+------+-----------+
1 row in set (0.04 sec)

mysql> Bye

Comments

1

how can i send you the fake-credit card file? maybe by mail or web upload?

guly
2011/05/16 17:57
2

I want just reply to this message “http://sourceforge.net/mailarchive/message.php?msg_id=27441025

Error in installation dionaea :

binding.c:10037: error: initialization from incompatible pointer type
error: command 'gcc' failed with exit status 1

You must delete this lignes in binding.pyx

        #def __hash__(self):
              #return <long>self.thisptr
hafidh
2011/05/18 12:59
3

@hafidh: simply subscribe to the ml? Your 'fix' is invalid, it will break things at runtime, refer to the post you cited yourself for proper instructions.

Markus
2011/05/19 08:20
4

@guly: would be great if it was a script creating a random database, for a script, paste it and I'd include it in git. A static database will have all users offering the same database.

Markus
2011/05/19 08:21
5

Markus, thanks for Dionaea !

For the CC part, there is a python script here : http://www.darkcoding.net/credit-card-generator/ ruled by GNU General Public Licence.

“A simple program to generate credit card numbers that pass the MOD 10 check (Luhn formula)”

Hope it can help.

Kafeine
2011/06/08 00:14
6

@Kafeine: thanks for the hint - added the required bits and words to create bait.

Markus
2011/06/09 14:13


2011/05/15/extending_dionaea.txt · Last modified: 2011/06/09 14:12 by common
chimeric.de = chi`s home Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0