uberdaemon - festivaldev/Paradise GitHub Wiki

uberdaemon

uberdaemon.exe (or just uberdaemon on macOS), found in the game's root directory, is responsible for verifying the game's integrity when connecting to any Comm or Game server.
Upon connection, the game sends the auth token and a "Magic Hash" to the server. The server then compares the hash to a hash calculated from the auth token and a Composite hash in Paradise.Realtime\bin\CompositeHashes.txt. If there is a match, the client authentication continues, otherwise the connection is terminated.

This page aims to explain how uberdaemon works, thanks to the hard work of daniel_crime on the Paradise Discord Server, who singlehandedly reverse-engineered uberdaemon.exe and "translated" the code from Go to Python.

For the full Python implementation, as well as the customized Go re-implementation for Paradise, see https://github.com/festivaldev/Paradise/tree/new-paradise/uberdaemon.

Determining the executable path

uberdaemon starts by getting the executable's working directory, which should be the game's root directory (aka. the parent directory above UberStrike_Data), from the launch arguments. The full path to the executable is usually located at index 0 inside the list of launch arguments.

args = sys.argv
executable = args[0]

working_path = ''
tmp = executable
while tmp[0].startswith('/') or tmp[0].startswith('.'):
    working_path += tmp[0]
    tmp = tmp[1:]

if len(working_path) < 2:
    working_path = '.'
else:
    working_path = working_path[:-1]

if executable.startswith(working_path):
    executable = executable[len(working_path)+1:]

Getting platform-specific paths

It then determines the OS platform by checking the executable name: If it's uberdaemon.exe, the platform is is set to 1 (Windows). If it's just uberdaemon, the platform is set to 2 (macOS).
If there is no match, depending on the executable's suffix, a SHA256 hash of either wrongexewin or wrongexeosx is returned. Same for when the executable cannot be found within the working directory.
After that, managed_path (path for assembly dependencies) and mono_path (path to the Mono runtime) are set to their platform-specific values.

os = (executable == 'uberdaemon.exe' and 1) or (
    executable == 'uberdaemon' and 2) or 0
if os == 1:
    if not path.exists(f'{working_path}/uberdaemon.exe'):
        print(to_sha('wrongexewin'))
        exit()

    managed_path = f'{working_path}/UberStrike_Data/Managed/'
    mono_path = f'{working_path}/UberStrike_Data/Mono/'
elif os == 2:
    if not path.exists(f'{working_path}/uberdaemon'):
        print(to_sha('wrongexeosx'))
        exit()

    managed_path = f'{working_path}/UberStrike.app/Contents/Data/Managed/'
    mono_path = f'{working_path}/UberStrike.app/Contents/Frameworks/MonoEmbedRuntime/osx/'
else:
    if executable.endswith('exe'):
        print(to_sha('wrongexewin'))
    else:
        print(to_sha('wrongexeosx'))
    exit()

List the files to verify

uberdaemon has two lists of files to verify. The first is the list of files inside the Managed directory. Note that Assembly-*.dll files are present twice.

# Individual hashes
managed = hash_files([
    "Assembly-CSharp-firstpass.dll",
    "Assembly-CSharp.dll",
    "Assembly-UnityScript-firstpass.dll",
    "Assembly-CSharp-firstpass.dll",
    "Assembly-CSharp.dll",
    "Assembly-UnityScript-firstpass.dll",
    "Boo.Lang.dll",
    "GraphLogger.dll",
    "Mono.Posix.dll",
    "Mono.Security.dll",
    "mscorlib.dll",
    "Photon3Unity3D.dll",
    "System.Configuration.dll",
    "System.Core.dll",
    "System.dll",
    "System.Security.dll",
    "System.Xml.dll",
    "UnityEngine.dll",
    "UnityEngine.UI.dll",
    "UnityScript.Lang.dll"
], managed_path)

The second list contains files for the Mono runtime. Because these files are platform-specific, uberdaemon checks different files on Windows and macOS:

if os == 1:
    mono = hash_files([
        "mono.dll"
    ], mono_path)
else:
    mono = hash_files([
        "libMonoPosixHelper.dylib",
        "libmono.0.dylib"
    ], mono_path)

Hashing

uberdaemon has four types of hashes: Common, Salt, Composite and Final.

The Common hash is calculated by combining all file names to a single string and hashing that with SHA256:

# Common hash
full = ''.join([x[1] for x in managed + mono])
common = to_sha(full)

The Salt hash is a SHA256 hash of what appears to be 8 individual strings joined to a single string. This might be caused due to disassembly or previous obfuscation and could have been a single string in the first place.

# Salt hash
salt = ''.join([
    "b8700db7",
    "3e6b30af",
    "4f180e0e",
    "3c5afb94",
    "e52de85f8",
    "9297cee7",
    "1067ee13",
    "25bc2fa"
])

salt = to_sha(salt)

The Composite hash is yet another SHA256 hash, this time of the Common and Salt hashes combined:

# Composite hash
composite = to_sha(common + salt)

This Composite hash is also the one stored server-side, which will then be used to compare hashes sent by the client.
The Final hash uses the Composite Hash and the auth token that is specified as a launch parameter. If no auth token was specified, the SHA256 hash of nousertoken is returned instead.

# Final hash
if len(sys.argv) > 1:
    final = to_sha(composite + sys.argv[1])
    debug('Magic Hash:', final)
else:
    final = to_sha('nousertoken')
    debug('No user token - Magic Hash:', final)


print(final)

Paradise Custom Implementation

Paradise uses a custom implementation of uberdaemon that includes additional files to be verified. It also allows users to see each individual hash by specifying the debug parameter (-d).

The following files in the Managed directory are verified when running the Paradise implementation:

0Harmony.dll
Assembly-CSharp-firstpass.dll
Assembly-CSharp.dll
Assembly-UnityScript-firstpass.dll
Boo.Lang.dll
GraphLogger.dll
Mono.Posix.dll
Mono.Security.dll
mscorlib.dll
Newtonsoft.Json.dll
Paradise.Client.Bootstrap.dll
Paradise.Client.dll
Photon3Unity3D.dll
System.Configuration.dll
System.Core.dll
System.dll
System.Security.dll
System.Xml.dll
UnityEngine.dll
UnityEngine.UI.dll
UnityScript.Lang.dll
YamlDotNet.dll

For improved compatibility, Paradise stores Composite hashes for both the Cmune build of uberdaemon and the build included in Paradise on the server.

Building uberdaemon

To build the custom implementation of uberdaemon for Paradise, you'll need a version of Go that still includes support for 32-bit macOS binaries (<= 1.14.15). Just run the build script for your desired platform (win/mac) and architecture (x86/x64). The compiled executables will be located in build.