euler314 commited on
Commit
64457ce
·
verified ·
1 Parent(s): 49b91da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +569 -258
app.py CHANGED
@@ -9,20 +9,20 @@ import re
9
  import json
10
  from pathlib import Path
11
 
12
- st.set_page_config(page_title="Executable Reverse Engineer", page_icon="🔍", layout="wide")
13
 
14
- st.title("Executable Reverse Engineering Tool")
15
  st.markdown("""
16
- This tool performs reverse engineering on executables (.exe/.dll) to show their inner workings:
17
- - Disassembles machine code to view assembly instructions
18
- - Attempts to decompile to pseudocode
19
- - Shows strings, imports, and other binary artifacts
20
- - Works with executables from any programming language
21
  """)
22
 
23
  # Install necessary packages at startup
24
  try:
25
- with st.spinner("Setting up reverse engineering environment..."):
26
  # Install key analysis libraries
27
  subprocess.run(["pip", "install", "pyinstaller-extractor"], capture_output=True)
28
  subprocess.run(["pip", "install", "uncompyle6"], capture_output=True)
@@ -35,7 +35,7 @@ try:
35
  st.success("Environment ready")
36
  except Exception as e:
37
  st.error(f"Setup error: {str(e)}")
38
-
39
  def extract_strings(file_path, min_length=4):
40
  """Extract ASCII and Unicode strings from binary file"""
41
  try:
@@ -56,72 +56,357 @@ def extract_strings(file_path, min_length=4):
56
  except Exception as e:
57
  return [f"Error extracting strings: {str(e)}"]
58
 
59
- def analyze_with_radare2(file_path):
60
- """Use radare2 through r2pipe for deep analysis"""
61
- try:
62
- # Open file with r2pipe
63
- r2 = r2pipe.open(file_path)
64
-
65
- # Perform initial analysis
66
- r2.cmd("aaa") # Analyze all
67
-
68
- # Get basic information
69
- info = json.loads(r2.cmd("ij"))
70
-
71
- # Get entry point
72
- entry_point = r2.cmd("ie")
73
-
74
- # Get imports
75
- imports = r2.cmd("iij")
76
- imports = json.loads(imports) if imports else []
77
-
78
- # Get exports (for DLLs)
79
- exports = r2.cmd("iEj")
80
- exports = json.loads(exports) if exports else []
81
 
82
- # Get sections
83
- sections = r2.cmd("iSj")
84
- sections = json.loads(sections) if sections else []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- # Disassemble main function
87
- main_disasm = r2.cmd("s main; pdf")
88
- if not main_disasm or "Cannot find function" in main_disasm:
89
- main_disasm = r2.cmd("s entry0; pdf") # Try entry point instead
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- # Get function list
92
- functions = r2.cmd("aflj")
93
- functions = json.loads(functions) if functions else []
 
 
 
 
 
 
 
 
94
 
95
- # Get decompiled pseudocode (if available)
96
- pseudocode = r2.cmd("s main; pdc")
97
- if not pseudocode or pseudocode.strip() == "":
98
- pseudocode = r2.cmd("s entry0; pdc") # Try entry point instead
 
 
 
 
 
 
 
99
 
100
- # Close r2
101
- r2.quit()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
- return {
104
- "info": info,
105
- "entry_point": entry_point,
106
- "imports": imports,
107
- "exports": exports,
108
- "sections": sections,
109
- "main_disasm": main_disasm,
110
- "functions": functions,
111
- "pseudocode": pseudocode
112
- }
113
- except Exception as e:
114
- return {"error": f"Radare2 analysis failed: {str(e)}"}
 
115
 
116
  def try_pyinstaller_extraction(file_path, output_dir):
117
  """Attempt to extract Python scripts from PyInstaller executables"""
118
  try:
119
- # Run pyinstxtractor on the file
120
- result = subprocess.run(["python", "-m", "pyinstaller-extractor", file_path],
121
- cwd=output_dir, capture_output=True, text=True)
 
 
 
 
 
 
 
 
 
 
122
 
123
  extracted_dir = os.path.join(output_dir, os.path.basename(file_path) + "_extracted")
124
 
 
 
 
 
 
 
125
  if os.path.exists(extracted_dir):
126
  # Try to decompile the Python bytecode files
127
  python_files = {}
@@ -150,22 +435,34 @@ def try_pyinstaller_extraction(file_path, output_dir):
150
  pyz_extract_dir = pyz_path + "_extracted"
151
  os.makedirs(pyz_extract_dir, exist_ok=True)
152
  try:
153
- # Extract PYZ files (these contain most of the Python modules)
154
- subprocess.run(["python", "-m", "pyinstxtractor", pyz_path],
155
- cwd=output_dir, capture_output=True)
156
- for pyz_root, _, pyz_files in os.walk(pyz_extract_dir):
157
- for pyz_file in pyz_files:
158
- if pyz_file.endswith('.pyc') or pyz_file.endswith('.pyo'):
159
- pyc_path = os.path.join(pyz_root, pyz_file)
160
- py_path = pyc_path + ".py"
161
- try:
162
- subprocess.run(["uncompyle6", pyc_path, "-o", py_path], capture_output=True)
163
- if os.path.exists(py_path):
164
- with open(py_path, 'r', encoding='utf-8', errors='ignore') as f:
165
- rel_path = os.path.join("PYZ_ARCHIVE", os.path.relpath(pyc_path, pyz_extract_dir))
166
- python_files[rel_path] = f.read()
167
- except:
168
- pass
 
 
 
 
 
 
 
 
 
 
 
 
169
  except:
170
  pass
171
 
@@ -184,8 +481,79 @@ def try_pyinstaller_extraction(file_path, output_dir):
184
  "message": f"PyInstaller extraction error: {str(e)}"
185
  }
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  def analyze_binary(file_path, is_dll=False):
188
- """Comprehensive analysis of a binary file"""
189
  try:
190
  results = {}
191
 
@@ -244,44 +612,15 @@ def analyze_binary(file_path, is_dll=False):
244
  # Extract strings
245
  results["strings"] = extract_strings(file_path)
246
 
247
- # Use Radare2 for deeper analysis if available
248
- try:
249
- r2_results = analyze_with_radare2(file_path)
250
- if "error" not in r2_results:
251
- results["disassembly"] = r2_results["main_disasm"]
252
- results["functions"] = r2_results["functions"]
253
- results["pseudocode"] = r2_results["pseudocode"]
254
- else:
255
- # Fallback to basic disassembly with Capstone
256
- from capstone import Cs, CS_ARCH_X86, CS_MODE_32, CS_MODE_64
257
-
258
- # Determine if 32-bit or 64-bit
259
- is_64bit = pe.OPTIONAL_HEADER.Magic == 0x20b
260
- mode = CS_MODE_64 if is_64bit else CS_MODE_32
261
-
262
- # Initialize disassembler
263
- md = Cs(CS_ARCH_X86, mode)
264
- md.detail = True
265
-
266
- # Find the entry point
267
- entry_rva = pe.OPTIONAL_HEADER.AddressOfEntryPoint
268
- for section in pe.sections:
269
- if section.contains_rva(entry_rva):
270
- # Calculate file offset of entry point
271
- entry_offset = entry_rva - section.VirtualAddress + section.PointerToRawData
272
- entry_data = pe.get_memory_mapped_image()[entry_rva:entry_rva+512] # Get 512 bytes from entry
273
-
274
- disassembly = []
275
- for i, (address, size, mnemonic, op_str) in enumerate(md.disasm_lite(entry_data, pe.OPTIONAL_HEADER.ImageBase + entry_rva)):
276
- if i >= 100: # Limit to 100 instructions for preview
277
- break
278
- disassembly.append(f"0x{address:08x}: {mnemonic} {op_str}")
279
-
280
- results["disassembly"] = "\n".join(disassembly)
281
- break
282
- except ImportError:
283
- # If r2pipe or capstone isn't available
284
- results["disassembly"] = "Advanced disassembly not available. Install r2pipe or capstone."
285
 
286
  return results
287
  except Exception as e:
@@ -365,114 +704,83 @@ if uploaded_file is not None:
365
  analysis = file_info['analysis']
366
  python_extraction = file_info['python_extraction']
367
 
368
- tabs = st.tabs(["Summary", "Imports", "Strings", "Assembly", "Python Code"])
369
 
370
  with tabs[0]:
371
  if "Error" in analysis:
372
  st.error(analysis["Error"])
373
  else:
374
- st.json(analysis.get("basic_info", {}))
375
-
376
- st.subheader("Sections")
377
- sections_df = {
378
- "Name": [],
379
- "VirtualSize": [],
380
- "SizeOfRawData": [],
381
- "Entropy": []
382
- }
383
- for section in analysis.get("sections", []):
384
- sections_df["Name"].append(section["Name"])
385
- sections_df["VirtualSize"].append(section["VirtualSize"])
386
- sections_df["SizeOfRawData"].append(section["SizeOfRawData"])
387
- sections_df["Entropy"].append(section["Entropy"])
388
-
389
- st.dataframe(sections_df)
390
 
391
  with tabs[1]:
 
 
 
 
 
 
 
 
 
 
392
  for imp in analysis.get("imports", []):
393
  with st.expander(f"DLL: {imp['DLL']}"):
394
  st.code("\n".join(imp["Functions"]))
395
 
396
- with tabs[2]:
397
  st.subheader("Strings Found")
398
  all_strings = analysis.get("strings", [])
399
  interesting_strings = [s for s in all_strings if len(s) > 8] # Filter out very short strings
400
  st.code("\n".join(interesting_strings[:500])) # Limit to 500 strings
401
-
402
- with tabs[3]:
403
- st.subheader("Disassembly")
404
- if "disassembly" in analysis:
405
- st.code(analysis["disassembly"], language="asm")
406
- else:
407
- st.warning("Disassembly not available")
408
-
409
- if "pseudocode" in analysis and analysis["pseudocode"]:
410
- st.subheader("Decompiled Pseudocode")
411
- st.code(analysis["pseudocode"], language="c")
412
-
413
- with tabs[4]:
414
- if python_extraction.get("success", False):
415
- st.success("Python code extracted successfully!")
416
- for filename, content in python_extraction.get("files", {}).items():
417
- with st.expander(f"Python File: {filename}"):
418
- st.code(content, language="python")
419
- else:
420
- st.warning(python_extraction.get("message", "Not a Python executable or extraction failed."))
421
  else: # DLL
422
  analysis = file_info['analysis']
423
 
424
- tabs = st.tabs(["Summary", "Exports", "Imports", "Strings", "Assembly"])
425
 
426
  with tabs[0]:
427
  if "Error" in analysis:
428
  st.error(analysis["Error"])
429
  else:
430
- st.json(analysis.get("basic_info", {}))
431
-
432
- st.subheader("Sections")
433
- sections_df = {
434
- "Name": [],
435
- "VirtualSize": [],
436
- "SizeOfRawData": [],
437
- "Entropy": []
438
- }
439
- for section in analysis.get("sections", []):
440
- sections_df["Name"].append(section["Name"])
441
- sections_df["VirtualSize"].append(section["VirtualSize"])
442
- sections_df["SizeOfRawData"].append(section["SizeOfRawData"])
443
- sections_df["Entropy"].append(section["Entropy"])
444
-
445
- st.dataframe(sections_df)
446
 
447
  with tabs[1]:
 
 
 
 
 
 
 
 
 
448
  st.subheader("Exported Functions")
449
  st.json(analysis.get("exports", []))
450
 
451
- with tabs[2]:
 
452
  for imp in analysis.get("imports", []):
453
  with st.expander(f"DLL: {imp['DLL']}"):
454
  st.code("\n".join(imp["Functions"]))
455
 
456
- with tabs[3]:
457
  st.subheader("Strings Found")
458
  all_strings = analysis.get("strings", [])
459
  interesting_strings = [s for s in all_strings if len(s) > 8] # Filter out very short strings
460
  st.code("\n".join(interesting_strings[:500])) # Limit to 500 strings
461
-
462
- with tabs[4]:
463
- st.subheader("Disassembly")
464
- if "disassembly" in analysis:
465
- st.code(analysis["disassembly"], language="asm")
466
- else:
467
- st.warning("Disassembly not available")
468
-
469
- if "pseudocode" in analysis and analysis["pseudocode"]:
470
- st.subheader("Decompiled Pseudocode")
471
- st.code(analysis["pseudocode"], language="c")
472
 
473
  elif uploaded_file.name.lower().endswith('.exe'):
474
  st.subheader("EXE File Analysis")
475
- with st.spinner("Reverse engineering executable..."):
476
  output_dir = os.path.join(temp_dir, "exe_unpacked")
477
  os.makedirs(output_dir, exist_ok=True)
478
 
@@ -482,115 +790,118 @@ if uploaded_file is not None:
482
  # Try Python extraction
483
  python_extraction = try_pyinstaller_extraction(file_path, output_dir)
484
 
485
- tabs = st.tabs(["Summary", "Imports", "Strings", "Assembly", "Python Code"])
486
 
487
  with tabs[0]:
488
  if "Error" in analysis:
489
  st.error(analysis["Error"])
490
  else:
491
- st.subheader("Basic Information")
492
- st.json(analysis.get("basic_info", {}))
493
-
494
- st.subheader("Sections")
495
- sections_df = {
496
- "Name": [],
497
- "VirtualSize": [],
498
- "SizeOfRawData": [],
499
- "Entropy": []
500
- }
501
- for section in analysis.get("sections", []):
502
- sections_df["Name"].append(section["Name"])
503
- sections_df["VirtualSize"].append(section["VirtualSize"])
504
- sections_df["SizeOfRawData"].append(section["SizeOfRawData"])
505
- sections_df["Entropy"].append(section["Entropy"])
506
-
507
- st.dataframe(sections_df)
508
 
509
  with tabs[1]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  st.subheader("Imported Functions")
511
  for imp in analysis.get("imports", []):
512
  with st.expander(f"DLL: {imp['DLL']}"):
513
  st.code("\n".join(imp["Functions"]))
514
 
515
- with tabs[2]:
516
  st.subheader("Strings Found")
517
  all_strings = analysis.get("strings", [])
518
  interesting_strings = [s for s in all_strings if len(s) > 8] # Filter out very short strings
519
  st.code("\n".join(interesting_strings[:500])) # Limit to 500 strings
520
-
521
- with tabs[3]:
522
- st.subheader("Disassembly")
523
- if "disassembly" in analysis:
524
- st.code(analysis["disassembly"], language="asm")
525
- else:
526
- st.warning("Disassembly not available")
527
-
528
- if "pseudocode" in analysis and analysis["pseudocode"]:
529
- st.subheader("Decompiled Pseudocode")
530
- st.code(analysis["pseudocode"], language="c")
531
-
532
- with tabs[4]:
533
- if python_extraction.get("success", False):
534
- st.success("Python code extracted successfully!")
535
- for filename, content in python_extraction.get("files", {}).items():
536
- with st.expander(f"Python File: {filename}"):
537
- st.code(content, language="python")
538
- else:
539
- st.warning(python_extraction.get("message", "Not a Python executable or extraction failed."))
540
 
541
  elif uploaded_file.name.lower().endswith('.dll'):
542
  st.subheader("DLL File Analysis")
543
- with st.spinner("Reverse engineering DLL..."):
544
  # Perform comprehensive analysis (with is_dll=True)
545
  analysis = analyze_binary(file_path, is_dll=True)
546
 
547
- tabs = st.tabs(["Summary", "Exports", "Imports", "Strings", "Assembly"])
548
 
549
  with tabs[0]:
550
  if "Error" in analysis:
551
  st.error(analysis["Error"])
552
  else:
553
- st.subheader("Basic Information")
554
- st.json(analysis.get("basic_info", {}))
555
-
556
- st.subheader("Sections")
557
- sections_df = {
558
- "Name": [],
559
- "VirtualSize": [],
560
- "SizeOfRawData": [],
561
- "Entropy": []
562
- }
563
- for section in analysis.get("sections", []):
564
- sections_df["Name"].append(section["Name"])
565
- sections_df["VirtualSize"].append(section["VirtualSize"])
566
- sections_df["SizeOfRawData"].append(section["SizeOfRawData"])
567
- sections_df["Entropy"].append(section["Entropy"])
568
-
569
- st.dataframe(sections_df)
570
 
571
  with tabs[1]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  st.subheader("Exported Functions")
573
  st.json(analysis.get("exports", []))
574
 
575
- with tabs[2]:
576
  st.subheader("Imported Functions")
577
  for imp in analysis.get("imports", []):
578
  with st.expander(f"DLL: {imp['DLL']}"):
579
  st.code("\n".join(imp["Functions"]))
580
 
581
- with tabs[3]:
582
  st.subheader("Strings Found")
583
  all_strings = analysis.get("strings", [])
584
  interesting_strings = [s for s in all_strings if len(s) > 8] # Filter out very short strings
585
- st.code("\n".join(interesting_strings[:500])) # Limit to 500 strings
586
-
587
- with tabs[4]:
588
- st.subheader("Disassembly")
589
- if "disassembly" in analysis:
590
- st.code(analysis["disassembly"], language="asm")
591
- else:
592
- st.warning("Disassembly not available")
593
-
594
- if "pseudocode" in analysis and analysis["pseudocode"]:
595
- st.subheader("Decompiled Pseudocode")
596
- st.code(analysis["pseudocode"], language="c")
 
9
  import json
10
  from pathlib import Path
11
 
12
+ st.set_page_config(page_title="Binary Decompiler", page_icon="🔍", layout="wide")
13
 
14
+ st.title("Executable Decompiler Tool")
15
  st.markdown("""
16
+ This tool decompiles executables to source code:
17
+ - Extracts assembly instructions from any .exe or .dll
18
+ - Converts assembly to Python-like or C++-like code
19
+ - Extracts embedded strings and resources
20
+ - Works with any executable regardless of original language
21
  """)
22
 
23
  # Install necessary packages at startup
24
  try:
25
+ with st.spinner("Setting up decompilation environment..."):
26
  # Install key analysis libraries
27
  subprocess.run(["pip", "install", "pyinstaller-extractor"], capture_output=True)
28
  subprocess.run(["pip", "install", "uncompyle6"], capture_output=True)
 
35
  st.success("Environment ready")
36
  except Exception as e:
37
  st.error(f"Setup error: {str(e)}")
38
+
39
  def extract_strings(file_path, min_length=4):
40
  """Extract ASCII and Unicode strings from binary file"""
41
  try:
 
56
  except Exception as e:
57
  return [f"Error extracting strings: {str(e)}"]
58
 
59
+ def assembly_to_python(assembly_lines):
60
+ """Convert assembly code to Python-like code"""
61
+ python_code = []
62
+ python_code.append("# Python code decompiled from assembly")
63
+ python_code.append("# This is an approximation of the original code")
64
+ python_code.append("")
65
+
66
+ # Track variables and functions
67
+ variables = set()
68
+ functions = set()
69
+ current_function = None
70
+ indentation = 0
71
+ in_loop = False
72
+ in_condition = False
73
+
74
+ i = 0
75
+ while i < len(assembly_lines):
76
+ line = assembly_lines[i]
 
 
 
 
77
 
78
+ # Parse assembly line
79
+ if ": " in line:
80
+ parts = line.split(": ", 1)
81
+ if len(parts) > 1:
82
+ addr, instruction = parts
83
+
84
+ # Function start detection
85
+ if "push ebp" in instruction and "mov ebp, esp" in assembly_lines[i+1] if i+1 < len(assembly_lines) else "":
86
+ current_function = f"function_{addr}"
87
+ functions.add(current_function)
88
+ python_code.append(f"\ndef {current_function}():")
89
+ indentation = 1
90
+ i += 2 # Skip the function prologue
91
+ continue
92
+
93
+ # Function return detection
94
+ if "ret" in instruction and indentation > 0:
95
+ python_code.append(f"{' ' * 4 * indentation}return")
96
+ indentation = 0
97
+ current_function = None
98
+
99
+ # Call instruction - function call
100
+ if "call" in instruction:
101
+ target = instruction.split("call")[1].strip()
102
+ if target.startswith("0x"):
103
+ called_func = f"function_{target}"
104
+ functions.add(called_func)
105
+ python_code.append(f"{' ' * 4 * indentation}{called_func}()")
106
+ else:
107
+ python_code.append(f"{' ' * 4 * indentation}# Call to external function: {target}")
108
+
109
+ # Variable assignment (mov)
110
+ elif "mov" in instruction:
111
+ dest, source = instruction.split("mov")[1].split(",", 1)
112
+ dest = dest.strip()
113
+ source = source.strip()
114
+
115
+ if dest not in variables:
116
+ variables.add(dest)
117
+
118
+ python_code.append(f"{' ' * 4 * indentation}{dest} = {source}")
119
+
120
+ # Comparison and jumps (if statements)
121
+ elif "cmp" in instruction:
122
+ parts = instruction.split("cmp")[1].split(",", 1)
123
+ if len(parts) > 1:
124
+ a, b = parts
125
+ a = a.strip()
126
+ b = b.strip()
127
+
128
+ # Look ahead for jump instruction
129
+ next_line = assembly_lines[i+1] if i+1 < len(assembly_lines) else ""
130
+ if "j" in next_line: # Any jump instruction
131
+ jump_type = next_line.split(": ")[1].split()[0] if ": " in next_line else ""
132
+
133
+ if jump_type == "je" or jump_type == "jz":
134
+ python_code.append(f"{' ' * 4 * indentation}if {a} == {b}:")
135
+ elif jump_type == "jne" or jump_type == "jnz":
136
+ python_code.append(f"{' ' * 4 * indentation}if {a} != {b}:")
137
+ elif jump_type == "jg" or jump_type == "jnle":
138
+ python_code.append(f"{' ' * 4 * indentation}if {a} > {b}:")
139
+ elif jump_type == "jge" or jump_type == "jnl":
140
+ python_code.append(f"{' ' * 4 * indentation}if {a} >= {b}:")
141
+ elif jump_type == "jl" or jump_type == "jnge":
142
+ python_code.append(f"{' ' * 4 * indentation}if {a} < {b}:")
143
+ elif jump_type == "jle" or jump_type == "jng":
144
+ python_code.append(f"{' ' * 4 * indentation}if {a} <= {b}:")
145
+ else:
146
+ python_code.append(f"{' ' * 4 * indentation}# Comparison: {a} ? {b}")
147
+
148
+ indentation += 1
149
+ in_condition = True
150
+ i += 1 # Skip the jump instruction
151
+ continue
152
+
153
+ # Loop detection (simplified)
154
+ elif "loop" in instruction or "jmp" in instruction:
155
+ if not in_loop:
156
+ python_code.append(f"{' ' * 4 * indentation}while True: # Loop at {addr}")
157
+ indentation += 1
158
+ in_loop = True
159
+ else:
160
+ python_code.append(f"{' ' * 4 * indentation}# Jump or loop at {addr}")
161
+
162
+ # Add/sub operations
163
+ elif "add" in instruction:
164
+ parts = instruction.split("add")[1].split(",", 1)
165
+ if len(parts) > 1:
166
+ dest, value = parts
167
+ dest = dest.strip()
168
+ value = value.strip()
169
+ python_code.append(f"{' ' * 4 * indentation}{dest} += {value}")
170
+
171
+ elif "sub" in instruction:
172
+ parts = instruction.split("sub")[1].split(",", 1)
173
+ if len(parts) > 1:
174
+ dest, value = parts
175
+ dest = dest.strip()
176
+ value = value.strip()
177
+ python_code.append(f"{' ' * 4 * indentation}{dest} -= {value}")
178
+
179
+ # Other arithmetic
180
+ elif "mul" in instruction:
181
+ operand = instruction.split("mul")[1].strip()
182
+ python_code.append(f"{' ' * 4 * indentation}# Multiply by {operand}")
183
+
184
+ elif "div" in instruction:
185
+ operand = instruction.split("div")[1].strip()
186
+ python_code.append(f"{' ' * 4 * indentation}# Divide by {operand}")
187
+
188
+ # Default case - just comment the assembly
189
+ else:
190
+ python_code.append(f"{' ' * 4 * indentation}# {instruction}")
191
 
192
+ i += 1
193
+
194
+ # Add main execution
195
+ python_code.append("\nif __name__ == '__main__':")
196
+ if functions:
197
+ first_function = next(iter(functions))
198
+ python_code.append(f" {first_function}()")
199
+ else:
200
+ python_code.append(" pass # No clear entry point found")
201
+
202
+ return "\n".join(python_code)
203
+
204
+ def assembly_to_cpp(assembly_lines):
205
+ """Convert assembly code to C++-like code"""
206
+ cpp_code = []
207
+ cpp_code.append("// C++ code decompiled from assembly")
208
+ cpp_code.append("// This is an approximation of the original code")
209
+ cpp_code.append("")
210
+ cpp_code.append("#include <iostream>")
211
+ cpp_code.append("#include <vector>")
212
+ cpp_code.append("#include <string>")
213
+ cpp_code.append("")
214
+
215
+ # Track variables and functions
216
+ variables = set()
217
+ functions = set()
218
+ current_function = None
219
+ indentation = 0
220
+ in_loop = False
221
+ in_condition = False
222
+
223
+ # Add forward declarations
224
+ cpp_code.append("// Forward declarations")
225
+
226
+ i = 0
227
+ # First pass to identify functions
228
+ while i < len(assembly_lines):
229
+ line = assembly_lines[i]
230
 
231
+ # Parse assembly line
232
+ if ": " in line:
233
+ parts = line.split(": ", 1)
234
+ if len(parts) > 1:
235
+ addr, instruction = parts
236
+
237
+ # Function start detection
238
+ if "push ebp" in instruction and "mov ebp, esp" in assembly_lines[i+1] if i+1 < len(assembly_lines) else "":
239
+ func_name = f"function_{addr.replace('0x', '')}"
240
+ functions.add(func_name)
241
+ cpp_code.append(f"void {func_name}();")
242
 
243
+ i += 1
244
+
245
+ cpp_code.append("")
246
+ cpp_code.append("// Variable declarations")
247
+ cpp_code.append("int eax, ebx, ecx, edx, esi, edi, ebp, esp;")
248
+ cpp_code.append("")
249
+
250
+ # Second pass to generate function code
251
+ i = 0
252
+ while i < len(assembly_lines):
253
+ line = assembly_lines[i]
254
 
255
+ # Parse assembly line
256
+ if ": " in line:
257
+ parts = line.split(": ", 1)
258
+ if len(parts) > 1:
259
+ addr, instruction = parts
260
+
261
+ # Function start detection
262
+ if "push ebp" in instruction and "mov ebp, esp" in assembly_lines[i+1] if i+1 < len(assembly_lines) else "":
263
+ current_function = f"function_{addr.replace('0x', '')}"
264
+ cpp_code.append(f"\nvoid {current_function}() {{")
265
+ indentation = 1
266
+ i += 2 # Skip the function prologue
267
+ continue
268
+
269
+ # Function return detection
270
+ if "ret" in instruction and indentation > 0:
271
+ cpp_code.append(f"{' ' * 4 * indentation}return;")
272
+ cpp_code.append("}")
273
+ indentation = 0
274
+ current_function = None
275
+
276
+ # Call instruction - function call
277
+ if "call" in instruction:
278
+ target = instruction.split("call")[1].strip()
279
+ if target.startswith("0x"):
280
+ called_func = f"function_{target.replace('0x', '')}"
281
+ cpp_code.append(f"{' ' * 4 * indentation}{called_func}();")
282
+ else:
283
+ cpp_code.append(f"{' ' * 4 * indentation}// Call to external function: {target}")
284
+
285
+ # Variable assignment (mov)
286
+ elif "mov" in instruction:
287
+ dest, source = instruction.split("mov")[1].split(",", 1)
288
+ dest = dest.strip()
289
+ source = source.strip()
290
+
291
+ # Check if memory access
292
+ if "[" in dest:
293
+ cpp_code.append(f"{' ' * 4 * indentation}// Memory write to {dest}")
294
+ elif "[" in source:
295
+ cpp_code.append(f"{' ' * 4 * indentation}// Memory read from {source}")
296
+ else:
297
+ cpp_code.append(f"{' ' * 4 * indentation}{dest} = {source};")
298
+
299
+ # Comparison and jumps (if statements)
300
+ elif "cmp" in instruction:
301
+ parts = instruction.split("cmp")[1].split(",", 1)
302
+ if len(parts) > 1:
303
+ a, b = parts
304
+ a = a.strip()
305
+ b = b.strip()
306
+
307
+ # Look ahead for jump instruction
308
+ next_line = assembly_lines[i+1] if i+1 < len(assembly_lines) else ""
309
+ if "j" in next_line: # Any jump instruction
310
+ jump_type = next_line.split(": ")[1].split()[0] if ": " in next_line else ""
311
+
312
+ if jump_type == "je" or jump_type == "jz":
313
+ cpp_code.append(f"{' ' * 4 * indentation}if ({a} == {b}) {{")
314
+ elif jump_type == "jne" or jump_type == "jnz":
315
+ cpp_code.append(f"{' ' * 4 * indentation}if ({a} != {b}) {{")
316
+ elif jump_type == "jg" or jump_type == "jnle":
317
+ cpp_code.append(f"{' ' * 4 * indentation}if ({a} > {b}) {{")
318
+ elif jump_type == "jge" or jump_type == "jnl":
319
+ cpp_code.append(f"{' ' * 4 * indentation}if ({a} >= {b}) {{")
320
+ elif jump_type == "jl" or jump_type == "jnge":
321
+ cpp_code.append(f"{' ' * 4 * indentation}if ({a} < {b}) {{")
322
+ elif jump_type == "jle" or jump_type == "jng":
323
+ cpp_code.append(f"{' ' * 4 * indentation}if ({a} <= {b}) {{")
324
+ else:
325
+ cpp_code.append(f"{' ' * 4 * indentation}// Comparison: {a} ? {b}")
326
+
327
+ indentation += 1
328
+ in_condition = True
329
+ i += 1 # Skip the jump instruction
330
+ continue
331
+
332
+ # Loop detection (simplified)
333
+ elif "loop" in instruction or "jmp" in instruction:
334
+ if not in_loop:
335
+ cpp_code.append(f"{' ' * 4 * indentation}while (true) {{ // Loop at {addr}")
336
+ indentation += 1
337
+ in_loop = True
338
+ else:
339
+ cpp_code.append(f"{' ' * 4 * indentation}// Jump or loop at {addr}")
340
+
341
+ # Add/sub operations
342
+ elif "add" in instruction:
343
+ parts = instruction.split("add")[1].split(",", 1)
344
+ if len(parts) > 1:
345
+ dest, value = parts
346
+ dest = dest.strip()
347
+ value = value.strip()
348
+ cpp_code.append(f"{' ' * 4 * indentation}{dest} += {value};")
349
+
350
+ elif "sub" in instruction:
351
+ parts = instruction.split("sub")[1].split(",", 1)
352
+ if len(parts) > 1:
353
+ dest, value = parts
354
+ dest = dest.strip()
355
+ value = value.strip()
356
+ cpp_code.append(f"{' ' * 4 * indentation}{dest} -= {value};")
357
+
358
+ # Other arithmetic
359
+ elif "mul" in instruction:
360
+ operand = instruction.split("mul")[1].strip()
361
+ cpp_code.append(f"{' ' * 4 * indentation}// Multiply by {operand}")
362
+
363
+ elif "div" in instruction:
364
+ operand = instruction.split("div")[1].strip()
365
+ cpp_code.append(f"{' ' * 4 * indentation}// Divide by {operand}")
366
+
367
+ # Default case - just comment the assembly
368
+ else:
369
+ cpp_code.append(f"{' ' * 4 * indentation}// {instruction}")
370
 
371
+ i += 1
372
+
373
+ # Add main function
374
+ cpp_code.append("\nint main() {")
375
+ if functions:
376
+ first_function = next(iter(functions))
377
+ cpp_code.append(f" {first_function}();")
378
+ else:
379
+ cpp_code.append(" // No clear entry point found")
380
+ cpp_code.append(" return 0;")
381
+ cpp_code.append("}")
382
+
383
+ return "\n".join(cpp_code)
384
 
385
  def try_pyinstaller_extraction(file_path, output_dir):
386
  """Attempt to extract Python scripts from PyInstaller executables"""
387
  try:
388
+ # Run pyinstaller-extractor on the file
389
+ # Try both potential command names
390
+ try:
391
+ result = subprocess.run(["python", "-m", "pyinstxtractor", file_path],
392
+ cwd=output_dir, capture_output=True, text=True)
393
+ except:
394
+ try:
395
+ result = subprocess.run(["python", "-m", "pyinstaller_extractor", file_path],
396
+ cwd=output_dir, capture_output=True, text=True)
397
+ except:
398
+ # Direct command attempt
399
+ result = subprocess.run(["pyinstxtractor", file_path],
400
+ cwd=output_dir, capture_output=True, text=True)
401
 
402
  extracted_dir = os.path.join(output_dir, os.path.basename(file_path) + "_extracted")
403
 
404
+ if not os.path.exists(extracted_dir):
405
+ # Try with different naming convention
406
+ potential_dirs = [d for d in os.listdir(output_dir) if os.path.isdir(os.path.join(output_dir, d)) and "_extracted" in d]
407
+ if potential_dirs:
408
+ extracted_dir = os.path.join(output_dir, potential_dirs[0])
409
+
410
  if os.path.exists(extracted_dir):
411
  # Try to decompile the Python bytecode files
412
  python_files = {}
 
435
  pyz_extract_dir = pyz_path + "_extracted"
436
  os.makedirs(pyz_extract_dir, exist_ok=True)
437
  try:
438
+ # Try different extraction methods for the PYZ
439
+ try:
440
+ subprocess.run(["python", "-m", "pyinstxtractor", pyz_path],
441
+ cwd=output_dir, capture_output=True)
442
+ except:
443
+ try:
444
+ subprocess.run(["python", "-m", "pyinstaller_extractor", pyz_path],
445
+ cwd=output_dir, capture_output=True)
446
+ except:
447
+ # Direct command attempt
448
+ subprocess.run(["pyinstxtractor", pyz_path],
449
+ cwd=output_dir, capture_output=True)
450
+
451
+ # Look for extracted PYZ content
452
+ if os.path.exists(pyz_extract_dir):
453
+ for pyz_root, _, pyz_files in os.walk(pyz_extract_dir):
454
+ for pyz_file in pyz_files:
455
+ if pyz_file.endswith('.pyc') or pyz_file.endswith('.pyo'):
456
+ pyc_path = os.path.join(pyz_root, pyz_file)
457
+ py_path = pyc_path + ".py"
458
+ try:
459
+ subprocess.run(["uncompyle6", pyc_path, "-o", py_path], capture_output=True)
460
+ if os.path.exists(py_path):
461
+ with open(py_path, 'r', encoding='utf-8', errors='ignore') as f:
462
+ rel_path = os.path.join("PYZ_ARCHIVE", os.path.relpath(pyc_path, pyz_extract_dir))
463
+ python_files[rel_path] = f.read()
464
+ except:
465
+ pass
466
  except:
467
  pass
468
 
 
481
  "message": f"PyInstaller extraction error: {str(e)}"
482
  }
483
 
484
+ def disassemble_binary(file_path, is_dll=False):
485
+ """Disassemble a binary file to get assembly code"""
486
+ try:
487
+ # Try with radare2 first
488
+ try:
489
+ import r2pipe
490
+ r2 = r2pipe.open(file_path)
491
+ r2.cmd("aaa") # Analyze all
492
+
493
+ # Get main or entry point disassembly
494
+ main_disasm = r2.cmd("s main; pdf")
495
+ if not main_disasm or "Cannot find function" in main_disasm:
496
+ main_disasm = r2.cmd("s entry0; pdf") # Try entry point instead
497
+
498
+ # Get list of functions
499
+ functions = r2.cmd("afl")
500
+
501
+ # Get all functions disassembly for more complete code
502
+ all_functions_disasm = []
503
+ function_addresses = re.findall(r'0x[0-9a-fA-F]+', functions)
504
+ for addr in function_addresses[:10]: # Limit to first 10 functions to avoid huge output
505
+ func_disasm = r2.cmd(f"s {addr}; pdf")
506
+ all_functions_disasm.append(func_disasm)
507
+
508
+ r2.quit()
509
+
510
+ # Extract assembly instructions
511
+ assembly_lines = []
512
+ for disasm in [main_disasm] + all_functions_disasm:
513
+ for line in disasm.splitlines():
514
+ if "│" in line: # radare2 format contains this separator
515
+ parts = line.split("│")
516
+ if len(parts) > 1:
517
+ addr_part = parts[0].strip()
518
+ instr_part = parts[-1].strip()
519
+ if addr_part and instr_part and "0x" in addr_part:
520
+ address = addr_part.strip()
521
+ instruction = instr_part.strip()
522
+ assembly_lines.append(f"{address}: {instruction}")
523
+
524
+ return assembly_lines
525
+ except:
526
+ # Fallback to pefile + capstone if radare2 fails
527
+ pe = pefile.PE(file_path)
528
+
529
+ # Determine if 32-bit or 64-bit
530
+ is_64bit = pe.OPTIONAL_HEADER.Magic == 0x20b
531
+ mode = CS_MODE_64 if is_64bit else CS_MODE_32
532
+
533
+ # Initialize disassembler
534
+ md = Cs(CS_ARCH_X86, mode)
535
+ md.detail = True
536
+
537
+ assembly_lines = []
538
+
539
+ # Find and disassemble code sections
540
+ for section in pe.sections:
541
+ if section.Characteristics & 0x20000000: # IMAGE_SCN_CNT_CODE
542
+ section_data = pe.get_data(section.VirtualAddress, section.SizeOfRawData)
543
+ section_addr = pe.OPTIONAL_HEADER.ImageBase + section.VirtualAddress
544
+
545
+ # Disassemble section code
546
+ for i, (address, size, mnemonic, op_str) in enumerate(md.disasm_lite(section_data, section_addr)):
547
+ if i >= 500: # Limit to 500 instructions per section
548
+ break
549
+ assembly_lines.append(f"0x{address:08x}: {mnemonic} {op_str}")
550
+
551
+ return assembly_lines
552
+ except Exception as e:
553
+ return [f"Disassembly error: {str(e)}"]
554
+
555
  def analyze_binary(file_path, is_dll=False):
556
+ """Comprehensive analysis and decompilation of a binary file"""
557
  try:
558
  results = {}
559
 
 
612
  # Extract strings
613
  results["strings"] = extract_strings(file_path)
614
 
615
+ # Disassemble to get assembly
616
+ assembly_lines = disassemble_binary(file_path, is_dll)
617
+ results["assembly_lines"] = assembly_lines
618
+
619
+ # Convert assembly to Python
620
+ results["python_code"] = assembly_to_python(assembly_lines)
621
+
622
+ # Convert assembly to C++
623
+ results["cpp_code"] = assembly_to_cpp(assembly_lines)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
 
625
  return results
626
  except Exception as e:
 
704
  analysis = file_info['analysis']
705
  python_extraction = file_info['python_extraction']
706
 
707
+ tabs = st.tabs(["Python Code", "C++ Code", "Assembly", "Imports", "Strings"])
708
 
709
  with tabs[0]:
710
  if "Error" in analysis:
711
  st.error(analysis["Error"])
712
  else:
713
+ # Check if we have extracted Python code
714
+ if python_extraction.get("success", False):
715
+ st.success("Original Python code extracted successfully!")
716
+ for filename, content in python_extraction.get("files", {}).items():
717
+ with st.expander(f"Python File: {filename}"):
718
+ st.code(content, language="python")
719
+ else:
720
+ # Show decompiled Python from assembly
721
+ st.warning("Converting assembly to Python code (not original source)")
722
+ st.code(analysis.get("python_code", "# Failed to generate Python code"), language="python")
 
 
 
 
 
 
723
 
724
  with tabs[1]:
725
+ st.subheader("Decompiled C++ Code")
726
+ st.code(analysis.get("cpp_code", "// Failed to generate C++ code"), language="cpp")
727
+
728
+ with tabs[2]:
729
+ st.subheader("Assembly Code")
730
+ assembly = "\n".join(analysis.get("assembly_lines", []))
731
+ st.code(assembly, language="asm")
732
+
733
+ with tabs[3]:
734
+ st.subheader("Imported Functions")
735
  for imp in analysis.get("imports", []):
736
  with st.expander(f"DLL: {imp['DLL']}"):
737
  st.code("\n".join(imp["Functions"]))
738
 
739
+ with tabs[4]:
740
  st.subheader("Strings Found")
741
  all_strings = analysis.get("strings", [])
742
  interesting_strings = [s for s in all_strings if len(s) > 8] # Filter out very short strings
743
  st.code("\n".join(interesting_strings[:500])) # Limit to 500 strings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
  else: # DLL
745
  analysis = file_info['analysis']
746
 
747
+ tabs = st.tabs(["Python Code", "C++ Code", "Assembly", "Exports", "Imports", "Strings"])
748
 
749
  with tabs[0]:
750
  if "Error" in analysis:
751
  st.error(analysis["Error"])
752
  else:
753
+ st.subheader("Decompiled Python Code")
754
+ st.code(analysis.get("python_code", "# Failed to generate Python code"), language="python")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
755
 
756
  with tabs[1]:
757
+ st.subheader("Decompiled C++ Code")
758
+ st.code(analysis.get("cpp_code", "// Failed to generate C++ code"), language="cpp")
759
+
760
+ with tabs[2]:
761
+ st.subheader("Assembly Code")
762
+ assembly = "\n".join(analysis.get("assembly_lines", []))
763
+ st.code(assembly, language="asm")
764
+
765
+ with tabs[3]:
766
  st.subheader("Exported Functions")
767
  st.json(analysis.get("exports", []))
768
 
769
+ with tabs[4]:
770
+ st.subheader("Imported Functions")
771
  for imp in analysis.get("imports", []):
772
  with st.expander(f"DLL: {imp['DLL']}"):
773
  st.code("\n".join(imp["Functions"]))
774
 
775
+ with tabs[5]:
776
  st.subheader("Strings Found")
777
  all_strings = analysis.get("strings", [])
778
  interesting_strings = [s for s in all_strings if len(s) > 8] # Filter out very short strings
779
  st.code("\n".join(interesting_strings[:500])) # Limit to 500 strings
 
 
 
 
 
 
 
 
 
 
 
780
 
781
  elif uploaded_file.name.lower().endswith('.exe'):
782
  st.subheader("EXE File Analysis")
783
+ with st.spinner("Decompiling executable..."):
784
  output_dir = os.path.join(temp_dir, "exe_unpacked")
785
  os.makedirs(output_dir, exist_ok=True)
786
 
 
790
  # Try Python extraction
791
  python_extraction = try_pyinstaller_extraction(file_path, output_dir)
792
 
793
+ tabs = st.tabs(["Python Code", "C++ Code", "Assembly", "Summary", "Imports", "Strings"])
794
 
795
  with tabs[0]:
796
  if "Error" in analysis:
797
  st.error(analysis["Error"])
798
  else:
799
+ # Check if we have extracted Python code
800
+ if python_extraction.get("success", False):
801
+ st.success("Original Python code extracted successfully!")
802
+ for filename, content in python_extraction.get("files", {}).items():
803
+ with st.expander(f"Python File: {filename}"):
804
+ st.code(content, language="python")
805
+ else:
806
+ # Show decompiled Python from assembly
807
+ st.warning("Converting assembly to Python code (not original source)")
808
+ st.code(analysis.get("python_code", "# Failed to generate Python code"), language="python")
 
 
 
 
 
 
 
809
 
810
  with tabs[1]:
811
+ st.subheader("Decompiled C++ Code")
812
+ st.code(analysis.get("cpp_code", "// Failed to generate C++ code"), language="cpp")
813
+
814
+ with tabs[2]:
815
+ st.subheader("Assembly Code")
816
+ assembly = "\n".join(analysis.get("assembly_lines", []))
817
+ st.code(assembly, language="asm")
818
+
819
+ with tabs[3]:
820
+ st.subheader("Basic Information")
821
+ st.json(analysis.get("basic_info", {}))
822
+
823
+ st.subheader("Sections")
824
+ sections_df = {
825
+ "Name": [],
826
+ "VirtualSize": [],
827
+ "SizeOfRawData": [],
828
+ "Entropy": []
829
+ }
830
+ for section in analysis.get("sections", []):
831
+ sections_df["Name"].append(section["Name"])
832
+ sections_df["VirtualSize"].append(section["VirtualSize"])
833
+ sections_df["SizeOfRawData"].append(section["SizeOfRawData"])
834
+ sections_df["Entropy"].append(section["Entropy"])
835
+
836
+ st.dataframe(sections_df)
837
+
838
+ with tabs[4]:
839
  st.subheader("Imported Functions")
840
  for imp in analysis.get("imports", []):
841
  with st.expander(f"DLL: {imp['DLL']}"):
842
  st.code("\n".join(imp["Functions"]))
843
 
844
+ with tabs[5]:
845
  st.subheader("Strings Found")
846
  all_strings = analysis.get("strings", [])
847
  interesting_strings = [s for s in all_strings if len(s) > 8] # Filter out very short strings
848
  st.code("\n".join(interesting_strings[:500])) # Limit to 500 strings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
849
 
850
  elif uploaded_file.name.lower().endswith('.dll'):
851
  st.subheader("DLL File Analysis")
852
+ with st.spinner("Decompiling DLL..."):
853
  # Perform comprehensive analysis (with is_dll=True)
854
  analysis = analyze_binary(file_path, is_dll=True)
855
 
856
+ tabs = st.tabs(["Python Code", "C++ Code", "Assembly", "Summary", "Exports", "Imports", "Strings"])
857
 
858
  with tabs[0]:
859
  if "Error" in analysis:
860
  st.error(analysis["Error"])
861
  else:
862
+ st.subheader("Decompiled Python Code")
863
+ st.code(analysis.get("python_code", "# Failed to generate Python code"), language="python")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
864
 
865
  with tabs[1]:
866
+ st.subheader("Decompiled C++ Code")
867
+ st.code(analysis.get("cpp_code", "// Failed to generate C++ code"), language="cpp")
868
+
869
+ with tabs[2]:
870
+ st.subheader("Assembly Code")
871
+ assembly = "\n".join(analysis.get("assembly_lines", []))
872
+ st.code(assembly, language="asm")
873
+
874
+ with tabs[3]:
875
+ st.subheader("Basic Information")
876
+ st.json(analysis.get("basic_info", {}))
877
+
878
+ st.subheader("Sections")
879
+ sections_df = {
880
+ "Name": [],
881
+ "VirtualSize": [],
882
+ "SizeOfRawData": [],
883
+ "Entropy": []
884
+ }
885
+ for section in analysis.get("sections", []):
886
+ sections_df["Name"].append(section["Name"])
887
+ sections_df["VirtualSize"].append(section["VirtualSize"])
888
+ sections_df["SizeOfRawData"].append(section["SizeOfRawData"])
889
+ sections_df["Entropy"].append(section["Entropy"])
890
+
891
+ st.dataframe(sections_df)
892
+
893
+ with tabs[4]:
894
  st.subheader("Exported Functions")
895
  st.json(analysis.get("exports", []))
896
 
897
+ with tabs[5]:
898
  st.subheader("Imported Functions")
899
  for imp in analysis.get("imports", []):
900
  with st.expander(f"DLL: {imp['DLL']}"):
901
  st.code("\n".join(imp["Functions"]))
902
 
903
+ with tabs[6]:
904
  st.subheader("Strings Found")
905
  all_strings = analysis.get("strings", [])
906
  interesting_strings = [s for s in all_strings if len(s) > 8] # Filter out very short strings
907
+ st.code("\n".join(interesting_strings[:500])) # Limit to 500 strings