August 26, 2026
How the fix for the fix to Globalprotect that was meant to fix the fix failed to fix the fix… wait……
Paloalto recently released a fix for CVE-2025–0117, which broke three of my exploits that made use of the RPC comms between the GUI and the…
By Nullsgotroot
5 min read
Paloalto recently released a fix for CVE-2025–0117, which broke three of my exploits that made use of the RPC comms between the GUI and the service. The other two had yet to be released to Paloalto (working but not cleaned up yet, hence not disclosed), so this fix was a move in the right direction.
The fix however proved to be easy to bypass, which revived all of my exploits up to the latest version, 6.3.3, as if the patch was just a polite suggestion. In this sense easy to bypass is not the same as easy to understand why the bypass worked, and I got a bit lucky. But hey, understanding things is overrated when half a theory and a bit of duct tape gets the job done. The more I dive into reversing, the more these little "accidents" seem to go my way. Pure skill, obviously.
To understand the root cause of the issue requires some background knowledge about how the GUI and service work together, Spoiler: they don't always see eye to eye. In particular, the new memory mapped region in 6.3.3, and how the service tries to authenticate RPC messages comes into play. Anyway, here's the high-level process flow:
First, set up a TCP port on localhost and listen on port 4767
Then, loop over below if the connection dies, and accept incoming connections
Read the registry key "check-communication". If its value is set to 'yes', enable the patch by setting a global variable to true, else, set it to false. We'll refer to this as condition A
Unmap shared memory if the connection died since the last connection loop
Map the shared memory if it hasn't been mapped already. Then, search for the PanGPA process by name. If it is found, set an ACL on the mapped region and set a global variable to true to indicate that the memory is mapped. If the process is not found, set the global variable to false. We'll call this Condition B, because it sounds much fancier than "Condition A"
Check that the connection is coming from a process within the Globalprotect folder, and reject the connection if it isn't
Loop over below for each RPC messages. For each packet, receive the 16 byte RPC message header
If the patch (CVE-2025–0117) is enabled via the registry key (Condition A) and the memory mapping is set to true (Condition B), verify all processes that have their PIDs listed in the mapped memory via WinVerifyTrust (PIDs are added to the map by the GUI). If no PIDs are stored in the mapped region, fail the check in the function calling WinVerifyTrust. If either Condition A or B is false, the whole RPC auth check vanishes like it had better things to do, and the service proceeds to process the RPC message without performing the check.
Receive the rest of the RPC message, with the length specified in the message header received earlier
Decrypt and process the RPC message
So a couple of things stand out here, though they might have solid logic behind them, it's always hard to tell with assembly. You either dive in too deep and lose focus of the big picture, or keep it so broad you miss the important details. So, yeah, take the above as probably maybe correct on some days of the week.
Here's an interesting observation: why is WinVerifyTrust used on processes that send RPC messages to the service, specifically, those that write their PID to the mapped region (as far as I can tell, only PanGPA does this)?
All processes that send RPC messages have their executable paths checked to ensure they reside within the Palo Alto folder. Because of this, and since you don't typically have write access to that folder, the certificate validation with WinVerifyTrust will always succeed, even if you inject into the process. Injection doesn't alter the file on disk, which is what WinVerifyTrust actually verifies.
On the other hand, if you can modify the Palo folder (e.g., by placing a custom binary that passes the path check and can thus sends RPC messages), then you already have administrative privileges, making this privilege escalation unnecessary.
So, as far as I can tell, WinVerifyTrust will never return a negative result under these conditions. It's also worth noting that the function calling WinVerifyTrust can fail in another case, specifically, if it receives a RPC message but there are no PIDs present in the mapped region. This condition could indeed happen in our exploit, but we're going to avoid that code path completely instead.
So to pass the auth check, first prize would be to get condition A or B to fail, skipping the auth check completely instead of entering it. Condition A depends on a registry key we don't have permissions to modify, so that's a no go. Looking closely, what would happen if PanGPA wasn't running during the memory mapping stage, ie, causing the memory map to fail? Yep, it fails open, letting us dodge the auth check like it's a minor inconvenience.
One snag with offing PanGPA is the service's nagging habit of restarting it, meaning we would have to kill it, start our own, inject into it, and grab the socket before the service can launch it (which would grab the socket we need). This means that if we launch and inject into PanGPA, like the previous exploits, the process would exist when the memory map is performed. With the process alive at memory map time, condition B suddenly remembers it's supposed to do something and the auth check doesn't fail open anymore.
So what do we do? It's simple actually, pick another lucky process in the palo folder to (ab)use. We then kill PanGPA and immediately grab the socket, which causes the socket setup code to run again. This kicks off the memory mapping code, and the service starts looking for PanGPA to do the mapping but doesn't find it (the service doesn't restart it that fast). This causes the fail open condition for the auth check. Our injected process is now free to send RPC messages without worrying about the pesky auth check and we're golden, ready to dust off exploits like CVE-2025–0117.
So how do they fix this, well the same way I said in the other exploit posts. Don't let an unprivileged process boss around a privileged one, it's just asking for trouble. Since bolting on security to a process with a big code base is about as easy as teaching a cat to fetch, what else can we do? The only thing that comes to mind at the moment is to only accept RPC messages coming from the PanGPA instance that the service explicitly launched, that would limit what we could inject into. This could probably be bypassed by re-implementing the memory mapping code in our injected code and inject into that process though. Happy hacking.