Skip to content

Commit

Permalink
Initial.
Browse files Browse the repository at this point in the history
  • Loading branch information
mzhang28 committed Mar 21, 2017
0 parents commit c19872a
Show file tree
Hide file tree
Showing 321 changed files with 7,793 additions and 0 deletions.
Binary file added 20xx/_20xx.dtm
Binary file not shown.
1 change: 1 addition & 0 deletions 20xx/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
My friend sent me [this file](${_20xx_dtm}) and told me to git gud.
4 changes: 4 additions & 0 deletions 20xx/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def grade(random, key):
if key.lower().find("foxonlyfd") != -1:
return True, "Correct!"
return False, "Work on your tech skill, skrub."
8 changes: 8 additions & 0 deletions 20xx/problem.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
author: arxenix
title: 20xx
category: Forensics
autogen: false
programming: false
value: 50
files:
- _20xx.dtm
112 changes: 112 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
OpenCTF Problem Import via Github
-------------------------------------------------

Each problem belongs in its own folder under the root folder of the repository. The name of the folder will become a readable identifier for the problem, so it should only contain letters, numbers, and `-`.

Inside this folder, there are 3 required files:

- `problem.yml`: This is probably the most important file. It contains all the metadata for the problem, including:
- `title`: The title of the problem.
- `category`: The category of the problem.
- `value`: An integer value for the problem.
- `hint`: A hint for the problem. (no markdown supported)
- `autogen`: (`true`/`false`) whether or not the problem is autogenerated. If you put `true`, read on to see what needs to be added to the grader.
- `description.md`: A problem description. As the extension suggests, you may use markdown in this file.
- `grader.py`: A python file containing several functions that relate to grading the problem (and generating, if autogen is enabled).

The Grader
--------------

At the most basic level, the grader should contain a `grade` function that takes in two parameters (you can call them whatever you like): (1) an instance of the random library used for autogenerating problems, and (2) the flag that the user actually typed into the input box. It should return a tuple `(correct, message)`, where `correct` is a boolean value signifying whether the user got the problem right, and `message`, a custom message to be displayed.

An example of a basic grader looks like this:

```python
def grade(random, key):
if key.find("this_is_the_flag") != -1:
return True, "Correct!"
return False, "Nope."
```

If autogen is not enabled, please do not use `random` while judging the key. It's meant for checking problems that were autogenerated with the same seed. Also, note that I'm using `key.find` rather than comparing them with equals. This is useful if you want to match both `flag{this_is_the_flag}` and `this_is_the_flag`, or if you are trying to perform a case-insensitive match.

If autogen were enabled, you would need to add an additional function to the grader, called `generate`. Essentially, this function is required for generating the problem. It takes in a single parameter, the same instance of `random` as the `grade` function gets, and must return a dictionary of values (obviously, this dictionary can be blank, but ideally it shouldn't be).

Possible keys that you can include in your dictionary are:
- `variables`: This should be another dictionary of variables that are inserted into the description during runtime. More details on how these variables are inserted comes in a later section. You would probably want to use the `random` variable that is passed to the function to generate a value that is different per team.
- `files`: This is similar to `variables` in that it is a dictionary of keys that are inserted into the description. However, instead of returning variables directly, you should return a function that takes in the `random` variable and returns a File object (could be StringIO as well). Files generated this way are stored into the static container during runtime.

An example using both `variables` and `files` is:

```python
from cStringIO import StringIO
def gen_b(random):
b = random.choice(range(50, 100))
return b
def generate(random):
a = random.choice(range(50, 100))
return dict(variables={
"a": a
}, files={
"b.txt": gen_b
})
```

Note that we are returning `gen_b` without `()`, since we want to return the function definition, rather than a value returned by the function. This way, the platform can call this function at runtime, generating a different problem for every user.

**This part is very important**. Filenames usually have extensions, like `ciphertext.txt`. In this case, we are passing the filename into the description as a string. But Python template strings don't allow symbols such as periods. Therefore, ALL FUNCTION NAMES ARE SANITIZED by replacing anything matching this regex: `[^a-zA-Z]+` with `_` (underscore). This way, `ciphertext.txt` becomes `ciphertext_txt` in the description. Here is a description that uses the above generator.

```markdown
Help me decipher [this](${b_txt}).
```

Here is a full example using autogen:

problem.yml:

```yaml
title: Caesar Cipher
value: 20
author: mzhang
autogen: true
```
description.md:
```markdown
Help me decipher [this ciphertext](${ciphertext_txt}).
```

grader.py:

```python
from cStringIO import StringIO
from string import maketrans

flag = "caesar_cipher_is_fun!"
alphabet = "abcdefghijklmnopqrstuvwxyz"

def get_problem(random):
n = random.randint(1, 25)
salt = "".join([random.choice("0123456789abcdef") for i in range(6)])
return (n, salt)

def generate_ciphertext(random):
n, salt = get_problem(random)
trans = maketrans(alphabet, alphabet[n:] + alphabet[:n])
ciphertext = ("easyctf{%s_%s}" % (flag, salt)).translate(trans)
return StringIO(ciphertext)

def generate(random):
return dict(files={
"ciphertext.txt": generate_ciphertext
})

def grade(random, key):
n, salt = get_problem(random)
if key.find("%s_%s" % (flag, salt)) >= 0:
return True, "Correct!"
return False, "Nope."
```

In this way, not only is the shift randomly generated, so is the actual flag itself. Note that I use a helper function, `get_random` to actually generate the numbers to ensure that the numbers generated are the same for both the generation and the grading.
1 change: 1 addition & 0 deletions a-maze-ing/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Solve a maze! 'j' is left, 'k' is down, 'l' is right, and 'i' is up. You input directions in a string. An example: "jkliilillikjk". Submit your input string as the flag. (Whoops! You don't have a maze, do you? Sucks to be you.)
4 changes: 4 additions & 0 deletions a-maze-ing/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def grade(autogen, answer):
if len(answer)>= 25:
return True, "You guessed right!"
return False, "Nope. The maze is a bit longer than that."
7 changes: 7 additions & 0 deletions a-maze-ing/problem.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
title: A-maze-ing
author: nicebowlofsoup
hint: It may take you a while to get to the end. Just keep going!
category: Miscellaneous
autogen: false
programming: false
value: 30
Binary file added bizarro/crpt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions bizarro/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Something seems very strange about [this](${crpt_png}) strange looking image. Check it out?
4 changes: 4 additions & 0 deletions bizarro/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def grade(autogen, key):
if key.find("t0uchybraill3fakeq4c0d3iss00faaaake_efceeaca") != -1:
return True, "Nice!"
return False, "Nope."
9 changes: 9 additions & 0 deletions bizarro/problem.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
author: blockingthesky
title: Bizarro
category: Forensics
autogen: false
programming: false
value: 400
hint: Red herrings are always a _touchy_ subject. Combine this hint with intel you find in the problem, throw in a blind guess, and perhaps you'll stumble into the answer.
files:
- crpt.png
1 change: 1 addition & 0 deletions blogbox/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
I found another [blog](http://blogbox.web.easyctf.com/)! Do you think you can find a flag on it?
4 changes: 4 additions & 0 deletions blogbox/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def grade(autogen, key):
if key.find("i_cant_GET_n0_s@tisfAct10N") != -1:
return True, "I always knew you could GET it!"
return False, "Keep trying."
7 changes: 7 additions & 0 deletions blogbox/problem.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
author: blockingthesky
title: Blogbox
category: Web
autogen: false
programming: false
value: 135
hint: Use the search bar to see all the public posts! (And only the public posts!)
Binary file added commentary/Commentary.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions commentary/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The flag is in [Commentary.pdf](${Commentary_pdf}). Use lowercase.
4 changes: 4 additions & 0 deletions commentary/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def grade(autogen, answer):
if answer.find("yougotit")!=-1:
return True, "Correct!"
return False, "Nope, try again."
9 changes: 9 additions & 0 deletions commentary/problem.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
title: Clear and Concise Commentary on Caesar Cipher
author: nicebowlofsoup
hint: Practice!
category: Cryptography
autogen: false
programming: false
value: 20
files:
- Commentary.pdf
1 change: 1 addition & 0 deletions cookieblog/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
I found the cookie monster's [blog](http://cookieblog.web.easyctf.com)!
4 changes: 4 additions & 0 deletions cookieblog/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def grade(random, key):
if key.find("yum_c00kies!!!") != -1:
return True, "Yum!"
return False, "Eww... wrong. bad cookie"
7 changes: 7 additions & 0 deletions cookieblog/problem.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
title: Cookie Blog
author: neptunia
hint: Where can I find cookies on a website?
category: Web
autogen: false
programming: false
value: 30
48 changes: 48 additions & 0 deletions count.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python

import os
import yaml
import traceback
from collections import Counter

problem_names = os.listdir(os.path.dirname(os.path.abspath(__file__)))
problems = []

failed = []
total = 0

for problem_name in problem_names:
folder = os.path.dirname(os.path.abspath(__file__)) + os.sep + problem_name
if not (os.path.exists(folder) and os.path.isdir(folder)): continue
try:
metadata_file = folder + os.sep + "problem.yml"
with open(metadata_file, "r") as f:
metadata_raw = f.read()
metadata = yaml.load(metadata_raw)
if "category" in metadata:
problems.append(metadata)
except:
failed.append(problem_name)

problems.sort(key=lambda p: p.get("value"), reverse=True)
print("Grand Total: %d" % len(problems))
print("Category Breakdown:")

maxtitle = max(map(lambda p: len(p.get("title")), problems)) + 3
maxauthor = max(map(lambda p: len(p.get("author")), problems)) + 3

c = Counter(map(lambda p: p.get("category", ""), problems))
categories = sorted(c.items(), key=lambda c: c[1], reverse=True)
for category, count in categories:
print(" %s: %s" % (category, count))
for problem in problems:
if problem.get("category") != category: continue
total += int(problem.get("value"))
print(" %s %s %sp" % (problem.get("title") + " " * (maxtitle - len(problem.get("title"))), problem.get("author") + " " * (maxauthor - len(problem.get("author"))), problem.get("value")))

print ("\nTOTAL VALUE: %d" % total)

print("\nThe following problems failed to parse.")
for title in failed:
if title in [".git"]: continue
print(" %s" % title)
1 change: 1 addition & 0 deletions decode-me/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Someone I met today told me that they had a perfect encryption method. To prove that there is no such thing, I want you to decrypt this [encrypted flag](${encrypted_flag_txt}) he gave me.
Loading

0 comments on commit c19872a

Please sign in to comment.