{"slug": "hellgates-custom-cpu-gate-level-challenge", "title": "HellGates, custom CPU gate-level challenge", "summary": "A custom 32-bit CPU gate-level crackme called HellGates, published by its creator on crackmes.one in November, was solved by GPT-6 in under 20-30 minutes in September 2026 on SRE-Bench, after going unsolved for a year by humans and by Claude, ChatGPT, and DeepSeek. The creator said the crypto protecting the CPU's state was weak and GPT-6 used a side-channel/differential analysis attack, giving hints on the encryption, decrypting and re-encrypting registers, running the netlist and dumping all 1GB of decrypted data.bin memory.", "body_md": "# Introduction[#](#introduction)\n\nIn the Summer of 2025 I published a crackme called HellGates.\n\nThen I published it in November [crackmes.one](https://crackmes.one/crackme/692c1d9a2d267f28f69b820c).\n\nBasically 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.\n\n**For a year, nobody solved it.**\n\nNot the humans, they spent weeks/months on it and gave up.\n\nNot 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.”\n\nThen, in September 2026, [GPT-6 solved it in under 20-30 minutes](https://x.com/i2huer/status/2099892644826505392) on [SRE-Bench](https://sre-bench.lol/).\n\nThank you [Zhuo Zhang](https://zzhang.xyz/) for making this possible !\n\nThe 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.\n\nIt gave hints on the encryption used, decrypted registers, re-encrypted registers, ran the netlist properly and dumped all the decrypted 1GB (data.bin) memory.\n\nThe details of the challenge are here:\n\n1. The [virtual CPU architecture](#cpu) .\n2. The [program](#program) I used in the virtual CPU.\n3. The [obfuscations](#obfuscation) on host side I used.\n4. The [toolchains](#toolchains) .\n\n## CPU architecture[#](#cpu)\n\n### General information:[#](#general-information)\n\nThe architecture contains 16 32-bit general purpose registers, and some special registers:\n\n```\ntype special_registers is record\n    overflow_flag           : boolean;\n    condition_flag          : boolean;\n    program_counter         : cpu_address_type;\n    key_modifiers           : special_key_modifiers_type;\n    index_for_key_modifier  : cpu_word_index_type;\n    tea_pseudo_random_state : tea_integer_type;\nend record special_registers;\n\ntype registers_record is record\n    general : register_array;\n    special : special_registers;\nend record registers_record;\n```\n\noverflow_flag is when any type of integer overflow happened, or division by zero.\n\nThe operations were all signed integer operations and contained your basic ALU operations. (add, subtract, multiply …)\n\ncondition_flag is used for branching between different locations.\n\nExample:\n\n```\nIsEqual R2, 0 // condition_flag=1\nBranch @loc\n    ... instructions when condition_flag=0 ...\nloc:\n... instructions when condition_flag=1 ...\n```\n\nNow you may ask: what are key_modifiers/index_for_key_modifier ?\n\nI will explain the memory architecture a bit later below.\n\nNow all opcodes:\n\n```\n  -- Integer operations --\n  constant opcode_type_or        : opcode_type := \"00001\";\n  constant opcode_type_and       : opcode_type := \"00010\";\n  constant opcode_type_not       : opcode_type := \"00011\";\n  constant opcode_type_add       : opcode_type := \"00100\";\n  constant opcode_type_substract : opcode_type := \"00101\";\n  constant opcode_type_division  : opcode_type := \"00110\";\n  constant opcode_type_multiply  : opcode_type := \"00111\";\n  constant opcode_type_sla       : opcode_type := \"01000\";\n  constant opcode_type_sra       : opcode_type := \"01001\";\n  constant opcode_type_sll       : opcode_type := \"01010\";\n  constant opcode_type_srl       : opcode_type := \"01011\";\n  constant opcode_type_rol       : opcode_type := \"01100\";\n  constant opcode_type_ror       : opcode_type := \"01101\";\n  -- Memory operations --\n  constant opcode_type_read  : opcode_type := \"01110\";\n  constant opcode_type_write : opcode_type := \"01111\";\n  -- Branch operations --\n  constant opcode_type_is_bigger            : opcode_type := \"10000\";\n  constant opcode_type_is_lower             : opcode_type := \"10001\";\n  constant opcode_type_is_equal             : opcode_type := \"10010\";\n  constant opcode_type_had_integer_overflow : opcode_type := \"10011\";\n  -- Jumping, branches, set --\n  constant opcode_type_jump   : opcode_type := \"10100\";\n  constant opcode_type_branch : opcode_type := \"10101\";\n  constant opcode_type_set    : opcode_type := \"10110\";\n  -- Expanding instructions --\n  constant opcode_type_xor : opcode_type := \"10111\";\n```\n\nOkay 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)\n\n### The memory architecture:[#](#the-memory-architecture)\n\nWhen 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.\n\nThis 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).\n\nIt needs read/write memory in an aligned address.\n\nA 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.\n\nOn my architecture the word size is 64 bits, which is, as you guessed, the block size of the encryption method I’ve used.\n\nHowever 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.\n\nExcept 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).\n\nFor 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):\n\n```\n// Instruction is between 0x10001 and 0x10041\n// Let's imagine the first word is at 0x10000, second at 0x10040\n// Okay let's start to read until 0x10040\n\nstatic constexpr uint64_t WORD_SIZE_IN_BITS = 64;\n\nuint64_t read_bits = 0;\nuint64_t wanted_address = 0x10001;\nuint64_t bit_offset = wanted_address - (wanted_address % WORD_SIZE_IN_BITS);\nuint64_t word_index = (wanted_address - bit_offset) / WORD_SIZE_IN_BITS; // 0x400\nbit_array_t word_bits = read_word_bits(word_index);\ninstruction_bit_array_t instruction;\n\nfor (uint64_t i = bit_offset; i < WORD_SIZE_IN_BITS; i++) {\n    if (read_bits < sizeof(instruction_t)) {\n        instruction[read_bits] = word_bits[i];\n        read_bits++;\n    }\n}\n\n// read_bits is now 63, not 64 !\n// So we miss one bit. We need to read the next word to get it\n\nbit_offset = 0;\nword_index++;\nword_bits = read_word_bits(word_index);\n\nfor (uint64_t i = bit_offset; i < WORD_SIZE_IN_BITS; i++) {\n    if (read_bits < sizeof(instruction_t)) {\n        instruction[read_bits] = word_bits[i];\n        read_bits++;\n    }\n}\n\n// Now the instruction is being completely fetched ! Safe to read.\n```\n\nNow 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.\n\n#### Memory encryption:[#](#memory-encryption)\n\nAlright, now let’s talk about memory encryption.\nThe algorithm I used to encrypt was TEA.\n\nIt was very easy to implement from the C reference, but generates a lot of logic gates because of the number of operations and rounds.\n\nTo be simple, there are 2 layers of encryption.\n\nOne 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).\n\nSecond, the word keys generated are themselves encrypted (KEK) so it couldn’t be retrieved by simply reading memory.\n\nThe 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.\n\nThis 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.\n\nOn 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.\n\nThis 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!\n\nThis 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.\n\nThe 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.\n\n### The weakness (the side-channel):[#](#the-weakness-the-side-channel)\n\nHere a sample of the code how the CPU encrypted its state (cpu_integer_type is just 32-bit signed integer):\n\n```\ntype fake_array is array(2 downto 0) of cpu_integer_type;\n\nvariable fake_values : fake_array := (others => (others => '0'));\n\nfor i in internal_registers.general'range loop\n\n    internal_registers.general(i) := internal_registers.general(i) + fake_values(i mod fake_values'length);\n    internal_registers.general(i) := internal_registers.general(i) xor (cpu_integer_type(internal_registers.special.tea_pseudo_random_state) + i + 1);\n\nend loop;\n```\n\nfake_values:\n\n```\nfor i in fake_values'range loop\n\n    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);\n\nend loop;\n```\n\nAs you can see it was clearly weak crypto.\n\nI 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.\n\n**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.\n\nAnd you can do it too now !\n\n## The program[#](#program)\n\nI’ve made my own assembly language in ANTLR, and a compiler for it. This is the current CTF program, written in hgasm (HellGatesAssembly):\n\n```\n#define DMA_ADDRESS_CHARACTER 0x80001000\n#define DMA_ADDRESS_RECEIVED_CHARACTER 0x80001008\n#define DMA_ADDRESS_LCD_CLEAR 0x80001FF0\n#define DMA_ADDRESS_LCD 0x80002000\n#define DMA_ADDRESS_MARK_FOR_DEBUGGER 0x82000014\n#define DMA_ADDRESS_ANTITAMPER 0x82000127\n#define DMA_ADDRESS_TIME 0x81000000\n#define DMA_ANTI_TAMPER_DEBUG_BITS 0xA0000000\n\n#define LCD_MAX_X 128\n#define LCD_MAX_Y 32\n#define LCD_MAX_PACK_4_CHARACTERS 1024\n\n#define PASSWORD_LENGTH 100\n#define MAX_CHARACTER_COUNT_FOR_ASK_PASSWORD 120\n\n#define CPU_SHELLCODE_HASH 0xa7e2fb5c\n\nboot@0x00000000:\n    Jump @code_start\n\ndata@§:\n\nlcd_square_init:\n\"--------------------------------------------------------------------------------------------------------------------------------\"\n\"|                                   .   ..         .   =-    .::::-.    .--*****-                                              |\" \n\"|                                  +*   *+   ..:. .%: .@=  -=-:.       *.#..-*:. ..:.   ....                                   |\" \n\"|                                 -%=---#+.-+-:=- -#  :#  -%.   -++-  -==#+.#: -#=:-=  =+---                                   |\" \n\"|                                 **   =#.-@#-..  -%: :#: .*=...-@* -#=-=%:.#- :#=-:: ---*+                                    |\" \n\"|                              .  =:   --  :..-: ..::...::. --==--....::.:: .:.. ::-:...:::. ..                                |\" \n\"|                            :::::...::.::::....:::..:::::...:..::::::.::.::.::::.:::::::::-::::.                              |\" \n\"|                                             ..         .+#%@@@@%#=.         ..                                               |\" \n\"|                               ..::::... ..:.......    :**++#@@#+++=.     ...::--:..::::.::...                                |\" \n\"|                             ....::--:..-=-..:..   ... #=.x.-=-- X .-+ ...  .....:==-::-=--:::...                             |\" \n\"|                             ::.:::.:.:-=: .:.  .:-::..=+==#*--+*=-=-..:::.....:: :--:.===-.::......                          |\" \n\"|                          .:...:---:..-=- :.  ..:::.     . -=-=-- .      .:::.  :: -=-.:=---.  .:..                           |\" \n\"|                          ::..-==-=-..:::... :::.        :-:---::-:        .::: ....::..-::.. :--:.                           |\" \n\"|                         ....:.:--:. .::-:  ::-:          :.=*+=.           :::.. :-::: .::...:-::....                        |\" \n\"|                       .....:...:==-  .... .:::-                            -::.. .:..  ...::. .:.:::.                        |\" \n\"|                       .....:-::--==: .::  .:-:-----S3nd-l3tt3r-t0-h3ll----:|:::.  ... :-==-:::...  ..                        |\" \n\"|                     .::::...::.::::. :--: ..::-                            --::. .--: .:::..:::.......                       |\" \n\"|                    ..:::::..:-==-:-: .:-. .::--                            -:.:: .-:. ....::.:..:.....                       |\" \n\"|                    .:..:...:--:::... :--. .::--                            ::::: .-.. ::--+-..::--:..  .                     |\" \n\"|               ...............:::::.. :--. ::::-           |---|            --::: :=::  ---=::::::::.  ..                     |\" \n\"|              ..... .  .....:::====-  :--. .:::-           | X |            ----: .--: ::::::::....   . ....                  |\" \n\"|              ...  ......::.:--:===-: .::. ::::-           |---|            -:::: .--: ...::.....   ..::....                  |\" \n\"|             ........:::.::==-..::::-: ::. ...::                            -:::. :-:. .:--...::::..:..........               |\" \n\"|             .. .  ..::.::..:-:..::.-=-... .::::                            ::::. .:.:===::..:...:...:..::::...               |\" \n\"|         ........:-::.:::-:::::: .-=::=:   ..:::                            ::... ..:=--:. ..::-=-:..  .::::..:...            |\" \n\"|        ......:::::::.:...::====- .....::.  ...:                            :....  ::--::--:..:-: ...  .  ..:::....           |\" \n\"|       .. ........:.:---:-:..:=--=-: ....:..:..: ....::.::.::::..::::::.....-:.:..:=-:::---:......:...::.  ..... ...          |\" \n\"|     ..   ...    .:::-::::-::. .:::-:.....:::::::::-====++++**+===+++=----::::...-:..:::. .:--:.. ....... ..     ....         |\" \n\"|            ...... ...::...:::::::::-=====+=====++++**##**+=+****+++===++==------=----::..:::........:::.:::. ..... ..        |\" \n\"|          ... .:::...:::----:::::---=-==+==++-+=+**++++*******+**+=++++*+==++=--==+==+=+*++=======--:....::. .:.              |\" \n\"|                                                                                                                              |\"\n\"--------------------------------------------------------------------------------------------------------------------------------\"\n\nlcd_square_init_2:\n\"Please wait for LCD message ....................................................................................................\"\n\"|                                   .   ..         .   =-    .::::-.    .--*****-                                              |\" \n\"|                                  +*   *+   ..:. .%: .@=  -=-:.       *.#..-*:. ..:.   ....                                   |\" \n\"|                                 -%=---#+.-+-:=- -#  :#  -%.   -++-  -==#+.#: -#=:-=  =+---                                   |\" \n\"|                                 **   =#.-@#-..  -%: :#: .*=...-@* -#=-=%:.#- :#=-:: ---*+                                    |\" \n\"|                              .  =:   --  :..-: ..::...::. --==--....::.:: .:.. ::-:...:::. ..                                |\" \n\"|                            :::::...::.::::....:::..:::::...:..::::::.::.::.::::.:::::::::-::::.                              |\" \n\"|                                             ..         .+#%@@@@%#=.         ..                                               |\" \n\"|                               ..::::... ..:.......    :**++#@@#+++=.     ...::--:..::::.::...                                |\" \n\"|                             ....::--:..-=-..:..   ... #=.o.-=-- O .-+ ...  .....:==-::-=--:::...                             |\" \n\"|                             ::.:::.:.:-=: .:.  .:-::..=+==#*--+*=-=-..:::.....:: :--:.===-.::......                          |\" \n\"|                          .:...:---:..-=- :.  ..:::.     . -=-=-- .      .:::.  :: -=-.:=---.  .:..                           |\" \n\"|                          ::..-==-=-..:::... :::.        :-:---::-:        .::: ....::..-::.. :--:.                           |\" \n\"|                         ....:.:--:. .::-:  ::-:          :.=*+=.           :::.. :-::: .::...:-::....                        |\" \n\"|                       .....:...:==-  .... .:::-                            -::.. .:..  ...::. .:.:::.                        |\" \n\"|                       .....:-::--==: .::  .:-:-   Wow. Congratulations !   :|:::.  ... :-==-:::...  ..                       |\" \n\"|                     .::::...::.::::. :--: ..::-  https://www.youtube.com/  --::. .--: .:::..:::.......                       |\" \n\"|                    ..:::::..:-==-:-: .:-. .::--     watch?v=Un4p-6lzIpI    -:.:: .-:. ....::.:..:.....                       |\" \n\"|                    .:..:...:--:::... :--. .::--                            ::::: .-.. ::--+-..::--:..  .                     |\" \n\"|               ...............:::::.. :--. ::::- You can now love yourself. --::: :=::  ---=::::::::.  ..                     |\" \n\"|              ..... .  .....:::====-  :--. .:::-   I wonder how much time,  ----: .--: ::::::::....   . ....                  |\" \n\"|              ...  ......::.:--:===-: .::. ::::-     you wasted on this.    -:::: .--: ...::.....   ..::....                  |\" \n\"|             ........:::.::==-..::::-: ::. ...::    But I took pleasure,    -:::. :-:. .:--...::::..:..........               |\" \n\"|             .. .  ..::.::..:-:..::.-=-... .::::      from your agony.      ::::. .:.:===::..:...:...:..::::...               |\" \n\"|         ........:-::.:::-:::::: .-=::=:   ..:::   Thank you for staying.   ::... ..:=--:. ..::-=-:..  .::::..:...            |\" \n\"|        ......:::::::.:...::====- .....::.  ...:                            :....  ::--::--:..:-: ...  .  ..:::....           |\" \n\"|       .. ........:.:---:-:..:=--=-: ....:..:..: ....::.::.::::..::::::.....-:.:..:=-:::---:......:...::.  ..... ...          |\" \n\"|     ..   ...    .:::-::::-::. .:::-:.....:::::::::-====++++**+===+++=----::::...-:..:::. .:--:.. ....... ..     ....         |\" \n\"|            ...... ...::...:::::::::-=====+=====++++**##**+=+****+++===++==------=----::..:::........:::.:::. ..... ..        |\" \n\"|          ... .:::...:::----:::::---=-==+==++-+=+**++++*******+**+=++++*+==++=--==+==+=+*++=======--:....::. .:.              |\" \n\"|                                                                                                                              |\"\n\"--------------------------------------------------------------------------------------------------------------------------------\"\n\nlcd_square_init_3:\n\"Please wait for LCD message ....................................................................................................\"\n\"|                                   .   ..         .   =-    .::::-.    .--*****-                                              |\" \n\"|                                  +*   *+   ..:. .%: .@=  -=-:.       *.#..-*:. ..:.   ....                                   |\" \n\"|                                 -%=---#+.-+-:=- -#  :#  -%.   -++-  -==#+.#: -#=:-=  =+---                                   |\" \n\"|                                 **   =#.-@#-..  -%: :#: .*=...-@* -#=-=%:.#- :#=-:: ---*+                                    |\" \n\"|                              .  =:   --  :..-: ..::...::. --==--....::.:: .:.. ::-:...:::. ..                                |\" \n\"|                            :::::...::.::::....:::..:::::...:..::::::.::.::.::::.:::::::::-::::.                              |\" \n\"|                                             ..         .+#%@@@@%#=.         ..                                               |\" \n\"|                               ..::::... ..:.......    :**++#@@#+++=.     ...::--:..::::.::...                                |\" \n\"|                             ....::--:..-=-..:..   ... #=.x.-=-- X .-+ ...  .....:==-::-=--:::...                             |\" \n\"|                             ::.:::.:.:-=: .:.  .:-::..=+==#*--+*=-=-..:::.....:: :--:.===-.::......                          |\" \n\"|                          .:...:---:..-=- :.  ..:::.     . -=-=-- .      .:::.  :: -=-.:=---.  .:..                           |\" \n\"|                          ::..-==-=-..:::... :::.        :-:---::-:        .::: ....::..-::.. :--:.                           |\" \n\"|                         ....:.:--:. .::-:  ::-:          :.=*+=.           :::.. :-::: .::...:-::....                        |\" \n\"|                       .....:...:==-  .... .:::-                            -::.. .:..  ...::. .:.:::.                        |\" \n\"|                       .....:-::--==: .::  .:-:-      Congratulations !!!   :|:::.  ... :-==-:::...  ..                       |\" \n\"|                     .::::...::.::::. :--: ..::-                            --::. .--: .:::..:::.......                       |\" \n\"|                    ..:::::..:-==-:-: .:-. .::--         You suck.          -:.:: .-:. ....::.:..:.....                       |\" \n\"|                    .:..:...:--:::... :--. .::--                            ::::: .-.. ::--+-..::--:..  .                     |\" \n\"|               ...............:::::.. :--. ::::-     Do it the real way.    --::: :=::  ---=::::::::.  ..                     |\" \n\"|              ..... .  .....:::====-  :--. .:::-      Like a real man.      ----: .--: ::::::::....   . ....                  |\" \n\"|              ...  ......::.:--:===-: .::. ::::-  Try again bruteforcing,   -:::: .--: ...::.....   ..::....                  |\" \n\"|             ........:::.::==-..::::-: ::. ...::    and there will be ...   -:::. :-:. .:--...::::..:..........               |\" \n\"|             .. .  ..::.::..:-:..::.-=-... .::::  Unforeseen Consequences.  ::::. .:.:===::..:...:...:..::::...               |\" \n\"|         ........:-::.:::-:::::: .-=::=:   ..:::    YOU'VE BEEN WARNED.     ::... ..:=--:. ..::-=-:..  .::::..:...            |\" \n\"|        ......:::::::.:...::====- .....::.  ...:                            :....  ::--::--:..:-: ...  .  ..:::....           |\" \n\"|       .. ........:.:---:-:..:=--=-: ....:..:..: ....::.::.::::..::::::.....-:.:..:=-:::---:......:...::.  ..... ...          |\" \n\"|     ..   ...    .:::-::::-::. .:::-:.....:::::::::-====++++**+===+++=----::::...-:..:::. .:--:.. ....... ..     ....         |\" \n\"|            ...... ...::...:::::::::-=====+=====++++**##**+=+****+++===++==------=----::..:::........:::.:::. ..... ..        |\" \n\"|          ... .:::...:::----:::::---=-==+==++-+=+**++++*******+**+=++++*+==++=--==+==+=+*++=======--:....::. .:.              |\" \n\"|                                                                                                                              |\"\n\"--------------------------------------------------------------------------------------------------------------------------------\"\n\nanimations_right_eye:\n\"0 .-\"\n\"o .-\"\n\"= .-\"\n\"- .-\"\n\"  .-\"\n\"- .-\"\n\"= .-\"\n\"o .-\"\n\"O .-\"\n\nanimations_left_eye:\n\"o.-=\"\n\"=.-=\"\n\"-.-=\"\n\" .-=\"\n\" .-=\"\n\" .-=\"\n\" .-=\"\n\" .-=\"\n\"o.-=\"\n\nanimation_index: 0x00000000\nlast_animate_time: 0x00000000\nlast_dma_time: 0xFFFFFFFF\nhad_timeouted: 0x00000000\nhad_debugger_on: 0x00000000\nhad_mismatched_hash: 0x00000000\nfake_smc_index: 0x00000000\nanti_tamper_triggered: 0x00000000\nfake_smc_jump_back: 0x00000000\ncount_characters: 0x00000000\n\ndata_end: 0x00\n\ncode_start@§:\n    Set R7, 1\n    Write R7, @DMA_ADDRESS_MARK_FOR_DEBUGGER\n    // Set R7, 0\n    // Write R7, @DMA_ANTI_TAMPER_DEBUG_BITS\n    Set R0, @DMA_ADDRESS_LCD_CLEAR\n    Set R1, 1\n    Write R1, R0\n    Jump @init_lcd\n\ninit_lcd:\n    Set R0, @DMA_ADDRESS_LCD\n    Set R1, @lcd_square_init\n    Set R2, 0\n\n    loop_init_lcd:\n        Add R2, 1\n        Read R3, R1\n        Write R3, R0\n        Add R0, 32\n        Add R1, 32\n    loop_init_lcd_tmp_label:\n        IsLower R2, @LCD_MAX_PACK_4_CHARACTERS\n        Branch @loop_init_lcd\n            Jump @ask_password\n\n// Fake SMC\nfake_smc:\n        // Let's rewrite a word to confuse people with fake SMC with encryption\n        Read R7, @fake_smc_index\n        Read R6, R7\n        // Insert a random value for RAM encryption\n        Read R5, @DMA_ADDRESS_TIME\n        Write R6, R7\n        IsBigger R7, @code_end\n        Branch @reset_fake_smc_index\n            Add R7, 64\n            Jump @write_fake_smc_index\n    reset_fake_smc_index:\n        Set R7, 0\n    write_fake_smc_index:\n        Write R7, @fake_smc_index\n        Read R7, @fake_smc_jump_back\n        Jump R7\n\ncheck_anti_tamper:\n    // Jump to fake SMC\n    Set R7, @continue_debugger_check\n    Write R7, @fake_smc_jump_back\n    Jump @fake_smc\n\n    continue_debugger_check:\n        Read R7, @had_debugger_on\n        // Read R6, @DMA_ANTI_TAMPER_DEBUG_BITS\n        // SLL R7, 0\n        // Or R7, R6\n        // Write R7, @DMA_ANTI_TAMPER_DEBUG_BITS\n        IsBigger R7, 0\n        Branch @anti_tamper_check_not_passed\n            // Debugger check, override old value to be sure it's not a simply nop anyway.\n            Set R7, 1\n            Write R7, @DMA_ADDRESS_MARK_FOR_DEBUGGER\n            Read R7, @DMA_ADDRESS_MARK_FOR_DEBUGGER\n            IsEqual R7, 0\n            Branch @check_hash\n                Set R7, 1\n                Write R7, @had_debugger_on\n                // Else ask a new password and continue like nothing happened\n                Jump @anti_tamper_check_not_passed\n\n    check_hash:\n        Read R7, @had_mismatched_hash\n        // Read R6, @DMA_ANTI_TAMPER_DEBUG_BITS\n        // SLL R7, 1\n        // Or R7, R6\n        // Write R7, @DMA_ANTI_TAMPER_DEBUG_BITS\n        IsBigger R7, 0\n        Branch @anti_tamper_check_not_passed\n            // Verify CPU netlist code, override hash so that it forces the user to write it again.\n            Set R7, 0\n            Write R7, @DMA_ADDRESS_ANTITAMPER\n            Read R7, @DMA_ADDRESS_ANTITAMPER\n            IsEqual R7, @CPU_SHELLCODE_HASH\n            Branch @check_timeout\n                Set R7, 1\n                Write R7, @had_mismatched_hash\n                Jump @anti_tamper_check_not_passed\n\n    check_timeout:\n        Read R7, @had_timeouted\n        // Read R6, @DMA_ANTI_TAMPER_DEBUG_BITS\n        // SLL R7, 2\n        // Or R7, R6\n        // Write R7, @DMA_ANTI_TAMPER_DEBUG_BITS\n        IsBigger R7, 0\n        Branch @anti_tamper_check_not_passed\n            Read R7, @DMA_ADDRESS_TIME\n            Read R5, @last_dma_time\n            Write R7, @last_dma_time\n            IsEqual R5, 0xFFFFFFFF // For the first loop, don't check it.\n            Branch @anti_tamper_check_passed\n                Subtract R7, R5\n                IsBigger R7, 1\n                Branch @check_higher_time\n                    Jump @weird_constant_time\n            check_higher_time:\n                IsLower R7, 10000000 // If it's 10 seconds that we stopped, it's probably a debugger, don't continue ever here.\n                Branch @anti_tamper_check_passed\n            weird_constant_time:\n                Set R7, 1\n                Write R7, @had_timeouted\n                Jump @anti_tamper_check_not_passed\n\n    anti_tamper_check_not_passed:\n        Set R7, 1\n        Write R7, @anti_tamper_triggered\n        Jump @anti_tamper_check_passed\n\nask_password:\n    Set R4, 0 // R4 is pw index\n\nask_letter_and_do_animation:\n    IsLower R4, 0 // Check if negative\n        Branch @ask_password\n    // Do animation, let's see if we can animate\n    Read R6, @last_animate_time\n    Read R7, @DMA_ADDRESS_TIME\n    Subtract R7, R6\n    IsLower R7, 400000\n\n    Branch @wait_for_letter\n        Read R6, @DMA_ADDRESS_TIME\n        Write R6, @last_animate_time\n        Read R7, @animation_index\n        Add R7, 1\n        IsBigger R7, 8\n        Branch @set_zero_animation_index\n            Jump @write_animation_index\n    set_zero_animation_index:\n        Set R7, 0\n    write_animation_index:\n        Write R7, @animation_index\n        Set R6, @animations_right_eye\n        Multiply R7, 32\n        Add R6, R7\n        Read R7, R6\n        Set R6, 1218 // Set cursor to the right skull eye\n        Multiply R6, 8\n        Add R6, @DMA_ADDRESS_LCD\n        Write R7, R6\n\n        Read R7, @animation_index\n        Set R6, @animations_left_eye\n        Multiply R7, 32\n        Add R6, R7\n        Read R7, R6\n        Set R6, 1211 // Set cursor to the left skull eye\n        Multiply R6, 8\n        Add R6, @DMA_ADDRESS_LCD\n        Write R7, R6\n\nwait_for_letter:\n    // Check first for debugger etc.\n    Jump @check_anti_tamper\n\n    // Check the actual character now\n    anti_tamper_check_passed:\n        Read R1, @DMA_ADDRESS_RECEIVED_CHARACTER\n        IsEqual R1, 1\n        Branch @ask_letter_and_do_animation\n            Read R0, @DMA_ADDRESS_CHARACTER\n            And R0, 0x000000FF\n            Set R3, R0 // Save letter\n            Set R6, 0x00000024\n            Set R7, 1\n            Add R7, R4\n            Multiply R6, R7\n            And R6, 0x000000FF\n            XOR R3, R6 // XOR password, at least give a chance to the challenger\n            Set R1, 1\n            Write R1, @DMA_ADDRESS_RECEIVED_CHARACTER\n            Set R1, @DMA_ADDRESS_LCD\n            Set R2, 2622 // Set cursor pos to the square X\n            Multiply R2, 8\n            Add R1, R2\n            Add R0, 0x207C2000\n            Write R0, R1 // Write the LCD\n            Set R5, @password\n            Set R6, R4 // Get current password character index\n            Multiply R6, 8 // Get the bit position\n            Add R5, R6 // Add the bit position\n            Read R6, R5\n\n            // We care only about the 8 bits character\n            And R6, 0x000000FF\n            Jump @check_character\n\ncheck_character:\n    // Bruteforcing needs a special case where I need to insult the person who does this.\n    // I don't know why, it's stronger than me. (joking lmao)\n    Read R7, @count_characters\n    Add R7, 1\n    Write R7, @count_characters\n    IsBigger R7, @MAX_CHARACTER_COUNT_FOR_ASK_PASSWORD\n    Branch @you_suck\n        // If anti tamper is triggered, do not check password.\n        Read R7, @anti_tamper_triggered\n        IsBigger R7, 0\n        Branch @ask_password\n            // Check if character is correct\n            XOR R3, R6 // If the values are the same, it will be zero\n            Subtract R4, R3 // Substract zero if correct\n            Add R4, 1 // Increment index\n            IsLower R4, @PASSWORD_LENGTH\n            Branch @ask_letter_and_do_animation\n                Jump @good_password\n\n// Send congratulations\ngood_password:\n    Set R0, @DMA_ADDRESS_LCD_CLEAR\n    Set R1, 1\n    Write R1, R0\n\n    init_lcd_2:\n        Set R0, @DMA_ADDRESS_LCD\n        Set R1, @lcd_square_init_2\n        Set R2, 0\n    loop_init_lcd_2:\n        Add R2, 1\n        Read R3, R1\n        Write R3, R0\n        Add R0, 32\n        Add R1, 32\n        IsLower R2, @LCD_MAX_PACK_4_CHARACTERS\n        Branch @loop_init_lcd_2\n            Jump @init_lcd_2\n\nyou_suck:\n    Set R0, @DMA_ADDRESS_LCD_CLEAR\n    Set R1, 1\n    Write R1, R0\n\n    init_lcd_3:\n        Set R0, @DMA_ADDRESS_LCD\n        Set R1, @lcd_square_init_3\n        Set R2, 0\n    loop_init_lcd_3:\n        Add R2, 1\n        Read R3, R1\n        Write R3, R0\n        Add R0, 32\n        Add R1, 32\n        IsLower R2, @LCD_MAX_PACK_4_CHARACTERS\n        Branch @loop_init_lcd_3\n            Jump @init_lcd_3\n\ncode_end:\n    Jump @code_start\n```\n\nHere is how the snapshot encrypted RAM created it with the compiled program: [RAMCreator](ramcreator.cpp)\n\nI 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 ! :)\n\nTo summarize, it uses:\n\n- 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.\n- Anti-bruteforcing (You couldn’t type more than 120 characters)\n- 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.\n- Anti-debug watchdog on host.\n- 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)\n- 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.\n- An animated skull. (this was fun to write in assembly)\n\nYou 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.\n\nIt 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.\n\nThe same anti-stuffs made the password check fail instead of exiting/breaking the host program.\n\nThis is evil, I know.\n\n## Obfuscation used on host side[#](#obfuscation)\n\nIn short, here’s what I used on the host side:\n\n- 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](#toolchains) ).\n- A recursive runtime decryption/encryption exception handler.\n- 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.\nThe fun part is that it breaks disassemblers and misinterprets some bytes, and is not easy to trace without dynamic analysis:\n\nHere 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):\n\n```\nconstexpr auto CPUShellCodeSize       = sizeof(cpu_cxe_h_CXE_BINARY_BLOB);\nconstexpr auto AntiDebugShellCodeSize = sizeof(\n  antidebug_cxe_h_CXE_BINARY_BLOB);\n\nCXEHeader* CPUShellCodeCXEHeader  = nullptr;\nbool InitializedShellCode         = false;\nsize_t LastDecryptedPageByteIndex = std::numeric_limits<size_t>::max();\n\n// Exception handler starts here\nextern \"C\" bool shellcode_entry(\n  const inputs_central_processing_unit_t& inputs,\n  outputs_central_processing_unit_t& outputs)\n{\n    if (not __cxe_self_relocate())\n    {\n        return false;\n    }\n\n    cpu(inputs, outputs);\n\n    return true;\n}\n\nvoid __attribute__((noinline)) EraseInstructions(void* ptr)\n{\n    auto bytes = reinterpret_cast<uint8_t*>(ptr);\n\n    for (size_t i = 0; i < HellGates::PageSize; i++)\n    {\n        bytes[i] = 0x90;\n    }\n}\n\ninline void CPUShellCodeAntiTamper(HellGates::RAM& RAM)\n{\n    auto start_hash_from = reinterpret_cast<uint8_t*>(\n                             CPUShellCodeCXEHeader)\n                           + CPUShellCodeCXEHeader->offset_to_shellcode\n                           + cpu_cxe_h_CXE_SYMBOL_OFFSETS\n                             [cpu_cxe_h_CXE___code_start];\n    constexpr auto hashed_size = cpu_cxe_h_CXE_SYMBOL_OFFSETS\n                                   [cpu_cxe_h_CXE___code_end]\n                                 - cpu_cxe_h_CXE_SYMBOL_OFFSETS\n                                   [cpu_cxe_h_CXE___code_start];\n\n    auto hash = HellGates::HashArray(start_hash_from, hashed_size);\n\n    auto HashedCPUShellCode = static_cast<uint32_t>(hash)\n                              ^ static_cast<uint32_t>(hash >> 32);\n\n    RAM.Set(HellGates::DMA_ADDRESS_ANTITAMPER,\n            std::bitset<32>(HashedCPUShellCode));\n}\n\nextern \"C\" void __attribute__((aligned(4096))) InitCPU(\n  decltype(mprotect) linux_mprotect,\n  decltype(mmap) linux_mmap,\n  HellGates::RAM& RAM)\n{\n    auto data = reinterpret_cast<uintptr_t>(RAM.bits.get_data());\n    data      = data - (data % HellGates::PageSize);\n\n    linux_mprotect(reinterpret_cast<void*>(data),\n                   RAM.bits.data_size() * __SIZEOF_LONG__,\n                   PROT_READ | PROT_WRITE | PROT_EXEC);\n\n    CPUShellCodeCXEHeader = reinterpret_cast<\n      decltype(CPUShellCodeCXEHeader)>(\n      data + HellGates::CPU_SHELLCODE_BYTE_ADDRESS);\n\n    auto bytes = reinterpret_cast<uint8_t*>(CPUShellCodeCXEHeader);\n\n    for (size_t i = 0; i < CPUShellCodeSize; i++)\n    {\n        bytes[i] = cpu_cxe_h_CXE_BINARY_BLOB[i];\n    }\n\n    constexpr auto EncryptEverytimeForThisSize = 0x40000;\n\n    for (size_t i = 0; i < CPUShellCodeSize; i += HellGates::PageSize)\n    {\n        if (i >= EncryptEverytimeForThisSize\n            and i % EncryptEverytimeForThisSize == 0)\n        {\n            linux_mprotect(bytes + i, HellGates::PageSize, PROT_NONE);\n            continue;\n        }\n\n        HellGates::ChaCha20::DecryptPageSize(\n          &bytes[i],\n          i,\n          std::to_array(cpu_cxe_h_CXE_KEY));\n    }\n\n    InitializedShellCode = true;\n\n    CPUShellCodeAntiTamper(RAM);\n\n    EraseInstructions(reinterpret_cast<void*>(InitCPU));\n\n    __asm__(\".space 4096 - ( . - InitCPU ), 0x90\\n\");\n}\n\ninline void RuntimeDecryption(decltype(mprotect) linux_mprotect,\n                              siginfo_t* info,\n                              int sig)\n{\n    auto bytes = reinterpret_cast<uint8_t*>(CPUShellCodeCXEHeader);\n\n    if (LastDecryptedPageByteIndex != std::numeric_limits<size_t>::max())\n    {\n        auto bytes_to_encrypt = bytes + LastDecryptedPageByteIndex;\n        auto bytes_to_copy    = &cpu_cxe_h_CXE_BINARY_BLOB\n                               [LastDecryptedPageByteIndex];\n\n        for (size_t i = 0; i < HellGates::PageSize; i++)\n        {\n            bytes_to_encrypt[i] = bytes_to_copy[i];\n        }\n\n        linux_mprotect(bytes_to_encrypt, HellGates::PageSize, PROT_NONE);\n\n        LastDecryptedPageByteIndex = std::numeric_limits<size_t>::max();\n    }\n\n    if (sig == SIGSEGV)\n    {\n        auto aligned_address = reinterpret_cast<uint8_t*>(info->si_addr)\n                               - (reinterpret_cast<size_t>(info->si_addr)\n                                  % HellGates::PageSize);\n\n        if (aligned_address\n              < reinterpret_cast<uint8_t*>(CPUShellCodeCXEHeader)\n            or aligned_address\n                 >= (reinterpret_cast<uint8_t*>(CPUShellCodeCXEHeader)\n                     + CPUShellCodeSize))\n        {\n            NanomitesErrors::Do(\n              NanomitesErrors::NOT_WITHIN_SHELLCODE_SCOPE,\n              reinterpret_cast<size_t>(aligned_address));\n            return;\n        }\n\n        size_t page_index = aligned_address - bytes;\n\n        LastDecryptedPageByteIndex = page_index;\n\n        linux_mprotect(aligned_address,\n                       HellGates::PageSize,\n                       PROT_READ | PROT_EXEC | PROT_WRITE);\n\n        HellGates::ChaCha20::DecryptPageSize(\n          aligned_address,\n          page_index,\n          std::to_array(cpu_cxe_h_CXE_KEY));\n    }\n}\n\ninline void AntiDebug(decltype(mmap) linux_mmap,\n                      decltype(munmap) linux_munmap,\n                      HellGates::RAM& RAM,\n                      bool AntiTamper)\n{\n    auto AntiDebugCXEHeader = reinterpret_cast<CXEHeader*>(\n      linux_mmap(nullptr,\n                 AntiDebugShellCodeSize,\n                 PROT_EXEC | PROT_READ | PROT_WRITE,\n                 MAP_ANONYMOUS | MAP_PRIVATE,\n                 -1,\n                 0));\n\n    auto bytes = reinterpret_cast<uint8_t*>(AntiDebugCXEHeader);\n\n    for (size_t i = 0; i < AntiDebugShellCodeSize; i++)\n    {\n        bytes[i] = antidebug_cxe_h_CXE_BINARY_BLOB[i];\n    }\n\n    for (size_t i  = 0; i < AntiDebugShellCodeSize;\n         i        += HellGates::PageSize)\n    {\n        HellGates::ChaCha20::DecryptPageSize(\n          &bytes[i],\n          i,\n          std::to_array(antidebug_cxe_h_CXE_KEY));\n    }\n\n    auto AntiDebugShellCodeEntryFunction = reinterpret_cast<\n      void (*)(HellGates::RAM&, CXEHeader*, size_t&, bool&, uint32_t&, bool)>(\n      reinterpret_cast<uintptr_t>(AntiDebugCXEHeader)\n      + AntiDebugCXEHeader->offset_to_shellcode\n      + antidebug_cxe_h_CXE_SYMBOL_OFFSETS\n        [antidebug_cxe_h_CXE_shellcode_entry]);\n\n    static size_t CounterCPUSCHash     = 0;\n    static bool DebuggerDetected       = false;\n    static uint32_t HashedCPUShellCode = 0;\n\n    AntiDebugShellCodeEntryFunction(RAM,\n                                    CPUShellCodeCXEHeader,\n                                    CounterCPUSCHash,\n                                    DebuggerDetected,\n                                    HashedCPUShellCode,\n                                    AntiTamper);\n\n    for (size_t i = 0; i < AntiDebugShellCodeSize; i++)\n    {\n        bytes[i] = 0x90;\n    }\n\n    linux_munmap(AntiDebugCXEHeader, AntiDebugShellCodeSize);\n}\n\ninline void CPUShellCode(const inputs_central_processing_unit_t& inputs,\n                         outputs_central_processing_unit_t& outputs,\n                         decltype(mprotect) linux_mprotect,\n                         decltype(mmap) linux_mmap,\n                         decltype(munmap) linux_munmap,\n                         decltype(mremap) linux_mremap,\n                         // decltype(printf) linux_printf,\n                         int sig,\n                         siginfo_t* info,\n                         void* ucontext,\n                         HellGates::RAM& RAM)\n{\n    if (not InitializedShellCode)\n    {\n        InitCPU(linux_mprotect, linux_mmap, RAM);\n    }\n\n    auto CPUShellCodeEntryFunction = reinterpret_cast<void (*)(\n      const inputs_central_processing_unit_t& inputs,\n      outputs_central_processing_unit_t& outputs)>(\n      reinterpret_cast<uintptr_t>(CPUShellCodeCXEHeader)\n      + CPUShellCodeCXEHeader->offset_to_shellcode\n      + cpu_cxe_h_CXE_SYMBOL_OFFSETS[cpu_cxe_h_CXE_shellcode_entry]);\n\n    CPUShellCodeEntryFunction(inputs, outputs);\n\n    RuntimeDecryption(linux_mprotect, info, sig);\n}\n\nvoid RuntimeCPUNanomites(const inputs_central_processing_unit_t& inputs,\n                         outputs_central_processing_unit_t& outputs,\n                         decltype(mprotect) linux_mprotect,\n                         decltype(mmap) linux_mmap,\n                         decltype(munmap) linux_munmap,\n                         decltype(mremap) linux_mremap,\n                         int sig,\n                         siginfo_t* info,\n                         mcontext_t* mcontext,\n                         uint8_t* HellGatesCodeStart,\n                         size_t HellGatesCodeSize,\n                         HellGates::RAM& RAM)\n{\n    auto addr = reinterpret_cast<uintptr_t>(info->si_addr);\n    auto local_central_processing_unit = reinterpret_cast<bool*>(\n                                           CPUShellCodeCXEHeader)\n                                         + CPUShellCodeCXEHeader\n                                             ->offset_to_shellcode\n                                         + cpu_cxe_h_CXE_SYMBOL_OFFSETS\n                                           [cpu_cxe_h_CXE_nanomites_inputs];\n    auto shared_central_processing_unit = reinterpret_cast<bool*>(\n                                            CPUShellCodeCXEHeader)\n                                          + CPUShellCodeCXEHeader\n                                              ->offset_to_shellcode\n                                          + cpu_cxe_h_CXE_SYMBOL_OFFSETS\n                                            [cpu_cxe_h_CXE_shared_central_processing_unit];\n    bool should_increment_rip = true;\n    \n    // I will let you discover how many there is of those:\n    switch (addr)\n    {\n        case 0xDEADC0DE:\n        {\n            local_central_processing_unit[0] = (shared_central_processing_unit\n                                                  [1144]\n                                                or shared_central_processing_unit\n                                                  [457]);\n            // ...\n            *reinterpret_cast<uintptr_t*>(0xFADE) = 0xB00B;\n            asm volatile(\".byte 0x00\");\n            break;\n        }\n\n        case 0xFADE:\n        {\n            local_central_processing_unit[1841] = not(\n              shared_central_processing_unit[1160]\n              and shared_central_processing_unit[281]);\n         \n            asm volatile(\".byte 0x00\");\n            break;\n        }\n\n        case 0xEFFACED:\n        {\n            local_central_processing_unit[1867] = not(\n              shared_central_processing_unit[1078]\n              and shared_central_processing_unit[583]);\n            ...\n            break;\n        }\n\n        case 0xB00BFACE:\n        {\n            shared_central_processing_unit[1072] = true;\n            break;\n        }\n\n        default:\n        {\n            should_increment_rip = false;\n            RuntimeDecryption(linux_mprotect, info, sig);\n            break;\n        }\n    }\n\n    if (should_increment_rip)\n    {\n        auto byte = reinterpret_cast<uint8_t*>(mcontext->gregs[REG_RIP]);\n\n        while (*reinterpret_cast<uint32_t*>(byte) != 0xB00B)\n        {\n            byte++;\n        }\n\n        mcontext->gregs[REG_RIP] = reinterpret_cast<uintptr_t>(byte) + 5;\n    }\n}\n\nextern \"C\" bool shellcode_entry(\n  const inputs_central_processing_unit_t& inputs,\n  outputs_central_processing_unit_t& outputs,\n  decltype(mprotect) linux_mprotect,\n  decltype(mmap) linux_mmap,\n  decltype(munmap) linux_munmap,\n  decltype(mremap) linux_mremap,\n  // decltype(printf) linux_printf,\n  int sig,\n  siginfo_t* info,\n  void* ucontext,\n  uint8_t* HellGatesCodeStart,\n  size_t HellGatesCodeSize,\n  HellGates::RAM& RAM)\n{\n    if (not __cxe_self_relocate())\n    {\n        return false;\n    }\n\n    bool Int3Debugged = false;\n\n    auto context  = reinterpret_cast<ucontext_t*>(ucontext);\n    auto mcontext = &context->uc_mcontext;\n\n    if (sig == SIGTRAP)\n    {\n        auto byte = *reinterpret_cast<uint8_t*>(mcontext->gregs[REG_RIP]\n                                                - 1);\n        if (byte == 0xF1)\n        {\n            CPUShellCode(inputs,\n                         outputs,\n                         linux_mprotect,\n                         linux_mmap,\n                         linux_munmap,\n                         linux_mremap,\n                         // linux_printf,\n                         sig,\n                         info,\n                         ucontext,\n                         RAM);\n        }\n        else if (byte == 0xCC)\n        {\n            Int3Debugged = true;\n        }\n        else\n        {\n            NanomitesErrors::Do(NanomitesErrors::UNKNOWN_TRAP);\n        }\n    }\n    else if (sig == SIGSEGV)\n    {\n        RuntimeCPUNanomites(inputs,\n                            outputs,\n                            linux_mprotect,\n                            linux_mmap,\n                            linux_munmap,\n                            linux_mremap,\n                            sig,\n                            info,\n                            mcontext,\n                            HellGatesCodeStart,\n                            HellGatesCodeSize,\n                            RAM);\n    }\n    else\n    {\n        NanomitesErrors::Do(NanomitesErrors::UNKNOWN_SIGNAL);\n    }\n\n    AntiDebug(linux_mmap, linux_munmap, RAM, Int3Debugged);\n\n    return true;\n}\n\ninline bool IsDebuggerPresent(HellGates::RAM& RAM)\n{\n    constexpr auto xor_path = HellGates::X0RString(\"/proc/self/status\");\n\n    char buffer[1024];\n    long fd;\n    long bytes_read;\n\n    auto path = xor_path.decrypt_no_compile_time();\n\n    asm volatile(\"mov $2, %%rax\\n\"\n                 \"syscall\\n\"\n                 : \"=a\"(fd)\n                 : \"D\"(path.data()), \"S\"(0), \"d\"(0)\n                 : \"rcx\", \"r11\", \"memory\");\n\n    if (fd < 0)\n    {\n        return false;\n    }\n\n    asm volatile(\"mov $0, %%rax\\n\"\n                 \"syscall\\n\"\n                 : \"=a\"(bytes_read)\n                 : \"D\"(fd), \"S\"(buffer), \"d\"(sizeof(buffer))\n                 : \"rcx\", \"r11\", \"memory\");\n\n    asm volatile(\"mov $3, %%rax\\n\"\n                 \"syscall\\n\"\n                 :\n                 : \"D\"(fd)\n                 : \"rax\", \"rcx\", \"r11\", \"memory\");\n\n    if (bytes_read <= 0)\n    {\n        return false;\n    }\n\n    constexpr auto xor_marker    = HellGates::X0RString(\"TracerPid:\");\n    constexpr size_t marker_len  = xor_marker.SIZE - 1;\n    const size_t bytes_available = static_cast<size_t>(bytes_read);\n\n    if (bytes_available < marker_len)\n    {\n        return false;\n    }\n\n    auto marker = xor_marker.decrypt_no_compile_time();\n\n    for (size_t i = 0; i <= bytes_available - marker_len; i++)\n    {\n        if (buffer[i] == 'T' && buffer[i + 9] == ':')\n        {\n            bool match = true;\n\n            for (size_t j = 0; j < marker_len; j++)\n            {\n                if (buffer[i + j] != marker[j])\n                {\n                    match = false;\n                    break;\n                }\n            }\n\n            if (match)\n            {\n                i += marker_len;\n\n                while (i < bytes_available\n                       && (buffer[i] == ' ' || buffer[i] == '\\t'))\n                {\n                    i++;\n                }\n\n                long pid = 0;\n\n                while (i < bytes_available && buffer[i] >= '0'\n                       && buffer[i] <= '9')\n                {\n                    pid = pid * 10 + (buffer[i++] - '0');\n                }\n\n                return pid != 0;\n            }\n        }\n    }\n\n    return false;\n}\n\ninline void CPUShellCodeAntiTamper(HellGates::RAM& RAM,\n                                   CXEHeader* CPUShellCodeCXEHeader,\n                                   uint32_t& HashedCPUShellCode)\n{\n    auto start_hash_from = reinterpret_cast<uint8_t*>(\n                             CPUShellCodeCXEHeader)\n                           + CPUShellCodeCXEHeader->offset_to_shellcode\n                           + cpu_cxe_h_CXE_SYMBOL_OFFSETS\n                             [cpu_cxe_h_CXE___code_start];\n    constexpr auto hashed_size = cpu_cxe_h_CXE_SYMBOL_OFFSETS\n                                   [cpu_cxe_h_CXE___code_end]\n                                 - cpu_cxe_h_CXE_SYMBOL_OFFSETS\n                                   [cpu_cxe_h_CXE___code_start];\n\n    auto hash = HellGates::HashArray(start_hash_from, hashed_size);\n\n    HashedCPUShellCode = static_cast<uint32_t>(hash)\n                         ^ static_cast<uint32_t>(hash >> 32);\n}\n\nextern \"C\" bool shellcode_entry(HellGates::RAM& RAM,\n                                CXEHeader* CPUShellCodeCXEHeader,\n                                size_t& CounterCPUSCHash,\n                                bool& DebuggerDetected,\n                                uint32_t& HashedCPUShellCode,\n                                bool AntiTamper)\n{\n    if (not __cxe_self_relocate())\n    {\n        return false;\n    }\n\n    bool debugger_present = IsDebuggerPresent(RAM);\n\n    if (debugger_present)\n    {\n        DebuggerDetected = true;\n    }\n\n    RAM.Set(HellGates::DMA_ADDRESS_MARK_FOR_DEBUGGER,\n            { DebuggerDetected });\n\n    if (AntiTamper)\n    {\n        if (CounterCPUSCHash == 0)\n        {\n            CPUShellCodeAntiTamper(RAM,\n                                   CPUShellCodeCXEHeader,\n                                   HashedCPUShellCode);\n\n            static HellGates::SimpleRand<size_t> simpleRand;\n            CounterCPUSCHash = simpleRand.RandomInteger(64, 128);\n        }\n        else\n        {\n            CounterCPUSCHash--;\n        }\n    }\n\n    RAM.Set(HellGates::DMA_ADDRESS_ANTITAMPER,\n            std::bitset<32>(HashedCPUShellCode));\n\n    return true;\n}\n\n// CPU shellcode\n\nbool shared_central_processing_unit[1173] = {false,false,false,false,false,false,false,...};\nbool nanomites_inputs[0x1000];\nbool nanomites_outputs[0x1000];\n\nvoid cpu(const inputs_central_processing_unit_t& inputs,\n         outputs_central_processing_unit_t& outputs)\n{\n\nbool local_central_processing_unit[388716];\n*reinterpret_cast<uintptr_t*>(0xDEADC0DE) = 0xB00B;\nasm volatile(\".byte 0x00\");\n// for(std::size_t i = 0; i < 1870;i++){\n//     local_central_processing_unit[i] = nanomites_inputs[i];\n// }\nlocal_central_processing_unit[0] = nanomites_inputs[0];\nlocal_central_processing_unit[1] = nanomites_inputs[1];\nlocal_central_processing_unit[2] = nanomites_inputs[2];\nlocal_central_processing_unit[3] = nanomites_inputs[3];\nlocal_central_processing_unit[4] = nanomites_inputs[4];\nlocal_central_processing_unit[5] = nanomites_inputs[5];\nlocal_central_processing_unit[6] = nanomites_inputs[6];\n.... huge code ....\n*reinterpret_cast<uintptr_t*>(0xB00BFACE) = 0xB00B;\nasm volatile(\".byte 0x00\");\n\noutputs.index_for_special_key_modifiers_43_ = local_central_processing_unit[251358];\noutputs.index_for_special_key_modifiers_44_ = local_central_processing_unit[251915];\noutputs.index_for_special_key_modifiers_45_ = local_central_processing_unit[250703];\noutputs.index_for_special_key_modifiers_46_ = local_central_processing_unit[252032];\noutputs.index_for_special_key_modifiers_47_ = local_central_processing_unit[250379];\noutputs.index_for_special_key_modifiers_48_ = local_central_processing_unit[248991];\noutputs.index_for_special_key_modifiers_49_ = local_central_processing_unit[250380];\noutputs.index_for_special_key_modifiers_50_ = local_central_processing_unit[251698];\noutputs.index_for_special_key_modifiers_51_ = local_central_processing_unit[251463];\nshared_central_processing_unit[1169] = local_central_processing_unit[226965];\nshared_central_processing_unit[1170] = local_central_processing_unit[225981];\nshared_central_processing_unit[1171] = local_central_processing_unit[227727];\nshared_central_processing_unit[1172] = local_central_processing_unit[227726];\n\n}\n```\n\nThis should give you a strong view of the obfuscations used now.\n\n## Toolchains[#](#toolchains)\n\nI’ve used yosys and GHDL to generate the netlist.\nI’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](https://github.com/YosysHQ/yosys/pull/4928) so that DFFs can be initialized.\n\nFor CXE (shellcode generator), I’ve used also LLVM and especially ELFIO, a very cool library, which I’ve also contributed for [auxiliary vectors support](https://github.com/serge1/ELFIO/pull/105/changes/be6fe31bfc33ec1c871ae70e1c532bcf5fcf93b6) (which was needed for another injector I’ve made in [Kokabiel](https://github.com/XutaxKamay/Asura/blob/master/src/kokabiel.h)).\nI had to make my own linker script (ld file) to discard regions I didn’t need\n\nThen 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)\n\nThe nice thing about it is that the shellcodes can be used as a library ! (as shown in previous snippets)\n\n```\nauto AntiDebugShellCodeEntryFunction = reinterpret_cast<\n    void (*)(HellGates::RAM&, CXEHeader*, size_t&, bool&, uint32_t&, bool)>(\n    reinterpret_cast<uintptr_t>(AntiDebugCXEHeader)\n    + AntiDebugCXEHeader->offset_to_shellcode\n    + antidebug_cxe_h_CXE_SYMBOL_OFFSETS\n    [antidebug_cxe_h_CXE_shellcode_entry]);\n```\n\nAnd voilà, [here](antidebug.sample.cpp) is a sample of a generated shellcode.\n\n# Conclusion[#](#conclusion)\n\nThe challenge had music I liked.\n\nYou should have enough to get a password now !\n\nI 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.\n\nOn 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.\n\nIt was not a complete reversal of the sequential netlist.\n\nThis changes things.\n\nOn a small 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.\n\nI hope you had a fun read and that it wasn’t too difficult to follow.\n\nIf you have questions or improvements I could make on the blog post, [send me mails or contact me through XMPP](https://xutaxkamay.com) !", "url": "https://wpnews.pro/news/hellgates-custom-cpu-gate-level-challenge", "canonical_source": "https://blog.xutaxkamay.com/posts/hellgates/", "published_at": "2026-09-19 21:44:07+00:00", "updated_at": "2026-09-19 21:54:17.029863+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-research"], "entities": ["HellGates", "GPT-6", "Claude", "ChatGPT", "DeepSeek", "SRE-Bench", "Zhuo Zhang", "crackmes.one"], "alternates": {"html": "https://wpnews.pro/news/hellgates-custom-cpu-gate-level-challenge", "markdown": "https://wpnews.pro/news/hellgates-custom-cpu-gate-level-challenge.md", "text": "https://wpnews.pro/news/hellgates-custom-cpu-gate-level-challenge.txt", "jsonld": "https://wpnews.pro/news/hellgates-custom-cpu-gate-level-challenge.jsonld"}}