Solving the GrrCon Black Badge Cipher
Solving the GrrCon Black Badge Cipher

Solving the GrrCon Black Badge Cipher

Published
Published October 10, 2021
👉
This post was a carry over from my previous site so formatting might be a bit off.

Quick Note

I posted this over at my Obsidian vault hosted at My Obsidian Vault. It may be a bit easier to read there.

Video Walk-Through

Video preview

Challenge

(3:37:48 EDT) Dantheman: I am PUMPED for GrrCon! Going stir crazy. (15:24:49 EDT) EggDropX: I am too! Working endless hours to get everything sorted. It is going to be prime! (17:15:26 EDT) Dantheman: I heard keeping the beer cold will not be a factor this year. (6:30:15 EDT) EggDropX: You’re right! All the **factors** are figured. At GrrCon we **go a bit further** (16:41:21 EDT) Dantheman: I am thinking of setting up a **base** of operations at one of the tables to hack away at things. (7:43:13 EDT) EggDropX: That would be **capital** – There are plenty of tables to setup a variety of different **bases** if you would like. (4:41:46 EDT) Dantheman: I can barely sleep I am so excited. I haven’t see the sun for days. (5:28:4 EDT) EggDropX: Somebody is asking for another crypto challenge. (5:11:47 EDT) Dantheman: I will see what I can do… (0:0:28 EDT) Dantheman: I have nothing but **time** on my hands until GrrCon happens! ———————— (0:52:55 EDT) EggDropX: Hello, you awake at this hour? (8:28:28 EDT) Dantheman: No. But give me a **second**. Not all of us can go with no sleep. (19:19:39 EDT) EggDropX: Where are you with the crypto challenge? (1:31:21 EDT) Dantheman: Good news – it is ready and you won’t be asked for another (6:1:20 EDT) EggDropX: Why… (5:40:16 EDT) Dantheman: Because by my estimate it will take them **days** to solve this… though probably years… (8:52:31 EDT) EggDropX: Why… (21:10:14 EDT) Dantheman: Two custom built ciphers, two SMS bots, two curve balls, and a whole lot of custom code to reverse engineer. (6:43:12 EDT) EggDropX: Whelp they did want a challenge. (17:36:23 EDT) Dantheman: I can drop hints on twitter if they get stuck… every second counts… (0:21:44 EDT) EggDropX: I will make it worth their time!

Stage 1 (A) - Base86400 => ASCII

Overview

The general goal is to determine that the clocks are representative of base 86400 numbers and calculate out a giant number. The part which I feel is an INSANE jump in logic is to use prime factorization on the number and see the exponential pattern. Using a prime number cipher words form.

Code

import math import numpy as np cipherTxt = [[ "3:37:48", "15:24:49", "17:15:26", "6:30:15", "16:41:21", "7:43:13", "4:41:46", "5:28:4", "5:11:47", "0:0:28" ],[ "0:52:55", "8:28:28", "19:19:39", "1:31:21", "6:1:20", "5:40:16", "8:52:31", "21:10:14", "6:43:12", "17:36:23", "0:21:44" ]] primaryLookup = { 2:"A", 3:"B", 5:"C", 7:"D", 11:"E", 13:"F", 17:"G", 19:"H", 23:"I", 29:"J", 31:"K", 37:"L", 41:"M", 43:"N", 47:"O", 53:"P", 59:"Q", 61:"R", 67:"S", 71:"T", 73:"U", 79:"V", 83:"W", 89:"X", 97:"Y", 101:"Z" } def prime_factors(n): i = 2 factors = [] while i * i <= n: if n % i: i += 1 else: n //= i factors.append(i) if n > 1: factors.append(n) return factors def Sort(sub_li): # reverse = None (Sorts in Ascending order) # key is set to sort using second element of # sublist lambda has been used return(sorted(sub_li, key = lambda x: x[1])) for cipher in cipherTxt: number = [] total = 0 for time in cipher: times = time.split(":") times_int_array = list(map(int, times)) # print(times_int_array) seconds = (times_int_array[0] * 60 * 60) + \\ (times_int_array[1] * 60) + (times_int_array[2]) number.append(seconds) # print(seconds) for index, entry in enumerate(number): baseToConvert = 24 * 60 * 60 # 24 hour clock x 60 minutes x 60 seconds 86400s ... where as a 12 hour clock is 43200seconds total += entry * baseToConvert ** index print(total) primeArray = prime_factors(total) x = np.array(primeArray) y = np.bincount(x) ii = np.nonzero(y)[0] result = np.vstack((ii,y[ii])).T print(result) decodedString = '' for character in Sort(result): decodedString += primaryLookup[character[0]] print(decodedString)

Output

7570544468224711313445832099254724140259222668 [[ 2 2] [ 3 6] [11 5] [23 7] [43 8] [53 1] [67 3] [71 4]] PASTEBIN 30245395812315433401997367499706862980116448751734375 [[ 5 6] [ 7 1] [23 5] [29 8] [53 7] [67 3] [79 6]] DSICVPJ
Making a bit sense of the output. The first line is what the number calculates out to be. The second part is the prime factorization. Notice the pattern below. 53^1 = P 2^2 = A 67^3 = S 71^4 = T 11^5 = E 3^6 = B 23^7 = I 43^8 = N
The last line is the out from the lookup. PASTEBIN DSICVPJ

Stage 2 - Brute forcing PasteBin

Overview

Issue I found is the link https://pastebin.com/DSICVPJ doesn't work. So I assumed the capitalization was wrong. So I did a straight forward directory brute force but only messed with the capitalization. About ~255 possible combinations so that is reasonable. Ended up only taking taking 101 attempts to get a 200 from the server, i.e., dCSciVpJ.

Result

Final capitalization that worked: https://pastebin.com/dCSciVpJ

Archive of the page (with extra spaces removed where I noted)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "<http://www.w3.org/TR/html4/loose.dtd>"> <html> <head> <title>GrrConCrypto Decode</title> <br> Maybe you can help me decode this:<br> <strong> 唶啮噂偹囷啲分孥垒側囲倫匓圷匳匸厓儳匳圠劅卍唲夠噦彲切周噒偮噗塴切却噗倮 </strong> <!-- I REMOVED SPACE HERE (Note from Silk) --> <!-- It is possible to do this without any programming whatsoever --> <!-- I REMOVED BREAKS HERE (Note from Silk) --> <br> Below is the encode function... I seem to have forgoten the decode function... oops. <br> <!-- I REMOVED SPACE HERE (Note from Silk) --> <br> <br> <input type="text" id="toEncode"/> <input type="button" onclick="doEncode()" value="encode"/><br/><br/> <div id="outputContainer" style="display:none;"> <span style="display: inline-block; width: 120px;">Encoded:</span><span id="outputEnc"></span><br/> </div> </body> </body> <!-- I REMOVED SPACE HERE (Note from Silk) --> <script type="text/javascript"> GrrConCrypto = function() { } GrrConCrypto.BASE_FLAG_START = 0x04000; GrrConCrypto.BASE1_START = 0x05000; GrrConCrypto.BASE_FLAG_SIZE = 0x100; GrrConCrypto.BASE1_SIZE = 0x01000; GrrConCrypto.encode = function(raw) { var offset = 0; var result = ""; for(var i=0;i<raw.length*2-2;i+=3) { if(i%2==0) offset = ((raw[i/2]<<4)|((raw[i/2+1]>>>4)&0x0f))&0x0fff; else offset = ((raw[(i-1)/2]<<8)|(raw[(i+1)/2]&0xff))&0x0fff; offset += GrrConCrypto.BASE1_START; result += String.fromCharCode(offset); } if((raw.length*2) % 3 == 2) { offset = (raw[raw.length-1]&0xff)+GrrConCrypto.BASE_FLAG_START; result += String.fromCharCode(offset); } else if((raw.length*2) % 3 == 1) { offset = ((raw[raw.length-1]&0x0f))+GrrConCrypto.BASE_FLAG_START; result += String.fromCharCode(offset); } return result; }</script> </head> <body> <script type="text/javascript" src="<http://code.jquery.com/jquery-1.11.1.min.js>"></script> <script type="text/javascript"> toString = function(array) { var result = ""; for(var i=0;i<array.length;i++) result+=String.fromCharCode(array[i]); return result; }; toArray = function(string) { var result = new Uint8Array(string.length); for(var i=0;i<string.length;i++) result[i] = string.charCodeAt(i); return result; }; doEncode = function() { var encoded = GrrConCrypto.encode(toArray($('#toEncode').val())); $('#outputEnc').text(encoded); $('#outputContainer').show(); }; </script>

Stage 3 - Reversing the Encode

Code

const decode = (raw) => { const base1Start = 0x05000; //can be 0 var code = 0; var buffer = []; var intCollector = []; for (var i = 0; i < raw.length; i++) { code = raw.charCodeAt(i); intCollector.push(code); } for (var i = 0; i < intCollector.length; i++) { intCollector[i] -= base1Start; // can if (i % 2 === 0) { buffer.push((intCollector[i] >>> 4) & 0xff); } else { buffer.push(((intCollector[i - 1] << 4) | ((intCollector[i] & 0x0f00) >>> 8)) & 0xff); buffer.push(intCollector[i] & 0xff); } } return buffer; } const toString = (array) => { let result = ""; for (var i = 0; i < array.length; i++) result += String.fromCharCode(array[i]); return result; }; const doEncode = () => { const encoded = '唶啮噂偹囷啲分孥垒側囲倫匓圷匳匸厓儳匳圠劅卍唲夠噦彲切周噒偮噗塴切却噗倮' const decoded = toString(decode(encoded)); console.log(decoded) }; doEncode()

Output

Encoded: 唶啮噂偹囷啲分孥垒側囲倫匓圷匳匸厓儳匳圠劅卍唲夠噦彲切周噒偮噗塴切却噗倮
Decoded: Send your key to +17733891337 (SMS) for the next step.

Stage 4

Overview

The response "Please enter your key" is a riddle. You needed to input "your key" back.

Sending SMS to +17733891337

Silk: Hello?
+1 (773) 389-1337: Incorrect. Please enter your key.
Silk: your key
+1 (773) 389-1337: Correct - GrrCon goes Further: 40 41 3g 4b 40 41 1f 4g 44 45 4f 37 1f 50 2g 51 46 3g 49 51 2e 45 41 4a 51 55 43 50 43 3g 49 51 55 3g 43 36 50 3g 2g 50 43 50 2e 2f 4g 45 3g 4f 53 4a 4g 3g 52 4b 4g 41 32 49 4g 49 55 4a 52 3a 3a

Stage 5

Overview

The code you get back goes a literal bit further. Instead of Base 16 (Hex), it is Base 17. Hence the "g" characters.

Code

grrConCode = '40 41 3g 4b 40 41 1f 4g 44 45 4f 37 1f 50 2g 51 46 3g 49 51 2e 45 41 4a 51 55 43 50 43 3g 49 51 55 3g 43 36 50 3g 2g 50 43 50 2e 2f 4g 45 3g 4f 53 4a 4g 3g 52 4b 4g 41 32 49 4g 49 55 4a 52 3a 3a' grrConCodeArray = grrConCode.split(' ') result = '' for character in grrConCodeArray: result += (chr(int(character,17))) print(result)

Result

DECODE THIS: U2VJCMV0IENVZGUGCMVZCG9UC2UGU01TICSXNTCWOTE5MTMZNW==

Stage 6

Overview

I thought this would be simple... Nope. While it is Base64 the capitalization is screwed up. This can be fixed manually or I found some code online I modified to get an array of possible outputs.

Code

import base64 import itertools def b64check(s): for grp in [s[i:i+16] for i in range(0,len(s),16)]: out=[] for itp in set(list(map(''.join, itertools.product(*zip(grp.upper(), grp.lower()))))): bdc=base64.b64decode(itp) if all(c in range(32,127) for c in bdc): out.append(str(bdc,'utf-8')) print(out) resultsArray = b64check("U2VJCMV0IENVZGUGCMVZCG9UC2UGU01TICSXNTCWOTE5MTMZNW==") print('\\nManually selecting sections that make sense:') print('Secret Code response SMS +15709191337') # print('\\nManually dorking with base64 capitalization...') # manually = "U2VjcmV0IENvZGUgcmVzcG9uc2UgU01TICsxNTcwOTE5MTMzNw==" # print(base64.b64decode(manually))

Result

['Secret CUde ', 'SeIret Code ', 'SeIret CUde ', 'Secret Code '] ['response SMS', 'respoTse SMm', 'respoTse SMS', 'response SMm', 'reYpoTse SMS', 'reYpoTse SMm', 'reYponse SMS', 'reYponse SMm'] [' +1570919133'] ['5', '7'] Manually selecting sections that make sense: Secret Code response SMS +15709191337
Since the last number ended with 1337 it made sense that this one would as well. I am guessing the designer either knew that or did not realize that the last part could be a 5 or a 7.

Stage 7 - Final SMS to +15709191337

Overview

Nothing special here. I just sent an SMS.

Result

Silk: Hello?
+1 (570) 919-1337: You are reached the end. Congratulations! You are victorious! Decrypting code... allyourbasearebelong2uslookingforwardtoGrrCon
notion image

Thoughts?

This was insane and mind numbing, but I got it!