Everyone who has played Arma for an extended period has come across TFAR, Task Force Arrowhead Radio. I am not going to go into how the mod works, you can check it out here.
I am going to dive directly into the code that drives TFAR, and do my best at explaining a type of lazy eval code execution that is much more complex than the previous exploit I covered. If you do not know what Lazy Evaluation is, I recommend reading that previous post to get a grasp on this style of exploit. To best explain my logic, I am going to take you down the path of how I figured out this exploit. It's going to be different than how I normally explain exploits. Hopefully, at the end of this, you'll understand how the TFAR exploit works, and how I created it from my initial thought.
The way I find exploits is by opening up a mod and looking through all of its scripts for a few identifiers that could point to a vulnerability. This is what I did for TFAR. Opening up the script files, I was looking for any lazy eval exploits. One of the identifiers I use for lazy eval exploits is A and B, where B is a variable. If B is a variable, there is a possibility that I can change the value of it, thus allowing a lazy eval code execution. Very often this identifier leads to a dead end. Either the variable is hard-coded or I cannot manipulate it. While looking at TFAR's files, I came across fn_onLRTangentReleasedHack.sqf which contains 3 instances of this identifier. Let me show you the code.
Here we can see the following identifiers:
- (_scancode == SHIFTL) and (_mods select 0)
- (_scancode == CTRLL) and (_mods select 1)
- (_scancode == ALTL) and (_mods select 2)
What this is telling me is that, if I can change the value of _mods, I can execute code through any of these fields. My next step is to figure out what SQF code determines the value of these fields.
Looking again at the code, _mods comes from _keybind, and _keybind comes from CBA_fnc_getKeybind. So we have a clue. If we can control the output of CBA's CBA_fnc_getKeybind function, we can exploit this function! Let's take a look at CBA_fnc_getKeybind. Quick warning, you will see comments in the code. These comments describe how the exploit works, and I'll get into them towards the end.
Looking back at TFAR's code, we can see that the item in index 5 is where _mods is derrived from. So the first thing I look at in CBA_fnc_getKeybind is the return value. Index 5 of this return value is _oldKeybind. Working backwards, _oldKeybind is index 0 of _keybinds. Quickly, checking out that line, we can see that param does not have a default type or any checks on the internal data types. Great, so now we just need to modify _keybinds. Looking at line #16, we can see _keybinds is at the 2nd index of _actionInfo.
Finally, we come to the point at which the complexity of this exploit increases. _actionInfo is set by GVAR(actions) getVariable _action. _action is a string formatted by our input parameters. So we can conclude, _action = "tfar$lrtransmit" based on the input parameters of CBA_fnc_getKeybind (this is important and will be used later). But what is GVAR(actions)?
If we can modify GVAR(actions), then we can change the values of our specific actions keybind. To do so, we must take a look at CBA's CBA_fnc_addKeybind function. This is quite a large function, so I have provided a link to it on my hastebin. I will be using screenshots of snippets from CBA_fnc_addKeybind but will refer to line numbers. I recommend opening the hastebin to follow along. First off, let's take a look at line # 80, where GVAR(actions) is given a value.
From this, we can see that it is a common namespace that CBA likes to make. CBA does this thing where they use virtual location objects to store variables like another namespace. I don't understand why, but it does the job they need it to.
So 'actions' is a location object. From here we can take a few approaches, we could try to trick the game into pre-defining actions as another namespace (such as uiNamespace). This is difficult because locations and namespaces do not line up 1 to 1. We'll likely run into an issue. The approach I decided to take from here was to figure out what sets the value of keybinds in the 'actions' namespace.
Take a look at line # 93 in CBA_fnc_addKeybind.
Here we set the action, and give it some keybinds in index #2. So if we can somehow modify the incoming _keybinds variable, we can still exploit this. Looking farther up, we can see it being used. Note that this image contains a lot of the content discussed below.
_keybinds comes from CBA's CBA_fnc_hashGet method. It is then checked for default (or overwrite) and possibly overwritten. If the TFAR keybind is overwritten, this is game over, we can't exploit it. So before moving on, let's just make sure we're not overwriting the preset value from CBA. In TFAR, we can find the CBA_fnc_addKeybind call for LRTransmit in fn_ClientInit.sqf.
Looking here, it seems like only a few parameters are passed. Taking a look back at CBA_fnc_addKeybind, we can see _overwrite is defaulted to FALSE.
Okay, so as long as the CBA_fnc_hashGet returns a valid keybind, the data will be inserted & we can exploit the lazy eval. Take a quick look at line # 69 in CBA_fnc_addKeybind, where CBA_fnc_hashGet is called. We can see two parameters passed in, _registry and _action. We already know _action is our action string, we can see so on line # 23, so lets look above where _registry is assigned a value.
Line # 62 shows us that _registry comes directly from the profilenamespace! This is terrific. Any exploit that runs back into profilenamespace or uinamespace is one that we can really abuse! We can also see that if a value is not set, then a default is given (line # 64-67). Okay last thing to do is know how CBA_fnc_hashGet pulls our keybinds from the registry.
In this, _hash refers to our registry (profilenamespace), and _key refers to our action name. So we start by finding our action within the registry. The list of action names is stored at index 1 (HASH_KEYS). If we sucessfully find the action in that list, then we go pick out our Keybinds from the list stored at index 2 (HASH_VALUES). This is all we care about. Take note that we return directly from the "select" command, which keeps a reference to the list. This will be important later on.
Okay, the last bit was quite heavy, but now we know everything we need to successfully write exploitable code to the keybind data. Let me line out what we know in a more effective way:
- The data's source lies in the profilenamespace within the variable "cba_keybinding_registry_v3"
- The action name is ""tfar$lrtransmit"
- By finding the index at (_registry select 1) of our action, we can find the keybind data
- The keybind data is stored in (_regsitry select 2)
- This keybind data is then moved into GVAR(actions) getVariable (action_name) under index #2
- The keybind data in CBA_fnc_getKeybind is 1 layer deep (select 0)
- This keybind data is returned in CBA_fnc_getKeybind under index #5
- The keybind data is structured as [keycode,keymods] where keymods is an array of boolean values
- If a value of keymods is code, TFAR should lazy evaluate it as long as either SHIFT, CTRL, or ALT, is pressed.
So now that we know all of this, we can build an exploit.
Our first step is to get the registry, or create a new one, for cba_keybinding_registry_v3. Next, we must find our action in the registry. If our item does exist, we must set the value in the registry for that index to an exploitable payload. Remember, the payload must be an array within an array as CBA_fnc_getKeybind accesses index 0 before returning. If our item does not exist, we just have to push it back into the registry.
And that is it. Now if we load up a game with TFAR and press, in my example, SHIFT, it'll execute the payload.
As always, the source for my exploit can be found on my hastebin here.
So one last thing I wanted to cover was a unique feature of this exploit. My comments throughout the screenshots have been alluding to this. The way that getKeybind and addKeybind interact with the keybind data array, the values in it are actually directly linked to the profilenamespace. I covered this a while ago with my advanced variable hiding post. What it allows you to do is create a single line of code for this entire exploit.
This was shown to me by a friend, and I never actually wrote it, but here is a rough take that may or may not work.
In theory, setting the value retrieved here, should link directly back to the profile, and update the profile with the new exploit. Like I said, I didn't test this, I just know that somehow it is possible.