HellGates, custom CPU gate-level challenge 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. Introduction introduction In the Summer of 2025 I published a crackme called HellGates. Then I published it in November crackmes.one https://crackmes.one/crackme/692c1d9a2d267f28f69b820c . 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 https://x.com/i2huer/status/2099892644826505392 on SRE-Bench https://sre-bench.lol/ . Thank you Zhuo Zhang https://zzhang.xyz/ 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 cpu . 2. The program program I used in the virtual CPU. 3. The obfuscations obfuscation on host side I used. 4. The toolchains toolchains . CPU architecture cpu General information: 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: 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: 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 : 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 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 ramcreator.cpp 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 obfuscation 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 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: 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