This page looks best with JavaScript enabled

Cyber Talents - R3V3RS3 ME

Tries: 782 Times, Solved: 23 Times

 ·  ☕ 11 min read  ·  👻 Ahmed Raof
⬇️ Challenge Link: R3V3RS3 ME

Intro

Welcome to my blog, today we will delve into the intricacies of a reverse engineering challenge from Cybertalent, specifically, the challenge known as R3V3RS3 ME. This challenge presents us with a .jar file, and as any reverse engineer would know, the first step in solving this challenge is to decompile the file. To accomplish this, we will use the online decompiler

We will be presented with a Java file upon obtaining the decompilation results. We can then proceed to open and analyze the file.

Briefly

I have included an image to give a brief description of the program’s functionality.

Encryption

The program begins by prompting the user for the key and message. It is important to note that the key must not contain any repeated characters, and the message should be only composed of both [a-z] and digits [0-9]. Next, the “col” variable, a 2-D array, is created and its size is set using a specific formula within the prepareColumns() method. Then, the encryption process begins, the program loops through each character of the message, and determines their position in the ‘grid’ array, which is defined in the Messedup() class. Each character is then represented in the encryption text by two characters. For example, if the character is “A”, the position in the ‘grid’ array is (4, 3). The program then takes the value 4 and looks up the corresponding character in the morse array, which is ‘Y’ and value 3 which is ‘G’ in morse. Therefore, “A” will be represented as “YG”. After that do some sort operation to colAlpha. Finally, the resulting encrypted text is represented in the ‘colAlpha’ variable and printed as the encryption result.”

For example if the key is “tes” and msg is “hello”,

  • the value of nb will be [4, 3, 3] that’s mean the col size is (max(nb), len(key))
  • message to encrypt is “HELLO
  • after mapping the value of message in grid then get the value in morse array the final value will be GG XK KX KX DG
    • grid = [
      [‘F’, ‘T’, ‘B’, ‘0’, ‘8’, ‘L’],
      [‘J’, ‘2’, ‘D’, ‘O’, ‘S’, ‘3’],
      [‘7’, ‘V’, ‘C’, ‘1’, ‘M’, ‘I’],
      [‘W’, ‘U’, ‘4’, ‘H’, ‘K’, ‘X’],
      [‘5’, ‘P’, ‘Q’, ‘A’, ‘N’, ‘R’],
      [‘E’, ‘6’, ‘Y’, ‘G’, ‘T’, ‘9’]
      ]
    • for example “H” position in grid is (3, 3)
    • morse = [‘K’, ‘D’, ‘I’, ‘G’, ‘Y’, ‘X’]
    • the value will be GG and so on..
  • col will be like

  • so the value of col will be GKKG GKX XXD (READ COLUMN BY COLUMN)
  • then we need to sort it to get colAlpha
    • the key is “tes” if we sort it the result will be “est
    • and we do sort operation to col as the key is sorted that’s mean colAlpha = GKX XXD GKKG
    • finally our encryption text is colAlpha (enc = GK XX XD GK KG)

SOLVE

Finally, I will provide you with the code solution for this challenge. By reading through the comments, you can gain a deeper understanding of the code and its purpose. This will be helpful for you to understand the solution.

  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
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
import numpy as np
from itertools import combinations
from string import printable
from itertools import permutations

grid = [   
    ['F', 'T', 'B', '0', '8', 'L'], 
    ['J', '2', 'D', 'O', 'S', '3'], 
    ['7', 'V', 'C', '1', 'M', 'I'], 
    ['W', 'U', '4', 'H', 'K', 'X'], 
    ['5', 'P', 'Q', 'A', 'N', 'R'], 
    ['E', '6', 'Y', 'G', 'T', '9']
]
morse = ['K', 'D', 'I', 'G', 'Y', 'X']

# same functionality as prepareColumns()
def calc_nb(key_len, cipher_len):
    nbPerCol = cipher_len // key_len # col_len is key_len actually
    nb = [nbPerCol for i in range(key_len)]
    for i in range(cipher_len - key_len * nbPerCol): # where the value in range is "reminder"
        nb[i] += 1
    return nb

def unsort(key, enc):
    sorted_key = "".join(sorted(key))
    key_dic = {} # we need to make it like {original_pos: pos_after_sort, original_pos: pos_after_sort, ..}
    for i in range(len(key)):
        key_dic[i] = sorted_key.find(key[i])
    unsorted_enc = [type(enc[i])]*len(enc)
    for k, val in key_dic.items():
        unsorted_enc[val] = enc[k]
    return unsorted_enc

def unsort_cipher(key, cipher):
    sorted_key = "".join(sorted(key))
    cipher_dic = {}
    for i, val in enumerate(sorted_key):
        cipher_dic[val] = cipher[i]
    new_cipher = []
    for i in key:
        new_cipher.append(cipher_dic[i])
    return new_cipher

def dec(key, cipher):
    ############### FIRST GET COL
    # first we need the cipher to be in format of nb for example [4, 3, 3]
        # but NOTES THAT value after split operation is sorted in encrypted cipher so we have nb like [3 3 4] for example
            # so we need to unsort the "nb" and then reshape the cipher
        # so we need to [calculate the value of nb - unsort nb depend on key - then edit cipher]
    nb = calc_nb(len(key), len(cipher)) # in prepareColumns the len passed is (len_msg*2) which is the same as cipher cause msg_len = cipher / 2
        # reposition the value of nb (unsort it)
    unsort_nb = unsort(key, nb)
        # cipher HERE EQUAL "colAlpha"
    cipher = ["".join(i) for i in np.array_split(list(cipher), np.cumsum(unsort_nb)[:-1])] # reshape the cipher
        # cipher HERE EQUAL "col"
    cipher = unsort_cipher(key, cipher)
    ############### SECOND ORDER CIPHER (COL) CORRECTLY
    # if we notice that the program calculate the col as the following
        # if we have text "ahmed" and mappend in morse to be "YG GG IY XK DI"
    '''
        REMEMBER nb = [4, 3, 3]
        then the value of col (where the key len is 3) will be
        Y G G
        G I Y
        X K D
        I
        where it fill from left to right until len of 3 so "Y G G" and move to the next line and so on
    '''
    '''
        then it read it like
        YGXI GIK GYD => col value
    '''
    '''
    so to get the original value back we need to do the following
    [create a 2d array where we fill column first with the value - then read each row]
    '''
    col = np.full((max(nb), len(key)), "?", dtype=str)
    str_cipher = "".join(cipher)
    c, k = 0, 0
    # create a 2d array where we fill column first with the value
    for i in nb:
        for j in range(i):
            col[j][k] = str_cipher[c]
            c += 1
        k += 1
    # then read each row (read 2d array normally)
    final_res = ""
    for i in range(col.shape[0]):
        for j in range(col.shape[1]):
            if col[i][j] == "?":
                continue
            else:
                final_res += col[i][j]
    ############### THIRD GET TWO CHAR IN CIPHER - GET POSITION IN MORSE - MAP IT IN GRID
    haha_got_the_text = ""
    for i in range(0, len(final_res), 2):
        two_char = final_res[i:i+2]
        x, y = morse.index(two_char[0]),  morse.index(two_char[1])
        haha_got_the_text += grid[x][y]
    
    return haha_got_the_text

cipher = "XY XY XG IY IY XX YG KY XD XY XK DX DY DX YX DX YY IY DY DD XD YY KD XI XX YG".replace(" ", "")
# NOW LET'S START A SHITTY BRUTE-FORCE, HMMMMMMMMM :(
with open("all_possible_pt.txt", "w") as f:
    for i in ['A', 'AB', 'ABC', 'ABCD', 'ABCDE', 'ABCDEF', 'ABCDEFG']:
        for j in permutations(i):
            key = "".join(j)
            pt = dec(key, cipher)
            f.write(f"{key}: {pt}\n")
            
🚩 R3V3RS33N9IN33RIN9KANB3FUN

In Details

 
The main function starts by asking the user for a key and then creates a new instance of the Messedup() class and assigns it to a variable called “spaghetti”. The variable “key” is passed as an argument to the constructor of the Messedup() class, which initializes the grid variable and sets the key variable to the passed in key. “spaghetti” variable can be used to call methods of the Messedup() class, such as “encode” method. Messedup() also do some operation and then return setKey(key).

 
After that, the main function asks the user for a message to encrypt and assigns it to a variable called “msg”. The “msg” variable is then passed as an argument to the “encode” method of the “spaghetti” object. The encode method performs several operations, such as calling the prepareColumns(), msgToProcess(), and findPos() methods, before returning the final encrypted output. The main function then prints the encrypted output and the encrypted flag

 
Now that we have a general understanding let’s breaking down the logic and operations of each function.

Moving to Messedup() class, we can observe that the class initializes an ArrayList, ‘al’, containing a set of characters {‘9’, ‘T’, ‘G’, ‘Y’, ‘6’, ‘E’, ‘R’, ‘N’, ‘A’, ‘Q’, ‘P’, ‘5’, ‘X’, ‘K’, ‘H’, ‘4’, ‘U’, ‘W’, ‘I’, ‘M’, ‘1’, ‘C’, ‘V’, ‘7’, ‘3’, ‘S’, ‘O’, ‘D’, ‘2’, ‘J’, ‘L’, ‘8’, ‘0’, ‘B’, ‘T’, ‘F’, ‘Z’}. Additionally, a 2-dimensional array, ‘grid’, is declared with a length of 6x6, corresponding to the length of Morse variable. The class then proceeds to execute a nested loop, which iterates over the elements of ‘al’ and assigns them to the corresponding positions within ‘grid’.”


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
al = ['9', 'T', 'G', 'Y', '6', 'E', 'R', 'N', 'A', 'Q', 'P', '5', 
'X', 'K', 'H', '4', 'U', 'W', 'I', 'M', '1', 'C', 'V', '7', '3', 'S', 
'O', 'D', '2', 'J', 'L', '8', '0', 'B', 'T', 'F', 'Z']
grid = [['?' for i in range(6)] for j in range(6)]
index = 35
for i in range(6):
    for j in range(6):
        if len(al) != 0:
            grid[i][j] = al.pop(index) # or al[index]
            index -= 1
'''
grid = 
[   ['F', 'T', 'B', '0', '8', 'L'], 
    ['J', '2', 'D', 'O', 'S', '3'], 
    ['7', 'V', 'C', '1', 'M', 'I'], 
    ['W', 'U', '4', 'H', 'K', 'X'], 
    ['5', 'P', 'Q', 'A', 'N', 'R'], 
    ['E', '6', 'Y', 'G', 'T', '9']
]
'''

 
Next, we will move to setKey() method within the Messedup() class. This method performs several operations, including a:

  • check to verify if the key is empty or not.
  • It also verifies if the key contains unique characters and if not, it returns an empty key.
  • The final operation is defining a private inner class called “Column” within another class. The Column class has a constructor that takes in a single char parameter called “header” and assigns it to the “header” instance variable of the Column object. The class also has several methods such as “setSize”, “add”, “getChar”, “toString”, and “compareTo”.

 
Now, we will examine the encode() method within the Messedup() class. This method first passes the string to be encrypted, as an argument with True boolean value, to the msgToProcess() function.

We will now analyze the msgToProcess() method in detail.

  • Check if the input string “str” is null or if the key length is 0 it will immediately return a new empty char array
  • The method will first convert the input string to uppercase using the ‘toUpperCase()’, then convert the resulting string to an array of characters using ‘toCharArray()’, and assigns it to the “charArray” variable.
  • Then, the method will iterate over the “charArray” variable, and for each character, it will check if the character is between ‘A’ and ‘Z’ or between ‘0’ and ‘9’ and will append the character to the StringBuilder “sb”
  • After that, the method will convert the StringBuilder “sb” to an array of characters using the ‘toString().toCharArray()’ and assigns it to the “digit” variable. Then return the digit value to digit in encode()

1
2
3
4
5
6
7
8
def msgToProcess(str_v): # coding always True
    charArray = [i.upper() for i in str_v]
    sb = []
    for c in charArray:
        val = ord(c)
        if (val >= ord('A') and val <= ord('Z')) or (val >= ord('0') and val <= ord('9')):
            sb.append(c)
    return "".join(sb)

 
Then prepareColumns

To better understand the functionality of the prepareColumns() method, let’s provide an example.
If the input message is ‘hello’ and the key is ‘key’. In this case, the input to the prepareColumns() method is 10, which is the length of the message ‘hello’ multiplied by 2.

  • The variable “nbPerCol” is calculated by dividing the input value by the length of the ‘col’ array, which is 3 in this case. So, nbPerCol = 10/3 = 3.333. Since it is an integer variable, the result will be rounded down to 3.

  • An int array “nb” with the same length as the ‘col’ array is initialized. This array is then populated by looping through its values and assigning the value of nbPerCol to each element. The resulting array will be nb = [3, 3, 3].

  • The variable “reminder” is calculated by taking (10 - 3*3) = 1. Then a loop from j=0 until reminder, the value of nb = [4, 3, 3]

  • Finally, the last loop will make the size of ‘col’ array equal

    (max(nb), len(key))
     

 
Finally, we will examine the findPos() method within the Messedup() class. This method takes a character as an argument and searches for its position in the 2-dimensional array ‘grid’. Once the position is found, it is returned as a set of coordinates.

Share on

Ahmed Raof. AKA 50r4.
WRITTEN BY
Ahmed Raof
📚Learner🤓Nerd🔫reverse engineering👾malware analysis🔒cryptography