dionaea-mysql.py.diff: Patch for dionaea to capture cna12 MySQL binaries

#####################################################################
# Unified diff (patch file) to modify Dionaea's mysql.py module     #
# to enable it to capture binaries from the cna12 MySQL attacks     #
#                                                                   #
# Application instructions                                          #
#   [from Dionaea's top level source directory]                     #
#   cd ./modules/python/scripts/mysql                               #
#   patch -l < dionaea-mysql.py.diff                                #
#                                                                   #
# dionaea-mysql.py.diff v2013.04.16                                 #
# https://malwaremusings.com/supporting-files/dionaea-mysql-py-diff/ #
#####################################################################

--- mysql.py.dist       2012-11-24 11:27:00.000000000 +1100
+++ mysql.py    2013-01-31 00:01:41.323397721 +1100
@@ -30,12 +30,15 @@
 from .include.packets import *
 import logging
 import sqlite3
+import tempfile

 logger = logging.getLogger('mysqld')
 class mysqld(connection):
        def __init__ (self):
                connection.__init__(self,"tcp")

+               self.autocommit = 1
+
        def handle_established(self):
                self.config = g_dionaea.config()['modules']['python']['mysql']['databases']

@@ -89,8 +92,51 @@

        def _handle_COM_QUERY(self, p):
                r = None
-               if p.Query.startswith(b"SET "):
-                       r = MySQL_Result_OK(Message="#2")
+               #logger.info("Q: %s\n" % p.Query)
+               if p.Query.upper().startswith(b"SET "):
+                       #r = MySQL_Result_OK(Message="#2")
+
+                       if (p.Query.upper() == b'SET AUTOCOMMIT=0'):
+                               logger.info("   Setting autocommit to 0")
+                               self.autocommit = 0
+                               r = MySQL_Result_OK(ServerStatus = 0x000)
+                       else:
+                               # check for 0x4D5A
+                               qry = p.Query.lower()
+                               try:
+                                       # strip white space
+                                       qry = qry.translate(None,b"\r\n\t ")
+                               except Exception as e:
+                                       logger.info(e)
+
+                               # convert it to a string so .find() will work
+                               qry = str(qry)
+                               #logger.info("q: %s\n" % qry)
+                               peidx = qry.find("0x4d5a")
+
+                               if (peidx >= 0):
+                                       # extract PE file
+                                       idx = peidx + 2
+                                       while ((qry[idx] in "0123456789abcdef\r\n\t ") and (idx < len(qry))):
+                                               #logger.info("[%d]: %c" % (idx,qry[idx]))
+                                               idx = idx + 1
+                                       logger.info("pe @ %d - %d" % (peidx + 2,idx))
+                                       hexstr = qry[peidx + 2:idx]
+                                       #logger.info("PE: %s\n" % hexstr)
+                                       try:
+                                               f = tempfile.NamedTemporaryFile(delete = False,prefix = "mysql-",suffix = ".tmp",dir = g_dionaea.config()['downloads']['dir'])
+                                               f.write(bytes.fromhex(hexstr))
+                                               f.close()
+                                       except Exception as e:
+                                               logger.info(e)
+
+                                       i = incident("dionaea.download.complete")
+                                       i.path = f.name
+                                       i.url = "hex://" + self.remote.host
+                                       i.con = self
+                                       i.report()
+
+                               r = MySQL_Result_OK()
                elif p.Query == b'select @@version_comment limit 1':
                        r = [MySQL_Result_Header(FieldCount=1),

@@ -158,6 +204,57 @@
                                x = MySQL_Result_Row_Data(ColumnValues=[res[name] for name in names])
                                r.append(x)
                        r.append(MySQL_Result_EOF(ServerStatus=0x002))
+               elif p.Query.upper() == b'SELECT VERSION()':
+                       logger.info("   EOF with autocommit: %d" % self.autocommit)
+                       r = [MySQL_Result_Header(FieldCount = 1),
+
+                               MySQL_Result_Field(Catalog='def',
+                                       Database=b'',
+                                       Table=b'',
+                                       ORGTable=b'',
+                                       Name=b'VERSION()',
+                                       ORGName=b'',
+                                       CharSet=33,                     # utf8, collate utf8_general_ci
+                                       Length=8,
+                                       Type=FIELD_TYPE_VAR_STRING,
+                                       Flags=FLAG_NOT_NULL,
+                                       Decimals=31),
+                               MySQL_Result_EOF(ServerStatus=self.autocommit * 0x002)]
+
+                       r.append(MySQL_Result_Row_Data(ColumnValues=["5.5.28-1"]))
+                       r.append(MySQL_Result_EOF(ServerStatus=self.autocommit * 0x002))
+               elif p.Query.upper().startswith(b"SELECT XPDL3("):
+                       qry = p.Query.lower()
+                       try:
+                               # strip white space
+                               qry = qry.translate(None,b"\r\n\t ")
+                       except Exception as e:
+                               logger.info(e)
+
+                       # convert it to a string so .find() will work
+                       qry = str(qry)
+                       #logger.info("q: %s\n" % qry)
+
+                       urlidx = -1
+                       httpidx = qry.find("'http:")
+                       httpsidx = qry.find("'https:")
+                       if (httpidx >= 0):
+                               urlidx = httpidx + 1
+                       elif (httpsidx >= 0):
+                               urlidx = httpsidx + 1
+
+                       if (urlidx >= 0):
+                               urlend = qry.find("'",urlidx)
+                               if (urlend >= 0):
+                                       url = qry[urlidx:urlend]
+                                       logger.info("U: %s\n" % url)
+
+                                       i = incident("dionaea.download.offer")
+                                       i.con = self
+                                       i.url = url
+                                       i.report()
+
+                                       r = MySQL_Result_OK(Message='#ok...(c:\isetup.exe) portnumber (3389) osversion (WinXP)',ServerStatus=self.autocommit * 0x002)
                else:
                        p.show()
                        try:

3 comments on “dionaea-mysql.py.diff: Patch for dionaea to capture cna12 MySQL binaries

  1. Pingback: Capturing the cna12 MySQL Attacks with Dionaea | Malware Musings

    • Hi Marcelo,

      Thanks for highlighting this and apologies for the delay, but I’d over estimated the complexity of the problem and the amount of work involved and hence the amount of time required to fix it.

      I’d assumed that the Dionaea code had changed and that I needed to find some time to go through and update my changes, when in fact my original copy of the patch still works with the latest Dionaea code.

      Yesterday, I went back to trying to get my HoneyDrive3 installation to a point where I can use it to replace my existing honeypot set-up, and naturally wanted to modify the Dionaea installation there-on to capture these MySQL attacks. So I thought that that would be a good time to test this patch.

      You’re right. A simple copy and paste version of the patch from my blog page will fail to apply. There are two reasons. One, there is a missing blank line around line 85, for some reason. Two, a copy and paste means that you end up with a series of spaces where there once were tabs.

      I’ve updated the copy on my blog page to include the blank line, so it is now a correctly formatted patch at least. However, the second problem still remains. You can get around this by using the ‘-l’ option when running ‘patch’. The ‘-l’ (lower case letter) option tells ‘patch’ to ignore differences in white space.

      However, I’d rather have a way for readers to get a byte-for-byte copy of scripts/patches as I’ve written them, rather than as they end up after being copied and pasted. Especially when indentation (leading white space) matters when it comes to Python scripts.

      So, I have created a sub-page of this one which simply contains a base64 encoded version of the patch shown on this page. You can copy and paste the base64 version, and then simply decode it using any of the base64 decoding utilities (‘openssl’ and ‘uudeview’ for instance).

      Unfortunately, if you’re using Windows, you may be a bit pushed for native base64 decoding utilities, although a quick Internet search shows a number of downloadable ones. I’ve also just thrown together a Python script (https://malwaremusings.com/supporting-files/b64decode-py/) which will do base64 decoding. Base64 encoding that will create a chicken-and-egg type situation though.

      Musingly,
      Karl.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s