We've discovered this vulnerability in RakNet versions as early as 3.711 (released on 11/20/2009), Roblox updated to a RakNet version newer than this in the middle of 2010. We have updated the affected dates & our patcher to protect these clients
A heap OOB write in RakNet's split-packet reassembly logic (misattributed to DoS, reported to the RakNet project by 8ARTEK0V0 & fixed in 2014) that affects Legacy Roblox binaries from 2010 through mid-late 2017 due to no one merging the upstream fix.
InternalPacket * ReliabilityLayer::BuildPacketFromSplitPacketList( SplitPacketChannel *splitPacketChannel, CCTimeType time )
{
#if PREALLOCATE_LARGE_MESSAGES==1
InternalPacket *returnedPacket=splitPacketChannel->returnedPacket;
RakNet::OP_DELETE(splitPacketChannel, __FILE__, __LINE__);
(void) time;
return returnedPacket;
#else
unsigned int j;
InternalPacket * internalPacket, *splitPacket;
int splitPacketPartLength;
// Reconstruct
internalPacket = CreateInternalPacketCopy( splitPacketChannel->splitPacketList[0], 0, 0, time );
internalPacket->dataBitLength=0;
for (j=0; j < splitPacketChannel->splitPacketList.Size(); j++)
internalPacket->dataBitLength+=splitPacketChannel->splitPacketList[j]->dataBitLength;
splitPacketPartLength=BITS_TO_BYTES(splitPacketChannel->firstPacket->dataBitLength);
internalPacket->data = (unsigned char*) rakMalloc_Ex( (size_t) BITS_TO_BYTES( internalPacket->dataBitLength ), _FILE_AND_LINE_ );
internalPacket->allocationScheme=InternalPacket::NORMAL;
for (j=0; j < splitPacketChannel->splitPacketList.Size(); j++)
{
splitPacket=splitPacketChannel->splitPacketList[j];
memcpy(internalPacket->data+splitPacket->splitPacketIndex*splitPacketPartLength, splitPacket->data, (size_t) BITS_TO_BYTES(splitPacketChannel->splitPacketList[j]->dataBitLength));
}
for (j=0; j < splitPacketChannel->splitPacketList.Size(); j++)
{
FreeInternalPacketData(splitPacketChannel->splitPacketList[j], _FILE_AND_LINE_ );
ReleaseToInternalPacketPool(splitPacketChannel->splitPacketList[j]);
}
RakNet::OP_DELETE(splitPacketChannel, __FILE__, __LINE__);
return internalPacket;
#endif
}
There is no check on splitPacket->splitPacketIndex*splitPacketPartLength
Roblox never pulled the upstream fix that fixed it until mid-late 2017, where we observe no crashes/OOB writes. The proper fix, if you had source, would be to pull the ReliabilityLayer fixes, right?
Well, the fix in RakNet actually introduces a bug where if packets are received out of order, it causes the packet to reassemble into a malformed packet. We are recommending to also merge Garry Newman's fix for the bug introduced to avoid any issues.
We don't have the source to these clients so we have to improvize. We've made a novel patcher that runs in your browser! to fix this issue with mid-function-hooking in a different (security oriented) manner.
The patch is a mid-function hook inside the memcpy loop of BuildPacketFromSplitPacketList. This is what it basically does.
for (j=0; j < splitPacketChannel->splitPacketList.Size(); j++)
{
splitPacket=splitPacketChannel->splitPacketList[j];
if (splitPacket->splitPacketIndex * splitPacketPartLength
+ (size_t) BITS_TO_BYTES(splitPacket->dataBitLength)
> (size_t) BITS_TO_BYTES(internalPacket->dataBitLength))
continue;
memcpy(internalPacket->data+splitPacket->splitPacketIndex*splitPacketPartLength, splitPacket->data, (size_t) BITS_TO_BYTES(splitPacketChannel->splitPacketList[j]->dataBitLength));
}
This is a security fix. Our patch prevents it from writing past the buffer.
In the PoC, we say This PoC will kick you out of the game when ran, but why? And how does this affect reverse proxy services?
In RakNet, when an unverified sender starts sending packets that isn't a connection request, your IP gets temporarily banned for sending nonsense data. You can see this when running the PoC, you will be the only one to get kicked if the server is port forwarded.
This is not the vulnerability, and is intended in RakNet. Although, due to how reverse proxies work, that "temporarily banned" bans everyone connected for a few seconds, until they can join again.
Our patcher does not resolve this, we cannot make a general solution because every reverse proxy is different. Our patcher fixes the heap OOB write vulnerability.