View raw

<!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>