Scratching Machine

I have done this challenge with m1nds.

Handout

scrathing noises intensify
https://scratch.mit.edu/projects/1183933180/

First analysis

When opening the link, we arrive on a Scratch project. For those who don’t know Scratch:

Scratch is the world’s largest coding community for children and a coding language with a simple visual interface that allows young people to create digital stories, games, and animations.

By running the project, we get this: first_run

Let’s have a look at the actual “code” of the scratch project, by clicking on See inside:

first_edit

There are a lot of blocks, not well organized. Let’s organize them a bit by right-clicking and selecting Clean up Blocks.

We can now observe two things:

We now have two choices:

We chose the second option, because by rewriting the code in Python, we can easily test it and understand what is happening. As there is also a long array in the scratch project, we also use https://github.com/BirdLogics/sb3topy to extract it.

Rewriting the code in Python

We decided to start with the little functions, as they are easier to understand. I will explain here the method for the first one, and then just give the code for the others.

Let’s take this one:

first_function

Scratch is easy to understand, as it is block-based with simple operations. Let’s rewrite this in Python:

def abobus(w):
    A1 = 1
    B2 = 0
    C3 = w
    J4 = 31
    J5 = J4 - J4
    T6 = J5
    while B2 < C3:
        A1 = A1 * 2 + J5
        if J5 > 0:
            T6 = T6 + J5 - J5
        B2 = B2 + 1 + J5
        J5 = J5 * T6
    A1 = A1 - 1 - J5
    return A1 + J5

When before even trying to understand what the function is doing let’s compare the result we get from Python to the result from Scratch. We can do that in Scratch by removing the flag block from the main function, and create a little block that says the result of the function abobus.

running_first_function

We can see here that abobus(4) returns 15. Let’s see what our Python function returns:

print(abobus(4))

And we also get 15. We can try with other values, and we always have the same result. So our function seems correct.

Let’s try to deobfuscate it a bit. We can see that some variables are not relevant, as there are always 0, and some are never really used. By deleting them, and rename some others we have:

def abobus(w):
    total = 1
    counter = 0
    while counter < w:
        total = total * 2
        counter = counter + 1
    total = total - 1
    return total

We can see that the function returns 2^w - 1. After renaming the function in Python and in Scratch, we can move to the next one.

After doing the same for the other small functions, we have:

globals = [0 for _ in range(28)]


def power_w_min_1(w):
    return 2**w - 1

def logical_or(a, b):
    return a | b

def logical_xor(a, b):
    return a ^ b
    
def shift_a_by_n_left(a, n):
    return a << n

def size_bits(n):
    return n.bit_length()

def shift_a_by_n_right(a, n):
    return a >> n

def logical_and(a, b):
    return a & b

def sussus(a, n, w):
    global globals
    C3 = n % w
    D6 = power_w_min_1(w)
    globals[7] = D6
    E7 = shift_a_by_n_left(a, C3)
    globals[8] = E7
    F8 = logical_and(E7, D6)
    globals[9] = F8
    G9 = shift_a_by_n_right(a, (w - C3))
    globals[10] = G9
    H0 = logical_or(F8, G9)
    globals[11] = H0
    return H0

def notsussus(a, n, w):
    global globals
    x3 = n % w
    x4 = power_w_min_1(w)
    globals[1] = x4
    x5 = shift_a_by_n_right(a, x3)
    globals[2] = x5
    x6 = logical_and(x5, x4)
    globals[3] = x6
    x7 = shift_a_by_n_left(a, (w - x3))
    globals[4] = x7
    X8 = logical_and(x4, x7)
    globals[5] = X8
    x9 = logical_or(x6, X8)
    globals[6] = x9
    return x9

We decided not to change sussus and notsussus functions, as they are also modifying what we thought were global variables. We thought by testing that these functions were doing left circular shift and right circular shift, but we were not sure, so we kept the original names.

Let’s now rewrite the big block. I will not explain the whole process, as the code is not obfuscated here, and it is just a matter of translating the blocks to Python. Here is the code we obtained, after adding some logging, replacing the if else with match and getting the long array from sb3topy:

list_cacamaca = [0]
list_cacamaca = list_cacamaca + [17,142,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,44,30,4,30,188,142,85,54,77,46,212,151,22,149,190,68,143,14,165,95,28,189,158,100,87,252,143,117,206,133,38,101,159,4,31,140,135,36,63,124,55,68,127,133,30,101,6,181,158,100,15,196,62,69,230,165,142,29,231,252,191,180,215,76,182,197,190,5,238,40,0,1,69,0,2,1,37,3,2,1,3,2,35,4,3,28,4,3,37,5,4,28,5,4,37,6,3,3,6,1,35,7,6,12,4,7,39,3,4,1,2,1,25,2,69,148,0,2,1,37,3,2,1,3,2,35,4,3,1,3,69,35,5,3,41,4,5,141,1,2,1,25,2,69,191,38]


list_verysussy = [0]
for _ in range(8):
    list_verysussy.append(0)
watdat = len(list_cacamaca)
i = 1
test1 = 5
test2 = 2
globals[27] = size_bits(test1)
globals[12] = notsussus(test1, test2, globals[27])
while i < watdat:
    print(f"opcode: {list_cacamaca[i]:<3} at addr {i:<3}", end=" | ")
    match list_cacamaca[i]:
        case 0:
            print(f"reg[{list_cacamaca[i + 1]}] = {list_cacamaca[i + 2]}")
            list_verysussy[list_cacamaca[i + 1]] = list_cacamaca[i + 2]
            i += 2
        case 1:
            print(f"reg[{list_cacamaca[i + 1]}] += {list_cacamaca[i + 2]}")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] + list_cacamaca[i + 2]
            i += 2
        case 2:
            print(f"reg[{list_cacamaca[i + 1]}] += reg[{list_cacamaca[i + 2]}]")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] + list_verysussy[list_cacamaca[i + 2]]
            i += 2
        case 3:
            print(f"reg[{list_cacamaca[i + 1]}] -= {list_cacamaca[i + 2]}")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] - list_cacamaca[i + 2]
            i += 2
        case 4:
            print(f"reg[{list_cacamaca[i + 1]}] -= reg[{list_cacamaca[i + 2]}]")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] - list_verysussy[list_cacamaca[i + 2]]
            i += 2
        case 5:
            print(f"reg[{list_cacamaca[i + 1]}] *= {list_cacamaca[i + 2]}")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] * list_cacamaca[i + 2]
            i += 2
        case 6:
            print(f"reg[{list_cacamaca[i + 1]}] *= reg[{list_cacamaca[i + 2]}]")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] * list_verysussy[list_cacamaca[i + 2]]
            i += 2
        case 7:
            print(f"reg[{list_cacamaca[i + 1]}] &= {list_cacamaca[i + 2]}")
            globals[13] = logical_and(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
            list_verysussy[list_cacamaca[i+1]] = globals[13]
            i += 2
        case 8:
            print(f"reg[{list_cacamaca[i + 1]}] &= reg[{list_cacamaca[i + 2]}]")
            globals[14] = logical_and(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
            list_verysussy[list_cacamaca[i+1]] = globals[14]
            i += 2
        case 9:
            print(f"reg[{list_cacamaca[i + 1]}] |= {list_cacamaca[i + 2]}")
            globals[15] = logical_or(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
            list_verysussy[list_cacamaca[i+1]] = globals[15]
            i += 2
        case 10:
            print(f"reg[{list_cacamaca[i + 1]}] |= reg[{list_cacamaca[i + 2]}]")
            globals[16] = logical_or(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
            list_verysussy[list_cacamaca[i+1]] = globals[16]
            i += 2
        case 11:
            print(f"reg[{list_cacamaca[i + 1]}] ^= {list_cacamaca[i + 2]}")
            globals[17] = logical_xor(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
            list_verysussy[list_cacamaca[i+1]] = globals[17]
            i += 2
        case 12:
            print(f"reg[{list_cacamaca[i + 1]}] ^= reg[{list_cacamaca[i + 2]}]")
            globals[18] = logical_xor(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
            list_verysussy[list_cacamaca[i+1]] = globals[18]
            i += 2
        case 13:
            print(f"reg[{list_cacamaca[i + 1]}] <<= {list_cacamaca[i + 2]}")
            globals[19] = shift_a_by_n_left(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
            list_verysussy[list_cacamaca[i+1]] = globals[19]
            i += 2
        case 14:
            print(f"reg[{list_cacamaca[i + 1]}] <<= reg[{list_cacamaca[i + 2]}]")
            globals[20] = shift_a_by_n_left(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
            list_verysussy[list_cacamaca[i+1]] = globals[20]
            i += 2
        case 15:
            print(f"reg[{list_cacamaca[i + 1]}] >>= {list_cacamaca[i + 2]}")
            globals[21] = shift_a_by_n_right(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
            list_verysussy[list_cacamaca[i+1]] = globals[21]
            i += 2
        case 16:
            print(f"reg[{list_cacamaca[i + 1]}] >>= reg[{list_cacamaca[i + 2]}]")
            globals[22] = shift_a_by_n_right(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
            list_verysussy[list_cacamaca[i+1]] = globals[22]
            i += 2
        case 17:
            print(f"jump to {list_cacamaca[i + 1]}")
            i = list_cacamaca[i + 1] - 1
        case 18:
            print(f"reg[8] = cmp(reg[{list_cacamaca[i + 1]}], {list_cacamaca[i + 2]}), result = {list_verysussy[8]}")
            if list_verysussy[list_cacamaca[i + 1]] > list_cacamaca[i + 2]:
                list_verysussy[8] = 1
            if list_verysussy[list_cacamaca[i + 1]] == list_cacamaca[i + 2]:
                list_verysussy[8] = 0
            if list_verysussy[list_cacamaca[i + 1]] < list_cacamaca[i + 2]:
                list_verysussy[8] = -1
            i += 2
        case 19:
            print(f"reg[8] = cmp(reg[{list_cacamaca[i + 1]}], reg[{list_cacamaca[i + 2]}]), result = {list_verysussy[8]}")
            if list_verysussy[list_cacamaca[i + 1]] > list_verysussy[list_cacamaca[i + 2]]:
                list_verysussy[8] = 1
            if list_verysussy[list_cacamaca[i + 1]] == list_verysussy[list_cacamaca[i + 2]]:
                list_verysussy[8] = 0
            if list_verysussy[list_cacamaca[i + 1]] < list_verysussy[list_cacamaca[i + 2]]:
                list_verysussy[8] = -1
            i += 2
        case 20:
            print(f"if reg[{list_cacamaca[i + 1]}] == {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
            if list_verysussy[list_cacamaca[i + 1]] == list_cacamaca[i + 2]:
                i = list_cacamaca[i + 3] - 4
            i += 3
        case 21:
            print(f"if reg[{list_cacamaca[i + 1]}] != {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
            if not list_verysussy[list_cacamaca[i + 1]] == list_cacamaca[i + 2]:
                i = list_cacamaca[i + 3] - 4
            i += 3
        case 22:
            print(f"if reg[{list_cacamaca[i + 1]}] > {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
            if list_verysussy[list_cacamaca[i + 1]] > list_cacamaca[i + 2]:
                i = list_cacamaca[i + 3] - 4
            i += 3
        case 23:
            print(f"if reg[{list_cacamaca[i + 1]}] < {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
            if list_verysussy[list_cacamaca[i + 1]] < list_cacamaca[i + 2]:
                i = list_cacamaca[i + 3] - 4
            i += 3
        case 24:
            print(f"if reg[{list_cacamaca[i + 1]}] >= {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
            if not list_verysussy[list_cacamaca[i + 1]] < list_cacamaca[i + 2]:
                i = list_cacamaca[i + 3] - 4
            i += 3
        case 25:
            print(f"if reg[{list_cacamaca[i + 1]}] <= {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
            if not list_verysussy[list_cacamaca[i + 1]] > list_cacamaca[i + 2]:
                i = list_cacamaca[i + 3] - 4
            i += 3
        case 26:
            print(f"reg[{list_cacamaca[i + 1]}] %= {list_cacamaca[i + 2]}")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] % list_cacamaca[i + 2]
            i += 2
        case 27:
            print(f"reg[{list_cacamaca[i + 1]}] %= reg[{list_cacamaca[i + 2]}]")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] % list_verysussy[list_cacamaca[i + 2]]
            i += 2
        case 28:
            print(f"reg[{list_cacamaca[i + 1]}] = left_circular_shift(reg[{list_cacamaca[i + 1]}], {list_cacamaca[i + 2]})")
            globals[23] = sussus(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2], 8)
            list_verysussy[list_cacamaca[i+1]] = globals[23]
            i += 2
        case 29:
            print(f"reg[{list_cacamaca[i + 1]}] = left_circular_shift(reg[{list_cacamaca[i + 1]}], reg[{list_cacamaca[i + 2]}])")
            globals[24] = sussus(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]], 8)
            list_verysussy[list_cacamaca[i+1]] = globals[24]
            i += 2
        case 30:
            print(f"reg[{list_cacamaca[i + 1]}] = right_circular_shift(reg[{list_cacamaca[i + 1]}], {list_cacamaca[i + 2]})")
            globals[25] = notsussus(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2], 8)
            list_verysussy[list_cacamaca[i+1]] = globals[25]
            i += 2
        case 31:
            print(f"reg[{list_cacamaca[i + 1]}] = right_circular_shift(reg[{list_cacamaca[i + 1]}], reg[{list_cacamaca[i + 2]}])")
            globals[26] = notsussus(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]], 8)
            list_verysussy[list_cacamaca[i+1]] = globals[26]
            i += 2
        case 32:
            print(f"mem[{list_cacamaca[i + 1]}] = {list_cacamaca[i + 2]}")
            list_cacamaca[list_cacamaca[i + 1]] = list_cacamaca[i + 2]
            i += 2
        case 33:
            print(f"mem[{list_cacamaca[i + 1]}] = reg[{list_cacamaca[i + 2]}]")
            list_cacamaca[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 2]]
            i += 2
        case 34:
            print(f"reg[{list_cacamaca[i + 1]}] = mem[{list_cacamaca[i + 2]}]")
            list_verysussy[list_cacamaca[i + 1]] = list_cacamaca[list_cacamaca[i + 2]]
            i += 2
        case 35:
            print(f"reg[{list_cacamaca[i + 1]}] = mem[reg[{list_cacamaca[i + 2]}]]")
            list_verysussy[list_cacamaca[i + 1]] = list_cacamaca[list_verysussy[list_cacamaca[i + 2]]]
            i += 2
        case 36:
            i = i
        case 37:
            print(f"reg[{list_cacamaca[i + 1]}] = reg[{list_cacamaca[i + 2]}]")
            list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 2]]
            i += 2
        case 38:
            print("Good")
            exit(0)
        case 39:
            print(f"mem[reg[{list_cacamaca[i + 1]}]] = reg[{list_cacamaca[i + 2]}]")
            list_cacamaca[list_verysussy[list_cacamaca[i + 1]]] = list_verysussy[list_cacamaca[i + 2]]
            i += 2
        case 40:
            print("Bad")
            exit(1)
        case 41:
            print(f"if reg[{list_cacamaca[i + 1]}] != reg[{list_cacamaca[i + 2]}] jump to {list_cacamaca[i + 3]}")
            if not list_verysussy[list_cacamaca[i + 1]] == list_verysussy[list_cacamaca[i + 2]]:
                i = list_cacamaca[i + 3] - 4
            i += 3
    i += 1

The only trick we use here is adding a 0 at the start of each list, as Scratch uses 1-based indexing, and Python 0-based indexing.

By running the code, after the logs, we have Bad, which is the normal behaviour in the Scratch project. Perfect! we have reimplemented the code in Python.

Let’s now try to understand it, and see how we can get Good instead of Bad. If you have not noticed yet, we are actually in front of a VM.

VM theory

During this holiday, I watched this video https://www.youtube.com/watch?v=b6udPT79itk which is about Analysis of Virtualization-based Obfuscation, and learn the basis of VM obfuscation.

I will try to explain it here, with my own words, but if you want to learn more about it, I suggest you to watch the video. Knowing about VM obfuscation here is not mandatory to solve this challenge, but it helps a lot.

The idea of VM obfuscation is to create a custom architecture, with its own opcodes, registers, memory, etc. Then, the original code is compiled to this custom architecture, and an interpreter (the VM) is created to execute the code.

The goal of this technique is to make reverse engineering harder, as the original code is not present in the binary, and the custom architecture is not known. The only way to understand what is happening is to understand the custom architecture, and the interpreter.

A VM is usually composed of:

This is what the Control Flow Graph of a VM looks like: vm_example

In terms of data structures, there are usually:

This is the global theory of VM obfuscation, now let’s apply it to our challenge.

Understanding the VM

By looking at the code, we can suggest that:

With that being said, you can now understand how we did the previous logging in the code. By running it, we can have an idea of the real instructions that are executed.

opcode: 17  at addr 1   | jump to 142
opcode: 0   at addr 142 | reg[1] = 69
opcode: 0   at addr 145 | reg[2] = 1
opcode: 37  at addr 148 | reg[3] = reg[2]
opcode: 1   at addr 151 | reg[3] += 2
opcode: 35  at addr 154 | reg[4] = mem[reg[3]]
opcode: 28  at addr 157 | reg[4] = left_circular_shift(reg[4], 3)
opcode: 37  at addr 160 | reg[5] = reg[4]
opcode: 28  at addr 163 | reg[5] = left_circular_shift(reg[5], 4)
opcode: 37  at addr 166 | reg[6] = reg[3]
opcode: 3   at addr 169 | reg[6] -= 1
opcode: 35  at addr 172 | reg[7] = mem[reg[6]]
opcode: 12  at addr 175 | reg[4] ^= reg[7]
opcode: 39  at addr 178 | mem[reg[3]] = reg[4]
opcode: 1   at addr 181 | reg[2] += 1
opcode: 25  at addr 184 | if reg[2] <= 69 jump to 148
opcode: 37  at addr 148 | reg[3] = reg[2]
opcode: 1   at addr 151 | reg[3] += 2
opcode: 35  at addr 154 | reg[4] = mem[reg[3]]
opcode: 28  at addr 157 | reg[4] = left_circular_shift(reg[4], 3)
opcode: 37  at addr 160 | reg[5] = reg[4]
opcode: 28  at addr 163 | reg[5] = left_circular_shift(reg[5], 4)
opcode: 37  at addr 166 | reg[6] = reg[3]
opcode: 3   at addr 169 | reg[6] -= 1
opcode: 35  at addr 172 | reg[7] = mem[reg[6]]
opcode: 12  at addr 175 | reg[4] ^= reg[7]
opcode: 39  at addr 178 | mem[reg[3]] = reg[4]
opcode: 1   at addr 181 | reg[2] += 1
opcode: 25  at addr 184 | if reg[2] <= 69 jump to 148
[...]
opcode: 25  at addr 184 | if reg[2] <= 69 jump to 148
opcode: 37  at addr 148 | reg[3] = reg[2]
opcode: 1   at addr 151 | reg[3] += 2
opcode: 35  at addr 154 | reg[4] = mem[reg[3]]
opcode: 28  at addr 157 | reg[4] = left_circular_shift(reg[4], 3)
opcode: 37  at addr 160 | reg[5] = reg[4]
opcode: 28  at addr 163 | reg[5] = left_circular_shift(reg[5], 4)
opcode: 37  at addr 166 | reg[6] = reg[3]
opcode: 3   at addr 169 | reg[6] -= 1
opcode: 35  at addr 172 | reg[7] = mem[reg[6]]
opcode: 12  at addr 175 | reg[4] ^= reg[7]
opcode: 39  at addr 178 | mem[reg[3]] = reg[4]
opcode: 1   at addr 181 | reg[2] += 1
opcode: 25  at addr 184 | if reg[2] <= 69 jump to 148
opcode: 0   at addr 188 | reg[2] = 1
opcode: 37  at addr 191 | reg[3] = reg[2]
opcode: 1   at addr 194 | reg[3] += 2
opcode: 35  at addr 197 | reg[4] = mem[reg[3]]
opcode: 1   at addr 200 | reg[3] += 69
opcode: 35  at addr 203 | reg[5] = mem[reg[3]]
opcode: 41  at addr 206 | if reg[4] != reg[5] jump to 141
opcode: 40  at addr 141 | Bad

I didn’t display all the logs here, as there is 908 lines, but there is a loop from addr 148 to 184, which is executed 69 times. Let’s try to write the real code of this loop after the VM deobfuscation:

def power_w_min_1(w):
    return 2**w - 1

def logical_or(a, b):
    return a | b

def logical_xor(a, b):
    return a ^ b
    
def shift_a_by_n_left(a, n):
    return a << n

def size_bits(n):
    return n.bit_length()

def shift_a_by_n_right(a, n):
    return a >> n

def logical_and(a, b):
    return a & b

def left_circular_shift(a, n, w):
    global globals
    C3 = n % w
    D6 = power_w_min_1(w)
    E7 = shift_a_by_n_left(a, C3)
    F8 = logical_and(E7, D6)
    G9 = shift_a_by_n_right(a, (w - C3))
    H0 = logical_or(F8, G9)
    return H0

reg = [0] * 8

mem = [0]
mem = mem + [17,142,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,44,30,4,30,188,142,85,54,77,46,212,151,22,149,190,68,143,14,165,95,28,189,158,100,87,252,143,117,206,133,38,101,159,4,31,140,135,36,63,124,55,68,127,133,30,101,6,181,158,100,15,196,62,69,230,165,142,29,231,252,191,180,215,76,182,197,190,5,238,40,0,1,69,0,2,1,37,3,2,1,3,2,35,4,3,28,4,3,37,5,4,28,5,4,37,6,3,3,6,1,35,7,6,12,4,7,39,3,4,1,2,1,25,2,69,148,0,2,1,37,3,2,1,3,2,35,4,3,1,3,69,35,5,3,41,4,5,141,1,2,1,25,2,69,191,38]

reg[1] = 69
reg[2] = 1
if reg[2] <= 69:
    reg[3] = reg[2]
    reg[3] += 2
    reg[4] = mem[reg[3]]
    reg[4] = left_circular_shift(reg[4], 3, 8)
    reg[5] = reg[4]
    reg[5] = left_circular_shift(reg[5], 4, 8)
    reg[6] = reg[3]
    reg[6] -= 1
    reg[7] = mem[reg[6]]
    reg[4] ^= reg[7]
    mem[reg[3]] = reg[4]
    reg[2] += 1

reg[2] = 1
reg[3] = reg[2]
reg[3] += 2
reg[4] = mem[reg[3]]
reg[3] += 69
reg[5] = mem[reg[3]]
if reg[4] != reg[5]:
    print("Bad")

We don’t know the rest of the code, because the VM exits after the first check, as we do not pass it.

Let’s try to deobfuscate this code to make it more readable:

reg = [0] * 8
mem = [142,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,44,30,4,30,188,142,85,54,77,46,212,151,22,149,190,68,143,14,165,95,28,189,158,100,87,252,143,117,206,133,38,101,159,4,31,140,135,36,63,124,55,68,127,133,30,101,6,181,158,100,15,196,62,69,230,165,142,29,231,252,191,180,215,76,182,197,190,5,238,40,0,1,69,0,2,1,37,3,2,1,3,2,35,4,3,28,4,3,37,5,4,28,5,4,37,6,3,3,6,1,35,7,6,12,4,7,39,3,4,1,2,1,25,2,69,148,0,2,1,37,3,2,1,3,2,35,4,3,1,3,69,35,5,3,41,4,5,141,1,2,1,25,2,69,191,38]


i = 1
while i < 70:
    mem[i] = left_circular_shift(mem[i], 3, 8) ^ mem[i - 1]
    i += 1
    
reg[4] = mem[1]
reg[5] = mem[70]


print(f"compare reg[4] and reg[5]: {reg[4]} and {reg[5]}")
if reg[4] != reg[5]:
    print("Bad")

We can clearly see here that the code is just decoding the first 69 bytes of mem with a simple algorithm (which also uses the previous value mem[i-1]), and then comparing the first byte with the 70th byte. If they are not equal, we have Bad.

Let’s try to compute the first character to pass the first check:

print(chr(right_circular_shift(mem[70] ^ mem[0], 3, 8)))

which gives us T, the first character of the flag format. Interesting… By trying by setting this character in the mem array, and running the code again, we can see that we pass the first check, and go to the second one. The second one compares mem[2] with mem[71], so we can compute the second character of the flag the same way as before. We can also assume that this comparison is done with all the characters, so we can automate the process to get the full flag:

globals = [0 for _ in range(28)]


def power_w_min_1(w):
    return 2**w - 1

def logical_or(a, b):
    return a | b

def logical_xor(a, b):
    return a ^ b
    
def shift_a_by_n_left(a, n):
    return a << n

def size_bits(n):
    return n.bit_length()

def shift_a_by_n_right(a, n):
    return a >> n

def logical_and(a, b):
    return a & b

def sussus(a, n, w): # left circular shift
    global globals
    C3 = n % w
    D6 = power_w_min_1(w)
    globals[7] = D6
    E7 = shift_a_by_n_left(a, C3)
    globals[8] = E7
    F8 = logical_and(E7, D6)
    globals[9] = F8
    G9 = shift_a_by_n_right(a, (w - C3))
    globals[10] = G9
    H0 = logical_or(F8, G9)
    globals[11] = H0
    return H0

def notsussus(a, n, w): # right circular shift
    global globals
    x3 = n % w
    x4 = power_w_min_1(w)
    globals[1] = x4
    x5 = shift_a_by_n_right(a, x3)
    globals[2] = x5
    x6 = logical_and(x5, x4)
    globals[3] = x6
    x7 = shift_a_by_n_left(a, (w - x3))
    globals[4] = x7
    X8 = logical_and(x4, x7)
    globals[5] = X8
    x9 = logical_or(x6, X8)
    globals[6] = x9
    return x9

known = 0
actual_flag = ""
while True:
    inp = actual_flag
    inp += (69 - len(inp)) * "A"
    inp_hex = [ord(c) for c in inp]
    list_cacamaca = [0, 17, 142] + inp_hex + [44,30,4,30,188,142,85,54,77,46,212,151,22,149,190,68,143,14,165,95,28,189,158,100,87,252,143,117,206,133,38,101,159,4,31,140,135,36,63,124,55,68,127,133,30,101,6,181,158,100,15,196,62,69,230,165,142,29,231,252,191,180,215,76,182,197,190,5,238,40,0,1,69,0,2,1,37,3,2,1,3,2,35,4,3,28,4,3,37,5,4,28,5,4,37,6,3,3,6,1,35,7,6,12,4,7,39,3,4,1,2,1,25,2,69,148,0,2,1,37,3,2,1,3,2,35,4,3,1,3,69,35,5,3,41,4,5,141,1,2,1,25,2,69,191,38]

    list_verysussy = [0]
    for _ in range(8):
        list_verysussy.append(0)
    watdat = len(list_cacamaca)
    i = 1
    test1 = 5
    test2 = 2
    globals[27] = size_bits(test1)
    globals[12] = notsussus(test1, test2, globals[27])

    finish = False
    while i < watdat and not finish:
        #print(f"i: {i}, opcode: {list_cacamaca[i]}")
        #print(f"{actual_flag} opcode: {list_cacamaca[i]:<3} at addr {i:<3}", end=" | ")
        #print(actual_flag)
        match list_cacamaca[i]:
            case 0:
                #print(f"reg[{list_cacamaca[i + 1]}] = {list_cacamaca[i + 2]}")
                list_verysussy[list_cacamaca[i + 1]] = list_cacamaca[i + 2]
                i += 2
            case 1:
                #print(f"reg[{list_cacamaca[i + 1]}] += {list_cacamaca[i + 2]}")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] + list_cacamaca[i + 2]
                i += 2
            case 2:
                #print(f"reg[{list_cacamaca[i + 1]}] += reg[{list_cacamaca[i + 2]}]")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] + list_verysussy[list_cacamaca[i + 2]]
                i += 2
            case 3:
                #print(f"reg[{list_cacamaca[i + 1]}] -= {list_cacamaca[i + 2]}")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] - list_cacamaca[i + 2]
                i += 2
            case 4:
                #print(f"reg[{list_cacamaca[i + 1]}] -= reg[{list_cacamaca[i + 2]}]")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] - list_verysussy[list_cacamaca[i + 2]]
                i += 2
            case 5:
                #print(f"reg[{list_cacamaca[i + 1]}] *= {list_cacamaca[i + 2]}")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] * list_cacamaca[i + 2]
                i += 2
            case 6:
                #print(f"reg[{list_cacamaca[i + 1]}] *= reg[{list_cacamaca[i + 2]}]")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] * list_verysussy[list_cacamaca[i + 2]]
                i += 2
            case 7:
                #print(f"reg[{list_cacamaca[i + 1]}] &= {list_cacamaca[i + 2]}")
                globals[13] = logical_and(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
                list_verysussy[list_cacamaca[i+1]] = globals[13]
                i += 2
            case 8:
                #print(f"reg[{list_cacamaca[i + 1]}] &= reg[{list_cacamaca[i + 2]}]")
                globals[14] = logical_and(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
                list_verysussy[list_cacamaca[i+1]] = globals[14]
                i += 2
            case 9:
                #print(f"reg[{list_cacamaca[i + 1]}] |= {list_cacamaca[i + 2]}")
                globals[15] = logical_or(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
                list_verysussy[list_cacamaca[i+1]] = globals[15]
                i += 2
            case 10:
                #print(f"reg[{list_cacamaca[i + 1]}] |= reg[{list_cacamaca[i + 2]}]")
                globals[16] = logical_or(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
                list_verysussy[list_cacamaca[i+1]] = globals[16]
                i += 2
            case 11:
                #print(f"reg[{list_cacamaca[i + 1]}] ^= {list_cacamaca[i + 2]}")
                globals[17] = logical_xor(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
                list_verysussy[list_cacamaca[i+1]] = globals[17]
                i += 2
            case 12:
                #print(f"reg[{list_cacamaca[i + 1]}] ^= reg[{list_cacamaca[i + 2]}]")
                globals[18] = logical_xor(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
                list_verysussy[list_cacamaca[i+1]] = globals[18]
                i += 2
            case 13:
                #print(f"reg[{list_cacamaca[i + 1]}] <<= {list_cacamaca[i + 2]}")
                globals[19] = shift_a_by_n_left(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
                list_verysussy[list_cacamaca[i+1]] = globals[19]
                i += 2
            case 14:
                #print(f"reg[{list_cacamaca[i + 1]}] <<= reg[{list_cacamaca[i + 2]}]")
                globals[20] = shift_a_by_n_left(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
                list_verysussy[list_cacamaca[i+1]] = globals[20]
                i += 2
            case 15:
                #print(f"reg[{list_cacamaca[i + 1]}] >>= {list_cacamaca[i + 2]}")
                globals[21] = shift_a_by_n_right(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2])
                list_verysussy[list_cacamaca[i+1]] = globals[21]
                i += 2
            case 16:
                #print(f"reg[{list_cacamaca[i + 1]}] >>= reg[{list_cacamaca[i + 2]}]")
                globals[22] = shift_a_by_n_right(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]])
                list_verysussy[list_cacamaca[i+1]] = globals[22]
                i += 2
            case 17:
                #print(f"jump to {list_cacamaca[i + 1]}")
                i = list_cacamaca[i + 1] - 1
            case 18:
                #print(f"reg[8] = cmp(reg[{list_cacamaca[i + 1]}], {list_cacamaca[i + 2]}), result = {list_verysussy[8]}")
                if list_verysussy[list_cacamaca[i + 1]] > list_cacamaca[i + 2]:
                    list_verysussy[8] = 1
                if list_verysussy[list_cacamaca[i + 1]] == list_cacamaca[i + 2]:
                    list_verysussy[8] = 0
                if list_verysussy[list_cacamaca[i + 1]] < list_cacamaca[i + 2]:
                    list_verysussy[8] = -1
                i += 2
            case 19:
                #print(f"reg[8] = cmp(reg[{list_cacamaca[i + 1]}], reg[{list_cacamaca[i + 2]}]), result = {list_verysussy[8]}")
                if list_verysussy[list_cacamaca[i + 1]] > list_verysussy[list_cacamaca[i + 2]]:
                    list_verysussy[8] = 1
                if list_verysussy[list_cacamaca[i + 1]] == list_verysussy[list_cacamaca[i + 2]]:
                    list_verysussy[8] = 0
                if list_verysussy[list_cacamaca[i + 1]] < list_verysussy[list_cacamaca[i + 2]]:
                    list_verysussy[8] = -1
                i += 2
            case 20:
                #print(f"if reg[{list_cacamaca[i + 1]}] == {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
                if list_verysussy[list_cacamaca[i + 1]] == list_cacamaca[i + 2]:
                    i = list_cacamaca[i + 3] - 4
                i += 3
            case 21:
                #print(f"if reg[{list_cacamaca[i + 1]}] != {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
                if not list_verysussy[list_cacamaca[i + 1]] == list_cacamaca[i + 2]:
                    i = list_cacamaca[i + 3] - 4
                i += 3
            case 22:
                #print(f"if reg[{list_cacamaca[i + 1]}] > {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
                if list_verysussy[list_cacamaca[i + 1]] > list_cacamaca[i + 2]:
                    i = list_cacamaca[i + 3] - 4
                i += 3
            case 23:
                #print(f"if reg[{list_cacamaca[i + 1]}] < {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
                if list_verysussy[list_cacamaca[i + 1]] < list_cacamaca[i + 2]:
                    i = list_cacamaca[i + 3] - 4
                i += 3
            case 24:
                #print(f"if reg[{list_cacamaca[i + 1]}] >= {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
                if not list_verysussy[list_cacamaca[i + 1]] < list_cacamaca[i + 2]:
                    i = list_cacamaca[i + 3] - 4
                i += 3
            case 25:
                #print(f"if reg[{list_cacamaca[i + 1]}] <= {list_cacamaca[i + 2]} jump to {list_cacamaca[i + 3]}")
                if not list_verysussy[list_cacamaca[i + 1]] > list_cacamaca[i + 2]:
                    i = list_cacamaca[i + 3] - 4
                i += 3
            case 26:
                #print(f"reg[{list_cacamaca[i + 1]}] %= {list_cacamaca[i + 2]}")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] % list_cacamaca[i + 2]
                i += 2
            case 27:
                #print(f"reg[{list_cacamaca[i + 1]}] %= reg[{list_cacamaca[i + 2]}]")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 1]] % list_verysussy[list_cacamaca[i + 2]]
                i += 2
            case 28:
                #print(f"reg[{list_cacamaca[i + 1]}] = left_circular_shift(reg[{list_cacamaca[i + 1]}], {list_cacamaca[i + 2]})")
                globals[23] = sussus(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2], 8)
                list_verysussy[list_cacamaca[i+1]] = globals[23]
                i += 2
            case 29:
                #print(f"reg[{list_cacamaca[i + 1]}] = left_circular_shift(reg[{list_cacamaca[i + 1]}], reg[{list_cacamaca[i + 2]}])")
                globals[24] = sussus(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]], 8)
                list_verysussy[list_cacamaca[i+1]] = globals[24]
                i += 2
            case 30:
                #print(f"reg[{list_cacamaca[i + 1]}] = right_circular_shift(reg[{list_cacamaca[i + 1]}], {list_cacamaca[i + 2]})")
                globals[25] = notsussus(list_verysussy[list_cacamaca[i + 1]], list_cacamaca[i + 2], 8)
                list_verysussy[list_cacamaca[i+1]] = globals[25]
                i += 2
            case 31:
                #print(f"reg[{list_cacamaca[i + 1]}] = right_circular_shift(reg[{list_cacamaca[i + 1]}], reg[{list_cacamaca[i + 2]}])")
                globals[26] = notsussus(list_verysussy[list_cacamaca[i + 1]], list_verysussy[list_cacamaca[i + 2]], 8)
                list_verysussy[list_cacamaca[i+1]] = globals[26]
                i += 2
            case 32:
                #print(f"mem[{list_cacamaca[i + 1]}] = {list_cacamaca[i + 2]}")
                list_cacamaca[list_cacamaca[i + 1]] = list_cacamaca[i + 2]
                i += 2
            case 33:
                #print(f"mem[{list_cacamaca[i + 1]}] = reg[{list_cacamaca[i + 2]}]")
                list_cacamaca[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 2]]
                i += 2
            case 34:
                #print(f"reg[{list_cacamaca[i + 1]}] = mem[{list_cacamaca[i + 2]}]")
                list_verysussy[list_cacamaca[i + 1]] = list_cacamaca[list_cacamaca[i + 2]]
                i += 2
            case 35:
                #print(f"reg[{list_cacamaca[i + 1]}] = mem[reg[{list_cacamaca[i + 2]}]]")
                list_verysussy[list_cacamaca[i + 1]] = list_cacamaca[list_verysussy[list_cacamaca[i + 2]]]
                i += 2
            case 36:
                i = i
            case 37:
                #print(f"reg[{list_cacamaca[i + 1]}] = reg[{list_cacamaca[i + 2]}]")
                list_verysussy[list_cacamaca[i + 1]] = list_verysussy[list_cacamaca[i + 2]]
                i += 2
            case 38:
                print("Good")
                exit(0)
                finish = True
            case 39:
                #print(f"mem[reg[{list_cacamaca[i + 1]}]] = reg[{list_cacamaca[i + 2]}]")
                list_cacamaca[list_verysussy[list_cacamaca[i + 1]]] = list_verysussy[list_cacamaca[i + 2]]
                i += 2
            case 40:
                #print("Bad")
                #exit(1)
                finish = True
            case 41:
                #print(f"if reg[{list_cacamaca[i + 1]}] != reg[{list_cacamaca[i + 2]}] jump to {list_cacamaca[i + 3]}")
                #print(f"compare {list_verysussy[list_cacamaca[i + 1]]} and {list_verysussy[list_cacamaca[i + 2]]}")
                if not list_verysussy[list_cacamaca[i + 1]] == list_verysussy[list_cacamaca[i + 2]]:
                    #print(list_cacamaca[known + 2])
                    v = notsussus(list_verysussy[list_cacamaca[i + 2]] ^ list_cacamaca[known + 2], 3, 8)
                    #print(f"{v} = {chr(v)}")
                    
                    i = list_cacamaca[i + 3] - 4
                    known += 1
                    actual_flag += chr(v)
                    print(actual_flag)
                i += 3
        i += 1

Which gives us this beautiful output:

T
TF
TFC
TFCC
TFCCT
TFCCTF
TFCCTF{
TFCCTF{l
TFCCTF{lo
TFCCTF{lol
TFCCTF{lol_
TFCCTF{lol_h
TFCCTF{lol_h0
TFCCTF{lol_h0p
TFCCTF{lol_h0pe
TFCCTF{lol_h0pe_
TFCCTF{lol_h0pe_y
TFCCTF{lol_h0pe_y0
TFCCTF{lol_h0pe_y0u
TFCCTF{lol_h0pe_y0u_
TFCCTF{lol_h0pe_y0u_h
TFCCTF{lol_h0pe_y0u_h4
TFCCTF{lol_h0pe_y0u_h4d
TFCCTF{lol_h0pe_y0u_h4d_
TFCCTF{lol_h0pe_y0u_h4d_f
TFCCTF{lol_h0pe_y0u_h4d_fu
TFCCTF{lol_h0pe_y0u_h4d_fun
TFCCTF{lol_h0pe_y0u_h4d_fun_
TFCCTF{lol_h0pe_y0u_h4d_fun_w
TFCCTF{lol_h0pe_y0u_h4d_fun_wi
TFCCTF{lol_h0pe_y0u_h4d_fun_wit
TFCCTF{lol_h0pe_y0u_h4d_fun_with
TFCCTF{lol_h0pe_y0u_h4d_fun_with_
TFCCTF{lol_h0pe_y0u_h4d_fun_with_s
TFCCTF{lol_h0pe_y0u_h4d_fun_with_sc
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scr
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scra
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scrat
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratc
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratch
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratchi
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratchin
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_s
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_so
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_sol
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solv
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_m
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_o
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_ot
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_oth
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_othe
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_c
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_ch
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_cha
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_chal
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_chals
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_chals_
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_chals_n
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_chals_no
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_chals_now
TFCCTF{lol_h0pe_y0u_h4d_fun_with_scratching_solve_my_other_chals_now}
Good

Conclusion

This challenge was very fun, as it is a Scratch-like obfuscated VM, which is not very common in CTFs.