The App Compat Guy

Chris Jackson's Semantic Consonance

February, 2008

  • The App Compat Guy

    ShellRunAs v1.0

    • 17 Comments

    Today, Mark and Jon finally shipped ShellRunAs, a Sysinternals free tool that adds a "Run as Different User" option to the right-click context menu.

    Introduction

    The command-line Runas utility is handy for launching programs under different accounts, but it’s not convenient if you’re a heavy Explorer user. ShellRunas provides functionality similar to that of Runas to launch programs as a different user via a convenient shell context-menu entry.

    ShellRunas works on Windows XP, Windows Server 2003, Windows Vista, and Windows Server 2008.

    This has been a HUGE request from IT Pros, so please enjoy!

    ShellRunAs v1.0

  • The App Compat Guy

    My Worst Travel Day (So Far)

    • 2 Comments

    John Robbins asks, "What's your worst travel day?"

    Wow, he sure has me beat. I can't top that.

    My worst travel day so far was returning home from Taipei last year. We had been watching for days as they were reporting a typhoon might be heading towards Taiwan, and as the day of our departure approached, it looked as if it was pretty likely to come right across Taipei. Sure enough, the day I was supposed to leave, this was on my television:

    Yep, it was going to be a fun morning. Of course, there were 3 of us there, and we all kind of wanted to get home. Could we beat the storm?

    My friend Rick had left earlier in the day, long before I would ever want to wake up. We hadn't heard from him, so we assumed he must have flown out. So, my friend Mark and I (we were on the same flight) decided we may as well give it a go - the worst that could happen is that we would end up turning around and coming back.

    So, we hopped in a cab. The wind was blowing so hard that the prodigious rain was blowing sideways. This did not dissuade our driver from driving beyond quickly. Now, I've been in my share of white knuckle cabs (most recently, I shared one with folks in Barcelona), but there's something objectively different when you're on such a ride during a typhoon. So, we're zipping along, and Mark (who enjoys living) asks the driver to kindly slow down. He nods, and keeps going. A few minutes later, he asks him to slow down again. He acknowledges him again, and keeps going. Mark is occasionally looking at me with a "holy crap" look that's really hard to describe. We watch an SUV spin out of control directly in front of us. Mark offers to pay him money to slow down. This gets the cab slowed for a little while, but then back up to full speed. Finally, Mark decides to name a number, and pull out that much money and show him. "You can have this if you don't go over this speed." This finally works.

    We get to the airport whole, and a little surprised to be that way. Here, things are OK. We get our tickets, and head to the lounge. It is then that I realize that I have never emptied my safe at the hotel. So, I have left behind my cell phone, my travel wallet (fortunately no longer containing my passport), and my DVDs (there goes my entertainment for the flight home). There is nothing I need, so I don't have to go back and wait out the storm, but I figure if I am to stand a chance of getting anything back, I ought to call them. Convincing the lounge people to let me use the phone and explain my dilemma is my first challenge, but I eventually succeed. Then, I have to explain to the people at the hotel what I have done, without speaking their language. But they eventually just tell me to email such and such person at this email address to take care of it - they got tired of trying to communicate using the spoken word. So, I try to send the email, and naturally, it bounces. At this point, I've had enough, and I start rationalizing "well, I could use a new cell phone anyhow...."

    We eat a few tasty treats while waiting (mmm... dim sum...), and then head to the airplane to board. Once one the plane, Mark and I both agree that this is the worst turbulence we have ever felt while on an airplane. Which was surprising since we were still at the gate and actively boarding - we're both more used to turbulence while actually in the air. So, we had a bit of a white knuckle boarding and taxi, but takeoff was surprisingly smooth. A few bounces on the way up, and finally we could relax.

    Oh, and in the end, the hotel in Taipei shipped all of my belonging  to me. Grand Hyatt Taipei, I applaud and thank you.

    Such was my worst day of travel. I'm glad this is much less rattling than John's!

  • The App Compat Guy

    Discovering the Arguments Passed to Windows API Functions with Public Symbols

    • 5 Comments

    I've been talking at a bit of a higher level lately, and today I'm just in the mood to go into some of the deeper debugging aspect that you may run across when looking at Windows Vista application compatibility. (Well, perhaps not deep to some, but for most of us, we haven't spent much time in a low-level debugger lately so the skills have frequently atrophied somewhat.)

    When I talk about debugging for application compatibility, I always emphasize this point: the application itself is the same - Windows is now different. So the entry and exit points from Windows take on an exaggerated importance in these debugging sessions. However, internally, we have a bit of an advantage: those entry points are more expressive. You see, we have private symbols to Windows, so the Locals window in WinDbg is quite helpful. Everyone else just gets public symbols. So, those entry points are a little bit more mysterious. Of course, that doesn't mean you are stuck, it just means you have to do a touch more work.

    For this exercise, I'm just going to use an application that I know checks the version number in Windows, the MSDN library, and the Debugging Tools for Windows. We're going to get the same information internal folks get without requiring private symbols!

    Going in, I know that the application has an error where it tells me that it's expecting an older version of the operating system (in this case, Windows XP). Let's dissect that entry point.

    MSDN tells us that the function is implemented in Kernel32.dll, so let's set our breakpoint:

    0:000> bp kernel32!GetVersionExW

    Now, let's run it up to that breakpoint. While we're here, let's take a look at the function.

    0:000> uf kernel32!getversionexw
    kernel32!GetVersionExW:
    769248d8 8bff         mov     edi,edi
    769248da 55           push    ebp
    769248db 8bec         mov     ebp,esp
    769248dd 56           push    esi
    769248de 8b7508       mov     esi,dword ptr [ebp+8]
    769248e1 8b06         mov     eax,dword ptr [esi]
    769248e3 57           push    edi
    769248e4 bf1c010000   mov     edi,11Ch
    769248e9 3bc7         cmp     eax,edi
    769248eb 740b         je      kernel32!GetVersionExW+0x2b (769248f8)
    769248ed 3d14010000   cmp     eax,114h
    769248f2 0f858e8d0200 jne     kernel32!GetVersionExW+0x1c (7694d686)
    769248f8 56           push    esi
    769248f9 ff15f0139076 call    dword ptr [kernel32!_imp__RtlGetVersion (769013f0)]
    769248ff 85c0         test    eax,eax
    76924901 0f85868d0200 jne     kernel32!GetVersionExW+0x23 (7694d68d)
    76924907 393e         cmp     dword ptr [esi],edi
    76924909 7409         je      kernel32!GetVersionExW+0x3a (76924914)
    7692490b 33c0         xor     eax,eax
    7692490d 40           inc     eax
    7692490e 5f           pop     edi
    7692490f 5e           pop     esi
    76924910 5d           pop     ebp
    76924911 c20400       ret     4
    76924914 a046ef9c76   mov     al,byte ptr [kernel32!BaseRCNumber (769cef46)]
    76924919 88861b010000 mov     byte ptr [esi+11Bh],al
    7692491f ebea         jmp     kernel32!GetVersionExW+0x45 (7692490b)
    7694d686 6a7a         push    7Ah
    7694d688 e8eeeeffff   call    kernel32!SetLastError (7694c57b)
    7694d68d 33c0         xor     eax,eax
    7694d68f e97a72fdff   jmp     kernel32!GetVersionExW+0x25 (7692490e)

    We want to esablish the calling convention. If we were using _fastcall, we'd be passing arguments in registers (ECX and EDX), but we don't even use those registers here. We're calling ret 4 to unwind our own stack, so we can't be using a cdecl function (where the caller is responsible for unwinding the stack). We're looking at a stdcall function.

    (While you're looking at it - the first instruction - mov edi,edi - is an instruction that does absolutely nothing. It's a 2-byte filler. Why? To enable hot patching. With two byes, we can add a short jump, from which we can long jump to a new implementation of the function.)

    Now, we could set up our stack and begin looking at the arguments passed, but in this case, we don't really care much about what was passed. What's interesting is what we return. So, let's get out of this function and back into our main function and have a look.

    First, let's look at our return value. Again, looking at MSDN, we see that it returns a BOOL, returning non-zero upon success. This appears in the EAX register. Let's see how we did.

    0:000> r eax
    eax=00000001

    OK, so the function succeeded. However, you're probably far more interested in the LPOSVERSIONINFO argument that the function modified! How do we get at that?

    Well, first we need to know where it sits. Because the arguments are passed on the stack, the stack pointer tells us where to look. We look at ESP+8 ESP+4, and we'll begin to find our arguments. And, fortunately, we can use the dt command to format it nicely for us.

    0:000> dt OSVERSIONINFO esp+8
    DWM_Compositing_Rendering_Dem!OSVERSIONINFO
       +0x000 dwOSVersionInfoSize : 6
       +0x004 dwMajorVersion   : 0
       +0x008 dwMinorVersion   : 0x1771
       +0x00c dwBuildNumber    : 2
       +0x010 dwPlatformId     : 0x650053
       +0x014 szCSDVersion     : [128]  "rvice Pack 1"

    0:000> dt OSVERSIONINFO esp+4
    DWM_Compositing_Rendering_Dem!OSVERSIONINFO
       +0x000 dwOSVersionInfoSize : 0x114
       +0x004 dwMajorVersion   : 6
       +0x008 dwMinorVersion   : 0
       +0x00c dwBuildNumber    : 0x1771
       +0x010 dwPlatformId     : 2
       +0x014 szCSDVersion     : [128]  "Service Pack 1"

    So, this is how the function is coming back to us. And now we can see our first argument. Fortunately for us, this is our only argument, and our job is done.

    (You may be scratching your head as to why szCSDVersion seems to be missing the letters Se - I'm doing the same thing. Actually, no more head scratching. I mixed up esp+4 outside with ebp+8 inside - fixed this.)

    That wasn't so hard, was it? A little help from MSDN, and we are on our way.

    Let's do one more, for good measure. This application happens to display a message box, so let's fast forward to that API call. MSDN tells us that this is found in user32.dll, so let's set our breakpoint.

    0:000> bp user32!messageboxw

    Now that we're in the function, this time we want to stay in it. The arguments here are interesting going in. Let's take a look at this function:

    0:000> uf user32!messageboxw
    USER32!MessageBoxW:
    76d5d667 8bff            mov     edi,edi
    76d5d669 55              push    ebp
    76d5d66a 8bec            mov     ebp,esp
    76d5d66c 833da89cd67600  cmp     dword ptr [USER32!gfEMIEnable (76d69ca8)],0
    76d5d673 7424            je      USER32!MessageBoxW+0x32 (76d5d699)
    76d5d675 64a118000000    mov     eax,dword ptr fs:[00000018h]
    76d5d67b 6a00            push    0
    76d5d67d ff7024          push    dword ptr [eax+24h]
    76d5d680 6824a3d676      push    offset USER32!gdwEMIThreadID (76d6a324)
    76d5d685 ff150412d076    call    dword ptr [USER32!_imp__InterlockedCompareExchange (76d01204)]
    76d5d68b 85c0            test    eax,eax
    76d5d68d 750a            jne     USER32!MessageBoxW+0x32 (76d5d699)
    76d5d68f c70520a3d67601000000 mov dword ptr [USER32!gpReturnAddr (76d6a320)],1
    76d5d699 6a00            push    0
    76d5d69b ff7514          push    dword ptr [ebp+14h]
    76d5d69e ff7510          push    dword ptr [ebp+10h]
    76d5d6a1 ff750c          push    dword ptr [ebp+0Ch]
    76d5d6a4 ff7508          push    dword ptr [ebp+8]
    76d5d6a7 e849ffffff      call    USER32!MessageBoxExW (76d5d5f5)
    76d5d6ac 5d              pop     ebp
    76d5d6ad c21000          ret     10h

    The first few lines set up our stack frame. We can step through the first 3 lines to set it up ourselves, or we can just use the existing stack pointer (ESP). Since it saves us some time, let's do that.

    Looking at MSDN, the first argument is the HWND of the window that owns the message box. This will be a 4-byte HWND value. If we had stepped through our stack frame setup, we'd be starting at EBP+8, but since we haven't called PUSH EBP yet, we're going to be starting at ESP+4. So, our first argument is:

    0:000> dc esp+4
    0012fdd0  00000000

    OK, so it's passing a NULL HWND. On to argument #2: a pointer to the text to display in the message box. We'll just walk 4 more bytes on the stack to grab that pointer:

    0:000> ddu esp+8
    0012fdd4  00402160 "This application requires Windows XP"

    And what are they using as the caption in argument 3?

    0:000> ddu esp+c
    0012fdd8  00402134 "Unsupported Version"

    And our last argument tells us what buttons it's going to support:

    0:000> dc esp+10
    0012fddc  00000010

    Ah - this one is a bit harder to crack, because it's expressed as a UINT instead of a handy #define'd flag. Fortunately, MSDN also tells us where the API is defined (winuser.h), so we can just head there and search for MB_ - they're typically all grouped together. So, let's translate. 10 is MB_ICONHAND (which also happens to be MB_ICONERROR and MB_ICONSTOP if you continue reading the header), and, since we have to have buttons, we must have MB_OK (0x0). So, we must be calling this as MB_ICONHAND | MB_OK.

    So, with a little help from the SDK header files, and a lot of help from MSDN, we were able to point our debugger at Windows APIs with public symbols and gather just as much information about arguments as the private symbols would give you.

    Updated 2/23/2008: I'm so used to typing EBP+8 inside a method that I typed ESP+8 outside. I corrected this.

  • The App Compat Guy

    What Percentage of Consumers Have UAC Enabled on Windows Vista?

    • 27 Comments

    From the "doesn't just saying it make it true?" department:

    I was reading the March 2008 issue of Maximum PC (I love my Maximum PC), and while I like the real dirt and the attitude, I just don't understand the extent of their hatred of UAC. Their Editor in Chief writes, "UAC is the worst feature ever. In fact, it's so annoying that most users turn it off after mere moments." (p.94)

    Now, I'm a huge fan of running as a standard user. I ran as a standard user on Windows XP (which I could never have done without MakeMeAdmin). UAC makes that easier. For those who want to run as admin, yeah - you give something up. You are no longer an admin all the time. Well, let me correct that. You are still an admin on your computer. It's just that your word processor isn't an admin on your computer. Your web browser isn't an admin on your computer. Every web site on the planet isn't an admin on your computer. And so forth. And I would assert that this is good. So clearly, I don't go into this with the same attitude towards the feature.

    But it doesn't matter. This is an assertion of fact, so why don't we look at the real data?

    We looked at data from 9 million user sessions. We stripped out all data from computers joined to a Microsoft domain, and non-RTM builds. (Us 'Softies are a wacky bunch.) And we looked at the results. This is from the October - December 2007 data collection period.

    What did we find?

    UAC is enabled on 88% of consumer sessions. And that number is increasing, up from 84%.

    What else did we find? Well, I didn't get permission to share all that, so I'll talk in vague generalities. :-) We're looking at which applications are prompting the most frequently, and seeing what we can do to fix that. (We have more than our fair share, trust me.) But this is definitely a "long tail" problem. I spend a significant portion of my life shimming up applications so they no longer require admin rights, and teaching others how to do the same. I do the same thing for developers writing code. Because, in an ideal world, you would be prompted exactly one time for each time you wanted to change the global state of your machine or some other user. We're not there yet, but we're getting better.

    I'm really happy that the numbers are that high. And Maximum PC, you're still tied for 1st place in my list of favorite magazines (well, I also love my PC Gamer), even if your hatred blinds you sometimes. Time to let my non-admin blog editor publish this post...

  • The App Compat Guy

    Where Should I Write Program Data Instead of Program Files?

    • 27 Comments

    When I'm working to resolve compatibility issues, there are always multiple options to mitigate. The solution we prefer to use is to update the code.

    A common application code update is this: "my application used to write files to program files. It felt like as good a place to put it as any other. It had my application's name on it already, and because my users were admins, it worked fine. But now I see that this may not be as great a place to stick things as I once thought, because with UAC even Administrators run with standard user-like privileges most of the time. So, where should I put my files instead?"

    The answer, as it turns out, is: it depends.

    Let's look at the options, and when you might want to choose each.

    First, you'll want to use the SHGetKnownFolderPath API function to pull the function if you are using native code. If you are using managed code, System.Environment.GetFolderPath will do the trick for you.

    FOLDERID_ProgramData / System.Environment.SpecialFolder.CommonApplicationData
    The user would never want to browse here in Explorer, and settings changed here should affect every user on the machine. The default location is %systemdrive%\ProgramData, which is a hidden folder, on an installation of Windows Vista. You'll want to create your directory and set the ACLs you need at install time.

    FOLDERID_Public / FOLDERID_PublicDocuments / System.Environment.GetEnvironmentVariable("public")
    The user would want to browse here in Explorer and double click to open the file. The default location is %public%, which has explicit links throughout Explorer, on an installation of Windows Vista. You'll want to create your directory and set the ACLs you need at install time.

    FOLDERID_RoamingAppData / System.Environment.SpecialFolder.ApplicationData
    The user would never want to browse here in Explorer, and settings changed here should roam with the user. The default location is %appdata%, which is a hidden folder, on an installation of Windows Vista.

    FOLDERID_LocalAppData / System.Environment.SpecialFolder.LocalApplicationData
    The user would never want to browse here in Explorer, and settings changed here should stay local to the computer. The default location is %localappdata%, which is a hidden folder, on an installation of Windows Vista.

    FOLDERID_Documents / System.Environment.SpecialFolder.MyDocuments
    The user would want to browse here in Explorer and double click to open the file. The default location is %userprofile%\documents, which has explicit links throughout Explorer, on an installation of Windows Vista.

    Now, you'll note that FOLDERID_Public is kind of the oddball here. System.Environment.GetFolderPath just calls SHGetFolderPath, which takes CSIDLs. There is no analogue for %public% here. However, we could have gone after CSIDL_COMMON_DOCUMENTS (FOLDERID_PublicDocuments) and dropped things there, but even though we just need to pass 0x2e (46) as the int argument, we don't offer that. Because we have this subset going, I'd probably start thinking about using p/invoke if I needed to support public documents (http://www.pinvoke.net/default.aspx/shell32.SHGetKnownFolderPath).

    If you're using Visual Basic, you can also use My.Computer.FileSystem.SpecialDirectories, but it also doesn't seem to get any public documents love...

Page 1 of 1 (5 items)