Comparing version 0.9.4 to 0.9.5
@@ -83,11 +83,3 @@ "use strict"; | ||
}).catch((error) => { | ||
const message = (error.all || (error.toString().replace(/^Error: /, ''))) | ||
// Replace mentions of the (sometimes very long) temporary directory path | ||
.replace(new RegExp(tmpDir, 'g'), chalk_1.default `{bold <tmp_dir>}`) | ||
// Highlight (usually relevant) warning lines in Apktool output | ||
.replace(/^W: .+$/gm, line => chalk_1.default `{yellow ${line}}`) | ||
// De-emphasize Apktool info lines | ||
.replace(/^I: .+$/gm, line => chalk_1.default `{dim ${line}}`) | ||
// De-emphasize (not very helpful) Apktool "could not exec" error message | ||
.replace(/^.+brut\.common\.BrutException: could not exec.+$/gm, line => chalk_1.default `{dim ${line}}`); | ||
const message = getErrorMessage(error, { tmpDir }); | ||
console.error([ | ||
@@ -99,4 +91,4 @@ '', | ||
'', | ||
` The full logs of all commands are available here:`, | ||
` ${path_1.default.join(tmpDir, 'logs')}`, | ||
` The full logs of all commands are available here:`, | ||
` ${path_1.default.join(tmpDir, 'logs')}`, | ||
'' | ||
@@ -109,2 +101,18 @@ ].join('\n')); | ||
} | ||
function getErrorMessage(error, { tmpDir }) { | ||
if (error.all) | ||
return formatCommandError(error.all, { tmpDir }); | ||
return error.stack; | ||
} | ||
function formatCommandError(error, { tmpDir }) { | ||
return error | ||
// Replace mentions of the (sometimes very long) temporary directory path | ||
.replace(new RegExp(tmpDir, 'g'), chalk_1.default `{bold <tmp_dir>}`) | ||
// Highlight (usually relevant) warning lines in Apktool output | ||
.replace(/^W: .+$/gm, line => chalk_1.default `{yellow ${line}}`) | ||
// De-emphasize Apktool info lines | ||
.replace(/^I: .+$/gm, line => chalk_1.default `{dim ${line}}`) | ||
// De-emphasize (not very helpful) Apktool "could not exec" error message | ||
.replace(/^.+brut\.common\.BrutException: could not exec.+$/gm, line => chalk_1.default `{dim ${line}}`); | ||
} | ||
function showHelp() { | ||
@@ -111,0 +119,0 @@ console.log(chalk_1.default ` |
@@ -27,4 +27,4 @@ "use strict"; | ||
const path = __importStar(require("path")); | ||
const events_1 = require("events"); | ||
const fs = __importStar(require("./utils/fs")); | ||
const rxjs_1 = require("rxjs"); | ||
const listr_1 = __importDefault(require("listr")); | ||
@@ -36,2 +36,3 @@ const chalk_1 = __importDefault(require("chalk")); | ||
const disable_certificate_pinning_1 = __importDefault(require("./tasks/disable-certificate-pinning")); | ||
const observe_async_1 = __importDefault(require("./utils/observe-async")); | ||
function patchApk(taskOptions) { | ||
@@ -69,14 +70,10 @@ const { inputPath, outputPath, tmpDir, apktool, uberApkSigner, wait, } = taskOptions; | ||
enabled: () => wait, | ||
task: (_) => { | ||
return new rxjs_1.Observable(subscriber => { | ||
process.stdin.setEncoding('utf-8'); | ||
process.stdin.setRawMode(true); | ||
subscriber.next("Press any key to continue."); | ||
process.stdin.once('data', () => { | ||
subscriber.complete(); | ||
process.stdin.setRawMode(false); | ||
process.stdin.pause(); | ||
}); | ||
}); | ||
}, | ||
task: () => observe_async_1.default(async (next) => { | ||
process.stdin.setEncoding('utf-8'); | ||
process.stdin.setRawMode(true); | ||
next('Press any key to continue.'); | ||
await events_1.once(process.stdin, 'data'); | ||
process.stdin.setRawMode(false); | ||
process.stdin.pause(); | ||
}) | ||
}, | ||
@@ -88,8 +85,10 @@ { | ||
title: 'Encoding using AAPT2', | ||
task: (_, task) => new rxjs_1.Observable(subscriber => { | ||
apktool.encode(decodeDir, tmpApkPath, true).subscribe(line => subscriber.next(line), () => { | ||
subscriber.complete(); | ||
task: (_, task) => observe_async_1.default(async (next) => { | ||
try { | ||
await apktool.encode(decodeDir, tmpApkPath, true).forEach(next); | ||
} | ||
catch (_a) { | ||
task.skip('Failed, falling back to AAPT...'); | ||
fallBackToAapt = true; | ||
}, () => subscriber.complete()); | ||
} | ||
}), | ||
@@ -106,11 +105,7 @@ }, | ||
title: 'Signing patched APK file', | ||
task: () => new rxjs_1.Observable(subscriber => { | ||
(async () => { | ||
await uberApkSigner | ||
.sign([tmpApkPath], { zipalign: true }) | ||
.forEach(line => subscriber.next(line)) | ||
.catch(error => subscriber.error(error)); | ||
await fs.copyFile(tmpApkPath, outputPath); | ||
subscriber.complete(); | ||
})(); | ||
task: () => observe_async_1.default(async (next) => { | ||
await uberApkSigner | ||
.sign([tmpApkPath], { zipalign: true }) | ||
.forEach(line => next(line)); | ||
await fs.copyFile(tmpApkPath, outputPath); | ||
}), | ||
@@ -117,0 +112,0 @@ }, |
@@ -27,3 +27,2 @@ "use strict"; | ||
const cross_zip_1 = require("@tybys/cross-zip"); | ||
const rxjs_1 = require("rxjs"); | ||
const fs = __importStar(require("./utils/fs")); | ||
@@ -34,2 +33,3 @@ const path = __importStar(require("path")); | ||
const patch_apk_1 = __importDefault(require("./patch-apk")); | ||
const observe_async_1 = __importDefault(require("./utils/observe-async")); | ||
function patchXapkBundle(options) { | ||
@@ -69,10 +69,7 @@ return patchAppBundle(options, { isXapk: true }); | ||
title: 'Signing APKs', | ||
task: () => new rxjs_1.Observable(subscriber => { | ||
(async () => { | ||
const apkFiles = await globby_1.default(path.join(bundleDir, '**/*.apk')); | ||
await uberApkSigner | ||
.sign(apkFiles, { zipalign: false }) | ||
.forEach(line => subscriber.next(line)); | ||
subscriber.complete(); | ||
})(); | ||
task: () => observe_async_1.default(async (next) => { | ||
const apkFiles = await globby_1.default(path.join(bundleDir, '**/*.apk')); | ||
await uberApkSigner | ||
.sign(apkFiles, { zipalign: false }) | ||
.forEach(line => next(line)); | ||
}), | ||
@@ -79,0 +76,0 @@ }, |
@@ -30,3 +30,3 @@ "use strict"; | ||
const escape_string_regexp_1 = __importDefault(require("escape-string-regexp")); | ||
const rxjs_1 = require("rxjs"); | ||
const observe_async_1 = __importDefault(require("../utils/observe-async")); | ||
const INTERFACE_LINE = '.implements Ljavax/net/ssl/X509TrustManager;'; | ||
@@ -57,59 +57,57 @@ /** The methods that need to be patched to disable certificate pinning. */ | ||
async function disableCertificatePinning(directoryPath, task) { | ||
return new rxjs_1.Observable(observer => { | ||
(async () => { | ||
observer.next('Finding smali files...'); | ||
// Convert Windows path (using backslashes) to POSIX path (using slashes) | ||
const directoryPathPosix = directoryPath.split(path.sep).join(path.posix.sep); | ||
const globPattern = path.posix.join(directoryPathPosix, 'smali*/**/*.smali'); | ||
const smaliFiles = await globby_1.default(globPattern); | ||
let pinningFound = false; | ||
for (const filePath of smaliFiles) { | ||
observer.next(`Scanning ${path.basename(filePath)}...`); | ||
let originalContent = await fs.readFile(filePath, 'utf-8'); | ||
// Don't scan classes that don't implement the interface | ||
if (!originalContent.includes(INTERFACE_LINE)) | ||
continue; | ||
return observe_async_1.default(async (next) => { | ||
next('Finding smali files...'); | ||
// Convert Windows path (using backslashes) to POSIX path (using slashes) | ||
const directoryPathPosix = directoryPath.split(path.sep).join(path.posix.sep); | ||
const globPattern = path.posix.join(directoryPathPosix, 'smali*/**/*.smali'); | ||
let pinningFound = false; | ||
for await (const filePathChunk of globby_1.default.stream(globPattern)) { | ||
// Required because Node.js streams are not typed as generics | ||
const filePath = filePathChunk; | ||
next(`Scanning ${path.basename(filePath)}...`); | ||
let originalContent = await fs.readFile(filePath, 'utf-8'); | ||
// Don't scan classes that don't implement the interface | ||
if (!originalContent.includes(INTERFACE_LINE)) | ||
continue; | ||
if (os.type() === 'Windows_NT') { | ||
// Replace CRLF with LF, so that patches can just use '\n' | ||
originalContent = originalContent.replace(/\r\n/g, '\n'); | ||
} | ||
let patchedContent = originalContent; | ||
for (const pattern of METHOD_PATTERNS) { | ||
patchedContent = patchedContent.replace(pattern, (_, openingLine, body, closingLine) => { | ||
const bodyLines = body | ||
.split('\n') | ||
.map(line => line.replace(/^ /, '')); | ||
const fixLines = openingLine.includes('getAcceptedIssuers') | ||
? RETURN_EMPTY_ARRAY_FIX | ||
: RETURN_VOID_FIX; | ||
const patchedBodyLines = [ | ||
'# inserted by apk-mitm to disable certificate pinning', | ||
...fixLines, | ||
'', | ||
'# commented out by apk-mitm to disable old method body', | ||
'# ', | ||
...bodyLines.map(line => `# ${line}`) | ||
]; | ||
return [ | ||
openingLine, | ||
...patchedBodyLines.map(line => ` ${line}`), | ||
closingLine, | ||
].map(line => line.trimEnd()).join('\n'); | ||
}); | ||
} | ||
if (originalContent !== patchedContent) { | ||
pinningFound = true; | ||
if (os.type() === 'Windows_NT') { | ||
// Replace CRLF with LF, so that patches can just use '\n' | ||
originalContent = originalContent.replace(/\r\n/g, '\n'); | ||
// Replace LF with CRLF again | ||
patchedContent = patchedContent.replace(/\n/g, '\r\n'); | ||
} | ||
let patchedContent = originalContent; | ||
for (const pattern of METHOD_PATTERNS) { | ||
patchedContent = patchedContent.replace(pattern, (_, openingLine, body, closingLine) => { | ||
const bodyLines = body | ||
.split('\n') | ||
.map(line => line.replace(/^ /, '')); | ||
const fixLines = openingLine.includes('getAcceptedIssuers') | ||
? RETURN_EMPTY_ARRAY_FIX | ||
: RETURN_VOID_FIX; | ||
const patchedBodyLines = [ | ||
'# inserted by apk-mitm to disable certificate pinning', | ||
...fixLines, | ||
'', | ||
'# commented out by apk-mitm to disable old method body', | ||
'# ', | ||
...bodyLines.map(line => `# ${line}`) | ||
]; | ||
return [ | ||
openingLine, | ||
...patchedBodyLines.map(line => ` ${line}`), | ||
closingLine, | ||
].map(line => line.trimEnd()).join('\n'); | ||
}); | ||
} | ||
if (originalContent !== patchedContent) { | ||
pinningFound = true; | ||
if (os.type() === 'Windows_NT') { | ||
// Replace LF with CRLF again | ||
patchedContent = patchedContent.replace(/\n/g, '\r\n'); | ||
} | ||
await fs.writeFile(filePath, patchedContent); | ||
} | ||
await fs.writeFile(filePathChunk, patchedContent); | ||
} | ||
if (!pinningFound) | ||
task.skip('No certificate pinning logic found.'); | ||
observer.complete(); | ||
})(); | ||
} | ||
if (!pinningFound) | ||
task.skip('No certificate pinning logic found.'); | ||
}); | ||
} | ||
exports.default = disableCertificatePinning; |
@@ -1,81 +0,55 @@ | ||
'use strict' | ||
var __importStar = | ||
(this && this.__importStar) || | ||
function (mod) { | ||
if (mod && mod.__esModule) return mod | ||
var result = {} | ||
if (mod != null) | ||
for (var k in mod) | ||
if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k] | ||
result['default'] = mod | ||
return result | ||
} | ||
Object.defineProperty(exports, '__esModule', { value: true }) | ||
const fs = __importStar(require('fs')) | ||
const fs_1 = require('fs') | ||
const pathUtils = __importStar(require('path')) | ||
const envPaths = require('env-paths') | ||
const rxjs_1 = require('rxjs') | ||
const followRedirects = require('follow-redirects') | ||
const { https } = followRedirects | ||
const cachePath = envPaths('apk-mitm', { suffix: '' }).cache | ||
function createToolDownloadTask(tool) { | ||
return { | ||
title: `Downloading ${tool.name} ${tool.version.name}`, | ||
task: (_, task) => { | ||
if (!tool.version.downloadUrl) return task.skip('Using custom version') | ||
const fileName = `${tool.name}-${tool.version.name}.jar` | ||
return downloadFile(task, tool.version.downloadUrl, fileName) | ||
}, | ||
} | ||
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fs = __importStar(require("./fs")); | ||
const rxjs_1 = require("rxjs"); | ||
const followRedirects = require("follow-redirects"); | ||
const { https } = followRedirects; | ||
function downloadFile(url, path) { | ||
return new rxjs_1.Observable(subscriber => { | ||
https.get(url, response => { | ||
if (response.statusCode !== 200) { | ||
const error = new Error(`The URL "${url}" returned status code ${response.statusCode}, expected 200.`); | ||
// Cancel download with error | ||
response.destroy(error); | ||
} | ||
const fileStream = fs.createWriteStream(path); | ||
const totalLength = parseInt(response.headers['content-length']); | ||
let currentLength = 0; | ||
const reportProgress = () => { | ||
const percentage = currentLength / totalLength; | ||
subscriber.next(`${(percentage * 100).toFixed(2)}% done (${formatBytes(currentLength)} / ${formatBytes(totalLength)} MB)`); | ||
}; | ||
reportProgress(); | ||
response.pipe(fileStream); | ||
response.on('data', (chunk) => { | ||
currentLength += chunk.byteLength; | ||
reportProgress(); | ||
}); | ||
response.on('error', error => subscriber.error(error)); | ||
fileStream.on('close', () => subscriber.complete()); | ||
}).on('error', error => subscriber.error(error)); | ||
}); | ||
} | ||
exports.createToolDownloadTask = createToolDownloadTask | ||
function downloadFile(task, url, fileName) { | ||
return new rxjs_1.Observable(subscriber => { | ||
;(async () => { | ||
const finalFilePath = getCachedPath(fileName) | ||
if (fs.existsSync(finalFilePath)) { | ||
task.skip('Version already downloaded!') | ||
subscriber.complete() | ||
return | ||
} | ||
// Ensure cache directory exists | ||
await fs_1.promises.mkdir(cachePath, { recursive: true }) | ||
// Prevent file corruption by using a temporary file name | ||
const downloadFilePath = finalFilePath + '.dl' | ||
https | ||
.get(url, response => { | ||
const fileStream = fs.createWriteStream(downloadFilePath) | ||
const totalLength = parseInt(response.headers['content-length']) | ||
let currentLength = 0 | ||
const reportProgress = () => { | ||
const percentage = currentLength / totalLength | ||
subscriber.next( | ||
`${(percentage * 100).toFixed(2)}% done (${formatBytes( | ||
currentLength, | ||
)} / ${formatBytes(totalLength)} MB)`, | ||
) | ||
} | ||
reportProgress() | ||
response.pipe(fileStream) | ||
response.on('data', chunk => { | ||
currentLength += chunk.byteLength | ||
reportProgress() | ||
}) | ||
fileStream.on('close', async () => { | ||
await fs_1.promises.rename(downloadFilePath, finalFilePath) | ||
subscriber.complete() | ||
}) | ||
}) | ||
.on('error', subscriber.error) | ||
})() | ||
}) | ||
} | ||
exports.default = downloadFile | ||
function getCachedPath(name) { | ||
return pathUtils.join(cachePath, name) | ||
} | ||
exports.getCachedPath = getCachedPath | ||
exports.default = downloadFile; | ||
function formatBytes(bytes) { | ||
return (bytes / 1000000).toFixed(2) | ||
return (bytes / 1000000).toFixed(2); | ||
} |
@@ -21,11 +21,12 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getCachedPath = void 0; | ||
const fs = __importStar(require("fs")); | ||
const fs_1 = require("fs"); | ||
const fs = __importStar(require("./fs")); | ||
const pathUtils = __importStar(require("path")); | ||
const envPaths = require("env-paths"); | ||
const rxjs_1 = require("rxjs"); | ||
const followRedirects = require("follow-redirects"); | ||
const { https } = followRedirects; | ||
const observe_async_1 = __importDefault(require("./observe-async")); | ||
const download_file_1 = __importDefault(require("./download-file")); | ||
const cachePath = envPaths('apk-mitm', { suffix: '' }).cache; | ||
@@ -39,3 +40,3 @@ function createToolDownloadTask(tool) { | ||
const fileName = `${tool.name}-${tool.version.name}.jar`; | ||
return downloadFile(task, tool.version.downloadUrl, fileName); | ||
return downloadCachedFile(task, tool.version.downloadUrl, fileName); | ||
}, | ||
@@ -45,40 +46,15 @@ }; | ||
exports.default = createToolDownloadTask; | ||
function downloadFile(task, url, fileName) { | ||
return new rxjs_1.Observable(subscriber => { | ||
(async () => { | ||
const finalFilePath = getCachedPath(fileName); | ||
if (fs.existsSync(finalFilePath)) { | ||
task.skip('Version already downloaded!'); | ||
subscriber.complete(); | ||
return; | ||
} | ||
// Ensure cache directory exists | ||
await fs_1.promises.mkdir(cachePath, { recursive: true }); | ||
// Prevent file corruption by using a temporary file name | ||
const downloadFilePath = finalFilePath + '.dl'; | ||
https.get(url, response => { | ||
if (response.statusCode !== 200) { | ||
const error = new Error(`The URL "${url}" returned status code ${response.statusCode}, expected 200.`); | ||
// Cancel download with error | ||
response.destroy(error); | ||
} | ||
const fileStream = fs.createWriteStream(downloadFilePath); | ||
const totalLength = parseInt(response.headers['content-length']); | ||
let currentLength = 0; | ||
const reportProgress = () => { | ||
const percentage = currentLength / totalLength; | ||
subscriber.next(`${(percentage * 100).toFixed(2)}% done (${formatBytes(currentLength)} / ${formatBytes(totalLength)} MB)`); | ||
}; | ||
reportProgress(); | ||
response.pipe(fileStream); | ||
response.on('data', (chunk) => { | ||
currentLength += chunk.byteLength; | ||
reportProgress(); | ||
}); | ||
fileStream.on('close', async () => { | ||
await fs_1.promises.rename(downloadFilePath, finalFilePath); | ||
subscriber.complete(); | ||
}); | ||
}).on('error', error => subscriber.error(error)); | ||
})(); | ||
function downloadCachedFile(task, url, fileName) { | ||
return observe_async_1.default(async (next) => { | ||
const finalFilePath = getCachedPath(fileName); | ||
if (await fs.exists(finalFilePath)) { | ||
task.skip('Version already downloaded!'); | ||
return; | ||
} | ||
// Ensure cache directory exists | ||
await fs.mkdir(cachePath, { recursive: true }); | ||
// Prevent file corruption by using a temporary file name | ||
const downloadFilePath = finalFilePath + '.dl'; | ||
await download_file_1.default(url, downloadFilePath).forEach(next); | ||
await fs.rename(downloadFilePath, finalFilePath); | ||
}); | ||
@@ -90,4 +66,1 @@ } | ||
exports.getCachedPath = getCachedPath; | ||
function formatBytes(bytes) { | ||
return (bytes / 1000000).toFixed(2); | ||
} |
@@ -21,31 +21,32 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fs = __importStar(require("../utils/fs")); | ||
const pathUtils = __importStar(require("path")); | ||
const rxjs_1 = require("rxjs"); | ||
const observe_async_1 = __importDefault(require("./observe-async")); | ||
function observeProcess(process, logName) { | ||
return new rxjs_1.Observable(subscriber => { | ||
(async () => { | ||
await fs.mkdir('logs', { recursive: true }); | ||
const fileName = pathUtils.join('logs', `${logName}.log`); | ||
const failedFileName = pathUtils.join('logs', `${logName}.failed.log`); | ||
const stream = fs.createWriteStream(fileName); | ||
process | ||
.then(() => { | ||
stream.close(); | ||
subscriber.complete(); | ||
}) | ||
.catch(async (error) => { | ||
stream.close(); | ||
await fs.rename(fileName, failedFileName); | ||
subscriber.error(error); | ||
}); | ||
process.stdout.on('data', (data) => { | ||
subscriber.next(data.toString().trim()); | ||
stream.write(data); | ||
}); | ||
process.stderr.on('data', (data) => stream.write(data)); | ||
})(); | ||
return observe_async_1.default(async (next) => { | ||
await fs.mkdir('logs', { recursive: true }); | ||
const fileName = pathUtils.join('logs', `${logName}.log`); | ||
const failedFileName = pathUtils.join('logs', `${logName}.failed.log`); | ||
const stream = fs.createWriteStream(fileName); | ||
process.stdout.on('data', (data) => { | ||
next(data.toString().trim()); | ||
stream.write(data); | ||
}); | ||
process.stderr.on('data', (data) => stream.write(data)); | ||
try { | ||
await process; | ||
} | ||
catch (error) { | ||
await fs.rename(fileName, failedFileName); | ||
throw error; | ||
} | ||
finally { | ||
stream.close(); | ||
} | ||
}); | ||
} | ||
exports.default = observeProcess; |
@@ -13,3 +13,3 @@ { | ||
], | ||
"version": "0.9.4", | ||
"version": "0.9.5", | ||
"license": "MIT", | ||
@@ -16,0 +16,0 @@ "scripts": { |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
24
3
49678
966