Opportunities multiply as they are seized.
I like concise nuggets of ancient wisdom, and often old truisms of war apply to the field of cyber-security. This one seems quite apt here. Another great saying also springs to mind: know your enemy. In the realms of cyber security, it's fair to start from the assumption that your attacker will be motivated and capable. If you are to simulate an attack from such a threat, you need to have a motivated and capable party performing the attack. That's where we come in.
And so began one of our Red Team engagements. We were commissioned to try and penetrate the defenses of a client, and after a successful initial incursion into the inside of their network through a carefully crafted phishing campaign, we were at the network reconnaissance stage. Upon looking around their network, it became apparent that they were using an infrastructure management tool from BMC called 'PATROL'. Now PATROL makes use of credential files containing encrypted passwords, but in a properly hardened PATROL environment these files should not be present on network shares, and should be protected by NTFS permissions. However, looking around various hosts and temporary directories on the network in question, it appeared that these password files had been placed on network shares and left unprotected. In an attack, such a resource is like a red flag to a bull, and just begs the question: can these encrypted passwords be decrypted? And will the resulting plain text passwords allow us further inroads on the client network?
To answer this question, we need to hone in on the part of the product that does the decrypting (or, in the first place, the encrypting). This will hopefully allow us to work out what the encryption algorithm might be and give us a chance at decrypting the numerous encrypted passwords we had begun to collect on our way around the network. We then happened upon a command-line tool on one of the hosts that forms part of the PATROL suite: "sec_encrypt_p3x.exe". It certainly looked like what we were after, so we grabbed a copy to study offline. It turns out this tool is quite simple in that it takes one command line argument of the plain text password to be encrypted, and it prints the resulting encrypted password to the standard out:
D:\sec_encrypt_p3x>sec_encrypt_p3x.exe Usage: sec_encrypt_p3x.exe [-patrol_conf] <plain text password> sec_encrypt_p3x.exe -h sec_encrypt_p3x.exe -v D:\sec_encrypt_p3x>sec_encrypt_p3x.exe -v sec_encrypt_p3x.exe V9.0.70:Build_201208220914:20120822:nt-50-x86:Aug 22 2012 D:\sec_encrypt_p3x>sec_encrypt_p3x.exe context 1516379765771364
So we know that within this executable, or the DLLs upon which it is dependent, is the encryption algorithm used to encrypt the password. If we can hone in further on the relevant code, we have a chance at working out what the algorithm is and perhaps reimplementing it for our own purposes: to decrypt the encrypted passwords we have collected. So the first thing to do is to look at the binaries involved, and see if we can start by disassembling them easily. As long as there is no executable protection in the form of a packer, then we won't have to worry about unpacking the code first before we begin looking for encryption related artifacts such as mathematical constants, tables or even strings that might be the hallmark of the encryption implementation.
bmc9_t.dll globlc32_t80.dll i18n9_t.dll oss9_t.dll pcl9_t.dll sec_encrypt_p3x.exe tss9_t.dll
The first thing to do is to look at the imported functions from the main module, sec_encrypt_p3x.exe. We see that in total, it imports 67 functions from 6 DLLs. Three of these are Windows DLLs: kernel32.dll, msvcr80.dll and msvcp80.dll. The other three are DLLs that the executable is packaged with: bmc9_t.dll, i18n9_t.dll and oss9_t.dll. From these DLLs, the main executable imports the following functions:
Module name: oss9_t.dll OSS_Init [email protected][email protected]@[email protected]@[email protected]@Z [email protected]@[email protected][email protected]@[email protected]@@Z [email protected][email protected]@[email protected]@[email protected]@@Z [email protected]@[email protected] Module name: i18n9_t.dll [email protected]@[email protected]@Z [email protected]@[email protected] [email protected][email protected]@QBEPBDXZ Module name: bmc9_t.dll [email protected]@[email protected] [email protected]@[email protected]@Z [email protected]@[email protected]@@Z ??Ybmc_str[email protected]@[email protected]@Z [email protected]@[email protected]@@Z [email protected]@[email protected]
The obvious function that stands out is [email protected][email protected]@[email protected]@[email protected]@Z. This has a 'mangled' function name which means a more simple function name 'encryptPatrol3x' was decorated with other information by Microsoft's C++ compiler. In this case it's apparent that the compiler used was Microsoft Visual C++ version 8 (we can tell this from the Visual C runtime library names msvcr80.dll and msvcp80.dll) which is better known as part of Visual Studio 2005. It would appear that a function with the name 'encryptPatrol3x' could be just what we are looking for. Upon disassembling sec_encrypt_p3x.exe we find that this function is only called from one place in the main executable - it has only one cross-reference in IDA, using Ctrl-X:
We see this call comes in the code at address 0x401714. Disassembling oss9_t.dll, we can examine the internals of this function. At this point, it's a good idea to see if we can find any constants or tables that might give away the encryption algorithm used. For this, I chose to use PEiD with the 'Kanal Krypto Analyzer' plugin:
We can see that the plugin finds quite a few cryptographic constants:
BASE64 table :: 000D0460 :: 100D0460 CryptGenRandom [Import] :: 00092130 :: 10092130 CryptGenRandom [Name] :: 0009CA4C :: 1009CA4C DES [key schedule] [char] :: 0009E230 :: 1009E230 MD5 :: 0006D048 :: 1006D048 SHA1 [Compress] :: 00040505 :: 10040505 SHA1 [Compress] :: 000418B5 :: 100418B5 SHA1 [Compress] :: 00042EDE :: 10042EDE SHA1 [Compress] :: 000444FA :: 100444FA SHA1 [Compress] :: 0005EAB1 :: 1005EAB1 SHA1 [Compress] :: 0005FED5 :: 1005FED5 SHA1 [Compress] :: 0006158A :: 1006158A SHA1 [Compress] :: 00062C36 :: 10062C36
One really handy feature of the Krypto Analyzer plugin is the ability to export these results as an IDC file ready for direct import into IDA. Of particular note is the DES key schedule. This stands out as the only algorithm that is directly related to encryption - the others are more closely linked to hashing algorithms (MD5, SHA1) or random number generation (the Microsoft CryptGenRandom function). If we import both bookmarks and comments into IDA, we can then go the location of the DES key schedule in the binary, and work backwards in cross references to see where this key schedule is used.
After finding the initial function that makes use of the table, we follow the cross references back a few levels to see if we can tie this in with the encryptPatrol3x function. By good fortune, we discover that a mere five levels up we do indeed find ourselves at the entry to encryptPatrol3x. This means that it does indeed look like we have found the code necessary for performing the encryption on the plain-text password, and we have established it is very likely to be the DES algorithm being used.
We can see from the disassembly of encryptPatrol3x that the function takes two parameters:
We can see that the first parameter is immediately dereferenced (in other words, it is a pointer) and that the result is passed to a string function '[email protected][email protected]@QBEPBDXZ'. Again, this is a mangled C++ name, being derived from the simpler 'c_str' in the source code. Could it be that this function simply converts an input string to a 'C-string'? We can now quickly test our assumptions by firing up the executable in the debugger with a password to encrypt. For this we will use OllyDbg (from http://ollydbg.de). We put our test password in as the only argument and then put a breakpoint on the entry to encryptPatrol3x at 0x10034244. If we step over the call to the 'c_str' function we see that it does indeed convert our input password into a C-string. Stepping over the more interesting second function results in the following:
We see that the return value in EAX is a pointer to the encrypted password. So we have found a function that takes a C-style string as its sole input parameter and returns the encrypted string as output. We also know that the internals of this function make use of the DES encryption algorithm. We are surely honing in quite nicely on what we're interested in, so at this stage we might think about trying to identify the key used in the encryption, as well as the mode of operation: as DES is a block cipher, it might be implemented using several such modes such as cipher feedback (CFB) or cipher block chaining (CBC). If we know this and the key we could possibly implement our own DES code to try and perform the decryption. But wait. Another wise saying is 'don't reinvent the wheel'. Yet another is 'keep it simple, stupid'. Is there an even easier way of achieving our goal?
An obvious idea here is to look among the other functions that oss9_t.dll exports; if it exports a decryption function to match our encryptPatrol3x we might save ourselves a lot of trouble! Looking at the functions exported by this DLL, we see that there are many - it exports 1450 functions in all. If we grep these for the string 'decrypt' we find one match:
This looks promising at first, but we also find a corresponding encrypt function:
If we look at the encryption function, it's not obvious that it has anything in common with the function used to encrypt our password; encryptPatrol3x. So unfortunately it's not going to be quite that easy. So let's drill down into encryptPatrol3x a bit to see what we can find. We know the function at 0x10034250 takes a plain-text string as input, and outputs the resulting encrypted string. Looking inside this function, we see a simple bit of code to test the pointer passed as an argument points to a valid C-string, then finally a call to another subroutine before it returns. This sub-function must lead to the actual encryption code so we drill down another level:
Here we see a sequence of bytes being copied to a buffer one by one, before a call to a further subroutine that also takes our plain text password as a parameter. These bytes look to be ASCII characters - potentially making up the string 'k$C4}@"_'. Could this be a key, hard coded into the binary, being passed to the actual encryption function? And what about the string of all the hexadecimal digits ('0123456789ABCDEF') and the call that follow - might these form a binary to hexadecimal conversion for the resulting encrypted password? The function at 0x10069B10 looks really interesting - let's go one level deeper into the rabbit hole...
The internals of this next function look a good deal more complicated, with many subroutines, memory allocations and loops. Certainly it could be our encryption function. But one thing that's well worth noting is that there are two cross references to this function. One from encryptPatrol3x of course - that's how we got here. But what about the other? Let's have a look at that parent function:
Again, we can see what looks like the hard-coded key being set prior to the call. We can also see the hexadecimal character conversion function, this time preceding the function. If this is happening in reverse order here (i.e. converting from hexadecimal characters to binary), could this be an equivalent decryption function? Going up one more level from here, we see the parent function is almost identical to the equivalent in the encryption routine; it takes a single argument, tests for NULL pointer, then dereferences and looks for a NULL terminator before passing to the subroutine that we came from. This all points to this function taking a C-style string as its argument, and passing it to the encryption function, just like 'encryptPatrol3x' but hopefully working in reverse.
The proof is in the pudding, as they say, so let's test this out dynamically. We'll input the encrypted password instead of the plain text, and put a breakpoint on the 'encrypt' function at 0x1006A1A0. We'll then change the instruction pointer (using the 'New origin here' option in Olly) to our suspected 'decrypt' function at 0x1006A2F0 and see what happens!
Success! We see that our plain text password is output by this function so it is indeed a decrypt function. So the next question is: is this function or a parent function exported by the DLL? If so then we can easily make use of it for our own decryption tool just by using the LoadLibrary and GetProcAddress Windows API functions. Alas the answer is no! That would perhaps be too easy. But since we know the address of this function, we can just hard code this in our code, and with a suitable declaration of the calling convention and function prototype, we can make our tool!
typedef char* (cdecl *PATROLDECRYPT)(char*); ... Library = LoadLibrary(TEXT("oss9_t.dll")); //Function isn't exported so we have to hardcode its address Decrypt = (PATROLDECRYPT) ((DWORD)Library + 0xXXXXX); // Offset obfuscated output = Decrypt(argv); printf("Decrypted password:\n%s\n", output);
We can now use our tool to quickly decrypt any PATROL-encrypted passwords we find on this and future red-team engagements. Network defenders beware!
D:\sec_encrypt_p3x\PatrolDecrypt\Release>PatrolDecrypt.exe 1516379765771364 PatrolDecrypt - Context 2015 Decrypted password: context
Upon discovering the presence of these unprotected credential files on this client’s network, Context informed BMC about their decryption as part of our routine responsible disclosure process. BMC have been very forthcoming and helpful in their response, and although the exploitation here did not depend on a vulnerability per se, BMC have addressed the issue by strengthening the encryption used to encrypt credential files, with updates to roll out with the next PATROL release, and an assignment of CVE-2016-2348 as reference.
But most importantly, they have reiterated the security best practise which dictates that credential files should not be held on network shares and should have file-level permissions to protect them for unauthorised access!
Contact and Follow-up
Kevin O’Reilly is a Senior Consultant at Context. If you would like to find out more about Context or would like to discuss anything in this blog, please get in touch with our team via the Contact page or email [email protected]