12

Let's participate in the SHA2017 CTF!

SHA2017 a Jeopardy-style CTF running in the context of the SHA2017 hacker camp and lasts 36 hours. There are currently 100+ participating teams announced on CTFtime.

Basic info:

  • We compete as team sec.se.
  • We communicate over Slack. To get an invitation to the group you can contact Benoit Esnard, HamZa, Xavier59, Arminius or any other active team member. (We will need to know an email address to send the invitation to and a reference to your Security.SE profile.)
  • For questions, join us in the public chat room.

Good luck everyone!

| |
8

WannaFly

(forensics, 100)

We have an image of an ext4 filesystem. It contains some pictures that have been encrypted and blurred by the ransomware WannaFly. Apart from the blurry pictures, there is a Python script called ... and a .bash_history file which shows that ... was executed with the argument Hb8jnSKzaNQr5f7p.

Looking at the Python script, we see that it transforms each input file in the following way:

  • Load it as an image.
  • Encrypt the original image using AES-CFB, using the key passed as an argument, and with an IV which is derived from the time.
  • Blur the image and overlay a bird logo and some text.
  • Overwrite the original file with the blurred image, a newline and the encrypted image in base64 format.

So to recover the original images, we need to extract the part of each file after the last newline, decode it as base64, and decrypt it with the known key and the IV.

The seed for deriving the IV is the current time when the script starts encrypting each file, rounded down to a whole second. That's going to be the timestamp of the file or a little before. The timestamps on the files are less than one second apart on average, so the time seed is likely to be exactly the file's timestamp or one second before.

#!/usr/bin/env python

import random, string, sys, os
from time import time
from Crypto.Cipher import AES
import base64
import textwrap

def get_iv(seed):
    iv = ""
    random.seed(seed)
    for i in range(0,16):
        iv += random.choice(string.letters + string.digits)
    return iv

def encrypt(m, p):
    iv=get_iv()
    aes = AES.new(p, AES.MODE_CFB, iv)
    return base64.b64encode(aes.encrypt(m))

def decrypt(m, p, i):
    aes = AES.new(p, AES.MODE_CFB, i)
    return aes.decrypt(base64.b64decode(m))

def encrypt_image(img):
    data = open(img, 'r').read()
    encrypted_img = encrypt(data, sys.argv[1])
    blurred_img = open('/tmp/sha.png', 'r').read()
    stat = os.stat(img)
    with open(img, 'r+') as of:
        of.write('\0' * stat.st_size)
        of.flush()
    open(img, 'w').write(blurred_img + "\n" + encrypted_img)

def load_file(file_name):
    with open(file_name) as f:
        data = f.read()
        return data[data.rfind('\n')+1:]

def decrypt_file(password, encrypted_file_name):
    basename = os.path.basename(encrypted_file_name)
    mtime = os.stat(encrypted_file_name).st_mtime
    ciphertext = load_file(encrypted_file_name)
    for delta in [0,1]:
        iv = get_iv(mtime - delta)
        print iv
        output_file_name = 'recovered-%d-%s' % (delta, basename)
        out = open(output_file_name, 'wb')
        out.write(decrypt(ciphertext, password, iv))
        out.close()

if __name__ == '__main__':
    if len(sys.argv) <= 1:
        print "Usage: %s PASSWORD FILE..." % sys.argv[0]
    else:
        for encrypted_file in sys.argv[2:]:
            decrypt_file(sys.argv[1], encrypted_file)

The seed time delta turns out to be 0 for all files except one. Looking at the decrypted images, in recovered-0-f09086061f03f080d0851d9154e11653.png, we see some text over the pony image which is the flag: flag{ed70550afe72e2a8fed444c5850d6f9b}.

Stack Overflow

(crypto, 100)

We have some broken encryption code in Python with PyCrypto. (Source)

from Crypto.Cipher import AES
...
crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=lambda: secret) 

What a badly-designed API — writing broken crypto code shouldn't be that easy! This code is using CTR with a constant counter. This means that there is a 16-byte string m = AES_enc(key, secret) such that each 16-byte block b[i] of the file is encrypted as b[i] xor m. All we have to do to decrypt the file is guess 16 consecutive bytes of it, which will give us the mask to xor with.

We start by guessing that the file begins with %%PDF-1., which gives us the first 8 bytes of the 16-byte mask. Then look at the partially-decrypted output, find partially-corrupted PDF operators such as /Contents, /MediaBox, etc. and work out the remaining bytes in the mask to rectify those operators.

#!/usr/bin/env perl
use warnings;
use strict;
my $guess1 = "%PDF-1.3 \n1 0 ob";
sysread STDIN, $_, 16;
my $mask = $_ ^ $guess1;
do {
    print STDOUT ($_ ^ $mask)
} while (sysread STDIN, $_, 16);

ONE DOES NOT SIMPLY COPY CRYPTO CODE FROM STACK OVERFLOW FLAG{15AD69452103C5DF1CF61E9D98893492}

Secure Login

(crypto, 200)

There is a server which responds to signed commands. The command we want is 2: “Collect flag”. This command requires a signed ticket whose plaintext starts with ticket:admin|root|.

A signed ticket is generated by encoding the ticket string as an integer $m$ and calculating $m^d \bmod n$ where $d$ is a secret. In other words, this is textbook RSA, and we have the public key only. It's a 2048-bit key with $e=65537$.

We can obtain the signature of messages in two forms:

  • Messages that start with the string ticket:user| and contain another |, using command 1 “Register yourself as a user”.
  • Messages that start with the byte 0xff, using command 3 “Sign a message”.

We want to obtain the signature of a message that starts with ticket:admin|root|, i.e. a message of the form $0x7469636b65743a61646d696e7c726f6f747c \times 2^{8b} + a$ where $a < 2^{8b}$.

Let $u = $ticket:user|| and $v = 0xffffffffffffffd3e3bae71d30e6eac5d3ad$. Then $u \times v = $ticket:admin|root|l\xb3\xf0m\xca1\x16I\xe4\x0edS\xcc.

Use command 1 on an empty user name and full name to get $u^d$. Use command 2 on ffffffffffffd3e3bae71d30e6eac5d3ad ($v$ without the leading 0xff) to get $v^d$. Multiply them; the result is

0x6e08962dabfd99df74dcff5f0685be2950fa030a2f3ea59f8f836f80eca71aee19027bdee3e0d7828f3b651efc1b21878254d6d69aa8776405d4bd3f88d9cdda3b538dc2fec25bbd3974287187adde132e389ec7a3b468d499a768967fa390e9b0b1712112d131940032b32c31c19c095f36277fd1b18b21ec04869f526f6eb405262d4395f24c374a4450bb095c9f764d2c582627feb2da6576e6652afe8018533c414276303fb50881d848ce73f404907e00db220e040ff81b658a4cdc9233ea379c2538c6fa88f95643b32dbf8af6fc54feaa25bddf4c76361af2d2fbd8fb3895f08bd6c177b79c702c24631c206dbee19b5f924b521ff937708355e4041abc2f0f73b80e6187a5b1cf68cc9b4b06cfb9caeb1cb03823e0f409a751f5ef68a5978c32522bafd6d7b9751c7e4d63faa608847dc461def7078d889ee02a92271d21fbb9cf602ee15937b1df87e36ad7f36e38af5ddb63ec074b33293d9ee6f1356083da7143149a3f49a1798f146c2d22a390f337ffe24d191ea5ac4b8107651a9d2af6ba2d37092b40be0d51842a5a6c91ca74997b995c40955480fce657a352186a8b98027c6e7bf67441ccf62f10fd9ea2569c4a823b43462f7bdaa1c83a561e0152a584dd8b737c69ee9f2d3ef0fa480e40a06555bb89ae60bd266395369f5c4aa5ae5aee936e4312a53bb33ff944e01c35078bea27b3a28f38a76be7c

Enter that at command 2 without the leading 0x to get the flag

Here you go!
flag{8f898e19de410591acbcdbfae798d603}
| |

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .