Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

Random (crypto, 100p)

We were given this simple C++ code:

#include <fstream>
#include <iomanip>
#include <iostream>
#include <random>
#include <string>

using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::string;

[[noreturn]] void fatal_error(const string& msg)
{
        cout << msg << endl;
        exit(0);
}

class RandStream
{
        std::random_device rd;
        std::mt19937 gen;

public:
        RandStream() : rd(), gen(rd()) {}

        unsigned int NextUInt()
        {
                return gen();
        }
};

int main()
{
        cout << "Let's see if you can predict Mersenne Twister output from just"
             << " six values!" << endl;
        cout << "btw. You have only 5 seconds." << endl;

        RandStream rand{};
        for (int i = 0; i < 6; i++)
                cout << std::hex << std::setfill('0') << std::setw(8) << rand.NextUInt()
                     << endl;

        for (int i = 0; i < 5; i++)
        {
                unsigned int num;
                cin >> std::hex >> num;
                if (num != rand.NextUInt())
                        fatal_error("Wrong!");
        }

        cout << "Good work!" << endl;

        ifstream f("flag.txt");
        string flag;
        f >> flag;
        if (f.fail())
                fatal_error("Reading flag failed, contact admin");
        cout << flag << endl;
}

This line may look strong:

RandStream() : rd(), gen(rd()) {}

But in fact it's completely broken (mt is seeded with only 32bits of entropy). So it's enough to bruteforce the seed to get the flag.

According to organisers we were supposed to "cache" something to be "fast". Well, we're more lazy than that, so we just guessed randomly until we did it. With tiny complication, beacuse of 5 second timeout, code looked like this:

def stuff():
    s = socket.socket()
    s.connect(('random.hackable.software', 1337))
    data = s.recv(9999)
    sample = data.split('\n')[2]
    out = Command(['./a.out', sample, '0']).run(timeout=5)  # class stolen from stackoverflow
    if out:
        print '---'
        print out
        print '---'
        s.send(out + '\n')

        print s.recv(9999)

def main():
    while True:
        if stuff():
            break

And a.out was compiled version of minimally modified given program:

class RandStream
{
        //std::random_device rd;
        std::mt19937 gen;

public:
        RandStream(int i) : gen(i) {}

        unsigned int NextUInt()
        {
                return gen();
        }
};

int main(int argc, char *argv[])
{
    unsigned int sought = std::strtoul(argv[1], NULL, 16);
    unsigned int start = std::strtoul(argv[2], NULL, 16);
    for (unsigned int i = start; i < 0xFFFFFFFE; i++) {
        RandStream rand(i);
        if (rand.NextUInt() != sought) {
            continue;
        }
        for (int i = 0; i < 5; i++) {
            rand.NextUInt();
        }
        for (int i = 0; i < 5; i++) {
            cout << std::hex << rand.NextUInt() << ' ';
        }
        return 0;
    }
}

Just good, plain, old brute force.