@bytehide/webpack-shield
Version:
Webpack plugin for ByteHide Shield obfuscation.
208 lines (181 loc) • 7.65 kB
JavaScript
const fs = require('fs');
const https = require('https');
const crypto = require('crypto');
const path = require('path');
const packageJson = require('../package.json');
class ByteHideShieldPlugin {
constructor(options = {}) {
const projectInfo = this.getProjectInfo();
this.options = {
projectToken: options.projectToken || '',
replace: options.replace || false,
obfuscatedExtension: options.obfuscatedExtension || '.obf',
exclude: options.exclude || [],
config: options.config || {
controlFlowFlattening: true,
debugProtection: false,
devtoolsBlocking: false
},
include: options.include || ['.js', '.mjs', '.cjs', '.jsx'],
assembly: {
version: projectInfo.version,
framework: projectInfo.framework,
name: projectInfo.name,
},
integration: {
name: packageJson.name,
version: packageJson.version,
}
};
}
generateRandomID() {
return crypto.randomBytes(24).toString('hex');
}
generateWatermark() {
const uniqueId = crypto.randomBytes(4).toString('hex');
return `// _0xBHSHLD_${uniqueId}_marker`;
}
obfuscate(code, obfuscationID) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({
code: code,
processId: obfuscationID,
config: this.options.config,
assembly: this.options.assembly,
integration: this.options.integration,
});
const options = {
hostname: 'shield.microservice.bytehide.com',
port: 443,
path: `/api/start/${this.options.projectToken}/js`,
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Content-Length': Buffer.byteLength(payload, 'utf8')
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const result = JSON.parse(data);
if (res.statusCode === 200) {
const watermark = this.generateWatermark();
const outputWithWatermark = `${watermark}\n${result.output}`;
resolve(outputWithWatermark);
} else {
reject(result);
}
} catch (e) {
reject('Unexpected response format');
}
});
});
req.on('error', (error) => {
reject(error);
});
req.write(payload, 'utf8');
req.end();
});
}
apply(compiler) {
compiler.hooks.emit.tapAsync('ByteHideShieldPlugin', async (compilation, callback) => {
const tasks = [];
for (const filename in compilation.assets) {
const ext = filename.split('.').pop().toLowerCase();
if (this.options.include.includes(`.${ext}`)) {
const asset = compilation.assets[filename];
const source = asset.source();
if (source.includes('_0xBHSHLD_')) {
console.log(`Skipping already obfuscated file: ${filename}`);
continue;
}
if (this.options.exclude.some((excluded) => filename.includes(excluded))) {
console.log(`Excluding file: ${filename}`);
continue;
}
const task = this.obfuscate(source, this.generateRandomID())
.then(obfuscatedCode => {
if (this.options.replace) {
compilation.assets[filename] = {
source: () => obfuscatedCode,
size: () => obfuscatedCode.length
};
} else {
const obfuscatedFilename = filename.replace(
path.extname(filename), `${this.options.obfuscatedExtension}${path.extname(filename)}`
);
compilation.assets[obfuscatedFilename] = {
source: () => obfuscatedCode,
size: () => obfuscatedCode.length
};
}
})
.catch(error => {
console.error(`Error obfuscating ${filename}:`, error.message || error);
});
tasks.push(task);
}
}
try {
await Promise.all(tasks);
callback();
} catch (error) {
callback(error);
}
});
}
getProjectPackageJson() {
try {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
throw new Error('No se encontrĂ³ package.json en el proyecto.');
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
return packageJson;
} catch (error) {
console.error('Error al leer package.json:', error.message);
return null;
}
}
detectFramework(packageJson) {
const frameworks = {
nextjs: ['next'],
react: ['react', 'react-dom', 'next'],
angular: ['@angular/core', '@angular/cli', 'rxjs', 'zone.js'],
vue: ['vue', '@vue/cli-service', 'nuxt'],
vuejs: ['vue', '@vue/cli'],
svelte: ['svelte', '@sveltejs/kit'],
express: ['express', 'body-parser'],
nestjs: ['@nestjs/core', '@nestjs/common', '@nestjs/platform-*'],
ember: ['ember-cli', 'ember-source'],
gulp: ['gulp', 'gulp-cli'],
grunt: ['grunt', 'grunt-cli'],
webpack: ['webpack', 'webpack-cli', 'webpack-dev-server'],
rollup: ['rollup', 'rollup-plugin-*'],
javascript: [],
};
const dependencies = Object.keys(packageJson.dependencies || {});
const devDependencies = Object.keys(packageJson.devDependencies || {});
const allDependencies = [...dependencies, ...devDependencies];
for (const [framework, keywords] of Object.entries(frameworks)) {
if (keywords.some(keyword => allDependencies.includes(keyword))) {
return framework.charAt(0).toUpperCase() + framework.slice(1); // Capitalizar el nombre del framework
}
}
return 'javascript';
}
getProjectInfo(){
const packageJson = this.getProjectPackageJson();
const framework = this.detectFramework(packageJson);
return {
framework,
version: packageJson?.version || null,
name: packageJson?.name || null,
};
}
}
module.exports = ByteHideShieldPlugin;