Intro
This challenge has a single HTML file. It has obfuscated JavaScript code.
Write-Up
Script has a lot of junk inside. It is very hard to clean up the mess manually. So I coded basic utility to clean up the script. Basically, I will use esprima
to find junk function blocks and replace them with new lines.
const esprima = require('esprima')
const fs = require('fs');
const _ = require('lodash');
function isFunc(node) {
const name = _.get(node,'id.name')
const whiteList = ['Add','S8PnPCDSnKdSqe'] // don't remove those
if (_.includes(whiteList,name)) {
return false
}
if (node.type == 'FunctionDeclaration') {
return true;
}
return false;
}
function removeCalls(source) {
const entries = [];
esprima.parseScript(source, {}, function (node, meta) {
if (isFunc(node)) {
entries.push({
start: meta.start.offset,
end: meta.end.offset
});
}
});
entries.sort((a, b) => { return b.end - a.end }).forEach(n => {
let replacement = Array(n.end-n.start).fill('\n') // fill functions with new lines
source = source.slice(0, n.start) + replacement + source.slice(n.end);
});
return source.replace(/\n,/g,'')
}
let input = fs.readFileSync('./input.js','utf-8')
let result = removeCalls(input);
fs.writeFileSync('./cleaned.js',result)
After the cleanup, we end up with the below script.
PyKEvIqAmUkUVL0Anfn9FElFUN2dic3z = "4fny3z...."
GJrFu0fnwTxv2znmydOO5NG23UTO0MypKl = "b2JDN2luc2tiYXhLOFZaUWRRWTlSeXdJbk9lVWxLcHlrMXJsRnk5NjJaWkQ4SHdGVjhyOENQeFE5dGxUaEd1dGJ5ZDNOYTEzRmZRN1V1emxkZUJQNTN0Umt6WkxjbDdEaU1KVWF1M29LWURzOGxUWFR2YjJqQW1HUmNEU2RRcXdFSERzM0d3emhOaGVIYlE3dm9aeVJTMHdLY2Vhb3YyVGQ4UnQ2SXUwdm1ZbGlVYjA4YVRES2xESnlXU3NtZENMN0J4MnBYdlZET3RUSmlhY2V6Y3B6eUM2Mm4yOWs=";
Ljasr99E9HLv1BBnSfEHYw = 64
EuF8AepyhtkSXEWvNKIKZMaSHm4v = atob(GJrFu0fnwTxv2znmydOO5NG23UTO0MypKl).split('');
npxuau2RsDO0L4hSVCBHx = atob(PyKEvIqAmUkUVL0Anfn9FElFUN2dic3z).split('');
Oz9nOiwWfRL6yjIwvM4OgaZMIt0B = npxuau2RsDO0L4hSVCBHx
PeFEvMaDMvyrYg8UZgKKfVMBhak5 = npxuau2RsDO0L4hSVCBHx.length;
bNT5lGtaxYHeyHFeEdImdD12Csa7MlR='Cflsdgfdjgflkdsfjg4980utjkfdskfglsldfgjJLmSDA49sdfgjlfdsjjqdgjfj'.split('');
// This is read from the edit box.
if(qguBomGfcTZ6L4lRxS0TWx1IwG[0].length==Ljasr99E9HLv1BBnSfEHYw)bNT5lGtaxYHeyHFeEdImdD12Csa7MlR=qguBomGfcTZ6L4lRxS0TWx1IwG[0].split('');
for (i=0; i < EuF8AepyhtkSXEWvNKIKZMaSHm4v.length; i++) { EuF8AepyhtkSXEWvNKIKZMaSHm4v[i] = (EuF8AepyhtkSXEWvNKIKZMaSHm4v[i].charCodeAt(0) + bNT5lGtaxYHeyHFeEdImdD12Csa7MlR[i % Ljasr99E9HLv1BBnSfEHYw].charCodeAt(0)) & 0xFF; }
for (i=0; i < PeFEvMaDMvyrYg8UZgKKfVMBhak5; i++) { Oz9nOiwWfRL6yjIwvM4OgaZMIt0B[i] = (Oz9nOiwWfRL6yjIwvM4OgaZMIt0B[i].charCodeAt(0) - EuF8AepyhtkSXEWvNKIKZMaSHm4v[i % EuF8AepyhtkSXEWvNKIKZMaSHm4v.length]) & 0xFF; }
sEjdWWMFU4wObKZap4WeMBgdfgIfTHCvS="";
for (i=0; i < npxuau2RsDO0L4hSVCBHx.length; i++) { sEjdWWMFU4wObKZap4WeMBgdfgIfTHCvS+=String.fromCharCode(Oz9nOiwWfRL6yjIwvM4OgaZMIt0B[i]);}
We have to find key for bNT5lGtaxYHeyHFeEdImdD12Csa7MlR
(table) so that when this code runs, it should create a valid JavaScript code for sEjdWWMFU4wObKZap4WeMBgdfgIfTHCvS
(code) variable. Table values must be valid ASCII values because they would be entered with the keyboard. Code should also have valid chars between 0x20-0x7F after the decryption. So I extracted two big tables to different text files and run the below script.
import base64
def isPrintable(char):
whitespace = [' ', '\r', '\n', '\t']
if chr(char) in whitespace:
return True
return char > 0x20 and char < 0x7F
def readbase64File(name):
with open(name,'rb') as f:
result = bytearray(base64.b64decode(f.read()))
f.close()
return result
def writeFile(name,content):
with open(name,'wb') as f:
f.write(content)
f.close()
first_table = readbase64File('./first_table.txt') #4fny3zLzDRYIOe37Ax
second_table = readbase64File('./second_table.txt') #b2JDN2
# first_table = readbase64File('./second_layer_first.txt') #4fny3zLzDRYIOe37Ax
# second_table = readbase64File('./second_layer_second.txt') #b2JDN2
dictlist = [dict() for x in range(64)]
for i,val in enumerate(first_table):
for j in range(0x20,0x7F):
plain = (first_table[i] - second_table[i % len(second_table)] - j) & 0xFF
if isPrintable(plain):
index = i % len(second_table) % 64
if j in dictlist[index]:
dictlist[index][j] += 1
else:
dictlist[index][j] = 1
all_possibilities = []
for index,val in enumerate(dictlist):
counts = sorted(val.items(), key=lambda item: item[1], reverse=True)
letter_candidates = []
(x,highfreq) = counts[0]
for i in range(20):
(l,frequency) = counts[i]
if (frequency == highfreq):
letter_candidates.append(chr(l))
all_possibilities.append(letter_candidates)
print(all_possibilities)
final = ''
for item in all_possibilities:
if len(item) > 1:
final += '*'
else:
final += item[0]
print(final)
final = ''
for item in all_possibilities:
print(item)
if len(item) > 1:
final += item[len(item)-1]
else:
final += item[0]
print(final)
When we run the above script, we get our key as ChVCVYzI1dU9cVg1ukBqO2u4UGr9aVCNWHpMUuYDLmDO22cdhXq3oqp8jmKBHUWI
When we decrypt it using the above code, then we get another JavaScript file that starts with
//Yes, but who can deny the heart that is yearning?
//Affirmative!
//Uh-oh!
//This.
//Here's your change. Have a great afternoon! Can I help who's next?
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!
It is a JSFuck encoded script. We use a decoder to decode this and we get another JS file
(function (qguBomGfcTZ6L4lRxS0TWx1IwG) { sInNWkbompb8pOyDG5D = "u8n2Ffpa3OQdRcn9UvkS3T8CAR+mOc/7/wYBJu/...
We modify the above script and run it against this one and we get UQ8yjqwAkoVGm7VDdhLoDk0Q75eKKhTfXXke36UFdtKAi0etRZ3DoHPz7NxJPgHl
as a password. Upon decryption, we get the next JavaScript code.
//He's not bothering anybody.
//Why would you question anything? We're bees.
//Listen, you better go 'cause we're really busy working.
[][(![]+[])[....
We again use the same decoder and this time we get
alert("I_h4d_v1rtU411y_n0_r3h34rs4l_f0r_th4t@flare-on.com")
Flare-On 2021 Write-ups