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
0:005> g
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.

0:000> kb
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

 Highlighted in red are the interesting stack frames. We see there is a CSitesHelper class in the mfeCmnLib71.DLL file. Additionally, we can see there is function calls further up related to parsing XML. The CSitesHelper class would be the likely place where all the action regarding the SiteList.xml file occurs and would be a good place to start looking. To further investigate the CSitesHelper class, we load the mfeCmnLib71.DLL in IDA Pro.
Once the mfeCmnLib71.DLL is loaded we can look at the export table. The export table shows all the functions that applications utilizing the mfeCmnLib71.DLL library can call. Initially a few of the exported functions popped out as interesting, they are highlighted in red in the above screenshot.

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


0:000> kb

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 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

The following is some example output from the tool.

$ python -f SiteList.xml
___________           .__              ____  ___
\_   _____/_ __  _____|__| ____   ____ \   \/  /
 |    __)|  |  \/  ___/  |/  _ \ /    \ \     / 
 |     \ |  |  /\___ \|  (  <_> )   |  \/     \ 
 \___  / |____//____  >__|\____/|___|  /___/\  \
     \/             \/               \/      \_/ 
McAfee SiteList.XML Decryptor Version 1.0

RelativePath Products/CommonUpdater
Username (empty)
Password (empty)

Server server
RelativePath (empty)
Share TestShare
Domain TestDomain
Username McSvcAccount
Password Password1

Server server.pwnag3.local: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.

CVE-2015-5090 is an Adobe Reader/Acrobat Pro bug discovered and reported on by ZDI a handful of months ago.  They used this bug during their fantastic Abusing Adobe Reader’s JavaScript APIs talk at Defcon 23 to demonstrate one of many sandbox escapes discovered in Adobe Reader’s Javascript API.  Reader is and has been a huge target for spear phishing and privilege escalation bugs; not only is the attack surface massive, but patching often poses problematic from a usability and enterprise perspective.  Red teams and real-world adversaries often can harness months old bugs during phishing campaigns to compromise targets, as opposed to potentially burning 0days.  FusionX often relies on these sorts of bugs or flaws when simulating advanced adversaries; there is no need burn an 0day when a public vulnerability can be weaponized and deployed just as easily.  Though ZDI never released a proof of concept (PoC) for this bug, we thought it was a great, reliable bug that still has plenty of utility today.  This post details the bug, a variation in its exploitation, and a working proof of concept.

The AdobeARMService is Adobe’s updating software, and is installed on a system as a service upon installation of Adobe Reader or Acrobat Pro.  This service creates and registers a service control handler on start-up, which allows the service to process external control requests.  The control dispatcher processes incoming service request codes and routes them to appropriate methods; the updating binary, armsvc, has eight or so of these functions, all of which can be triggered from unprivileged processes (intended).  You can read more about service control handlers here.

Our bug begins with service code 0x000000AB, or SHAREDMEMOTY_CREATE (misspelling intentional).  Once the service receives this control request, the request is routed into the CreateSharedMemory function, which creates a named shared memory file with weak permissions and a predictable name.  The security descriptor is as follows:


The shared memory handle name is Global\{E8F34725-3471-4506-B28B-47145817B1AE}_, followed by the C drive’s serial number and the hard-coded string thsnYaViMRAeBoda.  Very simple to generate on the fly.  Once we’ve triggered the generation of the shared memory object, any process running on the system has full read/write to it.  What exactly does this mean?

As it just so happens, another exposed service control 0x000000B4, or ELEVATE_ARM, reads from this shared memory file.  ELEVATE_ARM actually parses out the data pulled from the SM and passes it as command-line arguments to AdobeARMHelper.exe via a ShellExecuteExW call.  This allows us to pass semi-arbitrary flags to the AdobeARMHelper.exe binary, as launched by the armsvc.  I say semi-arbitrary because there is some parsing logic here, though it’s about as trivial as a wcsstr scan of the literal string and copying data out.

AdobeARMHelper.exe is a pretty simple binary that essentially bootstraps some functionality for the updater, including preference setting, PDF ownership patching, and registry changes.  If the binary receives the /Svc or /ArmService flags, it’ll call into the ProcessRequestFromArmService function, which in turn, checks for the /ArmUpdate flag.

If this flag is found, we dive into the ProcessARMUpdateRequest function, a behemoth of self-updating mechanisms and failed install failsafes.  The real meat of this function is its auto-updating mechanisms.  Given a flag on the command line, it will find the first file in the folder, check its digital signature, and then copy it to C:\Program Files (x86)\Common Files\Adobe\ARM\1.0.

As an example, let’s say we write the following into the shared memory handle:

FOLDER:"C:\Windows\Temp\AdobeTEST\" /ArmUpdate

armsvc will parse this out into

FOLDER:"C:\Windows\Temp\AdobeTEST\" /Svc /USER:/ArmUpdate /SESSIONID:0

Our helper binary will then attempt to copy the first file in the C:\Windows\Temp\AdobeTEST\ directory signed by Adobe into the directory in which AdobeARM and AdobeARMHelper reside.  This routine makes no attempt to validate the filename nor its extension.  Thusly, we can get an arbitrary binary signed by Adobe, rename it to AdobeARM.exe, and force armsvc to overwrite the AdobeARM.exe executable.  We would then retrigger the ELEVATE_ARM service handler to have it execute our newly copied binary with whatever command-line flags we’d like.

This is the point in which ZDI demonstrated its JavaScript sandbox bypasses.  By overwriting AdobeARM.exe with the Adobe Reader binary, they forced it to load a malicious PDF that then dropped a DLL into the local directory using the Javascript API and a sandbox escape in Reader.  It’s a great example of a reliable logic-based bug.

We found a second way of exploiting this without the PDF dropper, however ran into a rather interesting hitch.  adl.exe, or the Adobe AIR Debug Launcher, is a tool packaged with Reader that can be used to run and debug AIR applications.  It’s an Adobe signed binary that allows us to specify a remote runtime directory, which we can use to load in a malicious DLL.  Something like this: \adl.exe -run c:\windows\temp\airtmp.

On launch, this will attempt to load C:\Windows\Temp\airtmp\Adobe AIR\Versions\1.0\Adobe AIR.dll.  Pretty simple.  Unfortunately, the digital certificate check in AdobeARMHelper is pretty specific, and oddly irritating.  adl.exe is signed with the following:

Thumbprint Subject
---------- -------
A2D24C4708488FE490310CEF7AC667A71921D635 CN=Adobe Systems Incorporated, OU=Universal Client, OU=Digital ID Class 3 ...

And AcroRdr32.exe is signed as follows:

Thumbprint Subject
---------- -------
45548B92B80CB79A7C628B83D9DBA37B9C86971D CN="Adobe Systems, Incorporated", OU=Acrobat DC, O="Adobe Systems, Incorpo...

It would seem Adobe has incorrectly spelled its own name.  This check is obvious in the AdobeARMHelper code, in which they strcmp the certificate names:


Quite odd, and rather frustrating.  After digging through quite a few of Adobe’s binaries, it would appear that the quoted and comma CN is used primarily within the Reader/Acrobat group, so we limited our search there.

While binary hunting, we thought of a third way of exploiting this without the need for a separate binary.  Using Forshaw’s NTLM reflection PoC, we can force AdobeARM to open a pdf, much like the ZDI folks did, but pass it a UNC path instead to a local attacker-controlled system.  We were able to use this to compromise hashes via Responder, but using Forshaw’s technique, we can actually use the Adobe updater to trigger the privileged access instead of, say, Windows Defender.  This would allow us to redirect the privileged access to a local WebDAV service and proxy an NTLM session.

Finally, we found yet another way of exploiting this, without the need for a separate bug or Windows-specific vector.  Adobe distributes an Enterprise Toolkit (ETK) containing a variety of different enterprise and administrative specific tools related to the management and deployment of Adobe Reader.  Acrobat Pro DC actually uses one of these ETK tools for updating and patching, called a bootstrap deployment.  This tool, setup.exe, simply providers a wrapper around msiexec.exe, used for executing MSI files.  A snippet from Adobe’s documentation on this:

The Acrobat-Reader bootstrapper is provided as part of the Reader bundle on the
CD and the web download. It is also provided for some releases on the FTP
download site. It provides a streamlined way to chain installs without the need
for administrative install points. The bootstrapper provides the following
benefits (...)

We found a copy of this binary on a test system with Acrobat Pro installed, under the following directory: C:\ProgramData\Adobe\Setup\{AC76BA86-7AD7-1033-7B44-AC0F074E4100}.  This application takes a single ini file (setup.ini), parses it out (as described in the link above), and executes an MSI file.  Thankfully, setup.exe allows us to specify alternative ini files, doesn’t choke on extraneous command line flags, and is signed with the correct Adobe digital certificate that we require.

Getting the correct ini file is pretty simple and is just a few lines:


The key entry here being the msi value, which points setup.exe to our malicious MSI to execute.  msfvenom is capable of generating MSI files to obtain code execution, but the template file used is missing a key property that setup.exe requires.  The property UpgradeCode is required to be set in the MSI, and this can set using Microsoft’s Orca tool, as shown:


It doesn’t matter what this value is, we just want setup.exe to query the value and compare it to the current.  With these values in place, we simply trigger the overwrite, then retrigger with /sAll /ini “C:\path\to\ini”; as mentioned, the extraneous commands are thrown out.  This culminates in a safe and reliable privilege escalation bug:


Obviously you don’t want to launch interactive services, but you get the picture.  I’ve attached the code at the bottom of this post.  In order to use this, you’ll just need to generate your own MSI and drop it into the resources folder, named asdf.msi and compile.  setup.exe, asdf.ini, and asdf.msi are compiled into the binary as resources, and dropped to hard-coded folders (in %tmp%) on the system.  Adjust to preference.

The latest version, 11.0.14 with armsvc 1.824.16.6751, doesn’t actually fix the crux of the bug, our exposed shared memory handle.  Instead, they’ve removed the 7Z updating mechanism and now instead enforce an MSI update.  Adequate checks are in place to ensure the binary in question is both a real MSI file and signed by Adobe.  This is done by first inspecting the digital signature of the given file, then ensuring that it’s an MSI:


It then continues to poll for MSI product information using the msi!Msi* suite of win32 functions, eventually using MsiReinstallProductW or MsiInstallProduct to install the MSI.

The proof of concept has been tested with Adobe Reader 11.0.10 with version 1.801.10.4720 of armsvc and can be found here.