This blog post walks you into the discovery of CVE-2022-43747 in baramundi Management Agent (bMA). CVE-2022-43747 is a buffer overflow vulnerability, which allows an attacker to achieve remote code execution when a certain condition is met. In this post I will demonstrate how I have discovered the vulnerability, what condition is required for code execution and how I have developed a proof of concept.

Intro

Late 2019 I took the Corelan Advanced heap exploit development course. With so much adrenaline kicking in, I wanted to see if I can apply some of those heap exploitation techniques on a black box. At that time I had a pentesting project going on and a system had bMA on it. “It would be very nice if I could find a heap vulnerability in bMA that I can exploit, especially remotely, because that would mean a wide scale network compromise of all systems running bMA”, I thought to myself.

Gathering intel

I copied bMA to a VM and started tinkering with it. It is essential to gather as much passive information as possible, just like with any other pentesting engagement. Looking at the application’s folder we can tell it has a very large and probably huge code base by just looking at the number of EXEs and own DLLs not related to 3rd parties. Inside configuration and log files you can find some more information which might come handy at some point. Looking at listening ports.. oh wait look, there are two TCP ports in listening state and one UDP port open. The ports are waiting for connections from any host.

Looking at available processes I see that bma.exe is running as a privileged Windows service while BMSTrayNotifier.exe is running in my current user’s context. Obviously, finding a vulnerability in a privileged service has more advantages over less-privileged processes.

So we have processes listening on some TCP ports. Let’s try a quick and dirty way to figure out what is actually listening on those ports.

Well, will you look at that. Testing port 11001 (and 11000) it appears that we have a web server listening. Additionally, we see that the server banner says gSOAP/2.7 and that the response indicates a SOAP message, so the request should probably be a SOAP message. Nice.

What about the firewall? Well, looking at inbound firewall rules I do not see a rule that would allow incoming connections to those ports. The default firewall settings blocks incoming connections, unless a rule explicitly allow access. Hmm, that’s bad.

Considering the firewall settings I would presume that those ports are used for inter-communications between bma.exe and BMSTrayNotifier.exe. Additionally, I would also presume that bma.exe initiates a connection to the remote central server, polling for new commands, etc. Makes sense.

To recap what we have learned so far:

  • Two sepearate processes, listening on TCP ports for remote connections from 0.0.0.0, one is highly privileged.
  • HTTP server is listening on those ports, expecting a SOAP request, delivering a SOAP response.
  • Firewall settings and rules do not allow remote network connections to reach the ports, so even if we find some sort of vulnerability exploitable over those ports, we would be limited to local scope.

Discovering the overflow

A locally exploitable application is not bad if we could exploit bma.exe and obtain SYSTEM privileges, say for local privilege escalation situations.

We need to figure out what SOAP requests are valid and whether we can abuse one to achieve something useful. In order to do this, we need to dig into the decompiled/disassembled source code and we need to intercept some inter-communications, which makes linking the dots while working with a debugger easier.

I fired up some tools, intercepted some messages, inspected some strings within the binaries, started placing breakpoints here and there and dynamically inspected the execution flow. After spending some time I started constructing my own messages. It appeard that I could trigger certain actions with the help of those messages, like animating the systray icon and displaying own notifications, among others. All such actions could be triggered without any sort of authorization! Supposedly I could interact with bMA over the network, I could then display a notification with my own text instructing the user to do something or delivering fake information to trick him into something. That was the first vulnerability that I have found (more to that later).

Jumping between bma.exe and BMSTrayNotifier.exe I had a good confidence on what was going on. In a nutshell when a SOAP message is received, the embedded gSOAP parser (remember the server banner leaked in the response?) starts checking if the SOAP message is valid (conform). SOAP is based on XML, so lots of syntax checking is done on elements and attributes. If the message is a valid SOAP message, the application checks if the action is a valid action (one that corresponds to a certain function to be called/executed). Each action has different “parameters” (elements) that are also checked if they exist, and when they do, some are checked whether they have the same expected data type (integer, boolean, etc.). Let us take a look at the following example:



  
    
      http://localhost:11000/clientagent
      12345
      blah blah blah
      c29tZSBiYXNlNjQgZW5jb2RlZCBzdHVmZg==
      0
      false
      0
    
  

The example shows a SOAP message that can be used to display a notification in the systray area. The action called is ShowInfoWindow which has several parameters, such as CallbackEndpoint, pid, Content, etc. As you might have guessed, sending that SOAP message to localhost:11001 with the Content of blah blah blah would cause a notification from bMA with that message. This notification is used when bMA wants to notify a user about something, such as asking the user whether s/he would like to start an assigned job immediately or at a later time.

Looking for an overflow, my first thought was to use some crazy long dummy text as Content and play around with the values of each parameter, of different actions, and observe what was going on. An overflow is not always so straight forward. This is not the 90s.

What I could tell is that my crazy long content was being saved on the heap, at some point, for parsing.  Being so naive I started looking for all sorts of heap management issues, logging allocations and frees, looking for double frees, memory leaks, use-after-free, and so on. It was just new land and it definitely was a dumb idea without years of practicing 😀

If I recall correctly, I spent around a week doing that, by that time the project was over but I was not ready to move on. I still had bMA in my VM. I started looking for simpler things, like actions that could be abused in some unexpected ways. It was around christmas time. I have had built myself a long list of actions that I have seen with their parameters and possible values, etc. That is when it occured to me. What happens if I manipulate the SOAP message in a way, that the message itself remains conform? I started adding dummy attributes and parameters and dummy attributes on those dummy parameters; while looking at the debugger to see what was going on.

What I observed was that as long as the message is SOAP conform, the action is correct (valid), and all required parameters are there with valid data types, then it does not matter what crazy parameters I add. In order for that to work, the parser must iterate over all parameters (elements) in order to tell which is which and whether all required ones are there, so what happens if I use a looooong parameter (element) name?

Here goes nothing. When I sent my first parameter alongside the required ones nothing happend. I started increasing the size (like a n00b, as if this was the Overflow101 course), but nothing kept on happening. Sure, the application just allocated enough memory for that and as long as there is no other memory management bug hiding somewhere to be triggered only on some weird condition, then this approach not going to work, again, it is not the 90s. Being so focused on exploiting bma.exe due to its higher privileges I was not paying so much attention to BMSTrayNotifier.exe. After all I had presumed that I was dealing with the same logic. I was wrong, and I am glad that I have found that out 🙂

While everything I have mentioned so far applied to both applications, it struck me when I saw that BMSTrayNotifier.exe placed the parameters (elements) on the stack while parsing them. Wait what? Why? I took my crazy long element and sent a message to BMSTrayNotifier.exe. The application disappeared from the systray (the process died)!

While I was still unable to quit jumping, BMSTrayNotifier.exe came back on. bma.exe has revived it!

So why did it crash?

I ran the application again attached to Immunity Debugger, sent the message, looked at the debugger to see that I have hit an access violation when writing to an invalid memory address. That memory address was under my control, the written value was not. It appears that I have overwritten a memory pointer that was being referenced by EAX+10.

So where does EAX get its value from? Looking few lines above I noticed the following.

There, ESI+155E8 moves another pointer to EAX. Looking at ESI it holds the address 0x413895C, which if you look at the bottom right corner you will see that it is an address on the stack. In short: the stack holds somewhere a pointer to what seems to be a struct (also on the stack). The access violation occurs because I am able to overwrite that pointer. Was I able to overwrite just that pointer, or a larger portion of the stack?

To confirm what was written where I sent a looooooooooooooooooooo…ooonger malicious element and observed that the crash changed to an access violation because I was now trying to write beyond the end of the stack. Nice. This has more potentials.

Is it a classical stack overflow?

A classical stack overflow would occur when you are able to overflow a buffer in a way that you also overwrite the saved return address.

In the above illustration let us say that one of the DrawLine()’s locals is a buffer, which we are able to overflow. When we write enough data beyond the buffer’s boundaries then we will soon overwrite the Return Address. This address points to the next instruction that must be executed when the application finishes executing DrawLine() and wishes to return from DrawLine() (callee) to the parent subroutine DrawSquare() (caller). If we overwrite that return address and allow the application to execute normally (by avoiding any exceptions), then the application will return to OUR controlled address. That’s how you control the execution flow in a classical stack overflow.

So is it a classical overflow? unfortunately (or fortunately?) it was not. The buffer I was overflowing was in the main() subroutine of the application, so while I would be able to overwrite the return address of main(), the exception that we saw earlier due to EAX+10 would get thrown waaaay before that. In addition, the exploit is not practical as overwriting the return address of main() is not enough unless I can make the application exists cleanly.

At that point one has to figure out another approach. One obvious approach would be overflowing the buffer just enough (but not alot) to overwrite some pointer on the stack. If the original pointer was a pointer to a subroutine and the subroutine is called, then by overwriting the pointer, I would be able to make the application call my own subroutine (memory address). Check out the following example.


MOV EAX, [ESI+1234]
...
CALL EAX

Remember that JMP and REG+offset would also do the trick

Unfortunately I was not able to locate such instruction that I would be able to reach without triggering the aforementioned exception 🙁

All is not lost. So we have a stack overflow and we can overflow as much as we need all the way to the end of the stack. We also have an exception thanks to an overwritten pointer, so how about overwriting SEH records?

As you can see, I was able to corrupt the SEH chain and overwrite the SEH and nSEH. With that, I would be able to control the execution flow, as soon as the exception is raised… well, not that fast. In 2003 Microsoft introduced a memory protection mechanism called SafeSEH to prevent attackers from exploiting SEH-based stack overflows. SafeSEH enforces few checks before a SE Handler is called. To keep up with the game, exploit developers came up with few ways to bypass SafeSEH. One of the easiest approachs was using a SE Handler (memory address) from an application’s module (DLL) that is not compiled with /SAFESEH.

*Cough* OK, that would have been too easy. Another approach was to find a heap address, marked exceutable, sacrifice a lamb, draw a circle with its blood and dance in it half naked while praying to find an instruction that you can use as a trampoline (e.g. pop, pop, ret).

Fast forward 2021

With no module that is not compiled with /SAFESEH available, it seemed that my SEH-based stack overflow would get me nothing more than a crash of the application. At that point I have decided to wait and see if any future versions of bMA would introduced a bug that would make the exploitation conditions easier to fulfill. So I waited, waited for 2 years 🙂 (let us say that I had access to a version of bMA that would get the usual updates).

During that time I would come back every couple of months to check if something changed. While being at it I was intrigued into understanding what that buffer that I have overflowen had, and why are there pointers there.

Knowing that I am dealing with SOAP and gSOAP as parser, I started looking at the documentation and published samples of gSOAP. From the documentation I knew I was dealing with a SOAP struct. However according to online resources, the current version was 2.8.x, while according to the banner version we saw earlier, I was dealing with 2.7, which might as well be 2.7.x.

OSINT to the rescue!

I started googling all over the place looking for all 2.7.x header files. I needed as many as possible to try to see if one of them would match the structure that I have in memory, worst case, I would at least find the most similar one. Eventually I found those:

Unfortunately none of them matched my structure 100% (or maybe I got blind looking at hex dumps for many hours). In any case, I have used the gained information from the files to understand what I am dealing with. The following screenshot shows part of stdsoap2.7.1.h. Highlighted are the attributes that form buffers within the object. I knew that SOAP_BUFLEN was max 0x8000 by examining the structure in memory dumps (that is the standard value as well by the way), and I knew SOAP_TAGLEN was 0xFF. Both had those exact values when WITH_LEAN is true. With that information I was able to map most of the values I saw in the hex dump with the structure.

Fast forward end of 2021 (it is christmas time again ;-))

So here I am looking at Trend Micro Apex One as part of a red teaming engagement I was running and trying to figure out a way to avoid detection. That moment when you are looking at something probably irrelevant to what you are doing but still something makes your eyes go wide open..

OK, maybe not that wide but here is what I saw:

Waaaaaiiittt.. that (hidden) module is the UMH module that gets injected automatically in EVERY user-space process. It is NOT compiled with SafeSEH enabled. Meet my CVE-2022-44654.

Basically if you happen to find bMA on a system with an AV/EDR/Whatever that is auto. injecting a module into your user-space processes and that module is not compiled with SafeSEH, then you have basically fulfilled the missing requirement to bypass SafeSEH and exploit that SEH-based stack overflow 😀

I took a copy of the 32bit and 64bit versions of the module and using Windows Registry I forced loading them into the user-space processes using an old “feature”.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows]
"AppInit_DLLs"="C:\\Windows\\System32\\tmumh\019\\TmMon\.8.0.1054\\tmmon64.dll"
"LoadAppInit_DLLs"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\Windows]
"AppInit_DLLs"="C:\\Windows\\SysWOW64\\tmumh\019\\TmMon\.8.0.1054\\tmmon.dll"
"LoadAppInit_DLLs"=dword:00000001

After reboot that DLL was now loaded in the memory space of bMA but is not part of its own loaded modules. I had what I needed to continue building my exploit based on an assumption :B.

Exploiting that SEH-based stack overflow

Now that I have all the ingredients that I need for the exploit (forget about ASLR/DEP for a moment), it is time to find out what limitation is there on the payload I can use, starting with bad characters.

Bad characters are characters that get mangled or removed or are simply unaccepted in certain contexts because they make the syntax wrong. In our case, I was injecting within an element’s name. The XML syntax sets clear rules on what characters are accepted within an element’s name (RTF RFC :P). With that information and simple trial and error, the following characters were breaking the whole thing:


badchars="\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x22\x26\x27"

With that off the table next I need to find my trampoline instruction within the UMH module. A trampoline instruction is an instruction that when executed would allow me to redirect the execution flow to a memory address under my control. I picked a dummy executable memory address within the UMH address space and overwrote the SEH+nSEH with that address. With a breakpoint on that address I let the exception get triggered and then I inspected the registers and the stack at the time I first entered the UMH module. The idea was to see if any value under my control has already overwritten a register or was on the stack somewhere close to my current stack pointer. Luckily a DWORD under my control were there on the stack right below my current stack pointer. All I needed was to POP 4 bytes then return, so any POP REG; RET; chain would work. Easy.

Without ASLR and DEP the full exploit would now be ready to rock ‘n roll, but that would have been too easy. My payload being on the stack means that I cannot just jump to my shell on the stack. DEP (Data Execution Prevention) would kick in, preventing my stack “data” from getting executed. I need to bypass DEP.

Dealing with DEP

Bypassing DEP can be done using several approaches, but the most common and dominant approach is the ROP (Return-oriented Programming) approach. In simple words: the stack can hold only “data”, such as “pointers”. It is not meant to hold instructions that get executed (as it was back in the golden age of popping shells in everything). The stack is used to store return addresses, that are popped off the stack and used to redirect the execution flow. Let us imagine that we have the following on the stack:


0xAAAAAAAA
0xBBBBBBBB
0xCCCCCCCC
...

Now let us imagine that our IP (instruction pointer) is currently on a RET instruction. If that RET gets executed, the value of 0xAAAAAAAA would be popped off the stack into EIP and the execution flow goes to the instruction at 0xAAAAAAAA. Say now that we have a POP REG instruction at 0xAAAAAAAA followed by RET. Once POP REG gets executed, it will copy the value of 0xBBBBBBBB from the stack into that REG register, then once the RET is executed, the execution flow would be redirected to 0xCCCCCCCC and 0xCCCCCCCC is removed from the stack. Get it?

The idea of ROP is simple: instead of having instructions on the stack, which would not work because of DEP, we will store pointers to useful instructions (we call them gadgets) and using a trampoline, we abuse the fact that each subroutine ends with a RET instruction that transfers the execution to a certain code, so if we place only pointers to gadgets that end with a RET instruction, these gadgets will get executed and the RET instruction would move us automagically to the next set of instructions (gadget) and so on. This is why it is called RETurn-oriented Programming. We are not actually writing any code, we are simply reusing EXISTING instructions to achieve the same result.

One catch to watch out for though is that we will have to find pointers to useful gadgets within the existing code, and as we are dealing with pointers, we will need to account for the fact that the addresses will not remain the same due to Rebasing and ASLR (when these are an issue).

Dealing with ASLR and Rebasing

The PE loader would automatically rebase the base address of a module by default if their preferred base address is conflicting with another preferred base address of another module. For most modules rebasing is almost guaranteed to kick in. EXEcutables have usually a preferred base address of 0x00400000, unless otherwise changed and since there is only one EXE module in a process memory, it does not change that often, but its lower address starts with a 0x00, which is a bad character most of the time, so unless you have a very large EXE, the higher address of its code section would also contain a bad character.

ASLR would randomize the application’s own modules on each new app start, while it would randomize system modules only on reboot. It also guarantees a conflict-free base address.

ASLR is a beast. Bypassing ASLR like a boss would require finding a memory leak, e.g. via a format string vulnerability. Not having that much time (and while I was not planning on waiting another 2 years :-P) I took the dumb approach to bypass ASLR, which is bruteforcing, since I was targeting a 32 bit application 🙂

We know that BMSTrayNotifier.exe would get revived by bma.exe automatically. Unfortunately that does not happen instantly. The check appears to be done one time per minute. That is alot of time under certain circumstances (more on that later) if I want to bruteforce every possible high-nibble. To reduce the number in the guessing game, I started rebooting the Windows 10 system and taking note of every high-nibble I saw for the UMH module. Eventually I have settled on few base addresses that seemed to repeat more often than others. These were [0x6C??, 0x6B??, 0x6A??, 0x6D??, 0x6E??, 0x6F??]. Because ASLR randomizes the base address of system modules only once per reboot, the UMH module was a perfect candidate to target. Other system modules would not have worked because they have SafeSEH active.

So, I will target UMH. How will I be able to tell if I was able to find the current base address? Well, I would not be able to do it, but I will try a different base address each time and add the offset of my trampoline instruction to it. If the application crash because I told my PoC to jump to an instruction that did not exist or was not the instruction I was hopping for, then the application would crash. It would not be responsive and I would know that it has crashed due to me missing the shot. I will wait a minute or so then attempt again with a different base address.

Dealing with DEP using ROP (continued)

Supposedly I was able to figure the base address of UMH and hit the trampoline instruction, I would now need to find addresses of gadgets within the UMH module. I need to count for bad characters within those addresses. Because of ASLR, this might get tricky, so I have to count for this dynamically.

I will be looking for gadgets that would allow me to mark the stack executable again (other approaches exist, such as allocating executable heap memory, copy the shellcode to it, then jump to the shellcode; but I have choosen another approach because I had a pointer to my current position on the stack, so I could just simply mark it executable directly). For this I will be using VirtualProtect().

After spending some time looking for gadgets I managed to locate all what I needed in UMH. Actually, once you defeat ASLR it would be easy to pick gadgets from ANY module that you desire, dynamically 🙂

Because I did not know if ASLR would cause the address of one of my gadgets to have a bad character in it, I simply wrote the code to count for that and check if the baseaddr+offset (of each gadget) would contain a bad character. If so, the attack would get aborted because I would have to wait for a reboot.

baramundi adds an Authorization Token 🙂

I have mentioned earlier that I could send SOAP messages and execute actions without any sort of authorization, as bMA did not check to see if the message was coming from the application’s modules. However, sometime between late 2021 and 2022 baramundi added an authorization token to the actions. This had to be presented or otherwise the action would not get executed. Bummer, because I was planning on submitting it as a different finding 🙂

Nevertheless the authorization token did not have any affect on the overflow vulnerability. I would still be able to exploit it even if I present an invalid token, because the vulnerability would get exploited during the parsing of the elements and before that token is even verified 🙂

PoC||GTFO

I picked my POP ESI; RET trampoline at [BASEADDR+6FDBC]. I have used a NOP-sled just in case and orchestrated the rest as mentioned earlier. Here is my little proof-of-concept (do not judge my coding style :)):

https://securiteam.io/CVE/CVE-2022-43747.py (due to issues with syntax highlighting I had to put it in a separate file)

Remote code execution ftw!

All what we have discussed so far was exploiting bMA locally and not even achieving higher privileges 🙁 We need..

Let us go back to the basics. Check this out again:

So, we have a web server, listening on localhost, for any remote connection, but we cannot talk to it over the LAN because the firewall would kick in. Web server + remote connection? How about talking to it over the Internet/Intranet from our own website? Would SOP kick in? Would SOP even apply? Did they account for that?

Evil Plotting Raccoon meme

Just use JavaScript’s unescape(“%uXXXX%uXXXX”) for pointers and non ASCII characters and you are set to exploit this vulnerability over the Internet.

Happy hunting 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.