Windows Management and Scripting

A wealth of tutorials Windows Operating Systems SQL Server and Azure

  • Enter your email address to follow this blog and receive notifications of new posts by email.

    Join 721 other subscribers
  • SCCM Tools

  • Twitter Updates

  • Alin D

    Alin D

    I have over ten years experience of planning, implementation and support for large sized companies in multiple countries.

    View Full Profile →

Posts Tagged ‘Editor’

How to use Windbg to troubleshoot your Windows operating system Part 2 – Writing WINDBG Extensions

Posted by Alin D on May 11, 2011

Introduction

Welcome to the fourth installment of this debugging series. In this installment, we will be getting a bit away from actual debugging for a bit and dive into creating helpful debugging aids. I definitely would never condone writing debugging aids just to write them. I would find a reason to write something, perhaps something you do all the time that gets tedious. Once you have found something you would love to automate, I would then see how you could automate it.

This is what I have done. During my debugging escapades, I always search the stack and other locations for strings. Why do I do this? People are not computers, we understand language rather than numbers. This being the case, a lot of applications and even drivers are written based upon strings. I would not say everything is a string, but there’s usually a string somewhere. If you think about it, you really don’t need strings at all. We could all just use numbers and never use another string again. Some may think well, when you expose a UI of course, you’re going to eventually run into a string somewhere… Well, doesn’t have to be that way, now does it? I mean, I’m not even talking about just User Interfaces, I’m talking about the guts of programs that aren’t even exposed to the user at all. Programmers are still human and like to talk in some language beyond binary. This being the case, even the internals of applications sometimes use strings to represent things. You can find strings just about anywhere, even in drivers.

So There’re Strings, So What?

Well, they provide an added level of readability to an application. This is the first rule of thumb, perhaps if you could find a string somewhere on the stack, you could better track down what the application is doing. These strings could be environment strings, file names, device names (COM1,Devicexxxx, etc.), names of other objects, user names, GUIDs, etc. This information can better help track down what the application is doing and where it could be in your program.

Another interesting concept is, I don’t know this for a fact, but it’s my belief that the most common buffer overruns are due to invalid string manipulations. Whether it be forgetting the NULL character, or misjudging allocation since an API returns # of characters and not # of bytes. If a string overwrote memory in your program and you can find the string, it’s a lot easier to track down who created it.

Where do we start?

I start on the stack. I have a trap, the first thing I do after “KB” and “DDS ESP” would then be to use “DC ESP”. This command dumps the DWORDs on one side and the printable characters on the other. Let’s see an example of this:

Collapse
0:000> dc esp
0006febc  77d43a09 77d43c7d 0006fefc 00000000  .:.w}<.w........
0006fecc  00000000 00000000 00000000 0006ff1c  ................
0006fedc  010028e4 0006fefc 00000000 00000000  .(..............
0006feec  00000000 00000000 77e7ad86 00091ee7  ...........w....
0006fefc  001a03e4 00000118 0000ffff bf8a75ed  .............u..
0006ff0c  0768a2ca 00000229 00000251 00000000  ..h.)...Q.......
0006ff1c  0006ffc0 01006c54 01000000 00000000  ....Tl..........
0006ff2c  00091ee7 0000000a 00000000 00000000  ................
0:000> dc
0006ff3c  7ffdf000 80543940 f544fc5c 00000044  ....@9T..D.D...
0006ff4c  00092b28 00092b48 00092b70 00000000  (+..H+..p+......
0006ff5c  00000000 00000000 00000000 00000000  ................
0006ff6c  00000000 00000000 00000000 00000000  ................
0006ff7c  00000000 ffffffff ffffffff ffffffff  ................
0006ff8c  00091ee7 00000000 00000001 00272620  ............ &'.
0006ff9c  00272d00 00000000 00000000 0006ff34  .-'.........4...
0006ffac  e24296d0 0006ffe0 01006d14 01001888  ..B......m......
0:000>
0006ffbc  00000000 0006fff0 77e814c7 00000000  ...........w....
0006ffcc  00000000 7ffdf000 f544fcf0 0006ffc8  ..........D.....
0006ffdc  80534504 ffffffff 77e94809 77e91210  .ES......H.w...w
0006ffec  00000000 00000000 00000000 01006ae0  .............j..
0006fffc  00000000 78746341 00000020 00000001  ....Actx .......
0007000c  00000654 0000007c 00000000 00000020  T...|....... ...
0007001c  00000000 00000014 00000001 00000003  ................
0007002c  00000034 000000ac 00000001 00000000  4...............
0:000>
0007003c  00000000 00000000 00000000 00000000  ................
0007004c  00000002 00000000 00000000 00000000  ................
0007005c  00000168 00000190 00000000 2d59495b  h...........[IY-
0007006c  000002f8 00000032 0000032c 000002b8  ....2...,.......
0007007c  00000010 00000002 0000008c 00000002  ................
0007008c  00000001 000000ac 00000538 00000001  ........8.......
0007009c  00000002 000005e4 00000070 00000001  ........p.......
000700ac  64487353 0000002c 00000001 00000001  SsHd,...........
0:000>
000700bc  00000003 00000002 0000008c 00000001  ................
000700cc  00000000 0000002c 0000005e 0000005e  ....,...^...^...
000700dc  00000000 00000000 00000000 00000000  ................
000700ec  00000000 00000000 00000000 00000000  ................
000700fc  00000000 00000002 00000028 00000034  ........(...4...
0007010c  003a0043 0057005c 004e0049 004f0044  C.:..W.I.N.D.O.
0007011c  00530057 0030002e 0057005c 006e0069  W.S...0..W.i.n.
0007012c  00780053 005c0073 00000000 00000000  S.x.s..........

I started notepad.exe and just broke into it, then dumped the stack of the primary (and only) thread. The strings on the stack are “local arrays”, such as declaring char x[10]; in your function. These aren’t the only strings in a program though. There are others and these others are stored in pointers that are declared as local variables and even passed to functions such as CreateFile. The first parameter of CreateFile takes a string.

So, what do I usually do? I then search the stack for memory locations which could be strings and I then do “DC” again on them or “DA” for Dump ANSI string or “DU” for Dump Unicode String. The problem with this is that it’s slow and tedious. I could not find any debugger command to do this for me (if there is one, let me know), so I ended up writing my own.

Writing Your Own?

WINDBG supports DLLs created by anyone as long as they export functions and behave in a manner defined by Microsoft. This means you can write PLUGINS! It used to be that people would write plug-ins to !<mydatastructure> <address> which basically dumped the members of their data structure along with the names. However, WINDBG supports the “dt” command that if you have a PDB (symbol file) it can do this for you without writing any code! Let’s see an example of this.

Using the “dt <yourdll>!<your data structure>” will dump the structure’s content along with their names. Let’s look at a quick example.

Collapse
0:000> dt ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 SpareBool        : UChar
   +0x004 Mutant           : Ptr32 Void
   +0x008 ImageBaseAddress : Ptr32 Void
   +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
   +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
   +0x014 SubSystemData    : Ptr32 Void
   +0x018 ProcessHeap      : Ptr32 Void
   +0x01c FastPebLock      : Ptr32 _RTL_CRITICAL_SECTION
   +0x020 FastPebLockRoutine : Ptr32 Void
   +0x024 FastPebUnlockRoutine : Ptr32 Void
   +0x028 EnvironmentUpdateCount : Uint4B
   +0x02c KernelCallbackTable : Ptr32 Void
   +0x030 SystemReserved   : [1] Uint4B
   +0x034 ExecuteOptions   : Pos 0, 2 Bits
   +0x034 SpareBits        : Pos 2, 30 Bits
   +0x038 FreeList         : Ptr32 _PEB_FREE_BLOCK
   +0x03c TlsExpansionCounter : Uint4B
   +0x040 TlsBitmap        : Ptr32 Void
   +0x044 TlsBitmapBits    : [2] Uint4B
   +0x04c ReadOnlySharedMemoryBase : Ptr32 Void
   +0x050 ReadOnlySharedMemoryHeap : Ptr32 Void
   +0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void
   +0x058 AnsiCodePageData : Ptr32 Void
   +0x05c OemCodePageData  : Ptr32 Void
   +0x060 UnicodeCaseTableData : Ptr32 Void
   +0x064 NumberOfProcessors : Uint4B
   +0x068 NtGlobalFlag     : Uint4B
   +0x070 CriticalSectionTimeout : _LARGE_INTEGER
   +0x078 HeapSegmentReserve : Uint4B
   +0x07c HeapSegmentCommit : Uint4B
   +0x080 HeapDeCommitTotalFreeThreshold : Uint4B
   +0x084 HeapDeCommitFreeBlockThreshold : Uint4B
   +0x088 NumberOfHeaps    : Uint4B
   +0x08c MaximumNumberOfHeaps : Uint4B
   +0x090 ProcessHeaps     : Ptr32 Ptr32 Void
   +0x094 GdiSharedHandleTable : Ptr32 Void
   +0x098 ProcessStarterHelper : Ptr32 Void
   +0x09c GdiDCAttributeList : Uint4B
   +0x0a0 LoaderLock       : Ptr32 Void
   +0x0a4 OSMajorVersion   : Uint4B
   +0x0a8 OSMinorVersion   : Uint4B
   +0x0ac OSBuildNumber    : Uint2B
   +0x0ae OSCSDVersion     : Uint2B
   +0x0b0 OSPlatformId     : Uint4B
   +0x0b4 ImageSubsystem   : Uint4B
   +0x0b8 ImageSubsystemMajorVersion : Uint4B
   +0x0bc ImageSubsystemMinorVersion : Uint4B
   +0x0c0 ImageProcessAffinityMask : Uint4B
   +0x0c4 GdiHandleBuffer  : [34] Uint4B
   +0x14c PostProcessInitRoutine : Ptr32
   +0x150 TlsExpansionBitmap : Ptr32 Void
   +0x154 TlsExpansionBitmapBits : [32] Uint4B
   +0x1d4 SessionId        : Uint4B
   +0x1d8 AppCompatFlags   : _ULARGE_INTEGER
   +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER
   +0x1e8 pShimData        : Ptr32 Void
   +0x1ec AppCompatInfo    : Ptr32 Void
   +0x1f0 CSDVersion       : _UNICODE_STRING
   +0x1f8 ActivationContextData : Ptr32 Void
   +0x1fc ProcessAssemblyStorageMap : Ptr32 Void
   +0x200 SystemDefaultActivationContextData : Ptr32 Void
   +0x204 SystemAssemblyStorageMap : Ptr32 Void
   +0x208 MinimumStackCommit : Uint4B
0:000>

We just dumped out the structure information for the _PEB structure defined by Windows. We will now attempt to find our PEB and dump this address.

Collapse
0:000> !teb
TEB at 7ffde000
    ExceptionList:        0006ffb0
    StackBase:            00070000
    StackLimit:           0005f000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 7ffde000
    EnvironmentPointer:   00000000
    ClientId:             00000b80 . 00000f40
    RpcHandle:            00000000
    Tls Storage:          00000000
    PEB Address:          7ffdf000
    LastErrorValue:       0
    LastStatusValue:      c0000034
    Count Owned Locks:    0
    HardErrorMode:        0
0:000> dt ntdll!_PEB 7ffdf000
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
   +0x003 SpareBool        : 0 ''
   +0x004 Mutant           : 0xffffffff
   +0x008 ImageBaseAddress : 0x01000000
   +0x00c Ldr              : 0x00191ea0
   +0x010 ProcessParameters : 0x00020000
   +0x014 SubSystemData    : (null)
   +0x018 ProcessHeap      : 0x00090000
   +0x01c FastPebLock      : 0x77fc49e0
   +0x020 FastPebLockRoutine : 0x77f5b2a0
   +0x024 FastPebUnlockRoutine : 0x77f5b380
   +0x028 EnvironmentUpdateCount : 1
   +0x02c KernelCallbackTable : 0x77d42a38
   +0x030 SystemReserved   : [1] 0
   +0x034 ExecuteOptions   : 0y00
   +0x034 SpareBits        : 0y000000000000000000000000000000 (0)
   +0x038 FreeList         : (null)
   +0x03c TlsExpansionCounter : 0
   +0x040 TlsBitmap        : 0x77fc4680
   +0x044 TlsBitmapBits    : [2] 0x7ff
   +0x04c ReadOnlySharedMemoryBase : 0x7f6f0000
   +0x050 ReadOnlySharedMemoryHeap : 0x7f6f0000
   +0x054 ReadOnlyStaticServerData : 0x7f6f0688  -> (null)
   +0x058 AnsiCodePageData : 0x7ffb0000
   +0x05c OemCodePageData  : 0x7ffc1000
   +0x060 UnicodeCaseTableData : 0x7ffd2000
   +0x064 NumberOfProcessors : 1
   +0x068 NtGlobalFlag     : 0x70
   +0x070 CriticalSectionTimeout : _LARGE_INTEGER 0xffffe86d`079b8000
   +0x078 HeapSegmentReserve : 0x100000
   +0x07c HeapSegmentCommit : 0x2000
   +0x080 HeapDeCommitTotalFreeThreshold : 0x10000
   +0x084 HeapDeCommitFreeBlockThreshold : 0x1000
   +0x088 NumberOfHeaps    : 5
   +0x08c MaximumNumberOfHeaps : 0x10
   +0x090 ProcessHeaps     : 0x77fc5a80  -> 0x00090000
   +0x094 GdiSharedHandleTable : 0x00360000
   +0x098 ProcessStarterHelper : (null)
   +0x09c GdiDCAttributeList : 0x14
   +0x0a0 LoaderLock       : 0x77fc1774
   +0x0a4 OSMajorVersion   : 5
   +0x0a8 OSMinorVersion   : 1
   +0x0ac OSBuildNumber    : 0xa28
   +0x0ae OSCSDVersion     : 0x100
   +0x0b0 OSPlatformId     : 2
   +0x0b4 ImageSubsystem   : 2
   +0x0b8 ImageSubsystemMajorVersion : 4
   +0x0bc ImageSubsystemMinorVersion : 0
   +0x0c0 ImageProcessAffinityMask : 0
   +0x0c4 GdiHandleBuffer  : [34] 0
   +0x14c PostProcessInitRoutine : (null)
   +0x150 TlsExpansionBitmap : 0x77fc4660
   +0x154 TlsExpansionBitmapBits : [32] 0
   +0x1d4 SessionId        : 0
   +0x1d8 AppCompatFlags   : _ULARGE_INTEGER 0x0
   +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER 0x0
   +0x1e8 pShimData        : (null)
   +0x1ec AppCompatInfo    : (null)
   +0x1f0 CSDVersion       : _UNICODE_STRING "Service Pack 1"
   +0x1f8 ActivationContextData : 0x00080000
   +0x1fc ProcessAssemblyStorageMap : 0x000929a8
   +0x200 SystemDefaultActivationContextData : 0x00070000
   +0x204 SystemAssemblyStorageMap : (null)
   +0x208 MinimumStackCommit : 0
0:000>

If you remember correctly, the !teb gives you the “thread environment block”. A part of this block will show you the PEB for a process. Now, you see that not only does it print the information contained in the data structure, it also knows how to display the information based upon the type of data. Why did I show you this? This is because of what I specified above. The first thing people do or want to do is start to write debug extensions to dump all their data structures and this is not feasible anymore! You then have to keep changing your debug code every time you add or move information around. It’s best to use the “dt” command and painlessly find all the data contained in any of your internal data structures.

Starting To Write The Extension

So, my proposed extension is !dumpstrings. This will go through a memory location and dump all the pointers. I can then do !dumpstrings esp and dump the strings to all pointers on the stack.

First, let’s start with how you write the extension. WINDBG requires you at least export two functions. These functions in my source are posted below:

Collapse
/***********************************************************
 * ExtensionApiVersion
 *
 * Purpose: WINDBG will call this function to get the version
 *          of the API
 *
 *  Parameters:
 *     Void
 *
 *  Return Values:
 *     Pointer to a EXT_API_VERSION structure.
 *
 ***********************************************************/              
LPEXT_API_VERSION WDBGAPI ExtensionApiVersion (void)
{
    return &g_ExtApiVersion;
}

/***********************************************************
 * WinDbgExtensionDllInit
 *
 * Purpose: WINDBG will call this function to initialize
 *          the API
 *
 *  Parameters:
 *     Pointer to the API functions, Major Version, Minor Version
 *
 *  Return Values:
 *     Nothing
 *
 ***********************************************************/              
VOID WDBGAPI WinDbgExtensionDllInit (PWINDBG_EXTENSION_APIS 
           lpExtensionApis, USHORT usMajorVersion, 
           USHORT usMinorVersion)
{
     ExtensionApis = *lpExtensionApis;
}

The first function, ExtensionApiVersion, simply returns a version, and all we do is supply version numbers that WINDBG would expect to make it happy. Here is the declaration of g_ExtApiVersion.

Collapse
/***********************************************************
 * Global Variable Needed For Versioning
 ***********************************************************/              
EXT_API_VERSION g_ExtApiVersion = {
         5 ,
         5 ,
         EXT_API_VERSION_NUMBER ,
         0
     } ;

The EXT_API_VERSION_NUMBER is declared in wdbgexts.h. Please note that there are other variations of debugger extension DLLs, such as usingntsdexts.h. I am going over just one simple example that works with the current CDB and WinDbg debuggers that you can download off of Microsoft’s web site. I am also using windbgexts.h, not ntsdexts.h. If you look at your SDK include files, you will notice you have both headers.

So, how is EXT_API_VESRION_NUMBER declared? On my system, it is declared as:

Collapse
#define EXT_API_VERSION_NUMBER   5

typedef struct EXT_API_VERSION {
    USHORT  MajorVersion;
    USHORT  MinorVersion;
    USHORT  Revision;
    USHORT  Reserved;
} EXT_API_VERSION, *LPEXT_API_VERSION;

Where did you come up with 5, 5? I just debugged some of the other extensions that come with WINDBG to find those numbers. I also found that older WINDBGs use 3, 5. This is really a moot point though, we just need a basic frame work written, after which we can simply write all the commands we want! I’m not big on making things like this big issues or digging deep into their meaning when, it’s really irrelevant to me as long as my extension loads.

The WinDbgExtensionDllInit API simply gives your application a virtual function table. This table *must* be named a certain name. Well, it doesn’t have to be but it’s easier when it is. The reason is that the windbgexts.h contains macros to make function calls to functions stored in this structure. If you don’t use the same name, you’ll have to create the calls yourself. This is my global in the code:

Collapse
/***********************************************************
 * Global Variable Needed For Functions
 ***********************************************************/              
WINDBG_EXTENSION_APIS ExtensionApis = {0};

It’s really no big deal, the macros just make it a little easier, that’s all. You can do whatever you like though. This is the dump of WINDBGEXTS.Hmacros:

Collapse
#define dprintf          (ExtensionApis.lpOutputRoutine)
#define GetExpression    (ExtensionApis.lpGetExpressionRoutine)
#define GetSymbol        (ExtensionApis.lpGetSymbolRoutine)
#define Disassm          (ExtensionApis.lpDisasmRoutine)
#define CheckControlC    (ExtensionApis.lpCheckControlCRoutine)
#define ReadMemory       (ExtensionApis.lpReadProcessMemoryRoutine)
#define WriteMemory      (ExtensionApis.lpWriteProcessMemoryRoutine)
#define GetContext       (ExtensionApis.lpGetThreadContextRoutine)
#define SetContext       (ExtensionApis.lpSetThreadContextRoutine)
#define Ioctl            (ExtensionApis.lpIoctlRoutine)
#define StackTrace       (ExtensionApis.lpStackTraceRoutine)

What’s next? Aside from these two APIs, you can have a CheckVersion() function to force your extension to only use commands on certain versions of WINDBG. I found this completely useless and didn’t implement it as it’s not required. So, let’s just write a function!

The first function will be simple. We’ll do “!help” which will dump the help.

Collapse
/***********************************************************
 * !help
 *
 * Purpose: WINDBG will call this API when the user types !help
 *          
 *
 *  Parameters:
 *     N/A
 *
 *  Return Values:
 *     N/A
 *
 ***********************************************************/
DECLARE_API (help)
{
    dprintf("Toby's Debug Extensionsnn");
    dprintf("!dumpstrings <ADDRESS register> - Dumps 20 strings in"
       "ANSI/UNICODE using this address as a pointer to strings (useful for" 
       "dumping strings on the stack) n");
       /* String Split so it is readable in this article. */
}

dprintf(); is basically, “debug printf” and it’s exactly like printf()! It will print output to the debugger. The DECLARE_API(<command>) is a simple way to declare the API name. Remember, the name you give the function is the name you use in the debugger. I called this help, so to use it, it’s !help or !<dllname>.help. This is a simple function that will simply display a help message to the user.

The next thing we want to do is implement our string function. This function will take a parameter which will be a memory address. I also want it to work like current commands, if you do dc <address> then dc again, it will continue where it left off dumping the memory. So, let’s see what I’ve come up with.

Collapse
/***********************************************************
 * !dumpstrings
 *
 * Purpose: WINDBG will call this API when the user types !dumpstrings
 *          
 *
 *  Parameters:
 *     !dumpstrings or !dumpstrings <ADDRESS register>
 *
 *  Return Values:
 *     N/A
 *
 ***********************************************************/
DECLARE_API (dumpstrings)
{
    static ULONG Address = 0;
    ULONG  GetAddress, StringAddress, Index = 0, Bytes;
    WCHAR MyString[51] = {0};

    GetAddress = GetExpression(args);

    if(GetAddress != 0)
    {
        Address = GetAddress;
    }

    dprintf("STACK   ADDR   STRING n");

    for(Index = 0; Index < 4*20; Index+=4)
    {
        memset(MyString, 0, sizeof(MyString));

        Bytes = 0;

        ReadMemory(Address + Index, &StringAddress, 
                           sizeof(StringAddress), &Bytes);

        if(Bytes)
        {
           Bytes = 0;

           ReadMemory(StringAddress, MyString, 
                 sizeof(MyString) - 2, &Bytes);

           if(Bytes)
           {
              dprintf("%08x : %08x = (UNICODE) "%ws"n", 
                       Address + Index, StringAddress, MyString);
              dprintf("%08x : %08x = (ANSI)    "%s"n", 
                       Address + Index, StringAddress, MyString);
           }
           else
           {
              dprintf("%08x : %08x =  Address Not Validn", 
                             Address + Index, StringAddress);
           }
        }
        else
        {
           dprintf("%08x : Address Not Validn", Address + Index);
        }
    }

    Address += Index;
}

So, the first function I use is GetExpression(). On the newer WINDBGs, it works like this. ADDRESS GetExpression(SYMBOLIC STRING). You pass in a symbolic string, such as the arguments to the command and it attempts to resolve it to an address. The arguments to the command are stored in args, so I pass in args. This will resolve symbols, addresses or even registers to numbers as would be with the case of passing in ESP.

I now have a static variable. If GetExpression() returned 0, it’s possible there are no arguments. If this is the case, I use Address, my static variable. This works if someone does !dumpstrings. It will continue where it left off. At the end of the function, I always save Address as being the next location to dump.

The next function I use is dprintf() which works just like using printf. I’ve explained this one already, so onto the next tidbit. Addresses are 4 bytes long, so I will increment this address by 4 each time around in the loop. I want to dump 20 addresses, so I loop from 0 to 4*20. Very simple indeed.

Now, you cannot simply reference the memory returned as you are not in the process space of the application. So, WINDBG provides functions to do this such as ReadMemory(). (The windbgexts.h provides prototypes for all the APIs, so you can experiment if you cannot find the API online.) TheReadMemory() function takes 4 arguments:

Collapse
ReadMemory(Address In Process To Read,
           Local Variable to store the memory read, 
           size of the local variable, 
           pointer to a DWORD that returns the number 
           of bytes read from the memory location);

So, we pass in our pointer to the memory in the application, a pointer to place the data on return, then receive the number of bytes. If no bytes are returned, we print an invalid memory address; if the bytes are returned, we then reference this memory location to read up to 49 bytes (we have 51 and put two NULLs at the end for Unicode support). If I was able to read anything, I then attempt to display it using the dprintf() function in ANSI then in UNICODE. If the memory returns 0 bytes, I print an error specifying it’s not a valid address. This is all very simple.

The next thing we need to do is create our .DEF file. This file will simply export our functions.

Collapse
LIBRARY "TDBG.DLL"

EXPORTS
    WinDbgExtensionDllInit
    ExtensionApiVersion
    dumpstrings
    help

Now, we need to build it. I like using make files and I use Visual Slickedit as my editor. I don’t use the VC++ IDE at all. So, in the project, I’ve created a make file. This is how you would set it up. First, run VCVARS32.BAT which is located in the BIN directory of your VC++ installation. I moved mine toC: for easier use. The next thing to do is simply type “nmake” in the directory that the source code was extracted to.

Collapse
C:ProgrammingProgramsdebugextsrcdebug>vcvars32
Setting environment for using Microsoft Visual C++ tools.
C:ProgrammingProgramsdebugextsrcdebug>nmake

Microsoft (R) Program Maintenance Utility   Version 6.00.8168.0
Copyright (C) Microsoft Corp 1988-1998. All rights reserved.

        cl /nologo /MD /W3 /Oxs /Zi  /I ".inc"  /D "WIN32" /DLL /D "_WINDOWS"
/Fr.obji386\ /Fo.obji386\ /Fd.obji386\ /c .tdbg.c
tdbg.c
C:PROGRA~1MICROS~2VC98INCLUDEwdbgexts.h(526) : warning C4101: 'li' : unrefe
renced local variable
        link.exe /DLL /nologo /def:tdbg.def /out:....bintdbg.dll  /pdb:tdbg.p
db /debug /debugtype:both USER32.LIB  KERNEL32.LIB .obji386tdbg.obj
   Creating library ....bintdbg.lib and object ....bintdbg.exp
        rebase.exe -b 0x00100000 -x ....bin -a ....bintdbg.dll

REBASE: Total Size of mapping 0x00010000
REBASE: Range 0x00100000 -0x00110000

C:ProgrammingProgramsdebugextsrcdebug>nmake clean

Microsoft (R) Program Maintenance Utility   Version 6.00.8168.0
Copyright (C) Microsoft Corp 1988-1998. All rights reserved.

Deleted file - C:ProgrammingProgramsdebugextsrcdebugobji386tdbg.obj
Deleted file - C:ProgrammingProgramsdebugextsrcdebugobji386tdbg.sbr
Deleted file - C:ProgrammingProgramsdebugextsrcdebugobji386vc60.pdb

C:ProgrammingProgramsdebugextsrcdebug>

If you want to build again, you’d need to either change the source so the date is updated or type “nmake clean” to rebuild. You should now have a binary.

Collapse
 Volume in drive C has no label.
 Volume Serial Number is 2CF8-F7B5

 Directory of C:ProgrammingProgramsdebugextbin

03/25/2004  08:56 PM    <DIR>          .
03/25/2004  08:56 PM    <DIR>          ..
03/25/2004  09:53 PM             2,412 tdbg.dbg
03/25/2004  09:53 PM            20,752 tdbg.dll
03/25/2004  09:53 PM             1,044 tdbg.exp
03/25/2004  09:53 PM             2,538 tdbg.lib
03/25/2004  09:53 PM            82,944 tdbg.pdb
               5 File(s)        109,690 bytes
               2 Dir(s)  12,229,009,408 bytes free

Simply copy this binary to a location that can be found by your WinDbg, such as your windows directory. You can specify the path to load these extensions using !load and !unload (to unload the extension if you built a new version/ want to force the debugger to unload it so you can reload the next one). You can use !<dll name>.<function name> or just !<function name>. The mentioning of the binary name will force WINDBG to look for it to load it. It can also help to distinguish between which DLL to use for a command if multiple DLL extensions export it.

Let’s Try It Out

Now that we have created our debug extension, I move it to a location where WINDBG can load it (such as my windows directory). I then use “!tdbg.dumpstrings esp” to dump all the strings on the stack. This is the same application (restarted so addresses may change) restarted (I verified with dc esp, I get the same strings on the stack as before). I now want to dump all the pointers to strings on the stack. Let’s try this and see what happens!

Collapse
0:000> !tdbg.dumpstrings esp
STACK   ADDR   STRING
0006febc : 77d43a09 = (UNICODE) ""
0006febc : 77d43a09 = (ANSI)    "┬►"
0006fec0 : 77d43c7d = (UNICODE) ""
0006fec0 : 77d43c7d = (ANSI)    "�N♦�∙☻☺"
0006fec4 : 0006fefc = (UNICODE) ""
0006fec4 : 0006fefc = (ANSI)    "▐♥↔"
0006fec8 : 00000000 =  Address Not Valid
0006fecc : 00000000 =  Address Not Valid
0006fed0 : 00000000 =  Address Not Valid
0006fed4 : 00000000 =  Address Not Valid
0006fed8 : 0006ff1c = (UNICODE) ""
0006fed8 : 0006ff1c = (ANSI)    "└ ♠"
0006fedc : 010028e4 = (UNICODE) ""
0006fedc : 010028e4 = (ANSI)    "�└u�Φ├∩   5�"
0006fee0 : 0006fefc = (UNICODE) ""
0006fee0 : 0006fefc = (ANSI)    "▐♥↔"
0006fee4 : 00000000 =  Address Not Valid
0006fee8 : 00000000 =  Address Not Valid
0006feec : 00000000 =  Address Not Valid
0006fef0 : 00000000 =  Address Not Valid
0006fef4 : 77e7ad86 = (UNICODE) ""
0006fef4 : 77e7ad86 = (ANSI)    "�|$♦"
0006fef8 : 00091ee8 = (UNICODE) ""
0006fef8 : 00091ee8 = (ANSI)    ""
0006fefc : 001d03de = (UNICODE) ""
0006fefc : 001d03de = (ANSI)    ""
0006ff00 : 00000118 =  Address Not Valid
0006ff04 : 0000ffff =  Address Not Valid
0006ff08 : bf8a75ed =  Address Not Valid
0:000> !tdbg.dumpstrings
STACK   ADDR   STRING
0006ff0c : 077d5cc8 =  Address Not Valid
0006ff10 : 000001f3 =  Address Not Valid
0006ff14 : 000001df =  Address Not Valid
0006ff18 : 00000000 =  Address Not Valid
0006ff1c : 0006ffc0 = (UNICODE) ""
0006ff1c : 0006ffc0 = (ANSI)    "≡ ♠"
0006ff20 : 01006c54 = (UNICODE) ""
0006ff20 : 01006c54 = (ANSI)    "�≡�u�9]ΣuV �▄↕"
0006ff24 : 01000000 = (UNICODE) ""
0006ff24 : 01000000 = (ANSI)    "MZ�"
0006ff28 : 00000000 =  Address Not Valid
0006ff2c : 00091ee8 = (UNICODE) ""
0006ff2c : 00091ee8 = (ANSI)    ""
0006ff30 : 0000000a =  Address Not Valid
0006ff34 : 00000000 =  Address Not Valid
0006ff38 : 00000000 =  Address Not Valid
0006ff3c : 7ffdf000 = (UNICODE) ""
0006ff3c : 7ffdf000 = (ANSI)    ""
0006ff40 : 80543940 =  Address Not Valid
0006ff44 : f4910c5c =  Address Not Valid
0006ff48 : 00000044 =  Address Not Valid
0006ff4c : 00092b30 = (UNICODE) ""
0006ff4c : 00092b30 = (ANSI)    ""
0006ff50 : 00092b50 = (UNICODE) ""
0006ff50 : 00092b50 = (ANSI)    "WinSta0Default"
0006ff54 : 00092b78 = (UNICODE) ""
0006ff54 : 00092b78 = (ANSI)    "C:WINDOWS.0System32notepad.exe"
0006ff58 : 00000000 =  Address Not Valid
0:000> !tdbg.dumpstrings
STACK   ADDR   STRING
0006ff5c : 00000000 =  Address Not Valid
0006ff60 : 00000000 =  Address Not Valid
0006ff64 : 00000000 =  Address Not Valid
0006ff68 : 00000000 =  Address Not Valid
0006ff6c : 00000000 =  Address Not Valid
0006ff70 : 00000000 =  Address Not Valid
0006ff74 : 00000000 =  Address Not Valid
0006ff78 : 00000000 =  Address Not Valid
0006ff7c : 00000000 =  Address Not Valid
0006ff80 : ffffffff =  Address Not Valid
0006ff84 : ffffffff =  Address Not Valid
0006ff88 : ffffffff =  Address Not Valid
0006ff8c : 00091ee8 = (UNICODE) ""
0006ff8c : 00091ee8 = (ANSI)    ""
0006ff90 : 00000000 =  Address Not Valid
0006ff94 : 00000001 =  Address Not Valid
0006ff98 : 00272620 = (UNICODE) ""
0006ff98 : 00272620 = (ANSI)    "(&'"
0006ff9c : 00272d00 = (UNICODE) ""
0006ff9c : 00272d00 = (ANSI)    "░-'"
0006ffa0 : 00000000 =  Address Not Valid
0006ffa4 : 00000000 =  Address Not Valid
0006ffa8 : 0006ff34 = (UNICODE) ""
0006ffa8 : 0006ff34 = (ANSI)    ""

We found two strings on the stack. Not bad and all I did was start Notepad. I didn’t do anything else. This is a simple example of how this API could be used. Take a memory location and dump out all the pointers to strings. You are always going to get garbage and invalid address dumped on the stack (or any memory location for that matter). It’s the valid addresses that contain strings that we are looking for.

Conclusion

I hope you enjoyed learning how to write a debug extension and hopefully even find the example useful! There are other APIs listed which you can look up or perhaps another tutorial can explain them in the future. The basics of reading memory and displaying information were covered. Hopefully, in the future, we can explore more advanced debugging commands

 

Posted in TUTORIALS | Tagged: , , , , , , | Leave a Comment »

SSIS Script Task

Posted by Alin D on October 27, 2010

SQL Server 2008 Integration Services contains an assortment of predefined Control Flow tasks designed to carry out specific types of actions. Collectively, they cover a wide range of scenarios; however, their specialized nature sometimes turns out to be overly restrictive. This article explains how to accommodate these custom requirements by employing the considerably more flexible Script Task.

SQL Server 2008 Integration Services contains a diverse assortment of predefined Control Flow tasks, which are designed to carry out specific types of actions. While collectively they cover a wide range of scenarios involving data extraction, transformation, and loading, their specialized nature sometimes turns out to be overly restrictive. In situations like these, it is usually possible to accommodate custom requirements by employing the considerably more flexible Script Task. In this article, we will cover its most relevant features (it is important to note that its purpose is quite different from the Data Flow-based Script Component, whose characteristics we are also planning to present on this forum).

It is hard to overstate the flexibility of the Script Task, considering that boundaries of its capabilities are defined primarily by your ingenuity, skills, and .NET programming model (starting with SQL Server 2008 Integration Services, it became possible to use Microsoft Visual C#, in addition to Microsoft Visual Basic available in its predecessor). The task operates essentially as a wrapper of managed code with access to SSIS-specific objects, including their methods and properties, interacting with a parent container (and other SSIS entities) through arbitrarily chosen System and User variables. Its modifications are handled using Visual Studio Tools for Applications (VSTA) replacing Visual Studio for Applications (VSA) present in earlier versions (which, incidentally, was the primary obstacle to providing support for Visual C#). The VSTA interface offers visual enhancements standard in practically every current software development environment such as color-coding or IntelliSense as well as debugging functionality including breakpoints (which integrate with breakpoint indicators of SSIS tasks and containers) or Immediate and Output windows. Furthermore, it simplifies referencing Dynamic Linked Libraries as well as making it possible to directly reference Web services and COM modules, eliminating the need for the creation of proxy classes and interop assemblies or for copying them into Global Assembly Cache and Microsoft.NETFramework folders (which used to be the case when working with VSA). The resulting code is precompiled into binary format (VSA was more flexible in this regard, giving you an option to postpone compilation until execution), effectively shortening total runtime of the package (although at the cost of its overall size).

In order to identify the most relevant characteristics of Script Task, let’s examine in more detail its interface exposed in the Business Intelligence Development Studio. Create a new project based on the Integration Services template, add the task to its Designer window (by dragging its icon from the Toolbox), and display its Editor dialog box (by selecting Edit entry from its context sensitive menu), which is divided into three sections:

  • Script section – containing the following elements:
    • ScriptLanguage textbox – designates the programming language (Microsoft Visual Basic 2008 or Microsoft Visual C# 2008) in which code contained within the task is written. Make sure to choose the intended entry before you activate the Visual Studio Tools for Applications interface (by clicking on the Edit Script… command button), since at that point, you will no longer be able to alter your selection (this action triggers auto generation of the ScriptMain class based on a built-in template using the language of your choosing).
    • EntryPoint textbox – identifies a method (which must be defined as part of the ScriptMain class in the VSTA-based project) that is invoked when the Script Task executes (set by default to Main)
    • ReadOnlyVariables and ReadWriteVariables textboxes – determines which SSIS variables will be accessible from within the script by listing their names (as comma-separated entries in the format namespace::variable name). While it is possible to type them in, the most straightforward (and the least error prone – since they are case sensitive) approach involves pointing them out directly in the Select Variables dialog box accessible via the ellipsis (...) command button located next to each textbox. Another approach to specifying SSIS variables that can be either viewed or modified within a Script Task leverages LockForRead and GetVariables methods of VariableDispenser object (we will explore it in more detail in our future articles), however it is important to realize that these methods are mutually exclusive (an attempt to reference the same variable using both will result in an error).
    • Edit Script… command button – triggers display of Microsoft Visual Studio Tools for Applications 2.0 Integrated Development Environment with the majority of its desktop estate occupied by the window containing the task code. In addition, you will also find Properties and Project Explorer windows, where the latter references the newly created Script Task via automatically generated identifier (which guarantees its uniqueness and therefore should not be changed). Effectively, content of the task constitutes a separate project, with its own set of properties (independent from characteristics of the SSIS project it is part of) accessible via its context sensitive menu and displayed in the tabbed window divided into the following sections:
      • Application – designates properties of the assembly (some of which, such as output file, name, and root namespace are derived from the auto generated identifier of the script task). In general, settings on this page are intended to create independent assemblies via a full-fledged edition of Visual Studio and therefore are not relevant in the context of our presentation (as a matter of fact, most of them are grayed out because of their read-only status), although you have the ability to customize Assembly Information (including such parameters as Title, Description, Company, Product, Copyright, Trademark, Assembly Version, File Version, GUID, or Neutral Language) as well as Make assembly COM-Visible.
      • Compile – allows you to modify the Build output path (by default pointing to bin subfolder), Compile option (such as Option explicit, Option strict, Option compare, and Option infer), a number of Warning configurations, settings such as Disable all warnings, Treat all warnings as errors, Generate XML documentation file, or Register for COM interop, as well as a number of Advanced Compile Options (for example, defining DEBUG, TRACE, or custom constants).
      • Debug – provides the ability to assign Command line arguments and Working directory for debug start options.
      • References – likely the most helpful feature available via the Properties window, considerably simplifies adding assembly references to your projects (replacing cumbersome process implemented in previous version of Visual Studio for Applications of SQL Server 2005 Integration Services) as well as identifying and removing unused ones.
      • Resources – facilitates management of project resources, such as strings or bitmap, icon, audio, and text files. This functionality is intended for development of standalone programs and is not applicable here.
      • Settings – defines project’s application settings and similar to the Resources page, contains entries pertinent to development of independent applications.
      • Signing – provides the ability to sign assemblies (which requires a strong name key file), which is not relevant in this context.

  • General section – containing the following entries:
    • Name – allows customizing the name of the Script Task (which happens to be its default) in order to improve readability of your package. Note that its value must be unique within a package.
    • Description – intended for additional information helping you make packages self-documenting.

  • Expressions section – provides the ability to assign values of Script Task properties , by dynamically evaluating associated expressions (rather than specifying their values directly).

Posted in SQL | Tagged: , , , , , , , , , , , , , | Leave a Comment »

How to Manage Multiple Mailboxes

Posted by Alin D on October 19, 2010

The other day I was using one of my email accounts to respond to a forum that I don’t use very often. And then later in the day I needed to switch to another email account that I use to purchase products. That’s how I started thinking about the number of email accounts I use for various purposes and how often I sometimes have to switch back and forth between them.

Having multiple email accounts has been a benefit to me since it allows me to communicate  with known and unknown persons or companies using a range of identities for protection. The multiple email accounts can buffer my personal email accounts from spam and other malicious email attacks. I know many users have multiple email accounts which they keep separate from their company or organization’s email account.

It is also likely that your company or organization even encourages your end users to maintain more than one email account to serve a variety of business related purposes. In this economy there are many employees who are performing multiple tasks and have more than one business role in their company. So it is necessary for email administrators to educate their end users on how to access and manage their multiple email accounts when using one email client such as Microsoft Outlook.

In Allison Nunn’s blog, Using Outlook to Manage Multiple Email Accounts, Allison explains how to access email using the Post Office Protocol (POP3) which is used by many email clients.

In her blog post, Allison mentions a couple things to consider before setting up the email clients to access the multiple mailbox accounts:

  • “All email will be delivered to one inbox unless another Outlook Data File is created for the account and a rule created to move the message to the data file. If storing all (inbound) email in one data file is acceptable then a rule can be created to move the email to a folder to keep accounts separate.”
  • “When creating or replying to an email you need to select which (email account should be used to send the email). This is accomplished by selecting Accounts in the mail compose windows and selecting the account to use.”

Allison then describes how to set up additional email accounts and also offers some other tips regarding outgoing authentication and server port numbers.

Another task which an email administrator can perform is to configure Outlook to access two or more Exchange Server mailboxes from one end user’s profile. This will allow your end users to access all of their Microsoft Outlook accounts without having to log on separately to each account.

An administrator can perform the following steps to configure Microsoft Outlook 2010 such that an end user can access two or more Exchange Server mailboxes from one profile. These steps assume there are two mailbox accounts A and B on the same Exchange Server.

  1. Open Outlook with the profile the profile associated with mailbox B for the Exchange Server. If validation is required then log on to the network as the user of account B.
  2. From the Files menu, click Info, and then click Account Settings.
  3. Click Delegate Access.
  4. Click Add from the Delegates tab.
  5. Select the name for the user of account A.
  6. Click Add, and then click OK.
  7. Click to select Editor (read, create, modify) in the Delegate Permissions dialog box and then click OK two times.
  8. You may need to click Folder List on the View menu if it is not displayed.
  9. Right click on Mailbox and then click Properties for the appropriate username.
  10. From the Permissions tab, click Add.
  11. Select the name for the user of account A.
  12. Click Add, and then click OK.
  13. Click on the newly added entry for account A in the name box.
  14. Click on the Owner in the Permission Level box and then click OK.
  15. Repeat steps 8 through 13 for all the other folders in the mailbox.
  16. Exit, Log Off and the restart Windows.
  17. After Windows restarts log back on as the user of account A.
  18. Start Outlook with a profile for account A.
  19. From the Files menu, click Info.
  20. Click Account Settings.
  21. From the Email tab, select the name of the account, and then click the Change icon.
  22. Click the More Settings button and then the Advanced tab.
  23. Click Add to add an account to the Open these additional mailboxes: list.
  24. Enter the name of the user for account B.
  25. Click OK three times.
  26. Click Next, Finish, and then click Close.

You will now be able to view the mailbox for account B from the Folder List. Messages can now be sent as coming from account A or B be entering the name of the user in the From Field.

Posted in Exchange | Tagged: , , , , , , | Leave a Comment »

Configure Single Sign-On to Remote Desktop Services

Posted by Alin D on August 20, 2010

Introduction

To increase security in your terminal services environment, you should consider using Single Sign-On (SSO) technology. Because of the complexity of IT systems and network environments, many companies suffer from supporting multiple authentication methods across multiple (and sometimes disparate) systems. Distributed environments pose many challenges. Since distributed environments imply independent security domains, it is important to view SSO as a way to create one single security domain (adding all secondary domains) for ease of access and manageability. The goal is to create the appearance of a centralized system if you cannot create a single instance in the first place. Many users typically have to sign-on to multiple systems when they would only need to sign on one time with one set of credentials using SSO. SSO works to help to alleviate these issues.

What is SSO? SSO is defined as a process or solution that will create a single ‘authentication’ of a user valid to all other participating systems. SSO will allow a user to authenticate and have authorization to permit or access all computers and/or systems where he/she has access permission without the need to enter multiple passwords. The only main disadvantage to this design is that when using SSO technology, although you create an architecture that is ‘user friendly’, the issue you may face is that all users, hosts and applications must ‘trust’ the same authentication mechanism. If this authentication mechanism is secure, and you are testing it correctly, then you should not face any issues using SSO technology.

When working with Terminal Services, SSO can be very desirable to help aid the complexity of users trying to attach to a system and having to log on every time they attempt to connect to a system that requires authentication. With Windows Server 2008, SSO can be implemented with the Terminal Services Role very easily.

Using SSO with Terminal Services

Single Sign-On (SSO) is an authentication method that allows users with a domain account to log on once. They do this by using pre-established credentials (a password or smart card) to access systems without being asked for credentials multiple times. To implement single sign-on functionality in Terminal Services, ensure that you meet (and for production systems – exceed) the minimum requirements. The basic requirements needed to implement SSO are:

  • You can only use single sign-on for remote connections from a computer running Windows Vista or Windows Server 2008 to a Windows Server 2008 Terminal Server.
  • You must ensure that the user accounts that are used for logging on to the Terminal Server have appropriate rights to log on to both the Terminal Server and the Windows Vista/2008 client computer.
  • Your client computer and Terminal Server must be joined to a domain. In this example, in this article we are using the TESTDOM domain.

Understanding what the basic requirements are, more specific requirements for setting up SSO, is as follows:

Servers:

  • Windows Server 2008 Terminal Server with TS Server Role and TS Licensing Server Role enabled
  • Windows Server 2008 Domain Controller (Active Directory)
  • Proper Hardware Requirements

Note:
Although you can make a DC a Terminal Server, it is recommended that you split the roles and use separate servers based on the load that is expected. Obviously, whenever implementing a production system, you will want to make sure that you know what your application/traffic flows are and what load your users and the application puts on your network as well as the individual servers connected to it.

Clients:

  • Windows Vista (or Windows Server 2008 used as a client system)
  • Remote Desktop Client (RDC) with Network Level Authentication (NLA) Support. NLA support is only available with RDC 6.0 and with Vista or 2008.
  • Proper Hardware Requirements (exceed as needed)

A layout of the test lab used to simulate this exercise is seen in Figure 1.


Figure 1: The Terminal Server Test Lab

To configure the recommended settings for your Terminal Server, complete the following steps:

  • Configure authentication on the Terminal Server, this can be done with AD, or locally on the server you want access to.
  • Configure the computer running Windows Vista to allow default credentials to be used for logging on to the specified Terminal Server(s) on your network.
  • You need administrative privileges on the Terminal Server you are configuring.

Now that you know what you need, let us begin configuring SSO with Windows Server 2008 Terminal Services.

Configure Authentication on a Terminal Server

First, verify you have a working Terminal Server. Check the Server Manager as seen in Figure 2 to verify that you have the correct Terminal Services roles installed and operational. Remember, you will need to have (at minimum), the Terminal Services Role and the Licensing Server Role installed and ready to configure SSO.


Figure 2: Viewing Server Manager

Next, we will configure Single Sign-On (SSO) on the Terminal Server by opening Terminal Services Configuration. Go to Start => Administrative Tools => Terminal Services, and then click Terminal Services Configuration.

Once you open the Terminal Services Configuration console, find the Connections pane. You should, at minimum, have the default connection in place which should be RDP-Tcp. To configure this (or any other connection) right-click the appropriate connection and then click Properties, as seen in Figure 3.


Figure 3: Viewing the Connection

Once you open the Properties dialog box, on the General tab as seen in Figure 4, you can verify that the Security Layer value is set to either Negotiate or SSL (TLS 1.0). Negotiation will allow the system to ‘negotiate’ with a client what type of Security Layer is needed.


Figure 4: Configuring the Security Layer

Figure 5 shows you how to configure different Security Layers, to include RDP, SSL or Negotiate.


Figure 5: Adjusting the Security Layer for RDP, SSL or Negotiate

Once you finish selecting the Security Layer, click on the Log in Settings Tab as seen in Figure 6.


Figure 6: Configuring the Log on Settings

On the Log on Settings tab, ensure that the Always prompt for password check box is not selected or checked, and then click OK to close the RDP-Tcp Properties dialog box.

Now, you have configured authentication, next we will configure the default credential usage to be used with SSO.

Allow Default Credential Usage for Single Sign-On (SSO)

Now that we have authentication configured, we need to finish the process. To do this, you need to go to the client system (Vista, or 2008) and configure the Local Group Policy Editor. On your client computer open the Local Group Policy Editor. To open Local Group Policy Editor, go to Start, and in the Start Search box, type gpedit.msc and then press ENTER. This will launch the Local Group Policy Editor in a Microsoft Management Console (MMC) as see in Figure 7.


Figure 7: Configuring the Local Group Policy Editor (gpedit.msc)

In the Editor, look in the left pane and expand Computer Configuration => Administrative Templates => System => and then click Credentials Delegation. Double-click the Delegating Default Credentials setting to open it as seen in Figure 8.


Figure 8: Configuring the Default Credentials Setting

Next, in the Properties dialog box on the Setting tab, select Enabled, and then select Show. In the Show Contents dialog box, click Add to add servers to the list as seen in Figure 9. In the Add Item dialog box, type the prefix termsrv/ followed by the name of the Terminal Server you will be connecting too. In this example we would specify it as:

termsrv/USNYTS01


Figure 9: Adding a Server to the List

Once you have added the server name, click OK to close the Add Item dialog box. Click OK a few times until you are back in the Local Group Policy Editor and close the MMC.

Now you should be all ready to use SSO with Windows Server 2008 and 2008 Terminal Services.

Posted in Windows 2008 | Tagged: , , , , , , , , , , , , , , , , , , , , | Leave a Comment »