Unprivileged User Persistence with Zoom

*** UPDATE: It looks like the most recent version of Zoom has fixed the issue of loading an unsigned DllSafeCheck.dll. This persistence technique no longer works. Thank you to @LadhaAleem for bringing this to my attention. See the Patching Zoom.exe section for details on how this can be bypassed.

Zoom has been in security news lately as researchers have turned their attention to it given it’s widespread adoption during the COVID-19 pandemic. This has kicked off some Twitter discussions on what the “real” vulnerabilities and issues with Zoom are, and whether some researchers are just trying to exploit the media hype around Zoom to generate buzz for themselves. I’m too dumb to really comment on all that, and I imagine many will find this post to fall into the latter…

Anyway, I installed the Zoom client on my system and wanted to see if there was anything I could take advantage of as an amateur redteamer (amateur meaning I don’t know what I am doing).

Zoom in Process Monitor

After installing Zoom, I enabled it to start at system boot up.

I then configured Process Monitor, from the SysInternalsSuite, to log activity during boot up. This is down with Options -> Enable Boot Logging.

After restarting my computer, I noticed that Zoom was loading all of its DLLs from the following directory:


This directory is in the user’s profile, and is writable to the user.

It looked like all of Zoom’s executables and libraries it needed were executed out of this directory. I assume this is all running out of a user’s profile instead of something like “C:\Program Files\” is so that non-administrator users can download, install, and run the Zoom client. Since the expected users for Zoom are going to include regular, non-administrator users at organizations and young students whose parents may restrict their privileges on a computer, it makes sense for Zoom to do this.

So, what can you actually do with this? You could just replace the Zoom.exe with your own executable. When the user starts Zoom, it will run your executable. This isn’t really “fun” and will probably be obvious to the user that something is wrong when Zoom doesn’t start like they expected.

Overwriting DLLs for Persistence

Zoom has a lot of DLLs in its directory that it loads when it executes. These DLLs are all writable so you could easily replace them. I had randomly chosen the “msaalib.dll” library as one to try and replace. Dumpbin should the following functions were imported from msaalib.dll from Zoom.exe.

dumpbin /imports C:\Users\<username>\AppData\Roaming\Zoom\bin\Zoom.exe

                40A2D4 Import Address Table
                40D8A4 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                    2 ZAccInitModule
                    3 ZAccTermModule

I downloaded FireEye’s DueDLLigence project and added the following to DueDLLigence.cs:

// Zoom
// |--> DLL == "MSAALIB.dll"; Arch == x86;
[DllExport("ZAccInitModule", CallingConvention = CallingConvention.StdCall)]
public static bool ZAccInitModule()
    return false;
[DllExport("ZAccTermModule", CallingConvention = CallingConvention.StdCall)]
public static bool ZAccTermModule() { return false; }

I used metasploit to generate shellcode that would launch cmd.exe. This was base64 encoded and copied and pasted into DueDLLigence.cs.

msfvenom -p windows/exec -a x86 CMD=cmd.exe -f raw | base64 -w0

I then compiled it as an x86 library and copied it to my Zoom directory as msaalib.dll. Unfortunately, when I started Zoom I got the following message.

So, it looked like the Zoom client was checking the digital signature of the libraries it loaded. If the user clicks “yes”, it will load your DLL and execute your code, but have the user click through a prompt seemed less than ideal.

Bypassing DLL Check

One library that Zoom loads is “DllSafeCheck.dll.” I assumed this was what Zoom was using to verify that the libraries it loaded were legitimate. Dumpbin showed Zoom.exe imported its “HackCheck” function.

dumpbin /imports C:\Users\murph\AppData\Roaming\Zoom\bin\Zoom.exe
40A208 Import Address Table
40D7D8 Import Name Table
 0 time date stamp
 0 Index of first forwarder reference

0 HackCheck

The modification to DueDLLigence.cs was quick and easy:

// Zoom
// |--> DLL == "DllSafeSearch.dll"; Arch == x86;
[DllExport("HackCheck", CallingConvention = CallingConvention.StdCall)]
public static bool HackCheck()
    return false;

Recompiled DueDlligence.dll and copied it to my Zoom directory as DllSafeCheck.dll. I ran Zoom, and got back three command prompts. Process Monitor showed Zoom.exe starting the new cmd.exe processes.

Great! Zoom loaded my DLL and launched my payload. One problem, though, was that Zoom never launched. DueDLLigence by default launches shellcode by creating a thread within the current process. I modified this so it would create a new process and execute my shellcode in the external process. The following line in DueDLLigence:

public const ExecutionMethod method = ExecutionMethod.CreateThread;

Can be changed to be “ExecutionMethod.CreateRemoteThread”

public const ExecutionMethod method = ExecutionMethod.CreateRemoteThread;

By default, this will launch notepad.exe and inject your shellcode into the new notepad process. You can change the process in DueDLLigence to be what you want. I left it as notepad for my testing.

I recompiled and re-copied everything and launched Zoom again. I got my three command prompts, and Zoom launched as well. Process Monitor showed Zoom.exe starting the new notepad.exe processes.

As far as I could tell, Zoom behaved normally after I had replaced “DllSafeCheck.dll” with my own DLL. There wasn’t any obvious error messages presented to the user.

At “C:\Users\<username>\AppData\Roaming\Zoom” there is a file called “dllsafecheck.txt.” I thought this would be a log file of when Zoom detected an unsigned library being loaded, but nothing ever seemed to be written to it. As far as I could tell, Zoom would just check if the text file existed. If it didn’t, it would create a new empty text file.

Unprivileged User Persistence

The new processes launched by Zoom ran with the same user privileges as the user that ran Zoom. This wasn’t an escalation of privileges, but it did look like it could be used for persistence on a system after you gained access to it.

Before, I had configured Zoom to run at boot up through its settings menu. As a pentester / redteamer, you may not have access to the system’s GUI to modify that setting. Process Monitor showed that when I changed the setting in the GUI, Zoom.exe was making a modification to a Run key in the registry. It would create a key called “Zoom” and provide it the path to Zoom.exe in your user profile. If you selected for it to start “silently”, it would include the “–background=true” parameter for Zoom.exe. The below image shows when the setting is checked, and then after when I unchecked it.

So, after gaining access to a system that a user has Zoom installed on, just check for the Zoom run key. If it doesn’t exist, create it. Then, add your own copy of DllSafeSearch.dll to the Zoom bin directory. If Zoom.exe is currently running, you may need to kill the user’s Zoom.exe process before you can copy DllSafeSearch.dll. Hopefully the user isn’t in the middle of a call when you do so…

How is This Useful?

This isn’t much different than other unprivileged user persistence methods. It uses a run and requires you dropping a file to disk. The advantage is that the Zoom run key may be “normal” in an environment, so that key being added to a user’s system may help you stay under the radar compared to other persistence methods.

I did most of my testing / discovery for this on my personal system using the free copy of the Zoom client. I’m not sure if there is a difference between this client and a “licensed” client. I’ve used Zoom a few times for work, and the client appeared to be configured the same way on my employer’s system as it was on my personal system. It appears to me at least that this will also work within organizations where employee’s use Zoom to join meetings.

Otherwise, this isn’t very useful. You aren’t “exploiting” Zoom. Everything its doing seems fine given its usecase, but this could be something that other pentesters / redteamers find useful. I don’t know.

Patching Zoom.exe

@LadhaAleem sent me a message on Twitter about how the instructions in the above post no longer worked. I double checked, and they were correct. Updates to Zoom prevented the DllSafeCheck.dll hijacking. It looked like there was some code added to Zoom.exe that checked if that file was legitimate or not.

First, I wanted to see if maybe there was anything being loaded by Zoom that may not be checked by DllSafeCheck.dll. I found one item that looked interesting, SciLexer.dll.

Zoome was trying to load SciLexer.dll from the Zoom\data\ directory. I first tried to put an arbitrary SciLexer.dll in that directory, but it didn’t seem to execute even though Process Monitor was saying that Zoom.exe loaded the Dll. I then noticed appsafecheck.txt log file was populated with the following.

Anything I tried to drop in the Zoom directories was getting caught by the check, so I figured I would try and see what the Zoom.exe binary was doing to perform the check. To do this, I loaded Zoom.exe in Immunity Debugger.

During prior testing, I noticed that the error message that popped up when Zoom was loading my DLLs contained the string “Unknown Publisher”, so I searched for the string in Immunity. Right Click -> Search For -> All Referenced text strings

In the new window, Right Click – Search for text

I then typed in “unknown publisher”, unchecked “Case sensitive”, and hit enter. It should find the string.

Double click on the string and it will take you to that location in the CPU menu.

Scroll up a bit in the CPU menu until you see the beginning of the function. Click the first instruction, and Immunity will tell you where the function is called from. Right Click -> Go to CALL to move the CPU menu to that call.

Through some trial and error debugging, it seemed that there was a conditional jump at 0xefb617 (the address may be different on your test machine). I modified this from a JE to a JMP, so that the jump was always taken. This can be done with Right Click -> Assemble.

When this runs in a debugger, Zoom will eventually crash. I wasn’t sure if that was the cause of the debugger or not, so I saved my patch to a new executable. In the CPU window, Right Click -> Copy to executable -> All modifications.

Click “Copy all” in the new popup. Then, Right Click -> Save file

I saved it as Zoom2.exe. Then, I backed up the original Zoom.exe file, renamed my patched Zoom2.exe to Zoom.exe, and ran it. Zoom loaded my SciLexer.dll file.

When SciLexer.dll was loaded, Zoom.exe would start a cmd.exe process.

No error pop up messages for the user, and no log entry in appsafecheck.txt. I imagine you could get Zoom.exe to load other DLLs as well but I did not test that.

I mostly did the binary patching to see if I could figure it out, but this isn’t really the greatest persistence technique. You have to patch a binary and then drop that AND a Dll to disk. If Zoom updates, you’re patched binary will be overwritten as well. Since Zoom.exe runs in the user’s Appdata directory, the user you are running as has write privileges over it, unlike other applications running under Program Files where only an admin can modify the binary file.

Note: this was all done on Zoom version 5.0.23502.0430.