Skydata001 commited on
Commit
68386a6
·
verified ·
1 Parent(s): 140084d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +203 -166
app.py CHANGED
@@ -12,13 +12,21 @@ import time
12
  import struct
13
  import subprocess
14
  from datetime import datetime
15
- from typing import List, Dict, Any, Tuple
16
 
17
  from fastapi import FastAPI, UploadFile, File, Request, WebSocket, WebSocketDisconnect
18
  from fastapi.responses import HTMLResponse, FileResponse
19
  from fastapi.templating import Jinja2Templates
20
 
21
- app = FastAPI(title="APK & Binary Advanced RE Inspector")
 
 
 
 
 
 
 
 
22
 
23
  WORKSPACE_DIR = "/tmp/re_workspace"
24
  OUTPUT_DIR = "/tmp/re_output"
@@ -27,36 +35,26 @@ os.makedirs(OUTPUT_DIR, exist_ok=True)
27
 
28
  templates = Jinja2Templates(directory="templates")
29
 
30
- # الامتدادات المقتطعة لتوفير الوقت والرام
31
- IGNORED_EXTENSIONS = {
32
- '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.ico',
33
- '.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv', '.mp3', '.wav', '.ogg',
34
- '.ttf', '.otf', '.woff', '.woff2', '.eot', '.pdf'
35
- }
36
 
37
- # المجلدات المستبعدة لتجنب الإيجابيات الخاطئة في تصميم الواجهات
38
- IGNORED_SUBDIRS = ['res/drawable', 'res/color', 'res/anim', 'res/mipmap']
39
 
40
- # تعابير نمطية مستهدفة بدقة
41
  DISCORD_PATTERN = re.compile(r'discord\.(?:gg|com/invite)/[a-zA-Z0-9_-]+', re.IGNORECASE)
42
  STUART_PATTERN = re.compile(r'https?://[a-zA-Z0-9.-]*stuartmods[a-zA-Z0-9.-]*\.[a-zA-Z]{2,}[^\s<>"{}|\\^`\']*', re.IGNORECASE)
43
  GENERIC_URL_PATTERN = re.compile(r'https?://[^\s<>"{}|\\^`\']+', re.IGNORECASE)
44
- EMAIL_PATTERN = re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', re.IGNORECASE)
45
 
46
- # أنماط السياق البرمجي (Code Context Analysis)
47
- CODE_CONTEXT_PATTERNS = [
48
- (re.compile(r'(?:getKey|get_Key|verify|LicenseManager|NetworkClient|loadUrl)\s*\(\s*["\']([^"\']+)["\']', re.IGNORECASE), "METHOD_CALL"),
49
- (re.compile(r'(?:BaseURL|API_URL|ENDPOINT|SERVER_URL|HOST_URL)\s*[:=]\s*["\']([^"\']+)["\']', re.IGNORECASE), "API_ENDPOINT_VAR"),
50
- (re.compile(r'const-string\s+v\d+,\s*["\']([^"\']+)["\']', re.IGNORECASE), "SMALI_CONST_STRING")
51
  ]
52
 
53
- NETWORK_API_PATTERNS = re.compile(
54
- r'(?:HttpURLConnection|OkHttpClient|Retrofit|WebView|loadUrl|HttpClient|curl_easy_init|SSL_read)',
55
- re.IGNORECASE
56
- )
57
-
58
- BASE64_PATTERN = re.compile(r'(?:[A-Za-z0-9+/]{4}){5,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?')
59
- HEX_ESCAPED_PATTERN = re.compile(r'(?:\\x[0-9a-fA-F]{2}){4,}')
60
 
61
  class ConnectionManager:
62
  def __init__(self):
@@ -112,27 +110,22 @@ def calculate_entropy(data: bytes) -> float:
112
  entropy -= p * math.log2(p)
113
  return entropy
114
 
115
- def rot13(text: str) -> str:
116
- trans = str.maketrans(
117
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
118
- 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'
119
- )
120
- return text.translate(trans)
 
121
 
122
- def extract_strings_native(file_path: str) -> str:
123
- try:
124
- res = subprocess.run(['strings', '-n', '4', file_path], capture_output=True, text=True, errors='ignore', timeout=10)
125
- return res.stdout
126
- except Exception:
127
- try:
128
- with open(file_path, 'rb') as f:
129
- content = f.read()
130
- matches = re.findall(rb'[\x20-\x7e]{4,}', content)
131
- return "\n".join([m.decode('ascii', errors='ignore') for m in matches])
132
- except Exception:
133
- return ""
134
 
135
  def parse_il2cpp_metadata(file_path: str) -> List[str]:
 
136
  extracted_strings = []
137
  try:
138
  with open(file_path, 'rb') as f:
@@ -155,22 +148,94 @@ def parse_il2cpp_metadata(file_path: str) -> List[str]:
155
  lit_len, lit_data_off = struct.unpack("<II", data[sl_offset + i * 8 : sl_offset + i * 8 + 8])
156
  if sld_offset + lit_data_off + lit_len <= len(data):
157
  s = data[sld_offset + lit_data_off : sld_offset + lit_data_off + lit_len].decode('utf-8', errors='ignore')
158
- if len(s.strip()) > 2:
159
  extracted_strings.append(s)
160
  except Exception:
161
  pass
162
  return extracted_strings
163
 
164
- def extract_snippet(text: str, start: int, end: int, margin: int = 100) -> str:
165
- s_start = max(0, start - margin)
166
- s_end = min(len(text), end + margin)
167
- snippet_text = text[s_start:s_end].replace('\n', ' ').replace('\r', ' ')
168
- return f"...{snippet_text}..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
  def brute_force_xor_full(raw_bytes: bytes) -> List[Dict[str, Any]]:
171
- """تطبيق XOR على كامل مفاتيح 1-255 للملفات الصغيرة"""
172
  results = []
173
- sample = raw_bytes[:200000] # أخذ عينة 200KB للأداء الفائق
174
  targets = [b"http://", b"https://", b"discord", b"stuartmods", b"license", b"verify"]
175
 
176
  for key in range(1, 256):
@@ -179,10 +244,11 @@ def brute_force_xor_full(raw_bytes: bytes) -> List[Dict[str, Any]]:
179
  if t in dec.lower():
180
  urls = GENERIC_URL_PATTERN.findall(dec.decode('ascii', errors='ignore'))
181
  for u in set(urls[:5]):
182
- results.append({
183
- "key": f"0x{key:02X}",
184
- "decoded": u
185
- })
 
186
  break
187
  return results
188
 
@@ -191,8 +257,8 @@ def analyze_file_deep(file_path: str, extract_root: str) -> Dict[str, Any]:
191
  file_name = os.path.basename(file_path).lower()
192
  file_size = os.path.getsize(file_path)
193
 
194
- links_found = []
195
- suspicious_findings = []
196
  encrypted_findings = []
197
 
198
  try:
@@ -202,25 +268,42 @@ def analyze_file_deep(file_path: str, extract_root: str) -> Dict[str, Any]:
202
  entropy = calculate_entropy(raw_bytes)
203
  text_content = ""
204
 
205
- # 1. تحليل global-metadata.dat
206
  if "global-metadata.dat" in file_name:
207
  metadata_strings = parse_il2cpp_metadata(file_path)
208
  if metadata_strings:
209
  text_content = "\n".join(metadata_strings)
210
 
211
- # 2. الملفات الثنائية (.so, .dex, .dat)
212
  if not text_content:
213
- if file_name.endswith(('.so', '.dex', '.dat')) or b'\x00' in raw_bytes[:512]:
214
- text_content = extract_strings_native(file_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  else:
216
  text_content = raw_bytes.decode('utf-8', errors='ignore')
217
 
218
- # أ) البحث في روابط Discord وسيرفر stuartmods (CRITICAL)
219
  for m in DISCORD_PATTERN.finditer(text_content):
220
  val = m.group()
221
  snip = extract_snippet(text_content, m.start(), m.end(), 100)
222
- links_found.append({
223
- "type": "DISCORD_LINK",
224
  "value": val,
225
  "snippet": snip,
226
  "severity": "CRITICAL"
@@ -229,67 +312,46 @@ def analyze_file_deep(file_path: str, extract_root: str) -> Dict[str, Any]:
229
  for m in STUART_PATTERN.finditer(text_content):
230
  val = m.group()
231
  snip = extract_snippet(text_content, m.start(), m.end(), 100)
232
- links_found.append({
233
  "type": "STUARTMODS_SERVER",
234
  "value": val,
235
  "snippet": snip,
236
  "severity": "CRITICAL"
237
  })
238
 
239
- # ب) البحث في الروابط العامة والربط بالسياق البرمجي (HIGH / MEDIUM)
240
  for m in GENERIC_URL_PATTERN.finditer(text_content):
241
  val = m.group()
242
- if "discord" not in val.lower() and "stuartmods" not in val.lower():
243
  snip = extract_snippet(text_content, m.start(), m.end(), 100)
244
- # تقييم ذكي بناءً على الكلمات المجاورة
245
  snip_lower = snip.lower()
 
246
  is_high = any(k in snip_lower for k in ["verify", "license", "activation", "api", "auth", "getkey"])
247
- links_found.append({
248
- "type": "URL",
249
- "value": val,
250
- "snippet": snip,
251
- "severity": "HIGH" if is_high else "MEDIUM"
252
- })
253
-
254
- # ج) تحليل السياق البرمجي والدوال الشبكية (Suspicious Files)
255
- has_net_api = bool(NETWORK_API_PATTERNS.search(text_content))
256
- for pattern, ctx_type in CODE_CONTEXT_PATTERNS:
257
- for m in pattern.finditer(text_content):
258
- matched_val = m.group(1) if m.groups() else m.group()
259
- snip = extract_snippet(text_content, m.start(), m.end(), 100)
260
- suspicious_findings.append({
261
- "type": f"CODE_CONTEXT_{ctx_type}",
262
- "value": matched_val,
263
- "snippet": snip,
264
- "severity": "HIGH" if has_net_api else "MEDIUM"
265
- })
266
 
267
- # د) فك التشفير المتقدم (ROT13, Base64, Hex, XOR, Zlib)
268
- # Base64
269
- for b64 in BASE64_PATTERN.findall(text_content)[:10]:
270
  try:
271
  dec = base64.b64decode(b64).decode('utf-8', errors='ignore')
272
- if any(k in dec.lower() for k in ['http', 'discord', 'stuartmods', 'key', 'license', 'verify']):
273
  encrypted_findings.append({
274
  "method": "Base64 Decoded",
275
  "value": f"{b64} -> {dec}",
276
- "snippet": f"مفكوك Base64: {dec}",
277
- "severity": "HIGH" if "discord" in dec.lower() or "http" in dec.lower() else "MEDIUM"
278
  })
279
  except Exception:
280
  pass
281
 
282
- # ROT13 Test
283
- if "discord" in rot13(text_content).lower() or "stuartmods" in rot13(text_content).lower():
284
- encrypted_findings.append({
285
- "method": "ROT13 Cipher",
286
- "value": "اكتشاف نصوص مشفرة بـ ROT13",
287
- "snippet": "تم رصد سلاسل نصية مفكوكة بـ ROT13 تحتوي على روابط أو مفاتيح",
288
- "severity": "HIGH"
289
- })
290
-
291
- # XOR Full Brute Force للملفات < 5MB
292
- if file_size <= 5 * 1024 * 1024 and (file_name.endswith('.so') or entropy > 6.0):
293
  xor_hits = brute_force_xor_full(raw_bytes)
294
  for x_item in xor_hits:
295
  encrypted_findings.append({
@@ -299,34 +361,14 @@ def analyze_file_deep(file_path: str, extract_root: str) -> Dict[str, Any]:
299
  "severity": "CRITICAL" if "discord" in x_item['decoded'].lower() or "stuartmods" in x_item['decoded'].lower() else "HIGH"
300
  })
301
 
302
- # هـ) فحص الملفات عالية العشوائية (Entropy > 7.5)
303
- if entropy > 7.5:
304
- suggested_decomp = "XOR / Custom Packer"
305
- try:
306
- zlib.decompress(raw_bytes)
307
- suggested_decomp = "Zlib Compressed"
308
- except Exception:
309
- try:
310
- gzip.decompress(raw_bytes)
311
- suggested_decomp = "Gzip Compressed"
312
- except Exception:
313
- pass
314
-
315
- encrypted_findings.append({
316
- "method": f"High Entropy ({entropy:.2f})",
317
- "value": f"ملف مشفر/مضغوط: {rel_path}",
318
- "snippet": f"العشوائية مرتفعة جداً ({entropy:.2f}). طريقة الفك المقترحة: {suggested_decomp}",
319
- "severity": "HIGH"
320
- })
321
-
322
  except Exception:
323
  pass
324
 
325
  return {
326
  "file": rel_path,
327
- "links": links_found,
328
- "suspicious": suspicious_findings,
329
- "encrypted": encrypted_findings
330
  }
331
 
332
  def run_full_analysis_sync(extract_folder: str) -> Dict[str, Any]:
@@ -334,26 +376,21 @@ def run_full_analysis_sync(extract_folder: str) -> Dict[str, Any]:
334
  for root, _, files in os.walk(extract_folder):
335
  for f in files:
336
  full_p = os.path.join(root, f)
337
- ext = os.path.splitext(f)[1].lower()
338
- if ext in IGNORED_EXTENSIONS:
339
- continue
340
-
341
  rel_p = os.path.relpath(full_p, extract_folder).replace('\\', '/')
342
  if any(rel_p.startswith(sub) for sub in IGNORED_SUBDIRS):
343
  continue
344
-
345
  files_to_scan.append(full_p)
346
 
347
  total_files = len(files_to_scan)
348
 
349
  all_links = []
350
- all_suspicious = []
351
  all_encrypted = []
352
 
353
  stats = {
354
  "total_files": total_files,
355
  "total_links": 0,
356
- "total_suspicious": 0,
357
  "total_encrypted": 0,
358
  "critical_count": 0,
359
  "high_count": 0,
@@ -363,29 +400,29 @@ def run_full_analysis_sync(extract_folder: str) -> Dict[str, Any]:
363
  for file_p in files_to_scan:
364
  res = analyze_file_deep(file_p, extract_folder)
365
 
366
- if res["links"]:
367
- stats["total_links"] += len(res["links"])
368
- for l in res["links"]:
369
  stats[f"{l['severity'].lower()}_count"] = stats.get(f"{l['severity'].lower()}_count", 0) + 1
370
- all_links.append({"file": res["file"], "items": res["links"]})
371
 
372
- if res["suspicious"]:
373
- stats["total_suspicious"] += len(res["suspicious"])
374
- for s in res["suspicious"]:
375
- stats[f"{s['severity'].lower()}_count"] = stats.get(f"{s['severity'].lower()}_count", 0) + 1
376
- all_suspicious.append({"file": res["file"], "items": res["suspicious"]})
377
 
378
- if res["encrypted"]:
379
- stats["total_encrypted"] += len(res["encrypted"])
380
- for e in res["encrypted"]:
381
  stats[f"{e['severity'].lower()}_count"] = stats.get(f"{e['severity'].lower()}_count", 0) + 1
382
- all_encrypted.append({"file": res["file"], "items": res["encrypted"]})
383
 
384
  return {
385
  "summary": stats,
386
- "links_found": all_links,
387
- "suspicious_files": all_suspicious,
388
- "encrypted_files": all_encrypted
389
  }
390
 
391
  @app.get("/", response_class=HTMLResponse)
@@ -405,7 +442,7 @@ async def websocket_logs(websocket: WebSocket):
405
  async def analyze_files(files: List[UploadFile] = File(...)):
406
  clean_workspace()
407
 
408
- await manager.broadcast_log("INFO", f"بدء استقبال {len(files)} ملف/ملفات...", 5)
409
 
410
  extract_folder = os.path.join(WORKSPACE_DIR, "extracted")
411
  os.makedirs(extract_folder, exist_ok=True)
@@ -417,8 +454,8 @@ async def analyze_files(files: List[UploadFile] = File(...)):
417
  while chunk := await uploaded_file.read(4 * 1024 * 1024):
418
  buffer.write(chunk)
419
 
420
- if uploaded_file.filename.lower().endswith(".zip") or uploaded_file.filename.lower().endswith(".apk"):
421
- await manager.broadcast_log("INFO", f"جاري فك ضغط الأرشيف: {uploaded_file.filename}...", 15)
422
  try:
423
  with zipfile.ZipFile(file_path, 'r') as zf:
424
  zf.extractall(extract_folder)
@@ -427,12 +464,12 @@ async def analyze_files(files: List[UploadFile] = File(...)):
427
  else:
428
  shutil.copy(file_path, os.path.join(extract_folder, uploaded_file.filename))
429
 
430
- await manager.broadcast_log("INFO", "بدء الفحص البرمجي المتطور وتتبع الدوال والتشفير...", 30)
431
 
432
- # تشغيل التحليل في الخلفية المستقلة لمنع التجمد نهائياً
433
  report_data = await asyncio.to_thread(run_full_analysis_sync, extract_folder)
434
 
435
- await manager.broadcast_log("SUCCESS", f"اكتمل الفحص! تم إيجاد {report_data['summary']['total_links']} رابط و {report_data['summary']['total_suspicious']} ملف مشتبه به.", 100)
436
 
437
  json_path = os.path.join(OUTPUT_DIR, "analysis_report.json")
438
  with open(json_path, "w", encoding="utf-8") as f:
@@ -448,7 +485,7 @@ def generate_html_report(data: Dict[str, Any], save_path: str):
448
  <html lang="ar" dir="rtl">
449
  <head>
450
  <meta charset="UTF-8">
451
- <title>Advanced RE Inspection Report</title>
452
  <style>
453
  body {{ font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 20px; line-height: 1.5; }}
454
  .card {{ background: #1e293b; padding: 16px; border-radius: 10px; margin-bottom: 16px; border: 1px solid #334155; }}
@@ -464,34 +501,34 @@ def generate_html_report(data: Dict[str, Any], save_path: str):
464
  </style>
465
  </head>
466
  <body>
467
- <h1>تقرير التفتيش البرمجي وفحص الروابط والتشفير</h1>
468
  <div class="card">
469
  <h3>الملخص الإحصائي</h3>
470
  <div class="stats-grid">
471
  <div class="stat-card">الملفات المفحوصة<br><b>{summary['total_files']}</b></div>
472
- <div class="stat-card">الروابط المكتشفة<br><b>{summary['total_links']}</b></div>
473
- <div class="stat-card">الملفات المشبوهة<br><b>{summary['total_suspicious']}</b></div>
474
  <div class="stat-card">حاسمة (CRITICAL)<br><b style="color:#dc2626;">{summary['critical_count']}</b></div>
475
  </div>
476
  </div>
477
 
478
- <h2>1. الروابط المكتشفة (Links Found)</h2>
479
  """
480
- for file_item in data['links_found']:
481
  html += f"""<div class="card"><h4 style="color:#facc15;">📁 {file_item['file']}</h4>"""
482
  for item in file_item['items']:
483
  html += f"""<div class="snippet"><span class="badge {item['severity']}">{item['severity']}</span> <b>[{item['type']}]</b>: {item['value']}<br><span style="color:#94a3b8;">{item['snippet']}</span></div>"""
484
  html += "</div>"
485
 
486
- html += "<h2>2. الملفات المشبوهة (Suspicious Files)</h2>"
487
- for file_item in data['suspicious_files']:
488
  html += f"""<div class="card"><h4 style="color:#facc15;">📁 {file_item['file']}</h4>"""
489
  for item in file_item['items']:
490
- html += f"""<div class="snippet"><span class="badge {item['severity']}">{item['severity']}</span> <b>[{item['type']}]</b>: {item['value']}<br><span style="color:#94a3b8;">{item['snippet']}</span></div>"""
491
  html += "</div>"
492
 
493
- html += "<h2>3. الملفات المشفرة (Encrypted Files)</h2>"
494
- for file_item in data['encrypted_files']:
495
  html += f"""<div class="card"><h4 style="color:#facc15;">📁 {file_item['file']}</h4>"""
496
  for item in file_item['items']:
497
  html += f"""<div class="snippet"><span class="badge {item['severity']}">{item['severity']}</span> <b>[{item['method']}]</b>: {item['value']}<br><span style="color:#94a3b8;">{item['snippet']}</span></div>"""
 
12
  import struct
13
  import subprocess
14
  from datetime import datetime
15
+ from typing import List, Dict, Any, Tuple, Set
16
 
17
  from fastapi import FastAPI, UploadFile, File, Request, WebSocket, WebSocketDisconnect
18
  from fastapi.responses import HTMLResponse, FileResponse
19
  from fastapi.templating import Jinja2Templates
20
 
21
+ from elftools.elf.elffile import ELFFile
22
+ from elftools.elf.sections import SymbolTableSection
23
+
24
+ try:
25
+ import cxxfilt
26
+ except ImportError:
27
+ cxxfilt = None
28
+
29
+ app = FastAPI(title="Professional ELF & Native RE Inspector")
30
 
31
  WORKSPACE_DIR = "/tmp/re_workspace"
32
  OUTPUT_DIR = "/tmp/re_output"
 
35
 
36
  templates = Jinja2Templates(directory="templates")
37
 
38
+ # 1. قائمة التصفية والاستبعاد الصارمة للقضاء على النويز وHTML/CSS
39
+ NOISE_REJECT_PATTERNS = [
40
+ '<', '>', 'style=', 'font-family', 'margin-top', 'padding-', 'background-color',
41
+ 'display:', 'position:', 'color:', 'width:', 'height:', 'border:', '.css', '.js',
42
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', 'http://www.w3.org/', 'xmlns:'
43
+ ]
44
 
45
+ ALLOWED_SECTIONS = {'.rodata', '.data', '.dynstr', '.strtab'}
 
46
 
47
+ # 2. التعابير النمطية المستهدفة
48
  DISCORD_PATTERN = re.compile(r'discord\.(?:gg|com/invite)/[a-zA-Z0-9_-]+', re.IGNORECASE)
49
  STUART_PATTERN = re.compile(r'https?://[a-zA-Z0-9.-]*stuartmods[a-zA-Z0-9.-]*\.[a-zA-Z]{2,}[^\s<>"{}|\\^`\']*', re.IGNORECASE)
50
  GENERIC_URL_PATTERN = re.compile(r'https?://[^\s<>"{}|\\^`\']+', re.IGNORECASE)
 
51
 
52
+ ACCEPT_KEYWORD_TRIGGERS = [
53
+ 'http://', 'https://', 'discord.gg', 'discord.com', 'stuartmods',
54
+ 'api.', 'verify', 'license', 'activation', 'key', 'password', 'check_key', 'auth'
 
 
55
  ]
56
 
57
+ FUNCTION_TRIGGER_KEYWORDS = ['check', 'verify', 'license', 'auth', 'java_', 'jni_onload', 'stuart', 'key']
 
 
 
 
 
 
58
 
59
  class ConnectionManager:
60
  def __init__(self):
 
110
  entropy -= p * math.log2(p)
111
  return entropy
112
 
113
+ def is_noise_string(text: str) -> bool:
114
+ """فلتر صارم لاستبعاد نصوص HTML/CSS والأيقونات"""
115
+ text_lower = text.lower()
116
+ for noise in NOISE_REJECT_PATTERNS:
117
+ if noise in text_lower:
118
+ return True
119
+ return False
120
 
121
+ def extract_snippet(text: str, start: int, end: int, margin: int = 100) -> str:
122
+ s_start = max(0, start - margin)
123
+ s_end = min(len(text), end + margin)
124
+ clean_text = text[s_start:s_end].replace('\n', ' ').replace('\r', ' ')
125
+ return f"...{clean_text}..."
 
 
 
 
 
 
 
126
 
127
  def parse_il2cpp_metadata(file_path: str) -> List[str]:
128
+ """محلل بنية Unity global-metadata.dat لاستخراج جدول النصوص C# مباشرة"""
129
  extracted_strings = []
130
  try:
131
  with open(file_path, 'rb') as f:
 
148
  lit_len, lit_data_off = struct.unpack("<II", data[sl_offset + i * 8 : sl_offset + i * 8 + 8])
149
  if sld_offset + lit_data_off + lit_len <= len(data):
150
  s = data[sld_offset + lit_data_off : sld_offset + lit_data_off + lit_len].decode('utf-8', errors='ignore')
151
+ if len(s.strip()) > 3 and not is_noise_string(s):
152
  extracted_strings.append(s)
153
  except Exception:
154
  pass
155
  return extracted_strings
156
 
157
+ def demangle_symbol_name(sym_name: str) -> str:
158
+ """فك تزييف أسماء C++ Mangled Symbols"""
159
+ if not sym_name:
160
+ return ""
161
+ if cxxfilt:
162
+ try:
163
+ return cxxfilt.demangle(sym_name)
164
+ except Exception:
165
+ pass
166
+ return sym_name
167
+
168
+ def analyze_elf_binary(file_path: str) -> Tuple[str, List[Dict[str, Any]], List[Dict[str, Any]]]:
169
+ """قراءة هيكلية ELF الرسمية واستخراج الأقسام والرموز والدوال المشبوهة"""
170
+ extracted_text_blocks = []
171
+ suspicious_symbols = []
172
+ high_entropy_sections = []
173
+
174
+ try:
175
+ with open(file_path, 'rb') as f:
176
+ elf = ELFFile(f)
177
+
178
+ # 1. استخراج الأقسام الحيوية فقط وسحب النصوص منها
179
+ for section in elf.iter_sections():
180
+ sec_name = section.name
181
+ sec_data = section.data()
182
+ sec_entropy = calculate_entropy(sec_data)
183
+
184
+ if sec_entropy > 7.4 and section.size() > 1024:
185
+ high_entropy_sections.append({
186
+ "section_name": sec_name,
187
+ "size": section.size(),
188
+ "entropy": round(sec_entropy, 2),
189
+ "suggested_action": "XOR Brute-force / Custom Unpacker Required"
190
+ })
191
+
192
+ if sec_name in ALLOWED_SECTIONS:
193
+ matches = re.findall(rb'[\x20-\x7e]{4,}', sec_data)
194
+ for m in matches:
195
+ try:
196
+ s = m.decode('ascii', errors='ignore')
197
+ if not is_noise_string(s):
198
+ s_lower = s.lower()
199
+ if any(trigger in s_lower for k in ACCEPT_KEYWORD_TRIGGERS for trigger in [k]):
200
+ extracted_text_blocks.append(s)
201
+ except Exception:
202
+ pass
203
+
204
+ # 2. استخراج الرموز والدوال المشبوهة من جداول الرموز
205
+ if isinstance(section, SymbolTableSection):
206
+ for symbol in section.iter_symbols():
207
+ sym_name = symbol.name
208
+ if not sym_name:
209
+ continue
210
+
211
+ sym_lower = sym_name.lower()
212
+ if any(kw in sym_lower for kw in FUNCTION_TRIGGER_KEYWORDS):
213
+ demangled = demangle_symbol_name(sym_name)
214
+ suspicious_symbols.append({
215
+ "mangled": sym_name,
216
+ "demangled": demangled,
217
+ "section": sec_name,
218
+ "binding": symbol['st_info']['bind']
219
+ })
220
+
221
+ except Exception:
222
+ # حل بديل إذا كان الملف ثنائياً عادياً وليس ELF قياسي
223
+ try:
224
+ res = subprocess.run(['strings', '-n', '4', file_path], capture_output=True, text=True, errors='ignore', timeout=10)
225
+ for line in res.stdout.splitlines():
226
+ if not is_noise_string(line):
227
+ line_lower = line.lower()
228
+ if any(trigger in line_lower for trigger in ACCEPT_KEYWORD_TRIGGERS):
229
+ extracted_text_blocks.append(line)
230
+ except Exception:
231
+ pass
232
+
233
+ return "\n".join(extracted_text_blocks), suspicious_symbols, high_entropy_sections
234
 
235
  def brute_force_xor_full(raw_bytes: bytes) -> List[Dict[str, Any]]:
236
+ """تطبيق مفاتيح XOR العنيفة (1-255) على الملفات وحصد النتائج الحقيقية"""
237
  results = []
238
+ sample = raw_bytes[:250000]
239
  targets = [b"http://", b"https://", b"discord", b"stuartmods", b"license", b"verify"]
240
 
241
  for key in range(1, 256):
 
244
  if t in dec.lower():
245
  urls = GENERIC_URL_PATTERN.findall(dec.decode('ascii', errors='ignore'))
246
  for u in set(urls[:5]):
247
+ if not is_noise_string(u):
248
+ results.append({
249
+ "key": f"0x{key:02X}",
250
+ "decoded": u
251
+ })
252
  break
253
  return results
254
 
 
257
  file_name = os.path.basename(file_path).lower()
258
  file_size = os.path.getsize(file_path)
259
 
260
+ critical_links = []
261
+ suspicious_functions = []
262
  encrypted_findings = []
263
 
264
  try:
 
268
  entropy = calculate_entropy(raw_bytes)
269
  text_content = ""
270
 
271
+ # أ) ملفات global-metadata.dat
272
  if "global-metadata.dat" in file_name:
273
  metadata_strings = parse_il2cpp_metadata(file_path)
274
  if metadata_strings:
275
  text_content = "\n".join(metadata_strings)
276
 
277
+ # ب) ملفات .so المباشرة والـ ELF Binaries
278
  if not text_content:
279
+ if file_name.endswith('.so') or b'ELF' in raw_bytes[:4]:
280
+ extracted_text, elf_syms, high_sec = analyze_elf_binary(file_path)
281
+ text_content = extracted_text
282
+
283
+ for sym in elf_syms:
284
+ suspicious_functions.append({
285
+ "function_name": sym["demangled"] or sym["mangled"],
286
+ "mangled_symbol": sym["mangled"],
287
+ "section": sym["section"],
288
+ "severity": "HIGH" if "verify" in sym["mangled"].lower() or "license" in sym["mangled"].lower() else "MEDIUM"
289
+ })
290
+
291
+ for sec in high_sec:
292
+ encrypted_findings.append({
293
+ "method": f"High Entropy Section ({sec['section_name']})",
294
+ "value": f"الحجم: {sec['size']} bytes | العشوائية: {sec['entropy']}",
295
+ "snippet": sec["suggested_action"],
296
+ "severity": "HIGH"
297
+ })
298
  else:
299
  text_content = raw_bytes.decode('utf-8', errors='ignore')
300
 
301
+ # 1. كشف وتصفية الروابط الحرجّة (Discord & StuartMods)
302
  for m in DISCORD_PATTERN.finditer(text_content):
303
  val = m.group()
304
  snip = extract_snippet(text_content, m.start(), m.end(), 100)
305
+ critical_links.append({
306
+ "type": "DISCORD_INVITE",
307
  "value": val,
308
  "snippet": snip,
309
  "severity": "CRITICAL"
 
312
  for m in STUART_PATTERN.finditer(text_content):
313
  val = m.group()
314
  snip = extract_snippet(text_content, m.start(), m.end(), 100)
315
+ critical_links.append({
316
  "type": "STUARTMODS_SERVER",
317
  "value": val,
318
  "snippet": snip,
319
  "severity": "CRITICAL"
320
  })
321
 
322
+ # 2. كشف الروابط العامة التابعة لـ APIs والتحقق
323
  for m in GENERIC_URL_PATTERN.finditer(text_content):
324
  val = m.group()
325
+ if not is_noise_string(val) and "discord" not in val.lower() and "stuartmods" not in val.lower():
326
  snip = extract_snippet(text_content, m.start(), m.end(), 100)
 
327
  snip_lower = snip.lower()
328
+
329
  is_high = any(k in snip_lower for k in ["verify", "license", "activation", "api", "auth", "getkey"])
330
+ if is_high:
331
+ critical_links.append({
332
+ "type": "VERIFICATION_API_URL",
333
+ "value": val,
334
+ "snippet": snip,
335
+ "severity": "HIGH"
336
+ })
 
 
 
 
 
 
 
 
 
 
 
 
337
 
338
+ # 3. فك تشفير Base64 وسلاسل XOR
339
+ base64_matches = re.findall(r'(?:[A-Za-z0-9+/]{4}){6,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?', text_content)
340
+ for b64 in base64_matches[:10]:
341
  try:
342
  dec = base64.b64decode(b64).decode('utf-8', errors='ignore')
343
+ if not is_noise_string(dec) and any(k in dec.lower() for k in ['http', 'discord', 'stuartmods', 'key', 'license', 'verify']):
344
  encrypted_findings.append({
345
  "method": "Base64 Decoded",
346
  "value": f"{b64} -> {dec}",
347
+ "snippet": f"فك ترميز Base64: {dec}",
348
+ "severity": "CRITICAL" if "discord" in dec.lower() or "stuartmods" in dec.lower() else "HIGH"
349
  })
350
  except Exception:
351
  pass
352
 
353
+ # XOR Full Brute Force للملفات أصغر من 5MB
354
+ if file_size <= 5 * 1024 * 1024 and (file_name.endswith('.so') or entropy > 6.8):
 
 
 
 
 
 
 
 
 
355
  xor_hits = brute_force_xor_full(raw_bytes)
356
  for x_item in xor_hits:
357
  encrypted_findings.append({
 
361
  "severity": "CRITICAL" if "discord" in x_item['decoded'].lower() or "stuartmods" in x_item['decoded'].lower() else "HIGH"
362
  })
363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  except Exception:
365
  pass
366
 
367
  return {
368
  "file": rel_path,
369
+ "critical_links": critical_links,
370
+ "suspicious_functions": suspicious_functions,
371
+ "encrypted_sections": encrypted_findings
372
  }
373
 
374
  def run_full_analysis_sync(extract_folder: str) -> Dict[str, Any]:
 
376
  for root, _, files in os.walk(extract_folder):
377
  for f in files:
378
  full_p = os.path.join(root, f)
 
 
 
 
379
  rel_p = os.path.relpath(full_p, extract_folder).replace('\\', '/')
380
  if any(rel_p.startswith(sub) for sub in IGNORED_SUBDIRS):
381
  continue
 
382
  files_to_scan.append(full_p)
383
 
384
  total_files = len(files_to_scan)
385
 
386
  all_links = []
387
+ all_functions = []
388
  all_encrypted = []
389
 
390
  stats = {
391
  "total_files": total_files,
392
  "total_links": 0,
393
+ "total_functions": 0,
394
  "total_encrypted": 0,
395
  "critical_count": 0,
396
  "high_count": 0,
 
400
  for file_p in files_to_scan:
401
  res = analyze_file_deep(file_p, extract_folder)
402
 
403
+ if res["critical_links"]:
404
+ stats["total_links"] += len(res["critical_links"])
405
+ for l in res["critical_links"]:
406
  stats[f"{l['severity'].lower()}_count"] = stats.get(f"{l['severity'].lower()}_count", 0) + 1
407
+ all_links.append({"file": res["file"], "items": res["critical_links"]})
408
 
409
+ if res["suspicious_functions"]:
410
+ stats["total_functions"] += len(res["suspicious_functions"])
411
+ for f_item in res["suspicious_functions"]:
412
+ stats[f"{f_item['severity'].lower()}_count"] = stats.get(f"{f_item['severity'].lower()}_count", 0) + 1
413
+ all_functions.append({"file": res["file"], "items": res["suspicious_functions"]})
414
 
415
+ if res["encrypted_sections"]:
416
+ stats["total_encrypted"] += len(res["encrypted_sections"])
417
+ for e in res["encrypted_sections"]:
418
  stats[f"{e['severity'].lower()}_count"] = stats.get(f"{e['severity'].lower()}_count", 0) + 1
419
+ all_encrypted.append({"file": res["file"], "items": res["encrypted_sections"]})
420
 
421
  return {
422
  "summary": stats,
423
+ "critical_links": all_links,
424
+ "suspicious_functions": all_functions,
425
+ "encrypted_sections": all_encrypted
426
  }
427
 
428
  @app.get("/", response_class=HTMLResponse)
 
442
  async def analyze_files(files: List[UploadFile] = File(...)):
443
  clean_workspace()
444
 
445
+ await manager.broadcast_log("INFO", f"بدء استقبال {len(files)} ملف/ملفات للتفتيش...", 5)
446
 
447
  extract_folder = os.path.join(WORKSPACE_DIR, "extracted")
448
  os.makedirs(extract_folder, exist_ok=True)
 
454
  while chunk := await uploaded_file.read(4 * 1024 * 1024):
455
  buffer.write(chunk)
456
 
457
+ if uploaded_file.filename.lower().endswith((".zip", ".apk")):
458
+ await manager.broadcast_log("INFO", f"جاري فك الأرشيف المرفوع: {uploaded_file.filename}...", 15)
459
  try:
460
  with zipfile.ZipFile(file_path, 'r') as zf:
461
  zf.extractall(extract_folder)
 
464
  else:
465
  shutil.copy(file_path, os.path.join(extract_folder, uploaded_file.filename))
466
 
467
+ await manager.broadcast_log("INFO", "بدء تحليل ELF Sections واستخراج الرموز وفك التشفير بـ XOR...", 30)
468
 
469
+ # تشغيل الفحص بعيداً عن الخيط الرئيسي لحماية الـ CPU والـ Event Loop
470
  report_data = await asyncio.to_thread(run_full_analysis_sync, extract_folder)
471
 
472
+ await manager.broadcast_log("SUCCESS", f"اكتمل التفتيش! تم اكتشاف {report_data['summary']['total_links']} رابط حاسم و {report_data['summary']['total_functions']} دالة مشبوهة.", 100)
473
 
474
  json_path = os.path.join(OUTPUT_DIR, "analysis_report.json")
475
  with open(json_path, "w", encoding="utf-8") as f:
 
485
  <html lang="ar" dir="rtl">
486
  <head>
487
  <meta charset="UTF-8">
488
+ <title>Professional ELF RE Inspection Report</title>
489
  <style>
490
  body {{ font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 20px; line-height: 1.5; }}
491
  .card {{ background: #1e293b; padding: 16px; border-radius: 10px; margin-bottom: 16px; border: 1px solid #334155; }}
 
501
  </style>
502
  </head>
503
  <body>
504
+ <h1>تقرير التفتيش الهندسي ومحلل المكتبات الأصلية (ELF / SO)</h1>
505
  <div class="card">
506
  <h3>الملخص الإحصائي</h3>
507
  <div class="stats-grid">
508
  <div class="stat-card">الملفات المفحوصة<br><b>{summary['total_files']}</b></div>
509
+ <div class="stat-card">الروابط الحاسمة<br><b>{summary['total_links']}</b></div>
510
+ <div class="stat-card">الدوال المستخرجة<br><b>{summary['total_functions']}</b></div>
511
  <div class="stat-card">حاسمة (CRITICAL)<br><b style="color:#dc2626;">{summary['critical_count']}</b></div>
512
  </div>
513
  </div>
514
 
515
+ <h2>1. الروابط الحرجة (Critical Links)</h2>
516
  """
517
+ for file_item in data['critical_links']:
518
  html += f"""<div class="card"><h4 style="color:#facc15;">📁 {file_item['file']}</h4>"""
519
  for item in file_item['items']:
520
  html += f"""<div class="snippet"><span class="badge {item['severity']}">{item['severity']}</span> <b>[{item['type']}]</b>: {item['value']}<br><span style="color:#94a3b8;">{item['snippet']}</span></div>"""
521
  html += "</div>"
522
 
523
+ html += "<h2>2. الدوال والرموز المشبوهة (Suspicious Functions)</h2>"
524
+ for file_item in data['suspicious_functions']:
525
  html += f"""<div class="card"><h4 style="color:#facc15;">📁 {file_item['file']}</h4>"""
526
  for item in file_item['items']:
527
+ html += f"""<div class="snippet"><span class="badge {item['severity']}">{item['severity']}</span> <b>[Function]</b>: {item['function_name']}<br><span style="color:#94a3b8;">Symbol: {item['mangled_symbol']} | Section: {item['section']}</span></div>"""
528
  html += "</div>"
529
 
530
+ html += "<h2>3. الأقسام المشفّرة ومخرجات فك التشفير (Encrypted Sections)</h2>"
531
+ for file_item in data['encrypted_sections']:
532
  html += f"""<div class="card"><h4 style="color:#facc15;">📁 {file_item['file']}</h4>"""
533
  for item in file_item['items']:
534
  html += f"""<div class="snippet"><span class="badge {item['severity']}">{item['severity']}</span> <b>[{item['method']}]</b>: {item['value']}<br><span style="color:#94a3b8;">{item['snippet']}</span></div>"""