Posts

CVE-2020-27708: Electronic Arts (EA) Origin – Local Privilege Escalation

We recently assessed the security posture of Electronic Arts Origin Client and discovered a privilege escalation issue that would allow a low privilege attacker to elevate privileges to NT AUTHORTY\SYSTEM.  This has been recorded as CVE-2020-27708.

Origin is a digital distribution platform, by Electronic Arts, who own the brand EA Games.  They acquired the trademark Origin when it purchased Origin Systems in 1992. The platform allows some reported 39 million [1] users to download and install games by Electronic Arts.

An initial look with procmon

First, we used the free SysInternals Process Monitor tool (procmon) [2] to look for any low-hanging fruit.  Something immediately stood out; two system services looking for the directory C:\platforms, which they were not able to locate.

E:\VMShare\REVERSE_ENG\origin1.PNG

In Microsoft Windows, any user is by default able to create a directory in the root of the C drive. So, we proceeded to do just this.

We followed this with a second run of procmon.

E:\VMShare\REVERSE_ENG\origin5.PNG

As can be seen in the second procmon output, a directory listing takes place on the C:\platforms directory, which is interesting and something we made a note of.

A closer look with ProcessHacker

Our next course of action was to have a look at one of the service processes OriginWebHelperService.exe process using another free tool called ProcessHacker [3]. Something immediately stood out to us, which can be seen in the image below; OriginWebHelperService.exe is loading a DLL qwindows.dll from the directory C:\Program Files (x86)\Origin\platforms\.

Because of the similar names, C:\platforms and C:\Program Files (x86)\Origin\platforms\, we decided to copy the contents of the C:\Program Files (x86)\Origin\platforms\ directory into the C:\platforms directory.

E:\VMShare\REVERSE_ENG\ploatforms.PNG

We then ran ProcessHacker again to view the loaded modules within OriginWebHelperService.exe.

Surprisingly, this DLL was loaded directly into the OriginWebHelperService.exe process.

A bump in the road

The next step was to replace qwindows.dll with our own malicious DLL that would open a command prompt on behalf of a low level user. This is where we hit a slight bump in the road. We could see in a procmon log that our DLL was being read, however it was then closed and the original qwindows.dll was read from the Program Files path.

Using another free tool CFF Explorer [4] we took a look at qwindows.dll.

E:\VMShare\REVERSE_ENG\export.PNG

qwindows.dll has only two exported functions, qt_plugin_instance and qt_plugin_query_metadata.

E:\VMShare\REVERSE_ENG\metad.PNG

Looking at the sections within the qwindows.dll there are two that stood out to us, .qtmetad and .gfids. What if the Origin Client executables are scanning the DLL’s in the C:\platforms directory and looking for these sections before loading the DLL?

We decided to find out and proceeded to copy the data from these two sections, adding the data to our own malicious DLL into sections with identical names.

C:\Users\twilson\Documents\Nettitude\Image\qmetad.PNG

Successful privilege escalation

The result was immediate; our DLL was loaded into the OriginWebHelperService.

The OriginWebHelperService runs as Local Service, which is a low privilege account and requires some further effort in order to gain full NT AUTHORITY\SYSTEM privileges.

A recent paper by Antonio Cocomazzi [7] details several ways to break out of Local Service accounts by abusing the SeImpersonatePrivilege.  We could have attempted to use the “Chimichurri Reloaded” technique, for example [8].

However there is another service included with Origin, “Origin Client Service” which runs under the account NT AUTHORITY\SYSTEM and shares the same DLL hijacking vulnerability as the OriginWebHelperService.

At this point we changed our focus to “Origin Client Service”.

Using the sdshow command of sc.exe, the Windows Service Control tool, it was possible to view the security permissions of the Origin Client Service:

E:\VMShare\REVERSE_ENG\sdshow.PNG

The Security Descriptor Definition Language (SDDL) output from the sc sdshow command allows us to view the Security Descriptor, which suspiciously has an ACL for the well known SID string [5] “BU” is used which represents the BUILTIN\Users group.

More detail can be obtained using a PowerShell script [6]:

This allowed us to determine that any user is able to start and stop the OriginClientService.exe service process. This is an added bonus; we now don’t have to wait for reboot in order to execute our malicious payload; we can simply start the service and get as many elevated command prompts as we want:

While both the OriginWebHelperService and the OriginClientService were vulnerable to the issue, the path of least resistance was to exploit the OriginClientService gaining system privileges directly.

CVE-2020-16091 for EA Games Origin Client

We were initially issued CVE-2020-16091 by MITRE, which exclusively describes the vulnerability in this post.  Electronic Arts subsequently became a CNA and have issued a new CVE number, CVE-2020-27708, which merges a lower impact incarnation of this vulnerability with our original finding.  We have opted to lead with CVE-2020-27708, with a reference to CVE-2020-16091 noted here to avoid confusion.

Timeline

  • 27 July 2020 – Initial discovery
  • 28 July 2020 – CVE-2020-16091 issued by MITRE
  • 8 September 2020 – Electronic Arts informed of vulnerability
  • 19 September 2020 – Electronic Arts granted CNA status
  • 28 October 2020 – Electronic Arts issued CVE-2020-27708
  • 29 October 2020 – Electronic Arts released patch
  • 3 November 2020 – Nettitude release vulnerability analysis

Conclusion

It takes a relatively low effort to audit for DLL path hijacks.  Tools such as process monitor are freely available and should be leveraged as part of a products testing cycle.

Developers should also assess if they really need a service to run as NT AUTHORITY\SYSTEM. For most practical purposes, running a service under the Local Service account is just as effective and more secure; the Local Service account has various privilege restrictions, although is not immune to further privilege escalation itself [7] [8].

We identified this vulnerability in Electronic Arts Origin Windows client, version 10.5.77.42374 – 763270.  The vendor has patched this vulnerability in version 10.5.87.45080 of the client.

References

    1. Origin has 39 million users – https://venturebeat.com/2013/01/30/origin-has-39-million-users-and-4-other-surprising-numbers-about-ea/
    2. Process Monitor – https://docs.microsoft.com/en-us/sysinternals/downloads/procmon
    3. ProcessHacker – https://processhacker.sourceforge.io/
    4. CFF Explorer – https://ntcore.com/
    5. Well known SID strings – https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-strings
    6. Using PowerShell to view service ACL’s – https://rohnspowershellblog.wordpress.com/2013/03/19/viewing-service-acls/
    7. Windows Privilege Escalations: Still abusing Service Accounts to get SYSTEM privilegeshttps://www.romhack.io/dl-2020/RH2020-slides-Cocomazzi.pdf
    8. Chimichurri Reloadedhttps://itm4n.github.io/chimichurri-reloaded/

CVE-2019-12750: Symantec Endpoint Protection Local Privilege Escalation – Part 2

In this post we will walk you through a more sophisticated method of exploiting CVE-2019-12750.  This is a local privilege escalation vulnerability that affects Symantec Endpoint Protection.  The method of exploitation described in this post works, at the time of writing, on all versions of Windows. Read more

CVE-2019-12750: Symantec Endpoint Protection Local Privilege Escalation – Part 1

A malicious application can take advantage of a vulnerability in Symantec Endpoint Protection to leak privileged information and/or execute code with higher privileges, thus taking full control over the affected host. Read more

CVE-2017-18019: Privilege Escalation via a Kernel Pointer Dereference

A little while ago, I discovered a vulnerability, CVE-2017-18019, affecting a kernel driver of multiple K7 Computing security products, as well as the products of Defenx, both for Windows.  Both were affected because they were using the same anti virus engine, and both are now patched.

The proof of concept was based on an invalid kernel pointer dereference, which led to a blue screen of death.  That research and the subsequent coordinated disclosure process were, at the time, sponsored and handled by SecuriTeam.  It turns out that the proof of concept could be exploited further, and turned into local privilege escalation.  So, with the permission of SecuriTeam, I decided to create a write-up of that local privilege escalation development process.

Targeting

This article targets the following 64-bit Windows versions: Windows 7 SP1 – Windows 10 v1809.

A Medium integrity level is required in order to exploit this vulnerability in the way that is demonstrated through this article. In order to exploit this from a Low integrity level, you will have to do extra work in order to leak some kernel pointers. This can be done either though other IOCTLs handlers of the target driver itself, or through other Windows driver kernel memory leak bugs.

Bug Analysis

The root cause of this issue is that the author of the following function trusts a pointer to read data from, originating from a user-supplied input buffer, as long as it references an address inside the kernel address space.

The vulnerable function fetches a pointer from the IOCTL’s input buffer and checks if it is greater or equal to nt!MmHighestUserAddress (0x00007ffffffeffff in x64). If that’s true, then the function will proceed by dereferencing that pointer and evaluating the first byte located at that memory address.

Clearly, the purpose (even though the implementation is buggy) of this check is to verify that the pointer address from where further information will be read resides in kernel memory of which, from the developer’s perspective, its virtual address and contents are not supposed to be known and controlled by the user. This, of course, is not entirely true because kernel object addresses may be leaked, and also they may reference directly or indirectly user-supplied data.

The following screenshot shows (in grouped nodes) what we described above.

Figure 1 – Verify it is a kernel pointer.

We can easily crash the host by supplying an arbitrary kernel pointer that references a non-allocated memory page.

The following image shows the output from Windbg the moment the memory access violation occurs.

Figure 2. Arbitrary kernel pointer dereference.

Further Analysis

What we know at this point is that we have a denial of service bug that can be triggered by any user in order to crash the host. So, we analysed this function further in order to find out if there is something more that we can do with it.

The following graph-view screenshot continues directly from what is shown in Figure 1.

Figure 3. Kernel memory buffer data checks.

Assuming that RCX points to a valid kernel address where the first byte is 0x4B, so that the previous check succeeds (cmp byte ptr [rcx], 4Bh), we arrive at the second part of the vulnerable function as shown above.

Here we notice further byte value checks, and specifically the second byte of the buffer referenced by RCX should be 0xFF in order to access the final part of our analysis.

Figure 4. Arbitrary Function Pointer Call.

A couple of pointer dereferences later, we see that the function is treating the last one as a function pointer. We also notice that the first and second parameters passed to RCX and RDX respectively can also be controlled.

To be more specific, the first parameter is taken from the buffer referenced by the arbitrary kernel pointer that we control, and the second one is pointing inside our user-input buffer that is defined through the call to DeviceIoControl function.

Setting things up

At this point, we have all the information we need in order to proceed with the exploitation of the vulnerability. To do that, we must know the address of a kernel object and also control its contents, to a certain extent. As we discussed, the initial pointer from where the rest of data is read leading to a function pointer called, must reference an address inside the kernel address space. This is also the developer’s assumption around the safety of that decision.

In a previous article we talked about Private Namespaces and the ability to insert user-defined data in the body of the associated kernel object. We will be using this type of objects in order to exploit the vulnerability, as they can be used reliably in this case as well.

In order to exploit this vulnerability, we will be using two kernel objects of the aforementioned type. The first object will be used for controlling the subsequent pointer dereferences that allow us to call an arbitrary function pointer, while the second object will be used in order to control the initial kernel pointer check that must reference a known kernel object in memory (first object).

Exploitation in Windows 7 SP1 x64

In the absence of exploitation mitigation such as SMEP (Supervisor Mode Execution Protection), taking advantage of this vulnerability is quite straight forward. We can execute our payload function in userland without taking any additional steps, such as temporarily disable SMEP. We just need to control the instruction pointer and that would be enough.

To start with, we will create a Private Namespace object using a random boundary name and we will use NtQuerySystemInformation function to leak its address.

Figure 5. 1st Object (Win7 SP1 x64).

Then, we will create another object of the same type with a crafted boundary name.

The first and second bytes must be 0x4B and 0xFF respectively (see Figures 1 and 3) to satisfy the byte value checks. Also, in the offset 0x0A (Figure 4 – first pointer dereference) of the crafted boundary name, we will be inserting the address of the first object + the distance in bytes (0x1a0) between that address and the location of the boundary name in that object + an arbitrary offset (0x1A) that contains a value that can be translated to a userland pointer, which satisfies the proof of concept for this version of Windows. Note that we take into account that at the result of the previous calculation, the value 0x0C will be added in order to reach the userland pointer value (Figure 4 – second pointer dereference).

Figure 6. 2nd Object (Win7 SP1 x64).

Let’s have a closer look at how these two objects are ‘inter-connected’.

Figure 7. Objects Interconnection (Win7 SP1 x64).

Finally, we can see the function pointer being called, in order to execute our payload at address 0x1010000.

Figure 8. Call Payload-Function Pointer (Win7 SP1 x64).

Exploitation in Windows 8.1 – 10 v1809 x64

In more recent Windows versions, exploiting a kernel driver bug is more challenging due to exploitation mitigations that have been added. In this case, we take control over the execution flow by calling an arbitrary function. However, due to the SMEP we are not able to directly execute code that resides in the user address space from kernel mode, so we will have to take another approach.

A common solution is to attempt to temporarily disable SMEP by clearing the 20th bit in CR4 register of a specific processor and lock our threads execution to only run on that one, so that we can execute our payload in userland as before. However, we would have to restore CR4 in order to avoid KPP (Kernel Patch Protection/PatchGuard) killing the host.

Another way, which we will be using in this write-up, is to take advantage of the execution flow control in order to turn it into a “write-what-where” primitive, which will enable us to modify arbitrary data in kernel memory. Once that is achieved, there are, again, two common ways of taking advantage of this in order to elevate our privileges.

The first method is to overwrite with a NULL value the SD (Security Descriptor) pointer in the object header of an elevated process running as SYSTEM. This will allow a non-privileged process to inject and execute malicious code in the same security context. However, this method will only work up to Windows 10 v1511 (Build 10586), as described in this article.

Another way to take advantage of a “write-what-where” primitive is to enable privileges in the primary token of a non-privileged process in order to enable it to again inject and execute code in the security context of a process running as SYSTEM. This method still works fine, but it requires a minor modification from Windows 10 v1709 (Build 15063) onwards, as described here. What we are about to describe here can also be used in Windows 7.

Going back to what we have described so far, we have noted that we are also able to control the first two parameters (see Figure 4) passed in RCX and RDX respectively, once our arbitrary function is called. We are going to take advantage of this capability in a moment.

In this case, we first need to leak the address of the primary token of our process, where will be enabling additional privileges. We will be using that address as the target of our exploitation primitive. As in Windows 7, NtQuerySystemInformation can be used for the same purpose from the standard ‘Medium Integrity’ of a user process in order to leak the kernel object and function addresses that we will be using.

We will then create our first Private Namespace object with a custom boundary name, where the first 8 bytes will be set to the kernel address that we will be using as our ‘gadget’ to modify arbitrary kernel data. So, instead of executing a payload in userland, we will be redirecting the execution to kernel function, nt!RtlCopyLuid that will enable us to modify arbitrary kernel data.

Figure 9. nt!RtlCopyLuid.

Since we control both the RCX and RDX registers, we can use this function to complete our “write-what-where” primitive.

We will be needing, again, a second Private Namespace object with a custom boundary name which at offset 0x0A of the name data (Figure 8 – first pointer dereference) must contain the address of the first object + the distance in bytes (0x1a0) between that address and the location of the boundary name in that object. Remember that at the first 8 bytes of the boundary name of the first Private Namespace object, we have inserted the address of nt!RtlCopyLuid. Note that as before, we must take into account that at the result of the previous calculation, the value 0x0C will be added in order to reach our arbitrary kernel function pointer value, loaded at the R10 register (Figure 8 – second pointer dereference).

So, this is how it should look:

*(ULONG_PTR*)(boundaryName + 0x0A) = customPrivNameSpaceAddress + boundaryNameOffsetInDireObject - 0x0C;

Then, we need to take control of the first two parameters.

The first parameter loaded in RCX is read again from our custom boundary name, at offset 2 (the first two bytes of our custom boundary name must be 0x4B,0xFF). So, we will be setting there the address of our process’ token object + the offset (0x40) to reach the nt!_SEP_TOKEN_PRIVILEGES structure member.

Figure 10. nt!_SEP_TOKEN_PRIVILEGES.

It should look as follows:

*(ULONG_PTR*)(boundaryName + 0x02) = tokenAddress + 0x40;

Finally, we can also control RDX since the value of R12 is copied over, which points at the address of our userland input buffer + 0x10 (see Figure 1 – 6th node). This is where we read the data from, to write into an arbitrary kernel address. In this case we will overwrite the ‘Enabled and ‘Present’ privileges members of the aforementioned structure (Figure 10).

It should look like this:

*(unsigned __int64*)(inputBuf + 0x10) = _ULLONG_MAX;

So, our exploit will have to reach the vulnerable function twice in order to complete the attack.

Figure 11. Objects Interconnection – Write-What-Where Primitive.

The image above shows how the two objects are ‘interconnected’ in order to complete our “write-what-where” primitive to finalize the exploit.

Conclusion

This was an interesting bug to examine and exploit, as it shows once more that no input data should ever be blindly trusted. From the developer’s perspective, trusting a kernel pointer to read data from, presumably out of user’s control, was a ‘safe’ decision to take. However, it turned out to become a serious vulnerability in multiple products of two different vendors that use the same SDK.