summaryrefslogtreecommitdiffstats
path: root/iv
diff options
context:
space:
mode:
Diffstat (limited to 'iv')
-rw-r--r--iv/oc_challs/dns/README.md43
-rw-r--r--iv/oc_challs/hertz/README.md17
-rw-r--r--iv/oc_challs/hertz/sound.flacbin0 -> 1488759 bytes
-rw-r--r--iv/oc_challs/oebb/README.md20
-rw-r--r--iv/oc_challs/oebb/oebb.jpgbin0 -> 3941275 bytes
-rw-r--r--iv/oc_challs/ov/.gitignore4
-rw-r--r--iv/oc_challs/ov/Dockerfile13
-rw-r--r--iv/oc_challs/ov/README.md19
-rw-r--r--iv/oc_challs/ov/challenge.yml23
-rw-r--r--iv/oc_challs/ov/docker-compose.yml9
-rw-r--r--iv/oc_challs/ov/hi.html1
-rw-r--r--iv/oc_challs/ov/index.html1
-rw-r--r--iv/oc_challs/ov/makefile21
-rwxr-xr-xiv/oc_challs/ov/server.php112
14 files changed, 283 insertions, 0 deletions
diff --git a/iv/oc_challs/dns/README.md b/iv/oc_challs/dns/README.md
new file mode 100644
index 0000000..7a7bd12
--- /dev/null
+++ b/iv/oc_challs/dns/README.md
@@ -0,0 +1,43 @@
+# dns
+
+## Details
+* points: 8
+* category: net
+* author: Anton
+* flags: `cnctf{treat-domain-zones-as-public-knowledge}` or `.*ea7a56e3c09728491d60b37636313faa.*` (case insensitive)
+
+## Public description:
+The flag is stored in a TXT record on a subdomain of the domain `4a.si`. Flag format is `cnctf{.*}`. Subdomain is in format `cnctf-<32 random hex characters>.4a.si`.
+
+## Deployment
+/
+
+## Solution / writeup
+
+### Solve method 1: AXFR zone transfer
+
+The primary DNS server of `4a.si` can be obtained with `dig +short soa 4a.si` and it is at the moment `ns1.4a.si.`. You can direct a query directly to it with `dig @ns1.4a.si 4a.si aaaa` for example. It has zone transfers enabled for the entire world, so you can use `dig @ns1.4a.si 4a.si axfr` to get all records in the 4a.si domain. With the following command, you extract the flag from the "zone dump":
+
+```
+dig @ns1.4a.si 4a.si axfr | grep cnctf{
+```
+
+### Solve method 2: NSEC Zone walking
+
+DNSSEC is a method of signing zones in the DNS. To simplify, each record gets a corresponding RRSIG record with the signature of the record. This RRSIG is passed along to any DNSSEC-capable resolvers so that they can cryptographically prove that no malicious resolver provided wrong responses.
+
+But how would you sign the non-existance of a specific domain name? You can't just sign a message saying that "subdomain xxx does not exist", since DNSSEC is based on the requirement that the entire zone must be able to be signed completely offline and that resolvers for the domain may operate entirely without possession of the private signing key, only utilising presigned RRSIG records.
+
+A solution to the problem of proving non-existance is to sign a record (called NSEC) that says that there are no subdomains between two subdomains (sorted alphabetically). Such NSEC records can be presigned (since we know all subdomains at sign-time) and stored on resolvers that don't do any cryptography. But the NSEC specification allows an attacker to obtain the entire zone with a process called NSEC zone-walking.
+
+While we could walk and download the entire zone, since we know how the record starts (cnctf-), we can just query a non-existing `cnctf.4a.si` with DNSSEC requested and we get the searched-for secret subdomain in the NSEC record response:
+
+```
+dig +dnssec cnctf.4a.si | grep cnctf-
+```
+
+And then, to get the flag:
+
+```
+dig cnctf-ea7a56e3c09728491d60b37636313faa.4a.si txt
+```
diff --git a/iv/oc_challs/hertz/README.md b/iv/oc_challs/hertz/README.md
new file mode 100644
index 0000000..722bd6d
--- /dev/null
+++ b/iv/oc_challs/hertz/README.md
@@ -0,0 +1,17 @@
+# hertz
+
+## Details
+* points: 8
+* category: forensic
+* author: Anton
+* flags: `cnctf{slow_scan_television_2023}`
+
+## Public description: (empty, just file)
+
+
+## Deployment
+* Players should get `sound.flac`.
+
+## Solution / writeup
+
+This is a SSTV encoded image containing the flag. Use a suitable decoder, such as the Android app "Robot 36" to read the image. For Windows, you can use MMSSTV if I recall correctly.
diff --git a/iv/oc_challs/hertz/sound.flac b/iv/oc_challs/hertz/sound.flac
new file mode 100644
index 0000000..768024a
--- /dev/null
+++ b/iv/oc_challs/hertz/sound.flac
Binary files differ
diff --git a/iv/oc_challs/oebb/README.md b/iv/oc_challs/oebb/README.md
new file mode 100644
index 0000000..151f89e
--- /dev/null
+++ b/iv/oc_challs/oebb/README.md
@@ -0,0 +1,20 @@
+# oebb
+
+## Details
+* points: 8
+* category: forensic
+* author: Anton
+* flags: `cnctf{aztec_code}` or `cnctf{aztec_code} a`
+
+## Public description:
+
+A coworker sent me his railway ticket. But the name in the upper right corner in the corner of the ticket is obscured. Can you help me recover my coworker's surname that is hidden under the colourful piece of paper?
+
+## Deployment
+
+* Players should get `oebb.jpg`.
+
+## Solution / writeup
+
+This entire rail ticket is encoded according to the UIC918.3 specification in the Aztec code in the bottom right corner of the image. Use a suitable decoder, like the Android app `nl.waarisdetrein.myfirstrailpocket`, to decode the surname from the ticket.
+
diff --git a/iv/oc_challs/oebb/oebb.jpg b/iv/oc_challs/oebb/oebb.jpg
new file mode 100644
index 0000000..4f579b4
--- /dev/null
+++ b/iv/oc_challs/oebb/oebb.jpg
Binary files differ
diff --git a/iv/oc_challs/ov/.gitignore b/iv/oc_challs/ov/.gitignore
new file mode 100644
index 0000000..a43297e
--- /dev/null
+++ b/iv/oc_challs/ov/.gitignore
@@ -0,0 +1,4 @@
+*.asm
+*.out
+*.log
+*.tar.gz
diff --git a/iv/oc_challs/ov/Dockerfile b/iv/oc_challs/ov/Dockerfile
new file mode 100644
index 0000000..4ba2b06
--- /dev/null
+++ b/iv/oc_challs/ov/Dockerfile
@@ -0,0 +1,13 @@
+FROM debian:latest
+
+MAINTAINER anton@sijanec.eu
+
+EXPOSE 6844/tcp
+
+WORKDIR /app
+
+RUN apt-get update && apt-get install --no-install-recommends -y php-cli nasm python3 make socat && apt-get clean
+
+COPY ./ /app
+
+CMD ["make", "run" ]
diff --git a/iv/oc_challs/ov/README.md b/iv/oc_challs/ov/README.md
new file mode 100644
index 0000000..4dd2e2c
--- /dev/null
+++ b/iv/oc_challs/ov/README.md
@@ -0,0 +1,19 @@
+# 1bitvm
+
+## Details
+* points: 8
+* category: misc
+* author: Adrian & Anton
+* flags: `cnctf{hard2pwnUnknownArch}` or `cnctf{hard2pwnUnknownArch}---` (same flag)
+
+
+## Description:
+1bitvm is a cursed virtual machine for a cursed imaginary architecture. A program is written that combines the horrors of PHP, python and architecture specific assembly language into a giant mess that somehow servers HTTP requests. It's your job to convince the "webserver" to read you the flag from it's RAM, totaling not more than 128 bits.
+
+## Deployment
+* Deploy the docker image with `docker compose up`. It is listening on port 6844, map it to whatever port is most convenient.
+ - alternatively you can just run `make run`
+* Players should get 1bitvm.tar.gz created with `make 1bitvm.tar.gz`. It doesn't contain solutions and flags.
+
+## Solution / writeup
+The `win` subroutine reads the flag from the virtual ROM. Since content is delivered based on the first two bytes of the path (control "jumps" to first two bytes of request path casted to address ORed with 0x8000), players can also jump to the `win` subroutine. The address of `win:` label can be read from `asm.log` file (`win na 0xe339`). Since the first bit of the address will always be set to 1, 0x63 (ASCII 'c') and 0x39 (ASCII '9') can be set as request path to jump to the `win` subroutine and print the flag.
diff --git a/iv/oc_challs/ov/challenge.yml b/iv/oc_challs/ov/challenge.yml
new file mode 100644
index 0000000..1662add
--- /dev/null
+++ b/iv/oc_challs/ov/challenge.yml
@@ -0,0 +1,23 @@
+# ctfcli ctfd challenge spec: https://github.com/CTFd/ctfcli/blob/master/ctfcli/spec/challenge-example.yml
+
+name: "1bitVM"
+author: "anton@sijanec.eu"
+category: misc
+description: 1bitVM is an idea for an architecture with one instruction bit. It is currently serving a simple website and is storing the flag somewhere in it's mere 128 bits of RAM. Local setup: run make run to start the server on port 6844 locally
+value: 8
+type: standard
+
+image: healthcheck
+
+protocol: tcp
+
+connection_info: nc b.4a.si 6844
+
+flags:
+ - cnctf{hard2pwnUnknownArch}---
+ - cnctf{hard2pwnUnknownArch}
+
+files:
+ - 1bitvm.tar.gz
+
+version: 0.1
diff --git a/iv/oc_challs/ov/docker-compose.yml b/iv/oc_challs/ov/docker-compose.yml
new file mode 100644
index 0000000..0cf2c27
--- /dev/null
+++ b/iv/oc_challs/ov/docker-compose.yml
@@ -0,0 +1,9 @@
+services:
+ 1bitvm:
+ build: .
+ ports:
+ - "6844:6844"
+ restart:
+ always
+ environment:
+ - flag=cnctf{hard2pwnUnknownArch} # if challenge breaks, you may have made the flag too long (128b RAM!)
diff --git a/iv/oc_challs/ov/hi.html b/iv/oc_challs/ov/hi.html
new file mode 100644
index 0000000..87a2780
--- /dev/null
+++ b/iv/oc_challs/ov/hi.html
@@ -0,0 +1 @@
+<h1>Created by Adrian & Anton 2023-09
diff --git a/iv/oc_challs/ov/index.html b/iv/oc_challs/ov/index.html
new file mode 100644
index 0000000..02aa964
--- /dev/null
+++ b/iv/oc_challs/ov/index.html
@@ -0,0 +1 @@
+<h1>One-Bit virtual machine<h2>GET the flag!</h2>READ SOURCE in TAR!<h1><a href=YO>authors<img src=//4a.si/sl><a href=//4a.si/1b>1bitvm
diff --git a/iv/oc_challs/ov/makefile b/iv/oc_challs/ov/makefile
new file mode 100644
index 0000000..f3690f4
--- /dev/null
+++ b/iv/oc_challs/ov/makefile
@@ -0,0 +1,21 @@
+default: 1bitvm.tar.gz run
+
+server.out: 1bitvm server.asm
+ 1bitvm/asm.py -d server.asm 2>&1 > asm.log
+
+server.asm: server.php index.html hi.html
+ ./server.php > $@
+
+run: server.out
+ socat TCP6-LISTEN:6844,fork,reuseaddr 'exec:1bitvm/main.py server.out'
+
+1bitvm:
+ git clone https://ass.si/git/adrian/1bitvm
+
+clean:
+ rm -fr `cat .gitignore`
+
+1bitvm.tar.gz: server.out docker-compose.yml Dockerfile
+ f=`grep -o cnctf{.*} docker-compose.yml` && sed -i s/$$f/cnctf{placeholder}/ docker-compose.yml && cd .. && tar --exclude *README.md --exclude *challenge.yml --exclude *1bitvm.tar.gz -zc `rev <<<$$OLDPWD | cut -d/ -f1 | rev` | sponge $$OLDPWD/1bitvm.tar.gz; cd $$OLDPWD; sed -i s/cnctf{placeholder}/$$f/ docker-compose.yml
+
+.PHONY: default clean run
diff --git a/iv/oc_challs/ov/server.php b/iv/oc_challs/ov/server.php
new file mode 100755
index 0000000..88034e8
--- /dev/null
+++ b/iv/oc_challs/ov/server.php
@@ -0,0 +1,112 @@
+#!/usr/bin/env php
+;; This VM has 128 bits of RAM so creating loops is hard. Instad this "HTTP" server works by unrolling all loops in assembly. To achieve that, assembly is generated by preprocessing with PHP. You can see the output file after running make in server.asm.
+<?php
+function escape ($input) { // escapes a byte string $input by converting every byte into \xHH.
+ return implode("", array_map(function ($element) { return "\\x" . bin2hex($element); }, str_split($input)));
+}
+?>
+
+;; start writing the following code to address 4 in ROM
+.org 4
+
+;; include standard library
+%include "1bitvm/std.asm"
+
+;; assembly macro that prints two bytes (black box)
+%macro print2 2
+ c16 %1, %2, 1
+ set_out_b %2 + 0 , 0x14
+ set_out_b %2 + 1 , 0x14
+ set_out_b %2 + 2 , 0x14
+ set_out_b %2 + 3 , 0x14
+ set_out_b %2 + 4 , 0x14
+ set_out_b %2 + 5 , 0x14
+ set_out_b %2 + 6 , 0x14
+ set_out_b %2 + 7 , 0x14
+ set_out_b %2 + 8 , 0x14
+ set_out_b %2 + 9 , 0x14
+ set_out_b %2 + 10, 0x14
+ set_out_b %2 + 11, 0x14
+ set_out_b %2 + 12, 0x14
+ set_out_b %2 + 13, 0x14
+ set_out_b %2 + 14, 0x14
+ set_out_b %2 + 15, 0x14
+%endm
+
+;; header that is sent in every response, 38 bytes
+header:
+<?php
+$header = "HTTP/1.0 200\nContent-Type: text/html\n\n";
+?>
+.db b"<?= escape($header) ?>"
+
+;; label that points to defined bytes as a string literal in ROM
+flag:
+.db b"<?= getenv("flag") ?: "cnctf{placeholder}" ?>"
+
+;; storage for hi page
+hi:
+.db b"<?= escape(file_get_contents("hi.html")) ?>"
+
+;; label that points to defined bytes as a string literal in ROM
+;; PHP is only used as a preprocessor for generating assembly with this bytestring read from a file
+index:
+.db b"<?= escape(file_get_contents("index.html")) ?>"
+
+print_header_pointer:
+.orgr 2
+
+;; prints header and jumps to address stored in 0x30 + 1
+print_header:
+<?php
+ for ($i = 0; $i < strlen($header)/2; $i++)
+ echo " print2 labels['header']+$i, 0x15" . PHP_EOL;
+?>
+ ret
+
+.org labels["print_header_pointer"]*2
+.db by2(labels["print_header"])
+
+;; the string b' H' will be read as two byte request-path (GET /< H>TTP/1.0) when requesting with empty path
+.org (int.from_bytes(b' H') | 0x8000)*2
+;; PHP generated assembly subroutine that prints bytes at label word. creating a loop on this VM would be hard.
+print_index:
+ call labels["print_header_pointer"], 1
+<?php
+ for ($i = 0; $i < strlen(file_get_contents("index.html"))/2; $i++)
+ echo " print2 labels['index']+$i, 0x15" . PHP_EOL;
+?>
+ exit
+
+;; hi page, the & 0xfffe means last bit will always be 0
+.org (int.from_bytes(b'YO') | 0x8000)*2
+print_hi:
+ call labels["print_header_pointer"], 1
+<?php
+ for ($i = 0; $i < strlen(file_get_contents("hi.html"))/2; $i++)
+ echo " print2 labels['hi']+$i, 0x15" . PHP_EOL;
+?>
+ exit
+
+;; sends the flag to the client
+win:
+ call labels["print_header_pointer"], 1
+<?php
+ for ($i = 0; $i < strlen(getenv("flag") ?: "cnctf{placeholder}")/2; $i++)
+ echo " print2 labels['flag']+$i, 0x15" . PHP_EOL;
+?>
+ exit
+
+main:
+ ;; we set first bit of address to 1 -- all pages are on addresses 0x8000 and above, first two letters of request path are casted to an address and ORed with 0x8000
+<?php
+ for ($i = 0; $i < strlen("GET /")*8; $i++) // read the first 5 bytes and discard them, they will always be the same (GET /)
+ echo "get_in_b 0x15, 0x14" . PHP_EOL . "set0 IN_A" . PHP_EOL;
+ for ($i = 0; $i < 16; $i++) // read two bytes, it will be used for selecting a page to send to the client
+ echo "get_in_b 0x30+$i, 0x14" . PHP_EOL . "set0 IN_A" . PHP_EOL;
+?>
+ set1 0x30
+ ;; jump to print subroutine
+ c16 0x30, 0, 0
+
+init "main"