Skip to content

Commit f4446df

Browse files
authored
Update help message and version number to 1.1.3
**pSlip v1.1.3** introduces a cleaner CLI, a modernized HTML report, and several reliability improvements. ### Unified CLI Scanning is now controlled by only two flags: * `-all` – full analysis * `-allsafe` – full analysis without AES/JADX Legacy flags have been removed from the user interface for simplicity. ### Improved AES Handling * Duplicate findings removed * More stable timeouts * Cleaner grouping and component paths ### Updated HTML Report * New layout with improved spacing and typography * Responsive Findings Index (table → mobile cards) * Category summaries for Hardening, Exposure, Crypto, JS Injection, Redirects, Permissions, Tapjacking * Consistent severity styling ### Updated Severity Scoring Severity model aligned with Android 15 behavior. ### General Fixes * Unified scan pipeline (one pass for all categories) * More reliable ADB PoC formatting * Cleaner component name rendering * Stability improvements for large directory sweeps
1 parent 71db007 commit f4446df

1 file changed

Lines changed: 129 additions & 117 deletions

File tree

pSlip.py

Lines changed: 129 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -44,32 +44,30 @@
4444
██║ ███████║███████╗██║██║
4545
╚═╝ ╚══════╝╚═╝╚═╝
4646
{RESET}{BOLD}
47-
Version 1.1.2 | Github.com/Actuator/pSlip
47+
Version 1.1.3 | Github.com/Actuator/pSlip
4848
{RESET}
4949
"""
5050

5151
def print_help():
5252
print(BANNER)
5353
print(textwrap.dedent(f"""\
54-
{BOLD}Usage:{RESET} python pSlip.py <apk_file or directory> [-p] [-js] [-call] [-aes] [-all] [-allsafe] [-html <output_file>]
55-
56-
{BOLD}Options:{RESET}
57-
-h, --help Show this help message and exit
58-
-p List all permissions requested by the application
59-
-perm Scan for custom permissions that are set to a 'normal' protection level
60-
-js Scan for explicit JavaScript injection vulnerabilities
61-
-call Scan for components with exposed CALL permissions
62-
-aes Scan for hardcoded AES/DES keys and IVs
63-
-taptrap Scan for tapjacking risk (obscured touch defenses)
64-
-json <file> Output the vulnerability details to a JSON file
65-
-all Scan for all of the vulnerabilities listed above
66-
-allsafe Skip AES/DES key detection for faster scans and mitigate decompilation issues
67-
-html <file> Output the vulnerability details to an HTML file
68-
69-
{BOLD}Note:{RESET} Basic manifest hardening checks (allowBackup, debuggable,
70-
cleartext traffic, exposed providers) are always enabled.
54+
{BOLD}Usage:{RESET} python pSlip.py <apk_file or directory> [-all] [-allsafe] [-html <output_file>] [-json <output_file>]
55+
56+
{BOLD}Scan Modes:{RESET}
57+
-all Run full analysis, including AES/DES key scanning
58+
-allsafe Run full analysis but skip AES/DES key scanning (faster & safer)
59+
60+
{BOLD}Output Options:{RESET}
61+
-html <file> Save the vulnerability report as an HTML file
62+
-json <file> Save the vulnerability report as a JSON file
63+
64+
{BOLD}Notes:{RESET}
65+
• Basic manifest hardening checks (allowBackup, debuggable, cleartextTraffic,
66+
exported components, etc.) are always enabled.
67+
• Version 1.1.2 uses a unified scanning model for consistent results.
7168
"""))
7269

70+
7371
def command_exists(command):
7472
return shutil.which(command) is not None
7573

@@ -86,7 +84,7 @@ def _has_inline_call_gate(elem):
8684

8785
def check_manifest_hardening(root, package_name, target_sdk_version):
8886
"""
89-
Perform cheap manifest-level hardening checks.
87+
perform cheap manifest-level hardening checks.
9088
9189
This runs by default (no CLI flag) because it is effectively free compared
9290
to bytecode/AES scanning and only walks the already-parsed manifest tree.
@@ -1662,17 +1660,17 @@ def generate_html_report(vulnerabilities, permissions, output_file):
16621660
<div class="pkg-sub">
16631661
Tapjacking Risk:
16641662
<span class='sev sev-{R['headline'].lower()}'>{R['headline']}</span>
1665-
(Score: {R['score']}/100)
1663+
16661664
</div>
1667-
<div class="pkg-sub">
1665+
<!---- <div class="pkg-sub">
16681666
Counts —
16691667
Critical: {counts['Critical']}
16701668
| High: {counts['High']}
16711669
| Medium: {counts['Medium']}
16721670
| Low: {counts['Low']}
16731671
| Info: {counts['Info']}
16741672
| Total: {counts['Total']}
1675-
</div>
1673+
</div> <-------!>
16761674
</div>
16771675
"""
16781676

@@ -2341,10 +2339,26 @@ def run_aes_with_timeout(apk_file, pkg_name, timeout_seconds):
23412339
return []
23422340

23432341

2344-
23452342
def main():
23462343
global check_aes
23472344
start_time = datetime.now()
2345+
2346+
# ------------------------------------------------------------------
2347+
# Ensure ALL expected flag variables ALWAYS exist (prevents NameError)
2348+
# ------------------------------------------------------------------
2349+
list_permissions_flag = False
2350+
check_js = False
2351+
check_call = False
2352+
check_taptrap = False
2353+
collect_permission_vulns = False
2354+
check_aes = False
2355+
html_output = None
2356+
json_output = None
2357+
csv_output = None
2358+
aes_timeout_minutes = 5
2359+
# ------------------------------------------------------------------
2360+
2361+
# Must have at least APK argument
23482362
if len(sys.argv) < 2:
23492363
print_help()
23502364
sys.exit(1)
@@ -2354,114 +2368,107 @@ def main():
23542368
print_help()
23552369
sys.exit(0)
23562370

2357-
list_permissions_flag = False
2358-
check_js = False
2359-
check_call = False
2360-
check_aes = False
2361-
check_taptrap = False
2362-
html_output = None
2363-
aes_timeout_minutes = 5
2364-
csv_output = None
2365-
json_output = None
2366-
collect_permission_vulns = False
2367-
html_output = None
2371+
# ------------------------------------------------------------------
2372+
# Unified scanning model: only -all and -allsafe matter
2373+
# ------------------------------------------------------------------
2374+
2375+
# Default mode → full scan
2376+
effective_mode = "all"
23682377

2378+
if "-allsafe" in sys.argv:
2379+
effective_mode = "allsafe"
2380+
elif "-all" in sys.argv:
2381+
effective_mode = "all"
2382+
2383+
# Apply unified mode
2384+
if effective_mode == "all":
2385+
check_js = True
2386+
check_call = True
2387+
check_taptrap = True
2388+
collect_permission_vulns = True
2389+
list_permissions_flag = True
2390+
check_aes = True
2391+
2392+
elif effective_mode == "allsafe":
2393+
check_js = True
2394+
check_call = True
2395+
check_taptrap = True
2396+
collect_permission_vulns = True
2397+
list_permissions_flag = True
2398+
check_aes = False
2399+
2400+
# ------------------------------------------------------------------
2401+
# Output flags (-html, -json, -aes-timeout)
2402+
# ------------------------------------------------------------------
23692403
options = sys.argv[2:]
23702404
skip_next = False
2405+
23712406
for i, option in enumerate(options):
2407+
23722408
if skip_next:
23732409
skip_next = False
23742410
continue
23752411

2376-
if option == '-p':
2377-
list_permissions_flag = True
2378-
elif option == '-js':
2379-
check_js = True
2380-
elif option == '-call':
2381-
check_call = True
2382-
elif option == '-aes':
2383-
check_aes = True
2384-
elif option == '-taptrap':
2385-
check_taptrap = True
2386-
elif option == '-perm':
2387-
collect_permission_vulns = True
2388-
elif option == '-all':
2389-
check_js = True
2390-
check_call = True
2391-
check_aes = True
2392-
collect_permission_vulns = True
2393-
check_taptrap = True
2394-
elif option == '-allsafe':
2395-
check_js = True
2396-
check_call = True
2397-
collect_permission_vulns = True
2398-
check_taptrap = True
2399-
#Disable AES scan for speed and safety
2400-
check_aes = False
2401-
continue
2402-
2403-
2404-
2405-
elif option == '-html':
2412+
if option == "-html":
24062413
if i + 1 < len(options):
24072414
html_output = options[i + 1]
24082415
skip_next = True
2416+
continue
24092417
else:
2410-
print(f"{RED}Error: '-html' flag requires an output file name.{RESET}")
2411-
print_help()
2418+
print(f"{RED}Error: -html requires a filename.{RESET}")
24122419
sys.exit(1)
2413-
elif option == '-json':
2420+
2421+
elif option == "-json":
24142422
if i + 1 < len(options):
24152423
json_output = options[i + 1]
24162424
skip_next = True
2425+
continue
24172426
else:
2418-
print(f"{RED}Error: '-json' flag requires a value (output file).{RESET}")
2419-
print_help()
2427+
print(f"{RED}Error: -json requires a filename.{RESET}")
24202428
sys.exit(1)
2421-
elif option == '-aes-timeout':
2429+
2430+
elif option == "-aes-timeout":
24222431
if i + 1 < len(options):
24232432
try:
24242433
aes_timeout_minutes = int(options[i + 1])
2425-
except Exception:
2426-
pass
24272434
except ValueError:
2428-
print(f"{RED}Error: '-aes-timeout' expects an integer number of minutes.{RESET}")
2429-
print_help()
2435+
print(f"{RED}Error: -aes-timeout requires minutes as an integer.{RESET}")
24302436
sys.exit(1)
24312437
skip_next = True
2438+
continue
24322439
else:
2433-
print(f"{RED}Error: '-aes-timeout' flag requires a value (minutes).{RESET}")
2434-
print_help()
2440+
print(f"{RED}Error: -aes-timeout requires a value.{RESET}")
24352441
sys.exit(1)
2436-
else:
2437-
print(f"{RED}Unknown option: {option}{RESET}")
2438-
print_help()
2439-
sys.exit(1)
24402442

2441-
if list_permissions_flag:
2442-
collect_permission_vulns = True
2443+
# ALL other flags are ignored silently now (legacy behavior removed)
24432444

2445+
# ------------------------------------------------------------------
2446+
# Locate APKs
2447+
# ------------------------------------------------------------------
24442448
apk_paths = []
2445-
if os.path.isfile(argument) and argument.endswith('.apk'):
2449+
if os.path.isfile(argument) and argument.endswith(".apk"):
24462450
apk_paths.append(argument)
24472451
elif os.path.isdir(argument):
24482452
for root, dirs, files in os.walk(argument):
24492453
for file in files:
2450-
if file.endswith('.apk'):
2451-
apk_file = os.path.join(root, file)
2452-
apk_paths.append(apk_file)
2454+
if file.endswith(".apk"):
2455+
apk_paths.append(os.path.join(root, file))
24532456
else:
2454-
print(f"{RED}Error: Please provide a valid APK file or directory.{RESET}")
2457+
print(f"{RED}Error: Invalid APK or directory.{RESET}")
24552458
print_help()
24562459
sys.exit(1)
24572460

24582461
if not apk_paths:
2459-
print(f"{RED}No APK files found to analyze.{RESET}")
2462+
print(f"{RED}No APK files found.{RESET}")
24602463
sys.exit(1)
24612464

2465+
# ------------------------------------------------------------------
2466+
# MANIFEST SCANNING
2467+
# ------------------------------------------------------------------
24622468
print(BANNER)
24632469
pool_args = [
2464-
(apk_file, list_permissions_flag, check_js, check_call, collect_permission_vulns, check_taptrap)
2470+
(apk_file, list_permissions_flag, check_js, check_call,
2471+
collect_permission_vulns, check_taptrap)
24652472
for apk_file in apk_paths
24662473
]
24672474

@@ -2480,42 +2487,53 @@ def main():
24802487
)
24812488

24822489
for result in results_list:
2483-
apk_file, vulnerabilities, perms, package_name = result
2484-
if vulnerabilities:
2485-
all_vulnerabilities.extend(vulnerabilities)
2490+
apk_file, vulns, perms, pkg_name = result
2491+
if vulns:
2492+
all_vulnerabilities.extend(vulns)
24862493
if perms and list_permissions_flag:
24872494
all_permissions_dict[apk_file] = perms
2488-
if package_name:
2489-
package_names_for_apks[apk_file] = package_name
2490-
2491-
## wired from CLI -> env for AES timeout wrapper
2492-
2493-
2494-
2495+
if pkg_name:
2496+
package_names_for_apks[apk_file] = pkg_name
24952497

2498+
# ------------------------------------------------------------------
2499+
# AES SCANNING (with duplicate suppression)
2500+
# ------------------------------------------------------------------
24962501
if check_aes:
24972502
print(f"\n{BOLD}Starting AES key extraction...{RESET}\n")
2503+
2504+
seen_aes = set()
2505+
24982506
for apk_file in tqdm(apk_paths, desc="Analyzing for AES keys"):
24992507
if not is_valid_apk(apk_file):
25002508
continue
2501-
pkg_name = package_names_for_apks.get(apk_file, os.path.basename(apk_file))
2502-
timeout_seconds = max(0, int(aes_timeout_minutes)) * 60
2503-
aes_vulns = run_aes_with_timeout(apk_file, pkg_name, timeout_seconds)
2504-
if aes_vulns:
2505-
all_vulnerabilities.extend(aes_vulns)
25062509

2510+
pkg = package_names_for_apks.get(apk_file, os.path.basename(apk_file))
2511+
timeout_seconds = max(0, aes_timeout_minutes) * 60
2512+
2513+
aes_vulns = run_aes_with_timeout(apk_file, pkg, timeout_seconds)
2514+
2515+
if aes_vulns:
2516+
for v in aes_vulns:
2517+
sig = (v.get("component"), v.get("issue_type"))
2518+
if sig not in seen_aes:
2519+
seen_aes.add(sig)
2520+
all_vulnerabilities.append(v)
2521+
2522+
# ------------------------------------------------------------------
2523+
# OUTPUT REPORTS
2524+
# ------------------------------------------------------------------
25072525
end_time = datetime.now()
25082526
total_time = end_time - start_time
25092527

25102528
print(f"\n{BOLD}Vulnerability Summary:{RESET}\n")
25112529
display_vulnerabilities_table(all_vulnerabilities)
25122530

25132531
if html_output:
2514-
print(f"\n{BOLD}Generating HTML report...{RESET}\n")
2532+
print(f"\n{BOLD}Generating HTML report...{RESET}")
25152533
generate_html_report(all_vulnerabilities, all_permissions_dict, html_output)
25162534

25172535
if csv_output:
2518-
print(f"\n{BOLD}Generating CSV report...{RESET}\n")
2536+
print(f"\n{BOLD}Generating CSV report...{RESET}")
25192537
generate_csv_report(all_vulnerabilities, all_permissions_dict, csv_output)
25202538
generate_csv_taptrap_rollup(all_vulnerabilities, csv_output)
25212539

@@ -2525,24 +2543,18 @@ def main():
25252543
print(f"{CYAN}{os.path.basename(apk_file)}:{RESET}")
25262544
for perm in perms:
25272545
print(f" {perm}")
2528-
print()
25292546

25302547
print(f"\n{BOLD}Total Execution Time:{RESET} {total_time}")
25312548

2532-
if 'json_output' in locals() and json_output:
2549+
if json_output:
25332550
try:
2534-
generate_json_report(all_vulnerabilities, locals().get("permissions", {}), json_output)
2551+
generate_json_report(all_vulnerabilities,
2552+
locals().get("permissions", {}),
2553+
json_output)
25352554
except Exception:
25362555
pass
2537-
except Exception as _e_json:
2538-
try:
2539-
print(f"{RED}Error generating JSON report: {_e_json}{RESET}")
2540-
except Exception:
2541-
pass
2542-
except Exception:
2543-
pass
25442556

25452557

2558+
# ENTRY POINT
25462559
if __name__ == "__main__":
25472560
main()
2548-

0 commit comments

Comments
 (0)