Tuesday, December 22, 2015
Saturday, September 26, 2015
Thursday, September 17, 2015

Markdown plugin test.

This is a test of Markdown. URLs are entered thus.

This should be preformatted text.

This is a blockquote

This should be preformatted text.
  • This is a bullet list
  • So is this

    This should be preformatted text.

  1. This is a numbered list
  2. So is this

    This should be preformatted text.

This is fixed width.

This should be preformatted text.

Tuesday, November 4, 2014

My incomplete review of Call of Duty: Ghosts

For various reasons related to timing, I didn’t finish this review of Call of Duty: Ghosts and we never published it. But I was re-reading it while working on my review of Call of Duty: Advanced Warfare and I realize what a shame it was that it never went live. So here’s what I wrote.

If you’re developing a first person military shooter with the US as the protagonist, you’ve got a problem: there’s no good enemy to fight against.

The US is the world’s only current superpower. There’s no rival force that’s comparable in terms of firepower and threat. Terrorism is real, but terrorists do not pose an existential threat to the United States in the way that the Soviet Union once did.

Using America’s real-world (notionally) anti-terrorist actions in Iraq and Afghanistan as the backdrop for a game can be done, but is problematic: these invasions and occupations aren’t universally popular, and in any case, so what if you lose? It’s not as if defeat is going to result in Saddam Hussein nuking the US with his Scud missiles. There’s so little at stake.

Which is not to say it can’t be done. The 2010 Medal of Honor reboot was set in Afghanistan, and loosely based on a real battle. Rather than travelling to exciting and exotic locations around the world, the action was all in one country, and all unfolded over a couple of days. It was low-key, with the game making no pretence that the safety of the free world hinged on our actions. It was also personal in scale—the only lives at risk were those of the characters we played—while creating a great sense that we were just a small part of a larger conflict.

We weren’t shooting rockets out of the sky, and there were no terrorists buying black market Russian nukes. It was very grounded. I think that 2010’s Medal of Honor worked well (and its sequel Warfighter was atrocious, in no small part because it completely eschewed this style), but I don’t think that kind of story-telling is a good fit for the Call of Duty series. The modern era Call of Duty games have always favored action movie spectacle and bombast.

With the nuclear terrorist storyline of the Modern Warfare trio wrapped up and thoroughly played out, Infinity Ward needed to create a new scenario to justify sending American warriors to exotic locations to kill innumerable bad guys.

The approach this time has been to make the US the underdog. Doing this in a credible way remains beyond the wit of action writers. The good news here is that Infinity Ward didn’t go for the currently fashionable “North Korea attacks” strategy, because of its blatant absurdity.

Instead, the writers decided to blow up the Middle East with nuclear weapons, sending oil prices sky high. This greatly enriches the oil-rich nations of South America, and they form a group known as “the Federation.” The Federation then goes on to invade Central America for whatever reason.

In spite of this obvious belligerence, the US and Federation work together on an orbiting kinetic bombardment superweapon, ODIN. It works by shooting large heavy rods at the ground, at high speed, with their kinetic energy being the destructive element. The US is betrayed by the Federation, which uses ODIN to destroy various American cities.

This wouldn’t work, by the way: although such a weapon could be designed and used, its payload just wouldn’t be city-levelling. Rather, it would be a precision-guided bunker buster. It’s hard to see just how big ODIN’s rods are, but presuming them to be cylinders of around 20 meters long, with a 0.5 meter diameter, and made of tungsten, they’d have a mass in the ballpark of 80 tons. Even if travelling at 20 kilometers a second—which is implausibly fast—the kinetic energy would be equivalent to less than 4 kilotons of TNT. For comparison, the yield of “Little Boy” bomb dropped on Hiroshima was about 16 kT TNT, and “Fat Man,” dropped on Nagasaki, had a yield of about 20 kT TNT. As destructive as these weapons were, they weren’t sufficient to destroy entire cities.

Such physical limitations serve to demonstrate the practical difficulties with ending the US’s dominant position on the world stage.

The player action starts off in that ODIN attack, witnessed first on the ground, and then in space. On the ground, we meet our protagonist, Logan Walker. In many Call of Duty games, play jumps from character to character. In this one, we almost exclusively play as Logan, save for a flashback, when we play as his father, Elias, and a couple of short space-based missions where we play as random astronauts.

Elias takes a commanding role in the now crippled US’s fight back against the Federation. It turns out that Elias was once a member of a near-mythical special forces group calling themselves the Ghosts, so he’s all scary and battle-hardened, and has seen all kinds of action. While out on a mission we soon learn that an American is working with the Federation, and, wouldn’t you know it, he’s a former Ghost too, by the name of Rorke.

As if that weren’t remarkable enough, he’s a former Ghost who just happens to have a major grievance with Elias. In the apparently obligatory flashback mission, we discover that Elias was forced to let go of Rorke’s hand, saving himself, but leaving Rorke behind. He was subsequently captured by Federation forces, tortured extensively using organic, FairTrade-sourced torture techniques, and turned traitor.

What are the chances? I mean, really. Various parts of the subsequent action are specifically motivated by Rorke’s animosity toward Elias and, transitively, his offspring. Even with the US suffering from heavy losses due to the ODIN attacks and subsequent warring with the Federation, it seems quite extraordinary that the guy who just happened to be the reason for much of Rorke’s rage should be the one commanding the team that goes after him. Sending in his sons, no less.

Based on this unlikely premise, the story plays out across the usual exotic locations that we’ve become used to in Call of Duty titles. Plus, there’s one new location: space.

Some may remember the James Bond film Moonraker, part of which takes part in space. James Bond wasn’t very good when it went into space. Call of Duty doesn’t fare much better. There are two short space missions. The first one, during which we disrupt the ODIN attack and prevent the US from being completely destroyed, is actually quite enjoyable. The mere fact of being in space, with debris and corpses floating around, the Blue Marble serene in the background, indifferent to the chaos we’re experiencing: it’s spectacular.

Second time around? It’s just annoying. The second space mission highlights that Infinity Ward hasn’t actually done a very good job of implementing space combat. First time around, this isn’t immediately apparent. Although we’re in space, much of the action takes place within the tubular confines of a space station, so the combat is straightforward—we know where we are, we know where the enemy is, and we know where we’re supposed to go—allowing us to enjoy the set-up without being frustrated by the gameplay.

The second time around, we’re less constrained, and have to fight more in space, rather than in a space station. At this point, we realize that moving around in space is deeply annoying, because you drift everywhere, it’s hard to tell where you’re being attacked from, and thanks to the lack of clear landmarks, it’s hard to tell where we’re meant to be going. I like that Call of Duty can take me to exotic or exciting locations, but in the second space mission, I wasn’t marvelling at miracle of being in space. I was frustrated at it not being clear who was killing me, or where I was meant to be going.

This wasn’t the only part of Ghosts that made me feel that way, either. There was an extensive swimming level. I hate swimming levels, in general, because the movement is sluggish and imprecise. The one in Call of Duty: Ghosts is no exception. It was longer than most swimming levels, and it felt laborious. In it, we learn that sharks can only detect prey if they’re directly in front of them and no more than a few feet away. As with the second of the space missions, it also suffered from making it hard to tell who’s shooting at me and from where.

I appreciate that in both space and underwater, the game designers can explore options that aren’t available on dry land. The mission design becomes considerably more three dimensional, because people can attack from literally any angle. I understand that in an objective sense, this makes the game more varied, and creates fighting dynamics that don’t normally exist.

But Call of Duty is a cover-based shooter. It’s designed so that you can duck behind cover and be moderately safe when doing so, regenerate your health, and then pop out to kill more bad guys. That’s fundamentally undermined in the underwater and space missions, where you can’t dart behind cover, and where cover doesn’t necessarily cover you.

Neither of these things are bad as such, and cover-based shooters aren’t the only valid design choice—I’m sure that many people would even argue that they’re one of the worst design choices—but if cover-based shooter is the decision you’ve made for your game, arbitrarily discarding those rules actually sucks. It means that the player has to throw away their knowledge and experience and play something that’s a different kind of game, but that still superficially looks like the kind of game they’re expecting.

The story ends on a dissatisfying note that serves to render the player’s actions in the game entirely irrelevant and sets us up for a sequel. As if either the setup or the sequel were actually necessary.

There’s also the usual post-credits sequence. The post-credits sequence is oh so tired. I hate to break it to you, game developers, but when those credits are rolling I’m actively doing something else. Call of Duty: Ghosts won’t even let you alt-tab out of the credits—it pauses them if you do—but that doesn’t mean that I’m going to sit there and read them. I’m just going to play on my phone instead. So please, let’s just cut the crap. Put the entirety of the game before the credits, or at least let me skip the credits and go to the post-credits sequence instantly.

Ghosts is far and away the best looking Call of Duty title I’ve played. But that qualification is important. It uses an updated and modified version of the same engine that previous titles have used, and while it looks OK, there’s definitely a gap in visual quality when compared to Battlefield 4. Ghosts has crisp textures and decent models, and a richer variety in settings than Battlefield 4, but Battlefield 4 gives me a much greater sense of being immersed in a real, malleable, environment.

But that’s about the best that can be said for the game. After the impressive high of the opening space level, the rest of the game falls terribly flat.

Shooting people in Ghosts doesn’t feel especially satisfying. I don’t know if it’s because of the way the levels are designed—almost all the engagements felt short range and cramped—or the fact that even though there are, technically, lots of different kinds of gun that you can pick up and use, they all feel basically the same, or the fact that I never felt short of ammo or punished for spraying bullets everywhere.

Normally in this kind of game, I pounce on any dropped sniper rifles because I know that the ability they give me to engage at long range affords me a tactical advantage. But I didn’t in Ghosts because almost every time I was shooting bad guys, they were no more than a few metres away from me. All these different kinds of guns, with all their various attachments and add-ons, and the differences are all basically irrelevant. Just pick up whatever gun is dropped nearby and use it. They’re all the same.

This is taken to absurd levels, too. There are a few missions where you have silenced weapons, because you’re trying to be all stealthy. You’d think that this would limit your ability to use enemy weapons: after all, they’re just guards out on patrol, so they have no need for silenced weapons. You’d think that, but you’d be wrong. They drop silenced weapons too.

Then there’s the thing with the dog. I actually tried to blank this out of my memory. It was awful at the time, and I managed to forget about it until someone reminded me about it, and then I remembered how bad it was.

You drive a dog around. You have one button to make him bark. Bow wow. You have another button to make him jump on people and rip out their throats.

You’re driving a goddamn dog, and apparently nobody at Infinity Ward stopped for even a nanosecond to realize how monumentally stupid this was and put an end to it.

The dog part of Call of Duty: Ghosts at once manages to be both too much, and not enough.

Even if we were to suppose that somehow the ability to essentially mind control animals were a thing in this universe, the dog driving level falls short. Because if that’s a thing, I want to do more than drive a dog around. I want to drive a tiger around. I want to leap out from the undergrowth, say ROAR in a loud tiger voice, and then eat people’s faces. I want to drive one of those annoying sharks around on the underwater level and use him to dismember the enemy troops.

With flat combat, a story that’s ultimately pointless, and idiocy like driving around a dog, the Call of Duty: Ghosts single player mode is a great disappointment. I had really hoped that Ghosts might include some of the innovations found in the Treyarch-developed sibling title, Call of Duty: Black Ops II. That game included seamlessly integrated decisions and multiple outcomes, without always depending on quick time events to force the player to consciously pick one course of action or another. It took Call of Duty single player in a new, altogether more interesting direction. A direction apparently ignored by Infinity Ward.

But as it is, the campaign just isn’t special. We see the same old settings and an astonishing disregard for logic. Not for the first time, for example, we find ourselves on an oil rig. This time we discover that apparently oil rigs have a big red button that, when pressed, makes the entire thing blow up, for god knows what reason. The plot needs an easy way to quickly destroy the oil rig, so they gave us a button to do it. Lazy isn’t the half of it.

Saturday, February 23, 2013
Tuesday, February 19, 2013

My delicious new job title

One of the things I was fortunate enough to write about today was the Burger King’s Twitter account getting hacked.

I particularly enjoyed the upper deck I wrote, promoting my unofficial job title.

mcrib-correspondent

After all, given the many media appearances I’ve had in McRib articles over the years, how could I be anything else?

Tuesday, January 29, 2013
Monday, January 28, 2013
Monday, January 21, 2013

Some notes on reinstating shadow copies on Windows 8

Most of the time, the thing that people need from a backup system isn’t disaster recovery. It’s accident reversal. Yes, it’s important to have backups stored on separate disks, ideally at separate locations, to handle hardware failures. But that’s not the most common data loss scenario, just the most severe. Far more common is “whoops, I didn’t mean to save over that file”.

Shadow Copies are an excellent solution for this common problem. When Shadow Copies are enabled, Windows periodically creates snapshots of the disk using the Volume Snapshot Service (VSS). As long as the snapshot exists, you can get a view of the disk as it existed at the time of the snapshot. For Shadow Copies, the operating system reserves a chunk of disk space (which is configurable) for snapshot storage. Once this disk space is full, snapshots start getting purged, starting with the oldest.

The length a snapshot can last depends on the amout of disk space you assign to the shadows and the amount of churn your disk experiences. Every modification made after the snapshot causes the snapshot delta file to grow larger, so disks with lots of writes will tend to burn through their snapshots quite quickly. Disks with less write activity will tend to retain a longer history.

Server versions of Windows, since Windows Server 2003, have had Shadow Copies as a supported feature with a user-visible interface. Shadow Copy configuration is done in Computer Management\System Tools\Shared Folders, where you can configure which volumes get snapshotted, how much storage is assigned to snapshots, and how often the snapshots are made. Shadow Copies are off by default; turn them on, and their schedule is organized around the working week, with two snapshots per day Monday to Friday, at 0700 hrs and 1200 hrs.

Browsing and restoring from Shadow Copies is done through Explorer. The Properties dialog of any folder protected by Shadow Copies has a Previous Versions tab, which allows you to pick an earlier version and restore from it.

Windows Vista and Windows 7 incorporate most of the machinery used to support Shadow Copies, and in these operating systems too, it’s a supported feature. They lack much of the configuration user interface, however. The operating system does make periodic snapshots, but these are primarily created for the purpose of System Restore; a new snapshot is made at midnight each night, by default.

Whether server or workstation, all versions of Windows have essentially the same underlying functionality. The behind-the-scenes components—the Volume Snapshot Service, or VSS, the filesystem drivers, the plugins from apps like SQL Server (so that they can make sure that their files are consistent before a snapshot is preserved)—are all the same.

This means that Windows 8 is perfectly capable of making periodic snapshots. What it’s lacking is some of the user interface components, both in the GUI and at the command-line. As with the underlying parts, the user interface parts are all there; it’s just that they’re deliberately crippled.

There are perhaps three important command-line tools for manipulating VSS. vssadmin.exe, diskshadow.exe, and vshadow.exe. vssadmin.exe is found on both server and desktop Windows. On the desktop, it can be used for querying VSS (enumerating disks with snapshot storage, stored snapshots, VSS plugins, and so on), deleting shadow copies, and changing the amount of storage dedicated to snapshot storage. On the server, it gains the all-important ability to create snapshots and revert the disk to a previous version (equivalent to System Restore).

diskshadow.exe is found only on server versions. It’s modelled after tools like diskpart.exe, providing a kind of interactive shell. It gives fairly extensive control over the creation of shadow copies, including the creation of multi-volume snapshots, exposing existing shadows as drive letters or mount points, and certain more advanced tasks. It’s quite useful to have, but it’s not required for basic shadow copy functionality.

vshadow.exe is found in the Windows SDK. It’s notionally a “sample” client for VSS to showcase its features. It is, in practice, a fairly fully-featured front-end to VSS.

Because it’s found in all versions of Windows already, vssadmin.exe is the best place to start with shadow copies. Not only is it found in all versions of Windows, it’s also the program used to actually create shadow copies in server versions of Window; it’s simply executed as a scheduled task. Although using vshadow.exe would work, making vssadmin.exe do the right thing also means that we don’t need to install the SDK onto systems just to get working shadow copies.

There’s just one small problem with that. As mentioned, its reported capabilities are different on desktop and server. However, a quick check of the executable itself shows that the program is the same regardless of which operating system variant it’s being used with.

On desktop, we see the reduced feature set:

C:>vssadmin
vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2012 Microsoft Corp.

Error: Invalid command.

---- Commands Supported ----

Delete Shadows        - Delete volume shadow copies
List Providers        - List registered volume shadow copy providers
List Shadows          - List existing volume shadow copies
List ShadowStorage    - List volume shadow copy storage associations
List Volumes          - List volumes eligible for shadow copies
List Writers          - List subscribed volume shadow copy writers
Resize ShadowStorage  - Resize a volume shadow copy storage association

On a server:

C:>vssadmin
vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool
(C) Copyright 2001-2012 Microsoft Corp.

Error: Invalid command.

---- Commands Supported ----

Add ShadowStorage     - Add a new volume shadow copy storage association
Create Shadow         - Create a new volume shadow copy
Delete Shadows        - Delete volume shadow copies
Delete ShadowStorage  - Delete volume shadow copy storage associations
List Providers        - List registered volume shadow copy providers
List Shadows          - List existing volume shadow copies
List ShadowStorage    - List volume shadow copy storage associations
List Volumes          - List volumes eligible for shadow copies
List Writers          - List subscribed volume shadow copy writers
Resize ShadowStorage  - Resize a volume shadow copy storage association
Revert Shadow         - Revert a volume to a shadow copy
Query Reverts         - Query the progress of in-progress revert operations.

With identical binaries on both systems, it’s clear that it must be doing some kind of runtime operating system detection to figure out which features to offer.

There are a few different ways of figuring out what operating system a Windows program is running on. To do this, I used the very useful API Monitor by Rohitab Batra. Sure enough, early in the program’s execution was a call to GetVersionExW(), one of the APIs for figuring out what version of Windows is being used.

I then wanted to verify the way in which the API was being used; which information was being used, and how it was being used. To do this, I used Visual Studio 2012. I created a new blank solution, then added an existing project, using the vssadmin.exe executable itself as the project file. This rather obtuse operation lets you debug an existing binary. I also made sure to turn on symbol server support, so that public symbols for Windows programs would be downloaded from Microsoft’s servers.

Right click the “project”, choose Debug… Start new instance, and Visual Studio loads the program and stops at the entrypoint. With the program suspended, we can then set a breakpoint on GetVersionExW() to let see the context in which the API was called.

The call stack shows that the call is made from a function CVssSKU::Initialize(). This is a fairly short and simple function:

000007F60C71B9FC  push        rbx  
000007F60C71B9FE  sub         rsp,150h  
000007F60C71BA05  mov         rax,qword ptr [__security_cookie (07F60C723110h)]  
000007F60C71BA0C  xor         rax,rsp  
000007F60C71BA0F  mov         qword ptr [rsp+140h],rax  
000007F60C71BA17  xor         ebx,ebx  
000007F60C71BA19  cmp         dword ptr [CVssSKU::ms_bInitialized (07F60C723814h)],ebx  
000007F60C71BA1F  jne         CVssSKU::Initialize+8Dh (07F60C71BA89h)  
000007F60C71BA21  lea         rcx,[rsp+20h]  
000007F60C71BA26  mov         dword ptr [rsp+20h],11Ch  
000007F60C71BA2E  call        qword ptr [__imp_GetVersionExW (07F60C725788h)]  
000007F60C71BA34  mov         al,byte ptr [rsp+13Ah]  
000007F60C71BA3B  lea         r11d,[rbx+1]  
000007F60C71BA3F  cmp         al,2  
000007F60C71BA41  je          CVssSKU::Initialize+59h (07F60C71BA55h)  
000007F60C71BA43  cmp         al,3  
000007F60C71BA45  je          CVssSKU::Initialize+59h (07F60C71BA55h)  
000007F60C71BA47  cmp         al,r11b  
000007F60C71BA4A  sete        bl  
000007F60C71BA4D  mov         dword ptr [CVssSKU::ms_eSKU (07F60C723818h)],ebx  
000007F60C71BA53  jmp         CVssSKU::Initialize+86h (07F60C71BA82h)  
000007F60C71BA55  test        byte ptr [rsp+138h],40h  
000007F60C71BA5D  je          CVssSKU::Initialize+75h (07F60C71BA71h)  
000007F60C71BA5F  mov         dword ptr [CVssSKU::ms_eSKU (07F60C723818h)],4  
000007F60C71BA69  mov         dword ptr [CVssSKU::ms_bTransportableShadowsAllowed (07F60C72381Ch)],ebx  
000007F60C71BA6F  jmp         CVssSKU::Initialize+86h (07F60C71BA82h)  
000007F60C71BA71  mov         dword ptr [CVssSKU::ms_eSKU (07F60C723818h)],2  
000007F60C71BA7B  mov         dword ptr [CVssSKU::ms_bTransportableShadowsAllowed (07F60C72381Ch)],r11d  
000007F60C71BA82  mov         dword ptr [CVssSKU::ms_bInitialized (07F60C723814h)],r11d  
000007F60C71BA89  mov         rcx,qword ptr [rsp+140h]  
000007F60C71BA91  xor         rcx,rsp  
000007F60C71BA94  call        __security_check_cookie (07F60C71D5E0h)  
000007F60C71BA99  add         rsp,150h  
000007F60C71BAA0  pop         rbx  
000007F60C71BAA1  ret  

From here there are a few important details. First, that the structure is on the stack, at an offset of 0x20, and second, that its field dwOSVersionInfoSize is initialized to 0x11c (284 bytes):

000007F60C71BA26  mov         dword ptr [rsp+20h],11Ch  

This means that the program is using the larger, more detailed OSVERSIONINFOEXW structure.

Third, after the call is made (with no check of the return value), one value is read from the structure:

000007F60C71BA34  mov         al,byte ptr [rsp+13Ah]  

This is the field at offset 0x11a within the structure itself, which is:

BYTE  wProductType;

There are three possible values for this field. If it contains 2 or 3, then the Windows version is Domain Controller or Server, respectively. If it contains 1, then it’s workstation/desktop Windows.

The function treats 2 and 3 identically:

000007F60C71BA3F  cmp         al,2  
000007F60C71BA41  je          CVssSKU::Initialize+59h (07F60C71BA55h)  
000007F60C71BA43  cmp         al,3  
000007F60C71BA45  je          CVssSKU::Initialize+59h (07F60C71BA55h)

If neither test branch is taken, it falls through to the workstation case. We’ll consider that case first. There may be some nuance to this that I don’t understand (I find reading assembly troublesome at the best of times). The line

000007F60C71BA17  xor         ebx,ebx  

Zeroes out the bottom 32 bits of the rbx register. The line

000007F60C71BA3B  lea         r11d,[rbx+1]  

sets the bottom 32 bits of the r11 register to the value of rbx + 1 (i.e. 1). Why it does it this way I don’t understand. rbx is a preserved register; its value is supposed to be maintained across function calls. This means that it must be a constant 1, so why not just set r11 to a constant 1? Indeed, why set r11 to anything at all—why not continue to use rbx?

Anyway, the bottom 8 bits of r11 are then compared with the wProductType:

000007F60C71BA47  cmp         al,r11b  

If they’re equal, then the bottom 8 bits of rbx are set to 1:

000007F60C71BA4A  sete        bl  

This value is then stored for subsequent lookups:

000007F60C71BA4D  mov         dword ptr [CVssSKU::ms_eSKU (07F60C723818h)],ebx  

Note how the public symbols are helpful here; it is fairly clear that CVssSKU::ms_eSKU is used as a record of which Windows SKU is being used.

The code then jumps to the common tail of the function

000007F60C71BA53  jmp         CVssSKU::Initialize+86h (07F60C71BA82h)  

which sets a flag to indicate that the initialization has been called

000007F60C71BA82  mov         dword ptr [CVssSKU::ms_bInitialized (07F60C723814h)],r11d  

and then does the stack cookie check and exits

000007F60C71BA89  mov         rcx,qword ptr [rsp+140h]  
000007F60C71BA91  xor         rcx,rsp  
000007F60C71BA94  call        __security_check_cookie (07F60C71D5E0h)  
000007F60C71BA99  add         rsp,150h  
000007F60C71BAA0  pop         rbx  
000007F60C71BAA1  ret  

So that’s what’s happening on desktop Windows. How about the servers?

In that case, a second test is performed.

000007F60C71BA55  test        byte ptr [rsp+138h],40h  

This examines the byte at offset 0x118 in the structure and does a bitwise AND with 0x40. This is the first byte of

  WORD  wSuiteMask;

Because x64 is a little-endian architecture, the first byte of the 16-bit word is the bottom 8 bits. For some reason I cannot fathom, this appears to be a test of whether VER_SUITE_EMBEDDEDNT is specified.

If the result of the bitwise AND is zero then the ZF flag is set, and je jumps are taken. If the branch is not taken (i.e. if it is running on Embedded NT) then

000007F60C71BA5F  mov         dword ptr [CVssSKU::ms_eSKU (07F60C723818h)],4  
000007F60C71BA69  mov         dword ptr [CVssSKU::ms_bTransportableShadowsAllowed (07F60C72381Ch)],ebx  

The SKU is set to 4, and the value 0 (remembering that in this branch the rbx register is never modified after being initially zeroed) is stored in the CVssSKU::ms_bTransportableShadowsAllowed variable to denote that transportable shadow copies aren’t permitted.

Transportable shadow copies are shadow copies with extra metadata preserved in Windows Cabinet (.cab) files so that they can be mounted on different systems.

It then jumps to the common tail, as before

000007F60C71BA6F  jmp         CVssSKU::Initialize+86h (07F60C71BA82h)  

Otherwise, for non-embedded NT, we have

000007F60C71BA71  mov         dword ptr [CVssSKU::ms_eSKU (07F60C723818h)],2  
000007F60C71BA7B  mov         dword ptr [CVssSKU::ms_bTransportableShadowsAllowed (07F60C72381Ch)],r11d  

The SKU is set to 2, and transportable shadow copies are permitted.

These various stored values (CVssSKU::ms_eSKU and CVssSKU::ms_bTransportableShadowsAllowed) are then examined at various places in vssadmin.exe. It doesn’t appear to perform any other interrogation or examination of the underlying operating system or its capabilities; this check is it.

To trick vssadmin.exe into giving us the full set of server features, we need only change the value of wProductType, replacing a 1 with a 2 or a 3. 3 makes the most sense, as obviously Windows workstations are not domain controllers. If we change the value in the debugger, lo and behold, the full set of options and features are shown in the help text, and the extra, hidden commands spring into life.

Obviously, stopping the program in the debugger and changing values in memory is not particularly convenient. What we really want to do is to hook the call to GetVersionExW() and replace the value automatically.

The standard technique for doing this on Windows is to spawn the process, inject a DLL into the process, and use that DLL to modify functions appropriately. It’s a time-honoured and trusted technique, it’s robust and well-understood, and so it’s what we’ll do here. First things first: DLL injection.

This is actually pretty simple. The outline is:

  1. Start the process in the suspended state.
  2. Allocate some memory within the process to store parameters, with VirtualAllocEx().
  3. Copy the parameters to the memory, with WriteProcessMemory().
  4. Use CreateRemoteThread() to start a thread within the process, with LoadLibrary() as the thread’s function, and the allocated memory as the thread’s parameter.
  5. Do the actual work from the DLL’s DllMain().

We can do this because essentially LoadLibrary() has a compatible type with ThreadProc() (the type of thread functions). The types are

HMODULE WINAPI LoadLibraryW(const wchar_t* lpFileName);

and

DWORD WINAPI ThreadProc(void* lpParameter);

There is one minor difference—the width of the return value (pointer-sized for LoadLibrary(), 32-bit for ThreadProc()) but it’s immaterial, as return values are passed in the rax register anyway, so there’s no practical difference between 32- and 64-bit integers.

The code to do this is pretty simple. It looks something like this:

#define _CRT_SECURE_NO_WARNINGS 1
#define NOMINMAX
#define STRICT

#include <SDKDDKVer.h>
#include <Windows.h>

#include <memory>
#include <cstring>

int main() {
    const wchar_t* vssadmin_path =  L"%systemroot%\system32\vssadmin.exe";
    DWORD buffer_size = ::ExpandEnvironmentStringsW(vssadmin_path, nullptr, 0);
    std::unique_ptr<wchar_t[]> vssadmin(new wchar_t[buffer_size]);
    ::ExpandEnvironmentStringsW(vssadmin_path, vssadmin.get(), buffer_size);

    ::STARTUPINFOW si = { sizeof(STARTUPINFOW) };
    ::PROCESS_INFORMATION pi = { 0 };
    ::CreateProcessW(vssadmin.get(), ::GetCommandLineW(), nullptr, nullptr, FALSE, CREATE_SUSPENDED | CREATE_PRESERVE_CODE_AUTHZ_LEVEL | INHERIT_PARENT_AFFINITY, nullptr, nullptr, &si, &pi);

    // we want to load the DLL from the directory with the wrapper program in it, not from vssadmin.exe's directory
    // so we need to provide the full path to the DLL
    const wchar_t* dll_base_name = L"patcher.dll";
    wchar_t dll_name[MAX_PATH] = {0}; // HATE
    ::GetModuleFileName(nullptr, dll_name, sizeof(dll_name) / sizeof(*dll_name));
    std::wcscpy(std::wcsrchr(dll_name, L'\') + 1, dll_base_name);

    void* target_memory = ::VirtualAllocEx(pi.hProcess, nullptr, sizeof(dll_name), MEM_COMMIT, PAGE_READWRITE);
    SIZE_T bytes_written = 0;
    ::WriteProcessMemory(pi.hProcess, target_memory, dll_name, sizeof(dll_name), &bytes_written);
    HANDLE remote_thread = ::CreateRemoteThread(pi.hProcess, nullptr, 0, reinterpret_cast<PTHREAD_START_ROUTINE>(&LoadLibraryW), target_memory, 0, nullptr);
    ::WaitForSingleObject(remote_thread, INFINITE);
    ::CloseHandle(remote_thread);
    ::ResumeThread(pi.hThread);
    ::WaitForSingleObject(pi.hProcess, INFINITE);
    DWORD exit_code = 0;
    ::GetExitCodeProcess(pi.hProcess, &exit_code);
    ::CloseHandle(pi.hThread);
    ::CloseHandle(pi.hProcess);
    return exit_code;
}

That lets us get code running inside the vssadmin.exe process. But what should that code do?

There are two ways of using DLLs in windows. Although all DLLs are “dynamically linked”, there’s dynamic and there’s dynamic. In the really dynamic case, you look up the name of the function you want to use with GetProcAddress() at runtime. This allows for things like loading DLLs supplied by a user (such as plugins) or tentative loading of functions that may or may not be present.

However, most Windows system DLLs aren’t used this way at all. They use load-time dynamic listing. Each executable embeds a data structure called the import address table (IAT). This table lists the names of all the DLLs that the executable uses, the names of all the functions in each DLL that the executable uses, and the address of each of those functions. The executable itself just contains null pointers for the addresses. When it loads the executable, Windows replaces all those null pointers with the actual addresses required.

vssadmin.exe’s use of GetVersionExW() uses this load-time dynamic linking. The vssadmin.exe executable has an IAT, and that IAT includes the import of the function GetVersionExW() from kernel32.dll.

To hook the function, what our DLL needs to do is to examine vssadmin.exe’s IAT, find the specific entry we’re interested in, and replace the address with an address of a function that we control.

We don’t need to go into the finer details of the PE format. The format is documented by Microsoft, and there’s lots of code out there to do this kind of thing. Here’s mine:

#define _CRT_SECURE_NO_WARNINGS 1
#define NOMINMAX
#define STRICT

#include <SDKDDKVer.h>
#include <Windows.h>

#define MakePtr(cast, base, offset) reinterpret_cast<cast>(reinterpret_cast<size_t>(base) + static_cast<size_t>(offset))

PROC hook_iat(HMODULE importing_module, const char* exporting_module, PSTR function_name, PROC hooking_proc)
{
    if(!importing_module) {
        return nullptr;
    }

    PROC original_proc = ::GetProcAddress(::GetModuleHandleA(exporting_module), function_name);
    if(!original_proc) {
        return nullptr;
    }

    if(::IsBadCodePtr(hooking_proc)) {
        return nullptr;
    }

    IMAGE_DOS_HEADER* dos_header = reinterpret_cast<IMAGE_DOS_HEADER*>(importing_module);
    if(::IsBadReadPtr(dos_header, sizeof(IMAGE_DOS_HEADER)) || dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
        return nullptr;
    }

    IMAGE_NT_HEADERS* pe_header = MakePtr(IMAGE_NT_HEADERS*, dos_header, dos_header->e_lfanew);
    if(::IsBadReadPtr(pe_header, sizeof(IMAGE_NT_HEADERS)) || pe_header->Signature != IMAGE_NT_SIGNATURE) {
        return nullptr;
    }

    if(pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0) {
        return nullptr;
    }

    for(IMAGE_IMPORT_DESCRIPTOR* import_descriptor = MakePtr(IMAGE_IMPORT_DESCRIPTOR*, dos_header, pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
        import_descriptor->Name;
        ++import_descriptor) {
        if(_stricmp(MakePtr(const char*, dos_header, import_descriptor->Name), exporting_module) == 0) {
            for(IMAGE_THUNK_DATA* thunk = MakePtr(IMAGE_THUNK_DATA*, dos_header, import_descriptor->FirstThunk);
                thunk->u1.Function;
                ++thunk) {

                if(thunk->u1.Function == reinterpret_cast<size_t>(original_proc)) {
                    DWORD original_protection = 0;
                    ::VirtualProtect(&thunk->u1.Function, sizeof(&thunk->u1.Function), PAGE_READWRITE, &original_protection);
                    thunk->u1.Function = reinterpret_cast<size_t>(hooking_proc);
                    DWORD ignored = 0;
                    ::VirtualProtect(&thunk->u1.Function, sizeof(&thunk->u1.Function), original_protection, &ignored);
                    return original_proc;
                }
            }
        }
    }
    return nullptr; // Function not found
}

typedef BOOL (WINAPI* gve)(OSVERSIONINFOW*);

gve GetVersionExOriginal;

BOOL WINAPI GetVersionExForcedServer(OSVERSIONINFOW* version_info) {
    BOOL return_value = GetVersionExOriginal(version_info);
    switch(version_info->dwOSVersionInfoSize) {
    case sizeof(OSVERSIONINFOW):
        break;
    case sizeof(OSVERSIONINFOEXW): {
            OSVERSIONINFOEXW* version_info_ex = reinterpret_cast<OSVERSIONINFOEXW*>(version_info);
            if(version_info_ex->wProductType == VER_NT_WORKSTATION) {
                version_info_ex->wProductType = VER_NT_SERVER;
            }
        }
        break;
    }
    return return_value;
}

BOOL APIENTRY DllMain(HMODULE module, DWORD reason, void* reserved) {
    switch(reason) {
    case DLL_PROCESS_ATTACH: {
            GetVersionExOriginal = reinterpret_cast<gve>(hook_iat(::GetModuleHandleW(nullptr), "kernel32.dll", "GetVersionExW", reinterpret_cast<PROC>(&GetVersionExForcedServer)));
        }
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH: {
            
        }
        break;
    }
    return TRUE;
}

Compile that all and run the resulting program from an elevated command prompt. It’ll give you the full set of vssadmin.exe options, including the important create shadow command used to actually create shadow copy snapshots.

From here it’s just a matter of creating a scheduled task to run the wrapper program periodically.

Monday, December 17, 2012
Wednesday, December 12, 2012