Reliably Finding and Exploiting ICS/SCADA Bugs

January 16, 2020 | Mat Powell

Full disclosure: I love SCADA software. I love the endless teaching opportunities available with a simple install. Looking for your first stack-based buffer overflow? Try SCADA. SQL Injection? Try SCADA! Ready to go directly to NT AUTHORITY/SYSTEM from an unprivileged account in a matter of seconds? Try SCADA!

Seriously, though. I love SCADA software. Anytime I’m looking to get more exposure to a particular classification of bug or when I make modifications to my fuzzing rig, I always go back to SCADA. Mostly because that’s what I started on when I on-boarded with ZDI, but it’s also never let me down in terms of finding immediate results.

With Pwn2Own Miami right around the corner, I wanted to go back and revisit one of my favorite bugs—Advantech’s infamous IOCTL 0x2711. The original bug was discovered and reported to the ZDI by Steven Seeley. I blogged about this back in 2018 and, for some reason, I still feel personally attached to this bug. Unauthenticated RCE over RPC as Administrator. Just… terrible.

Interesting enough, this bug went through the ZDI program several times and while some changes have been made, the underlying problem still exists. My thinking was that if Advantech wasn’t going to fix it, I would abuse it and just keep hitting them over and over in hopes they’d get sick of hearing from me and finally lock it down.

Side note: I’m still waiting for that day.

But I want to take a little bit of time and talk about my general approach to fuzzing, and in this particular instance, demonstrate why deploying a product without modern security mitigations and antiquated coding standards is still a thing in 2020 and is still always a bad idea.

Let’s talk about process.

Figure 1: The Fuzzing Process

Regardless of the product being targeted, my approach always follows the same structure. I’m going to generate something (i.e. arguments, file-based input, etc.), mutate, and feed it into an application with a debugger attached. After a pre-determined amount of time I will terminate the process being targeted and log the results. Crashes will be isolated, and the process will start all over again.

Once an application crashes, I like to use the !exploitable extension for windbg to assist with de-duplication and the prioritization and origination of crashes. For those unfamiliar with this extension, !exploitable is invoked once an application crashes and it attempts to classify the crash based on the analysis it performs. It assigns each crash an Exploitability Category (i.e. Exploitable, Probably-Exploitable, Not-Exploitable, etc.) and a hash associated with the crash. I use this information to bucketize the crashes based off of the category and then again with the major and minor hash. It ends up looking like this:

Figure 2: Crash Results

From here we can start opening up the debug logs and get a better understanding of what caused the application to crash. For example, if we look at the log file for bwmail.exe (ZDI-18-047 courtesy of Steven Seeley), we can see that it ended up crashing due to a stack-based buffer overflow.

Figure 3: Stack-based Buffer Overflow

If we were to open the bwmail.exe application within IDA, we can find that the root cause of this bug was the use of the blacklisted API call, strcpy. If we restart the application and set a breakpoint on that call and dump the contents of the EAX register, we can see it contains our payload.

Figure 4: Root Cause

If we step over this call and examine the stack, we can see that it has been completely overwritten with our payload. With no ASLR or DEP to worry about, this seems like a prime candidate to write our exploit. But first we need to make sure we can actually send this attack over the network.

Figure 5: The remains of the stack

To test this over RPC, I’m going to leverage some code that was published by ZDI researcher Fritz Sands along with his blog post talking about Advantech and RPC. To do this, I am going to use two machines: the Windows 7 machine on the right is the attacker and the Windows 10 machine on the left is the victim. I attach windbg to the webvrpcs process, enable child debugging, and fire away. As expected, the debugger breaks in, and we can verify that we have successfully tanked the stack. Time to build the exploit.

Figure 6: Checking if the payload can be delivered remotely

To help speed up development, I am going to be using the windbg implementation of mona written by Peter Van Eeckhoutte. Mona has some great features that will really simplify and streamline this process. First thing, we need to see at what point the buffer was actually overwritten. To do this, we can use mona to generate a cyclic pattern—a unique, non-repeating string of characters that we can search for during debugging.

Figure 7: Creating the cyclic pattern with mona

Next, we send this pattern back into the bwmail application as the command line argument, and, as expected, the application crashes.

Figure 8: Crash based off of the cyclic pattern

Once the debugger breaks at the crash, we use mona’s findmsp functionality to search the applications process memory for all references of the cyclic pattern. When looking at output like this, I first check the registers section to look for one of three things: overwriting the saved return pointer (EIP), direct register control, or overwriting the Structured Exception Handler (SEH). In this particular example, we’re going with the latter.

Figure 9: Finding the cyclic pattern with mona's findmsp function

Figure 9: Finding the cyclic pattern with mona's findmsp function

Mona tells us that the Next Exceptions Registration Record (nSEH) is overwritten after sending 1,192 bytes of data. Great! But now what?

Using mona’s sehchain functionality, mona will actually print an exploit template for us to use. Let’s take a look.

Figure 10: Suggested payload format, courtesy of mona

In order to pull this off, we’re going to need 1,192 bytes of data, a short jump, a pop/pop/ret instruction, and some shellcode. We have the 1,192 bytes. We have the short jump. Next we need the location of a pop/pop/ret instruction set. Mona seh -n will search the process memory for these instructions and omit any results that contain a null byte.

Figure 11: Using mona to find pop/pop/ret instructions

Mona returns a lengthy list of valid pop/pop/ret instructions found as well if the library takes advantage of modern exploit mitigations like DEP or ASLR. Lucky for us, none of those are found here. 😀

Figure 12: The results

Once we add in our Windows shellcode, we have everything we need to pull this off. Putting it all together we get something that looks like this:

Figure 13: Putting it all together

Conclusion

If you’ve always wanted to get started with bug hunting and make some extra cash on the side, you can’t go wrong with SCADA software. The lack of modern mitigations and proliferation of insecure coding practices make it a juicy target for both researchers and attackers. So what are you waiting for? Download some software, fire up that debugger, and submit your bugs to ZDI!

I hope to see you in Miami where other SCADA bugs will (hopefully) be demonstrated by Pwn2Own contestants. Until then, you can find me on twitter @mrpowell and follow the team for the latest in exploit techniques and security patches. See you in Miami!