GRIC

NRIC goes Global!

Files given:

Checksum

The challenge is to predict 100 checksums in a row. The algorithm for computing a checksum is:

constants = [0] * LEN_GRIC
sbox = list('ABCDEFGHJKLMRTXYZ')
def setup():
    global constants, sbox
    constants = [random.randint(0, 17) for x in range(LEN_GRIC)]
    random.shuffle(sbox)

def get_checksum(n): return sbox[sum(map(lambda x: int(x[0]) * int(x[1]), zip(n, constants))) % len(sbox)]

This algorithm is a rather simple linear checksum. It multiplies each digit by a random coefficient, then the final result is taken mod 17 and a random sbox is looked up to figure which letter corresponds to the checksum.

The difficulty here lies in the fact we can only get 25 checksums before the server refuses to give anymore to us.

Solution

We can assign each letter a number from 0 to 16 and each constant as a variable. Notice that for each checksum request, for instance G0123456789A, we get an equation of the form 0c0+1c1++9c9=A, which are linear equations that have no constant term. This tells us that we are interestd in finding the kernel of some matrix. Furthermore since everything is done modulo 17, our matrix elements are in F17.

We can represent each equation as a row of a matrix M, where the first 10 columns are our input digits and the rest of the columns are 0 except the column that is the checksum letter, where we assign 1=16(mod17). This gives us a 25 by 27 matrix that we are interested in finding the right kernel. This can easily be done in sage:

sage: M.right_kernel()
Vector space of degree 27 and dimension 4 over Finite Field of size 17
Basis matrix:
[ 1 12 15 10  7  5 10  1  0 16  0  3  0  7 10  5  0  2 15 16  6 11  0  4  9  8 12]
[ 0  0  0  0  0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0]
[ 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  0  0]
[ 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  1  0  0  0  0]

Here we immediately see the unknown coefficients have a unique solution ci, but some of the letters are interchangable with each other (the last 3 vectors). Importantly, we can use the fact that there is a bijective map between the letters and numbers in F17, this yields 3!=6 possible solutions in this case. As there are very little permutations possible, we can simply try several timse and we will eventually get the flag.

Note that we can arbitrarily multiply our solutions by any number. However since the constants and letters are changed accordingly, the output will remain the same.

With this, we eventually get the flag:

Wow you did it. Congratulations! Here flag!
CTFSG{NRIC_m3meS_F0R_GRIC_7eENs}

The solution script can be found at solve.py and the script to compute the kernel can be found at ker.sage

Flag: CTFSG{}