One of the least discussed issues with the Arma 3 Engine, Real Virtuality 4, is the Engine & the Developer’s inability to protect game servers from its own scripting language.
For the last 3+ years, I have been abusing the scripting language & its intricacies to execute SQF code wherever and whenever I want. For many SQF developers, the idea of abusing the implementation of this language can only reach so far. However, once you really dive into the possibilities and flaws in the SQF implementation, you can find some really crazy stuff. We’ve seen things like Server Transfering by KillZoneKid, to the last code execution exploit I posted on November 4th, 2016, nearly 3 years ago. None of these exploits really dove into anything inherently wrong with how the Engine operates. Server Transfer works due to when the game decides to “kill” the OnEachFrame thread. The UI execution exploit was a simple mistake made by the SQF developers for Arma 3.
Let’s first start with a quick refresher on the UI execution exploit. This worked by exploiting how the game saved custom colors for the UI frames. When you inserted code into the color section within your profile namespace, it would execute the code when you opened the game options menu. There are still a few more exploits like this, and I may cover them in the future, but they all have one thing in common: The end-user has to click on a few buttons or take a few actions to trigger code execution. This means, anyone with knowledge of these exploits, can prevent the user from taking those actions or clicking those buttons. As well, anticheat developers can block & ban for some of these exploits. These style of exploits are very powerful, it lets cheaters cheat without being BattlEye banned, but they are not “unblockable”, there is always a way to disable a user from executing their code.
But, why do the UI style exploits require a user-action? When a user loads into a server, the game terminates all SQF threads & removes all event handlers. This prevents any code from the main menu or a previous multiplayer session from interacting with the new session. I call this the SQF Firewall. Any code you have running will always stop at this intersection.
If we can break through the SQF Firewall, there is a lot of unique advantages we would have. We could effectively build a way of execution SQF cheats without BattlEye or the server-based anticheat from ever knowing.
Breaking the firewall requires an in-depth knowledge of how the engine kills Threads & Events. As well, a little history lesson in the field of firewall busting.
So, we know that when connecting to an MP session, any sqf thread & event handler gets removed. You can test this with a simple OnEachFrame check. Run this code at the main menu & connect to an MP session.
You’ll notice when you enter the lobby that the text “running” stops being printed into chat. This gives us an idea of when the event handler is terminated. It is some time between clicking “join” and entering the lobby. So, if we want to get any SQF code to run from this point forward, we need to come up with a way of tricking the engine into not removing an event that we set.
As with most of my posts, I will show you the completed code & then explain how it works. Also note, by clicking on the image, it will bring up the complete script in a new tab.
There is a lot here we’ve never discussed. When I was looking into breaking through the firewall, my early attempts were based on trying to abuse a pre-existing exploit that was leaked on unknowncheats. This exploit allowed scripters to abuse the “Draw” event handler on the map to execute code in an MP session. With that in mind, I wondered “If I create a map control on every display, could I use the draw event handler to carry code through the loading process?”. Essentially, if for every display created, I create a map control with a Draw event handler, would that be enough to break the firewall?
In the code, you can see I make extensive use of this idea. On line 36, you can see the idea in action. In all displays, I store a variable pointing to the map control I generate. If a display is found that does not contain this variable (it is nil) then I generate a new map control. Look at like 59, here you can see what makes my code “tick”. Every frame, we reset the onEachFrame event & run GUI_TickThread. As well, onEachFrame fires GUI_TickThread a second time. This fires our core code twice every frame.
Now let’s look at the core exploit on lines 2 to 34, GUI_TickThread.
In the first section, lines 5 to 12, we are checking if the client state is within the “connected, ready to play” phase. This allows us to detect when the game is ready to handle code that would otherwise be run from the mission’s init.sqf file. Basically, we check if we have finished loading into the game. After that, we clear out GUI_TickThread to prevent it from continuing to run & fire “GUI_Run” which is a container for any code we want to run once we’ve bypassed the firewall.
Next, on line 15, we have the secret sauce. This is what we are really using to bypass the firewall. This is where our knowledge of past exploits comes into play. Back August 2nd, 2015, there was an exploit released on UnknownCheats. The original author of this exploit JME, published the exploit on MPGH that same day. Here is his post. When the command inGameUISetEventHandler was implemented, BI forgot to clear it’s value when connecting to an MP session. This allowed cheaters to set the value in the editor & connect to a server with their code, bypassing the firewall that should have cleared the event handler. This was quickly patched in a hotfix a few weeks later. The question is Why has this command come up again, and how does it help make my exploit work? The key is timing. When connecting to an MP session, BI removes events and kills sqf threads. However, what if we set an event after BI has cleared it, and before BI stops my sqf threads from running? This is how IGUISEH makes our code work. By setting this event handler within the setup we have surrounding it, we are able to set the value of “NextAction” before our Draw event handler & OnEachFrame are stopped, as well as after BI clears the original value of the event handler. This way, when we hit the firewall, our event handler’s value is allowed through the wall & into the server. As we load into the server, NextAction automatically fires when we are assigned a playable unit. This restarts our GUI_TickThread and we’ve successfully bypassed the firewall.
That’s a bit complex, here is a diagram that helps explain how the Firewall & SQF Engine interact during this process.
Lastly, on lines 18 to 32, we are simply checking all active displays & creating Map controls with Draw events on all of them. This keeps our code running as we navigate the main menu displays.
So, we’ve bypassed the SQF firewall. Not only that, but our code is automatically triggered the second we start loading into the server.
Obviously, this is one of the most complex exploits I’ve built for Arma. It takes multiple history lessons in SQF exploiting & a long-standing hidden mistake within the engine to create. I haven’t pushed the limits of this exploit yet. To be honest, it’s quite overwhelming how intricate it is. From exploiting Draw event handlers to abusing the order in which the firewall kills events and threads, small changes to the code could completely alter how it operates (or if it works!). I can imagine ways of making the code harder to detect, randomizing the uiNamespace variables, making some of the variables hidden as I’ve shown in past posts.
This exploit I would argue is much worse than others that I have posted about in the past. The SQF code that is snuck through the firewall can be executed before any SQF anticheat (like InfiStar) has a chance to start on the client. This provides ample ways to cripple server-side SQF based anticheats. As well, firewall bypassing is not something a server can “block”. They have to detect it. I would recommend using BattlEye filters to catch this style of bypassing. By including inGameUISetEventHandler into the scripts.txt, I think, and I emphasize think, this method could be detected by a server. However, by proving that the firewall can be bypassed this way, I am confident that there are other means of bypassing it.
Bohemia needs to take a very good look at the order in which they kill events and sqf threads when connecting to an MP session.
My next post will cover another method of bypassing the firewall that is a little easier to detect but has a much larger impact on the security for servers.