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:
- The virtual CPU architecture .
- The program I used in the virtual CPU.
- The obfuscations on host side I used.
- 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 0xF1that 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<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.
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.
On 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.
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 !