custom-protocols.md (6745B)
1 --- 2 title: "Custom UDP RPC Enumeration & File-Transfer Abuse" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/custom-protocols.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/custom-protocols.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Custom UDP RPC Enumeration & File-Transfer Abuse 14 15 ## Mapping proprietary RPC objects with Frida 16 17 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:<sup>[[1]](#references)</sup> 18 19 | Field | Purpose | 20 | --- | --- | 21 | `ID` | RPC verb (16-bit) | 22 | `Flags` | Transport modifiers (reliability, ordering) | 23 | `Source` | Object ID of the caller | 24 | `TargetObject` | Remote object instance | 25 | `Method` | Method index inside the target class | 26 27 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.<sup>[[1]](#references)</sup> 28 29 <details> 30 <summary>Frida surface enumerator (trimmed)</summary> 31 32 ```javascript 33 'use strict'; 34 35 const classToMethod = Module.getExportByName('NetComEngine3.dll', 'ClassToMethodName'); 36 const targetName = Module.getExportByName('NetComEngine3.dll', 'TargetName'); 37 38 function tryID(objID, methodID) { 39 const method = new NativeFunction(classToMethod, 'pointer', ['pointer', 'uint']); 40 const target = new NativeFunction(targetName, 'pointer', ['pointer']); 41 const buf = Memory.alloc(Process.pointerSize); 42 buf.writeU32(objID); 43 const m = method(buf, methodID); 44 if (!m.isNull()) { 45 const t = target(buf); 46 console.log(objID.toString(16), '=', t.readUtf16String()); 47 console.log(' -', methodID, '=', m.readUtf16String()); 48 } 49 } 50 51 for (let obj = 0; obj < 0x9000000; obj += 0x400000) { 52 for (let meth = 0; meth < 0x40; meth++) { 53 tryID(obj, meth); 54 } 55 } 56 ``` 57 58 </details> 59 60 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`.<sup>[[1]](#references)</sup> 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. 61 62 ### Tips 63 64 - Use the engine’s own logging buffers (`WString::Format` in this case) to avoid reimplementing undocumented string encodings.<sup>[[1]](#references)</sup> 65 - Dump `Flags` to identify reliability features (ACK, resend requests) before attempting fuzzing; custom UDP stacks frequently drop malformed packets silently. 66 - 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. 67 68 ## Subverting file-transfer RPCs 69 70 Multiplayer save synchronization used a two-packet handshake:<sup>[[1]](#references)</sup> 71 72 1. `OnSendFileInit` — carries the UTF‑16 filename the client should use when saving the incoming payload. 73 2. `OnSendFileData` — streams raw file contents in fixed-size chunks. 74 75 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.<sup>[[1]](#references)</sup> 76 77 <details> 78 <summary>Filename swapper</summary> 79 80 ```javascript 81 const writeStr = ptr('0x1003A250'); 82 const ByteStreamWriteString = new NativeFunction(writeStr, 'pointer', ['pointer', 'pointer']); 83 const evil = Memory.allocUtf16String('..\\..\\..\\..\\Sauvegarde.sww'); 84 85 Interceptor.attach(writeStr, { 86 onEnter(args) { 87 const src = args[1].readPointer(); 88 const value = src.readUtf16String(); 89 if (value && value.indexOf('Sauvegarde.sww') !== -1) { 90 args[1].writePointer(evil); 91 } 92 } 93 }); 94 ``` 95 96 </details> 97 98 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:<sup>[[1]](#references)</sup> 99 100 - **Drop DLLs** for classic search-order hijacking on next launch, or 101 - **Overwrite asset archives** (RDA files) so that weaponized models, textures, or scripts are loaded live during the same session. 102 103 ### Defending / attacking other targets 104 105 - Look for RPC verbs named `SendFile`, `Upload`, `ShareSave`, etc., then intercept the serialization helper responsible for filenames or target directories. 106 - Even if filenames are length-checked, many stacks forget to canonicalize `..\` or mixed `/` vs `\` sequences; brute-force all separators. 107 - When the receiver stores files under the game install path, check ACLs via `icacls` to confirm whether an unprivileged user can drop code there. 108 109 ## Turning path traversal into live asset execution 110 111 Once you can upload arbitrary bytes, replace any frequently loaded asset:<sup>[[1]](#references)</sup> 112 113 1. **Unpack the archive.** RDA archives are DEFLATE-based containers whose metadata is optionally XOR-obfuscated with `srand(0xA2C2A)` seeded streams. Tools like [RDAExplorer](https://github.com/lysanntranvouez/RDAExplorer) re-pack archives after edits.<sup>[[1]](#references)[[2]](#references)</sup> 114 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`. 115 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. 116 117 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. 118 119 ## References 120 121 - [1] [Synacktiv – Exploiting Anno 1404](https://www.synacktiv.com/publications/exploiting-anno-1404.html) 122 - [2] [RDA File Format notes](https://github.com/lysanntranvouez/RDAExplorer/wiki/RDA-File-Format)