Recently I have been interacting directly with some guys at Bohemia Interactive. With the latest update, BIS_fnc_parseNumberSafe hasn't been as safe as it should have been. A few great discussions later and I am hopeful that the guys at BI have it right this time, finally patching that multi-year vulnerability.
During our back and forth, someone brought a new way to execute code to my attention. It's been in Arma for a very long time and most SQF devs have used it, but, I'll be honest, even I never imagined this as a possible vulnerability until recently. That method was Lazy Evaluation.
Lazy eval, for those who are unfamiliar with the term, is a slightly complex topic, but you'll recognize it as very useful functionality. It allows if statements to check if a variable is nil and compare that variable in a single line without error.
This is an example of lazy evaluation. The isNil boolean statement is checked first before the second statement is run. If the first statement returns false, the second is never run. This can save tons of performance if used properly. This is powered by both && and || operators. If the second statement is CODE, it will be executed if the first statement is true (or false in the latter case).
So there we have it. Lazy eval is useful, but it also executes code. Here is a great example clearly demonstrating how lazy eval executes code.
This function does exactly as it implies. If the first boolean value (alive _target) is true, then the second operation is executed. _result will contain a true or false value, letting the function caller know if it executed successfully.
So if we can find a place that code can be inserted into the second half of an AND or OR statement, then we could execute that code. A bit complex, but let's take a look at a location in Arma that has this flaw.
Alright, so this example snippet example comes from RscDisplayMPInterrupt. At the very end, we see this onUnload case. In here, we can see that, if stream friendly UI is disabled, and BIS_shownChat is true, then the game will show the chat once we close the escape menu. A few issues here.
- The first statement will almost always be true unless the user specifically changes a setting
- The second statement is not already being lazy evaluated
- The second statement is just a variable that could theoretically be any datatype
- The second statement is a variable from uiNamespace, which will allow us to set it's value outside of the MP session
So what happens if we do change BIS_shownChat to code? Well, the AND operation treats it as a lazy evaluation. If the first statement is true, which it will be in most cases, then the code set in BIS_shownChat will execute. Boom. Exploited.
One issue we could run into is at the top of this script. BIS_shownChat has it's value set whenever we open the escape menu, so game over? No, if we use compileFinal, that write will simply fail. So in order to exploit this, we need to build a compileFinaled payload that we can permanently set BIS_shownChat to. This payload must return a value, either true of false, otherwise this AND operation would error out. Secondly, we will need a variable that can contain our modifiable payload, something to actually run. This way we can change it without restarting our game. Let's look!
So this exploit does everything what we want. If BIS_shownChat needs to be set to code, we do so with compileFinal. We have a variable, Main_Payload, hidden away that contains our executable payload. We execute our payload from the missionNamespace, which is where most developers assume their code is executed in. Lastly, we do return true to prevent our AND statement in the exploited code from erroring out. This can be run in 3DEN and will be executable in MP simply by opening and closing the escape menu!
I am writing this before I've seen how BI plans to patch it, but a simple fix is to simply wrap our variable in RscDisplayMPInterrupt in code brackets. Such as this: {(uiNamespace getVariable ["BIS_shownChat", true])}. This way it always does lazy evaluation but the variable will not execute if it is code, but rather will error out.
Well, that's it. This exploit has existed for a very long time. I suppose it's been in Arma since alpha. I never had a reason to dive into lazy eval so I've never tried using it to execute a payload before. It's really cool to see how it works in such an obvious and elegant solution!
You can view the complete Source Code for this exploit on my hastebin!