If you're like me, you like visibility into the workings of your OS, which processes are running, what modules are loaded and from where and whether they are signed or not, what files are open, what files are being touched and what registry entries are being frobbed.
I use the SysInternals tools for getting increased visibility into these things. In particular, I use Process Explorer (procexp) alongside Task Manager (the two aren't complete overlaps for functionality), with File Monitor (filemon) and Registry Monitor (regmon) on XP and Process Monitor (procmon) on Vista. Filemon and regmon are more focused to their specific tasks and have easier to configure filters than procmon, so I usually prefer them, but they are not compatible with Vista.
Any time I see an unexpected filesystem locking error, such as a failure in Windows Explorer to delete a folder, I can search for the culprit in procexp's handle view. Often the culprit is Windows Explorer itself - perhaps caused by a shell extension like Winzip or Winrar - and I can force closed the handle right there from the procexp interface itself.
I have a set of "verification columns" configured procexp that makes visible the Company, Description and Verified Signer columns for processes and modules (DLLs). With Options | Verify Image Signatures turned on, this makes for easy checking for suspicious DLLs and kernel drivers (modules loaded into the System process) loaded in the system.
Procmon supports a whole lot more analysis than the easier to use tools like filemon and regmon, but the most important distinct feature is probably the stack trace on every file and registry operation. Double-clicking an event in the procmon view pops up a dialog that has a Stack tab, wherein the stack trace is displayed. When Windows symbols are correctly configured, procmon will call out to windbg.dll and show symbolic information such as function name and offset for each code address in the stack trace, up to and including downloading the symbols PDB if necessary.
Since I don't think I've done it already on this blog, I'll outline the sequence of steps required to get symbols correctly configured.
- Download and install WinDbg. WinDbg is a useful tool in its own right for low-level Windows debugging, and particularly for .NET debugging when using the SOS command extension DLL.
- Choose a location - i.e. an empty directory - for the symbol cache on your system. Once chosen, create an environment variable called _NT_SYMBOL_PATH with the value "
SRV*[symbol-cache-dir]*http://msdl.microsoft.com/download/symbols
" (without quotes), but replace [symbol-cache-dir] with the path to your selected directory. - Now, the SysInternals tools can be configured with these settings. Procmon and Procexp both have Configure Symbols dialogs in their Options menus. Set the DbgHelp.dll path to the DbgHelp.dll from the installation location of WinDbg, and set the "Symbol paths" textbox to the value of the _NT_SYMBOL_PATH environment variable created earlier.
However, the reason I wrote this post is not merely to laud the SysInternals tools and the work of Mark Russinovich. The fact is that SecuROM, a DRM solution used in many games these days, has panic attacks when it sees evidence of SysInternals tools being used. I don't really understand what SecuROM's justification for its conniptions could be, since monitoring the process with the tools could only help in an initial cracking attempt, yet game cracks appear to come out no slower than they ever did. All it seems to do is annoy honest users - and in this case, developers. Not only does SecuROM want you to exit the applications in question, but it also wants you to reboot the entire system, ignoring the fact that you might have concurrent long-lived tasks that don't merit interruption by a mere game
Well, I couldn't let that stand. I created a tool to enable me to simply exit the SysInternals utilities in question, rather than having to reboot the machine. The key to SecuROM's detection of the tools is in the communication mechanism between the SysInternals tools and their kernel driver counterparts, in particular the named devices in the Windows Object Manager namespace, visible using the WinObj tool. It turns out that merely by changing the ACL (access control list) on the device object to a single deny-all ACE (access control entry) is sufficient to fool SecuROM from thinking the device is no longer loaded - though SecuROM also looks for top-level windows with particular window classes, so the utilities do have to be exited as well.
So, I wrote a simple Delphi console application, called cacls_driver, to modify the ACL of a driver device. A key set of libraries that made this easy was the JEDI Win32 API library, in particular the units JwaWinType, JwaWinBase, JwaWinNT, JwaNative and JwaSddl. I've appended the tool's source code to this entry.
Using the tool is simple enough. The most important thing to know is the name of the driver one is changing the permissions of. For the versions of filemon and regmon I have installed, the correct driver names are filemon701 and regmon701 respectively. I figured this out by running the aforementioned WinObj and looking in the 'GLOBAL??' folder for devices with names starting with filemon and regmon. For the original Crysis, SecuROM even complained about Process Explorer, so I had to include procexp110 (IIRC) as well. SecuROM support, on the other hand, recommended that I upgrade Process Explorer, which of course used an incrementally different device name (procexp111) and thereby avoided the SecuROM sanction.
Here's an example of the tool in use hiding the regmon and filemon driver links ($ is my command-line prompt, and # are comments):
# This simply shows the current ACL $ cacls_driver -s filemon701 regmon701 filemon701 :: D:(A;;CCRC;;;WD)(A;;CCSDRCWDWO;;;SY)(A;;CCSDRCWDWO;;;BA)(A;;CCRC;;;RC) regmon701 :: D:(A;;CCRC;;;WD)(A;;CCSDRCWDWO;;;SY)(A;;CCSDRCWDWO;;;BA)(A;;CCRC;;;RC) # This disables the ACL. There's no output from this command. $ cacls_driver -d filemon701 regmon701 # Verifying that the ACL has been set. $ cacls_driver -s filemon701 regmon701 filemon701 :: D: regmon701 :: D:
These ACLs having been applied - and ensuring the tools in question aren't still running - I'm free to run my games without the odious requirement for a reboot.
Source code follows...
program cacls_driver; {$APPTYPE CONSOLE} uses SysUtils, JwaWinType, JwaWinBase, JwaWinNT, JwaNative, JwaSddl, Generics.Collections; type TOperation = (opNone, opApply, opShow); TOptions = class private FOperation: TOperation; FDrivers: TList<string>; FDacl: string; public constructor Create; destructor Destroy; override; property Operation: TOperation read FOperation write FOperation; property Drivers: TList<string> read FDrivers; property Dacl: string read FDacl write FDacl; end; function ParseOptions: TOptions; const // http://msdn.microsoft.com/en-us/library/cc230374.aspx // These were the defaults on my system (WinXP SP3) // SID tokens (last two letters in ACEs): // SY = local system; BA = builtin administrators; WD = everyone; // RC = restricted code // Permissions: // CC = create child; RC = read control; SD = delete; WD = write DAC; // WO = write owner DefaultDacl = 'D:(A;;CCRC;;;WD)(A;;CCSDRCWDWO;;;SY)(A;;CCSDRCWDWO;;;BA)(A;;CCRC;;;RC)'; NullDacl = 'D:'; var i: Integer; opt: string; procedure Usage; begin Writeln(Format('usage: %s <command> <driver...>', [ParamStr(0)])); Writeln('Modify discretionary ACL for the given global namespace driver symlinks.'); Writeln(' -d Disable access (deny-all ACL)'); Writeln(' -e Enable access (apply default ACL: ', DefaultDacl, ')'); Writeln(' -s Show ACL for the given drivers'); Writeln(' -a <acl> Apply an explicit ACL'); end; procedure SetOpOnly(Op: TOperation); begin if Result.Operation <> opNone then raise Exception.Create('only one of -s, -d or -e allowed'); Result.Operation := Op; end; procedure SetOp(Op: TOperation; const ADacl: string); begin SetOpOnly(Op); Result.Dacl := ADacl; end; procedure SetOpArg(Op: TOperation); begin Inc(i); if i > ParamCount then raise Exception.Create('missing argument to option ' + ParamStr(i - 1)); SetOp(Op, ParamStr(i)); end; begin Result := TOptions.Create; try i := 1; if ParamCount = 0 then begin Usage; Exit; end; while i <= ParamCount do begin opt := ParamStr(i); case opt[1] of '/', '-': begin if Length(opt) > 2 then raise Exception.Create('invalid option'); case opt[2] of 's': SetOpOnly(opShow); 'd': SetOp(opApply, NullDacl); 'e': SetOp(opApply, DefaultDacl); 'a': SetOpArg(opApply); '?', 'h': Usage; else raise Exception.Create('invalid option'); end; end; else Result.Drivers.Add(opt); end; Inc(i); end; except Result.Free; raise; end; end; { TOptions } constructor TOptions.Create; begin FDrivers := TList<string>.Create; end; destructor TOptions.Destroy; begin FDrivers.Free; inherited; end; procedure EnablePrivilege(Process: THandle; const Name: string); var privs: TTokenPrivileges; begin FillChar(privs, SizeOf(privs), 0); privs.PrivilegeCount := 1; privs.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; Win32Check(LookupPrivilegeValue(nil, PChar(Name), privs.Privileges[0].Luid)); Win32Check( AdjustTokenPrivileges(Process, False, @privs, SizeOf(privs), nil, nil)); end; procedure ElevatePrivileges; var process: THandle; begin Win32Check(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, process)); try EnablePrivilege(process, SE_SECURITY_NAME); EnablePrivilege(process, SE_DEBUG_NAME); finally CloseHandle(process); end; end; function NtErrorMessage(Status: TNTStatus): string; var buf: PChar; h: THandle; begin h := LoadLibrary('ntdll.dll'); try FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_FROM_HMODULE, Pointer(h), Status, 0, PChar(@buf), 0, nil); try Result := TrimRight(buf); finally LocalFree(Cardinal(buf)); end; finally CloseHandle(h); end; end; procedure NtCheck(Status: TNTStatus); begin if Status <> 0 then raise EOSError.Create(NtErrorMessage(Status)); end; procedure WithDriver(const Name: string; Mask: TAccessMask; const Proc: TProc<THandle>); var path: string; driverName: TUnicodeString; driverAttr: TObjectAttributes; handle: THandle; begin path := '\GLOBAL??\' + Name; RtlInitUnicodeString(@driverName, PChar(path)); try FillChar(driverAttr, SizeOf(driverAttr), 0); driverAttr.Length := SizeOf(driverAttr); driverAttr.ObjectName := @driverName; NtCheck(NtOpenSymbolicLinkObject(@handle, Mask, @driverAttr)); finally RtlFreeUnicodeString(@driverName); end; try Proc(handle); finally NtClose(handle); end; end; function GetKernelObjectDacl(Handle: THandle): string; var len: Cardinal; desc: PSecurityDescriptor; buf: PChar; begin GetKernelObjectSecurity(handle, DACL_SECURITY_INFORMATION, nil, 0, len); desc := AllocMem(len); try Win32Check( GetKernelObjectSecurity(handle, DACL_SECURITY_INFORMATION, desc, len, len)); Win32Check(ConvertSecurityDescriptorToStringSecurityDescriptor(desc, SDDL_REVISION_1, DACL_SECURITY_INFORMATION, buf, nil)); try Result := buf; finally LocalFree(Cardinal(buf)); end; finally FreeMem(desc); end; end; procedure SetKernelObjectDacl(Handle: THandle; const Dacl: string); var secDesc: PSecurityDescriptor; begin Win32Check(ConvertStringSecurityDescriptorToSecurityDescriptor(PChar(Dacl), SDDL_REVISION_1, secDesc, nil)); try Win32Check(SetKernelObjectSecurity(Handle, DACL_SECURITY_INFORMATION, secDesc)); finally LocalFree(Cardinal(secDesc)); end; end; procedure ApplyToDriverHandle(Drivers: TList<string>; const Proc: TProc<string,THandle>); var driver: string; begin ElevatePrivileges; for driver in Drivers do WithDriver(driver, MAXIMUM_ALLOWED, procedure(Handle: THandle) begin try Proc(driver, Handle); except on e: Exception do Writeln(Format('failed on "%s": %s', [driver, e.Message])); end; end); end; procedure DoShow(Drivers: TList<string>); begin ApplyToDriverHandle(Drivers, procedure(Driver: string; Handle: THandle) begin Writeln(Format('%s :: %s', [driver, GetKernelObjectDacl(Handle)])); end); end; procedure DoApply(Drivers: TList<string>; const Dacl: string); begin ApplyToDriverHandle(Drivers, procedure(Driver: string; Handle: THandle) begin SetKernelObjectDacl(Handle, Dacl); end); end; begin try with ParseOptions do try case Operation of opShow: DoShow(Drivers); opApply: DoApply(Drivers, Dacl); end; finally Free; end; except on E: EOSError do Writeln('error: ', e.Message); on E: Exception do Writeln(e.Message); end; end.
2 comments:
About file locked, I use Unlocker, it is very useful to unlock file or folder locked by delphi itself after a particular exception, etc.. Attention! despite it's freeware, since last versions installer deploy also a spyware.. be carefull if you want to try it.
bye
Very nice article, though I'd have to take issue with a bit of your terminology. The term "DRM Solution" is a contradiction in terms. First, as you mentioned, it doesn't keep games from being cracked or copied about freely, so it doesn't "solve" the problem it's intended to solve. Second, any code that takes control of the functionality of a computer out of the hands of its owner and causes the computer to act against its owner's will, for any reason, is malware and has no legitimacy and no justification under any circumstances.
DRM is not a solution and never will be. It's a blight on the computer industry. It's nothing but a form of hacking, made legal by a specific exemption in the DMCA, and the sooner we can get the exemption thrown out, the safer and better off we will all be.
Post a Comment