249 lines
11 KiB
Python
249 lines
11 KiB
Python
"""
|
|
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()
|