There, and back again - Part I
-------------------------------
Let me start this 3-article series with a quote:
'We shall not cease from exploration, and the end of all our exploring will be to arrive where we started and know the place for the first time.' --T.S. Elliot
I will not go so far as to claim that I came to the end of all exploration, far from that. But the tool I'm describing here had a long journey from the late nineties to present-day. And not only was I not able to pull this off back then when I started, but it turns out now that I never stood a chance!
But back to topic. Remember that I promised to "bridge the gap between Windows 2000 and Windows NT 4.0"? Well, if you expected to perfectly run DirectX games, or to get real PnP, I'll have to disappoint you. Those things are still way over my head.
But if I had to select just one feature of Win2k to be present on NT4, I know what I'd choose: It's 'runas'.
Uhm, you say, wasn't NT designed as a multi-user OS from the get-go? Ha, yes, but not until Windows 2000 did Microsoft allow you to run programs under multiple accounts in the same session! A thing that was common on UNIX for decades was just not there in NT before 2k! Oh, you say, then how did you devlop stuff that would require root privileges? Oh, well, you'd log in as Administrator and... Madness, you say? Well, I guess, those where happy times back then... At least for crackers.
So the question was: would it be possible to write a 'runas'-like utility on NT4? The answer is a resounding yes, and it's been done many times. For example in the NT Resource Kit, there was a utility called 'SU.EXE' [1], that would do just that. But with some twists: First of all, the Micrososft of old did not bother to publish source code. It was probably too convoluted, anyway. And second, it would only allow you to run a process in another user's security context if the account you worked with had been granted some pretty nasty privileges (e.g. 'Act as part of the operating system'). That was not exactly what I was looking for. So this utility can only serve as proove that it *IS* possible to do this, but nothing more. And, of course, I didn't have access to the resource kit, anyway.
At university, however, I had access to MSDN documentation. Must have been around summer 1998. I had stumbled upon an interesting system call, 'LogonUser'. The docs stated that the handle returned by this API was suitable to 'create a process running in the context of the specified user', by leveraging an API named 'CreateProcessAsUser'! Oh, what a joy!
Disillusion came when I discovered that not even Administrator was allowed to call those functions. Writing a Win32 service didn't occur to me, so that was the end of it. At least temporarily. And I found out later that these two calls weren't nearly enough, and that the additional functions needed (implemented in 'userenv.dll') where not part of the documented Win32 API until the advent of the Windows 2000 SDK somewhere in the fall of '99...
With Windows 2000, however, my interest rekindled. I wanted something like 'runas' on NT4! And with the new enthusiasm came the 'service' idea, and an article (which sadly I can't find any more) that described in detail the importance of calling 'ImpersonateLoggedOnUser' before calling 'CreateProcessAsUser'. Actually, one could just skip the 'As User' part, and call the much more straightforward 'CreateProcess', if one impersonated the requested user first. The downside of this, of course, is, that 'CreateProcessAsUser' allows to supply an 'environment block', which was needed to get the correct environment of the user profile. That could be achieved by 'LoadUserProfile', followed by 'CreateEnvironmentBlock'.
So, we have a happy quintet of system calls: 'LogonUser', 'LoadUserProfile', 'CreateEnvironmentBlock', and finally 'CreateProcessAsUser'. That should do the trick. So, I set out to implement a service, and a communication channel (named pipe), and an ordinary, user-callable command line executable as front end.
I was halfway there, when in 2004, my VW320 died, and to this day never came up again. Thus went my last NT4 machine, and with it the justification to create something like this in the first place. For a second time, the project grinded to a halt, and dust settled on the old hard drives.
Years later, I finally found a working NT compiler for my DEC Alpha box, and I was once again hooked to the old mission. With years of experience in the trenches of Windows development, and the whole internet (and in particular, stackoverflow.com) at our disposal, it should be easy to get this up and running, right?
Sadly, not many people care about NT4 any more, and much of the usefull content of old has been lost to the mists of time. Like the MSDN Magazine, or Microsoft Systems Journal, as it was called in the nineties. One would have to dig through the internet archive to find information on these arcane techniques.
Nevertheless, I pulled the harddrive from the dead 320, and dragged my old source code over to the Alpha and went to work. I even came across a utility on codeguru.com, runasv [2], that I could use as a blueprint! Because it came with source code! And that code compiled and ran on the Alpha!
Why I didn't call off the search at this point and just use this tool? Well, though it actually worked, it didn't do so without quirks. For example, it didn't bother to call the impersonation and profile APIs, but just called 'LogonUser' and 'CreateProcessAsUser'. As expected, this resulted in a 'cmd.exe' runas'd as Administrator in not having that user's environment or HKEY_CURRENT_USER registry hive. It just had Administrator's security context. And communication between command line interface and service was established using an unencrypted TCP/IP channel. Don't get me wrong, I'm using a machine that didn't get a single security update in two decades, so speaking of security appears somehwat hypocritical. But blasting unencrypted passwords over the network still is a no-go for me.
If this sounds overly critical, however, I assure you that this is not my intention. I rather find the code very interesting and of high quality. And, to be fair, the tool probably did exactly what it's developer intended, and documentation on this process was not exactly spread far and wide. And by providing source code, he helped teremendously in development of my own service on the Alpha, because I could now install, start, stop and remove my creations while being logged in with my unprivileged user account. So the trophy for most valuable support doesn't go to some dusty, old MSJ article, but to Jesz de la Vega and his 'runasv'. Thank you, man, I owe you a beer!
But I could do better, I was sure.
And now I finally had all the pieces together to formulate a plan:
A win32 service (which runs under 'system' account) waits on a named pipe, and when a request comes in, calls 'LogonUser' to get a user handle, followed by 'LoadUserProfile' to load the profile, then go on and create the env block with 'CreateEnvironmentBlock', then 'ImpersonateLoggedOnUser', and last but not least 'CreateProcessAsUser'. And the user gets an elevated command promt, provided that he did enter the correct password. And when everything compiles and links, use 'runasv' to launch the service.
Looking back from where I am now, that sounds too easy. What I failed to recognize, for example, was that getting helpful information 20 years after the demise of the involved systems (hardware and software) proved to be harder than I thought.
But let's save that story - and my encounter with the NT security API - for another post!
Dear Reader, thank you for staying with me through this text-heavy post. Part II will come with source code and executables, I promise!
References:
[1] "SU.EXE, a utility included with the Windows NT Resource Kit", Scott Field, 1996
[2] "Special runas Utility for Windows NT and Windows 2000", Jesz de la Vega, 2003