Introduction

In the Summer of 2025 I published a crackme called HellGates.
Then I published it in November crackmes.one.

Basically I designed a custom 32-bit CPU encrypted & bit addressable (not byte addressable) in VHDL, synthesized it down to a gate-level netlist with multiple layers of obfuscation, anti-tamper, timing checks & anti-debug. It was designed for humans, but apparently worked well against LLMs too, until now.

For a year, nobody solved it.

Not the humans, they spent weeks/months on it and gave up.
Not the LLMs, Claude failed, ChatGPT failed, and DeepSeek failed after days or weeks of work guided by hints I gave people, ending with the model declaring the challenge “computationally infeasible with available resources.”

Then, in September 2026, GPT-6 solved it in under 20-30 minutes on SRE-Bench.

Thank you Zhuo Zhang for making this possible !

The short version: the crypto protecting the CPU’s state was lazily done and was very weak; there was a side-channel/differential analysis attack used by GPT-6, which made the runs replayable very easily after all obfuscation layers were removed.
It gave hints on the encryption used, decrypted registers, re-encrypted registers, ran the netlist properly and dumped all the decrypted 1GB (data.bin) memory.

The details of the challenge are here:

  1. The virtual CPU architecture.
  2. The program I used in the virtual CPU.
  3. The obfuscations on host side I used.
  4. The toolchains.

CPU architecture

General information:

The architecture contains 16 32-bit general purpose registers, and some special registers:

type special_registers is record
    overflow_flag           : boolean;
    condition_flag          : boolean;
    program_counter         : cpu_address_type;
    key_modifiers           : special_key_modifiers_type;
    index_for_key_modifier  : cpu_word_index_type;
    tea_pseudo_random_state : tea_integer_type;
end record special_registers;

type registers_record is record
    general : register_array;
    special : special_registers;
end record registers_record;

overflow_flag is when any type of integer overflow happened, or division by zero.
The operations were all signed integer operations and contained your basic ALU operations. (add, subtract, multiply …)
condition_flag is used for branching between different locations.
Example:

IsEqual R2, 0 // condition_flag=1
Branch @loc
    ... instructions when condition_flag=0 ...
loc:
... instructions when condition_flag=1 ...

Now you may ask: what are key_modifiers/index_for_key_modifier ?
I will explain the memory architecture a bit later below.

Now all opcodes:

  -- Integer operations --
  constant opcode_type_or        : opcode_type := "00001";
  constant opcode_type_and       : opcode_type := "00010";
  constant opcode_type_not       : opcode_type := "00011";
  constant opcode_type_add       : opcode_type := "00100";
  constant opcode_type_substract : opcode_type := "00101";
  constant opcode_type_division  : opcode_type := "00110";
  constant opcode_type_multiply  : opcode_type := "00111";
  constant opcode_type_sla       : opcode_type := "01000";
  constant opcode_type_sra       : opcode_type := "01001";
  constant opcode_type_sll       : opcode_type := "01010";
  constant opcode_type_srl       : opcode_type := "01011";
  constant opcode_type_rol       : opcode_type := "01100";
  constant opcode_type_ror       : opcode_type := "01101";
  -- Memory operations --
  constant opcode_type_read  : opcode_type := "01110";
  constant opcode_type_write : opcode_type := "01111";
  -- Branch operations --
  constant opcode_type_is_bigger            : opcode_type := "10000";
  constant opcode_type_is_lower             : opcode_type := "10001";
  constant opcode_type_is_equal             : opcode_type := "10010";
  constant opcode_type_had_integer_overflow : opcode_type := "10011";
  -- Jumping, branches, set --
  constant opcode_type_jump   : opcode_type := "10100";
  constant opcode_type_branch : opcode_type := "10101";
  constant opcode_type_set    : opcode_type := "10110";
  -- Expanding instructions --
  constant opcode_type_xor : opcode_type := "10111";

Okay not much obfuscation here, I could have used chained instruction encryption, shuffling the opcodes and a microcode engine, but I thought it was overkill at that time. (which it was, but now, not sure)

The memory architecture:

When you are doing a CPU that runs on encrypted memory, you need to be careful, you can’t simply just fetch bits from memory at any bit address, otherwise you’ll just decrypt/encrypt garbage when you try to read/write an integer/instruction, so your CPU becomes useless.

This is where it differs from non-encrypted memory because you can read/write directly at any bit address (in theory, most popular CPUs don’t do that of course).
It needs read/write memory in an aligned address.

A word in my context is the number of bits (and only that number) that the CPU can read/write into a specific slot index in RAM.

On my architecture the word size is 64 bits, which is, as you guessed, the block size of the encryption method I’ve used.

However this is harder to write the code into compared to plaintext memory, because you can’t simply read a word at a specific index and decode an instruction.

Except if you’ve made your instruction size, integer size, word size all the same size and ALSO make the code impossible to jump to a specific bit address, but only to an address aligned to the word size, by design (the same applies for reading/writing integer to memory).

For example 0x10000 would be fine, but what if you try to read an instruction at 0x10001 ? Your instruction is split in two parts (C++ example, most people are probably more familiar with this):

// Instruction is between 0x10001 and 0x10041
// Let's imagine the first word is at 0x10000, second at 0x10040
// Okay let's start to read until 0x10040

static constexpr uint64_t WORD_SIZE_IN_BITS = 64;

uint64_t read_bits = 0;
uint64_t wanted_address = 0x10001;
uint64_t bit_offset = wanted_address - (wanted_address % WORD_SIZE_IN_BITS);
uint64_t word_index = (wanted_address - bit_offset) / WORD_SIZE_IN_BITS; // 0x400
bit_array_t word_bits = read_word_bits(word_index);
instruction_bit_array_t instruction;

for (uint64_t i = bit_offset; i < WORD_SIZE_IN_BITS; i++) {
    if (read_bits < sizeof(instruction_t)) {
        instruction[read_bits] = word_bits[i];
        read_bits++;
    }
}

// read_bits is now 63, not 64 !
// So we miss one bit. We need to read the next word to get it

bit_offset = 0;
word_index++;
word_bits = read_word_bits(word_index);

for (uint64_t i = bit_offset; i < WORD_SIZE_IN_BITS; i++) {
    if (read_bits < sizeof(instruction_t)) {
        instruction[read_bits] = word_bits[i];
        read_bits++;
    }
}

// Now the instruction is being completely fetched ! Safe to read.

Now the VHDL code is a bit more complex than this, but you have to remember that the instructions/operations on integers are being split into word indexes.

Memory encryption:

Alright, now let’s talk about memory encryption. The algorithm I used to encrypt was TEA.
It was very easy to implement from the C reference, but generates a lot of logic gates because of the number of operations and rounds.

To be simple, there are 2 layers of encryption.
One is for encrypting the words, where each word has its own set of keys which are dynamically generated based on CPU state (this is what key modifiers are about).

Second, the word keys generated are themselves encrypted (KEK) so it couldn’t be retrieved by simply reading memory.

The KEK for each word was also dynamically generated (deterministic though), based on a nonce between word_index and a static key, so if a wrong index is picked to decrypt a specific word, it will output garbage.

This was also used to defend against side-channel attacks, so a word would surely never be encrypted the same way. Maybe overkill, but that’s what I did.

On top of that, all word indexes were permuted to look random access on memory, so word_index at 0, would be something random like 0x2281FD.
This is why there is a 1GB data.bin: the virtual CPU’s program is scattered all around the 1GB of data, mixed with entropy data!

This was a nice trick to obfuscate the program, it was not a simple “static key” to find in the netlist. Unfortunately, GPT-6 didn’t even need to see this to decrypt memory.

The key modifiers are basically just randomly encrypted generated keys. Their indexes are also permuted, hence why ‘index’ for key modifiers. They output to different memory.

The weakness (the side-channel):

Here a sample of the code how the CPU encrypted its state (cpu_integer_type is just 32-bit signed integer):

type fake_array is array(2 downto 0) of cpu_integer_type;

variable fake_values : fake_array := (others => (others => '0'));

for i in internal_registers.general'range loop

    internal_registers.general(i) := internal_registers.general(i) + fake_values(i mod fake_values'length);
    internal_registers.general(i) := internal_registers.general(i) xor (cpu_integer_type(internal_registers.special.tea_pseudo_random_state) + i + 1);

end loop;

fake_values:

for i in fake_values'range loop

    fake_values(i) := fake_values(i) + cpu_integer_type(internal_registers.general(i mod internal_registers.general'length)) + cpu_integer_type(internal_registers.special.tea_pseudo_random_state + i + 1);

end loop;

As you can see it was clearly weak crypto.

I actually left this on purpose because if I used real cryptography, the logic gates count would have increased to a larger number and I thought nobody would see the side-channel anyway and so moved on because I needed to test my design rapidly after synthesis.

I was wrong, this mistake cost me a lot: it made GPT-6 partially solve it by just calling the netlist with the proper CPU state to decrypt memory. Even though it was probably a difficult differential analysis for a human, GPT-6 did it and predicted exactly what needed to be done to discover it.

And you can do it too now !

The program

I’ve made my own assembly language in ANTLR, and a compiler for it. This is the current CTF program, written in hgasm (HellGatesAssembly):

#define DMA_ADDRESS_CHARACTER 0x80001000
#define DMA_ADDRESS_RECEIVED_CHARACTER 0x80001008
#define DMA_ADDRESS_LCD_CLEAR 0x80001FF0
#define DMA_ADDRESS_LCD 0x80002000
#define DMA_ADDRESS_MARK_FOR_DEBUGGER 0x82000014
#define DMA_ADDRESS_ANTITAMPER 0x82000127
#define DMA_ADDRESS_TIME 0x81000000
#define DMA_ANTI_TAMPER_DEBUG_BITS 0xA0000000

#define LCD_MAX_X 128
#define LCD_MAX_Y 32
#define LCD_MAX_PACK_4_CHARACTERS 1024

#define PASSWORD_LENGTH 100
#define MAX_CHARACTER_COUNT_FOR_ASK_PASSWORD 120

#define CPU_SHELLCODE_HASH 0xa7e2fb5c

boot@0x00000000:
    Jump @code_start

data:

lcd_square_init:
"--------------------------------------------------------------------------------------------------------------------------------"
"|                                   .   ..         .   =-    .::::-.    .--*****-                                              |" 
"|                                  +*   *+   ..:. .%: .@=  -=-:.       *.#..-*:. ..:.   ....                                   |" 
"|                                 -%=---#+.-+-:=- -#  :#  -%.   -++-  -==#+.#: -#=:-=  =+---                                   |" 
"|                                 **   =#.-@#-..  -%: :#: .*=...-@* -#=-=%:.#- :#=-:: ---*+                                    |" 
"|                              .  =:   --  :..-: ..::...::. --==--....::.:: .:.. ::-:...:::. ..                                |" 
"|                            :::::...::.::::....:::..:::::...:..::::::.::.::.::::.:::::::::-::::.                              |" 
"|                                             ..         .+#%@@@@%#=.         ..                                               |" 
"|                               ..::::... ..:.......    :**++#@@#+++=.     ...::--:..::::.::...                                |" 
"|                             ....::--:..-=-..:..   ... #=.x.-=-- X .-+ ...  .....:==-::-=--:::...                             |" 
"|                             ::.:::.:.:-=: .:.  .:-::..=+==#*--+*=-=-..:::.....:: :--:.===-.::......                          |" 
"|                          .:...:---:..-=- :.  ..:::.     . -=-=-- .      .:::.  :: -=-.:=---.  .:..                           |" 
"|                          ::..-==-=-..:::... :::.        :-:---::-:        .::: ....::..-::.. :--:.                           |" 
"|                         ....:.:--:. .::-:  ::-:          :.=*+=.           :::.. :-::: .::...:-::....                        |" 
"|                       .....:...:==-  .... .:::-                            -::.. .:..  ...::. .:.:::.                        |" 
"|                       .....:-::--==: .::  .:-:-----S3nd-l3tt3r-t0-h3ll----:|:::.  ... :-==-:::...  ..                        |" 
"|                     .::::...::.::::. :--: ..::-                            --::. .--: .:::..:::.......                       |" 
"|                    ..:::::..:-==-:-: .:-. .::--                            -:.:: .-:. ....::.:..:.....                       |" 
"|                    .:..:...:--:::... :--. .::--                            ::::: .-.. ::--+-..::--:..  .                     |" 
"|               ...............:::::.. :--. ::::-           |---|            --::: :=::  ---=::::::::.  ..                     |" 
"|              ..... .  .....:::====-  :--. .:::-           | X |            ----: .--: ::::::::....   . ....                  |" 
"|              ...  ......::.:--:===-: .::. ::::-           |---|            -:::: .--: ...::.....   ..::....                  |" 
"|             ........:::.::==-..::::-: ::. ...::                            -:::. :-:. .:--...::::..:..........               |" 
"|             .. .  ..::.::..:-:..::.-=-... .::::                            ::::. .:.:===::..:...:...:..::::...               |" 
"|         ........:-::.:::-:::::: .-=::=:   ..:::                            ::... ..:=--:. ..::-=-:..  .::::..:...            |" 
"|        ......:::::::.:...::====- .....::.  ...:                            :....  ::--::--:..:-: ...  .  ..:::....           |" 
"|       .. ........:.:---:-:..:=--=-: ....:..:..: ....::.::.::::..::::::.....-:.:..:=-:::---:......:...::.  ..... ...          |" 
"|     ..   ...    .:::-::::-::. .:::-:.....:::::::::-====++++**+===+++=----::::...-:..:::. .:--:.. ....... ..     ....         |" 
"|            ...... ...::...:::::::::-=====+=====++++**##**+=+****+++===++==------=----::..:::........:::.:::. ..... ..        |" 
"|          ... .:::...:::----:::::---=-==+==++-+=+**++++*******+**+=++++*+==++=--==+==+=+*++=======--:....::. .:.              |" 
"|                                                                                                                              |"
"--------------------------------------------------------------------------------------------------------------------------------"

lcd_square_init_2:
"Please wait for LCD message ...................................................................................................."
"|                                   .   ..         .   =-    .::::-.    .--*****-                                              |" 
"|                                  +*   *+   ..:. .%: .@=  -=-:.       *.#..-*:. ..:.   ....                                   |" 
"|                                 -%=---#+.-+-:=- -#  :#  -%.   -++-  -==#+.#: -#=:-=  =+---                                   |" 
"|                                 **   =#.-@#-..  -%: :#: .*=...-@* -#=-=%:.#- :#=-:: ---*+                                    |" 
"|                              .  =:   --  :..-: ..::...::. --==--....::.:: .:.. ::-:...:::. ..                                |" 
"|                            :::::...::.::::....:::..:::::...:..::::::.::.::.::::.:::::::::-::::.                              |" 
"|                                             ..         .+#%@@@@%#=.         ..                                               |" 
"|                               ..::::... ..:.......    :**++#@@#+++=.     ...::--:..::::.::...                                |" 
"|                             ....::--:..-=-..:..   ... #=.o.-=-- O .-+ ...  .....:==-::-=--:::...                             |" 
"|                             ::.:::.:.:-=: .:.  .:-::..=+==#*--+*=-=-..:::.....:: :--:.===-.::......                          |" 
"|                          .:...:---:..-=- :.  ..:::.     . -=-=-- .      .:::.  :: -=-.:=---.  .:..                           |" 
"|                          ::..-==-=-..:::... :::.        :-:---::-:        .::: ....::..-::.. :--:.                           |" 
"|                         ....:.:--:. .::-:  ::-:          :.=*+=.           :::.. :-::: .::...:-::....                        |" 
"|                       .....:...:==-  .... .:::-                            -::.. .:..  ...::. .:.:::.                        |" 
"|                       .....:-::--==: .::  .:-:-   Wow. Congratulations !   :|:::.  ... :-==-:::...  ..                       |" 
"|                     .::::...::.::::. :--: ..::-  https://www.youtube.com/  --::. .--: .:::..:::.......                       |" 
"|                    ..:::::..:-==-:-: .:-. .::--     watch?v=Un4p-6lzIpI    -:.:: .-:. ....::.:..:.....                       |" 
"|                    .:..:...:--:::... :--. .::--                            ::::: .-.. ::--+-..::--:..  .                     |" 
"|               ...............:::::.. :--. ::::- You can now love yourself. --::: :=::  ---=::::::::.  ..                     |" 
"|              ..... .  .....:::====-  :--. .:::-   I wonder how much time,  ----: .--: ::::::::....   . ....                  |" 
"|              ...  ......::.:--:===-: .::. ::::-     you wasted on this.    -:::: .--: ...::.....   ..::....                  |" 
"|             ........:::.::==-..::::-: ::. ...::    But I took pleasure,    -:::. :-:. .:--...::::..:..........               |" 
"|             .. .  ..::.::..:-:..::.-=-... .::::      from your agony.      ::::. .:.:===::..:...:...:..::::...               |" 
"|         ........:-::.:::-:::::: .-=::=:   ..:::   Thank you for staying.   ::... ..:=--:. ..::-=-:..  .::::..:...            |" 
"|        ......:::::::.:...::====- .....::.  ...:                            :....  ::--::--:..:-: ...  .  ..:::....           |" 
"|       .. ........:.:---:-:..:=--=-: ....:..:..: ....::.::.::::..::::::.....-:.:..:=-:::---:......:...::.  ..... ...          |" 
"|     ..   ...    .:::-::::-::. .:::-:.....:::::::::-====++++**+===+++=----::::...-:..:::. .:--:.. ....... ..     ....         |" 
"|            ...... ...::...:::::::::-=====+=====++++**##**+=+****+++===++==------=----::..:::........:::.:::. ..... ..        |" 
"|          ... .:::...:::----:::::---=-==+==++-+=+**++++*******+**+=++++*+==++=--==+==+=+*++=======--:....::. .:.              |" 
"|                                                                                                                              |"
"--------------------------------------------------------------------------------------------------------------------------------"

lcd_square_init_3:
"Please wait for LCD message ...................................................................................................."
"|                                   .   ..         .   =-    .::::-.    .--*****-                                              |" 
"|                                  +*   *+   ..:. .%: .@=  -=-:.       *.#..-*:. ..:.   ....                                   |" 
"|                                 -%=---#+.-+-:=- -#  :#  -%.   -++-  -==#+.#: -#=:-=  =+---                                   |" 
"|                                 **   =#.-@#-..  -%: :#: .*=...-@* -#=-=%:.#- :#=-:: ---*+                                    |" 
"|                              .  =:   --  :..-: ..::...::. --==--....::.:: .:.. ::-:...:::. ..                                |" 
"|                            :::::...::.::::....:::..:::::...:..::::::.::.::.::::.:::::::::-::::.                              |" 
"|                                             ..         .+#%@@@@%#=.         ..                                               |" 
"|                               ..::::... ..:.......    :**++#@@#+++=.     ...::--:..::::.::...                                |" 
"|                             ....::--:..-=-..:..   ... #=.x.-=-- X .-+ ...  .....:==-::-=--:::...                             |" 
"|                             ::.:::.:.:-=: .:.  .:-::..=+==#*--+*=-=-..:::.....:: :--:.===-.::......                          |" 
"|                          .:...:---:..-=- :.  ..:::.     . -=-=-- .      .:::.  :: -=-.:=---.  .:..                           |" 
"|                          ::..-==-=-..:::... :::.        :-:---::-:        .::: ....::..-::.. :--:.                           |" 
"|                         ....:.:--:. .::-:  ::-:          :.=*+=.           :::.. :-::: .::...:-::....                        |" 
"|                       .....:...:==-  .... .:::-                            -::.. .:..  ...::. .:.:::.                        |" 
"|                       .....:-::--==: .::  .:-:-      Congratulations !!!   :|:::.  ... :-==-:::...  ..                       |" 
"|                     .::::...::.::::. :--: ..::-                            --::. .--: .:::..:::.......                       |" 
"|                    ..:::::..:-==-:-: .:-. .::--         You suck.          -:.:: .-:. ....::.:..:.....                       |" 
"|                    .:..:...:--:::... :--. .::--                            ::::: .-.. ::--+-..::--:..  .                     |" 
"|               ...............:::::.. :--. ::::-     Do it the real way.    --::: :=::  ---=::::::::.  ..                     |" 
"|              ..... .  .....:::====-  :--. .:::-      Like a real man.      ----: .--: ::::::::....   . ....                  |" 
"|              ...  ......::.:--:===-: .::. ::::-  Try again bruteforcing,   -:::: .--: ...::.....   ..::....                  |" 
"|             ........:::.::==-..::::-: ::. ...::    and there will be ...   -:::. :-:. .:--...::::..:..........               |" 
"|             .. .  ..::.::..:-:..::.-=-... .::::  Unforeseen Consequences.  ::::. .:.:===::..:...:...:..::::...               |" 
"|         ........:-::.:::-:::::: .-=::=:   ..:::    YOU'VE BEEN WARNED.     ::... ..:=--:. ..::-=-:..  .::::..:...            |" 
"|        ......:::::::.:...::====- .....::.  ...:                            :....  ::--::--:..:-: ...  .  ..:::....           |" 
"|       .. ........:.:---:-:..:=--=-: ....:..:..: ....::.::.::::..::::::.....-:.:..:=-:::---:......:...::.  ..... ...          |" 
"|     ..   ...    .:::-::::-::. .:::-:.....:::::::::-====++++**+===+++=----::::...-:..:::. .:--:.. ....... ..     ....         |" 
"|            ...... ...::...:::::::::-=====+=====++++**##**+=+****+++===++==------=----::..:::........:::.:::. ..... ..        |" 
"|          ... .:::...:::----:::::---=-==+==++-+=+**++++*******+**+=++++*+==++=--==+==+=+*++=======--:....::. .:.              |" 
"|                                                                                                                              |"
"--------------------------------------------------------------------------------------------------------------------------------"

animations_right_eye:
"0 .-"
"o .-"
"= .-"
"- .-"
"  .-"
"- .-"
"= .-"
"o .-"
"O .-"

animations_left_eye:
"o.-="
"=.-="
"-.-="
" .-="
" .-="
" .-="
" .-="
" .-="
"o.-="

animation_index: 0x00000000
last_animate_time: 0x00000000
last_dma_time: 0xFFFFFFFF
had_timeouted: 0x00000000
had_debugger_on: 0x00000000
had_mismatched_hash: 0x00000000
fake_smc_index: 0x00000000
anti_tamper_triggered: 0x00000000
fake_smc_jump_back: 0x00000000
count_characters: 0x00000000

data_end: 0x00

code_start:
    Set R7, 1
    Write R7, @DMA_ADDRESS_MARK_FOR_DEBUGGER
    // Set R7, 0
    // Write R7, @DMA_ANTI_TAMPER_DEBUG_BITS
    Set R0, @DMA_ADDRESS_LCD_CLEAR
    Set R1, 1
    Write R1, R0
    Jump @init_lcd

init_lcd:
    Set R0, @DMA_ADDRESS_LCD
    Set R1, @lcd_square_init
    Set R2, 0

    loop_init_lcd:
        Add R2, 1
        Read R3, R1
        Write R3, R0
        Add R0, 32
        Add R1, 32
    loop_init_lcd_tmp_label:
        IsLower R2, @LCD_MAX_PACK_4_CHARACTERS
        Branch @loop_init_lcd
            Jump @ask_password

// Fake SMC
fake_smc:
        // Let's rewrite a word to confuse people with fake SMC with encryption
        Read R7, @fake_smc_index
        Read R6, R7
        // Insert a random value for RAM encryption
        Read R5, @DMA_ADDRESS_TIME
        Write R6, R7
        IsBigger R7, @code_end
        Branch @reset_fake_smc_index
            Add R7, 64
            Jump @write_fake_smc_index
    reset_fake_smc_index:
        Set R7, 0
    write_fake_smc_index:
        Write R7, @fake_smc_index
        Read R7, @fake_smc_jump_back
        Jump R7

check_anti_tamper:
    // Jump to fake SMC
    Set R7, @continue_debugger_check
    Write R7, @fake_smc_jump_back
    Jump @fake_smc

    continue_debugger_check:
        Read R7, @had_debugger_on
        // Read R6, @DMA_ANTI_TAMPER_DEBUG_BITS
        // SLL R7, 0
        // Or R7, R6
        // Write R7, @DMA_ANTI_TAMPER_DEBUG_BITS
        IsBigger R7, 0
        Branch @anti_tamper_check_not_passed
            // Debugger check, override old value to be sure it's not a simply nop anyway.
            Set R7, 1
            Write R7, @DMA_ADDRESS_MARK_FOR_DEBUGGER
            Read R7, @DMA_ADDRESS_MARK_FOR_DEBUGGER
            IsEqual R7, 0
            Branch @check_hash
                Set R7, 1
                Write R7, @had_debugger_on
                // Else ask a new password and continue like nothing happened
                Jump @anti_tamper_check_not_passed

    check_hash:
        Read R7, @had_mismatched_hash
        // Read R6, @DMA_ANTI_TAMPER_DEBUG_BITS
        // SLL R7, 1
        // Or R7, R6
        // Write R7, @DMA_ANTI_TAMPER_DEBUG_BITS
        IsBigger R7, 0
        Branch @anti_tamper_check_not_passed
            // Verify CPU netlist code, override hash so that it forces the user to write it again.
            Set R7, 0
            Write R7, @DMA_ADDRESS_ANTITAMPER
            Read R7, @DMA_ADDRESS_ANTITAMPER
            IsEqual R7, @CPU_SHELLCODE_HASH
            Branch @check_timeout
                Set R7, 1
                Write R7, @had_mismatched_hash
                Jump @anti_tamper_check_not_passed

    check_timeout:
        Read R7, @had_timeouted
        // Read R6, @DMA_ANTI_TAMPER_DEBUG_BITS
        // SLL R7, 2
        // Or R7, R6
        // Write R7, @DMA_ANTI_TAMPER_DEBUG_BITS
        IsBigger R7, 0
        Branch @anti_tamper_check_not_passed
            Read R7, @DMA_ADDRESS_TIME
            Read R5, @last_dma_time
            Write R7, @last_dma_time
            IsEqual R5, 0xFFFFFFFF // For the first loop, don't check it.
            Branch @anti_tamper_check_passed
                Subtract R7, R5
                IsBigger R7, 1
                Branch @check_higher_time
                    Jump @weird_constant_time
            check_higher_time:
                IsLower R7, 10000000 // If it's 10 seconds that we stopped, it's probably a debugger, don't continue ever here.
                Branch @anti_tamper_check_passed
            weird_constant_time:
                Set R7, 1
                Write R7, @had_timeouted
                Jump @anti_tamper_check_not_passed

    anti_tamper_check_not_passed:
        Set R7, 1
        Write R7, @anti_tamper_triggered
        Jump @anti_tamper_check_passed

ask_password:
    Set R4, 0 // R4 is pw index

ask_letter_and_do_animation:
    IsLower R4, 0 // Check if negative
        Branch @ask_password
    // Do animation, let's see if we can animate
    Read R6, @last_animate_time
    Read R7, @DMA_ADDRESS_TIME
    Subtract R7, R6
    IsLower R7, 400000

    Branch @wait_for_letter
        Read R6, @DMA_ADDRESS_TIME
        Write R6, @last_animate_time
        Read R7, @animation_index
        Add R7, 1
        IsBigger R7, 8
        Branch @set_zero_animation_index
            Jump @write_animation_index
    set_zero_animation_index:
        Set R7, 0
    write_animation_index:
        Write R7, @animation_index
        Set R6, @animations_right_eye
        Multiply R7, 32
        Add R6, R7
        Read R7, R6
        Set R6, 1218 // Set cursor to the right skull eye
        Multiply R6, 8
        Add R6, @DMA_ADDRESS_LCD
        Write R7, R6

        Read R7, @animation_index
        Set R6, @animations_left_eye
        Multiply R7, 32
        Add R6, R7
        Read R7, R6
        Set R6, 1211 // Set cursor to the left skull eye
        Multiply R6, 8
        Add R6, @DMA_ADDRESS_LCD
        Write R7, R6

wait_for_letter:
    // Check first for debugger etc.
    Jump @check_anti_tamper

    // Check the actual character now
    anti_tamper_check_passed:
        Read R1, @DMA_ADDRESS_RECEIVED_CHARACTER
        IsEqual R1, 1
        Branch @ask_letter_and_do_animation
            Read R0, @DMA_ADDRESS_CHARACTER
            And R0, 0x000000FF
            Set R3, R0 // Save letter
            Set R6, 0x00000024
            Set R7, 1
            Add R7, R4
            Multiply R6, R7
            And R6, 0x000000FF
            XOR R3, R6 // XOR password, at least give a chance to the challenger
            Set R1, 1
            Write R1, @DMA_ADDRESS_RECEIVED_CHARACTER
            Set R1, @DMA_ADDRESS_LCD
            Set R2, 2622 // Set cursor pos to the square X
            Multiply R2, 8
            Add R1, R2
            Add R0, 0x207C2000
            Write R0, R1 // Write the LCD
            Set R5, @password
            Set R6, R4 // Get current password character index
            Multiply R6, 8 // Get the bit position
            Add R5, R6 // Add the bit position
            Read R6, R5

            // We care only about the 8 bits character
            And R6, 0x000000FF
            Jump @check_character

check_character:
    // Bruteforcing needs a special case where I need to insult the person who does this.
    // I don't know why, it's stronger than me. (joking lmao)
    Read R7, @count_characters
    Add R7, 1
    Write R7, @count_characters
    IsBigger R7, @MAX_CHARACTER_COUNT_FOR_ASK_PASSWORD
    Branch @you_suck
        // If anti tamper is triggered, do not check password.
        Read R7, @anti_tamper_triggered
        IsBigger R7, 0
        Branch @ask_password
            // Check if character is correct
            XOR R3, R6 // If the values are the same, it will be zero
            Subtract R4, R3 // Substract zero if correct
            Add R4, 1 // Increment index
            IsLower R4, @PASSWORD_LENGTH
            Branch @ask_letter_and_do_animation
                Jump @good_password

// Send congratulations
good_password:
    Set R0, @DMA_ADDRESS_LCD_CLEAR
    Set R1, 1
    Write R1, R0

    init_lcd_2:
        Set R0, @DMA_ADDRESS_LCD
        Set R1, @lcd_square_init_2
        Set R2, 0
    loop_init_lcd_2:
        Add R2, 1
        Read R3, R1
        Write R3, R0
        Add R0, 32
        Add R1, 32
        IsLower R2, @LCD_MAX_PACK_4_CHARACTERS
        Branch @loop_init_lcd_2
            Jump @init_lcd_2

you_suck:
    Set R0, @DMA_ADDRESS_LCD_CLEAR
    Set R1, 1
    Write R1, R0

    init_lcd_3:
        Set R0, @DMA_ADDRESS_LCD
        Set R1, @lcd_square_init_3
        Set R2, 0
    loop_init_lcd_3:
        Add R2, 1
        Read R3, R1
        Write R3, R0
        Add R0, 32
        Add R1, 32
        IsLower R2, @LCD_MAX_PACK_4_CHARACTERS
        Branch @loop_init_lcd_3
            Jump @init_lcd_3

code_end:
    Jump @code_start

Here is how the snapshot encrypted RAM created it with the compiled program: RAMCreator

I decided not to give the entire code, but you have the architecture now. I’ve also removed the password in the assembly, but it should be easily retrievable in the RAMCreator, so you can solve it yourself ! :)

To summarize, it uses:

  • Fake SMC/FSMC = fake self-modifying code, fake because in fact, nothing in plaintext changes, but it forces the keys to rotate so memory appears to self-modify all the time.
  • Anti-bruteforcing (You couldn’t type more than 120 characters)
  • Anti-tamper watchdog on host (I will explain later how it was implemented host-side which was also a weakness but on purpose) on the netlist running.
  • Anti-debug watchdog on host.
  • Tries to avoid branching to avoid a side-channel attack (by checking where the CPU reads for the next instruction) and uses XOR operations instead. (urgh, I know this was bad)
  • Has an easter egg if a side-channel was used, ironically was still solved anyway because of several weaknesses related to keyboard input and weak password check (GPT-6 used these weaknesses). A keygen would have been much better in this regard.
  • An animated skull. (this was fun to write in assembly)

You can notice DMA time, this one was used to measure how long the netlist would run, so for example if you ran it under an emulator, like qemu, the password check would fail instead of making the program just exit.
It was also used to generate more entropy to generate new word keys, because the keys are dynamically generated and are dependent on CPU registers/state.

The same anti-stuffs made the password check fail instead of exiting/breaking the host program.
This is evil, I know.

Obfuscation used on host side

In short, here’s what I used on the host side:

  • Recursively encrypted (using variant of ChaCha20) nested shellcodes (called CXE, for calvin-xutaxkamay-executable, as this was also intended for a reverse-engineering hypervisor, hi Calvin and my friends if you see this! Thank you for supporting me all these years) that contains the scattered netlist inside the exception handler. I had to make my own tool to create my own shellcode generator, so that it properly self-relocates (it is position independent and shellcode can be written in C/C++, I will detail that in toolchains).

  • A recursive runtime decryption/encryption exception handler.

  • Control-flow obfuscation but exception based on signal return using specific hardcoded addresses and some classic debug instructions int 3/.byte 0xF1 that drives to anti-debug/anti-tamper or the netlist itself. The CFO itself contained parts of the netlist. The fun part is that it breaks disassemblers and misinterprets some bytes, and is not easy to trace without dynamic analysis:

    The disassembly1

    The disassembly2

Here is the complete flow of the cpp code, which will be easier than explaining with words, the code should be easy enough to read through (snippets, not full code):


constexpr auto CPUShellCodeSize       = sizeof(cpu_cxe_h_CXE_BINARY_BLOB);
constexpr auto AntiDebugShellCodeSize = sizeof(
  antidebug_cxe_h_CXE_BINARY_BLOB);

CXEHeader* CPUShellCodeCXEHeader  = nullptr;
bool InitializedShellCode         = false;
size_t LastDecryptedPageByteIndex = std::numeric_limits<size_t>::max();

// Exception handler starts here
extern "C" bool shellcode_entry(
  const inputs_central_processing_unit_t& inputs,
  outputs_central_processing_unit_t& outputs)
{
    if (not __cxe_self_relocate())
    {
        return false;
    }

    cpu(inputs, outputs);

    return true;
}

void __attribute__((noinline)) EraseInstructions(void* ptr)
{
    auto bytes = reinterpret_cast<uint8_t*>(ptr);

    for (size_t i = 0; i < HellGates::PageSize; i++)
    {
        bytes[i] = 0x90;
    }
}

inline void CPUShellCodeAntiTamper(HellGates::RAM& RAM)
{
    auto start_hash_from = reinterpret_cast<uint8_t*>(
                             CPUShellCodeCXEHeader)
                           + CPUShellCodeCXEHeader->offset_to_shellcode
                           + cpu_cxe_h_CXE_SYMBOL_OFFSETS
                             [cpu_cxe_h_CXE___code_start];
    constexpr auto hashed_size = cpu_cxe_h_CXE_SYMBOL_OFFSETS
                                   [cpu_cxe_h_CXE___code_end]
                                 - cpu_cxe_h_CXE_SYMBOL_OFFSETS
                                   [cpu_cxe_h_CXE___code_start];

    auto hash = HellGates::HashArray(start_hash_from, hashed_size);

    auto HashedCPUShellCode = static_cast<uint32_t>(hash)
                              ^ static_cast<uint32_t>(hash >> 32);

    RAM.Set(HellGates::DMA_ADDRESS_ANTITAMPER,
            std::bitset<32>(HashedCPUShellCode));
}

extern "C" void __attribute__((aligned(4096))) InitCPU(
  decltype(mprotect) linux_mprotect,
  decltype(mmap) linux_mmap,
  HellGates::RAM& RAM)
{
    auto data = reinterpret_cast<uintptr_t>(RAM.bits.get_data());
    data      = data - (data % HellGates::PageSize);

    linux_mprotect(reinterpret_cast<void*>(data),
                   RAM.bits.data_size() * __SIZEOF_LONG__,
                   PROT_READ | PROT_WRITE | PROT_EXEC);

    CPUShellCodeCXEHeader = reinterpret_cast<
      decltype(CPUShellCodeCXEHeader)>(
      data + HellGates::CPU_SHELLCODE_BYTE_ADDRESS);

    auto bytes = reinterpret_cast<uint8_t*>(CPUShellCodeCXEHeader);

    for (size_t i = 0; i < CPUShellCodeSize; i++)
    {
        bytes[i] = cpu_cxe_h_CXE_BINARY_BLOB[i];
    }

    constexpr auto EncryptEverytimeForThisSize = 0x40000;

    for (size_t i = 0; i < CPUShellCodeSize; i += HellGates::PageSize)
    {
        if (i >= EncryptEverytimeForThisSize
            and i % EncryptEverytimeForThisSize == 0)
        {
            linux_mprotect(bytes + i, HellGates::PageSize, PROT_NONE);
            continue;
        }

        HellGates::ChaCha20::DecryptPageSize(
          &bytes[i],
          i,
          std::to_array(cpu_cxe_h_CXE_KEY));
    }

    InitializedShellCode = true;

    CPUShellCodeAntiTamper(RAM);

    EraseInstructions(reinterpret_cast<void*>(InitCPU));

    __asm__(".space 4096 - ( . - InitCPU ), 0x90\n");
}

inline void RuntimeDecryption(decltype(mprotect) linux_mprotect,
                              siginfo_t* info,
                              int sig)
{
    auto bytes = reinterpret_cast<uint8_t*>(CPUShellCodeCXEHeader);

    if (LastDecryptedPageByteIndex != std::numeric_limits<size_t>::max())
    {
        auto bytes_to_encrypt = bytes + LastDecryptedPageByteIndex;
        auto bytes_to_copy    = &cpu_cxe_h_CXE_BINARY_BLOB
                               [LastDecryptedPageByteIndex];

        for (size_t i = 0; i < HellGates::PageSize; i++)
        {
            bytes_to_encrypt[i] = bytes_to_copy[i];
        }

        linux_mprotect(bytes_to_encrypt, HellGates::PageSize, PROT_NONE);

        LastDecryptedPageByteIndex = std::numeric_limits<size_t>::max();
    }

    if (sig == SIGSEGV)
    {
        auto aligned_address = reinterpret_cast<uint8_t*>(info->si_addr)
                               - (reinterpret_cast<size_t>(info->si_addr)
                                  % HellGates::PageSize);

        if (aligned_address
              < reinterpret_cast<uint8_t*>(CPUShellCodeCXEHeader)
            or aligned_address
                 >= (reinterpret_cast<uint8_t*>(CPUShellCodeCXEHeader)
                     + CPUShellCodeSize))
        {
            NanomitesErrors::Do(
              NanomitesErrors::NOT_WITHIN_SHELLCODE_SCOPE,
              reinterpret_cast<size_t>(aligned_address));
            return;
        }

        size_t page_index = aligned_address - bytes;

        LastDecryptedPageByteIndex = page_index;

        linux_mprotect(aligned_address,
                       HellGates::PageSize,
                       PROT_READ | PROT_EXEC | PROT_WRITE);

        HellGates::ChaCha20::DecryptPageSize(
          aligned_address,
          page_index,
          std::to_array(cpu_cxe_h_CXE_KEY));
    }
}

inline void AntiDebug(decltype(mmap) linux_mmap,
                      decltype(munmap) linux_munmap,
                      HellGates::RAM& RAM,
                      bool AntiTamper)
{
    auto AntiDebugCXEHeader = reinterpret_cast<CXEHeader*>(
      linux_mmap(nullptr,
                 AntiDebugShellCodeSize,
                 PROT_EXEC | PROT_READ | PROT_WRITE,
                 MAP_ANONYMOUS | MAP_PRIVATE,
                 -1,
                 0));

    auto bytes = reinterpret_cast<uint8_t*>(AntiDebugCXEHeader);

    for (size_t i = 0; i < AntiDebugShellCodeSize; i++)
    {
        bytes[i] = antidebug_cxe_h_CXE_BINARY_BLOB[i];
    }

    for (size_t i  = 0; i < AntiDebugShellCodeSize;
         i        += HellGates::PageSize)
    {
        HellGates::ChaCha20::DecryptPageSize(
          &bytes[i],
          i,
          std::to_array(antidebug_cxe_h_CXE_KEY));
    }

    auto AntiDebugShellCodeEntryFunction = reinterpret_cast<
      void (*)(HellGates::RAM&, CXEHeader*, size_t&, bool&, uint32_t&, bool)>(
      reinterpret_cast<uintptr_t>(AntiDebugCXEHeader)
      + AntiDebugCXEHeader->offset_to_shellcode
      + antidebug_cxe_h_CXE_SYMBOL_OFFSETS
        [antidebug_cxe_h_CXE_shellcode_entry]);

    static size_t CounterCPUSCHash     = 0;
    static bool DebuggerDetected       = false;
    static uint32_t HashedCPUShellCode = 0;

    AntiDebugShellCodeEntryFunction(RAM,
                                    CPUShellCodeCXEHeader,
                                    CounterCPUSCHash,
                                    DebuggerDetected,
                                    HashedCPUShellCode,
                                    AntiTamper);

    for (size_t i = 0; i < AntiDebugShellCodeSize; i++)
    {
        bytes[i] = 0x90;
    }

    linux_munmap(AntiDebugCXEHeader, AntiDebugShellCodeSize);
}

inline void CPUShellCode(const inputs_central_processing_unit_t& inputs,
                         outputs_central_processing_unit_t& outputs,
                         decltype(mprotect) linux_mprotect,
                         decltype(mmap) linux_mmap,
                         decltype(munmap) linux_munmap,
                         decltype(mremap) linux_mremap,
                         // decltype(printf) linux_printf,
                         int sig,
                         siginfo_t* info,
                         void* ucontext,
                         HellGates::RAM& RAM)
{
    if (not InitializedShellCode)
    {
        InitCPU(linux_mprotect, linux_mmap, RAM);
    }

    auto CPUShellCodeEntryFunction = reinterpret_cast<void (*)(
      const inputs_central_processing_unit_t& inputs,
      outputs_central_processing_unit_t& outputs)>(
      reinterpret_cast<uintptr_t>(CPUShellCodeCXEHeader)
      + CPUShellCodeCXEHeader->offset_to_shellcode
      + cpu_cxe_h_CXE_SYMBOL_OFFSETS[cpu_cxe_h_CXE_shellcode_entry]);

    CPUShellCodeEntryFunction(inputs, outputs);

    RuntimeDecryption(linux_mprotect, info, sig);
}

void RuntimeCPUNanomites(const inputs_central_processing_unit_t& inputs,
                         outputs_central_processing_unit_t& outputs,
                         decltype(mprotect) linux_mprotect,
                         decltype(mmap) linux_mmap,
                         decltype(munmap) linux_munmap,
                         decltype(mremap) linux_mremap,
                         int sig,
                         siginfo_t* info,
                         mcontext_t* mcontext,
                         uint8_t* HellGatesCodeStart,
                         size_t HellGatesCodeSize,
                         HellGates::RAM& RAM)
{
    auto addr = reinterpret_cast<uintptr_t>(info->si_addr);
    auto local_central_processing_unit = reinterpret_cast<bool*>(
                                           CPUShellCodeCXEHeader)
                                         + CPUShellCodeCXEHeader
                                             ->offset_to_shellcode
                                         + cpu_cxe_h_CXE_SYMBOL_OFFSETS
                                           [cpu_cxe_h_CXE_nanomites_inputs];
    auto shared_central_processing_unit = reinterpret_cast<bool*>(
                                            CPUShellCodeCXEHeader)
                                          + CPUShellCodeCXEHeader
                                              ->offset_to_shellcode
                                          + cpu_cxe_h_CXE_SYMBOL_OFFSETS
                                            [cpu_cxe_h_CXE_shared_central_processing_unit];
    bool should_increment_rip = true;
    
    // I will let you discover how many there is of those:
    switch (addr)
    {
        case 0xDEADC0DE:
        {
            local_central_processing_unit[0] = (shared_central_processing_unit
                                                  [1144]
                                                or shared_central_processing_unit
                                                  [457]);
            // ...
            *reinterpret_cast<uintptr_t*>(0xFADE) = 0xB00B;
            asm volatile(".byte 0x00");
            break;
        }

        case 0xFADE:
        {
            local_central_processing_unit[1841] = not(
              shared_central_processing_unit[1160]
              and shared_central_processing_unit[281]);
         
            asm volatile(".byte 0x00");
            break;
        }

        case 0xEFFACED:
        {
            local_central_processing_unit[1867] = not(
              shared_central_processing_unit[1078]
              and shared_central_processing_unit[583]);
            ...
            break;
        }

        case 0xB00BFACE:
        {
            shared_central_processing_unit[1072] = true;
            break;
        }

        default:
        {
            should_increment_rip = false;
            RuntimeDecryption(linux_mprotect, info, sig);
            break;
        }
    }

    if (should_increment_rip)
    {
        auto byte = reinterpret_cast<uint8_t*>(mcontext->gregs[REG_RIP]);

        while (*reinterpret_cast<uint32_t*>(byte) != 0xB00B)
        {
            byte++;
        }

        mcontext->gregs[REG_RIP] = reinterpret_cast<uintptr_t>(byte) + 5;
    }
}

extern "C" bool shellcode_entry(
  const inputs_central_processing_unit_t& inputs,
  outputs_central_processing_unit_t& outputs,
  decltype(mprotect) linux_mprotect,
  decltype(mmap) linux_mmap,
  decltype(munmap) linux_munmap,
  decltype(mremap) linux_mremap,
  // decltype(printf) linux_printf,
  int sig,
  siginfo_t* info,
  void* ucontext,
  uint8_t* HellGatesCodeStart,
  size_t HellGatesCodeSize,
  HellGates::RAM& RAM)
{
    if (not __cxe_self_relocate())
    {
        return false;
    }

    bool Int3Debugged = false;

    auto context  = reinterpret_cast<ucontext_t*>(ucontext);
    auto mcontext = &context->uc_mcontext;

    if (sig == SIGTRAP)
    {
        auto byte = *reinterpret_cast<uint8_t*>(mcontext->gregs[REG_RIP]
                                                - 1);
        if (byte == 0xF1)
        {
            CPUShellCode(inputs,
                         outputs,
                         linux_mprotect,
                         linux_mmap,
                         linux_munmap,
                         linux_mremap,
                         // linux_printf,
                         sig,
                         info,
                         ucontext,
                         RAM);
        }
        else if (byte == 0xCC)
        {
            Int3Debugged = true;
        }
        else
        {
            NanomitesErrors::Do(NanomitesErrors::UNKNOWN_TRAP);
        }
    }
    else if (sig == SIGSEGV)
    {
        RuntimeCPUNanomites(inputs,
                            outputs,
                            linux_mprotect,
                            linux_mmap,
                            linux_munmap,
                            linux_mremap,
                            sig,
                            info,
                            mcontext,
                            HellGatesCodeStart,
                            HellGatesCodeSize,
                            RAM);
    }
    else
    {
        NanomitesErrors::Do(NanomitesErrors::UNKNOWN_SIGNAL);
    }

    AntiDebug(linux_mmap, linux_munmap, RAM, Int3Debugged);

    return true;
}

inline bool IsDebuggerPresent(HellGates::RAM& RAM)
{
    constexpr auto xor_path = HellGates::X0RString("/proc/self/status");

    char buffer[1024];
    long fd;
    long bytes_read;

    auto path = xor_path.decrypt_no_compile_time();

    asm volatile("mov $2, %%rax\n"
                 "syscall\n"
                 : "=a"(fd)
                 : "D"(path.data()), "S"(0), "d"(0)
                 : "rcx", "r11", "memory");

    if (fd < 0)
    {
        return false;
    }

    asm volatile("mov $0, %%rax\n"
                 "syscall\n"
                 : "=a"(bytes_read)
                 : "D"(fd), "S"(buffer), "d"(sizeof(buffer))
                 : "rcx", "r11", "memory");

    asm volatile("mov $3, %%rax\n"
                 "syscall\n"
                 :
                 : "D"(fd)
                 : "rax", "rcx", "r11", "memory");

    if (bytes_read <= 0)
    {
        return false;
    }

    constexpr auto xor_marker    = HellGates::X0RString("TracerPid:");
    constexpr size_t marker_len  = xor_marker.SIZE - 1;
    const size_t bytes_available = static_cast<size_t>(bytes_read);

    if (bytes_available < marker_len)
    {
        return false;
    }

    auto marker = xor_marker.decrypt_no_compile_time();

    for (size_t i = 0; i <= bytes_available - marker_len; i++)
    {
        if (buffer[i] == 'T' && buffer[i + 9] == ':')
        {
            bool match = true;

            for (size_t j = 0; j < marker_len; j++)
            {
                if (buffer[i + j] != marker[j])
                {
                    match = false;
                    break;
                }
            }

            if (match)
            {
                i += marker_len;

                while (i < bytes_available
                       && (buffer[i] == ' ' || buffer[i] == '\t'))
                {
                    i++;
                }

                long pid = 0;

                while (i < bytes_available && buffer[i] >= '0'
                       && buffer[i] <= '9')
                {
                    pid = pid * 10 + (buffer[i++] - '0');
                }

                return pid != 0;
            }
        }
    }

    return false;
}

inline void CPUShellCodeAntiTamper(HellGates::RAM& RAM,
                                   CXEHeader* CPUShellCodeCXEHeader,
                                   uint32_t& HashedCPUShellCode)
{
    auto start_hash_from = reinterpret_cast<uint8_t*>(
                             CPUShellCodeCXEHeader)
                           + CPUShellCodeCXEHeader->offset_to_shellcode
                           + cpu_cxe_h_CXE_SYMBOL_OFFSETS
                             [cpu_cxe_h_CXE___code_start];
    constexpr auto hashed_size = cpu_cxe_h_CXE_SYMBOL_OFFSETS
                                   [cpu_cxe_h_CXE___code_end]
                                 - cpu_cxe_h_CXE_SYMBOL_OFFSETS
                                   [cpu_cxe_h_CXE___code_start];

    auto hash = HellGates::HashArray(start_hash_from, hashed_size);

    HashedCPUShellCode = static_cast<uint32_t>(hash)
                         ^ static_cast<uint32_t>(hash >> 32);
}

extern "C" bool shellcode_entry(HellGates::RAM& RAM,
                                CXEHeader* CPUShellCodeCXEHeader,
                                size_t& CounterCPUSCHash,
                                bool& DebuggerDetected,
                                uint32_t& HashedCPUShellCode,
                                bool AntiTamper)
{
    if (not __cxe_self_relocate())
    {
        return false;
    }

    bool debugger_present = IsDebuggerPresent(RAM);

    if (debugger_present)
    {
        DebuggerDetected = true;
    }

    RAM.Set(HellGates::DMA_ADDRESS_MARK_FOR_DEBUGGER,
            { DebuggerDetected });

    if (AntiTamper)
    {
        if (CounterCPUSCHash == 0)
        {
            CPUShellCodeAntiTamper(RAM,
                                   CPUShellCodeCXEHeader,
                                   HashedCPUShellCode);

            static HellGates::SimpleRand<size_t> simpleRand;
            CounterCPUSCHash = simpleRand.RandomInteger(64, 128);
        }
        else
        {
            CounterCPUSCHash--;
        }
    }

    RAM.Set(HellGates::DMA_ADDRESS_ANTITAMPER,
            std::bitset<32>(HashedCPUShellCode));

    return true;
}

// CPU shellcode

bool shared_central_processing_unit[1173] = {false,false,false,false,false,false,false,...};
bool nanomites_inputs[0x1000];
bool nanomites_outputs[0x1000];

void cpu(const inputs_central_processing_unit_t& inputs,
         outputs_central_processing_unit_t& outputs)
{

bool local_central_processing_unit[388716];
*reinterpret_cast<uintptr_t*>(0xDEADC0DE) = 0xB00B;
asm volatile(".byte 0x00");
// for(std::size_t i = 0; i < 1870;i++){
//     local_central_processing_unit[i] = nanomites_inputs[i];
// }
local_central_processing_unit[0] = nanomites_inputs[0];
local_central_processing_unit[1] = nanomites_inputs[1];
local_central_processing_unit[2] = nanomites_inputs[2];
local_central_processing_unit[3] = nanomites_inputs[3];
local_central_processing_unit[4] = nanomites_inputs[4];
local_central_processing_unit[5] = nanomites_inputs[5];
local_central_processing_unit[6] = nanomites_inputs[6];
.... huge code ....
*reinterpret_cast<uintptr_t*>(0xB00BFACE) = 0xB00B;
asm volatile(".byte 0x00");

outputs.index_for_special_key_modifiers_43_ = local_central_processing_unit[251358];
outputs.index_for_special_key_modifiers_44_ = local_central_processing_unit[251915];
outputs.index_for_special_key_modifiers_45_ = local_central_processing_unit[250703];
outputs.index_for_special_key_modifiers_46_ = local_central_processing_unit[252032];
outputs.index_for_special_key_modifiers_47_ = local_central_processing_unit[250379];
outputs.index_for_special_key_modifiers_48_ = local_central_processing_unit[248991];
outputs.index_for_special_key_modifiers_49_ = local_central_processing_unit[250380];
outputs.index_for_special_key_modifiers_50_ = local_central_processing_unit[251698];
outputs.index_for_special_key_modifiers_51_ = local_central_processing_unit[251463];
shared_central_processing_unit[1169] = local_central_processing_unit[226965];
shared_central_processing_unit[1170] = local_central_processing_unit[225981];
shared_central_processing_unit[1171] = local_central_processing_unit[227727];
shared_central_processing_unit[1172] = local_central_processing_unit[227726];

}

This should give you a strong view of the obfuscations used now.

Toolchains

I’ve used yosys and GHDL to generate the netlist. I’ve also made my own tool called blif2cpp to try different generation designs (including performance ones in a private repository which I keep for myself) and contributed to yosys so that DFFs can be initialized.

For CXE (shellcode generator), I’ve used also LLVM and especially ELFIO, a very cool library, which I’ve also contributed for auxiliary vectors support (which was needed for another injector I’ve made in Kokabiel). I had to make my own linker script (ld file) to discard regions I didn’t need

Then I converted the ELF into an encrypted byte-array so I could include generated code directly + symbols as enums, so strings related symbols are completely stripped. (as shown in the snippet)

The nice thing about it is that the shellcodes can be used as a library ! (as shown in previous snippets)

auto AntiDebugShellCodeEntryFunction = reinterpret_cast<
    void (*)(HellGates::RAM&, CXEHeader*, size_t&, bool&, uint32_t&, bool)>(
    reinterpret_cast<uintptr_t>(AntiDebugCXEHeader)
    + AntiDebugCXEHeader->offset_to_shellcode
    + antidebug_cxe_h_CXE_SYMBOL_OFFSETS
    [antidebug_cxe_h_CXE_shellcode_entry]);

And voilà, here is a sample of a generated shellcode.

Conclusion

The challenge had music I liked.

You should have enough to get a password now !

I want to say that this was partially solved because a real solver would have recovered all algorithms and not just using side-channel attacks. The next challenge will be designed for LLMs and humans this time, I’ve already mostly finished designing another architecture while writing this. It solves already all side-channels I talked about and even do more to avoid this.

On a note, yes a LLM solved this challenge, but honestly even if I was impressed how fast it solved it (20-30 mins), it used a side-channel.
It was not a complete reversal of the sequential netlist.
This changes things.
It’s not that I dismiss the LLM for solving it this way, the less hard path is always the best, but if there is no practical side-channels, analyzing millions of logic gates would be much harder.

On a smaller note, even if slower, I personally think that humans are way better at thinking process, this is what made our survival possible after all, we would not live without arts. Even art could be considered in a way, useless except one thing: thrive for living instead of surviving. Despite all the troubles I had with humans, I still believe in humanity.

I hope you had a fun read and that it wasn’t too difficult to follow.

If you have questions or improvements I could make on the blog post, send me mails or contact me through XMPP !