Sunday, 19 June 2011

SmartFTP Password Recovery with Metasploit - The details

So last night I briefly mentioned the new additions I submitted to Metasploit. It looks like they will get merged after the 3.7.2 Release. Metasploit is in a feature freeze at the moment for that release. I wanted to take the opportunity to discuss how the SmartFTP Password recovery module works. It might help other who want to write similar modules in the future.

The Module Can be seen from the Metasploit Website here:

So let's get the simple stuff out of the way first. We pull the OS information and the root System drive information from the Meterpreter stdapi. We then check the OS to see whether we need to be looking for "Documents and Settings" or "Users" off the system root. We then drop into the appropriate users directory and enumerate the individual user Directories. We build the Directory paths based off the combination of all of these factors.

The enum_subdirs function then takes each of these potential SmartFTP data folder paths. If the path does not exist, or we do not have permission to access it, it will throw an exception caught by the rescue statement and will move on to the next path. If it can access the path, then it will enumerate all of the items in that directory.  If the item ends in .xml then it is added to the list of XML files to be parsed. If it is not an XML file, it is assumed to be a directory and is recursively passed back to the enum_subdirs function. In the rare case that this item is not actually a directory, it should throw an exception which will still be caught by the rescue. Once everything has been recursively enumerated, we should have a list of xmlfiles in the session array @xmlfiles.

For each xml file in the array we then run get_xml.  In get_xml we first try to open the file for reading. If for any reason we cannot do that, we catch an exception  let the users know, and move on to the next file. If we can open it, then we read all of the data into memory and send it to the parse_xml function.  parse_xml uses the Metasploit rexml library to parse the XML. We pull the host, port, username, and encrypted password. If no encrypted password is found, we skip this item and move to the next one since there is no password to steal. Once we have the encrypted password we pass it to the real meat and potatoes, our decryption routine.

The first thing we do, is unpack this encoded data as a series of hex bytes. The string is in fact a series of bytes.  So if the encoded string is "9722972FC57CAE5A78DBD64E23968440C794" then it is actually \x97\x22\x97 etc.

So now let's take a moment to look at the new Railgun function definition I have added. These functions come from Advapi32.dll. This DLL is already defined in Railgun so we just have to add the functions to it's definition:

#Functions for Windows CryptoAPI
railgun.add_function('advapi32', 'CryptAcquireContextW', 'BOOL',[
['PDWORD', 'phProv', 'out'],
['PWCHAR', 'pszContainer', 'in'],
['PWCHAR', 'pszProvider', 'in'],
['DWORD', 'dwProvType', 'in'],
['DWORD', 'dwflags', 'in']])

railgun.add_function('advapi32', 'CryptCreateHash', 'BOOL',[
['LPVOID', 'hProv', 'in'],
['DWORD', 'Algid', 'in'],
['DWORD', 'hKey', 'in'],
['DWORD', 'dwFlags', 'in'],
['PDWORD', 'phHash', 'out']])

railgun.add_function('advapi32', 'CryptHashData', 'BOOL',[
['LPVOID', 'hHash', 'in'],
['PWCHAR', 'pbData', 'in'],
['DWORD', 'dwDataLen', 'in'],
['DWORD', 'dwFlags', 'in']])

railgun.add_function('advapi32', 'CryptDeriveKey', 'BOOL',[
['LPVOID', 'hProv', 'in'],
['DWORD', 'Algid', 'in'],
['LPVOID', 'hBaseData', 'in'],
['DWORD', 'dwFlags', 'in'],
['PDWORD', 'phKey', 'inout']])

railgun.add_function('advapi32', 'CryptDecrypt', 'BOOL',[
['LPVOID', 'hKey', 'in'],
['LPVOID', 'hHash', 'in'],
['BOOL', 'Final', 'in'],
['DWORD', 'dwFlags', 'in'],
['PBLOB', 'pbData', 'inout'],
['PDWORD', 'pdwDataLen', 'inout']])

railgun.add_function('advapi32', 'CryptDestroyHash', 'BOOL',[
['LPVOID', 'hHash', 'in']])

railgun.add_function('advapi32', 'CryptDestroyKey', 'BOOL',[
['LPVOID', 'hKey', 'in']])

railgun.add_function('advapi32', 'CryptReleaseContext', 'BOOL',[
['LPVOID', 'hProv', 'in'],
['DWORD', 'dwFlags', 'in']])

There is a lot going on here. So we'll take it a little slow.
For those who don't know, Railgun is a part of Meterpreter that allows a user to hook Windows libraries and access their functions. In this case we are hooking the Advapi32.dll to gain access to the Windows CryptoAPI(CAPI) functions.

CryptAcquireContextW is the Unicode version of the AcquireContext function. This creates the Cryptographic context we will be working in. The first parameter is a pointer to the provider object. the provider object is, in of itself, a pointer to a data structure that will be initialised by this function. So we pass it a pointer to a DWORD for the pointer to be placed in, and set it as an out parameter so the function will return the pointer information to us. We are not using the container object in this case, so we pass it nil. We then tell it what provider to use in this case it is the Microsoft Enhanced Cryptographic Provider. This is passed as a pointer to a string. We then pass it a value to tell it what type of provider to use. WinCrypt.h normally provides Constants for this, but since we don't have access to those constants we pass it the raw numerical value of that constant. In this case we are passing it the value for the RSA_FULL provider. Finally we pass it the appropriate flags. Like the provider type this expects a constant so we need to pass it a numerical value. We pass it CRYPT_VERIFY_CONTEXT(0xF0000000). For more details on the available flags I suggest you look at the MSDN Doc.

CryptCreateHash creates a hash object for us to user in much the same way that AcquireContext created a provider object. We pass it the provider object as the first parameter. Remembering that this object is a pointer to an abstracted in-memory data structure. Then we pass it the algorithm id for what kind of hash algorithm we will be using. Again, this is typically expecting a constant we don't have so we pass it a numerical value. If this hashing algorithm is expecting a key we pass it a key object. Since we are using md5 we pass it a 0 to tell it we are not using a key. We pass it a 0 for the flags. Finally we pass it a pointer to a place in memory for it to store the hash object. Just like the provider object, this is actually a pointer to a memory structure intialised by this function.

CryptHashData is what will actually create the hash for us and put it in the hash object. The first thing we do is pass it our hash object. We then pass it the data to be hashed. In this case we are hashing the string "SmartFTP". We then pass it the data length of 16, and again we pass it no flags with a 0. This is the first step to deriving our Encryption Key.

CryptDeriveKey is going to take our hash and derive an encryption key from it. We pass it our provider object, an integer value for our encryption algorithm(RC4), the Hash object, our flag, and a pointer to a key object. This key object works the same way as the hash and provider objects did before it. The function derives an RC4 key for us and puts it in the memory structure pointed to by our key object.

CryptDecrypt is where the magic finally happens. We pass it our key object as the first parameter. If the data was to be decrypted and hashed at the same time we would pass it a hash object. Since we do not want to hash the results we pass it a 0. The next parameter is whether this is the final section to be decrypted. We pass this a value as true. We pass no flags, and a pointer to the data to be decrypted. Finally  we pass it the length of the data to be decrypted.

The remaining three functions are eseentialy just garbage collection. They close out the memory structures we initalised along the way.

Some of the parameters in these function calls are still not set up in the most ideal fashion. One of the big tricks to remember is that in the end, a pointer is just a number. So even though ruby has no pointer, just treat them as a numbers and pass them back to LPVOIDS.  I will be smoothing out any wrinkles in these function defs soon, and adding the other CAPI functions that I didn't need for this particular module.

Once the decryption is complete. The module displays the results back to the console. It also reports the data back to the backend database. This means the credentials will be stored for the target machines. If you use MetaSploit Pro (which if you are a professional Penetration Tester, I cannot recommend it enough) this will be especially useful. If the remote machines are in your project scope, those credentials will show up in the host information, and can be used for further module usage.

So there you have it. I hope you find this detailed breakdown useful and/or informative. Stay tuned for further updates and developments. I am continuing on my goal of making it into the Top contributors list, but I suspect I still have a ways to go.

No comments:

Post a Comment