The (8 year) journey of finding the solution to Hack 'n' Slash's encrypted SecretRoom.lua
Goal: Decrypt the file named SecretRoom.lua
that was encrypted using AES-256-CBC with an unknown key, found in the game files (relative path of ...\Data\Content\Secret\Rooms\SecretRoom.lua
).
After playing through Hack 'n' Slash when it was released over 8 years ago and exploring all that there was explore in the game, there was just one room that remained out of reach: SecretRoom.lua, which was obviously encrypted due to the seemingly random nature of the file's contents. Since the game itself had the mechanic of encrypting and decrypting files (represented in-game as "books"), it was a safe assumption that SecretRoom.lua was intended to be decrypted in-game as well. The solution would then be to "simply" guess the correct passphrase such that the file is successfully decrypted. After trying various notable phrases and sentences from the game without success, I searched online to see if anyone had figured it out.
There was a thread about SecretRoom.lua on the Double Fine Forums that I found, where several members were working together to try to figure out the solution. As a programmer, my first major attempt was to brute-force the solution, in case that was either the intended way to find the solution or the passphrase happened to be short enough for it to be feasible. Initially, I used the in-game mod system to try to decrypt SecretRoom.lua through brute-force, since it provided direct access to the decryption functionality. However, that was unsurprisingly slow, as the decryption attempts were all running on a single thread. At a certain point, a user named "tjablin" posted an implementation in C that replicated the encryption and decryption process that the game uses, which is AES-256-CBC with an initialization vector of 0, and the SHA-256 hash of a chosen string as the key. The fidelity of this implementation was proven by its ability to decrypt the PrincessChambers.lua
file that was encrypted using the in-game encryption mechanism when provided with the appropriate passphrase. Having the ability to perform the decryption outside of the game was a huge benefit, as it not only made it very quick to try various passphrases, but it also made it easy to run on multiple threads and greatly increase the speed of brute-forcing attempts.
I spent a while trying my luck at brute-forcing the passphrase, but eventually it seemed unlikely that the solution would be discovered that way. I left it alone for quite some time, and a few years had passed before I tried something else. My next approach was to extract all of the strings from the game's files and use those as passphrases. It was easy to extract the strings from many of the game's Lua files, but there are some that have been compiled into bytecode. I used LuaDec to decompile the compiled Lua files so that I could then extract the strings from the decompiled files. I put each extracted string on a separate line in a "password list" file, then had the decryption program load that list and attempt to decrypt SecretRoom.lua using each string in the list. Also, in case the passphrase was a single word, I split the strings into individual words and tried those as well. After a while of not making any progress with this method, and some more back-and-forth with the contributors of the SecretRoom.lua thread on the DF Forums, again multiple years passed by before the last and successful attempt was made.
While reorganizing my backed up files, I came across the folders for the decryption and string-gathering programs, and since it had been several years since I last looked into SecretRoom.lua, I decided to check if someone had found its solution. I found a tweet from Hack 'n' Slash's project lead Brandon Dillon where he responded to a question that NeoCortex asked him, saying that to his knowledge, "no one has solved it yet." I took the opportunity to ask Brandon directly if it would be practical to brute-force the solution to SecretRoom.lua, and he responded that he didn't think it was and that the passphrase shows up in the game. The confirmation that the passphrase shows up in game was extremely helpful, as it made me confident that using the extracted strings from the game files was the right way to find the solution.
I took a closer look at the specific strings that were extracted. Since none of the individual words were the solution, I now assumed that the passphrase was a sentence. There were a few thousand strings that had been extracted, but I was able to drastically narrow down the possibilities for the solution by removing strings that just contained single words as well as ones that were obviously not going to be be the passphrase, such as paths like Data/Content/Game
. In addition, I was able to eliminate strings that were just regular sentences such as I rule this kingdom!
, since none of them worked as the solution. That left mostly strings that contained irregular sentences, such as ones that contained formatting tags, as well as strings that were comprised of more than one sentence (of which there were a good amount). I figured that it was less likely that the passphrase would be in string that contained formatting tags, and it was faster to split the multiple strings at the punctuation marks, so I focused on those. After splitting a lot of those kinds of strings and trying again, the solution was still not found.
As I was reducing the list of possible passphrases further and further, I started wondering if there was a possibility that the decryption process was somehow faulty and the correct passphrase was being passed in without it being detected as successful. That didn't seem likely, since it had been proven to properly decrypt the encrypted PrincessChambers.lua, as previously mentioned. However, I had not looked at the code in several years, so I checked it and noticed that there was validation that was still enabled that had been added many years ago to filter out false positives while trying to brute-force the solution. This validation checks if the decrypted result starts with the Lua magic string \x1BLUA
, which every compiled Lua file is guaranteed to begin with, and if that magic string isn't present at the beginning of a decrypted file, it treats as a failed decryption. Since PrincessChambers.lua was a compiled Lua file, there was basically an implicit assumption that SecretRoom.lua would also be a compiled Lua file. Now that I was no longer brute-forcing the passphrase, this additional validation was no longer necessary anyway, but as it was put in place so long ago, it never came to mind to remove it. I then disabled the magic string validation, ran the narrowed-down password list through the decryption program again, and it was a success...
The passphrase that successfully decrypted SecretRoom.lua was "Be brave.", which exists in a single location in all of Hack 'n' Slash: the game's main loop, in main.lua
. It's a part of the text that is shown when every time when loading into the game. After selecting a "universe state" to load into, there is a loading screen that is shown that displays "It's dangerous to hack 'n' slash. Be brave." This repository contains the decryption program that was used to discover the solution. It can be used to decrypt your own copy of SecretRoom.lua that is found in Hack 'n' Slash's game files. Information about its dependencies and usage is provided below.
Luckily, since the correct passphrase was part of a multiple sentence string, and I didn't attempt to use the split strings as passphrases (and thus the correct passphrase) until this last attempt, the fact that the Lua magic string validation would have prevented the correct passphrase from counting as a success for all these years didn't actually end up causing the solution to be eluded before now. Still, there seems to be a lesson here somewhere about challenging assumptions, or something like that.
Special thanks to SmashManiac, NeoCortex, tjablin, and the rest of the participants of the "Secret Room" thread on Double Fine's Forums. I'm certain that I would not have found the solution without their important contributions to this effort. Also, many thanks to Brandon Dillon and the rest of the amazing team at Double Fine Productions that created the incredible game "Hack 'n' Slash", without which this journey would never have transpired in the first place.
- libtomcrypt
- gcc (Linux)
- Visual Studio (Windows)
(Note: SecretRoom.lua
is not provided. You will need to retrieve it from the Hack 'n' Slash game files. It is located at ...\Data\Content\Secret\Rooms\SecretRoom.lua
.)
Linux:
./dfcipher -d "Be brave." SecretRoom.lua SecretRoom_decrypted.lua
Windows:
DFCipher.exe -d "Be brave." SecretRoom.lua SecretRoom_decrypted.lua