Estimated Reading Time: 5 minutes
On a recent Red Team engagement, I was poking around having a look at different files and trying to see if I could extract any information that would allow me to move laterally through the network. I was hopeful, as always, that I would land on domain_admin_passwords_2024.xlsx or something (don’t laugh – we’ve all found that file at least once!). Unfortunately, this time, that file wasn’t present on the endpoint that I had landed on, so I had to settle for some Palo Alto Global Protect logs instead.
In C:\Users\username\AppData\Local\Palo Alto Networks\GlobalProtect there was a file called panGPA.log that contained something interesting:
It struck me as odd the way that the passcode and password were obfuscated. Why would they be different lengths? Surely, they wouldn’t have just replaced each character of the passcode/word with an asterisk? Because that would mean they would need to know the plaintext version – and there really isn’t a reason apart from pure laziness to do that.
With that in mind, it was time to fire up trusty x64dbg and see what is going on under the hood.
Assuming (I know, I know – but trust me, it’s not a huge mental leap this time) that panGPA.log was a log file for PanGPA.exe, we load that into our favourite debugger and have a look through some of the references.
Hmmm – let’s have a look at those shall we? Out of the three, this one looks the most interesting:
I picked this one as you can see the instruction to mov edx,0x2a, where 0x2a is the “*” character.
In fact – there are multiple references to mov edx,0x2a in this code section and searching for all instances of this command lands us generally in code sections that look like they are processing XML – which is exactly what we found in the log. Let’s go digging …
After _a lot_ of digging around we can start to put a picture together of what is happening. PanGPA ‘speaks’ to PanGPS over port 4767. It makes XML based requests and receives XML replies.
A typical response may look like something like this :
<?xml version="1.0" encoding="UTF-8"?>.
<response>..
<type>hello</type>..
<status>Connected</status>..
<protocol>IPSec</protocol>..
<portal-config-version>4100</portal-config-version>..
<error-must-show/>..
<error-must-show-level>error</error-must-show-level>..
<error/>..
<uptime>433</uptime>..
<byte-received>10495609</byte-received>..
<byte-sent>4743428</byte-sent>..
<packet-received>14169</packet-received>..
<packet-sent>9217</packet-sent>..
<incorrect-packet-received>0</incorrect-packet-received>..
<incorrect-packet-sent>0</incorrect-packet-sent>..
<server-ip>x.x.x.x</server-ip>..
<local-ip>y.y.y.y</local-ip>..
<local-ipv6/>..
<connect-mode>0</connect-mode>..
<product-version>6.2.4-652</product-version>..
<product-code>”{00243e9f-d787-4b07-a109-a1c885f2c032}”</product-code>..
<portal-status>Connected</portal-status>..
<user-name>[email protected]</user-name>..
<username-type>cc</username-type>..
<state>Connected</state>..
<check-version>no</check-version>..
<portal>express.gpcloudservice.com</portal>..
<discover-ready>yes</discover-ready>..
<mdm-is-enabled>no</mdm-is-enabled>.
</response>
Let’s start setting some breakpoints and see if we can build up a picture of what information flows back and forth between panGPA and panGPS. For now, let’s just focus on the * used for obscuring some of these details in the logs. Setting a breakpoint of every instance of this hits paydirt immediately:
Yeah … the red ones are a password in plaintext. The full XML response was located in memory and contained the username as well.
It was also possible to find the deactivation password and the uninstall password.
I cannot see how this is compliant with any current security framework such as NIST, FedRAMP etc. etc.
<rant>
Storing credentials in memory in plain text is such a basic security flaw – it leaves systems completely open to basic memory scraping or dump techniques to easily extract sensitive data, rendering any encryption efforts elsewhere irrelevant. It is negligent, it violates best practices, it disregards the principle of least privilege, and it creates an unnecessary attack surface that increases the risk of credential theft and subsequent lateral movement. In an age where memory attacks like Mimikatz are finally being retired, this sort of shoddy coding is inexcusable and reflects a deep misunderstanding of basic secure coding principles and threat mitigation.
The client is literally only speaking to the panGPS service and the Palo Alto endpoints. There is no reason AFAICT that this data ever needs to be in plaintext on the client endpoint apart from the very first time it is entered. Encrypt it dammit.
</rant>
With that rant out of the way, for our Red Team engagement, this was gold. The credentials to access the VPN were different than the ones used to log on to the machine – so now we had a second set of credentials for relay into the environment. Happy days indeed for us, less so for the client.
I have put together a proof of concept to demonstrate extracting these credentials from memory (focusing on username and password initially). The code can be found at https://github.com/t3hbb/PanGP_Extractor but basically we terminate the existing panGPA process, attach to a newly launched suspended panGPA process, set a breakpoint, resume, and then read the memory at RSI when the breakpoint is triggered.
Output looks like this :
It would be better, stealthier and generally cleaner if we spoke directly to panGPS itself and impersonated the panGPA client. This would eliminate stop/starting the VPN to extract data – total downtime is only a few seconds but still…
As always, any tips on improving the code/methodology gratefully received.