WebExec Revolutions: The strange case of the Update Service
In a previous blog post, I described how I bypassed the patch for the first fix for CVE-2018-15422. That bypass was also discovered by other researchers as well. You can check that out in Cisco’s advisory.
Now, WebExec was the name given to that first vulnerability by that Ron Bowes and Jeff McJunkin. The second vulnerability (a DLL hijacking) was found by several researchers, but @steventseeley gave it the name of WebExec Reloaded (you can check his blog post on WebExec Reloaded here). To continue the tradition of honoring “The Matrix”, I named this third vulnerability WebExec Revolutions.
The WebEx version that fixed the DLL hijacking vulnerability was 22.214.171.124. After that version the application was updated several times over: 126.96.36.199, 188.8.131.52, 184.108.40.2064, 220.127.116.11, 18.104.22.168, 22.214.171.124, 126.96.36.1999, 188.8.131.52, 184.108.40.206, 220.127.116.112, 18.104.22.168 and 22.214.171.124. (126.96.36.199 was the latest version at the time of my first tests) This vulnerability includes all the listed versions, except for 188.8.131.52, 184.108.40.2064, and the 33.9.X versions. All the 33.8.X require a two-stage attack to work. As you’ll see later, version 220.127.116.112 (and later) rendered this attack unusable.
After the release of a patched version, I tested it again to see if the issue had been fixed. After I installed the patched version for the DLL Hijacking vulnerability (18.104.22.168), and was able to prove that the bug was fixed, I got really interested in this update service and I decided to take a look under the hood.
In the previous attack, we used “install” as the first parameter for the service, but if you look at the main function of WebExService.exe, you’ll see that you can also pass “uninstall” or a third value, which could be “WebexService”.
If you look at the image 1, you’ll see that the function StartServiceCtrlDispatcherW will be called when the “WebexService” parameter is used. Looking at the documentation for that API, you’ll see that the ServiceMain function for the service, is defined in 0x402CE6 and that it points to function 0x402D60.
Inside 0x402D60, you’ll see that the things get interesting starting at 0x402F56:
I have renamed the called function to PreDownFParam (the address for the function is 0x403700). That function first extracts the installation path from the registry in order to obtain a full path to the ptUpdate.exe executable. Then it counts the number of passed parameters. Finally, we get to the really interesting part: 0x403A02
In image 3, you can see that the function of interest is DownloadFileParam.
The function takes 5 parameters. The first parameter (int) is checked:
Then it concatenates the value with “/DownloadFile” and all the rest of the parameters. Nnotice the PathQuoteSpacesW function calls!:
Later, it takes the token from winlogon.exe:
And finally runs ptUpdate.exe as SYSTEM with CreateProcessAsUserW using the duplicated token:
After learning all that, I realized I could run something like this…
sc start webexservice WebexService 1 989898 "C:\Users\McFly"
As I did before, I launched a Windows 10 x86-64 VM, with version 22.214.171.124 installed, Process Explorer and Process Monitor running (and logging only File System Activity) and tried the previous command:
Looks like we’ve found something good! The updater is trying to open ptUpdate.xml. Now, I needed to figure out the structure of that file.
To make life easier I connected the VM to the internet, launched the application, and logged in. After a few seconds, a new update message appeared. In that moment, I found out that the application already had downloaded all the files for the new version, and that the current version was 126.96.36.199.
In the temp path for the current user a folder named “ptools” was created, containing inside another folder called “ptools-<GUID>” where <GUID> is a value like “FE456789-2457-3678-2EDF-FFFFFF234568”.
Inside that folder there were a lot of .7z files, along with the file that I needed, ptUpdate.xml:
<serv:message xmlns:serv="http://www.webex.com/schemas/2002/06/service" xmlns:com="http://www.webex.com/schemas/2002/06/common" xmlns:use="http://www.webex.com/schemas/2002/06/service/user">
<serv:bodyContent xsi:type="use:getUpdateResponse" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Description>WebEx Productivity Tools 33.7.2</Description>
<UserName> [email protected]</UserName>
Notice that I have removed several <FileInfo> tags, for simplicity.
Now that I had the xml structure, I took a snapshot with the updated version.
Then, I increased the version numbers to force the installation (I changed all the 33.7.2 values to 33.7.3) and copied the ptUpdate.xml to my controlled folder.
Then I launched the service again:
sc start webexservice WebexService 1 989898 "C:\Users\McFly"
Our xml is found. Now, another file is requested:
Looking in the “ptools-<GUID>” folder, I found it. One curious thing is that when I tried to open it with 7zip, I noticed that the file wasn’t compressed:
In fact, that file turned out to be a library which is used to decompress the other files of the update. I figured that out by looking the exported functions of the library:
Next, I reverted to the saved snapshot and copied this file to my folder and started again the service:
The file was found this time and was copied to the temp folder of the current user. Since the updater is running as SYSTEM, the temp folder is C:\Windows\Temp.
Other than the above, nothing else happened.
The first thing I needed to know was if the update was working. In order to do that, I needed to copy to my controlled folder all the files that were listed in the xml. I needed to try something else because if the update works, I had no way to confirm it (unless I revert to the snapshot that had the previous version. A thing that was too boring, since I took that snapshot in not a “clean way”).
So, I decided to test a simple trick in order to fool the update mechanism: take the updater itself (ptUpdate.exe) and change it with the one in the previous version. The current version for the binary was 3307.1.1811.1500. My previous version was 3306.4.1811.1600.
I reverted the snapshot, and after copying all the files to my controlled folder, I compressed the previous ptUpdate.exe. I checked the compression settings used to create the .7z file with the current ptUpdate.7z:
That’s “Normal” compression level and “LZMA” compression method in 7zip GUI. The command line for 7z.exe is:
7z.exe a ptUpdate.7z ptUpdate.exe -m0=BCJ -m1=LZMA:d=21
Also, I updated the values of “Size”, “PackagedSize” and “Version” for the new binary in the ptUpdate.xml.
I ran the service again and went to see if the update (and our trick) worked by checking the version of the ptUpdate.exe binary:
I couldn’t believe my eyes: The updater was updated with its previous version!
At this point, knowing that we had an updater that downgrades itself, maybe we could replace it with the version that was vulnerable to DLL Hijacking (version 3306.0.1809.2900)
To test that theory I created a smaller ptUpdate.xml with the same previous data, but only 2 “FileInfo” entries: one for ptUpdate.exe and one for wbxtrace.dll (our previous malicious DLL). Notice that the DLL must have the value “Common” for the “SectionName” tag.
Once again I reverted the snapshot and copied the 3 files to my controlled folder; I took the atgpcdec.dll file from the application’s installation folder, since it’s not compressed, and renamed it to atgpcdec.7z.
But after running the service, I realized that the attack didn’t work…*sigh*
I decided to test again, but this time with the updater version that worked. But this updater was not vulnerable to DLL hijacking, so I checked for another DLL that could be loaded by the updater itself or by another process with SYSTEM privileges. Also, it cannot be any of the DLLs that are signed by Cisco. The answer was the Visual Studio C (VC) runtime DLL: vcruntime140.dll.
Again I compressed the previous, and working, version of the updater. I created another DLL that executes “notepad.exe” on load, but this time I had to add all the exported functions of the VC runtime. Once the xml was updated with the changes, I reverted the snapshot and copied the 3 files into my controlled folder (remember how I mentioned that you can copy the atgpcdec.7z file from the application’s installation folder).
And, after running the service one more time, I got…
Not 1, but 2 notepad.exe’s running as SYSTEM!
As you can see, there are several binaries that run as SYSTEM in the update process.
Keep in mind that in this way we render the application useless, so the VC runtime will have to be restored (and, if you want, the updater too) to allow the future functioning of the application.
As I mentioned previously - all the listed 33.8.X versions, starting from version 188.8.131.529, require a two-stage attack:
- First, you must replace only the current ptUpdate.exe with the one in version 33.7.X (except 184.108.40.2064).
- Secondly, you must replace ptUpdate.exe with the one in version 220.127.116.11 and the vcruntime140.dll library.
The two stages are required given that the version 18.104.22.1689 added the checking of signatures for all the downloaded binaries, not only the Cisco’s binaries.
Starting from version 22.214.171.1242 this attack is useless as ptUpdate.xml is not being used in the update process. Instead ptUpdate.ini is used. This .ini file has, among others, 2 sections ([CryptInfo] and [CryptInfoEx]) which enumerates all the hashes for the files in the update (information that can be calculated and changed), and a signing information value (GpcSignedInfo item). This base64 encoded value seems to be a certificate, but its value is invalid: I copied it into a .cer and .crt file, and it cannot be parsed as a valid certificate.
Cisco later confirmed that version 126.96.36.1992 was the first fix but that it was an intermediate release. The patched versions are 188.8.131.52, 184.108.40.2064, and all the 33.9.X versions. For updates, check the Cisco advisory for this vulnerability.