Skip to content

Commit

Permalink
Initial.
Browse files Browse the repository at this point in the history
  • Loading branch information
mzhang28 committed Feb 21, 2018
0 parents commit de79e4d
Show file tree
Hide file tree
Showing 335 changed files with 85,037 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
11 changes: 11 additions & 0 deletions .netserv/redirect.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
service {name}
{{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
redirect = {redirect}
bind = 0.0.0.0
port = {port}
}}
11 changes: 11 additions & 0 deletions .netserv/server.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
service {name}
{{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
server = {exe_path}
bind = 0.0.0.0
port = {port}
}}
611 changes: 611 additions & 0 deletions .netserv/services

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions .netserv/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import os
import traceback
import sys

import yaml

with open(os.path.join(os.path.dirname(__file__), "services")) as f:
base = f.read()
with open(os.path.join(os.path.dirname(__file__), "redirect.template")) as f:
redirect_template = f.read()
with open(os.path.join(os.path.dirname(__file__), "server.template")) as f:
server_template = f.read()

problem_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
problem_names = os.listdir(problem_dir)

if __name__ == "__main__":
service = None
if len(sys.argv) > 1:
service = sys.argv[1]
new_services = []
os.system("rm -f /etc/xinetd.d/easyctf/*") # probably redo this later
for name in problem_names:
if name.startswith("."):
continue
if service and name != service:
continue
problem_folder = os.path.join(problem_dir, name)
if not os.path.isdir(problem_folder):
continue
try:
curr = os.getcwd()
os.chdir(problem_folder)
files = os.listdir(problem_folder)
if "problem.yml" not in files:
continue
with open(os.path.join(problem_folder, "problem.yml")) as f:
metadata = yaml.load(f)
if metadata:
docker = metadata.get("docker")
if docker and ("Dockerfile" in files):
os.system("docker build -t {} .".format(name))
args = docker.get("args", "")
os.system("docker rm -f $(docker ps -q --filter='ancestor={name}')".format(name=name))
os.system("docker run --detach=true {args} {name}".format(args=args, name=name))
net = metadata.get("net")
if net:
port = net.get("port")
exe = net.get("server")
if exe:
exe_path = os.path.join(problem_folder, exe)
new_services.append("{} {}/tcp".format(name, port))
with open("/etc/xinetd.d/easyctf/{}".format(name), "w") as f:
f.write(server_template.format(name=name, exe_path=exe_path, port=port))
print("installed {}".format(name))
redir = net.get("redirect")
if redir:
new_services.append("{} {}/tcp".format(name, port))
with open("/etc/xinetd.d/easyctf/{}".format(name), "w") as f:
f.write(redirect_template.format(name=name, redirect=redir, port=port))
print("installed {}".format(name))
os.chdir(curr)
except:
traceback.print_exc(file=sys.stderr)

with open("/etc/services", "w") as f:
f.write(base + "\n".join(new_services))

os.system("/etc/init.d/xinetd restart")
156 changes: 156 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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:
- `author`: Your username on the EasyCTF website.
- `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.

## Autogen

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()
})
```

`files` contains a dictionary mapping filenames to either `StringIO` or `BytesIO` objects. Note that we are returning `gen_b` **with** `()`. 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: michael
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.

## Programming

For programming challenges, you'll need a separate `grader.py` and `generate.py` that behave a bit differently.

`generator.py` is responsible for generating each test case. It reads a single input: the test case number from stdin (you can use `int(input())` for that). The output (from stdout) will be passed to the user program as input.

`grader.py` acts like a "correct solution". It takes the same input that the user does (the output of generator) and produces the "correct answer" for that test case. The output of this program will be compared to the output of the user's program to determine correctness.

Overall the flow looks generally like

```ml
test case # ->
-> generator.py ->
-> (user's program) -> (user's answer) -+
-> grader.py -> (correct answer) -------+
v
compare!
```

## Net Problems

If you need to serve a server program (**RAW TCP NOT WEB**), then include a `net` section in your `problem.yml` like this:

```yml
title: "Intro: TCP Server"
author: michael
category: Intro
value: 30
autogen: true
net:
server: server.py
port: 12481
```
This will serve using `server.py` (make sure it has the hash bang at the top) on port 12481, using xinetd. Read user input from stdin and send data to the user via stdout, xinetd will redirect this over sockets.

See the designated xinetd import tool for more details.

## Web Problems

more info incoming...
2 changes: 2 additions & 0 deletions adder/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
all: adder.cpp
g++ -o adder adder.cpp
Binary file added adder/adder
Binary file not shown.
55 changes: 55 additions & 0 deletions adder/adder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
//y0u_added_thr33_nums!
char * gen (int thing){
char *flag = (char*) (malloc(22 * sizeof(char)));
flag[0] = 'y';
flag[1] = (thing % 7) + 48;
flag[2] = thing - 1220;
flag[3] = '_';
flag[4] = flag[2] - 20;
flag[5] = flag[4] + 3;
flag[6] = flag[5];
flag[7] = 'e';
flag[8] = flag[6];
flag[9] = flag[3];
flag[10] = 116;
*(flag + 11) = flag[10] - 12;
*(flag + 12) = 'r';
flag[13] = '3';
flag[14] = '3';
flag[15] = flag[3];
flag[16] = 'n';
flag[17] = flag[2];
flag[18] = flag[16] - 1;
flag[19] = 's';
flag[20] = 33;
flag[21] = '\n';
return flag;
}

void print_ptr(char * flag){
for(int i = 0; i < 21; i++){
printf("%c", *(flag+i));
}
}

int main(){
int first = 0;
int second = 0;
int third = 0;
cout<<"Enter three numbers!\n";
cin>>first>>second>>third;
char * flag = gen(first+second+third);
if(first + second + third == 1337){
cout<<"easyctf{";
print_ptr(flag);
printf("}\n");
}
else
cout<<"nope.\n";
free(flag);
return 0;
}
1 change: 1 addition & 0 deletions adder/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This program adds numbers. Find the flag! [adder](${adder})
4 changes: 4 additions & 0 deletions adder/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def grade(autogen, key):
if(key.find("y0u_added_thr33_nums!") != -1):
return True, "Yay it adds correctly!"
return False, "Try again?"
8 changes: 8 additions & 0 deletions adder/problem.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
title: Adder
author: soup
category: Reverse Engineering
value: 80
hint: Adds numbers.
autogen: false
files:
- adder
2 changes: 2 additions & 0 deletions adder/revise.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
put this at at least 100 points (i'd give 125), because for people who probably don't even know how to run this, this is DEFINITELY not worth only 15 points
also add a makefile for this, even though it's already compiled, just in case changes are made
61 changes: 61 additions & 0 deletions aes/aes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python3

from Crypto import Random
from Crypto.Random import random
from Crypto.Cipher import AES
from binascii import *

flag = "easyctf{beswymplirxwlhfsoupvqrwdqeabfrkdxgwhdawmvaklvdwxty}"

BLOCK_SIZE = 16

# Pad m using PKCS#7
def pad(m):
p = BLOCK_SIZE - len(m) % BLOCK_SIZE
return m + p * bytes([p])

# AES encrypt
def encrypt(key, message, mode=AES.MODE_CBC):
IV = key # totally a good idea
aes = AES.new(key, mode, IV)
return hexlify(IV + aes.encrypt(pad(message))).decode()

key = Random.get_random_bytes(16)
print("The key for this session is: {}".format(key))
print("Input 256 different plaintexts such that:")
print("\t - Each is a binary string")
print("\t - Each has length 256")
print("\t - Input can not be all 0's or all 1's")
print("\t - Let Pi denote the ith plaintext input. Then P0 ^ P1 ^ ... ^ P255 = encrypt(key, P0) ^ encrypt(key, P1) ^ ... ^ encrypt(key, P255)")

xor_1 = 0
xor_2 = 0

inputs = set()
for _ in range(256):
i = input("Input plaintext {}:\t".format(_))

if i in inputs or len(i) != 256 or not set(i) == set('01'):
print("Input error")
xor_1 = 0
xor_2 = 1
break

inputs.add(i)

input_dec = int(i, 2)
xor_1 ^= input_dec

t = hex(input_dec).lstrip("0x").rstrip("l")
if len(t) & 1:
t = unhexlify("0" + t)
else:
t = unhexlify(t)

xor_2 ^= int(encrypt(key, t), 16)

if xor_1 == xor_2:
print(flag)
else:
print("Try again")

Loading

0 comments on commit de79e4d

Please sign in to comment.