frida-js
Version:
Pure-JS bindings to control Frida from node.js & browsers.
103 lines • 4.96 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadFridaServer = downloadFridaServer;
exports.getFridaReleaseDetails = getFridaReleaseDetails;
exports.calculateFridaSRI = calculateFridaSRI;
const os = require("os");
const stream_1 = require("stream");
async function downloadFridaServer(options) {
const { version, platform, arch, sri, ghToken } = options;
if (sri && (!version || version === 'latest' || !platform || !arch)) {
throw new Error('SRI cannot be used to download Frida unless fixed version, platform & arch values are provided');
}
const releaseDetails = await getFridaReleaseDetails(version, ghToken || process.env.GITHUB_TOKEN);
const downloadUrl = findFridaDownloadUrl(releaseDetails.assets, {
version,
platform: platform || os.platform(),
arch: arch || os.arch()
});
return fetchFridaServer(downloadUrl, sri);
}
async function getFridaReleaseDetails(version, ghToken) {
const headers = !!ghToken
? { Authorization: `token ${ghToken}` }
: {};
const response = await fetch(`https://api.github.com/repos/frida/frida/releases/${version && version !== 'latest'
? `tags/${version}`
: 'latest'}`, { headers });
if (!response.ok) {
console.warn(`Frida releases ${response.status} response, body: `, await response.text(), '\n');
throw new Error(`Frida releases request rejected with ${response.status}`);
}
return response.json();
}
/**
* Calculates an SRI value for the given parameters, on a trust-on-first-download basis.
*
* This fetches and extracts the Frida server for the given parameters, and calculates the
* SRI details for the given content. Optionally, specific preferred hash algorithms can
* be provided if required.
*
* This returns a promise, resolving to an array of strings, one for each algorithm used.
* Any of these values can be used as an SRI argument for `downloadFridaServer`.
*/
async function calculateFridaSRI(options, hashOptions = {}) {
if (!options.version || options.version === 'latest' || !options.arch || !options.platform) {
throw new Error('Cannot calculate SRI without fixed version, platform & arch values');
}
// We import SSRI on demand, just when it's required, to avoid bundling in browsers etc.
const SSRI = await Promise.resolve().then(() => require('ssri'));
const fridaStream = await downloadFridaServer(options);
const results = await SSRI.fromStream(fridaStream, hashOptions);
return Object.values(results).flatMap((hash) => hash.toString());
}
function findFridaDownloadUrl(assets, releaseOptions) {
let { version, arch, platform } = releaseOptions;
// Map some os.platform()/arch() results into Frida format:
if (platform === 'darwin')
platform = 'macos';
if (platform === 'win32')
platform = 'windows';
if (arch === 'x64') {
arch = 'x86_64';
}
;
let extension = platform === 'windows' ? 'exe\\.xz' : 'xz';
const assetRegex = new RegExp(`frida-server-[\\d.\\.]+-${platform}-${arch}\\.${extension}`);
const asset = assets.find((asset) => asset.name.match(assetRegex));
if (!asset) {
console.warn(`No Frida release asset found matching ${assetRegex.toString()}`);
throw new Error(`No ${version} frida-server download available for ${platform} ${arch}`);
}
return asset.browser_download_url;
}
async function fetchFridaServer(downloadUrl, sri) {
// We delay these imports until here, since they're not needed in most cases (where
// you're not downloading) and they can be heavy, so good to avoid in browser bundles:
const [{ XzReadableStream }, SSRI] = await Promise.all([
Promise.resolve().then(() => require('xz-decompress')),
Promise.resolve().then(() => require('ssri'))
]);
const resultStream = sri
? SSRI.integrityStream({ integrity: sri })
: new stream_1.PassThrough();
const assetDownload = await fetch(downloadUrl);
if (!assetDownload.ok) {
throw new Error(`Frida server download was unsuccessful, returned ${assetDownload.status}`);
}
if (!assetDownload.body) {
throw new Error('No body available for Frida server download');
}
// Actually start streaming the body next tick, to ensure there's time to set up
// any error handlers required on the returned stream before it begins.
setTimeout(() => {
// Decompress the .xz file to a raw file stream (no tar - it's a single file)
const decodeStream = stream_1.Readable.fromWeb(
// Node web stream & DOM web stream types aren't a perfect match so we have to cast:
new XzReadableStream(assetDownload.body));
decodeStream.pipe(resultStream);
decodeStream.on('error', (e) => resultStream.emit('error', e));
}, 0);
return resultStream;
}
//# sourceMappingURL=download-frida.js.map