anais-apk-forensic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/anais.sh +669 -0
- package/analysis_tools/__pycache__/apk_basic_info.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/apk_basic_info.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/check_zip_encryption.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/check_zip_encryption.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/detect_obfuscation.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/detect_obfuscation.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/dex_payload_hunter.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/entropy_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/error_logger.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/error_logger.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/find_encrypted_payload.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/fix_apk_headers.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/fix_apk_headers.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/manifest_analyzer.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/manifest_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/network_analyzer.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/network_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/report_generator.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/report_generator.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/report_generator_modular.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/sast_scanner.cpython-313.pyc +0 -0
- package/analysis_tools/__pycache__/sast_scanner.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/so_string_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/yara_enhanced_analyzer.cpython-314.pyc +0 -0
- package/analysis_tools/__pycache__/yara_results_processor.cpython-314.pyc +0 -0
- package/analysis_tools/apk_basic_info.py +85 -0
- package/analysis_tools/check_zip_encryption.py +142 -0
- package/analysis_tools/detect_obfuscation.py +650 -0
- package/analysis_tools/dex_payload_hunter.py +734 -0
- package/analysis_tools/entropy_analyzer.py +335 -0
- package/analysis_tools/error_logger.py +75 -0
- package/analysis_tools/find_encrypted_payload.py +485 -0
- package/analysis_tools/fix_apk_headers.py +154 -0
- package/analysis_tools/manifest_analyzer.py +214 -0
- package/analysis_tools/network_analyzer.py +287 -0
- package/analysis_tools/report_generator.py +506 -0
- package/analysis_tools/report_generator_modular.py +885 -0
- package/analysis_tools/sast_scanner.py +412 -0
- package/analysis_tools/so_string_analyzer.py +406 -0
- package/analysis_tools/yara_enhanced_analyzer.py +330 -0
- package/analysis_tools/yara_results_processor.py +368 -0
- package/analyzer_config.json +113 -0
- package/apkid/__init__.py +32 -0
- package/apkid/__pycache__/__init__.cpython-313.pyc +0 -0
- package/apkid/__pycache__/__init__.cpython-314.pyc +0 -0
- package/apkid/__pycache__/apkid.cpython-313.pyc +0 -0
- package/apkid/__pycache__/apkid.cpython-314.pyc +0 -0
- package/apkid/__pycache__/main.cpython-313.pyc +0 -0
- package/apkid/__pycache__/main.cpython-314.pyc +0 -0
- package/apkid/__pycache__/output.cpython-313.pyc +0 -0
- package/apkid/__pycache__/rules.cpython-313.pyc +0 -0
- package/apkid/apkid.py +266 -0
- package/apkid/main.py +98 -0
- package/apkid/output.py +177 -0
- package/apkid/rules/apk/common.yara +68 -0
- package/apkid/rules/apk/obfuscators.yara +118 -0
- package/apkid/rules/apk/packers.yara +1197 -0
- package/apkid/rules/apk/protectors.yara +301 -0
- package/apkid/rules/dex/abnormal.yara +104 -0
- package/apkid/rules/dex/anti-vm.yara +568 -0
- package/apkid/rules/dex/common.yara +60 -0
- package/apkid/rules/dex/compilers.yara +434 -0
- package/apkid/rules/dex/obfuscators.yara +602 -0
- package/apkid/rules/dex/packers.yara +761 -0
- package/apkid/rules/dex/protectors.yara +520 -0
- package/apkid/rules/dll/common.yara +38 -0
- package/apkid/rules/dll/obfuscators.yara +43 -0
- package/apkid/rules/elf/anti-vm.yara +43 -0
- package/apkid/rules/elf/common.yara +54 -0
- package/apkid/rules/elf/obfuscators.yara +991 -0
- package/apkid/rules/elf/packers.yara +1128 -0
- package/apkid/rules/elf/protectors.yara +794 -0
- package/apkid/rules/res/common.yara +43 -0
- package/apkid/rules/res/obfuscators.yara +46 -0
- package/apkid/rules/res/protectors.yara +46 -0
- package/apkid/rules.py +77 -0
- package/bin/anais +3 -0
- package/dist/cli.js +82 -0
- package/dist/index.js +123 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/output.js +44 -0
- package/dist/utils/paths.js +107 -0
- package/docs/ARCHITECTURE.txt +353 -0
- package/docs/Workflow and Reference.md +445 -0
- package/package.json +70 -0
- package/rules/yara_general_rules.yar +323 -0
- package/scripts/dynamic_analysis_helper.sh +334 -0
- package/scripts/frida/dpt_dex_dumper.js +145 -0
- package/scripts/frida/frida_dex_dump.js +145 -0
- package/scripts/frida/frida_hooks.js +437 -0
- package/scripts/frida/frida_websocket_extractor.js +154 -0
- package/scripts/setup.sh +206 -0
- package/scripts/validate_framework.sh +224 -0
- package/src/cli.ts +91 -0
- package/src/index.ts +123 -0
- package/src/types/index.ts +44 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/output.ts +50 -0
- package/src/utils/paths.ts +72 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2024 RedNaga. https://rednaga.io
|
|
3
|
+
* All rights reserved. Contact: rednaga@protonmail.com
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* This file is part of APKiD
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
* Commercial License Usage
|
|
10
|
+
* ------------------------
|
|
11
|
+
* Licensees holding valid commercial APKiD licenses may use this file
|
|
12
|
+
* in accordance with the commercial license agreement provided with the
|
|
13
|
+
* Software or, alternatively, in accordance with the terms contained in
|
|
14
|
+
* a written agreement between you and RedNaga.
|
|
15
|
+
*
|
|
16
|
+
*
|
|
17
|
+
* GNU General Public License Usage
|
|
18
|
+
* --------------------------------
|
|
19
|
+
* Alternatively, this file may be used under the terms of the GNU General
|
|
20
|
+
* Public License version 3.0 as published by the Free Software Foundation
|
|
21
|
+
* and appearing in the file LICENSE.GPL included in the packaging of this
|
|
22
|
+
* file. Please visit http://www.gnu.org/copyleft/gpl.html and review the
|
|
23
|
+
* information to ensure the GNU General Public License version 3.0
|
|
24
|
+
* requirements will be met.
|
|
25
|
+
*
|
|
26
|
+
**/
|
|
27
|
+
|
|
28
|
+
rule is_res : file_type
|
|
29
|
+
{
|
|
30
|
+
meta:
|
|
31
|
+
description = "RES"
|
|
32
|
+
|
|
33
|
+
strings:
|
|
34
|
+
// Common patterns in resources.arsc (package ID, resource type,..)
|
|
35
|
+
$magic = { 02 00 0C 00 }
|
|
36
|
+
$type1 = { 01 00 1C 00 }
|
|
37
|
+
$type2 = { 03 00 00 00 }
|
|
38
|
+
$type3 = { 00 02 00 00 }
|
|
39
|
+
|
|
40
|
+
condition:
|
|
41
|
+
$magic at 0 and 1 of ($type*)
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2024 RedNaga. https://rednaga.io
|
|
3
|
+
* All rights reserved. Contact: rednaga@protonmail.com
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* This file is part of APKiD
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
* Commercial License Usage
|
|
10
|
+
* ------------------------
|
|
11
|
+
* Licensees holding valid commercial APKiD licenses may use this file
|
|
12
|
+
* in accordance with the commercial license agreement provided with the
|
|
13
|
+
* Software or, alternatively, in accordance with the terms contained in
|
|
14
|
+
* a written agreement between you and RedNaga.
|
|
15
|
+
*
|
|
16
|
+
*
|
|
17
|
+
* GNU General Public License Usage
|
|
18
|
+
* --------------------------------
|
|
19
|
+
* Alternatively, this file may be used under the terms of the GNU General
|
|
20
|
+
* Public License version 3.0 as published by the Free Software Foundation
|
|
21
|
+
* and appearing in the file LICENSE.GPL included in the packaging of this
|
|
22
|
+
* file. Please visit http://www.gnu.org/copyleft/gpl.html and review the
|
|
23
|
+
* information to ensure the GNU General Public License version 3.0
|
|
24
|
+
* requirements will be met.
|
|
25
|
+
*
|
|
26
|
+
**/
|
|
27
|
+
|
|
28
|
+
include "common.yara"
|
|
29
|
+
|
|
30
|
+
rule mtprotector_res : obfuscator
|
|
31
|
+
{
|
|
32
|
+
meta:
|
|
33
|
+
description = "MT Protector"
|
|
34
|
+
url = "https://mt2.cn/download/"
|
|
35
|
+
sample = "462475fb14ef7b979d1102a61d334cffcdcfc24183be37af868d1dc681bc7126"
|
|
36
|
+
author = "Eduardo Novella"
|
|
37
|
+
|
|
38
|
+
strings:
|
|
39
|
+
$sign = {
|
|
40
|
+
0000 0c0c // extra bytes
|
|
41
|
+
4d54 5f50 726f 7465 6374 6f72 00 // ..MT_Protector.
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
condition:
|
|
45
|
+
is_res and all of them
|
|
46
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2024 RedNaga. https://rednaga.io
|
|
3
|
+
* All rights reserved. Contact: rednaga@protonmail.com
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* This file is part of APKiD
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
* Commercial License Usage
|
|
10
|
+
* ------------------------
|
|
11
|
+
* Licensees holding valid commercial APKiD licenses may use this file
|
|
12
|
+
* in accordance with the commercial license agreement provided with the
|
|
13
|
+
* Software or, alternatively, in accordance with the terms contained in
|
|
14
|
+
* a written agreement between you and RedNaga.
|
|
15
|
+
*
|
|
16
|
+
*
|
|
17
|
+
* GNU General Public License Usage
|
|
18
|
+
* --------------------------------
|
|
19
|
+
* Alternatively, this file may be used under the terms of the GNU General
|
|
20
|
+
* Public License version 3.0 as published by the Free Software Foundation
|
|
21
|
+
* and appearing in the file LICENSE.GPL included in the packaging of this
|
|
22
|
+
* file. Please visit http://www.gnu.org/copyleft/gpl.html and review the
|
|
23
|
+
* information to ensure the GNU General Public License version 3.0
|
|
24
|
+
* requirements will be met.
|
|
25
|
+
*
|
|
26
|
+
**/
|
|
27
|
+
|
|
28
|
+
include "common.yara"
|
|
29
|
+
|
|
30
|
+
rule bugsmirror : protector
|
|
31
|
+
{
|
|
32
|
+
meta:
|
|
33
|
+
description = "BugsMirror"
|
|
34
|
+
url = "https://www.bugsmirror.com/"
|
|
35
|
+
sample = "c9bbf66ac86bf02663b7bc28a735881d4aeaa8d90e9b8b752e9cf337a26f0bdd"
|
|
36
|
+
author = "Abhi"
|
|
37
|
+
|
|
38
|
+
strings:
|
|
39
|
+
$comment = { 00 ?? ?? 53 65 63 75 72 65 64 20 62 79 20
|
|
40
|
+
42 75 67 73 6D 69 72 72 6F 72 00 } // Secured by Bugsmirror
|
|
41
|
+
$comment2 = { ?? 73 65 63 75 72 65 64 5F 62 79 5F 62 75
|
|
42
|
+
67 73 6D 69 72 72 6F 72 00 } // secured_by_bugsmirror
|
|
43
|
+
|
|
44
|
+
condition:
|
|
45
|
+
is_res and any of them
|
|
46
|
+
}
|
package/apkid/rules.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (C) 2023 RedNaga. https://rednaga.io
|
|
3
|
+
All rights reserved. Contact: rednaga@protonmail.com
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
This file is part of APKiD
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Commercial License Usage
|
|
10
|
+
------------------------
|
|
11
|
+
Licensees holding valid commercial APKiD licenses may use this file
|
|
12
|
+
in accordance with the commercial license agreement provided with the
|
|
13
|
+
Software or, alternatively, in accordance with the terms contained in
|
|
14
|
+
a written agreement between you and RedNaga.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
GNU General Public License Usage
|
|
18
|
+
--------------------------------
|
|
19
|
+
Alternatively, this file may be used under the terms of the GNU General
|
|
20
|
+
Public License version 3.0 as published by the Free Software Foundation
|
|
21
|
+
and appearing in the file LICENSE.GPL included in the packaging of this
|
|
22
|
+
file. Please visit http://www.gnu.org/copyleft/gpl.html and review the
|
|
23
|
+
information to ensure the GNU General Public License version 3.0
|
|
24
|
+
requirements will be met.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import os
|
|
29
|
+
from typing import Dict
|
|
30
|
+
from typing import Optional
|
|
31
|
+
|
|
32
|
+
import yara
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RulesManager(object):
|
|
36
|
+
def __init__(self, rules_dir=None, rules_ext='.yara'):
|
|
37
|
+
if not rules_dir:
|
|
38
|
+
rules_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'rules')
|
|
39
|
+
self.rules_dir: str = rules_dir
|
|
40
|
+
self.rules_path: str = os.path.join(self.rules_dir, 'rules.yarc')
|
|
41
|
+
self.rules_ext: str = rules_ext
|
|
42
|
+
self.rules: Optional[yara.Rules] = None
|
|
43
|
+
self.rules_hash: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
def load(self) -> yara.Rules:
|
|
46
|
+
self.rules = yara.load(self.rules_path)
|
|
47
|
+
return self.rules
|
|
48
|
+
|
|
49
|
+
def _collect_yara_files(self) -> Dict[str, str]:
|
|
50
|
+
files = {}
|
|
51
|
+
for root, dirnames, filenames in os.walk(self.rules_dir):
|
|
52
|
+
for filename in filenames:
|
|
53
|
+
if not filename.lower().endswith(self.rules_ext):
|
|
54
|
+
continue
|
|
55
|
+
path = os.path.join(root, filename)
|
|
56
|
+
files[path] = path
|
|
57
|
+
return files
|
|
58
|
+
|
|
59
|
+
def compile(self) -> yara.Rules:
|
|
60
|
+
yara_files = self._collect_yara_files()
|
|
61
|
+
self.rules = yara.compile(filepaths=yara_files)
|
|
62
|
+
return self.rules
|
|
63
|
+
|
|
64
|
+
def save(self) -> int:
|
|
65
|
+
self.rules.save(self.rules_path)
|
|
66
|
+
rules_count = len(set([r.identifier for r in self.rules]))
|
|
67
|
+
return rules_count
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def hash(self) -> str:
|
|
71
|
+
if not self.rules_hash:
|
|
72
|
+
h = hashlib.sha256()
|
|
73
|
+
for file_path in self._collect_yara_files():
|
|
74
|
+
with open(file_path, 'rb') as f:
|
|
75
|
+
h.update(f.read())
|
|
76
|
+
self.rules_hash = h.hexdigest()
|
|
77
|
+
return self.rules_hash
|
package/bin/anais
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const index_1 = require("./index");
|
|
14
|
+
const utils_1 = require("./utils");
|
|
15
|
+
const main = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
if (args.length === 0) {
|
|
18
|
+
(0, utils_1.printBanner)();
|
|
19
|
+
console.log("Usage:");
|
|
20
|
+
console.log(" anais <apk-file-path> Analyze an APK file");
|
|
21
|
+
console.log(" anais --version Show version");
|
|
22
|
+
console.log(" anais --help Show this help message");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
26
|
+
console.log("Anais APK Forensic CLI v1.0.0");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
30
|
+
(0, utils_1.printBanner)();
|
|
31
|
+
console.log("Usage:");
|
|
32
|
+
console.log(" anais <apk-file-path> Analyze an APK file");
|
|
33
|
+
console.log(" anais --version Show version");
|
|
34
|
+
console.log(" anais --help Show this help message");
|
|
35
|
+
console.log("\nDescription:");
|
|
36
|
+
console.log(" Comprehensive APK security analysis and SAST scanner");
|
|
37
|
+
console.log(" - Decompilation with APKTool and JADX");
|
|
38
|
+
console.log(" - YARA malware detection");
|
|
39
|
+
console.log(" - Obfuscation detection");
|
|
40
|
+
console.log(" - Network traffic analysis");
|
|
41
|
+
console.log(" - AndroidManifest analysis");
|
|
42
|
+
console.log(" - Entropy analysis for encrypted payloads");
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
const apkPath = args[0];
|
|
46
|
+
try {
|
|
47
|
+
console.log(`🔍 Anais APK Forensic Analysis\n`);
|
|
48
|
+
(0, utils_1.printSeparator)();
|
|
49
|
+
console.log();
|
|
50
|
+
const result = yield (0, index_1.executeForensicAnalysis)(apkPath);
|
|
51
|
+
console.log();
|
|
52
|
+
(0, utils_1.printSeparator)();
|
|
53
|
+
if (result.success) {
|
|
54
|
+
console.log(`\n${(0, utils_1.formatSuccess)("Analysis completed successfully!")}\n`);
|
|
55
|
+
if (result.workspaceDir) {
|
|
56
|
+
console.log(`📁 Workspace: ${result.workspaceDir}`);
|
|
57
|
+
}
|
|
58
|
+
if (result.reportPath) {
|
|
59
|
+
console.log(`📄 Report: ${result.reportPath}`);
|
|
60
|
+
}
|
|
61
|
+
if (result.jsonReportPath) {
|
|
62
|
+
console.log(`📊 JSON Report: ${result.jsonReportPath}`);
|
|
63
|
+
}
|
|
64
|
+
console.log("\n💡 Tip: Open the report with your preferred markdown viewer");
|
|
65
|
+
console.log(` e.g., cat "${result.reportPath}"\n`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(`\n${(0, utils_1.formatError)("Analysis failed!")}\n`);
|
|
69
|
+
console.log(`Error: ${result.message}`);
|
|
70
|
+
if (result.error) {
|
|
71
|
+
console.log(`Details: ${result.error}`);
|
|
72
|
+
}
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error(`\n${(0, utils_1.formatError)("Unexpected error during analysis:")}`);
|
|
78
|
+
console.error(error);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
main();
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
17
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
18
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
19
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
20
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
21
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
22
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.executeForensicAnalysis = void 0;
|
|
27
|
+
const child_process_1 = require("child_process");
|
|
28
|
+
const utils_1 = require("./utils");
|
|
29
|
+
__exportStar(require("./types"), exports);
|
|
30
|
+
const executeForensicAnalysis = (apkPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
// Resolve absolute path
|
|
33
|
+
const absoluteApkPath = (0, utils_1.resolveAbsolutePath)(apkPath);
|
|
34
|
+
// Check if APK file exists
|
|
35
|
+
if (!(0, utils_1.fileExists)(absoluteApkPath)) {
|
|
36
|
+
return resolve({
|
|
37
|
+
success: false,
|
|
38
|
+
apkPath,
|
|
39
|
+
message: `APK file not found: ${apkPath}`,
|
|
40
|
+
error: "File not found",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
console.log(`\n📱 APK: ${(0, utils_1.getBasename)(absoluteApkPath)}`);
|
|
44
|
+
console.log(`📂 Path: ${absoluteApkPath}\n`);
|
|
45
|
+
// Get paths
|
|
46
|
+
const scriptDir = (0, utils_1.getProjectRoot)();
|
|
47
|
+
const anaisScript = (0, utils_1.getAnaisScriptPath)();
|
|
48
|
+
if (!(0, utils_1.fileExists)(anaisScript)) {
|
|
49
|
+
return resolve({
|
|
50
|
+
success: false,
|
|
51
|
+
apkPath,
|
|
52
|
+
message: `Analysis script not found: ${anaisScript}`,
|
|
53
|
+
error: "Script not found",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
console.log(`🚀 Executing analysis...\n`);
|
|
57
|
+
// Spawn the bash script
|
|
58
|
+
const analysis = (0, child_process_1.spawn)("bash", [anaisScript, absoluteApkPath], {
|
|
59
|
+
cwd: scriptDir,
|
|
60
|
+
env: Object.assign(Object.assign({}, process.env), { PYTHONWARNINGS: "ignore" }),
|
|
61
|
+
});
|
|
62
|
+
let stdout = "";
|
|
63
|
+
let stderr = "";
|
|
64
|
+
let workspaceDir = "";
|
|
65
|
+
let reportPath = "";
|
|
66
|
+
// Capture stdout
|
|
67
|
+
analysis.stdout.on("data", (data) => {
|
|
68
|
+
const output = data.toString();
|
|
69
|
+
process.stdout.write(output);
|
|
70
|
+
stdout += output;
|
|
71
|
+
// Extract workspace directory from output
|
|
72
|
+
const workspaceMatch = output.match(/Workspace initialized: (.+)/);
|
|
73
|
+
if (workspaceMatch) {
|
|
74
|
+
workspaceDir = workspaceMatch[1].trim();
|
|
75
|
+
}
|
|
76
|
+
// Extract report path
|
|
77
|
+
const reportMatch = output.match(/Main report: (.+\.md)/);
|
|
78
|
+
if (reportMatch) {
|
|
79
|
+
reportPath = reportMatch[1].trim();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// Capture stderr (though we're logging warnings there)
|
|
83
|
+
analysis.stderr.on("data", (data) => {
|
|
84
|
+
stderr += data.toString();
|
|
85
|
+
// Don't print stderr to console as it's handled by the script
|
|
86
|
+
});
|
|
87
|
+
// Handle process completion
|
|
88
|
+
analysis.on("close", (code) => {
|
|
89
|
+
if (code === 0) {
|
|
90
|
+
const jsonReportPath = reportPath.replace(/\.md$/, ".json");
|
|
91
|
+
resolve({
|
|
92
|
+
success: true,
|
|
93
|
+
apkPath: absoluteApkPath,
|
|
94
|
+
workspaceDir,
|
|
95
|
+
reportPath,
|
|
96
|
+
jsonReportPath: (0, utils_1.fileExists)(jsonReportPath)
|
|
97
|
+
? jsonReportPath
|
|
98
|
+
: undefined,
|
|
99
|
+
message: "Analysis completed successfully",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
resolve({
|
|
104
|
+
success: false,
|
|
105
|
+
apkPath: absoluteApkPath,
|
|
106
|
+
workspaceDir,
|
|
107
|
+
message: `Analysis failed with exit code ${code}`,
|
|
108
|
+
error: stderr || "Unknown error",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Handle errors
|
|
113
|
+
analysis.on("error", (error) => {
|
|
114
|
+
reject({
|
|
115
|
+
success: false,
|
|
116
|
+
apkPath: absoluteApkPath,
|
|
117
|
+
message: "Failed to execute analysis script",
|
|
118
|
+
error: error.message,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
exports.executeForensicAnalysis = executeForensicAnalysis;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utility functions export
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./output"), exports);
|
|
21
|
+
__exportStar(require("./paths"), exports);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Output formatting utilities
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.colors = void 0;
|
|
7
|
+
exports.formatSuccess = formatSuccess;
|
|
8
|
+
exports.formatError = formatError;
|
|
9
|
+
exports.formatInfo = formatInfo;
|
|
10
|
+
exports.formatWarning = formatWarning;
|
|
11
|
+
exports.printSeparator = printSeparator;
|
|
12
|
+
exports.printBanner = printBanner;
|
|
13
|
+
exports.colors = {
|
|
14
|
+
reset: "\x1b[0m",
|
|
15
|
+
bright: "\x1b[1m",
|
|
16
|
+
dim: "\x1b[2m",
|
|
17
|
+
red: "\x1b[31m",
|
|
18
|
+
green: "\x1b[32m",
|
|
19
|
+
yellow: "\x1b[33m",
|
|
20
|
+
blue: "\x1b[34m",
|
|
21
|
+
magenta: "\x1b[35m",
|
|
22
|
+
cyan: "\x1b[36m",
|
|
23
|
+
};
|
|
24
|
+
function formatSuccess(message) {
|
|
25
|
+
return `${exports.colors.green}✅ ${message}${exports.colors.reset}`;
|
|
26
|
+
}
|
|
27
|
+
function formatError(message) {
|
|
28
|
+
return `${exports.colors.red}❌ ${message}${exports.colors.reset}`;
|
|
29
|
+
}
|
|
30
|
+
function formatInfo(message) {
|
|
31
|
+
return `${exports.colors.blue}ℹ ${message}${exports.colors.reset}`;
|
|
32
|
+
}
|
|
33
|
+
function formatWarning(message) {
|
|
34
|
+
return `${exports.colors.yellow}⚠ ${message}${exports.colors.reset}`;
|
|
35
|
+
}
|
|
36
|
+
function printSeparator(length = 50) {
|
|
37
|
+
console.log("═".repeat(length));
|
|
38
|
+
}
|
|
39
|
+
function printBanner() {
|
|
40
|
+
console.log(`\n${exports.colors.cyan}╔═══════════════════════════════════════════════════════════╗${exports.colors.reset}`);
|
|
41
|
+
console.log(`${exports.colors.cyan}║ Anais APK Forensic Automation - CLI v1.0.0 ║${exports.colors.reset}`);
|
|
42
|
+
console.log(`${exports.colors.cyan}║ Comprehensive APK Security Analysis & SAST ║${exports.colors.reset}`);
|
|
43
|
+
console.log(`${exports.colors.cyan}╚═══════════════════════════════════════════════════════════╝${exports.colors.reset}\n`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getUserDocumentsPath = getUserDocumentsPath;
|
|
37
|
+
exports.getDefaultWorkspacePath = getDefaultWorkspacePath;
|
|
38
|
+
exports.ensureWorkspaceExists = ensureWorkspaceExists;
|
|
39
|
+
exports.getProjectRoot = getProjectRoot;
|
|
40
|
+
exports.getAnaisScriptPath = getAnaisScriptPath;
|
|
41
|
+
exports.fileExists = fileExists;
|
|
42
|
+
exports.resolveAbsolutePath = resolveAbsolutePath;
|
|
43
|
+
exports.getBasename = getBasename;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
/**
|
|
48
|
+
* Get the user's Documents folder path
|
|
49
|
+
*/
|
|
50
|
+
function getUserDocumentsPath() {
|
|
51
|
+
const homeDir = os.homedir();
|
|
52
|
+
return path.join(homeDir, "Documents");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the default analysis workspace path (Documents/Anais-Reports)
|
|
56
|
+
*/
|
|
57
|
+
function getDefaultWorkspacePath() {
|
|
58
|
+
return path.join(getUserDocumentsPath(), "Anais-Reports");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Ensure the analysis workspace directory exists
|
|
62
|
+
*/
|
|
63
|
+
function ensureWorkspaceExists() {
|
|
64
|
+
const workspacePath = getDefaultWorkspacePath();
|
|
65
|
+
if (!fs.existsSync(workspacePath)) {
|
|
66
|
+
fs.mkdirSync(workspacePath, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get the project root directory (where package is installed)
|
|
71
|
+
* When installed via npm, __dirname will be in node_modules/anais-apk-forensic/dist/utils
|
|
72
|
+
* We need to go up to the package root
|
|
73
|
+
*/
|
|
74
|
+
function getProjectRoot() {
|
|
75
|
+
// Check if we're in node_modules (installed via npm)
|
|
76
|
+
const currentDir = path.resolve(__dirname, "../..");
|
|
77
|
+
if (currentDir.includes("node_modules")) {
|
|
78
|
+
// Go up to node_modules/anais-apk-forensic/
|
|
79
|
+
return path.resolve(__dirname, "../..");
|
|
80
|
+
}
|
|
81
|
+
// Otherwise we're in development mode (dist/utils -> root)
|
|
82
|
+
return path.resolve(__dirname, "../..");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the path to anais.sh script
|
|
86
|
+
*/
|
|
87
|
+
function getAnaisScriptPath() {
|
|
88
|
+
return path.join(getProjectRoot(), "anais.sh");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if a file exists
|
|
92
|
+
*/
|
|
93
|
+
function fileExists(filePath) {
|
|
94
|
+
return fs.existsSync(filePath);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve absolute path
|
|
98
|
+
*/
|
|
99
|
+
function resolveAbsolutePath(filePath) {
|
|
100
|
+
return path.resolve(filePath);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get basename of a file
|
|
104
|
+
*/
|
|
105
|
+
function getBasename(filePath) {
|
|
106
|
+
return path.basename(filePath);
|
|
107
|
+
}
|