HellGates、カスタムCPUのゲートレベルチャレンジ
HellGates, custom CPU gate-level challenge (blog.xutaxkamay.com)
要約
HellGatesは、カスタム32ビットCPUをVHDLで設計し、複数の難読化、アンチタンパー、タイミングチェック、アンチデバッグを施したゲートレベルのネットリストに合成した挑戦的なプロジェクトです。人間もLLMも長期間解けませんでしたが、GPT-6がサイドチャネル攻撃を用いてわずか20〜30分でこれを解決しました。この成功は、CPUの状態を保護する暗号化が不十分であったことと、GPT-6の高度な解析能力によるものです。
全文翻訳
Hello, HellGates
はじめに
2025年の夏、私はHellGatesというクラックミーを公開しました。
その後、2025年11月にcrackmes.oneにも公開しました。
基本的に、私はカスタム32ビットCPUをVHDLで設計しました。これは暗号化されており、ビットアドレス可能(バイトアドレス可能ではない)です。これを複数の難読化、改ざん防止、タイミングチェック、デバッグ防止層を経てゲートレベルのネットリストに合成しました。これは人間向けに設計されましたが、驚くべきことにLLMに対しても効果的でした。しかし、それは現在までです。
1年間、誰もこれを解けませんでした。
人間は数週間から数ヶ月かけて取り組みましたが、断念しました。LLMも同様で、Claude、ChatGPT、DeepSeekは、私が人々に与えたヒントを頼りに数日または数週間かけても失敗し、最終的にモデルは「利用可能なリソースでは計算不可能」と宣言しました。
その後、2026年9月に、GPT-6が20〜30分未満でこれを解きました。これはSRE-Bench上でのことです。
Zhuo Zhangさん、これを可能にしてくれてありがとう!
要点は以下の通りです。CPUの状態を保護していた暗号化は、ずさんに行われており非常に弱かったのです。サイドチャネル/差分解析攻撃がGPT-6によって使用され、すべての難読化レイヤーが削除された後でも、実行を簡単に再現できるようになりました。
これにより、使用された暗号化、復号化されたレジスタ、再暗号化されたレジスタに関するヒントが得られ、ネットリストを正しく実行して、復号化された1GB(data.bin)のメモリ全体をダンプすることができました。
チャレンジの詳細はこちらです。
- 仮想CPUアーキテクチャ
- 仮想CPUで使用したプログラム
- ホスト側で使用した難読化
- ツールチェーン
CPUアーキテクチャ
一般情報
このアーキテクチャには、16個の32ビット汎用レジスタと、いくつかの特殊レジスタが含まれています。
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は、あらゆる種類の整数オーバーフローが発生した場合、またはゼロ除算が発生した場合に設定されます。操作はすべて符号付き整数操作であり、基本的なALU操作(加算、減算、乗算など)が含まれていました。
condition_flagは、異なる場所間の分岐に使用されます。
例:
IsEqual R2, 0 // condition_flag=1
Branch @loc
... instructions when condition_flag=0 ...
loc:
... instructions when condition_flag=1 ...
さて、key_modifiers/index_for_key_modifierとは何か、と疑問に思うかもしれません。
メモリアーキテクチャについては、後ほど説明します。
すべてのオペコードは以下の通りです。
-- 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";
ここではあまり難読化されていません。命令の連鎖暗号化、オペコードのシャッフル、マイクロコードエンジンを使用することもできましたが、当時は過剰だと思いました(実際、そうでしたが、今はどうでしょうか)。
メモリアーキテクチャ
暗号化されたメモリで動作するCPUを作成する場合、注意が必要です。メモリの任意のビットアドレスからビットを単純にフェッチすることはできません。なぜなら、整数や命令を読み書きしようとすると、ゴミを復号/暗号化してしまうため、CPUは役に立たなくなります。
これは、非暗号化メモリとは異なり、任意のビットアドレスで直接読み書きできる(理論上は、ほとんどの一般的なCPUは実際にはそうしませんが)点です。
特定のインデックスにアラインされたメモリへの読み書きが必要です。
私の文脈における「ワード」とは、CPUがRAMの特定のインデックススロットに読み書きできるビット数(それだけ)です。
私のアーキテクチャでは、ワードサイズは64ビットです。これは、お察しの通り、使用した暗号化方式のブロックサイズです。
しかし、プレーンテキストメモリと比較して、コードの記述は難しくなります。なぜなら、特定のインデックスでワードを単純に読み込んで命令をデコードすることはできないからです。
命令サイズ、整数サイズ、ワードサイズをすべて同じサイズにし、かつコードが設計上、特定のビットアドレスにジャンプすることは不可能で、ワードサイズにアラインされたアドレスにのみジャンプできるようにした場合を除きます(これはメモリへの整数読み書きにも同様に適用されます)。
例えば、0x10000は問題ありませんが、0x10001で命令を読み込もうとしたらどうなるでしょうか?命令は2つの部分に分割されます(C++の例、ほとんどの人はこれに慣れているでしょう)。
// 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)) {
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)) {
instruction[read_bits] = word_bits[i];
read_bits++;
}
}
// Now the instruction is being completely fetched ! Safe to read.
VHDLコードはこれよりも少し複雑ですが、命令や整数操作がワードに分割され、複数のワードインデックスが必要になることを覚えておく必要があります。
メモリ暗号化
さて、メモリ暗号化について話しましょう。私が使用したアルゴリズムはTEAです。
Cの参照実装から実装するのは非常に簡単でしたが、多数の操作とラウンドのため、多くのロジックゲートを生成します。
単純にするために、2つの暗号化レイヤーがあります。
1つはワードを暗号化するためのもので、各ワードはCPUの状態に基づいて動的に生成される独自のキーセットを持ちます(これがkey_modifiersの役割です)。
2つ目は、ワードキー自体が(KEKとして)暗号化されているため、メモリを単純に読み取るだけでは取得できません。
各ワードのKEKも動的に生成されました(ただし決定論的)。ワードインデックスと静的キー間のノンスに基づいており、特定のワードを復号するために間違ったインデックスを選択すると、ゴミが出力されます。
これはサイドチャネル攻撃に対する防御にも使用されました。そのため、ワードが同じ方法で暗号化されることは決してありませんでした。おそらく過剰でしたが、それが私の行ったことです。
さらに、すべてのワードインデックスは順列化され、メモリへのランダムアクセスのように見えます。そのため、ワードインデックス0は、0x2281FDのようなランダムな値になります。
これが1GBのdata.binがある理由です。仮想CPUのプログラムは、エントロピーデータと混ざって、1GBのデータ全体に散らばっています!
これはプログラムを難読化するための良いトリックでしたが、単純な「静的キー」をネットリストで見つけるようなものではありませんでした。残念ながら、GPT-6はメモリを復号するためにこれを見る必要さえありませんでした。
key_modifiersは、基本的にランダムに暗号化された生成キーです。それらのインデックスも順列化されているため、「index」という言葉が使われています。これらは異なるメモリに出力されます。
弱点(サイドチャネル)
CPUが状態を暗号化する方法のコードサンプルを以下に示します(cpu_integer_typeは単なる32ビット符号付き整数です)。
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;
ご覧の通り、これは明らかに弱い暗号でした。
実際、これは意図的に残しました。なぜなら、実際の暗号を使用すると、ロジックゲートの数が大幅に増加し、誰もサイドチャネルを見ないだろうと思ったからです。そのため、合成後に設計を迅速にテストする必要があったため、先に進みました。
私は間違っていました。この間違いは私に大きな代償を払わせました。GPT-6は、メモリを復号するために適切なCPU状態を持つネットリストを呼び出すだけで、部分的にそれを解決しました。人間にとってはおそらく困難な差分解析だったでしょうが、GPT-6はそれを実行し、それを発見するために何をする必要があるかを正確に予測しました。
そして、あなたも今、それを行うことができます!
プログラム
ANTLRで独自の高級アセンブリ言語とコンパイラを作成しました。これが現在のCTFプログラムで、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 .-+ ... .....:==-::-=--:::... |"
"| ::.:::.:.:-=: .:. .:-::..=+==#*--+*=-=-..:::.....:: :--:.===-.::...... |"
"| .:...:---:..-=- :. ..:::. . -=-=-- . .:::. :: -=-.:=---. .:.. |"
"| ::..-==-=-..:::... :::. :-:---::-: .::: ....::..-::.. :--:. |"
"| ....:.:--:. .::-: ::-: :.=*+=. :::.. :-::: .::...:-::.... |"
"| .....:...:==- .... .:::- -::.. .:.. ...::. .:.:::. |"
"| .....:-::--==: .:: .:-:- 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_2:
"Please wait for LCD message ...................................................................................................."
"| . .. . =- .::::-. .--*****- |"
"| +* *+ ..:. .%: .@= -=-:. *.#..-*:. ..:. .... |"
"| -%=---#+.-+-:=- -# :# -%. -++- -==#+.#: -#=:-= =+--- |"
"| ** =#.-@#-.. -%: :#: .*=...-@* -#=-=%:.#- :#=-:: ---*+ |"
"| . =: -- :..-: ..::...::. --==--....::.:: .:.. ::-:...:::. .. |
"| :::::...::.::::....:::..:::::...:..::::::.::.::.::::.:::::::::-::::. |"
"| .. .+#%@@@@%#=. .. |"
"| ..::::... ..:....... :**++#@@#+++=. ...::--:..::::.::... |
"| ....::--:..-=-..:.. ... #=.o.-=-- O .-+ ... .....:==-::-=--:::... |"
"| ::.:::.:.:-=: .:. .:-::..=+==#*--+*=-=-..:::.....:: :--:.===-.::...... |"
"| .:...:---:..-=- :. ..:::. . -=-=-- . .:::. :: -=-.:=---. .:.. |"
"| ::..-==-=-..:::... :::. :-:---::-: .::: ....::..-::.. :--:. |"
"| ....:.:--:. .::-: ::-: :.=*+=. :::.. :-::: .::...:-::.... |"
"| .....:...:==- .... .:::- -::.. .:.. ...::. .:.:::. |"
"| .....:-::--==: .:: .:-:- 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. ::... ..:=--:. ..::-=-:.. .::::..:... |"
"| ......:::::::.:...::====- .....::. ...: :.... ::--::--:..:-: ... . ..:::.... |"
"| .. ........:.:---:-:..:=--=-: ....:..:..: ....::.::.::::..::::::.....-:.:..:=-:::---:......:...::. ..... ... |"
"| .. ... .:::-::::-::. .:::-:.....:::::::::-====++++**+===+++=----::::...-:..:::. .:--:.. ....... .. .... |"
"| ...... ...::...:::::::::-=====+=====++++**##**+=+****+++===++==------=----::..:::........:::.:::. ..... .. |"
"| ... .:::...:::----:::::---=-==+==++-+=+**++++*******+**+=++++*+==++=--==+==+=+*++=======--:....::. .:. |"
"| |"
"--------------------------------------------------------------------------------------------------------------------------------"
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
コンパイルされたプログラムで暗号化されたRAMスナップショットをどのように作成したかはこちらです:RAMCreator
コード全体は提供しませんでしたが、アーキテクチャはこれでわかります。アセンブリからパスワードも削除しましたが、RAMCreatorで簡単に見つけられるはずなので、自分で解決できます!:)
要約すると、これは以下を使用しています。
- Fake SMC/FSMC = フェイク自己変更コード。実際には何も平文で変更されませんが、キーをローテーションさせてメモリが常に自己変更しているように見せかけます。
- ブルートフォース防止(120文字以上入力できませんでした)。
- ホスト上の改ざん防止ウォッチドッグ(後でホスト側でどのように実装されたかを説明しますが、これも意図的な弱点でした)。ネットリスト実行中に機能します。
- デバッグ防止ウォッチドッグ。
- サイドチャネル攻撃を回避するために分岐を避ける試み(CPUが次の命令をどこから読み取るかを確認することで)。代わりにXOR演算を使用します。(うーん、これはまずかったですね)。
- サイドチャネルが使用された場合、イースターエッグがあります。皮肉なことに、キーボード入力と弱いパスワードチェックに関連するいくつかの弱点のために解決されました(GPT-6はこれらの弱点を使用しました)。この点ではキー生成の方がはるかに優れていたでしょう。
- アニメーションするドクロ。(これはアセンブリで書くのが楽しかったです)。
DMA時間が見えると思いますが、これはネットリストがどれだけ長く実行されるかを測定するために使用されました。例えば、QEMUのようなエミュレータで実行した場合、プログラムが単に終了するのではなく、パスワードチェックが失敗するようになります。
また、キーが動的に生成され、CPUレジスタ/状態に依存するため、新しいワードキーを生成するためのエントロピーを増やすためにも使用されました。
同様のアンチメカニズムにより、パスワードチェックは終了/ホストプログラムのクラッシュではなく、失敗するようになります。
これは悪質ですね、わかっています。
ホスト側で使用された難読化
簡単に言うと、ホスト側では以下のものを使用しました。
-
再帰的に暗号化された(ChaCha20のバリアントを使用)ネストされたシェルコード(CXE、calvin-xutaxkamay-executableの略。これはリバースエンジニアリングハイパーバイザー向けでもあったため、Calvinと私の友人たち、もしこれを見たら!長年のサポートありがとう!)例外ハンドラ内に散らばったネットリストが含まれています。独自のシェルコードジェネレーターを作成する必要があったため、自己配置が正しく行われます(位置独立であり、シェルコードはC/C++で記述できます。これはtoolchainsで詳しく説明します)。
-
再帰的な実行時復号/暗号化例外ハンドラ。
-
制御フロー難読化ですが、特定のハードコードされたアドレスと、デバッグ/改ざん防止またはネットリスト自体に繋がる古典的なデバッグ命令
int 3/.byte 0xF1を使用したシグナルリターンに基づく例外処理です。CFO自体にネットリストの一部が含まれていました。面白いのは、これがディスアセンブラを破壊し、一部のバイトを誤解釈するため、動的解析なしでは追跡が容易ではないことです。
[画像: The disassembly1]
[画像: The disassembly2]
以下は、C++コードの完全なフローです。言葉で説明するよりも、コードを読んだ方が簡単でしょう(スニペットであり、完全なコードではありません)。
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();
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;
}
}
// Exception handler starts here
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];
}
これで、使用されている難読化について強力な見解が得られるはずです。
ツールチェーン
YosysとGHDLを使用してネットリストを生成しました。また、blif2cppという独自のツールを作成し、さまざまな生成デザイン(パフォーマンス重視のものも含む)を試しました(プライベートリポジトリに保管しています)。また、yosysに貢献して、DFFを初期化できるようにしました。
CXE(シェルコードジェネレーター)には、LLVM、特にELFIOを使用しました。これは非常にクールなライブラリで、補助ベクトルサポートにも貢献しました(これは私がKokabielに作成した別のインジェクターで必要でした)。不要な領域を破棄するために、独自のリンカスクリプト(ldファイル)を作成する必要がありました。
その後、ビルドされたELFを暗号化されたバイト配列CXEに変換し、生成されたコードとシンボルを列挙型として直接含めることができるようにしました。これにより、文字列関連のシンボルは完全に削除されます(スニペットで示したように)。
これの素晴らしい点は、シェルコードをライブラリとして使用できることです!(前のスニペットで示したように)。
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]);
そして完成です。こちらに生成されたシェルコードのサンプルがあります。
結論
チャレンジには好きな音楽が含まれていました。
これでパスワードを見つけるのに十分な情報があるはずです!
これは部分的に解決されたと言わざるを得ません。なぜなら、真のソルバーは、サイドチャネル攻撃を使用するだけでなく、すべてのアルゴリズムを復元するからです。次のチャレンジは、今回はLLMと人間の両方を対象に設計されます。このブログ記事を書きながら、すでに別のアーキテクチャの設計をほぼ完了しています。それは私が言及したすべてのサイドチャネルをすでに解決しており、さらにそれらを回避するための工夫が施されています。
補足ですが、LLMがこのチャレンジを解決したのは事実ですが、正直に言って、その解決の速さ(20〜30分)には感銘を受けましたが、それはサイドチャネルを使用しました。
それは逐次的なネットリストの完全なリバースエンジニアリングではありませんでした。
これは状況を変えます。
LLMがそのように解決したことを軽視しているわけではありません。最も簡単な道が常に最善ですが、実用的なサイドチャネルが存在しない場合、数百万のロジックゲートを分析することははるかに困難になるでしょう。
別の補足として、たとえ遅くとも、私は個人的に人間が思考において優れていると考えています。LLMが思考できるとさえ言えるかどうかは別として。彼らは予測します。もし思考が単なる予測であるなら、おそらくLLMは思考できるのかもしれません。しかし、私たちは予測できないものも感じます。これが人間を予測不可能にし、私たちが思っているよりもカオスにする理由です。これが、結局のところ、私たちの生存を可能にしたのです。芸術なしでは生きられなかったでしょう。芸術でさえ、ある意味では無用と見なされるかもしれません。ただ一つのこと、つまり生き残るのではなく、生きるために奮闘することを除いては。人間関係で多くの困難がありましたが、私は依然として人類を信じています。
このブログ記事を楽しんでいただけたなら幸いです。そして、あまり難しくなかったことを願っています。
質問やブログ記事の改善点があれば、メールを送るかXMPPで連絡してください!