@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.
@@ -0,0 +1,1077 @@
1
+ // ────────────[ KAKUZU ]────────────────────────────
2
+ // | Discord : kakuzu_aon
3
+ // | Telegram : kakuzu_aon
4
+ // | Github : kakuzu-aon
5
+ // | File : modify-enhanced.js
6
+ // | License : MIT License © 2026 Kakuzu
7
+ // | Brief : Enhanced APK modification 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 inquirer = require('inquirer');
16
+ const AdmZip = require('adm-zip');
17
+ const ManifestParser = require('../utils/manifest-parser');
18
+ const SmaliEditor = require('../utils/smali-editor');
19
+ const IconManager = require('../utils/icon-manager');
20
+ const NetworkAnalyzer = require('../utils/network-analyzer');
21
+
22
+ const modifyCommand = new Command('modify')
23
+ .description('Professional APK modification with advanced features and real-time preview')
24
+ .argument('<apk-file>', 'APK file to modify')
25
+ .option('-o, --output <file>', 'Output modified APK file', './modified.apk')
26
+ .option('-q, --quick', 'Quick modify mode')
27
+ .option('--decode-dir <dir>', 'Use existing decoded directory')
28
+ .option('--interactive', 'Interactive modification mode')
29
+ .option('--batch', 'Batch modification mode')
30
+ .option('--preview', 'Show preview before applying changes')
31
+ .option('--backup', 'Create backup before modification')
32
+ .option('--manifest', 'Modify AndroidManifest.xml')
33
+ .option('--strings', 'Modify string resources')
34
+ .option('--permissions', 'Modify permissions')
35
+ .option('--package', 'Change package name')
36
+ .option('--icon', 'Replace app icon')
37
+ .option('--network', 'Modify network configurations')
38
+ .option('--ssl-bypass', 'Bypass SSL verification')
39
+ .option('--debug', 'Enable debug mode')
40
+ .option('--obfuscate', 'Apply code obfuscation')
41
+ .option('--deobfuscate', 'Remove code obfuscation')
42
+ .option('--sign', 'Sign modified APK')
43
+ .option('--keystore <path>', 'Custom keystore for signing')
44
+ .option('--dry-run', 'Show changes without applying')
45
+ .option('--log-level <level>', 'Logging level (debug, info, warn, error)', 'info')
46
+ .option('--undo', 'Undo last modification')
47
+ .option('--redo', 'Redo last modification')
48
+ .option('--history', 'Show modification history')
49
+ .option('--validate', 'Validate modified APK')
50
+ .option('--optimize', 'Optimize APK after modification')
51
+ .option('--compress', 'Compress APK after modification')
52
+ .option('--template <name>', 'Use modification template')
53
+ .option('--export <file>', 'Export modification script')
54
+ .option('--import <file>', 'Import modification script')
55
+ .action(async (apkFile, options) => {
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 spinner = ora('🔧 Initializing modification tools...').start();
63
+
64
+ await modifyAPKEnhanced(apkFile, options);
65
+
66
+ spinner.succeed('APK modification completed!');
67
+
68
+ } catch (error) {
69
+ spinner?.fail('Modification failed');
70
+ console.error(chalk.red('🔴 Error:'), error.message);
71
+ process.exit(1);
72
+ }
73
+ });
74
+
75
+ async function modifyAPKEnhanced(apkPath, options) {
76
+ let decodedPath;
77
+ const tempDir = path.join(path.dirname(apkPath), 'apkz_temp_' + Date.now());
78
+
79
+ try {
80
+ // Use existing decoded directory or decode APK
81
+ if (options.decodeDir && fs.existsSync(options.decodeDir)) {
82
+ decodedPath = path.resolve(options.decodeDir);
83
+ console.log(chalk.blue(`📁 Using existing decoded directory: ${decodedPath}`));
84
+ } else {
85
+ decodedPath = path.join(tempDir, 'decoded');
86
+ await decodeAPK(apkPath, decodedPath);
87
+ }
88
+
89
+ // Initialize tools
90
+ const manifestParser = new ManifestParser();
91
+ const smaliEditor = new SmaliEditor();
92
+ const iconManager = new IconManager();
93
+ const networkAnalyzer = new NetworkAnalyzer();
94
+
95
+ // Parse manifest
96
+ let manifestData;
97
+ try {
98
+ manifestData = await manifestParser.parseManifest(decodedPath);
99
+ } catch (error) {
100
+ console.log(chalk.yellow('⚠️ Could not parse manifest - binary format detected'));
101
+ manifestData = null;
102
+ }
103
+
104
+ if (!options.quick) {
105
+ await interactiveModification(decodedPath, manifestData, {
106
+ manifestParser,
107
+ smaliEditor,
108
+ iconManager,
109
+ networkAnalyzer
110
+ });
111
+ } else {
112
+ await quickModification(decodedPath, manifestData, {
113
+ smaliEditor,
114
+ iconManager
115
+ });
116
+ }
117
+
118
+ // Rebuild APK
119
+ const outputFile = path.resolve(options.output);
120
+ await rebuildAPK(decodedPath, outputFile);
121
+
122
+ console.log(chalk.green(`✅ Modified APK saved to: ${outputFile}`));
123
+
124
+ // Show summary
125
+ await showModificationSummary(decodedPath, outputFile);
126
+
127
+ } finally {
128
+ // Cleanup temp directory if we created one
129
+ if (!options.decodeDir) {
130
+ await fs.remove(tempDir);
131
+ }
132
+ }
133
+ }
134
+
135
+ async function interactiveModification(decodedPath, manifestData, tools) {
136
+ const { manifestParser, smaliEditor, iconManager, networkAnalyzer } = tools;
137
+
138
+ console.log(chalk.bold('\n🔧 Enhanced APK Modification'));
139
+ console.log(chalk.gray('Choose what you want to modify:\n'));
140
+
141
+ const { action } = await inquirer.prompt([
142
+ {
143
+ type: 'list',
144
+ name: 'action',
145
+ message: 'What would you like to modify?',
146
+ choices: [
147
+ { name: '📝 Edit AndroidManifest.xml', value: 'manifest' },
148
+ { name: '🔤 Replace strings in smali files', value: 'strings' },
149
+ { name: '🔐 Manage permissions', value: 'permissions' },
150
+ { name: '📦 Change package name', value: 'package' },
151
+ { name: '🎨 Replace app icons', value: 'icons' },
152
+ { name: '🌐 Network analysis & modification', value: 'network' },
153
+ { name: '🔒 SSL/TLS bypass', value: 'ssl' },
154
+ { name: '📊 Extract strings', value: 'extract' },
155
+ { name: '🔍 Search patterns', value: 'search' },
156
+ { name: '✅ Done - Rebuild APK', value: 'done' }
157
+ ]
158
+ }
159
+ ]);
160
+
161
+ switch (action) {
162
+ case 'manifest':
163
+ await modifyManifest(decodedPath, manifestData, manifestParser);
164
+ break;
165
+ case 'strings':
166
+ await modifyStrings(decodedPath, smaliEditor);
167
+ break;
168
+ case 'permissions':
169
+ await managePermissions(decodedPath, manifestData, smaliEditor);
170
+ break;
171
+ case 'package':
172
+ await changePackageName(decodedPath, manifestData, smaliEditor);
173
+ break;
174
+ case 'icons':
175
+ await replaceIcons(decodedPath, iconManager);
176
+ break;
177
+ case 'network':
178
+ await analyzeAndModifyNetwork(decodedPath, networkAnalyzer, smaliEditor);
179
+ break;
180
+ case 'ssl':
181
+ await bypassSSL(decodedPath, smaliEditor);
182
+ break;
183
+ case 'extract':
184
+ await extractStrings(decodedPath, smaliEditor);
185
+ break;
186
+ case 'search':
187
+ await searchPatterns(decodedPath, smaliEditor);
188
+ break;
189
+ case 'done':
190
+ return;
191
+ }
192
+
193
+ // Continue with more modifications
194
+ await interactiveModification(decodedPath, manifestData, tools);
195
+ }
196
+
197
+ async function modifyManifest(decodedPath, manifestData, manifestParser) {
198
+ console.log(chalk.blue('\n📝 AndroidManifest.xml Editor'));
199
+
200
+ if (!manifestData || manifestData.package === 'Unknown') {
201
+ console.log(chalk.yellow('⚠️ Manifest parsing failed - binary format detected'));
202
+ console.log(chalk.gray('Limited editing options available for binary manifests'));
203
+ return;
204
+ }
205
+
206
+ const { action } = await inquirer.prompt([
207
+ {
208
+ type: 'list',
209
+ name: 'action',
210
+ message: 'What would you like to modify?',
211
+ choices: [
212
+ { name: '📱 Change app name/label', value: 'label' },
213
+ { name: '🔢 Change version', value: 'version' },
214
+ { name: '🎯 Change SDK versions', value: 'sdk' },
215
+ { name: '🔐 Toggle debug mode', value: 'debug' },
216
+ { name: '📦 Change package name', value: 'package_name' },
217
+ { name: '🔧 Add permission', value: 'add_perm' },
218
+ { name: '❌ Remove permission', value: 'remove_perm' },
219
+ { name: '⬅️ Back', value: 'back' }
220
+ ]
221
+ }
222
+ ]);
223
+
224
+ switch (action) {
225
+ case 'label':
226
+ await changeAppLabel(decodedPath, manifestData, manifestParser);
227
+ break;
228
+ case 'version':
229
+ await changeVersion(decodedPath, manifestData, manifestParser);
230
+ break;
231
+ case 'sdk':
232
+ await changeSDKVersions(decodedPath, manifestData, manifestParser);
233
+ break;
234
+ case 'debug':
235
+ await toggleDebugMode(decodedPath, manifestData, manifestParser);
236
+ break;
237
+ case 'package_name':
238
+ await changePackageNameFromManifest(decodedPath, manifestData, manifestParser);
239
+ break;
240
+ case 'add_perm':
241
+ await addPermission(decodedPath, manifestData, manifestParser);
242
+ break;
243
+ case 'remove_perm':
244
+ await removePermissionFromManifest(decodedPath, manifestData, manifestParser);
245
+ break;
246
+ case 'back':
247
+ return;
248
+ }
249
+ }
250
+
251
+ async function modifyStrings(decodedPath, smaliEditor) {
252
+ console.log(chalk.blue('\n🔤 String Replacement'));
253
+
254
+ const { action } = await inquirer.prompt([
255
+ {
256
+ type: 'list',
257
+ name: 'action',
258
+ message: 'String replacement options:',
259
+ choices: [
260
+ { name: '✏️ Replace specific string', value: 'specific' },
261
+ { name: '🔄 Batch replace multiple strings', value: 'batch' },
262
+ { name: '🔤 Replace using regex pattern', value: 'regex' },
263
+ { name: '📱 Replace URLs', value: 'urls' },
264
+ { name: '📧 Replace email addresses', value: 'emails' },
265
+ { name: '⬅️ Back', value: 'back' }
266
+ ]
267
+ }
268
+ ]);
269
+
270
+ if (action === 'back') return;
271
+
272
+ let replacements = [];
273
+
274
+ switch (action) {
275
+ case 'specific':
276
+ replacements = await getSpecificStringReplacement();
277
+ break;
278
+ case 'batch':
279
+ replacements = await getBatchStringReplacements();
280
+ break;
281
+ case 'regex':
282
+ replacements = await getRegexStringReplacement();
283
+ break;
284
+ case 'urls':
285
+ replacements = [{
286
+ from: /https?:\/\/[^\s"']+/g,
287
+ to: 'https://example.com',
288
+ regex: true
289
+ }];
290
+ break;
291
+ case 'emails':
292
+ replacements = [{
293
+ from: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
294
+ to: 'modified@example.com',
295
+ regex: true
296
+ }];
297
+ break;
298
+ }
299
+
300
+ if (replacements.length > 0) {
301
+ const spinner = ora('🔄 Replacing strings...').start();
302
+
303
+ try {
304
+ const results = await smaliEditor.replaceStrings(decodedPath, replacements);
305
+
306
+ spinner.succeed('String replacement completed!');
307
+
308
+ console.log(chalk.green(`✅ Modified ${results.modified.length} files`));
309
+ console.log(chalk.blue(`📊 Total replacements: ${results.totalReplacements}`));
310
+
311
+ if (results.errors.length > 0) {
312
+ console.log(chalk.yellow(`⚠️ ${results.errors.length} files had errors`));
313
+ }
314
+
315
+ } catch (error) {
316
+ spinner.fail('String replacement failed');
317
+ console.error(chalk.red('🔴 Error:'), error.message);
318
+ }
319
+ }
320
+ }
321
+
322
+ async function managePermissions(decodedPath, manifestData, smaliEditor) {
323
+ console.log(chalk.blue('\n🔐 Permission Management'));
324
+
325
+ const { action } = await inquirer.prompt([
326
+ {
327
+ type: 'list',
328
+ name: 'action',
329
+ message: 'Permission management options:',
330
+ choices: [
331
+ { name: '➕ Add permission bypass', value: 'bypass' },
332
+ { name: '❌ Remove permission checks', value: 'remove' },
333
+ { name: '📋 List current permissions', value: 'list' },
334
+ { name: '⬅️ Back', value: 'back' }
335
+ ]
336
+ }
337
+ ]);
338
+
339
+ switch (action) {
340
+ case 'bypass':
341
+ await addPermissionBypass(decodedPath, manifestData, smaliEditor);
342
+ break;
343
+ case 'remove':
344
+ await removePermissionChecks(decodedPath, manifestData, smaliEditor);
345
+ break;
346
+ case 'list':
347
+ await listPermissions(manifestData);
348
+ break;
349
+ case 'back':
350
+ return;
351
+ }
352
+ }
353
+
354
+ async function replaceIcons(decodedPath, iconManager) {
355
+ console.log(chalk.blue('\n🎨 Icon Replacement'));
356
+
357
+ const { action } = await inquirer.prompt([
358
+ {
359
+ type: 'list',
360
+ name: 'action',
361
+ message: 'Icon replacement options:',
362
+ choices: [
363
+ { name: '📱 Replace launcher icons', value: 'launcher' },
364
+ { name: '🎯 Replace adaptive icons', value: 'adaptive' },
365
+ { name: '🔔 Replace notification icons', value: 'notification' },
366
+ { name: '📊 Show current icon info', value: 'info' },
367
+ { name: '📤 Extract existing icons', value: 'extract' },
368
+ { name: '⬅️ Back', value: 'back' }
369
+ ]
370
+ }
371
+ ]);
372
+
373
+ switch (action) {
374
+ case 'launcher':
375
+ case 'adaptive':
376
+ case 'notification':
377
+ await performIconReplacement(decodedPath, iconManager, action);
378
+ break;
379
+ case 'info':
380
+ await showIconInfo(decodedPath, iconManager);
381
+ break;
382
+ case 'extract':
383
+ await extractIcons(decodedPath, iconManager);
384
+ break;
385
+ case 'back':
386
+ return;
387
+ }
388
+ }
389
+
390
+ async function analyzeAndModifyNetwork(decodedPath, networkAnalyzer, smaliEditor) {
391
+ console.log(chalk.blue('\n🌐 Network Analysis & Modification'));
392
+
393
+ const spinner = ora('🔍 Analyzing network configurations...').start();
394
+
395
+ try {
396
+ const networkAnalysis = await networkAnalyzer.analyzeNetwork(decodedPath);
397
+ spinner.succeed('Network analysis completed!');
398
+
399
+ // Display analysis results
400
+ console.log(chalk.bold('\n📊 Network Analysis Results:'));
401
+ console.log(chalk.gray(`• URLs found: ${networkAnalysis.summary.totalUrls}`));
402
+ console.log(chalk.gray(`• API endpoints: ${networkAnalysis.summary.apiEndpoints}`));
403
+ console.log(chalk.gray(`• API keys detected: ${networkAnalysis.summary.apiKeysFound}`));
404
+ console.log(chalk.gray(`• Risk level: ${networkAnalysis.security.riskLevel}`));
405
+
406
+ if (networkAnalysis.security.issues.length > 0) {
407
+ console.log(chalk.red('\n⚠️ Security Issues:'));
408
+ networkAnalysis.security.issues.forEach(issue => {
409
+ console.log(chalk.red(` • ${issue}`));
410
+ });
411
+ }
412
+
413
+ const { action } = await inquirer.prompt([
414
+ {
415
+ type: 'list',
416
+ name: 'action',
417
+ message: 'Network modification options:',
418
+ choices: [
419
+ { name: '🔧 Replace URLs', value: 'replace_urls' },
420
+ { name: '🔑 Replace API keys', value: 'replace_keys' },
421
+ { name: '🔒 Remove SSL verification', value: 'remove_ssl' },
422
+ { name: '📋 Show detailed analysis', value: 'details' },
423
+ { name: '⬅️ Back', value: 'back' }
424
+ ]
425
+ }
426
+ ]);
427
+
428
+ switch (action) {
429
+ case 'replace_urls':
430
+ await replaceNetworkURLs(decodedPath, networkAnalysis, smaliEditor);
431
+ break;
432
+ case 'replace_keys':
433
+ await replaceAPIKeys(decodedPath, networkAnalysis, smaliEditor);
434
+ break;
435
+ case 'remove_ssl':
436
+ await bypassSSL(decodedPath, smaliEditor);
437
+ break;
438
+ case 'details':
439
+ await showDetailedNetworkAnalysis(networkAnalysis);
440
+ break;
441
+ case 'back':
442
+ return;
443
+ }
444
+
445
+ } catch (error) {
446
+ spinner.fail('Network analysis failed');
447
+ console.error(chalk.red('🔴 Error:'), error.message);
448
+ }
449
+ }
450
+
451
+ // Helper functions
452
+ async function getSpecificStringReplacement() {
453
+ const { from, to, caseSensitive, regex } = await inquirer.prompt([
454
+ {
455
+ type: 'input',
456
+ name: 'from',
457
+ message: 'String to replace:'
458
+ },
459
+ {
460
+ type: 'input',
461
+ name: 'to',
462
+ message: 'Replacement string:'
463
+ },
464
+ {
465
+ type: 'confirm',
466
+ name: 'caseSensitive',
467
+ message: 'Case sensitive?',
468
+ default: true
469
+ },
470
+ {
471
+ type: 'confirm',
472
+ name: 'regex',
473
+ message: 'Use regex pattern?',
474
+ default: false
475
+ }
476
+ ]);
477
+
478
+ return [{ from, to, caseSensitive, regex }];
479
+ }
480
+
481
+ async function getBatchStringReplacements() {
482
+ console.log(chalk.yellow('Enter replacements (one per line, format: "from|to"):'));
483
+ console.log(chalk.gray('Example: "hello|world"'));
484
+ console.log(chalk.gray('Press Enter twice when done:\n'));
485
+
486
+ const replacements = [];
487
+ let line;
488
+
489
+ // Simple batch input (in real implementation, you'd want better UI)
490
+ const { batchInput } = await inquirer.prompt([
491
+ {
492
+ type: 'editor',
493
+ name: 'batchInput',
494
+ message: 'Enter batch replacements (one per line, format: "from|to"):'
495
+ }
496
+ ]);
497
+
498
+ const lines = batchInput.split('\n');
499
+ for (const line of lines) {
500
+ const parts = line.split('|');
501
+ if (parts.length === 2 && parts[0].trim() && parts[1].trim()) {
502
+ replacements.push({
503
+ from: parts[0].trim(),
504
+ to: parts[1].trim(),
505
+ caseSensitive: true,
506
+ regex: false
507
+ });
508
+ }
509
+ }
510
+
511
+ return replacements;
512
+ }
513
+
514
+ async function getRegexStringReplacement() {
515
+ const { pattern, replacement, flags } = await inquirer.prompt([
516
+ {
517
+ type: 'input',
518
+ name: 'pattern',
519
+ message: 'Regex pattern:'
520
+ },
521
+ {
522
+ type: 'input',
523
+ name: 'replacement',
524
+ message: 'Replacement string:'
525
+ },
526
+ {
527
+ type: 'input',
528
+ name: 'flags',
529
+ message: 'Regex flags (optional):',
530
+ default: 'g'
531
+ }
532
+ ]);
533
+
534
+ return [{ from: pattern, to: replacement, regex: true, caseSensitive: false }];
535
+ }
536
+
537
+ async function decodeAPK(apkPath, outputDir) {
538
+ const zip = new AdmZip(apkPath);
539
+ zip.extractAllTo(outputDir, true);
540
+ }
541
+
542
+ async function rebuildAPK(decodedPath, outputPath) {
543
+ const zip = new AdmZip();
544
+
545
+ const files = await getAllFiles(decodedPath);
546
+ for (const file of files) {
547
+ const relativePath = path.relative(decodedPath, file);
548
+ const content = await fs.readFile(file);
549
+ zip.addFile(relativePath, content);
550
+ }
551
+
552
+ await fs.ensureDir(path.dirname(outputPath));
553
+ zip.writeZip(outputPath);
554
+ }
555
+
556
+ async function getAllFiles(dir) {
557
+ const files = [];
558
+ const items = await fs.readdir(dir);
559
+
560
+ for (const item of items) {
561
+ const fullPath = path.join(dir, item);
562
+ const stat = await fs.stat(fullPath);
563
+
564
+ if (stat.isDirectory()) {
565
+ const subFiles = await getAllFiles(fullPath);
566
+ files.push(...subFiles);
567
+ } else {
568
+ files.push(fullPath);
569
+ }
570
+ }
571
+
572
+ return files;
573
+ }
574
+
575
+ async function showModificationSummary(decodedPath, outputPath) {
576
+ const stats = await fs.stat(outputPath);
577
+ console.log(chalk.bold('\n📊 Modification Summary:'));
578
+ console.log(chalk.gray(` • Output file: ${outputPath}`));
579
+ console.log(chalk.gray(` • Final size: ${formatBytes(stats.size)}`));
580
+ console.log(chalk.gray(` • Files in APK: ${(await getAllFiles(decodedPath)).length}`));
581
+ }
582
+
583
+ function formatBytes(bytes) {
584
+ if (bytes === 0) return '0 Bytes';
585
+ const k = 1024;
586
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
587
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
588
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
589
+ }
590
+
591
+ // Placeholder functions for incomplete implementations
592
+ async function changeAppLabel() { console.log(chalk.yellow('⚠️ Feature coming soon')); }
593
+ async function changeVersion() { console.log(chalk.yellow('⚠️ Feature coming soon')); }
594
+ async function changeSDKVersions() { console.log(chalk.yellow('⚠️ Feature coming soon')); }
595
+ async function toggleDebugMode() { console.log(chalk.yellow('⚠️ Feature coming soon')); }
596
+ async function changePackageNameFromManifest() { console.log(chalk.yellow('⚠️ Feature coming soon')); }
597
+ async function addPermission() { console.log(chalk.yellow('⚠️ Feature coming soon')); }
598
+ async function removePermissionFromManifest() { console.log(chalk.yellow('⚠️ Feature coming soon')); }
599
+ async function addPermissionBypass(decodedPath, manifestData, smaliEditor) {
600
+ const permissions = manifestData?.permissions?.map(p => p.name) || [];
601
+ if (permissions.length === 0) {
602
+ console.log(chalk.yellow('⚠️ No permissions found to bypass'));
603
+ return;
604
+ }
605
+
606
+ const { selectedPermissions } = await inquirer.prompt([
607
+ {
608
+ type: 'checkbox',
609
+ name: 'selectedPermissions',
610
+ message: 'Select permissions to bypass:',
611
+ choices: permissions.map(p => ({ name: p, value: p }))
612
+ }
613
+ ]);
614
+
615
+ if (selectedPermissions.length > 0) {
616
+ const spinner = ora('🔓 Adding permission bypasses...').start();
617
+ try {
618
+ const results = await smaliEditor.addPermissionBypasses(decodedPath, selectedPermissions);
619
+ spinner.succeed('Permission bypasses added!');
620
+ console.log(chalk.green(`✅ Modified ${results.modified.length} files`));
621
+ } catch (error) {
622
+ spinner.fail('Failed to add bypasses');
623
+ console.error(chalk.red('🔴 Error:'), error.message);
624
+ }
625
+ }
626
+ }
627
+
628
+ async function removePermissionChecks(decodedPath, manifestData, smaliEditor) {
629
+ const permissions = manifestData?.permissions?.map(p => p.name) || [];
630
+ if (permissions.length === 0) {
631
+ console.log(chalk.yellow('⚠️ No permissions found'));
632
+ return;
633
+ }
634
+
635
+ const { selectedPermissions } = await inquirer.prompt([
636
+ {
637
+ type: 'checkbox',
638
+ name: 'selectedPermissions',
639
+ message: 'Select permissions to remove checks for:',
640
+ choices: permissions.map(p => ({ name: p, value: p }))
641
+ }
642
+ ]);
643
+
644
+ if (selectedPermissions.length > 0) {
645
+ const spinner = ora('🗑️ Removing permission checks...').start();
646
+ try {
647
+ const results = await smaliEditor.removePermissions(decodedPath, selectedPermissions);
648
+ spinner.succeed('Permission checks removed!');
649
+ console.log(chalk.green(`✅ Modified ${results.modified.length} files`));
650
+ } catch (error) {
651
+ spinner.fail('Failed to remove checks');
652
+ console.error(chalk.red('🔴 Error:'), error.message);
653
+ }
654
+ }
655
+ }
656
+
657
+ async function listPermissions(manifestData) {
658
+ if (!manifestData || !manifestData.permissions || manifestData.permissions.length === 0) {
659
+ console.log(chalk.yellow('⚠️ No permissions found'));
660
+ return;
661
+ }
662
+
663
+ console.log(chalk.bold('\n📋 Current Permissions:'));
664
+ manifestData.permissions.forEach((perm, index) => {
665
+ const type = perm.type || 'normal';
666
+ const color = type === 'dangerous' ? chalk.red : chalk.gray;
667
+ console.log(color(` ${index + 1}. ${perm.name} (${type})`));
668
+ });
669
+ }
670
+
671
+ async function performIconReplacement(decodedPath, iconManager, iconType) {
672
+ const { iconPath } = await inquirer.prompt([
673
+ {
674
+ type: 'input',
675
+ name: 'iconPath',
676
+ message: 'Path to new icon image:',
677
+ validate: async (input) => {
678
+ if (!fs.existsSync(input)) {
679
+ return 'Icon file does not exist';
680
+ }
681
+
682
+ const validation = await iconManager.validateIcon(input);
683
+ if (!validation.valid) {
684
+ return validation.error;
685
+ }
686
+
687
+ return true;
688
+ }
689
+ }
690
+ ]);
691
+
692
+ const spinner = ora('🎨 Replacing icons...').start();
693
+
694
+ try {
695
+ const options = {
696
+ includeNotifications: iconType === 'notification' || iconType === 'launcher',
697
+ updateManifest: true
698
+ };
699
+
700
+ const results = await iconManager.replaceIcons(decodedPath, iconPath, options);
701
+
702
+ spinner.succeed('Icon replacement completed!');
703
+ console.log(chalk.green(`✅ Replaced ${results.replaced.length} icons`));
704
+
705
+ if (results.errors.length > 0) {
706
+ console.log(chalk.yellow(`⚠️ ${results.errors.length} errors occurred`));
707
+ }
708
+
709
+ } catch (error) {
710
+ spinner.fail('Icon replacement failed');
711
+ console.error(chalk.red('🔴 Error:'), error.message);
712
+ }
713
+ }
714
+
715
+ async function showIconInfo(decodedPath, iconManager) {
716
+ const spinner = ora('📊 Analyzing icons...').start();
717
+
718
+ try {
719
+ const iconInfo = await iconManager.getIconInfo(decodedPath);
720
+ spinner.succeed('Icon analysis completed!');
721
+
722
+ console.log(chalk.bold('\n📊 Icon Information:'));
723
+ console.log(chalk.gray(`• Total icons: ${iconInfo.totalIcons}`));
724
+ console.log(chalk.gray(`• Launcher icons: ${iconInfo.launcherIcons.length}`));
725
+ console.log(chalk.gray(`• Adaptive icons: ${iconInfo.adaptiveIcons.length}`));
726
+ console.log(chalk.gray(`• Notification icons: ${iconInfo.notificationIcons.length}`));
727
+
728
+ if (iconInfo.launcherIcons.length > 0) {
729
+ console.log(chalk.bold('\n📱 Launcher Icons:'));
730
+ iconInfo.launcherIcons.forEach(icon => {
731
+ console.log(chalk.gray(` • ${icon.path} (${icon.density}, ${formatBytes(icon.size)})`));
732
+ });
733
+ }
734
+
735
+ } catch (error) {
736
+ spinner.fail('Icon analysis failed');
737
+ console.error(chalk.red('🔴 Error:'), error.message);
738
+ }
739
+ }
740
+
741
+ async function extractIcons(decodedPath, iconManager) {
742
+ const { outputDir } = await inquirer.prompt([
743
+ {
744
+ type: 'input',
745
+ name: 'outputDir',
746
+ message: 'Output directory for extracted icons:',
747
+ default: './extracted_icons'
748
+ }
749
+ ]);
750
+
751
+ const spinner = ora('📤 Extracting icons...').start();
752
+
753
+ try {
754
+ const results = await iconManager.extractIcons(decodedPath, outputDir);
755
+ spinner.succeed('Icon extraction completed!');
756
+
757
+ console.log(chalk.green(`✅ Extracted ${results.extracted.length} icons`));
758
+ console.log(chalk.blue(`📁 Output directory: ${outputDir}`));
759
+
760
+ if (results.errors.length > 0) {
761
+ console.log(chalk.yellow(`⚠️ ${results.errors.length} errors occurred`));
762
+ }
763
+
764
+ } catch (error) {
765
+ spinner.fail('Icon extraction failed');
766
+ console.error(chalk.red('🔴 Error:'), error.message);
767
+ }
768
+ }
769
+
770
+ async function replaceNetworkURLs(decodedPath, networkAnalysis, smaliEditor) {
771
+ const urls = networkAnalysis.urls.map(u => u.url).slice(0, 10);
772
+ if (urls.length === 0) {
773
+ console.log(chalk.yellow('⚠️ No URLs found'));
774
+ return;
775
+ }
776
+
777
+ const { selectedUrls, newUrl } = await inquirer.prompt([
778
+ {
779
+ type: 'checkbox',
780
+ name: 'selectedUrls',
781
+ message: 'Select URLs to replace:',
782
+ choices: urls.map(url => ({ name: url, value: url }))
783
+ },
784
+ {
785
+ type: 'input',
786
+ name: 'newUrl',
787
+ message: 'Replacement URL:',
788
+ default: 'https://example.com'
789
+ }
790
+ ]);
791
+
792
+ if (selectedUrls.length > 0) {
793
+ const replacements = selectedUrls.map(url => ({
794
+ from: url,
795
+ to: newUrl,
796
+ caseSensitive: false,
797
+ regex: false
798
+ }));
799
+
800
+ const spinner = ora('🔄 Replacing URLs...').start();
801
+ try {
802
+ const results = await smaliEditor.replaceStrings(decodedPath, replacements);
803
+ spinner.succeed('URL replacement completed!');
804
+ console.log(chalk.green(`✅ Modified ${results.modified.length} files`));
805
+ } catch (error) {
806
+ spinner.fail('URL replacement failed');
807
+ console.error(chalk.red('🔴 Error:'), error.message);
808
+ }
809
+ }
810
+ }
811
+
812
+ async function replaceAPIKeys(decodedPath, networkAnalysis, smaliEditor) {
813
+ const apiKeys = networkAnalysis.apiKeys.slice(0, 10);
814
+ if (apiKeys.length === 0) {
815
+ console.log(chalk.yellow('⚠️ No API keys found'));
816
+ return;
817
+ }
818
+
819
+ const { selectedKeys, newKey } = await inquirer.prompt([
820
+ {
821
+ type: 'checkbox',
822
+ name: 'selectedKeys',
823
+ message: 'Select API keys to replace:',
824
+ choices: apiKeys.map(key => ({
825
+ name: `${key.key} (${key.type}, ${key.confidence})`,
826
+ value: key.key
827
+ }))
828
+ },
829
+ {
830
+ type: 'input',
831
+ name: 'newKey',
832
+ message: 'Replacement API key:',
833
+ default: 'REPLACED_API_KEY'
834
+ }
835
+ ]);
836
+
837
+ if (selectedKeys.length > 0) {
838
+ const replacements = selectedKeys.map(key => ({
839
+ from: key,
840
+ to: newKey,
841
+ caseSensitive: false,
842
+ regex: false
843
+ }));
844
+
845
+ const spinner = ora('🔑 Replacing API keys...').start();
846
+ try {
847
+ const results = await smaliEditor.replaceStrings(decodedPath, replacements);
848
+ spinner.succeed('API key replacement completed!');
849
+ console.log(chalk.green(`✅ Modified ${results.modified.length} files`));
850
+ } catch (error) {
851
+ spinner.fail('API key replacement failed');
852
+ console.error(chalk.red('🔴 Error:'), error.message);
853
+ }
854
+ }
855
+ }
856
+
857
+ async function bypassSSL(decodedPath, smaliEditor) {
858
+ const spinner = ora('🔓 Bypassing SSL verification...').start();
859
+
860
+ try {
861
+ const results = await smaliEditor.removeSSLVerification(decodedPath);
862
+ spinner.succeed('SSL bypass completed!');
863
+ console.log(chalk.green(`✅ Modified ${results.modified.length} files`));
864
+ } catch (error) {
865
+ spinner.fail('SSL bypass failed');
866
+ console.error(chalk.red('🔴 Error:'), error.message);
867
+ }
868
+ }
869
+
870
+ async function extractStrings(decodedPath, smaliEditor) {
871
+ const options = await inquirer.prompt([
872
+ {
873
+ type: 'number',
874
+ name: 'minLength',
875
+ message: 'Minimum string length:',
876
+ default: 3
877
+ },
878
+ {
879
+ type: 'number',
880
+ name: 'maxLength',
881
+ message: 'Maximum string length:',
882
+ default: 100
883
+ },
884
+ {
885
+ type: 'confirm',
886
+ name: 'includeUrls',
887
+ message: 'Include URLs?',
888
+ default: true
889
+ },
890
+ {
891
+ type: 'confirm',
892
+ name: 'includeEmails',
893
+ message: 'Include emails?',
894
+ default: true
895
+ }
896
+ ]);
897
+
898
+ const spinner = ora('📤 Extracting strings...').start();
899
+
900
+ try {
901
+ const strings = await smaliEditor.extractStrings(decodedPath, options);
902
+ spinner.succeed('String extraction completed!');
903
+
904
+ console.log(chalk.green(`✅ Found ${strings.length} strings`));
905
+
906
+ // Save to file
907
+ const outputPath = './extracted_strings.json';
908
+ await fs.writeJson(outputPath, strings, { spaces: 2 });
909
+ console.log(chalk.blue(`📁 Saved to: ${outputPath}`));
910
+
911
+ // Show sample
912
+ console.log(chalk.bold('\n📝 Sample strings:'));
913
+ strings.slice(0, 10).forEach((str, index) => {
914
+ console.log(chalk.gray(` ${index + 1}. "${str.value}" (${path.basename(str.file)})`));
915
+ });
916
+
917
+ if (strings.length > 10) {
918
+ console.log(chalk.gray(` ... and ${strings.length - 10} more strings`));
919
+ }
920
+
921
+ } catch (error) {
922
+ spinner.fail('String extraction failed');
923
+ console.error(chalk.red('🔴 Error:'), error.message);
924
+ }
925
+ }
926
+
927
+ async function searchPatterns(decodedPath, smaliEditor) {
928
+ const { patternName, regex, flags } = await inquirer.prompt([
929
+ {
930
+ type: 'input',
931
+ name: 'patternName',
932
+ message: 'Pattern name (for reference):'
933
+ },
934
+ {
935
+ type: 'input',
936
+ name: 'regex',
937
+ message: 'Regex pattern to search:'
938
+ },
939
+ {
940
+ type: 'input',
941
+ name: 'flags',
942
+ message: 'Regex flags:',
943
+ default: 'g'
944
+ }
945
+ ]);
946
+
947
+ const patterns = [{ name: patternName, regex, flags }];
948
+
949
+ const spinner = ora('🔍 Searching patterns...').start();
950
+
951
+ try {
952
+ const results = await smaliEditor.searchPatterns(decodedPath, patterns);
953
+ spinner.succeed('Pattern search completed!');
954
+
955
+ console.log(chalk.green(`✅ Found matches in ${results.length} files`));
956
+
957
+ results.forEach(result => {
958
+ console.log(chalk.blue(`\n📄 ${result.file}:`));
959
+ console.log(chalk.gray(` Pattern: ${result.pattern}`));
960
+ console.log(chalk.gray(` Matches: ${result.count}`));
961
+ result.matches.slice(0, 3).forEach(match => {
962
+ console.log(chalk.gray(` - ${match.substring(0, 100)}${match.length > 100 ? '...' : ''}`));
963
+ });
964
+ });
965
+
966
+ } catch (error) {
967
+ spinner.fail('Pattern search failed');
968
+ console.error(chalk.red('🔴 Error:'), error.message);
969
+ }
970
+ }
971
+
972
+ async function showDetailedNetworkAnalysis(networkAnalysis) {
973
+ console.log(chalk.bold('\n🔬 Detailed Network Analysis:'));
974
+
975
+ console.log(chalk.blue('\n🌐 URLs:'));
976
+ networkAnalysis.urls.slice(0, 10).forEach(url => {
977
+ console.log(chalk.gray(` • ${url.url} (${url.type})`));
978
+ });
979
+
980
+ if (networkAnalysis.apiKeys.length > 0) {
981
+ console.log(chalk.red('\n🔑 API Keys:'));
982
+ networkAnalysis.apiKeys.forEach(key => {
983
+ console.log(chalk.red(` • ${key.key.substring(0, 20)}... (${key.type}, ${key.confidence})`));
984
+ });
985
+ }
986
+
987
+ if (networkAnalysis.security.recommendations.length > 0) {
988
+ console.log(chalk.yellow('\n💡 Security Recommendations:'));
989
+ networkAnalysis.security.recommendations.forEach(rec => {
990
+ console.log(chalk.yellow(` • ${rec}`));
991
+ });
992
+ }
993
+ }
994
+
995
+ async function changePackageName(decodedPath, manifestData, smaliEditor) {
996
+ if (!manifestData || !manifestData.package) {
997
+ console.log(chalk.yellow('⚠️ Could not determine current package name'));
998
+ return;
999
+ }
1000
+
1001
+ const { newPackage } = await inquirer.prompt([
1002
+ {
1003
+ type: 'input',
1004
+ name: 'newPackage',
1005
+ message: 'New package name:',
1006
+ default: manifestData.package
1007
+ }
1008
+ ]);
1009
+
1010
+ if (newPackage === manifestData.package) {
1011
+ console.log(chalk.yellow('⚠️ Package name unchanged'));
1012
+ return;
1013
+ }
1014
+
1015
+ const spinner = ora('📦 Changing package name...').start();
1016
+
1017
+ try {
1018
+ const results = await smaliEditor.replacePackageName(decodedPath, manifestData.package, newPackage);
1019
+ spinner.succeed('Package name changed!');
1020
+ console.log(chalk.green(`✅ Modified ${results.modified.length} files`));
1021
+ } catch (error) {
1022
+ spinner.fail('Package name change failed');
1023
+ console.error(chalk.red('🔴 Error:'), error.message);
1024
+ }
1025
+ }
1026
+
1027
+ async function quickModification(decodedPath, manifestData, tools) {
1028
+ console.log(chalk.yellow('\n⚡ Quick Modify Mode'));
1029
+ console.log(chalk.gray('Performing basic optimizations...\n'));
1030
+
1031
+ // Remove debug files
1032
+ await removeDebugFiles(decodedPath);
1033
+
1034
+ // Basic string replacements
1035
+ const quickReplacements = [
1036
+ { from: 'debug', to: 'release', caseSensitive: false },
1037
+ { from: 'DEBUG', to: 'RELEASE', caseSensitive: true }
1038
+ ];
1039
+
1040
+ try {
1041
+ const results = await tools.smaliEditor.replaceStrings(decodedPath, quickReplacements);
1042
+ console.log(chalk.green(`✅ Quick modifications: ${results.modified.length} files changed`));
1043
+ } catch (error) {
1044
+ console.log(chalk.yellow(`⚠️ Quick modifications skipped: ${error.message}`));
1045
+ }
1046
+ }
1047
+
1048
+ async function removeDebugFiles(decodedPath) {
1049
+ const debugPatterns = [
1050
+ '**/debug/**',
1051
+ '**/test/**',
1052
+ '**/*.map',
1053
+ '**/META-INF/debug/**'
1054
+ ];
1055
+
1056
+ let removedCount = 0;
1057
+
1058
+ for (const pattern of debugPatterns) {
1059
+ // Simple implementation
1060
+ const metaInfDir = path.join(decodedPath, 'META-INF');
1061
+ if (fs.existsSync(metaInfDir)) {
1062
+ const metaFiles = await fs.readdir(metaInfDir);
1063
+ for (const file of metaFiles) {
1064
+ if (file.includes('debug') || file.includes('test')) {
1065
+ await fs.remove(path.join(metaInfDir, file));
1066
+ removedCount++;
1067
+ }
1068
+ }
1069
+ }
1070
+ }
1071
+
1072
+ if (removedCount > 0) {
1073
+ console.log(chalk.green(`✅ Removed ${removedCount} debug files`));
1074
+ }
1075
+ }
1076
+
1077
+ module.exports = modifyCommand;