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
5151def 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+
7371def command_exists (command ):
7472 return shutil .which (command ) is not None
7573
@@ -86,7 +84,7 @@ def _has_inline_call_gate(elem):
8684
8785def 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-
23452342def 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
25462559if __name__ == "__main__" :
25472560 main ()
2548-
0 commit comments