Category Archives: reversing

Rollback of VAC module family 20151204-172540

The latest VAC module family (20151204-172540) has seemingly been rolled back. I’m not sure exactly when that happened, because this is the first live dump I’ve done since that module was released. I did end up looking into the updates/changes but didn’t bother posting them here because they were so predictable it wasn’t worth the time. But since we’re on the topic anyway, it basically added further support for scanning x64 process/modules to the 20151123-182038 (originally 20150609-214236) family. It might have been cool/interesting, if it wasn’t so predictable…

New VAC module family 20151204-172540

I’ve finally finished my move to the USA and have internet connectivity again. Seems in my absence there has been another minor update to what was originally (since my notes started anyway) the 20150609-214236 family. You may also recall the previous update to this family.

Not sure what’s changed yet. I will probably look into it, although VAC is getting boring so maybe not (depends if I can find something else that interests me, and right now I can’t). At any rate, if I don’t do it in the next few days it will probably be a while because my holidays end this week.


New VAC module family 20151123-182038

Tiny but interesting update to the 20151117-153712 family which removes the 64-bit module enumeration functionality and adds an extra check to scan 1 so it only attempts to return module info for 32-bit processes.

I’m not sure why the module enumeration code was removed. It was only run if a certain flag was set, and that flag was never set during any of the calls in this module as far as I can tell. The rest of the function still remains, including another unused branch, so it’s not just the optimizer stripping dead code…


VAC module family 20151117-153712 overview

Another pretty simple update, this time for the 20150609-214236 family. The most interesting change is that the module now uses the new 64-bit capable process info class which I first described here. Basically that means more scanning is now possible against native 64-bit processes (scans 1, 2, and 4 specifically are using this process info class). This support however does not yet extend to things like the PeFile class, and there are other areas too which are limited (e.g. always assuming a pointer is 32-bits), so there’s still work to be done before VAC’s 32-bit and 64-bit detection capabilities are equivalent. The process information class has also been updated to provide some bools which indicate whether the caller (i.e. Steam/SteamService) is running under WoW64 and whether the target (i.e. the thing being scanned) is not running under WoW64. These flags are now reported back in the first scan (which you will remember is responsible for reporting information about the modules loaded in a process). There’s a few minor changes, like to the first scan so that it now reports back the starting address for the module enumeration, and to the process info class so that it only performs module enumeration if a certain flag is set (whilst this does affect the functionality of two scans, it’s limited to flags which appear to be unimportant except perhaps for easily identifying modules which have been unlinked from the PEB). All the process information class changes can also be found in the other recent VAC update. Lastly, there is some extra error checking and reporting in the function which opens files by VSN/FileId. Not sure why… Perhaps failure to open a file by VSN/FileId causes a VAC authentication error, and they want more information to diagnose those cases? That’s purely speculation though.

VAC module family 20151117-153737 overview

Pretty simple update for the 20151021-211850 family. Replaces the broken 2nd scan with a new and completely different one. The new scan returns very basic information about a file (a copy of the path, whether it can be successfully opened and parsed as a PE file, the file size, and its VSN and file index), and also the VSNs and file indexes of each parent directory in the path (up to a maximum of 16).

New VAC module family 20151117-153737

After noticing the first new module I decided to do a full dump, and it turns out there’s another updated module. This time it’s an update for the 20151021-211850 family. I haven’t looked closely yet, but it appears that the update is targeting scan 2, which makes sense because as I remarked in my earlier post that scan was broken anyway. As with the other update, I’ll take a closer look on the weekend.


New VAC module family 20151117-153712

Update for the 20150609-214236 family described here. At first glance it appears that the x64 support from the 20151001-202837 family has been ported across to scans 1, 2 and 4. I’m not sure what else (if anything) has been changed yet, but it doesn’t appear to be much. I’ll probably take a closer look on the weekend.


New VAC module family 20151021-211850

Just a simple update for the 20151001-202837 family. It seems Valve finally noticed the bug causing threads to hang. However instead of narrowing down the scope of their NtQueryObject calls or even simply removing the file handle name query, they’ve removed the entire function containing the handle enumeration logic. Disappointing, so hopefully once they sort out their bugs they’ll re-add it.


Two more bugs in VAC module family 20150609-214236

Sometimes it feels like the stream of bugs will never end…

The first problem is again with the way VAC tries to get the overlay of a file and add it as a faux section (presumably so it participates in the section hashing etc. without any special handling). You may remember a different bug from the same function which I blogged about earlier.  This time the problem is in the way files with zero sections are handled. Basically the problem is that if the number of sections in a file is S, then S+1 section headers are allocated and VAC attempts to access section S-1 in order to calculate where the previous last section ends so it can add the overlay as the new last section. However this will obviously be incorrect for files with zero sections as there is no last section, and so a read buffer underflow will occur due to a negative index into the buffer being used.

.text:10006D16 014 2B DD sub ebx, ebp
; Increment NumberOfSections
.text:10006D18 014 66 FF 86 26 22 00 00 inc word ptr [esi+2226h]
.text:10006D1F 014 0F B7 86 26 22 00 00 movzx eax, word ptr [esi+2226h]
.text:10006D26 014 6B C0 28 imul eax, 28h
.text:10006D29 014 50 push eax ; dwBytes
.text:10006D2A 018 FF B6 08 24 00 00 push dword ptr [esi+2408h] ; lpMem
; Allocate memory for NumberOfSections * sizeof(IMAGE_SECTION_HEADER)
.text:10006D30 01C E8 B5 D9 FF FF call HeapAllocOrRealloc
.text:10006D35 01C 0F B7 96 26 22 00 00 movzx edx, word ptr [esi+2226h]
.text:10006D3C 01C 59 pop ecx
.text:10006D3D 018 89 86 08 24 00 00 mov [esi+2408h], eax
.text:10006D43 018 59 pop ecx
.text:10006D44 014 8D 4A FE lea ecx, [edx-2] 
.text:10006D47 014 6B F1 28 imul esi, ecx, 28h
.text:10006D4A 014 6B CA 28 imul ecx, edx, 28h
; ESI now holds the pointer to the previous last section, which in the case of a zero 
; section file lies before the start of the buffer (remember that NumberOfSections 
; is now 1, and we're accessing index NumberOfSections-2)
.text:10006D4D 014 03 F0 add esi, eax
.text:10006D4F 014 83 C1 D8 add ecx, 0FFFFFFD8h
.text:10006D52 014 03 C1 add eax, ecx
.text:10006D54 014 89 44 24 10 mov [esp+14h+overlay_section_header], eax
; Read from the potentially invalid pointer
.text:10006D58 014 8B 46 08 mov eax, [esi+8]
.text:10006D5B 014 3B 46 10 cmp eax, [esi+10h]
.text:10006D5E 014 73 0B jnb short loc_10006D6B

The second bug occurs in the string scanning routine. This one I haven’t finished investigating yet, so I could be wrong, but basically the problem appears to be due to a signed/unsigned integer mismatch. The outer part of the algorithm does some signed math to calculate the length of data to process, and whilst it does a check for negative values, it then does another subtraction afterwards and doesn’t check again. Then the inner part of the algorithm treats the length as unsigned and clamps it to a maximum length of 0x40, so in the case that the string being processed is near the end of the buffer, a read overflow will occur due to 0x40 bytes being attempted to be read regardless of the remaining buffer length. I may write more about this in the future once I put some time aside to understand it better, but right now I can’t be bothered because it’s not particularly interesting.

.text:10002FFA 010 85 C9 test ecx, ecx
.text:10002FFC 010 7E 46 jle short loc_10003044
.text:10002FFE 010 8D 04 0A lea eax, [edx+ecx]
.text:10003001 010 89 45 00 mov [ebp+0], eax
.text:10003004 010 2B 44 24 18 sub eax, [esp+10h+pe_buffer_len]
.text:10003008 010 85 C0 test eax, eax
.text:1000300A 010 7E 02 jle short loc_1000300E
; ECX is used as the length in the function which actually reads out the string
; from the PE file buffer and writes it to the output buffer, but it could be 
; negative after this instruction, due to insufficient validation.
.text:1000300C 010 2B C8 sub ecx, eax