Post

What The Hell

This is a writeup of the coding challenge What the HELL from the Security Valley CTF

Premise

All hell is breaking loose. Once again a frontend developer went completely nuts and left us with a JavaScrit project that nobody understands anymore…typical, these frontend developers…surely you can help us, right? Find the flag!

Link: http://pwnme.org:8666

Challenge code

Website:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
    <head>
        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link href="https://fonts.googleapis.com/css2?family=DotGothic16&display=swap" rel="stylesheet"> 
        <link rel="stylesheet" href="hell.css">
        <script src="hell.js"></script>
    </head>
    <body>
        <div class="content">
            <div>
                <h1>HELL'S PAGE</h1>
            </div>
            <div>
                <input type="text" id="hell-key" placeholder="Enter key to open the hell"/>
            </div>
            <div class="flag-content">
                <h1>Flag: <span id="flag-out"></span></h1>
            </div>
        </div>

    </body>
</html>

hell.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
function changeHandler(e) {
    window.hell_key = e.target.value;
}


function main() {
    const cmd = [
        [
            "a",
            "696564797e2a6f662a372a6e65697f676f647e246d6f7e4f666f676f647e4873436e22286c666b6d27657f7e282331006f66246364646f785e6f727e2a372a6b31"
        ],
        [
            "",
            "7d63646e657d246c666b6d2a372a284770653b465e5f3c44633a704560537e4460653b465e4f3c44633a7247606538465e4f724560697e475e4b3c44633a704560697e475e5f3c44593a3f4560537e47706539465e4f3b45605f7e454e6538465e5f3c44633a7247606538465e4f734560537e475e5f3c44593a734560537e445e6539465e5b3c44733a72445e653b465e533c44633a3b4560697e475e5b3c44633a724560437e475e473c447d373728"
        ],
        [
            "",
            "2a2a2a2a2a2a2a2a666f7e2a686665682a372a646f7d2a4866656822516b7e656822284673326d6e52446650494862684d6e7c43493a6d434d44626863483f68395f6d69675c38505240705059487a6e4e32415338337f69395b6d53594b33434943255a703263496772666e49487c6e525b6d5a594b63436d7a6768394365684d5c3a434d616d5a594b7d4573487a434e7d6d53593f79505d3f646e4d6d3d434d61784173616d6f7d656d43494b6d684d5c3a4342586668524b6d5a594862466744655352404e683858665b525b656b59616d40634b7d6f4f504d49634b6d4349487950525b6d68494b334342586668524b6d40634b7d6f4e484d49634b6d4349487950525b6d6b494b334349623a505d3b7d434e3e21434e5b7a4349536d4742624d5860794149634b6d4349487a50634b65414d61784759616d5a5e3a6d53593f79505d3f646e4d6d7a4342794143494b6d43494b6d4349487c6e525b6d41703a6d68494b78436065634173486549634b6d43494833434d5c7969385f6d6f7d656d43494b6d43494b6d434d333b6e494b785a5948794349796345634378434d6d7843633a6349634b6d4349483349643a4128235726717e737a6f30282a6b7a7a6663696b7e636564257e6f727e287723002a2a2a2a002a2a2a2a2a2a2a2a696564797e2a6f666f676f647e2a372a6e65697f676f647e2469786f6b7e6f4f666f676f647e22286b282331002a2a2a2a2a2a2a2a6f666f676f647e24796f7e4b7e7e7863687f7e6f222862786f6c28262a7d63646e657d245f58462469786f6b7e6f4568606f697e5f58462268666568232331002a2a2a2a2a2a2a2a6f666f676f647e24796f7e4b7e7e7863687f7e6f22286e657d6466656b6e28262a28676f79796b6d6f556c78656755626f6666247e727e282331002a2a2a2a2a2a2a2a6f666f676f647e24797e73666f246e63797a666b732a372a286465646f2831002a2a2a2a2a2a2a2a6e65697f676f647e2468656e73246b7a7a6f646e496263666e226f666f676f647e2331002a2a2a2a2a2a2a2a6f666f676f647e246966636961222331002a2a2a2a2a2a2a2a6e65697f676f647e2468656e7324786f67657c6f496263666e226f666f676f647e2331"
        ]
    ]
    
    const l_cmd = [
    
    ]
    

    function preloader() {    
        for(let a = 0; a < cmd.length; a++) {

            let t = [];

            for(let i = 0; i < cmd[a][1].length; i += 2) {
                t.push(String.fromCharCode(parseInt(cmd[a][1].substr(i, 2), 16) ^ 0xA));
            }

            l_cmd.push(new Function(cmd[a][0],t.join("")))

        }
    }

    preloader();

    l_cmd[0]("closed...")
    l_cmd[1]()

    setInterval(() => {
        if (window.hell_key == "666") {
            l_cmd[0]("MESSAGE FROM HELL!")
            window.hell_key = "";
            l_cmd[2]()
        } else {
            l_cmd[0]("still closed...")
        }
    }, 1000);
}

document.addEventListener("DOMContentLoaded", () => {

    const input = document.getElementById("hell-key");
    input.addEventListener('input', changeHandler, false);

    main();
});

message_from_hell.txt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// used algo -  can you reverse it?
const a = "???"
let out = ""
for(let i = 0; i < a.length; i++) {
    let temp = a.charCodeAt(i) & 0xFF
    let l = temp & 0x0F
    let h = (temp >> 4) & 0xFF;

    if ((i+1) == a.length) {
        out += l +":"+ h
    } else {
        out += l +":"+ h+"-"
    }
}

Solution

At first in this challenge, we find a web page with a text input field. This doesn’t give us too much information at first glance, so we move forwards by looking at the page source. An interesting line here is:

1
<script src="hell.js"></script>

Which leads us to our next step. Here we find the section:

1
2
3
4
5
6
7
8
9
10
11
12
l_cmd[0]("closed...")
l_cmd[1]()

setInterval(() => {
    if (window.hell_key == "666") {
        l_cmd[0]("MESSAGE FROM HELL!")
        window.hell_key = "";
        l_cmd[2]()
    } else {
        l_cmd[0]("still closed...")
    }
}, 1000);

As we can see, if we enter the hell_key, that being 666, we get a textfile called message_from_hell.txt. This file contains the algorithm used to encode messages. In it, we have a constant, a defined as “???”, but we can use the value set to a from hell.js, that being: "696564797e2a6f662a372a6e65697f676f647e246d6f7e4f666f676f647e4873436e22286c666b6d27657f7e282331006f66246364646f785e6f727e2a372a6b31"

However, we also have the preloader function from hell.js. With the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
function preloader() {    
    for(let a = 0; a < cmd.length; a++) {

        let t = [];

        for(let i = 0; i < cmd[a][1].length; i += 2) {
            t.push(String.fromCharCode(parseInt(cmd[a][1].substr(i, 2), 16) ^ 0xA));
        }

        l_cmd.push(new Function(cmd[a][0],t.join("")))

    }
}

We can see that the string has been hex encoded and XORed with 0xA. More explicitly:

1
2
3
Hex encoding character into decimal format: parseInt(cmd[a][1].substr(i, 2), 16)
XORing with 0xA: parseInt(cmd[a][1].substr(i, 2), 16) ^ 0xA
Create a string from the decimal number: String.fromCharCode(parseInt(cmd[a][1].substr(i, 2), 16) ^ 0xA)

Therefore, if we first hex decode the "696564797e2a6f662a372a6e65697f676f647e246d6f7e4f666f676f647e4873436e22286c666b6d27657f7e282331006f66246364646f785e6f727e2a372a6b31" string, and then XOR it with 0xA, we get the following:

1
2
const el = document.getElementById("flag-out");
el.innerText = a;

If we now do the same for the other two strings in the cmd array from hell.js, we get the following from the 7d63646e657d246c666b6d2a372a284770653b465e5f3c44633a704560537e4460653b465e4f3c44633a7247606538465e4f724560697e475e4b3c44633a704560697e475e5f3c44593a3f4560537e47706539465e4f3b45605f7e454e6538465e5f3c44633a7247606538465e4f734560537e475e5f3c44593a734560537e445e6539465e5b3c44733a72445e653b465e533c44633a3b4560697e475e5b3c44633a724560437e475e473c447d373728 string:

1
window.flag = "Mzo1LTU6Ni0zOjYtNjo1LTE6Ni0xMjo2LTExOjctMTA6Ni0zOjctMTU6NS05OjYtMzo3LTE1OjUtODo2LTU6Ni0xMjo2LTEyOjYtMTU6NS0yOjYtNTo3LTQ6Ny0xNTo1LTY6Ni01OjctMTQ6Ni0xOjItMTM6Nw=="

Which as we can see is base64 encoded.

The third and final string in the cmd array is 2a2a2a2a2a2a2a2a666f7e2a686665682a372a646f7d2a4866656822516b7e656822284673326d6e52446650494862684d6e7c43493a6d434d44626863483f68395f6d69675c38505240705059487a6e4e32415338337f69395b6d53594b33434943255a703263496772666e49487c6e525b6d5a594b63436d7a6768394365684d5c3a434d616d5a594b7d4573487a434e7d6d53593f79505d3f646e4d6d3d434d61784173616d6f7d656d43494b6d684d5c3a4342586668524b6d5a594862466744655352404e683858665b525b656b59616d40634b7d6f4f504d49634b6d4349487950525b6d68494b334342586668524b6d40634b7d6f4e484d49634b6d4349487950525b6d6b494b334349623a505d3b7d434e3e21434e5b7a4349536d4742624d5860794149634b6d4349487a50634b65414d61784759616d5a5e3a6d53593f79505d3f646e4d6d7a4342794143494b6d43494b6d4349487c6e525b6d41703a6d68494b78436065634173486549634b6d43494833434d5c7969385f6d6f7d656d43494b6d43494b6d434d333b6e494b785a5948794349796345634378434d6d7843633a6349634b6d4349483349643a4128235726717e737a6f30282a6b7a7a6663696b7e636564257e6f727e287723002a2a2a2a002a2a2a2a2a2a2a2a696564797e2a6f666f676f647e2a372a6e65697f676f647e2469786f6b7e6f4f666f676f647e22286b282331002a2a2a2a2a2a2a2a6f666f676f647e24796f7e4b7e7e7863687f7e6f222862786f6c28262a7d63646e657d245f58462469786f6b7e6f4568606f697e5f58462268666568232331002a2a2a2a2a2a2a2a6f666f676f647e24796f7e4b7e7e7863687f7e6f22286e657d6466656b6e28262a28676f79796b6d6f556c78656755626f6666247e727e282331002a2a2a2a2a2a2a2a6f666f676f647e24797e73666f246e63797a666b732a372a286465646f2831002a2a2a2a2a2a2a2a6e65697f676f647e2468656e73246b7a7a6f646e496263666e226f666f676f647e2331002a2a2a2a2a2a2a2a6f666f676f647e246966636961222331002a2a2a2a2a2a2a2a6e65697f676f647e2468656e7324786f67657c6f496263666e226f666f676f647e2331

1
2
3
4
5
6
7
8
9
        let blob = new Blob([atob("Ly8gdXNlZCBhbGdvIC0gIGNhbiB5b3UgcmV2ZXJzZSBpdD8KY29uc3QgYSA9ICI/Pz8iCmxldCBvdXQgPSAiIgpmb3IobGV0IGkgPSAwOyBpIDwgYS5sZW5ndGg7IGkrKykgewogICAgbGV0IHRlbXAgPSBhLmNoYXJDb2RlQXQoaSkgJiAweEZGCiAgICBsZXQgbCA9IHRlbXAgJiAweDBGCiAgICBsZXQgaCA9ICh0ZW1wID4+IDQpICYgMHhGRjsKCiAgICBpZiAoKGkrMSkgPT0gYS5sZW5ndGgpIHsKICAgICAgICBvdXQgKz0gbCArIjoiKyBoCiAgICB9IGVsc2UgewogICAgICAgIG91dCArPSBsICsiOiIrIGgrIi0iCiAgICB9Cn0K")],{type:" application/text"})
    
        const element = document.createElement("a");
        element.setAttribute("href", window.URL.createObjectURL(blob));
        element.setAttribute("download", "message_from_hell.txt");
        element.style.display = "none";
        document.body.appendChild(element);
        element.click();
        document.body.removeChild(element);

Moving back to the 2nd string, the one encoded in base64, has the following content if we decode it: 3:5-5:6-3:6-6:5-1:6-12:6-11:7-10:6-3:7-15:5-9:6-3:7-15:5-8:6-5:6-12:6-12:6-15:5-2:6-5:7-4:7-15:5-6:6-5:7-14:6-1:2-13:7

This looks like the output of something run with the message_from_hell.txt encoding algorithm, so if we do what the comment said and reverse it, we get something like the following algorithm:

1
2
3
4
5
6
7
8
9
10
11
12
const a = "3:5-5:6-3:6-6:5-1:6-12:6-11:7-10:6-3:7-15:5-9:6-3:7-15:5-8:6-5:6-12:6-12:6-15:5-2:6-5:7-4:7-15:5-6:6-5:7-14:6-1:2-13:7";
let out = "";
const pairs = a.split("-");
for (let i = 0; i < pairs.length; i++) {
  const pair = pairs[i].split(":");
  const l = parseInt(pair[0], 10);
  const h = parseInt(pair[1], 10);

  const temp = (h << 4) | l;
  out += String.fromCharCode(temp);
}
console.log(out);

Which prints out the flag SecVal{[redacted]}

This post is licensed under CC BY 4.0 by the author.