Recently Toufik Airane published a write up on Github about an issue with McAfee Virus Scan Enterprise. The vulnerability deals with the SiteList.XML file used by McAfee Virus Scan Enterprise to store credentials for various update mechanisms. Depending upon how the update mechanisms are configured this can lead to the disclosure of domain credentials if the update mechanism is using a UNC path.
Interestingly enough, FusionX has been using this issue for several years. The bug was first reported back in 2011 by SySS GmbH. Over the years the techniques used to decrypt the passwords has changed due to changes in McAfee Virus Scan Enterprise. However, FusionX spent the time to reverse engineer the responsible code and determined the actual encryption algorithm used. The importance of this is that the algorithm has never changed since the initial disclosure back in 2011 and McAfee has neglected to ever fix this vulnerability. Intel has responded to Toufik Airane’s Github post indicating that this is not a vulnerability but a configuration issue. The Intel response also outlines best practices for using the product update features.
FusionX reverse engineered the code responsible for handling the SiteList.XML file. The process began with setting up a Windows virtual machine and installing McAfee Virus Scan Enterprise. The next step was to setup the update mechanisms with accounts and varying password. At this point FusionX inspected the SiteList.XML file contents. The following screenshot shows the file content after setting up some test accounts with different passwords.
As can be seen in the red outlined areas, there are portions of the Base64 encoded data that repeats. This means that portions of the Base64 encoded data for different passwords contain the same data. Later on the reasoning behind this occurrence will be explained.
The next step in reverse engineering this code is to locate the process that actually reads and parses the SiteList.XML file. This can be done using ProcessMonitor from Microsoft Windows SysInternals. This tool will monitor processes and report back information regarding what they are doing. In this case we are only interested in seeing processes that read the SiteList.XML file. To limit the scope of the output filters can be used to find only the processes reading the SiteList.XML file. The red outline in the screenshot below shows how the filter for Process Monitor was constructed.
After monitoring with the filter applied, the results show the UdaterUI.exe process as the only process that touches the SiteList.XML file.This gives us a good starting point for investigating the parsing and decryption code.
At this point the best option is to perform some runtime analysis and get an idea of what DLL (dynamic link library) the UdaterUI.exe application utilizes. To perform this analysis we need to attach a debugger, so we naturally used our debugger of choice, Windbg. However, upon initially attaching it became clear we hit a road block.
The above screenshot shows an error dialog when trying to attach to the UdaterUI.exe process. At first glance this error appears to be a permissions issue. After some digging it turned out that was not the case, but the real problem was McAfee was protecting the process via the Access Protection Rules.
The screenshot shows the Access Protections Rules that prevent debugger access to the process. After disabling the specific Access Protection Rules that prevented us from attaching a debugger to the UdaterUI.exe process, we can now attach without any further problems.
Once attached we need to locate the code we are interested. The simplest way to do this is to set a breakpoint on the CreateFileA and CreateFileW Windows API functions. These functions are used to obtain a handle to a file that can then be used for reading or writing operations.
0:005> bp kernel32!CreateFileA
0:005> bp kernel32!CreateFileW
ModLoad: 64600000 64607000 C:\Program Files\McAfee\Common Framework\0409\CmaUIRes.dll Breakpoint 0 hit
eax=0012e590 ebx=ffffffff ecx=78abade3 edx=009da710 esi=0012e674 edi=7c801a28 eip=7c801a28 esp=0012e564 ebp=0012e5c0 iopl=0 nv up ei pl nz ac po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213 kernel32!CreateFileA:
7c801a28 8bff mov edi,edi
0:000> dd esp
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\McAfee\Common Framework\MSVCR100.dll –
0012e564 78b27359 009daac0 80000000 00000003
0012e574 0012e590 00000003 00000080 00000000
0012e584 00000000 0012e674 00008000 0000000c
0012e594 00000000 00000001 00000000 7c9101e0
0012e5a4 ffffffff 00000003 00000003 00000080
0012e5b4 80000000 00000000 00000008 0012e610
0012e5c4 78b27983 0012e5f4 009daac0 00008000
0012e5d4 00000040 00000180 d9b1a18d 00000080
0:000> da 009daac0
009daac0 “C:\Documents and Settings\All Us”
009daae0 “ers\Application Data\McAfee\Comm”
009dab00 “on Framework\SiteList.xml”
Inspecting the call stack from here will give us the exact location where the CreateFile API was called from.
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong. 0012e560 78b27359 009daac0 80000000 00000003 kernel32!CreateFileA
0012e5c0 78b27983 0012e5f4 009daac0 00008000 MSVCR100!fstat32+0x58e
0012e610 78b27a41 009daac0 00008000 00000040 MSVCR100!open+0x13b
0012e630 78b039ef 0012e674 009daac0 00008000 MSVCR100!sopen_s+0x1b
0012e668 78b03d86 009daac0 00000003 00000040 MSVCR100!putwchar+0x26c
0012e6ac 78b03dde 78b53068 6503934c 00000040 MSVCR100!fsopen+0x97
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\McAfee\Common Framework\naxml3_71.dll –
0012e6c0 65030683 009daac0 6503934c d9a18120 MSVCR100!fopen+0x12
0012e714 6503056c 0012e908 0012e784 0012e7dc naxml3_71!AdvXMLParser::ReadUtf8BufferFromFileNoExp+0x173
0012e760 650309b3 0012e908 0012e784 0012e848 naxml3_71!AdvXMLParser::ReadUtf8BufferFromFileNoExp+0x5c
0012e7b4 65030a53 0012e908 0012e8ec 0012e848 naxml3_71!AdvXMLParser::ReadBufferFromFileNoExp+0x73
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\McAfee\Common Framework\mfeCmnLib71.dll –
0012e7e4 64a6e50d 0012e908 0012e8ec 0012e848 naxml3_71!AdvXMLParser::ReadBufferFromFile+0x33
0012e938 64a7a895 009d8c6c 009d8cc0 009d8ca4 mfeCmnLib71!sitehlp::CSitesHelper::GetSitelistVersions+0x21d
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\McAfee\Common Framework\UdaterUI.exe
0012ebf4 0040a072 00000000 d9a4d770 00001409 mfeCmnLib71!sitehlp::CSitesHelper::CSitesHelper+0x265
0012f520 004163d7 004f01a6 00000000 00000000 UdaterUI+0xa072
0012f550 004164c9 000901fc 00001409 00000000 UdaterUI+0x163d7
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\USER32.dll –
0012f5a0 7e418734 00000000 00000000 00000000 UdaterUI+0x164c9
0012f5cc 7e418816 00a50ff0 000901fc 00001409 USER32!GetDC+0x6d
0012f634 7e428ea0 00000000 00a50ff0 000901fc USER32!GetDC+0x14f
0012f688 7e428eec 006db0a8 00001409 00000000 USER32!DefWindowProcW+0x180
0012f6b0 7c90e473 0012f6c0 00000018 006db0a8 USER32!DefWindowProcW+0x1cc
0012f700 7e419402 0012f88c 00000000 00000000 ntdll!KiUserCallbackDispatcher+0x13 0012f72c 004127f2 0012f88c 00000000 00000000 USER32!PeekMessageW+0x167
0012f75c 0041612b d9a4dd7c 004480b4 00000001 UdaterUI+0x127f2
0012ff2c 0041a6e7 00400000 00000000 0002069c UdaterUI+0x1612b
0012ffc0 7c81776f 0012ec08 001310a4 7ffd8000 UdaterUI+0x1a6e7
0012fff0 00000000 0041a81a 00000000 78746341 kernel32!RegisterWaitForInputIdle+0x49
Focus at this point turned directly to the exported CSitesHelper::AddUNCSite function. While looking at this function it became pretty obvious that the XML file was being parsed here.
In this screenshot we can see the AdvXMLParser class is being used to append XML elements. The instruction mov eax, off_64BBA4D0 is storing a pointer to the string “Password” in the EAX register and then appending the element to the XML.
Based on the format of the SiteList.XML file we can assume this is creating the password element. The password element contains the Base64 encoded text node. The next step is to look for code that is appending a text node to the XML.
Further down in the function the AddText function is called. This must be where the encrypted password is handled and stored as a Base64 encoded text node. Near the bottom of the above screenshot we can also see the “Encrypted” attribute being created.
In this screenshot we can see the “Password” element and the encrypted attribute children of the UNCSite element. The code that was just located seems to be responsible for creating this XML structure. Within this range of code, between the appending of the “Password” element and the appending of the text node is where the encryption of the password is likely to occur.
While looking within the range for function calls an interesting function call is made which is obviously related to cryptography. This function then calls the mcafee_com::MA::crypto::CryptoShim::instance method followed directly by calling the mcafee_com::MA::crypto::CryptoShim::create_encryption_object method. It then quickly calls functions, which destroy the encryption object. The full function assembly listing is depicted in the following screenshot.
Based on the function prototype for the mcafee_com::MA::crypto::CryptoShim::instance function we know that it does not take an argument. This means the mcafee_com::MA::crypto::CryptoShim::create_encryption_object function receives the argument 0x03. Based on the function prototype this argument represents the EncryptionType.
After loading the cryptshim.DLL in IDA and making some adjustments in the mcafee_com::MA::crypto::CryptoShim::create_encryption_object function we can see a switch statement against the EncryptionType argument.
By tracing the jump table we are able to locate the specific case we are interested in (0x03). The following screenshot depicts this code.
At the beginning of the listing we see a call to the new operator. This is creating an object of size 0x1C and storing it in var_B4 and also a copy in var_68. We can see that var_68 is passed to the constructor_IEncryption function, which is responsible for copying the virtual function table into the new object. Further inspection of the constructor_IEncryption function reveals it actually performs the encryption. We can see that two arguments are being passed to the constructor_IEncryption function, the value 0x07 and var_68. Determining the meaning behind the value 0x07 is essential. We already know that var_68 is a reference to the newly allocated object.
Looking at the code we see it is essentially a wrapper around the mcafee_com::MA::crypto::IEncryption::IEncryption constructor. However, when looking closer we see it also creates a key manager object.
This code calls the mcafee_com::MA::crypto::CryptoShim::create_manager_object method. The EAX register holds the newly created key manager object, which is then stored in a local variable.
The following code listing is related to the creation of the key manager object. One interesting take away from this code is the error message “Unable to get legacy symmetric key”; this gives a hint about the encryption algorithm being used.
When we follow this code path we notice that there is a call to the mc_CIPHER_new function, specifically, there are two calls to this function one right after another. After calling mc_CIPHER_new twice, a call to mc_CIPHER_get_info is called. Another interesting thing of note is that going back to to the switch case, the constructor_IEncryption function is only called in case 3. All of this combined likely indicates that this is a call for a specific cryptographic algorithm.
This is a good point to jump back to WinDBG and perform some further active reversing. The technique used for this analysis was to set a breakpoint on a function we have determined happens prior to the decryption of the password. We can then step over any calls and search the memory for the decrypted password. This technique relies on the fact we know the decrypted value of the password. Since we initially setup the update sites we already know this password. Once the decrypted password string is found in memory, we can then inspect the previous function call and set a breakpoint on that function. We then start this whole process over again. Using this technique we are able to peel back the layers of code paths and get closer to the end goal of locating the decryption routine. Another interesting feature of this technique is it can be completely scripted in WinDBG.
Using this technique FusionX discovered the decryption routine resided within the ccme_base.DLL library. We then inspected the arguments being passed to the decryption routine and discovered that the string being acted on was not the Base64 decoded version of the string.
The following is a log from WinDBG showing the results of using this technique:
Breakpoint 1 hit
eax=03ad7fd0 ebx=00000000 ecx=038d6fc0 edx=00000028 esi=00000028 edi=038d6fc0
eip=039d5b6c esp=0012e254 ebp=01e46fe0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
039d5b6c e81f930100 call ccme_base!R_FIPS140_MODULE_set_test_details_cb+0x4de10
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0012e264 039d3427 01e48e20 03ad7fd0 038d6fc0
0012e278 039d10b3 01e48e20 03ad7fd0 038d6fc0
0012e2a0 039c17d1 01e46fe0 03ad7fd0 0012e38c
00000000 00000000 00000000 00000000 00000000
0:000> dd 038d6fc0
038d6fc0 91e363b7 cbfdabd7 15bf7c6d dd5bd32d
038d6fd0 15bf7c6d dd5bd32d 15bf7c6d dd5bd32d
038d6fe0 15bf7c6d dd5bd32d 00000000 00000000
038d6ff0 00000000 00000000 c0c0c000 c0c0c0c0
038d7000 ???????? ???????? ???????? ????????
038d7010 ???????? ???????? ???????? ????????
038d7020 ???????? ???????? ???????? ????????
038d7030 ???????? ???????? ???????? ????????
0:000> dd 03ad7fd0
03ad7fd0 00000000 00000000 00000000 00000000
03ad7fe0 00000000 00000000 00000000 00000000
03ad7ff0 00000000 00000000 c0c0c000 c0c0c0c0
03ad8000 ???????? ???????? ???????? ????????
03ad8010 ???????? ???????? ???????? ????????
03ad8020 ???????? ???????? ???????? ????????
03ad8030 ???????? ???????? ???????? ????????
03ad8040 ???????? ???????? ???????? ????????
0:000> dd 1e48e20
01e48e20 01e64fe8 039fc3a0 00000000 00000000
01e48e30 01e48e68 01e48e60 000001e0 01e48e60
01e48e40 01e48e74 00000018 01e4cfe8 00000018
01e48e50 00000000 00000101 01e49000 00000000
01e48e60 00000000 00000000 00000000 00000000
01e48e70 00000000 039fdb68 ac8800a0 0400c101
01e48e80 e0808008 03004207 a0d88800 00410806
01e48e90 08901040 04c20c0c 60101404 8580004c
0:000> dd 01e4cfe8
01e4cfe8 b836f13e bcef3bb3 b5a72634 371ac44e
01e4cff8 9b19d37c 00000000 ???????? ????????
01e4d008 ???????? ???????? ???????? ????????
01e4d018 ???????? ???????? ???????? ????????
01e4d028 ???????? ???????? ???????? ????????
01e4d038 ???????? ???????? ???????? ????????
01e4d048 ???????? ???????? ???????? ????????
01e4d058 ???????? ???????? ???????? ????????
In the WinDBG log we can see the arguments being inspected. The arguments of importance are highlighted in red.
The argument at 0x038dfc0 is the encrypted password string. The argument at 0x1e48e20 is a pointer to the key, which is located at 0x01e4cfe8. I have highlighted all the relevant bytes in red. One interesting thing to note is that the 0x01e4cfe8 value is referenced multiple times in the 0x1e48e20 pointer. We see there is one reference, which is stored next to the value 0x18. When we inspect the value at 0x01e4cfe8 we see is it 0x18 bytes in length or 24 bytes in decimal. This means the key is 192 bits. I suspect the 0x3ad7fd0 argument is the destination buffer for the decrypted password.
A quick comparison of the Base64 decoded password and the string being decrypted proves that there is something else happening to the Base64 decoded password before it is passed along to the decryption routine. To figure out what is going on we used the same active reversing technique used to discover the decryption routine. The only difference this time is that instead of searching for the decrypted password we search for the first few bytes of the string being decrypted.
This leads us to a function in the cryptshim.DLL module, which performs XOR operations on the Base64 decoded password.
The screenshot depicts the first basic block of the XOR decryption function. It begins by saving a byte array of values in a local variable. As can be seen the XOR key is [0x12, 0x15, 0x0f, 0x10, 0x11, 0x1c, 0x1a, 0x06, 0x0a, 0x1f, 0x1b, 0x18, 0x17, 0x16, 0x05, 0x19]. Further inspection of the function revealed that the key was indexed based on the length of the decoded password.
This screenshot shows the graph view of the XOR decoding being performed on the Base64 decoded password. In this code edx is given the value of a byte from the key array based on the index of ecx. The eax register is the byte value, which comes from the decoded password. The two values are then XORed resulting in the XORed value that is used in the decryption routine.
At this stage we have located the decryption routine, the encryption key, and the XOR function. The only thing left to do is to determine what encryption algorithm is actually being used. Nothing within the code paths taken explicitly declares which algorithm is being used. Based on the original vulnerability report from SySS GmbH we know the algorithm is Triple DES (3DES). The only piece left is to determine the block cipher being used.
As we recall previously, the Base64 encoded password data had repeating patterns. This is due to the block cipher. If we use a password that fills the block space perfectly the repeating patterns are no longer seen in the Base64 encoded password.
With all the necessary components found we can now confirm the values we have collected and actually decrypt a password from the SiteLists.XML file. The simplest solution to determine the block cipher is to test out various block ciphers until we get the expected results, a plain text password. Using the online 3DES decryption tools available at http://tripledes.online-domain-tools.com/ or various other tools available online we can quickly test various block ciphers and view the results.
As we can see the block cipher turned out to be ECB. The input text is the encrypted password after being XORed with the XOR key we found earlier.
Now that we have determined everything we need to know to decrypt the SiteList.XML passwords we can write a tool to automate the process. The tool has been published on https://github.com/fxwarchest/McAfeeSiteList.
The following is some example output from the tool.
$ python sitelist.py -f SiteList.xml ___________ .__ ____ ___ \_ _____/_ __ _____|__| ____ ____ \ \/ / | __)| | \/ ___/ |/ _ \ / \ \ / | \ | | /\___ \| ( <_> ) | \/ \ \___ / |____//____ >__|\____/|___| /___/\ \ \/ \/ \/ \_/ -------------------[warchest]------------------- McAfee SiteList.XML Decryptor Version 1.0 <email@example.com> http://github.com/fxwarchest HttpSite ============================================= Server update.nai.com:80 RelativePath Products/CommonUpdater Username (empty) Password (empty) ============================================= UNCSite ============================================= Server server RelativePath (empty) Share TestShare Domain TestDomain Username McSvcAccount Password Password1 ============================================= SpipeSite ============================================= Server server.pwnag3.local:80 ServerIP 192.168.209.10:80 RelativePath Software Username (empty) Password (empty) =============================================
In the example output we can see most of the update sites are configured without any credentials. The most interesting site is the UNCSite since it is configured with domain credentials.
For more information on the tool check out the code on GitHub, and enjoy.