<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JS Safe - the leading localStorage based safe solution with advanced obfuscation technology</title> <!-- Advertisement: Looking for a hand-crafted, browser based virtual safe to store your most interesting secrets? Look no further, you have found it. You can order your own by sending a mail to js_safe@example.com. When ordering, please specify the password you'd like to use to open and close the safe and the content you'd like to store. We'll hand craft a unique safe just for you, that only works with your password of choice and contains your secret. (We promise we won't peek when handling your data.) --> <style> body { text-align: center; } input { font-size: 200%; margin-top: 5em; text-align: center; width: 26em; } #result { margin-top: 8em; font-size: 300%; font-family: monospace; font-weight: bold; } body.granted>#result::before { content: "Access Granted"; color: green; } body.denied>#result::before { content: "Access Denied"; color: red; } #content { display: none; } body.granted #content { display: initial; } .wrap { display: inline-block; margin-top: 50px; perspective: 800px; perspective-origin: 50% 100px; } .cube { position: relative; width: 200px; transform-style: preserve-3d; } .back { transform: translateZ(-100px) rotateY(180deg); } .right { transform: rotateY(-270deg) translateX(100px); transform-origin: top right; } .left { transform: rotateY(270deg) translateX(-100px); transform-origin: center left; } .top { transform: rotateX(-90deg) translateY(-100px); transform-origin: top center; } .bottom { transform: rotateX(90deg) translateY(100px); transform-origin: bottom center; } .front { transform: translateZ(100px); } @keyframes spin { from { transform: rotateY(0); } to { transform: rotateY(360deg); } } .cube { animation: spin 20s infinite linear; } .cube div { position: absolute; width: 200px; height: 200px; background: rgba(0, 0, 0, 0.51); box-shadow: inset 0 0 60px white; font-size: 20px; text-align: center; line-height: 200px; color: rgba(0,0,0,0.5); font-family: sans-serif; text-transform: uppercase; } </style> <script> class ExpressionDebugger { constructor(lhs, fn, arg1, arg2, env) { this.lhs = lhs this.fn = fn this.arg1 = arg1 this.arg2 = arg2 this.env = env this.oldarg1val = env[arg1] this.oldarg2val = env[arg2] this.labels = { a: 'getItem', b: 'apply', c: 'add', d: 'fromCharCode', g: 'passwordBytes', ѡ: 'bitwiseXOR', Ѧ: 'bitwiseOR', ѐ: 'window', ј: 'Array', } } getNiceName(symbol) { return this.labels[symbol] || symbol } async logFunctionCall() { this.log({ isInst: false }) } async logClassInstantiation() { this.log({ isInst: true }) } async log({ isInst }) { let lhs = this.lhs let fn = this.getNiceName(this.fn) let arg1 = this.getNiceName(this.arg1) let arg2 = this.getNiceName(this.arg2) let oldarg1val1 = this.oldarg1val let oldarg1val2 = this.oldarg2val let newval = this.env[this.lhs] if (newval instanceof Promise) { newval = await newval } console.log(`${lhs} = ${isInst ? 'new ' : ''}${fn}(${arg1}, ${arg2}) = ${fn}(${oldarg1val1}, ${oldarg1val2}) = ${newval}`) } } async function x(password) { // TODO: check if they can just use Google to get the password once they understand how this works. var code = 'icffjcifkciilckfmckincmfockkpcofqcoircqfscoktcsfucsivcufwcooxcwfycwiAcyfBcwkCcBfDcBiEcDfFcwoGcFfHcFiIcHfJcFkKcJfLcJiMcLfNcwwOcNNPcOOQcPORcQNScRkTcSiUcONVcUoWcOwXcWkYcVkЀcYiЁcЀfЂcQoЃcЂkЄcЃfЅcPNІcЅwЇcІoЈcЇiЉcЈfЊcPkЋcЊiЌcІiЍcЌfЎcWoЏcЎkАcЏiБcІkВcБfГcNkДcГfЕcЇkЖcЕiЗcЖfИcRwЙcИoКcЙkЛcUkМcЛiНcМfОcИkПcОiРcПfСcUwТcСiУcQkФcУiХcЃiЦcQwЧcЦoШcЧkЩcШiЪcЩfЫcRiЬcЫfЭcКiЮcЭfЯcСoаcЯiбcГiвcЙiгcRoдcгkеcдiжdТaзcЛfиdзaжcжийcСkкdйaжcжклcйfмdлaжcжмнdТaжcжноdЀaжcжопdNaжcжпрcUiсcрfтdсaуdЁaтcтутcтофcТfхdфaтcтхтcтктcтнтcтмцdсaтcтцтcтктcтутcтнчaaтшdЯaщcйiъcщfыdъaьcжыэcVfюdэaьcьюьcьояdЛaьcьяьcьуьcьыѐчшьёѐшшђcOfѓdђaѓcѓнѓcѓнєcUfѕdєaѓcѓѕіcЯfїdіaѓcѓїјaёѓљaaтњcжшћcЎiќcћfѝdќaњcњѝњcњeўcЏfџdўaњcњџѠdАaњcњѠњcњшњcњѝњcњfњcњџѡљшњѢaaтѣcжшѣcѣѝѣcѣeѣcѣџѤcЯkѥdѤaѣcѣѥѣcѣшѣcѣѝѣcѣfѣcѣџѦѢшѣѧcцнѧcѧїѨdСaѧcѧѨѧcѧкѧcѧуѩaёѧѪcхмѫdрaѪcѪѫѪcѪкѬdYaѪcѪѬѪcѪиѭaѩѪѮcяюѯdНaѮcѮѯѮcѮиѮcѮхѮcѮкѰaѭѮѱdVaѲcхѱѲcѲѕѳcNoѴcѳkѵcѴfѶdѵaѲcѲѶѲcѲiѲcѲlѲcѲmѷјѲgѸјѭѷѹbѰѸѺcXfѻdѺaѻcѻюѻcѻоѻcѻкѻcѻoѼdђaѻcѻѼѻcѻнѻcѻнѻcѻѕѻcѻїѽaёѻѾѽѹшѿceeҀceeҁcee҂ceeѿaѾeҀјѿT҂ѡҀшҁјh҂hѦҁшѿaѾfҀјѿV҂ѡҀшҁјh҂hѦҁшѿaѾiҀјѿU҂ѡҀшҁјh҂hѦҁшѿaѾjҀјѿX҂ѡҀшҁјh҂hѦҁшѿaѾkҀјѿЁ҂ѡҀшҁјh҂hѦҁшѿaѾlҀјѿF҂ѡҀшҁјh҂hѦҁшѿaѾmҀјѿЄ҂ѡҀшҁјh҂hѦҁшѿaѾnҀјѿЉ҂ѡҀшҁјh҂hѦҁшѿaѾoҀјѿЄ҂ѡҀшҁјh҂hѦҁшѿaѾpҀјѿЋ҂ѡҀшҁјh҂hѦҁшѿaѾqҀјѿЍ҂ѡҀшҁјh҂hѦҁшѿaѾrҀјѿА҂ѡҀшҁјh҂hѦҁшѿaѾsҀјѿF҂ѡҀшҁјh҂hѦҁшѿaѾtҀјѿВ҂ѡҀшҁјh҂hѦҁшѿaѾuҀјѿД҂ѡҀшҁјh҂hѦҁшѿaѾvҀјѿЗ҂ѡҀшҁјh҂hѦҁшѿaѾwҀјѿК҂ѡҀшҁјh҂hѦҁшѿaѾxҀјѿН҂ѡҀшҁјh҂hѦҁшѿaѾyҀјѿР҂ѡҀшҁјh҂hѦҁшѿaѾAҀјѿТ҂ѡҀшҁјh҂hѦҁшѿaѾBҀјѿФ҂ѡҀшҁјh҂hѦҁшѿaѾCҀјѿW҂ѡҀшҁјh҂hѦҁшѿaѾDҀјѿХ҂ѡҀшҁјh҂hѦҁшѿaѾEҀјѿЪ҂ѡҀшҁјh҂hѦҁшѿaѾFҀјѿЬ҂ѡҀшҁјh҂hѦҁшѿaѾGҀјѿЮ҂ѡҀшҁјh҂hѦҁшѿaѾHҀјѿа҂ѡҀшҁјh҂hѦҁшѿaѾIҀјѿe҂ѡҀшҁјh҂hѦҁшѿaѾJҀјѿб҂ѡҀшҁјh҂hѦҁшѿaѾKҀјѿв҂ѡҀшҁјh҂hѦҁшѿaѾLҀјѿK҂ѡҀшҁјh҂hѦҁшѿaѾMҀјѿе҂ѡҀшҁјh҂hѦҁш' var env = { a: (x,y) => x[y], b: (x,y) => Function.constructor.apply.apply(x, y), c: (x,y) => x+y, d: (x) => String.fromCharCode(x), e: 0, f: 1, g: new TextEncoder().encode(password), h: 0, }; for (var i = 0; i < code.length; i += 4) { var [lhs, fn, arg1, arg2] = code.substr(i, 4); var debuggah = new ExpressionDebugger(lhs, fn, arg1, arg2, env) try { env[lhs] = env[fn](env[arg1], env[arg2]); await debuggah.logFunctionCall() } catch(e) { env[lhs] = new env[fn](env[arg1], env[arg2]); await debuggah.logClassInstantiation() } if (env[lhs] instanceof Promise) env[lhs] = await env[lhs]; } console.log(env) return !env.h; } </script> <script> const alg = { name: 'AES-CBC', iv: Uint8Array.from([211,42,178,197,55,212,108,85,255,21,132,210,209,137,37,24])}; const secret = Uint8Array.from([26,151,171,117,143,168,228,24,197,212,192,15,242,175,113,59,102,57,120,172,50,64,201,73,39,92,100,64,172,223,46,189,65,120,223,15,34,96,132,7,53,63,227,157,15,37,126,106]); async function open_safe() { keyhole.disabled = true; password = /^CTF{([0-9a-zA-Z_@!?-]+)}$/.exec(keyhole.value); if (!password || !(await x(password[1]))) return document.body.className = 'denied'; document.body.className = 'granted'; const pwHash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password[1])); const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); content.value = new TextDecoder("utf-8").decode(await crypto.subtle.decrypt(alg, key, secret)) } </script> </head> <body> <div> <input id="keyhole" autofocus onchange="open_safe()" placeholder="🔑"> <script> // window.keyhole.onchange() </script> </div> <div class="wrap"> <div class="cube"> <div class="front"></div> <div class="back"></div> <div class="top"></div> <div class="bottom"></div> <div class="left"></div> <div class="right"></div> </div> </div> <div id="result"> </div> <div> <input id="content"> </div> </body> </html>