Tuesday, 13 August 2019

Cryptopals Challenge 23: Clone an MT19937 RNG from its output

I've recently gotten the cryptography bug.

I would highly recommend working through the challenges at http://cryptopals.com. The challenges give insight and practice into real attacks on weak cryptosystems.

Personally, I'm onto set 4, but I skipped the Mersenne Twister (MT) RNG  to get there. Today I went back to those challenges and in this blog post I present my solution to challenge 23.

The Mersenne Twister generates Pseudo Random Numbers. It would be nice as an attacker to be able to predict future numbers by looking only at the earlier output of the MT Random Number Generator. 

Firstly, an earlier challenge is to simply get an MT RNG working. I stole the code from https://github.com/james727/MTP. Is it bad that I copied the code? No. You will see there is much work needed to be done to break the MT RNG.

The key insight to the MT1997 RNG is that the complete internal state consists of 624 32-bit integers. If you can clone these integers, you can predict any future number generated by the RNG.

Another key insight is that each of these internal integers directly corresponds one-to-one to each one of the RNG outputs in sequential order. That is, given an array of 624 integers representing the state, state_1 corresponds random number 1, state_2 corresponds to random number 2 and so forth. After 624 numbers, the entire state gets 'twisted' and the process starts again.

The final insight into breaking the MT1997 RNG, is that the correspondence of each integer in the state to the random number output is a reversible function. That is, given an output from the RNG, you can reverse this output and reveal the associated integer in the state.

To put this all together, if you reverse the 624 integers that make up the state, you simply copy the state into your own MT1997 RNG and you have a working clone that can predict any future "random" number.

Like all things, the devil is in the details.

Remember the correspondence between the state and the RNG output. I will present to you the temper function in MT1997 which is the function that takes the integer state and produces an RNG output.

  def temper(state):
        y = state
        y = y ^  (y >> 11)
        y = y ^ ((y <<  7) & 0x9D2C5680))
        y = y ^ ((y << 15) & 0xEFC60000))
        y = y ^  (y >> 18)
        return y

This temper function takes in an integer from the state, and transforms it into the RNG output. However, remember our insight - this is a reversible function.

But how do we reverse it? Is there an analytical solution? Yes there is. But it is not immediately obvious to me in how to derive this. I will go for a simpler solution.

I will use an SMT solver (python-z3) to reverse this temper function.
 
from z3 import *

def untemper(out):
        y1 = BitVec('y1', 32)
        y2 = BitVec('y2', 32)
        y3 = BitVec('y3', 32)
        y4 = BitVec('y4', 32)
        y = BitVecVal(out, 32)
        s = Solver()
        equations = [
            y2 == y1 ^ (LShR(y1, 11)),
            y3 == y2 ^ ((y2 << 7) & 0x9D2C5680),
            y4 == y3 ^ ((y3 << 15) & 0xEFC60000),
            y  == y4 ^ (LShR(y4, 18))
        ]
        s.add(equations)
        s.check()
        return s.model()[y1].as_long()

This function reverses the temper function and we can now recover the state from the RNG output.

Let's put this all together and demonstrate a complete attack. The following code will generate 1000 "random" numbers. I will use the first 624 numbers to reverse the complete state of the RNG. I clone the state and can now predict the remaining numbers.

Thanks for reading. Hopefully you and I can use the skills developed from completing the cryptopals challenges into attacking real-world but weak crypto implementations.

 
#!/usr/bin/python

from z3 import *

def untemper(out):
#        y = state
#        y = y ^  (y >> 11)
#        y = y ^ ((y <<  7) & 0x9D2C5680))
#        y = y ^ ((y << 15) & 0xEFC60000))
#        y = y ^  (y >> 18)
#        return y

        y1 = BitVec('y1', 32)
        y2 = BitVec('y2', 32)
        y3 = BitVec('y3', 32)
        y4 = BitVec('y4', 32)
        y = BitVecVal(out, 32)
        s = Solver()

        equations = [
            y2 == y1 ^ (LShR(y1, 11)),
            y3 == y2 ^ ((y2 << 7) & 0x9D2C5680),
            y4 == y3 ^ ((y3 << 15) & 0xEFC60000),
            y  == y4 ^ (LShR(y4, 18))
        ]
        s.add(equations)
        s.check()
        return s.model()[y1].as_long()

class mersenne_rng_cracker(object):
    def __init__(self, state):
        self.state = state
        self.f = 1812433253
        self.m = 397
        self.u = 11
        self.s = 7
        self.b = 0x9D2C5680
        self.t = 15
        self.c = 0xEFC60000
        self.l = 18
        self.index = 624
        self.lower_mask = (1<<31 def="" for="" i="" in="" range="" self.upper_mask="1<<31" self="" temp="self.int_32((self.state[i]&self.upper_mask)+(self.state[(i+1)%624]&self.lower_mask))" temp_shift="temp" twist="">>1
            if temp%2 != 0:
                temp_shift = temp_shift^0x9908b0df
            self.state[i] = self.state[(i+self.m)%624]^temp_shift
        self.index = 0

    def get_random_number(self):
        if self.index >= 624:
            self.twist()
        y = self.state[self.index]
        y = y^(y>>self.u)
        y = y^((y<>self.l)

        self.index+=1
        return self.int_32(y)

    def int_32(self, number):
        return int(0xFFFFFFFF & number)

class mersenne_rng(object):
    def __init__(self, seed = 5489):
        self.state = [0]*624
        self.f = 1812433253
        self.m = 397
        self.u = 11
        self.s = 7
        self.b = 0x9D2C5680
        self.t = 15
        self.c = 0xEFC60000
        self.l = 18
        self.index = 624
        self.lower_mask = (1<<31 for="" i-1="" i="" in="" range="" seed="" self.f="" self.int_32="" self.state="" self.upper_mask="1<<31" state="" update="">>30)) + i)

    def twist(self):
        for i in range(624):
            temp = self.int_32((self.state[i]&self.upper_mask)+(self.state[(i+1)%624]&self.lower_mask))
            temp_shift = temp>>1
            if temp%2 != 0:
                temp_shift = temp_shift^0x9908b0df
            self.state[i] = self.state[(i+self.m)%624]^temp_shift
        self.index = 0

    def get_random_number(self):
        if self.index >= 624:
            self.twist()
        y = self.state[self.index]
        y = y^(y>>self.u)
        y = y^((y<>self.l)

        self.index+=1
        return self.int_32(y)

    def int_32(self, number):
        return int(0xFFFFFFFF & number)

def crack_mt(numbers):
    state = []
    for n in numbers[0:624]:
        state.append(untemper(n))
    rng = mersenne_rng_cracker(state)
    for n in numbers[624:]:
        p = rng.get_random_number()
        if p != n:
            print("FAILED")
            return
        print(n, p)
    print("SUCCESS!")

if __name__ == "__main__":
    numbers = []
    seed = 3521569528
    rng = mersenne_rng(seed)
    for i in range(1000):
        n = rng.get_random_number()
        numbers.append(n)
    crack_mt(numbers)




Saturday, 10 August 2019

Linux Heap House of Force Exploitation


In this paper, I introduce the reader to a heap metadata corruption against a recent version of the Linux Heap allocator in glibc 2.27. The House of Force attack is a known technique that requires a buffer overflow to overwrite the top chunk size. An attacker must then be able to malloc an arbitrary size of memory. The result is that it is possible to make a later malloc return an arbitrary pointer. With appropriate application logic, this attack can be used in exploitation. This attack has been mitigated in the latest glibc 2.29 but is still exploitable in glibc 2.27 as seen in Ubuntu 18.04 LTS.

Linux Heap House of Force Exploitation.PDF

Tuesday, 6 August 2019

Linux Heap Calloc Exploitation part 2

In this paper, I introduce the reader to a heap metadata corruption against the most current version of the Linux Heap allocator. Normally, calloc will allocate data and zero out the memory before returning a pointer to it. An attacker that can overflow from one chunk into a free chunk in a fast bin can force calloc to return uninitialised data. This information leak could be utilised to defeat ASLR or expose sensitive information.

Linux Heap Calloc Exploitation part 2.PDF

Linux Heap Calloc Exploitation

In this paper, I introduce the reader to a heap metadata corruption against a recent version of the Linux Heap allocator before the introduction of the tcache. Normally, calloc will allocate data and zero out the memory before returning a pointer to it. An attacker that can overflow from one chunk into a free chunk can force calloc to return uninitialised data. This information leak could be utilised to defeat ASLR or expose sensitive information.


Linux Heap Calloc Exploitation.PDF 

Monday, 5 August 2019

Linux Heap Overlapping Chunks Exploitation

In this paper, I introduce the reader to a heap metadata corruption against the current Linux Heap Allocator, ptmalloc. An attacker that can overflow from one chunk into the next allocated chunk can force ptmalloc to return overlapping allocations. Given the appropriate application logic, this can lead to exploitation.

This attack is known and is documented in various outlets.

Linux Heap Overlapping Chunks Exploitation.PDF

Saturday, 3 August 2019

Linux Heap Fast Bin Double Free Exploitation


In this paper, I introduce the reader to a heap metadata corruption against the current Linux Heap Allocator. An attacker that forces the application to perform a double free can manipulate it to make malloc return an arbitrary pointer in one instance and a duplicate pointer in another instance. Given the appropriate application logic, this can lead to exploitation.

Linux Heap Fast Bin Double Free Exploitation.PDF

Exploiting the Lorex 2K Indoor Wifi at Pwn2Own Ireland

Introduction In October InfoSect participated in Pwn2Own Ireland 2024 and successfully exploited the Sonos Era 300 smart speaker and Lor...