Skip to content

Pcg32Single state can be brute-forced from three outputs #2

@bgrainger

Description

@bgrainger

Based on this code linked from this article, the internal state of Pcg32Single can be recovered (in just seconds) given three successive values from Pcg32Single.GenerateNext.

(Note that this uses reflection to work around the Pcg32Single code scrambling the seed with the equivalent of pcg_oneseq_64_srandom_r; however, once the internal state has been recovered, all future PRNG values can be predicted. This also doesn't recover the initial seed passed to the constructor; however, that's not required to predict future output.)

ulong Recover(uint x0, uint x1, uint x2)
{
	Pcg32Single pcg = new(0);
	var stateField = pcg.GetType().GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance);
	for (ulong upper = 0; upper < (1ul << 32); upper++)
	{
		var rot = (int) (upper >> 27);
		uint rotx0 = (x0 << rot) | (x0 >> (32 - rot));

		if (((rotx0 ^ upper >> 13) & ~31u) == (uint)(upper << 5))
		{
			uint high_lower_bits = (uint) ((rotx0 ^ upper >> 13) & 31u) << 27;
			for (uint lower = 0; lower < (1u << 27); lower++)
			{
				var candidate = (ulong)upper << 32 | high_lower_bits | lower;
				stateField.SetValue(pcg, candidate);

				if (pcg.GenerateNext() == x0 && pcg.GenerateNext() == x1 && pcg.GenerateNext() == x2)
					return candidate;
			}
		}
	}
	// Can't happen	
	throw new InvalidOperationException();
}

var random = new Random();
var seed = (ulong) random.NextInt64();
var pcg = new Pcg32Single(seed);

var x0 = pcg.GenerateNext();
var x1 = pcg.GenerateNext();
var x2 = pcg.GenerateNext();

Console.WriteLine(Recover(x0, x1, x2));

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions