Archive for February, 2011

Spot the WinDBG Script Bug: Solution

Monday, February 21st, 2011

In my previous post I asked you to find the issue in the following script:

as Host 192.168.1.51
as Port 6969
sx- -c “!load winext\\MSEC.dll;!exploitable -bestormhost:Host-bestormport:Port” sov

So in this post I’ll solve the mystery…

Whenever I have a problem like this, I like to break it down into something simpler so that I can verify the result myself before declaring victory. In this case, the script is using a debugger extension DLL and command that aren’t part of the standard WinDBG package, so my first idea was to simplify the script and just have the script do the following:

as Host 192.168.1.51
as Port 6969
.echo Host Port

If you save the above script to a file script.txt and execute the following command:

$$><c:\scripts\script.txt

You will get a surprising result: No output! Nada, zip, zero, zilch:

How can that be? The answer lies in the documentation for as and $$><.  First, from the as docs:

If you do not use any switches, the as command uses the rest of the line as the alias equivalent.

OK, so as sets the remainder of the line (up to the newline) to be the alias equivalent. Now, from the $$>< docs:

 The $>< and $$>< tokens open the script file, replace all carriage returns with semicolons, and execute the resulting text as a single command block.

So, as sets the entire line after the alias name to be the alias and $$>< turns the entire script into a single line. Hmmm…Let’s check the aliases after the script is run:

Aha! Now it’s clear what’s going on, that first as command ended up eating the entire script and turning it into an alias for Host!

This is why the aS command exists. That command allows you to specify a quoted string to set the alias to, which avoids this type of confusion:

aS Host "192.168.1.51"
aS Port "6969"
.echo Host Port

However, before giving this script a try we have a couple of other refinements that we can make to it. First off, while it may appear to work in some cases, you should always wrap aliases in the alias interpreter command ${}. In that case, our .echo command should be:

.echo ${Host} ${Port}

In addition, whenever I define my aliases I also like to wrap them in the alias interpreter command with the /v: switch, which deliberately shuts off the alias interpreter. This prevents you from recursively defining the alias in cases where your script is run for multiple invocations:

aS ${/v:Host} "192.168.1.51"
aS ${/v:Port} "6969"

Putting it all together, we can now save the following in a text file and execute it:

aS ${/v:Host} "192.168.1.51"
aS ${/v:Port} "6969"
.echo ${Host} ${Port}

D’oh! Still not there! What’s the problem this time?

Faithful readers will remember the previous post on using WinDBG aliases, in which we discovered that aliases are not interpreted until some kind of block statement is reached. Because there are no block statements in the above script, the aliases aren’t interpreted until the script has finished running. Thus our .echo statement just shows us the literal string value that we passed it. In order to fix this final issue we simply need to add a .block directive after we have defined the aliases:

aS ${/v:Host} "192.168.1.51"
aS ${/v:Port} "6969"
.block {
    .echo ${Host} ${Port}
}

Using what I learned from my simplified script, this is the final solution that I submitted to the reader which ended up solving the issue:

aS ${/v:Host} "192.168.1.51"
aS ${/v:Port} "6969"
.block {
    sx- -c “!load winext\\MSEC.dll;!exploitable -bestormhost:${Host}-bestormport:${Port}” sov
}

Spot the WinDBG Script Bug

Thursday, February 17th, 2011

A reader sent me a question (snoone at this domain) because their WinDBG script wasn’t working properly. Here’s the script and the problem:

I am trying to do this (inside a windbg script):

===
as Host 192.168.1.51
as Port 6969

sx- -c “!load winext\\MSEC.dll;!exploitable -bestormhost:Host-bestormport:Port” sov
===

But the Host and Port aren’t being interpreted rather they are being passed as is

There are two scripting bugs in here, can you spot them? I’ll have a follow up post this week to explain the issues and name a winner if there is one. Your prize for being the winner? Absolutely nothing! I do run this blog at a loss you know :)

Interpreting !pool results part II: Large Pool oddities

Monday, February 14th, 2011

In my previous post Interepeting !pool results, I talked about the !pool command and walked through the output. However, I purposefully left off a couple of details when it comes to non-standard pool allocations, such as allocations from special pool and large pool allocations. This led to a question from a reader asking for an explanation of the strange output they were seeing when running !pool on a particular allocation:

Note how there appears to be two entries for a single allocation, one showing this as a freed allocation and the other saying that it is a valid, “large page allocation” of 0x2bc0 bytes. What’s up with that?

The answer is that large pool allocations are treated differently from other types of allocations. An allocation is considered to be a large allocation if it cannot fit within a page of memory (as defined by the architecture, i.e. 4K on the x86/x64) minus the overhead of the pool header required on the block. So, for example, an allocation of PAGE_SIZE bytes would be considered a large page allocation and thus tracked as such.

A large allocation is unique in that it does not contain a POOL_HEADER structure. Instead, the caller is returned a page aligned virtual address and the pool header is tracked in a global variable in the system. When a page aligned addressed is freed back to the allocator, the large pool table is consulted to determine if the allocation is indeed a large page so that the memory can be freed properly (NOTE: on legacy systems such as XP, this was managed a bit differently and the global table was only maintained when pool tracking was enabled via GFlags).

Now we can begin to unravel why the !pool output above is confused. Remember from the previous post that when supplied with an address, !pool will round the address down to PAGE_SIZE and interpret the result as a POOL_HEADER structure. However, in this case the address supplied is the base of a large pool allocation, thus it does not start with a POOL_HEADER structure. So what we have is the first bytes of this driver’s pool allocation being interpreted as a POOL_HEADER:

This is effectively garbage and thus the walk to the next entry fails. However, before reporting an error !pool decides to consult the large pool allocation table nt!PoolBigPageTable to determine if this is a valid large pool allocation (the structure of this table is not documented, however if you’re interested enough to care it’s easy enough to figure out :) ). In this case an entry is found, thus we get a successful hit and the output corrects itself.

I consider this to be a bug in the implementation, it would be better to look at the large pool allocation table first and then fall back to interpreting it as a pool header if it is not found. While this would be more overhead in the common case, it would make the less common case a little less confusing. As such, I have reported it as a bug and hopefully we’ll get a fix in the future.