Enabling File Write to DayZ Root Directory
So my recent work on DayZ BR has lead to me creating my own ban management system. The idea is all server bans will be aggregated into a database and synced between all of my official servers. In order to sync bans, I opted to follow the same method infiSTAR and Fini use for their Arma 3 anticheat solutions by writing directly to the ban.txt file on the server.
Unlike in Arma 3, Extensions are not supported. So the solution infiSTAR and Fini came up with, using an extension to write to this file, won’t work for us without creating an extension system ourselves. While this would be cool, it’d be impractical to maintain. Another solution could be using functionality similar to Arma 3’s serverCommand. Sadly, the technology just isn’t there yet for DayZ. So we can’t directly interact with server commands. DayZ does have two systems that Arma does not. We can read and write to disk, and we can easily send and get results from Web Requests.
I imagined this would be the perfect solution. I would send a web request to my API, retrieve my ban list, and write that to the ban.txt file in the server root. Let me show some of the code I came up with to do that:
So here, we pull my bans from my API, open a handle to our ban.txt file in the root directory, and write our bans to disk. Pretty simple. But the end result is that, in typical Bohemia fashion, OpenFile is returning NULL. We can’t open our ban.txt file! After a bit of debugging, I learned that we can open the ban.txt file, but only with FileMode.READ. It seems that we can only use FileMode.WRITE (and FileMode.APPEND) in the $saves: and $profile: directories. We can’t open files in the root directory with WRITE.
I would imagine Bohemia’s goal here was to increase the security of their game. Imagine if someone managed to write binary data! In reality, I think they handicapped their own system. Instead of checking what path the server is writing to, I would recommend they limit writing by file extension. Just don’t let the user write to executable files.
Anyway, we need to find a way around this. I am not going to write a ton more code just to bypass this, and I am definitely not going to use a Symlink between the $profile: and $CurrentDir: directories. To do so, I popped DayZServer_x64 open in Binary Ninja and looked for the string “OpenFile”. Once found, I checked the x-refs for this value to find the Register Function table. The third parameter of the Register function is the native functionality for our enforce method. You can see I renamed it to “s_OpenFile”.
Here is s_OpenFile, I know it’s a lot you may be unfamiliar with, but I’ll explain the important parts.
In s_OpenFile, there is a method that is run right away, I renamed this to “valid_path”. This method can be found in pretty much every File accessor in Enforce. Another example you could look up is “FileExist”, which checks if the path exists, agnostic of any READ/WRITE mode. The result of this valid_path method determines which branch we go down, true and we open a handle to the file, false and we return NULL. Anyway, the “valid_path” method is quite long, so here is the pseudocode generated by Binary Ninja:
This method is checking that the string passed is valid for windows and Linux, it takes into account the $Prefix: possibility, and if the MODE parameter is not 1, it only returns true if the prefix is either $saves or $profile. Take a look at line #47, this is the check that is tripping us up. Our mode is WRITE, which I assume is like 2, so we continue forward and pass this if statement and check our prefixes.
In order to enable writing to $CurrentDir:, we need to force this verification method to return true. The easiest way to do this is to force this if statement to always run. This can be done in a few ways. We could NOP the if instruction and convert our JE instruction into a JMP. The approach I decided to go with is to change the value from 0x1 to something completely incorrect, like 0x78, and change the JE instruction into a JNE instruction. For those not accustomed to assembly, here is the if statement before and after my patch:
With Binary Ninja, we can save the file contents as a new DayZServer_x64_patched.exe. Using this new executable, we can test writing to ban.txt
And there we have it! In order to speed up our patching for future versions of DayZ, and even automate patching these two bytes, signatures should be made for these.
Here are two signatures I generated with Binary Ninja’s SigMaker plugin:
CMP edi, 0x1: 83 FF 01 0F 84 95 00 00 00 (patch is applied to the 3rd byte (0x01 to 0x78))
je 0x14022ef8a: 0F 84 95 00 00 00 48 8D 0D ? ? ? ? (patch is applied to the 2nd byte (0x84 to 0x85))
You’ll likely need to create new signatures eventually, but hopefully, you’ll have learned to find the bytes yourself and patch them!
Update: I built a .NET Core application that can handle finding these style patterns and applying the patch to DayZ. The source code is available from the DayZBR Gitlab Here!