Here's the dirty dirty dirt dirt. (All addresses SP2)

If you send an SMBv2 packet off to Vista SP1 or SP2 that specifies the NEGOTIATE command, and the ProcessIDHigh word is not set to 0x0000, you do not in fact get a DoS. What happens, is this: (Note that we control eax, and esi points to our packet)

  1. 9914d6b7 8b048570521699 mov eax,dword ptr [ValidateRoutines + eax*4]
  2. 9914d6be 85c0 test eax,eax
  3. 9914d6c0 7507 jne 9914d6c9
  4. 9914d6c2 b8020000c0 mov eax,0C0000002h
  5. 9914d6c7 eb03 jmp 9914d6cc
  6. 9914d6c9 53 push ebx
  7. 9914d6ca ffd0 call eax

In this code, the eax in line 1 is in fact our ProcessIDHigh from the NEGOTIATE packet, and ValidateRoutines is a small dispatch table in the srv2.sys driver. After this dispatch table which is about 0x13 addresses long, (I didn't really count) there are several global variables specifying max values for certain things that I found to be uninteresting. The interesting thing is that if ValidateRoutines is at 0x99165270 and srv2.sys ends at 0x9916efff and 0xffff * 4 is 0x03FFFC, then we have quite a lot of space to go looking for pointers; not just in srv2.sys, but also in the immediately proceeding driver.

Now you have the problem of finding useful executable code to get you to esi, and also finding a pointer to this code within the space you have (0x03FFFC from ValidateRoutines). You can go about this in one of two ways. Firstly, you can find all of the jmp esi, call esi, and push esi -> ret calls in executable kernel memory, then search through the list of pointers in the range to see if they point at any of these instructions. This is very time consuming. The alternative is that you can use HD Moore's method of looking at all the possible pointers in this range, checking their symbols, and seeing if any of them point at anything executable and controllable. This method netted the following solution.

In the case of Vista SP1, the driver occupying the next address space is srvnet.sys. This is lucky because inside srvnet.sys (near the top) there is a pointer to a global variable which can be controlled by an external user: srvnet!SrvNetStatistics. In SP2, srvnet is loaded before srv2.sys so we cannot make use of the same global. However, we get lucky here as well in that there is a pointer srv!pSrvStatistics which also points to srvnet!SrvNetStatistics, and counts the number of requests that have been made to a specific call (as well as other things).

So the technique here is to firstly increment srvnet!SrvNetStatistics to be ffe6, ffd6, or 56c3 (jmp esi, call esi, push esi -> ret). Then we set ProcessHighID to a value that when multiplied by four and added to the base address of ValidateRoutines pushes us outside of srv2.sys and into srvnet.sys where we then end up dereferencing the pointer to srvnet!SrvNetStatistics. This now transfers control to the data in our packet which we can massage to gain execution.

. The problem here is that we don't know how many times things have been called before we got to the target. One way to handle this is to build our opcode with the GET_PRINT_QUEUE undocumented command. This SMBv2 command is in the dispatch table (just like NEGOTIATE) but it is undocumented and unimplemented, immediately calling a function which logs that it was called, and exits after incrementing a value in srvnet!SrvNetStatistics. We can safely assume that we're the only ones calling this function, and increment this counter value until it becomes an opcode that puts esi into eip. Note that this will require you to send a number of GET_PRINT_QUEUE requests equal to the opcode of the instruction you want. For push esi, ret, this will require you to send 0x56c3 requests. Thankfully, though, this can be done effectively in just a couple of minutes. If you'd like to be more stealthy, you can string it out over many days.

There are a few other hurdles to jump over, such as the possibility that somewhere else in functions have been called in such a way that the values in srvnet!SrvNetStatistics are opcodes that screw up esi or eax as well as the fact that esi points directly to our packet, not to fully controllable data. Currently, no one to my knowledge has a reliable remote exploit for this vulnerability finished. This said, I'm fairly confident that this method will become 100% reliable for remote code execution in the future.