SqexArg
This is the “encrypted argument” format used by a lot of FFXIV programs and is used in place of regular plaintext command line arguments. However, this is barely a security measure and just prevents easily snooping stuff like your login token. Despite this, the SqexArg format is well known, reversible and easily breakable.
Format #
When viewing the command line arguments for, say ffxiv.exe you will see it’s only something like this:
//**sqex0003S2_Utl8qdamv3_82SH7Lhtk=S**//
(Yes, I did garble some of the text, so it’s not actually decodable :-))
There are three distinct parts of this string:
//**sqex0003S2_Utl8qdamv3_82SH7Lhtk=S**//
^^ ^
version|| |
| base64 string |
| checksum
Let’s break them down:
- version
- From what I’ve seen, this is always
3
. I’m not sure if there any more meaning behind this, apart from they revised this 3 times.
- From what I’ve seen, this is always
- base64 string
- This is your usual base64-encoded string. However, there is a couple of important things to note:
- Use the URL-safe version of Base64.
- You may omit the trailing equal.
- The result is unreadable garbage, but how to encode/decode this will be revealed below.
- This is your usual base64-encoded string. However, there is a couple of important things to note:
- checksum
- This is also covered in a later section, but this is always one character long and located at the end of the string.
Encryption Algorithm #
The resulting bytes when you decode the base64 string is going to Blowfish ECB encrypted.
- However, please note that Square Enix does some weird bitflip endian-encoding nonsense which means your out-of-box Blowfish library might not work. I would highly recommend reading up on some existing implementations to get an idea of what to do:
- XIVQuickLauncher (C#)
- Astra (C++)
- XIV-on-Mac (Swift)
- Before encrypting or decrypting, ensure the buffer is padded.
Note: In the new Steam launcher update, Square Enix has actually switched to a more sane version of Blowfish ECB without their weird changes. Please look at XIVQuickLauncher for the changes required, as I have not tested this yet.
Key #
The key used for encrypting/decrypting the encrypted arguments is just your system’s uptime clock. All FFXIV executables just call GetTickCount()
, and theres about ~50 or so ticks of freedom before the game or launcher considers it invalid. There is a specific transformation you need to do in order to fetch a valid key though:
unsigned int rawTicks = TickCount();
unsigned int ticks = rawTicks & 0xFFFFFFFFu;
unsigned int key = ticks & 0xFFFF0000u;
char buffer[9] = {};
sprintf(buffer, "%08x", key);
To make this easier, here is a couple of platform-specific implementations of TickCount()
. Thank you Wine for being easily searchable, as this is what Wine is actually doing under the hood to emulate GetTickCount()
, so these are exact and have been tested on Astra for all platforms.
Windows #
uint32_t TickCount() {
return GetTickCount();
}
macOS #
uint32_t TickCount() {
struct mach_timebase_info timebase;
mach_timebase_info(&timebase);
auto machtime = mach_continuous_time();
auto numer = uint64_t (timebase.numer);
auto denom = uint64_t(timebase.denom);
auto monotonic_time = machtime * numer / denom / 100;
return monotonic_time / 10000;
}
Linux #
uint32_t TickCount() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}
Checksum #
If you’re just interested in decrypting game arguments, this is not essential. This is presumably used as a checksum when the game checks your encrypted string.
static char ChecksumTable[] = {
'f', 'X', '1', 'p', 'G', 't', 'd', 'S',
'5', 'C', 'A', 'P', '4', '_', 'V', 'L'
};
static char GetChecksum(unsigned int key) {
auto value = key & 0x000F0000;
return ChecksumTable[value >> 16];
}
Decrypting #
You can try the dedicated argcracker in Novus for this purpose. It allows you to easily crack any SqexArg enabled program assuming you have access to the string.
Notes #
Every instance where SqexArg is used, the first argument is always T
, where T
is set to the value of ticks
(as shown above). I’m not sure what the purpose of this really is, maybe for verifying the checksum?
The arguments (before encoding of course) must be formatted as " /%1 =%2"
. The extra space is required, even at the beginning of the arguments. Make sure that any spaces in your string is double padded as well.