Unturned Internal HWID Spoofer

2024-03-30 17:37:35
Go back.


HWID refers to "hardware identifier", which applications will use to fingerprint your computer. This could be software (such as registry keys) or hardware (serial numbers).
Before reading further, understand that this is not for Battleye, rather the actual Unturned client.

How Unturned HWID

Obviously the first step is to see how they actually fetch your HWID from the computer. We can do this very easily as the game is in C#, and shipped as a C# application. Almost all of the game source code can be found in Assembly-CSharp.dll. Now lets find the function that creates the HWID.

public static byte[][] GetHwids()
{
	byte[][] array = LocalHwid.InitHwids();
	if (array == null)
	{
		array = new byte[][]
		{
			LocalHwid.CreateRandomHwid()
		};
	}
	return array;
}

Very quickly we can spot an easy way to achieve this. We can just hook InitHwids and return null. This will force the game to create a random HWID for us.

Making It Better

After using this for awhile, I noticed a random modded server actually detected the randomly generated HWID made from the CreateRandomHwid function. How do we fix his? Lets remove the hook from InitHwids and see what our real HWID is.

As we can see, there is multiple arrays with 4 byte hex values, and some more 8 byte values further down off screen. Lets see if we can randomize these ourselves. First, we have to find a creative way of changing the HWID ourselves as it gets fetched. We can see the function calls InitHwids, so lets take a look at what that does.

private static byte[][] InitHwids()
{
	List list = LocalHwid.GatherAvailableHwids();
	if (list == null || list.Count < 1)
	{
		return null;
	}
	if (list.Count > 8)
	{
		byte[][] array = new byte[8][];
		for (int i = 0; i < 8; i++)
		{
			int randomIndex = list.GetRandomIndex();
			array[i] = list[randomIndex];
			list.RemoveAtFast(randomIndex);
		}
		return array;
	}
	return list.ToArray();
}

Okay so we can see it calls GatherAvailableHwids. No other HWID stuff is gathered inside this function, so lets see what is inside GatherAvailableHwids.

private static List GatherAvailableHwids()
{
	List list = new List();
	LocalHwid.GatherPlayerPrefsEntry(list);
	LocalHwid.GatherConvenientSavedataEntry(list);
	LocalHwid.GatherWindowsEntry(list);
	return list;
}

Awesome, we can see that this function calls 3 other functions to actually retrieve the HWID from the computer.

My Solution

I hooked GatherWindowsEntry. Once my hook was called, I would call the original GatherWindowsEntry and then loop the list passed into the function as an argument, and wrote randomized 8 byte values. This list is all of our HWID information.

for (size_t i = 0; i < size; i++)
{
	uint64_t HWID = *(uint64_t*)(list1 + 0x20 + (i * 0x08));

	if(!valid_ptr((void*)HWID))
		continue;

	for (size_t x = 0; x < 10; x++)
	{
		uint64_t* identifier = (uint64_t*)(HWID + 0x20 + (x * 0x08));

		if (!valid_ptr((void*)identifier))
			continue;

		// no identifier here :(
		if (!*identifier)
			continue;

		*identifier = ((uint64_t)rand() << 16) | rand();
	}
}

As you can see, I did only wrote the first 10 entries. This is because when I looked at the runtime memory, rarely it was over this. Either way, this did not break the desired effect.

Conclusion

Because we randomized the information ourselves, the modded server that blocked the original solution can no longer detect our new solution. Woohoo 🎉!