@kakuzu_aon/apkz 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/README.md +392 -0
- package/package.json +53 -0
- package/src/commands/analyze.js +261 -0
- package/src/commands/batch.js +549 -0
- package/src/commands/build.js +134 -0
- package/src/commands/clean.js +159 -0
- package/src/commands/compile.js +285 -0
- package/src/commands/config.js +343 -0
- package/src/commands/decode.js +133 -0
- package/src/commands/decompile.js +444 -0
- package/src/commands/diff.js +334 -0
- package/src/commands/extract.js +410 -0
- package/src/commands/info.js +886 -0
- package/src/commands/install.js +258 -0
- package/src/commands/modify-enhanced.js +1077 -0
- package/src/commands/modify.js +375 -0
- package/src/commands/monitor.js +421 -0
- package/src/commands/plugin.js +239 -0
- package/src/commands/sign.js +169 -0
- package/src/commands/vulnerability-scan.js +404 -0
- package/src/commands/web.js +97 -0
- package/src/index.js +139 -0
- package/src/utils/config.js +492 -0
- package/src/utils/config.json +118 -0
- package/src/utils/icon-manager.js +544 -0
- package/src/utils/manifest-parser.js +506 -0
- package/src/utils/network-analyzer.js +461 -0
- package/src/utils/obfuscation-detector.js +819 -0
- package/src/utils/plugin-system.js +390 -0
- package/src/utils/smali-editor.js +480 -0
- package/src/utils/vulnerability-scanner.js +838 -0
- package/src/web/public/index.html +1017 -0
- package/src/web/web-server.js +587 -0
- package/test_files/test.js +131 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// ────────────[ KAKUZU ]────────────────────────────
|
|
2
|
+
// | Discord : kakuzu_aon
|
|
3
|
+
// | Telegram : kakuzu_aon
|
|
4
|
+
// | Github : kakuzu-aon
|
|
5
|
+
// | File : extract.js
|
|
6
|
+
// | License : MIT License © 2026 Kakuzu
|
|
7
|
+
// | Brief : Advanced APK extraction command
|
|
8
|
+
// ────────────────★─────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const { Command } = require('commander');
|
|
11
|
+
const chalk = require('chalk').default;
|
|
12
|
+
const { default: ora } = require('ora');
|
|
13
|
+
const fs = require('fs-extra');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const AdmZip = require('adm-zip');
|
|
16
|
+
const cliProgress = require('cli-progress');
|
|
17
|
+
const ManifestParser = require('../utils/manifest-parser');
|
|
18
|
+
const NetworkAnalyzer = require('../utils/network-analyzer');
|
|
19
|
+
const VulnerabilityScanner = require('../utils/vulnerability-scanner');
|
|
20
|
+
|
|
21
|
+
const extractCommand = new Command('extract')
|
|
22
|
+
.description('Professional APK extraction with comprehensive analysis and reporting')
|
|
23
|
+
.argument('<apk-file>', 'APK file to extract')
|
|
24
|
+
.option('-o, --output <dir>', 'Output directory (default: ./extracted)', './extracted')
|
|
25
|
+
.option('-f, --force', 'Overwrite existing output directory')
|
|
26
|
+
.option('--no-resources', 'Skip resource extraction')
|
|
27
|
+
.option('--no-dex', 'Skip DEX file extraction')
|
|
28
|
+
.option('--no-native', 'Skip native library extraction')
|
|
29
|
+
.option('--no-assets', 'Skip assets extraction')
|
|
30
|
+
.option('--no-manifest', 'Skip manifest extraction')
|
|
31
|
+
.option('--no-meta', 'Skip metadata extraction')
|
|
32
|
+
.option('--analyze', 'Perform deep analysis during extraction')
|
|
33
|
+
.option('--vulnerability', 'Perform vulnerability scanning')
|
|
34
|
+
.option('--obfuscation', 'Perform obfuscation analysis')
|
|
35
|
+
.option('--network', 'Perform network analysis')
|
|
36
|
+
.option('--strings', 'Extract all strings from smali files')
|
|
37
|
+
.option('--signatures', 'Extract code signatures and metadata')
|
|
38
|
+
.option('--permissions', 'Extract permission analysis')
|
|
39
|
+
.option('--components', 'Extract component analysis')
|
|
40
|
+
.option('--framework', 'Detect development frameworks')
|
|
41
|
+
.option('--resources', 'Extract resource analysis')
|
|
42
|
+
.option('--native', 'Extract native library analysis')
|
|
43
|
+
.option('--parallel <num>', 'Parallel extraction threads', '4')
|
|
44
|
+
.option('--timeout <ms>', 'Extraction timeout in milliseconds', '300000')
|
|
45
|
+
.option('--progress', 'Show detailed progress information')
|
|
46
|
+
.option('--validate', 'Validate extracted files')
|
|
47
|
+
.option('--compress', 'Compress extracted files')
|
|
48
|
+
.option('--backup', 'Create backup of original APK')
|
|
49
|
+
.option('--export <file>', 'Export extraction report')
|
|
50
|
+
.option('--format <format>', 'Report format (json, html, csv)', 'json')
|
|
51
|
+
.option('--dry-run', 'Show extraction plan without executing')
|
|
52
|
+
.option('--template <name>', 'Use extraction template')
|
|
53
|
+
.option('--log-level <level>', 'Logging level (debug, info, warn, error)', 'info')
|
|
54
|
+
.action(async (apkFile, options) => {
|
|
55
|
+
let spinner;
|
|
56
|
+
try {
|
|
57
|
+
if (!fs.existsSync(apkFile)) {
|
|
58
|
+
console.error(chalk.red(`🔴 Error: APK file not found: ${apkFile}`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const outputDir = path.resolve(options.output);
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(outputDir) && !options.force) {
|
|
65
|
+
console.error(chalk.red(`🔴 Error: Output directory exists: ${outputDir}`));
|
|
66
|
+
console.error(chalk.yellow('💡 Use --force to overwrite'));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
spinner = ora('🔧 Starting advanced extraction...').start();
|
|
71
|
+
|
|
72
|
+
await performAdvancedExtraction(apkFile, outputDir, options);
|
|
73
|
+
|
|
74
|
+
spinner.succeed('Advanced extraction completed!');
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (spinner) spinner.fail('Extraction failed');
|
|
78
|
+
console.error(chalk.red('🔴 Error:'), error.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
async function performAdvancedExtraction(apkPath, outputDir, options) {
|
|
84
|
+
const zip = new AdmZip(apkPath);
|
|
85
|
+
const entries = zip.getEntries();
|
|
86
|
+
|
|
87
|
+
// Create output directory
|
|
88
|
+
await fs.ensureDir(outputDir);
|
|
89
|
+
|
|
90
|
+
// Multi-progress bars
|
|
91
|
+
const multiProgress = new cliProgress.MultiBar({
|
|
92
|
+
format: chalk.cyan('📦 {bar}') + ' | {percentage}% | {value}/{total} {file}',
|
|
93
|
+
barCompleteChar: '█',
|
|
94
|
+
barIncompleteChar: '░',
|
|
95
|
+
hideCursor: true
|
|
96
|
+
}, cliProgress.Presets.shades_grey);
|
|
97
|
+
|
|
98
|
+
const extractionProgress = multiProgress.create(entries.length, 0, { file: 'Extracting files...' });
|
|
99
|
+
const analysisProgress = multiProgress.create(100, 0, { file: 'Analysis...' });
|
|
100
|
+
|
|
101
|
+
// Extract files
|
|
102
|
+
let processed = 0;
|
|
103
|
+
const extractedFiles = {
|
|
104
|
+
total: entries.length,
|
|
105
|
+
dex: 0,
|
|
106
|
+
resources: 0,
|
|
107
|
+
native: 0,
|
|
108
|
+
assets: 0,
|
|
109
|
+
manifest: 0,
|
|
110
|
+
certificates: 0,
|
|
111
|
+
other: 0
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const entryPath = path.join(outputDir, entry.entryName);
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
// Skip based on options
|
|
119
|
+
if (!options.dex && entry.entryName.endsWith('.dex')) {
|
|
120
|
+
processed++;
|
|
121
|
+
extractionProgress.update(processed);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!options.resources && entry.entryName === 'resources.arsc') {
|
|
126
|
+
processed++;
|
|
127
|
+
extractionProgress.update(processed);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!options.native && entry.entryName.startsWith('lib/')) {
|
|
132
|
+
processed++;
|
|
133
|
+
extractionProgress.update(processed);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (entry.isDirectory) {
|
|
138
|
+
await fs.ensureDir(entryPath);
|
|
139
|
+
} else {
|
|
140
|
+
await fs.ensureDir(path.dirname(entryPath));
|
|
141
|
+
await fs.writeFile(entryPath, entry.getData());
|
|
142
|
+
|
|
143
|
+
// Categorize files
|
|
144
|
+
if (entry.entryName.endsWith('.dex')) {
|
|
145
|
+
extractedFiles.dex++;
|
|
146
|
+
} else if (entry.entryName === 'resources.arsc') {
|
|
147
|
+
extractedFiles.resources++;
|
|
148
|
+
} else if (entry.entryName.startsWith('lib/')) {
|
|
149
|
+
extractedFiles.native++;
|
|
150
|
+
} else if (entry.entryName.startsWith('assets/')) {
|
|
151
|
+
extractedFiles.assets++;
|
|
152
|
+
} else if (entry.entryName === 'AndroidManifest.xml') {
|
|
153
|
+
extractedFiles.manifest++;
|
|
154
|
+
} else if (entry.entryName.startsWith('META-INF/')) {
|
|
155
|
+
extractedFiles.certificates++;
|
|
156
|
+
} else {
|
|
157
|
+
extractedFiles.other++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.warn(chalk.yellow(`⚠️ Warning: Could not extract ${entry.entryName}: ${error.message}`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
processed++;
|
|
165
|
+
extractionProgress.update(processed, { file: entry.entryName });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
multiProgress.stop();
|
|
169
|
+
|
|
170
|
+
// Display extraction summary
|
|
171
|
+
console.log(chalk.bold('\n📊 Extraction Summary:'));
|
|
172
|
+
console.log(chalk.gray(` • Total files: ${extractedFiles.total}`));
|
|
173
|
+
console.log(chalk.cyan(` • DEX files: ${extractedFiles.dex}`));
|
|
174
|
+
console.log(chalk.blue(` • Resources: ${extractedFiles.resources}`));
|
|
175
|
+
console.log(chalk.magenta(` • Native libs: ${extractedFiles.native}`));
|
|
176
|
+
console.log(chalk.green(` • Assets: ${extractedFiles.assets}`));
|
|
177
|
+
console.log(chalk.yellow(` • Certificates: ${extractedFiles.certificates}`));
|
|
178
|
+
console.log(chalk.gray(` • Other files: ${extractedFiles.other}`));
|
|
179
|
+
|
|
180
|
+
// Perform analysis if requested
|
|
181
|
+
if (options.analyze || options.vulnerability || options.network) {
|
|
182
|
+
await performPostExtractionAnalysis(outputDir, options, analysisProgress);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Extract strings if requested
|
|
186
|
+
if (options.strings) {
|
|
187
|
+
await extractAllStrings(outputDir);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Extract signatures if requested
|
|
191
|
+
if (options.signatures) {
|
|
192
|
+
await extractSignatures(outputDir);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Save extraction metadata
|
|
196
|
+
const metadata = {
|
|
197
|
+
timestamp: new Date().toISOString(),
|
|
198
|
+
source_apk: path.resolve(apkPath),
|
|
199
|
+
extracted_files: extractedFiles,
|
|
200
|
+
options: options,
|
|
201
|
+
analysis: {}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
await fs.writeJson(path.join(outputDir, 'extraction_metadata.json'), metadata, { spaces: 2 });
|
|
205
|
+
|
|
206
|
+
console.log(chalk.green(`✅ Advanced extraction completed: ${outputDir}`));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function performPostExtractionAnalysis(outputDir, options, progress) {
|
|
210
|
+
progress.update(0, { file: 'Starting analysis...' });
|
|
211
|
+
|
|
212
|
+
const analysis = {};
|
|
213
|
+
|
|
214
|
+
// Manifest analysis
|
|
215
|
+
if (fs.existsSync(path.join(outputDir, 'AndroidManifest.xml'))) {
|
|
216
|
+
progress.update(25, { file: 'Analyzing manifest...' });
|
|
217
|
+
try {
|
|
218
|
+
const manifestParser = new ManifestParser();
|
|
219
|
+
analysis.manifest = await manifestParser.parseManifest(outputDir);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.warn(chalk.yellow('⚠️ Manifest analysis failed:', error.message));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Network analysis
|
|
226
|
+
if (options.network) {
|
|
227
|
+
progress.update(50, { file: 'Analyzing network...' });
|
|
228
|
+
try {
|
|
229
|
+
const networkAnalyzer = new NetworkAnalyzer();
|
|
230
|
+
analysis.network = await networkAnalyzer.analyzeNetwork(outputDir);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.warn(chalk.yellow('⚠️ Network analysis failed:', error.message));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Vulnerability scanning
|
|
237
|
+
if (options.vulnerability) {
|
|
238
|
+
progress.update(75, { file: 'Scanning for vulnerabilities...' });
|
|
239
|
+
try {
|
|
240
|
+
const vulnScanner = new VulnerabilityScanner();
|
|
241
|
+
analysis.vulnerability = await vulnScanner.scanAPK(outputDir);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.warn(chalk.yellow('⚠️ Vulnerability scanning failed:', error.message));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
progress.update(100, { file: 'Analysis completed!' });
|
|
248
|
+
|
|
249
|
+
// Save analysis results
|
|
250
|
+
await fs.writeJson(path.join(outputDir, 'analysis_results.json'), analysis, { spaces: 2 });
|
|
251
|
+
|
|
252
|
+
// Display analysis summary
|
|
253
|
+
if (analysis.manifest) {
|
|
254
|
+
console.log(chalk.bold('\n📱 Application Information:'));
|
|
255
|
+
console.log(chalk.cyan(` • Package: ${analysis.manifest.package || 'Unknown'}`));
|
|
256
|
+
console.log(chalk.green(` • Version: ${analysis.manifest.versionName || 'Unknown'} (${analysis.manifest.versionCode || 'Unknown'})`));
|
|
257
|
+
console.log(chalk.blue(` • Min SDK: ${analysis.manifest.minSdk || 'Unknown'}`));
|
|
258
|
+
console.log(chalk.blue(` • Target SDK: ${analysis.manifest.targetSdk || 'Unknown'}`));
|
|
259
|
+
|
|
260
|
+
if (analysis.manifest.application?.debuggable === 'true') {
|
|
261
|
+
console.log(chalk.red(' ⚠️ Debug mode enabled'));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (analysis.network) {
|
|
266
|
+
console.log(chalk.bold('\n🌐 Network Analysis:'));
|
|
267
|
+
console.log(chalk.cyan(` • URLs found: ${analysis.network.summary.totalUrls}`));
|
|
268
|
+
console.log(chalk.yellow(` • API keys: ${analysis.network.summary.apiKeysFound}`));
|
|
269
|
+
console.log(chalk.red(` • Risk level: ${analysis.network.security.riskLevel}`));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (analysis.vulnerability) {
|
|
273
|
+
console.log(chalk.bold('\n🔒 Security Scan:'));
|
|
274
|
+
console.log(chalk.red(` • Risk Score: ${analysis.vulnerability.summary.riskScore}/100`));
|
|
275
|
+
console.log(chalk.red(` • Total vulnerabilities: ${analysis.vulnerability.summary.total}`));
|
|
276
|
+
console.log(chalk.red(` • Critical: ${analysis.vulnerability.summary.critical}`));
|
|
277
|
+
console.log(chalk.yellow(` • High: ${analysis.vulnerability.summary.high}`));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function extractAllStrings(decodedPath) {
|
|
282
|
+
const SmaliEditor = require('../utils/smali-editor');
|
|
283
|
+
const spinner = ora('🔤 Extracting strings...').start();
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const smaliEditor = new SmaliEditor();
|
|
287
|
+
const strings = await smaliEditor.extractStrings(decodedPath, {
|
|
288
|
+
minLength: 3,
|
|
289
|
+
maxLength: 200,
|
|
290
|
+
includeUrls: true,
|
|
291
|
+
includeEmails: true
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
spinner.succeed(`Extracted ${strings.length} strings`);
|
|
295
|
+
|
|
296
|
+
// Save strings
|
|
297
|
+
const stringsPath = path.join(decodedPath, 'extracted_strings.json');
|
|
298
|
+
await fs.writeJson(stringsPath, strings, { spaces: 2 });
|
|
299
|
+
|
|
300
|
+
// Show sample
|
|
301
|
+
console.log(chalk.bold('\n🔤 Sample Strings:'));
|
|
302
|
+
strings.slice(0, 10).forEach((str, index) => {
|
|
303
|
+
console.log(chalk.gray(` ${index + 1}. "${str.value}" (${path.basename(str.file)})`));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (strings.length > 10) {
|
|
307
|
+
console.log(chalk.gray(` ... and ${strings.length - 10} more strings`));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
} catch (error) {
|
|
311
|
+
spinner.fail('String extraction failed');
|
|
312
|
+
console.error(chalk.red('🔴 Error:'), error.message);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function extractSignatures(decodedPath) {
|
|
317
|
+
const spinner = ora('🔍 Extracting signatures...').start();
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const signatures = {
|
|
321
|
+
certificates: [],
|
|
322
|
+
manifest: {},
|
|
323
|
+
native_libs: [],
|
|
324
|
+
file_hashes: {}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Extract certificates
|
|
328
|
+
const metaInfDir = path.join(decodedPath, 'META-INF');
|
|
329
|
+
if (fs.existsSync(metaInfDir)) {
|
|
330
|
+
const certFiles = await fs.readdir(metaInfDir);
|
|
331
|
+
for (const file of certFiles) {
|
|
332
|
+
if (file.endsWith('.RSA') || file.endsWith('.DSA') || file.endsWith('.SF')) {
|
|
333
|
+
const filePath = path.join(metaInfDir, file);
|
|
334
|
+
const stats = await fs.stat(filePath);
|
|
335
|
+
signatures.certificates.push({
|
|
336
|
+
name: file,
|
|
337
|
+
size: stats.size,
|
|
338
|
+
path: path.relative(decodedPath, filePath)
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Extract manifest info
|
|
345
|
+
const manifestPath = path.join(decodedPath, 'AndroidManifest.xml');
|
|
346
|
+
if (fs.existsSync(manifestPath)) {
|
|
347
|
+
const stats = await fs.stat(manifestPath);
|
|
348
|
+
signatures.manifest = {
|
|
349
|
+
size: stats.size,
|
|
350
|
+
modified: stats.mtime,
|
|
351
|
+
path: path.relative(decodedPath, manifestPath)
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Extract native library signatures
|
|
356
|
+
const libDir = path.join(decodedPath, 'lib');
|
|
357
|
+
if (fs.existsSync(libDir)) {
|
|
358
|
+
const archDirs = await fs.readdir(libDir);
|
|
359
|
+
for (const arch of archDirs) {
|
|
360
|
+
const archPath = path.join(libDir, arch);
|
|
361
|
+
if (fs.statSync(archPath).isDirectory()) {
|
|
362
|
+
const soFiles = await fs.readdir(archPath);
|
|
363
|
+
for (const file of soFiles) {
|
|
364
|
+
if (file.endsWith('.so')) {
|
|
365
|
+
const filePath = path.join(archPath, file);
|
|
366
|
+
const stats = await fs.stat(filePath);
|
|
367
|
+
signatures.native_libs.push({
|
|
368
|
+
name: file,
|
|
369
|
+
architecture: arch,
|
|
370
|
+
size: stats.size,
|
|
371
|
+
path: path.relative(decodedPath, filePath)
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Calculate file hashes for important files
|
|
380
|
+
const crypto = require('crypto');
|
|
381
|
+
const importantFiles = ['AndroidManifest.xml', 'classes.dex', 'resources.arsc'];
|
|
382
|
+
|
|
383
|
+
for (const file of importantFiles) {
|
|
384
|
+
const filePath = path.join(decodedPath, file);
|
|
385
|
+
if (fs.existsSync(filePath)) {
|
|
386
|
+
const content = await fs.readFile(filePath);
|
|
387
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
388
|
+
signatures.file_hashes[file] = hash;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
spinner.succeed('Signatures extracted');
|
|
393
|
+
|
|
394
|
+
// Save signatures
|
|
395
|
+
const signaturesPath = path.join(decodedPath, 'signatures.json');
|
|
396
|
+
await fs.writeJson(signaturesPath, signatures, { spaces: 2 });
|
|
397
|
+
|
|
398
|
+
// Display summary
|
|
399
|
+
console.log(chalk.bold('\n🔍 Signature Summary:'));
|
|
400
|
+
console.log(chalk.cyan(` • Certificates: ${signatures.certificates.length}`));
|
|
401
|
+
console.log(chalk.magenta(` • Native libraries: ${signatures.native_libs.length}`));
|
|
402
|
+
console.log(chalk.blue(` • File hashes: ${Object.keys(signatures.file_hashes).length}`));
|
|
403
|
+
|
|
404
|
+
} catch (error) {
|
|
405
|
+
spinner.fail('Signature extraction failed');
|
|
406
|
+
console.error(chalk.red('🔴 Error:'), error.message);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = extractCommand;
|