This challenge involves an old version of CS:GO VScript, which is vulnerable to a UAF bug and a type confusion bug.
The sort function of squirrel array is
sqbaselib.cpp, which will call
r index passed into
_qsort is fixed at the beginning, so by abusing
array.resize in compare function, we can retrieve dangling reference to freed objects through compare function parameters.
By freeing a string then overlap it with an array, the
_len field of the freed
SQString object will be overwritten by the
_sharedstate field of the newly created
SQArray. It’s a pointer so the value will be very large, and we can use the dangling string to do arbitrary reading over a large heap space after it.
_regexp_* functions in
SQRex object from the current object using
typetag parameter is
0, means that it will not check for type mismatch. So we can call
_regexp_* functions using any
instance object (examples: self-defined classes, external library classes like CS:GO script classes).
As we have a long string by using UAF bug above, we can just spray a lot of
CScriptKeyValues and find one of them using last 2 bytes of
SQInstance::vtable as they will not be affected by Windows ASLR, then use confusion to watch for changes to
_userpointer field. But there are other
instance objects too, and we have no way to be sure that it’s a
tostring method will return the type name and the address in memory of any object. For number and string it will just return the value. But we overlapped the freed string with an array, so we can get address of it by calling
tostring on the array. We can keep allocate new
CScriptKeyValues object until we get one that lies after our long string and in the range that we can read its data. I won’t go into detail of Source Engine heap in this writeup, but most of the time we will get a satisfied object without triggering Squirrel timeout watchdog.
By reading the
CScriptKeyValues object, we can get these values:
SQInstance::vtable, which can be used to calculate
vscript.dllbase address for ROP gadgets
_userpointerof that object
My approach is to use a CS:GO script class,
CScriptKeyValues. Squirrel will panic if you attempt to modify the prototype after 1 instance of a class has been created. Since in map loading, there’re no instance of this class would be created, we can modify its prototype:
When we call any method of a CS:GO script class,
vsquirrel.cpp will be called. It will access
_userpointer field of the object to get binding information:
_regexp_constructor will create a new
SQRex class and store it in
_userpointer field. That means we can control
pContext. Below is
pClassDesc field overlaps with
_bol field. When we call
_bol field will be set to the beginning of
str. So we can craft a fake
ScriptClassDesc_t object using a string. Below is
We can craft a fake
IScriptInstanceHelper object to control the virtual method table.
Fortunately, Squirrel string is not null-terminated, so we don’t have to worry about null bytes.
In conclusion, the fake object will look like this:
Thanks ALLES! team for organizing a great CTF with awesome challenges, and allowed late submission of 🔥 challenges.
Source Engine is a mature engine with a lot of functions, and use a lot of unsafe memory code. With the f.mdact that any people can host dedicated servers, it’s a huge attack surface. It’s sad that Valve never bothers fixing security bugs in the engine quickly. I really hoped that they will pick up the pace after secret club’s callout, but seems like they will never do that.
See POC here.