4LostInSpace
This commit is contained in:
327
convert.ts
Normal file
327
convert.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
const fs = require('fs')
|
||||
const JavaScriptObfuscator = require('javascript-obfuscator');
|
||||
|
||||
const KeyPadCode = 94
|
||||
|
||||
const randomkey = (len: number) => (new Array(len)).fill(0).map(v => Math.round(Math.random() * 0xffffff))
|
||||
|
||||
interface parsedKeyedLine {
|
||||
name: string,
|
||||
data: string,
|
||||
offset: number,
|
||||
obfuscatedname: string,
|
||||
cleanLine: string
|
||||
}
|
||||
|
||||
// MARK: keys
|
||||
function parseKeysFile() {
|
||||
function parse(line: string): parsedKeyedLine {
|
||||
const re = /^\s*?(.*?)\s*?:\s*?(.*?)(?:$|\s*?#\s*?(\d+)\s*?,\s*?(.*?)\s*?$)/.exec(line)
|
||||
if (!re || re.length < 3 || !re[1]) {
|
||||
throw "Cannot Parse"
|
||||
}
|
||||
|
||||
let name = re[1].trim(),
|
||||
data = re[2].trim(),
|
||||
offset: number,
|
||||
obfuscatedname: string
|
||||
|
||||
if (re.length < 5 || re[3] === undefined) {
|
||||
offset = Math.round(Math.random() * (data.length - 2) + 1)
|
||||
// add a random multiple of data.length, no effect but may obfuscate the shift
|
||||
offset += data.length * Math.round(Math.random() * 4)
|
||||
obfuscatedname = '_0x' + Math.round(Math.random() * 0xffffff).toString(16)
|
||||
} else {
|
||||
offset = parseInt(re[3].trim())
|
||||
if (isNaN(offset)) {
|
||||
throw 'offset is NaN'
|
||||
}
|
||||
obfuscatedname = re[4].trim()
|
||||
}
|
||||
|
||||
return {
|
||||
name, data, offset, obfuscatedname,
|
||||
cleanLine: `${name}: ${data} # ${offset}, ${obfuscatedname}`
|
||||
}
|
||||
}
|
||||
|
||||
function createscript({ name, data, offset }: parsedKeyedLine) {
|
||||
const k = randomkey(data.length)
|
||||
const d = data.split('').map((ch, i) => {
|
||||
const kk = k[(i + offset) % data.length]
|
||||
if (ch === '*') {
|
||||
return KeyPadCode + kk
|
||||
}
|
||||
return ch.charCodeAt(0) + kk
|
||||
})
|
||||
return `function ${name}key() { return ${JSON.stringify(k)} }
|
||||
function ${name}data() { return ${JSON.stringify(d)} }
|
||||
function ${name}check(str) { return str === getdata(${name}key, ${name}data, ${offset}) }`
|
||||
}
|
||||
|
||||
const file = fs.readFileSync('./src/flags').toString()
|
||||
|
||||
const filelines = file.split('\n')
|
||||
let newfile = ''
|
||||
let scriptfile = ''
|
||||
let names: [string, string][] = []
|
||||
|
||||
for (let i = 0; i < filelines.length; i++) {
|
||||
const line = filelines[i]
|
||||
if (line.trim().length === 0) {
|
||||
continue
|
||||
}
|
||||
if (line.startsWith('#!')) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = parse(line)
|
||||
newfile += parsed.cleanLine + '\n'
|
||||
scriptfile += createscript(parsed) + '\n'
|
||||
names.push([`${parsed.name}check`, parsed.obfuscatedname])
|
||||
} catch (e) {
|
||||
throw `Error on line ${i + 1}: ${e}`
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync('./src/flags', newfile)
|
||||
fs.writeFileSync('./out/debug/flags', newfile)
|
||||
|
||||
return {scriptfile, names}
|
||||
}
|
||||
|
||||
// MARK: hints
|
||||
function parseHints() {
|
||||
function parse(line: string): parsedKeyedLine {
|
||||
const re = /^(.*?):\s*?(.*?)(?:$|\s*?;\s*?(.*?)(?:$|\s*?#\s*?(\d+)\s*?,\s*?(.*?)\s*?$))/.exec(line)
|
||||
if (!re || re.length < 3 || re[1] === undefined) {
|
||||
throw "Cannot Parse"
|
||||
}
|
||||
|
||||
let name = re[1].trim(),
|
||||
hintraw = re[2].trim(),
|
||||
alertraw = (re[3] ?? '').trim(),
|
||||
offset: number,
|
||||
obfuscatedname: string
|
||||
|
||||
let hint = hintraw.replaceAll('\\n', '\n')
|
||||
let alert = alertraw.replaceAll('\\n', '\n')
|
||||
let data = btoa(JSON.stringify([hint, alert]))
|
||||
|
||||
if (re.length < 6 || !re[4]) {
|
||||
offset = Math.round(Math.random() * (data.length - 2) + 1)
|
||||
// add a random multiple of data.length, no effect but may obfuscate the shift
|
||||
offset += data.length * Math.round(Math.random() * 4)
|
||||
obfuscatedname = '_0x' + Math.round(Math.random() * 0xffffff).toString(16)
|
||||
} else {
|
||||
offset = parseInt(re[4].trim())
|
||||
if (isNaN(offset)) {
|
||||
throw 'offset is NaN'
|
||||
}
|
||||
obfuscatedname = re[5].trim()
|
||||
}
|
||||
|
||||
return {
|
||||
name, data, offset, obfuscatedname,
|
||||
cleanLine: `${name}: ${hintraw} ; ${alertraw} # ${offset}, ${obfuscatedname}`
|
||||
}
|
||||
}
|
||||
|
||||
function createscript({ name, data, offset }: parsedKeyedLine) {
|
||||
const k = randomkey(data.length)
|
||||
const d = data.split('').map((ch, i) => {
|
||||
const kk = k[(i + offset) % data.length]
|
||||
if (ch === '*') {
|
||||
return KeyPadCode + kk
|
||||
}
|
||||
return ch.charCodeAt(0) + kk
|
||||
})
|
||||
return `function h_${name}key() { return ${JSON.stringify(k)} }
|
||||
function h_${name}data() { return ${JSON.stringify(d)} }
|
||||
function hint_${name}() { return getdata(h_${name}key, h_${name}data, ${offset}) }`
|
||||
}
|
||||
|
||||
const file = fs.readFileSync('./src/hints').toString()
|
||||
|
||||
const filelines = file.split('\n')
|
||||
let newfile = ''
|
||||
let scriptfile = ''
|
||||
let names: [string, string][] = []
|
||||
|
||||
for (let i = 0; i < filelines.length; i++) {
|
||||
const line = filelines[i]
|
||||
if (line.trim().length === 0 || line.startsWith('#')) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = parse(line)
|
||||
newfile += parsed.cleanLine + '\n'
|
||||
scriptfile += createscript(parsed) + '\n'
|
||||
names.push([`hint_${parsed.name}`, parsed.obfuscatedname])
|
||||
} catch (e) {
|
||||
throw `Error on line ${i + 1}: ${e}`
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync('./src/hints', newfile)
|
||||
fs.writeFileSync('./out/debug/hints', newfile)
|
||||
|
||||
return {scriptfile, names}
|
||||
}
|
||||
|
||||
// MARK: obfuscate
|
||||
function obfuscateCore(core: string, reservedNames: string[]) {
|
||||
return JavaScriptObfuscator.obfuscate(core, {
|
||||
optionsPreset: 'default',
|
||||
seed: 7,
|
||||
target: 'browser',
|
||||
|
||||
controlFlowFlattening: true,
|
||||
controlFlowFlatteningThreshold: 1,
|
||||
deadCodeInjection: true,
|
||||
deadCodeInjectionThreshold: 1,
|
||||
identifierNamesGenerator: 'hexadecimal',
|
||||
numbersToExpressions: true,
|
||||
renameGlobals: true,
|
||||
renameProperties: true,
|
||||
renamePropertiesMode: 'safe',
|
||||
reservedNames,
|
||||
stringArray: false,
|
||||
transformObjectKeys: true,
|
||||
}).getObfuscatedCode()
|
||||
}
|
||||
|
||||
function obfuscateMain(mainjs: string) {
|
||||
const file = fs.readFileSync('./src/core.js').toString()
|
||||
mainjs += '\n' + file
|
||||
return JavaScriptObfuscator.obfuscate(mainjs, {
|
||||
optionsPreset: 'high-obfuscation',
|
||||
seed: 13,
|
||||
target: 'browser',
|
||||
|
||||
controlFlowFlattening: true,
|
||||
controlFlowFlatteningThreshold: 0.8,
|
||||
deadCodeInjection: true,
|
||||
deadCodeInjectionThreshold: 0.8,
|
||||
debugProtection: true,
|
||||
debugProtectionInterval: 2000,
|
||||
disableConsoleOutput: true,
|
||||
identifierNamesGenerator: 'hexadecimal',
|
||||
numbersToExpressions: true,
|
||||
renameGlobals: true,
|
||||
renameProperties: true,
|
||||
renamePropertiesMode: 'safe',
|
||||
selfDefending: true,
|
||||
simplify: true,
|
||||
splitStrings: true,
|
||||
splitStringsChunkLength: 4,
|
||||
stringArray: true,
|
||||
stringArrayCallsTransform: true,
|
||||
stringArrayCallsTransformThreshold: 0.8,
|
||||
stringArrayEncoding: ['rc4'],
|
||||
stringArrayIndexesType: ['hexadecimal-number', 'hexadecimal-numeric-string'],
|
||||
stringArrayWrappersCount: 2,
|
||||
stringArrayWrappersParametersMaxCount: 3,
|
||||
stringArrayWrappersType: 'function',
|
||||
stringArrayThreshold: 0.8,
|
||||
transformObjectKeys: true,
|
||||
|
||||
log: true
|
||||
|
||||
//domainLock
|
||||
}).getObfuscatedCode()
|
||||
}
|
||||
|
||||
function doswap(b64: string) {
|
||||
function swap(b64: string, c1: string, c2: string) { return b64.replaceAll(c1, '#').replaceAll(c2, c1).replaceAll('#', c2) }
|
||||
b64 = swap(b64, '8', 'f')
|
||||
b64 = swap(b64, 'W', '2')
|
||||
b64 = swap(b64, 'M', 'C')
|
||||
b64 = swap(b64, 'z', 'm')
|
||||
b64 = swap(b64, 'a', 'S')
|
||||
b64 = swap(b64, 'H', 'l')
|
||||
b64 = swap(b64, 'e', 'Y')
|
||||
return b64
|
||||
}
|
||||
|
||||
// MARK: templating
|
||||
function loadMainjs(coreObfuscatedB64swap: string) {
|
||||
const file = fs.readFileSync('./src/main.js').toString()
|
||||
return file.replace('{{TEXT}}', coreObfuscatedB64swap)
|
||||
}
|
||||
|
||||
function loadDesign(script: string) {
|
||||
const file = fs.readFileSync('./src/design.html').toString()
|
||||
const split = file.split('{{TEXT}}')
|
||||
return split[0] + script + split[1]
|
||||
//return file.replace('{{TEXT}}', script)
|
||||
}
|
||||
|
||||
// MARK: main
|
||||
function main() {
|
||||
if (fs.existsSync('./out')) {
|
||||
fs.rmSync('./out', { recursive: true, force: true })
|
||||
}
|
||||
fs.mkdirSync('./out')
|
||||
fs.mkdirSync('./out/debug')
|
||||
|
||||
console.log('Parsing keys')
|
||||
const keysFile = parseKeysFile()
|
||||
console.log('Parsing hints')
|
||||
const hintsFile = parseHints()
|
||||
|
||||
const renames = [
|
||||
["getFinalImage", "_0xc65ffb"],
|
||||
...keysFile.names,
|
||||
...hintsFile.names
|
||||
]
|
||||
|
||||
console.log('Loading core.js')
|
||||
const file = fs.readFileSync('./src/core.js').toString()
|
||||
const scriptfile = keysFile.scriptfile + '\n' + hintsFile.scriptfile + '\n' + file
|
||||
fs.writeFileSync('./out/debug/core.js', scriptfile)
|
||||
|
||||
console.log('Obfuscating core.js')
|
||||
let coreObfuscated = obfuscateCore(scriptfile, [...renames.map(v => v[0])])
|
||||
|
||||
for (let name of renames) {
|
||||
coreObfuscated = coreObfuscated.replace(`function ${name[0]}(`, `function ${name[1]}(`)
|
||||
}
|
||||
coreObfuscated += `function dear_osint_hater() { return "You made it so far! The end is almost here. Since you decided to destroy my hard work, you will not reach the final prize the normal way, so here you go: https://zomo.land/hosted/startrekctf.png. Good luck finding the flag!" }`
|
||||
fs.writeFileSync('./out/debug/core.obfuscated', coreObfuscated)
|
||||
|
||||
console.log('Converting core.js to base64')
|
||||
const coreObfuscatedB64 = btoa(coreObfuscated)
|
||||
fs.writeFileSync('./out/debug/core.obfuscated.base64', coreObfuscatedB64)
|
||||
|
||||
console.log('Obfuscating core.js base64')
|
||||
const coreObfuscatedB64swap = doswap(coreObfuscatedB64)
|
||||
fs.writeFileSync('./out/debug/core.obfuscated.base64.obfuscated', coreObfuscatedB64swap)
|
||||
|
||||
console.log('Loading main.js')
|
||||
const mainjs = loadMainjs(coreObfuscatedB64swap)
|
||||
fs.writeFileSync('./out/debug/main.js', mainjs)
|
||||
|
||||
console.log('Creating nonobfuscated.html')
|
||||
const mainhtml = loadDesign(mainjs)
|
||||
fs.writeFileSync('./out/debug/nonobfuscated.html', mainhtml)
|
||||
|
||||
if (process.argv[2] === 'dev') {
|
||||
console.log('Flag dev: skipping obfuscation')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Obfuscating main.js')
|
||||
const mainjsObfuscated = obfuscateMain(mainjs)
|
||||
fs.writeFileSync('./out/debug/main.obfuscated', mainjsObfuscated)
|
||||
|
||||
console.log('Creating file.html')
|
||||
const mainhtmlObfuscated = loadDesign(mainjsObfuscated)
|
||||
fs.writeFileSync('./out/debug/final.html', mainhtmlObfuscated)
|
||||
fs.writeFileSync('./out/4LostInSpace.html', mainhtmlObfuscated)
|
||||
}
|
||||
main()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user