Qualify for semi finals! Here is some writeups for the challenges

Challenges

brote (rev)

Challenge file:

We are given a python script

Try run it in terminal:

python3 brote.py
Enter flag for verification: a
Bad flag!

Look at the source code:

import re
import hashlib

def md5_sha1(x):
        a = hashlib.sha1(x).hexdigest().encode('utf-8')
        return hashlib.md5(a)

def md5_md5_md5(x):
    a = hashlib.md5(x).hexdigest().encode('utf-8')
    a = hashlib.md5(a).hexdigest().encode('utf-8')
    return hashlib.md5(a)

x = [hashlib.sha224, hashlib.sha256, hashlib.sha384, hashlib.sha512, hashlib.md5, hashlib.sha1, md5_sha1, md5_md5_md5]
y = ['3770f06168c0137f3471ba682068ba1de460468d3f04a6412c23fa59', '1d853c1e3b909efceb14a4d92337589041372449d2edc83f890b5fde31d3644b', 'e85fb4a65bc3613d33910d9836cddad339e82a9c3f773c2b23d6bb9734431251ba3e9900e7e22dd5df0f575977549147', '9d7108c35983cf0f0789ed4b09bf44f5b089dc2b9c770336d71fe123908f782318c48c2849dc59c7ad3be9ade3f568bb1cf7e42c3dd9795de829565d46343ac6', '2fc2e03201e06a52b59cdf57075336d8', 'f5043ed6f522cd05f3a771d471ba82f4945feab5', '6ef11bc47eadba4c9c4c44f1e4aaa84f', '3850cda130224d889937e2c652bad56c'
]

def check_flag(flag):
    flag = flag.encode()
    chunks = [flag[i:i+4] for i in range(0, len(flag), 4)]
    for c,(f,o) in zip(chunks, zip(x,y)):
        if f(c).hexdigest() != o:
            return False
    return True

user_input = input("Enter flag for verification: ")

if user_input and \
   len(user_input) == 32 and \
   re.match(r'^[a-z0-9]+$', user_input) and \
   check_flag(user_input):
    print("Congratulations!")
else:
    print("Bad flag!")

We can see our input is the flag, and the correct flag should be:

  1. In length of 32
  2. lowercase letter or number
  3. return true for check_flag

Let’s see what check_flag function does:

def check_flag(flag):
    flag = flag.encode()
    chunks = [flag[i:i+4] for i in range(0, len(flag), 4)]
    for c,(f,o) in zip(chunks, zip(x,y)):
        if f(c).hexdigest() != o:
            return False
    return True

We can see it split the flag in four chunks, then it pass into a for loop

The for loop is just looping for hash functions in x is it equal to hex values y

You can see the x and y array:

x = [hashlib.sha224, hashlib.sha256, hashlib.sha384, hashlib.sha512, hashlib.md5, hashlib.sha1, md5_sha1, md5_md5_md5]
y = ['3770f06168c0137f3471ba682068ba1de460468d3f04a6412c23fa59', '1d853c1e3b909efceb14a4d92337589041372449d2edc83f890b5fde31d3644b', 'e85fb4a65bc3613d33910d9836cddad339e82a9c3f773c2b23d6bb9734431251ba3e9900e7e22dd5df0f575977549147', '9d7108c35983cf0f0789ed4b09bf44f5b089dc2b9c770336d71fe123908f782318c48c2849dc59c7ad3be9ade3f568bb1cf7e42c3dd9795de829565d46343ac6', '2fc2e03201e06a52b59cdf57075336d8', 'f5043ed6f522cd05f3a771d471ba82f4945feab5', '6ef11bc47eadba4c9c4c44f1e4aaa84f', '3850cda130224d889937e2c652bad56c'
]

We know that hash functions cannot reverse but we can brute force it!

Therefore, to solve this we must brute force the flag but in 4 character at a time

So quite fast to solve this

Solving

Write a simple python script, I use pwntools the brute force the flag:

flag = ''

for i in range(len(x)):
    flag += pwnlib.util.iters.mbruteforce(lambda text: x[i](text.encode()).hexdigest() == y[i], string.ascii_lowercase+string.digits, length = 4)
print(flag)
# [+] MBruteforcing: Found key: "fsbr"
# [+] MBruteforcing: Found key: "34k4"
# [+] MBruteforcing: Found key: "llth"
# [+] MBruteforcing: Found key: "3s3h"
# [+] MBruteforcing: Found key: "4sh3"
# [+] MBruteforcing: Found key: "spl3"
# [+] MBruteforcing: Found key: "453c"
# [+] MBruteforcing: Found key: "yber"
# fsbr34k4llth3s3h4sh3spl3453cyber

Thats it! Simple reverse challenge! Full python script


Crypto Stream

Challenge files

We are given a python script encrypt.py and a binary file flag.enc

Let’s see the source code:

import random
from secret import flag, key

def generate_key(s):
    assert len(s) == 16, "The key must be 16 bytes long"

    k = [0, 0]
    for i in range(len(s)):
        if i == 0:
            k[0] = k[0] ^ s[i]
            k[1] = (k[1] + s[i]) & 0xFF
        else:
            k[0] = (k[0] ^ (s[i] - s[i - 1])) & 0xFF
            k[1] = (k[1] + s[i] ^ s[i - 1]) & 0xFF

    return k

def encrypt(s, k):
    n1 = k[0]
    n2 = k[1]

    encrypted = []
    for c in s:
        encrypted.append(c ^ n1)
        random.seed(n1)
        n1 = n2
        n2 = random.randint(0, 255)

    return bytes(encrypted).hex()

e = encrypt(flag, generate_key(key))

with open('flag.enc', 'w') as f:
    f.write(e)

As you can see, it encrypts the flag with a key that we don’t know

Then save the encrypted flag inside flag.enc

First bug

Notice that the generate_key function return array k and the value must be <= 255

Because it perform AND bitwise operation, therefore the generated key must be <= 255

Second bug

We can predict the value of n1 and n2 inside the encrypt function

Because it use n1 as the random seed, that means if we know the correct n1 we can predict n2 and the entire flag!!

Solving (Brute force)

My idea is to brute force all 256 values for k[0] and k[1], until it decrypt the flag starts with fs and ends with cyber

enc = binascii.unhexlify(open("flag.enc",'rb').read())

for k1 in range(256):
    for k2 in range(256):
        flag = decrypt(f,[k1,k2]) # decrypt is same as encrypt
        if flag.startswith(b"fs") and flag.endswith(b"cyber"):
            print(k1,k2)
            print(flag)
# 40 186
# b'fsKn0wnPl41NTextA77ackcyber'

We can see the key is 40 and 186, and we get the decrypted flag!!

Full python script

Solving (Decrypt)

Another method is to calculate the key using the flag format

n1 = k[0], n2 = k[1] and it encrypt the flag using c ^ n1

To find n1 and n2, we can XOR the character of encrypted flag and fs

We know the flag format starts with fs, therefore to find k1 we can calculate f XOR enc_flag[0], to find k2 calculate s XOR enc_flag[1]

Write in Python script:

enc = binascii.unhexlify(open("flag.enc",'rb').read())

k = [ord('f') ^ enc[0], ord('s') ^ enc[1]]
print(k)
print(decrypt(enc,k))
# [40, 186]
# b'fsKn0wnPl41NTextA77ackcyber'            

As you can see, we get the same key and same flag!

Full python script


Monalisa

Challenge files

We are given a PCAP file (Packet capture file)

Lets open it with Wireshark

There are many packets in the file..

We can filter the content by enter http in the filter and enter:

image1

Can see there are many stuff like html file, style css etc.

We can export the objects by going File > Export Objects > HTTP

image2

After that click Save All to a folder

Analyze

Saw a file called client.js seems interesting..

document.addEventListener("DOMContentLoaded", function() {
    function x() {
        return Math.floor(100 * Math.random()) + 1
    }
    var n = {
            click: !1,
            move: !1,
            pos: {
                x: 0,
                y: 0
            },
            pos_prev: !1
        },
        e = document.getElementById("board"),
        t = e.getContext("2d"),
        i = window.innerWidth,
        c = window.innerHeight,
        r = io.connect();
    e.width = i, e.height = c, e.onmousedown = function(o) {
            n.click = !0
        }, e.onmouseup = function(o) {
            n.click = !1
        }, e.onmousemove = function(e) {
            n.pos.x = e.clientX / i + x(), n.pos.y = e.clientY / c + x(), n.move = !0
        }, r.on("coor", function(o) {
            var n = o.line,
                x1 = (n[0].x - Math.floor(n[0].x)) * i,
                y1 = (n[0].y - Math.floor(n[0].y)) * c,
                x2 = (n[1].x - Math.floor(n[1].x)) * i,
                y2 = (n[1].y - Math.floor(n[1].y)) * c;
            t.beginPath(), t.moveTo(x1, y1), t.lineTo(x2, y2), t.stroke()
        }),
        function o() {
            n.click && n.move && n.pos_prev && (r.emit("coor", {
                line: [n.pos, n.pos_prev]
            }), n.move = !1), n.pos_prev = {
                x: n.pos.x,
                y: n.pos.y
            }, setTimeout(o, 25)
        }()
});

Seems like it is sending some x and y coordinates..

Also saw the HTML file:

<!DOCTYPE html>
<html>
   <head>
      <title>monalisa</title>
      <meta charset="utf-8">
      <link rel="stylesheet" href="style.css" />
      <script src="socket.io/socket.io.js"></script>
      <script src="client.js"></script>
   </head>
   <body>
      <canvas id="board"></canvas>
   </body>
</html>

The challenge name called Monalisa, so we can guess it is drawing the flag, and the draw coordinates was recorded in this packet capture file

Lets try to find the coordinates!

After finding in the wireshark, finally I found the coordinates in WebSocket!

image3

We can see the whole coordinates Right Click > Follow > TCP Stream

image4

Then it will show all coordinates sent:

image5

Click Save as to save as a file

Solving

We need to investigate how to recover the “drawing”

By searching Javascript drawing can see alot of tutorials like this one

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.moveTo(0,0);
ctx.lineTo(200,100);
ctx.stroke();

Is simple, moveTo is the starting point and lineTo is the ending point

Therefore we have to gather all coordinates in the capture file and then process it in this script!

We can use grep command in Bash to grab only the coordinates of the file:

grep -Po '\[{"x":[0-9]+\.[0-9]+,"y":[0-9]+\.[0-9]+},{"x":[0-9]+\.[0-9]+,"y":[0-9]+\.[0-9]+}\]' coordinates | while read line;do echo $line,;done

Alternatively, can use python script:

import re
f = open("coordinates",'r').read()

coor = re.findall(r'\[{"x":[0-9]+\.[0-9]+,"y":[0-9]+\.[0-9]+},{"x":[0-9]+\.[0-9]+,"y":[0-9]+\.[0-9]+}\]', f)
for c in coor:
        print(c,end=',\n')

I use the HTML template on W3school:

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="200" height="100"
style="border:1px solid #d3d3d3;">
Your browser does not support the canvas element.
</canvas>

<script>

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.moveTo(0,0);
ctx.lineTo(200,100);
ctx.stroke();

</script>

</body>
</html>

Copy the coordinates into the script:

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas"
style="border:1px solid #d3d3d3;">
Your browser does not support the canvas element.
</canvas>

<script>

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var coor = [
            [{"x":23.06351665375678,"y":74.46076794657763},{"x":23.06351665375678,"y":74.46076794657763}],
...
...
...
]
for (var i = 0; i < coor.length; i++) {
    x1 = coor[i][0]["x"];
    y1 = coor[i][0]["y"];
    x2 = coor[i][1]["x"];
    y2 = coor[i][1]["y"];
    ctx.moveTo(x1,y1);
    ctx.lineTo(x2,y2);
    ctx.stroke();
}

</script>

</body>
</html>

Open it in browser, it will show the flag (if not mistaken..)

image6

Its not like flag or Monalisa…

Look back the client.js:

function x() {
    return Math.floor(100 * Math.random()) + 1
}
...
...
e.onmousemove = function(e) {
    n.pos.x = e.clientX / i + x(), n.pos.y = e.clientY / c + x(), n.move = !0
},

Notice it got add some random values in the coordinates! But how we gonna guess the coordinate if we don’t know the flag??

Look carefully in how it draw the lines:

i = window.innerWidth,
c = window.innerHeight,
...
...
r.on("coor", function(o) {
    var n = o.line,
        x1 = (n[0].x - Math.floor(n[0].x)) * i,
        y1 = (n[0].y - Math.floor(n[0].y)) * c,
        x2 = (n[1].x - Math.floor(n[1].x)) * i,
        y2 = (n[1].y - Math.floor(n[1].y)) * c;
    t.beginPath(), t.moveTo(x1, y1), t.lineTo(x2, y2), t.stroke()
})

It takes the coordinates minus the integer part left only decimal part (minus Math.floor) then multiply by width or height

That means we don’t even need to guess the random number, because we only need the decimal part and integer part (random number) is not important!

Added some code to minus and multiply the width and height:

var w = window.innerWidth;
var h = window.innerHeight;
canvas.width = w;
canvas.height = h;

for (var i = 0; i < coor.length; i++) {
    x1 = (coor[i][0]["x"] - Math.floor(coor[i][0]["x"])) * w;
    y1 = (coor[i][0]["y"] - Math.floor(coor[i][0]["y"])) * h;
    x2 = (coor[i][1]["x"] - Math.floor(coor[i][1]["x"])) * w;
    y2 = (coor[i][1]["y"] - Math.floor(coor[i][1]["y"])) * h;
    ctx.moveTo(x1,y1);
    ctx.lineTo(x2,y2);
    ctx.stroke();
}

It draw us the Monalisa!! Full HTML script

image7

Interesting and fun forensics challenge!

Web

Challenge file:

We are given a HTML file, lets look at it:

<script>
    var url_string=window['location']['href'],url=new URL(url_string),p=url['searchParams']['get']('p');function ebg13(_0x394e9e){var _0x1bad7e='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',_0xe7ba36='NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm',_0x178280=_0x4299b3=>_0x1bad7e['indexOf'](_0x4299b3),_0x4e7551=_0x228b79=>_0x178280(_0x228b79)>-0x1?_0xe7ba36[_0x178280(_0x228b79)]:_0x228b79;return
    ...
    ...
    ...
</script>

Looks like obfuscated.. lets beautify it (Can use online Javascript Beautifier)

I use sublime text package for Javascript Beautify

Analyze

At the start of the script we can see it get the parameter p from the URL:

var url_string = window['location']['href'],
url = new URL(url_string),
p = url['searchParams']['get']('p');

Then at the end of script it check the p is length of 0x18 (24) and pass to check function:

p != null && p['length'] == 0x18 && check(p); 

Can see I test added p parameter in the URL:

image1

Looks like p is the password and also flag

Lets look into check function:

function check(_0x1c2dbb) {
    var _0x48f7e1 = [aa, bb, cc];
    for (var _0x4b521e = 0x0; _0x4b521e < _0x48f7e1['length']; _0x4b521e++) {
        var _0x1fd69e = _0x48f7e1[_0x4b521e](_0x1c2dbb);
        if (!_0x1fd69e) {
            alert('Not\x20quite\x20right');
            return;
        }
    }
    alert('Accepted\x20:)');
}

It looks messy! let me clean up abit to see whats going on:

function check(flag) {
    var func = [aa, bb, cc];
    for (var i = 0x0; i < func['length']; i++) {
        var correct = func[i](flag);
        if (!correct) {
            alert('Not\x20quite\x20right');
            return;
        }
    }
    alert('Accepted\x20:)');
}

Now we can see clearly, it just pass our password (flag) into 3 functions aa bb cc

Then if one of them return false then our flag is not correct!

First part (Sha256)

Look into function aa:

function aa(_0x23c1f1) {
    var _0xbdcdeb = ['dce7cce055566bed799f788cd0048e209a27a473c0f48b956fa1f1780e80d2c1', '635ca73d00d4f28b5f573b16eea56e9e4579d77e561c32aa68189d9769fa1753', 'a4d0ef23161b5b7c6a8d5b287543fd74e16b3bf313d71aa187c24cdd728a7b1e', 'e0b9a8799f32453a478c9122f8b83cee68e16db18f493ac81bc1d474594b5df4'],
        _0x39b271 = _0x23c1f1['substring'](0x0, 0x8)['match'](/.{1,2}/g);
    for (var _0x38d68b = 0x0; _0x38d68b < _0x39b271['length']; _0x38d68b++) {
        var _0x5169a8 = ansdweasd(_0x39b271[_0x38d68b]);
        if (_0x5169a8 != _0xbdcdeb[_0x38d68b]) return ![];
    }
    return !![];
}

Hmm.. the long string in the variable seems like hash, lets try to crack it in crackstation

image2

It is a hash! Is Sha256 and it cracked the flag format fs

After cracking all 4 hashes, we can get the part of flag fs0bfusc

Can use it running ['substring'](0x0, 0x8) with our flag (1st character to 8th character) so total 8 characters

Second part (ROT13)

We will look at function cc first:

function cc(_0x159c99) {
    var _0x593622 = ebg13(_0x159c99['substring'](0x10, 0x18));
    return _0x593622 == '4egplore';
}

Can see it pass the last 8 characters of the flag to function ebg13:

function ebg13(_0x394e9e) {
    var _0x1bad7e = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
        _0xe7ba36 = 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm',
        _0x178280 = _0x4299b3 => _0x1bad7e['indexOf'](_0x4299b3),
        _0x4e7551 = _0x228b79 => _0x178280(_0x228b79) > -0x1 ? _0xe7ba36[_0x178280(_0x228b79)] : _0x228b79;
    return _0x394e9e['split']('')['map'](_0x4e7551)['join']('');
}

This function looks like doing some mapping of alphabet, we can guess this is doing the famous encryption ROT13!! The function name also hint it

Therefore, we just have to decrypt 4egplore using ROT13, it is

4rtcyber

Third part (XOR)

Now we look at function bb:

function bb(_0x507300) {
    var _0x3360b0 = _0x507300['substring'](0x0, 0x2) + _0x507300['substring'](0x14, 0x16),
        _0x5415bb = _0x507300['substring'](0x8, 0x10),
        _0x244a1b = [0x52, 0x7, 0x4a, 0x6, 0x4, 0xa, 0x0, 0x12];
    for (var _0x58422e = 0x0; _0x58422e < _0x5415bb['length']; ++_0x58422e) {
        var _0x2e8721 = _0x3360b0[_0x58422e % _0x3360b0['length']]['charCodeAt'](0x0),
            _0x40bbc5 = _0x5415bb[_0x58422e]['charCodeAt'](0x0);
        if (_0x244a1b[_0x58422e] != (_0x2e8721 ^ _0x40bbc5)) return ![];
    }
    return !![];
}

Looks messy also clean it up:

function bb(flag) {
    var f1 = flag['substring'](0x0, 0x2) + flag['substring'](0x14, 0x16),
        f2 = flag['substring'](0x8, 0x10),
        verify = [0x52, 0x7, 0x4a, 0x6, 0x4, 0xa, 0x0, 0x12];
    for (var i = 0x0; i < f2['length']; ++i) {
        var a = f1[i % f1['length']]['charCodeAt'](0x0),
            b = f2[i]['charCodeAt'](0x0);
        if (verify[i] != (a ^ b)) return ![];
    }
    return !![];
}

Ok look much better..

Can see it perform some XOR operation, we know that XOR operation can be reverse easily

Like A XOR B = C then, A = C XOR B, B = C XOR A

We know the first and last 8 characters of the flag, which means we know what is f1, can see the code f1 is XOR with f2

Therefore, we can calculate middle part of flag (which is f2) based on f1 and the verify variable!

Write a simple python script to calculate this:

flag = bytearray("fs0bfusc????????4rtcyber",encoding="ascii")
f1 = flag[0:2] + flag[0x14:0x16]
verify = [0x52, 0x7, 0x4a, 0x6, 0x4, 0xa, 0x0, 0x12]

for i in range(8):
    flag[i+8] = verify[i] ^ f1[i%4]
print(flag)
# bytearray(b'fs0bfusc4t3dbyyp4rtcyber')

We made it! We get the whole flag! Lets test it in the URL:

image3

Thats it simple JS reversing!