View Full Version : Question about stealth methods
lostinspace
11-21-2002, 05:33 AM
*EDIT* since this thread changed focus to kernel driver after second page, I changed subject accordingly.
After someone posted very good link about injection techniques
(http://www.codeproject.com/system/hooksys.asp), i wrote about 10 days ago my version of keyreader that inject DLL with SetWindowsHook, and read memory with normal memory read, from inside EQ process. But after completing that, I still continued using ReadProcessMemory version.
Now after I saw t this very good stealth version of hook DLL that Maggotboy wrote, I still have same questions , namely:
1) What is way that SOE can detect using of ReadProcessMemory?
2) What is way that SOE can detect using of hooked DLL direct memory read?
3) Is it really safer using direct memory inproccess read?
My answers on those questions are reason I still use ReadProcessMemory. Maybe I'm wrong on some of those issues, thats why I'm posting this here :)
1) To detect call of API function, SOE needs to do exactly same techniques as described in above link, one of which is very same SetWindowsHook.So they need to first inject their code in all processes address space, and then to hook to some execution point (with SetHook on some mesages, or modifying Process Table or ..). Or, alternativelly, modify some kernel DLL or tables to hook on kernel level.
2) to detect hooked DLL, SOE need again to detect only some API function that is used in that DLL. Or it can use VirtualProtect on key data memory . Sniffer can use same VirtualProtect to unlock data, but then again SOE can detect only one API function called for area where key data is (VirtualProtect). Maggotboys example also use another API function easily detectable (SetWindowsHook with process ID of EQ itself), while my version is using global hook and is not detaching and attaching to single process so even intercepted SetWindowsHook would not point to EQ as target...but nevertheless, I still need to use VirtualProtect :\
3) It seemed to me that detecting in process DLL (which needs to use VirtualProtect) is exatly same job as detecting outprocess ( which needs to use ReadProcessMemory) -- > in both cases SOE only need to detect API CALL of 2 specific functions. Only detecting inprocess is easier since SOE does not need to do first part of all spy programs - hooking into other process. They can just change Process tables or put JMP on start of code of 2 API functions in their own addres space. And inprocess with use of VirtualProtect is more dangerous ( if wrong memory segment start is used in VirtualProtect , then it will fail and memory read will trip trigger, so sniffer will be detected even if SOE does not implement complicated API function interceptions)
Again, maybe I'm wrong on some of those issues, but so far I'll stay with ReadProcessMemory, and only will add some security measures to check memory footprint of area pointed by ReadProcessMemory pointer ... so even if SOE inject its own sniffer for that, i can detect it and not use API function.
maggotboy
11-21-2002, 10:05 AM
This is one of the reasons not to run a sniffer right after a patch. Everyone needs to hold off and wait until someone's had a look at the binaries to see what's going on.
The life of a sniffer is consigned to be a tit-for-tat life...with us making a sniffer, Verant adjusting their code, and us adjusting the sniffer.
It's not pleasant, but until I (or someone else) comes up with a ring-0 device driver approach, we're just going to have to live this life for a little bit.
As for the holes in ReadProcessMemory() ... In order to read another process's memory you first (obviously) must open the process. Verant can make this quite difficult, but not completely impossible. They can also put the key in relocatable global memory and reference it with a handle which cannot be used by another process to lock/unlock the memory. There's really easy ways to prevent another process from browsing your memory. Preventing something injected right into your own address space is completely another matter, though.
Maggotboy
lostinspace
11-21-2002, 11:24 AM
Yes, that 'wait until someone's had a look' is main potential problem here :(
That is exatly what I'm trying to avoid. Namely, I dont care if sniffer wont work after new patch and i have to wait till someone find new offset or pointer to offset or anything. What I do care is that I can be reasonably sure that using sniffer on patched version of EQ wont get me detected.
If we concluded that if SOE manage to intercept API functions on kernel level, they can catch both outside (ReadProcessMemory) and inside (VirtualProtect) version of sniffer - then I still feel more secure with ReadProcessMemory.
So SOE can do few things:
a) change offset of key. In that case both implememntations just dont wok till new offset posted, but no danger of detection
b) offset remain same , but they virtualProtect it. In that case ReadProcessMemory implementation temporary doesnt work, but is not detected, while inprocess DLL version can get detected, or need to be enhanced with VirtualProtect (but still have risk of detection)
c) change its own proccess to intercept ReadProcessMemory and VirtualProtect calls. In that case inside DLL can be detected
d) change on Kernel level or global hook to intercept ReadProcessMemory and VirtualProtect calls. In that case both implementations can be detected
So, I agree that if we wait untill someone carefully review every new patched code, we can rearrange sniffer to not be detected by new patched EQ. But in reallity they can make sneak patch, or people wont notice that exe/dll was changed ... and if they run only once old sniffer, they can be detected.
And from 4 listed potential types of EQ changes, ReadProcesMemory will be detected only on one of them while inside read memory can be detected on 3 of them.
Btw, i think that GlobalAlloc, LocalAlloc, VirtualAlloc all do similar things under full Win32, and I did test possibility to read memory allocated with VirtualAlloc from outside process and my inside DLL, and it works in both cases.
Dont get me wrong, I do think that stealth DLL is good thing. I'm only trying to point out possible weakness in all sniffer approaches that I know of, and to try to find solution for them. So far most serious weakness is posibility to intercept API calls, and I'll try to see if I can make check of memory foorprint of API functions before I call them and compare to previously saved values and in that way detect any interceptor. I would appreciate any other idea about disabling or detecting API function interception :)
maggotboy
11-21-2002, 11:53 AM
b) is wrong, in that VirtualProtect() will protect the key from all readers, including ReadProcessMemory(). Any attemt to read that memory will potentially raise an exception -- both externally and internally.
c) Intercepting ReadProcessMemory() has no effect on outside processes. They can still put up a guard page on the page containing the key, but they have to block out the entire 4k section of memory and cannot read anything in that block without unguarding the page first. Both methods can be detected using a guard page, I believe.
d) is complex, and is too cumbersome to implement. Its like throwing a huge net and reeling it in, and then having to sift through what you brought up from the bottom of the sea. Its way too complicated to implement with any satisfactory performance. Its easy to find or look for specific things, but to hook into all processes and redirect VirtualProtectEx means analyizing every single call on every single process and determining whether or not the call in question references EQ's process. That's even assuming they could decode the HANDLE being presented to the call and figure out what process the handle belongs to.
Maggotboy
speedphreak
11-21-2002, 11:58 AM
bear in mind that the offset changing will happen almost every time eqgame.exe is patched because memory structures likely change.
The offset changing with a patch is a byproduct of change, rather than an intentional change to mess us up.
Sodom
11-21-2002, 12:31 PM
Something to keep in mind is that there is a limit to how far VI will go to detect this. I think they've shown in the past that for the most part, they will lay some speed dumps down on the road occasionally, but beyond that SEQ is tolerated.
The old arguments were in regard to VI detecting a NIC running in promiscuous mode....heh, the envelope has been pushed so much further from those days.
Anywho, my point is this. VI never went after promiscuous NICs, they simply broke the encrytion a couple of times. They have also tolerated EQWin, because in and of itself, it really doesn't cause them problems.
Hooking into EQGame and sniffing the key from inside the EQ process is an entirely different beast however. There are those who will argue both sides about the legality of VI scanning your machine looking for this or that, but nobody questions their right, or the likelihood that they will react to people playing around in their main application process. It's getting too close to a hack for them to simply tolerate it.
Previous methods that they've employed to hinder the operation of SEQ have been few and far between, running as an external process will likely work for some time.
Your code is beautiful maggotboy, if I was male code and it was female, I'd be extremely aroused, but VI isn't going to see it that way. This is going to move SEQ, EQWin and Keysniffers, WAY up the priority list.
Your code is beautiful maggotboy, if I was male code and it was female, I'd be extremely aroused, but VI isn't going to see it that way. This is going to move SEQ, EQWin and Keysniffers, WAY up the priority list.
That's because SOE is female ... you know how one girl acts when a pretty girl walks by, eh?
BITCH! *SLAP*
And the guys hanging around are thinking "Cool, cat fight!"
LordCrush
11-21-2002, 02:39 PM
Lol Ratt ;)
but i think the most important that was said in this thread is :
This is one of the reasons not to run a sniffer right after a patch. Everyone needs to hold off and wait until someone's had a look at the binaries to see what's going on.
and that is what i want to implement - i have been thinking about a little proggy which makes a crc over eqgame.exe and some other dll´s and prohibit launching of the sniffer if the crc dont match ... by now i dont know how to implement this with maggotboy´s sniffer cause the rundll is already running before the patcher, but i am thinking over it :)
wiz60
11-21-2002, 02:49 PM
Most of the discussions have centered around cracking the virtual address space of the game. There are a number of potential issues - most notably the issue related to trapping and protecting pages in memory - that could lead to detection by SOE.
Any in-memory image could be traced and detected. I will admit that but I have not heard anyone (other than a casual mention a few days back) talking about cracking the game externally.
So what are the difficulties for doing the following:
1. Convert the address to be sniffed into PTE format.
2. Access the process header (and related data structures) and determine what physical address is mapped to the desired location.
3. Map yourself on-top of that space using Native-API calls
4. Grab the key.
This is completely undetectable since you will be using YOUR process memory mapping registers instead of EQs. You access the memory of the system totally independent.
Issues:
1. You need to make sure the page is in memory
2. Need elevated privilege to insure it does not get swapped out while you work.
3. Repeat the procedure every time you want to read the key - because the page could move around in physical memory.
I have been taking the sysinternals Physical Memory Driver apart to get a feel for the mapping concepts. Right now trying to focus on the conversion of the logical address in virtual space into a physical address.
Sodom
11-21-2002, 03:11 PM
Originally posted by LordCrush
i have been thinking about a little proggy which makes a crc over eqgame.exe and some other dll´s and prohibit launching of the sniffer if the crc dont match ... by now i dont know how to implement this with maggotboy´s sniffer cause the rundll is already running before the patcher, but i am thinking over it :)
The code needs to be part of the hook itself, there is no other way to do it. The easiest method of doing it is to fail when the offset has changed, there is no need for a crc check. So much work has gone into streamlining the process of finding the new offset that the benefits of not having it may have been overlooked.
Of course, DLL modifications wouldn't be flagged with this approach, but I believe any kind of attempt at detecting the sniffer would require an eqgame.exe modification.
MisterSpock
11-21-2002, 03:27 PM
From what I have read and discussed with other programmers, putting the key in a relocatable global block will really not buy them much. We can just sniff the handle value (using any of the existing technologies after we R.E. the changed code) instead of the key. With the handle, we can mark the page read only, read the key, and mark it back to no access -- all without throwing an exception.
I suspect that this will be a game of cat and mouse... We just need to stay on top of things.
lostinspace
11-21-2002, 03:43 PM
b) is wrong, in that VirtualProtect() will protect the key from all readers, including ReadProcessMemory(). Any attemt to read that memory will potentially raise an exception -- both externally and internally.
While VirtualProtect will really protect key from all readers, attempt to read from external process will NOT trip guard. I made test program to check this, and even implemented VirtualProtectEx in my ReadProcessMemory version of reader to unguard memory segment.
c) Intercepting ReadProcessMemory() has no effect on outside processes. They can still put up a guard page on the page containing the key, but they have to block out the entire 4k section of memory and cannot read anything in that block without unguarding the page first. Both methods can be detected using a guard page, I believe.
This section was about intercepting API function calls from inside process - where intercepting VirtualProtect is more important. And how large part of memory they guard is irrelevant ... inside process DLL will need to use VirtualProtect to unguard page, read mem and use VirtualProtect to guard page back. So if SOE intercept calls to VirtualProtect from its own process ( changing its own Process Tables, or even changing code where kernel procedure start ...) it will be able to catch if someone else is guarding/unguarding their area.
d) is complex, and is too cumbersome to implement. Its like throwing a huge net and reeling it in, and then having to sift through what you brought up from the bottom of the sea. Its way too complicated to implement with any satisfactory performance. Its easy to find or look for specific things, but to hook into all processes and redirect VirtualProtectEx means analyizing every single call on every single process and determining whether or not the call in question references EQ's process. That's even assuming they could decode the HANDLE being presented to the call and figure out what process the handle belongs to.
Yes, (d) is too complex and even more, too intrusive into system. Thats why i hope they will not use it. But if they do, it will not be any hit on performances..hell, it needs only compare input parameters with their own parameters, thats few assembly instructions. And calls to VirtualProtect or ReadProcessMemory are not so often anyway. HANDLE in case is same handle you use with OpenProcess, it has unique value for process across processes. Implementin all this in user mode is not so complex, read link i posted in first post here. Implementing in Kernel mode is bit more complex ...
LordCrush
11-21-2002, 03:57 PM
The code needs to be part of the hook itself, there is no other way to do it. The easiest method of doing it is to fail when the offset has changed, there is no need for a crc check. So much work has gone into streamlining the process of finding the new offset that the benefits of not having it may have been overlooked.
Hmm... how do i know in a program that the offset has changed ?
The sniffer goes to the offset and read 8 byte ... They are random and not preditable ( that's why we have the problem here :p )
- sure it has to be in the hook-procedure ...
maggotboy
11-21-2002, 04:30 PM
After doing some research on ring-0, I've discovered that there are some methods out there that can allow code originally running in ring-3 (user mode) to call and get access to ring-0. The papers I've read were from some hard-core virus guys, and most of it pertained to win9x ... so I may mess around with it a bit.
One of the notable things it talked about was VirtualProtect. It is simply a thin layer with some validation checking that ends up calling in the VMM.VXD through the use of an undocumented VxdCall() function which runs in ring-0.
So ... I'm still researching ... but my next project will likely be hooking into Explorer.exe, and spawning a worker thread there to read EQ's memory -- possibly using a ring-3 to ring-0 bridge for 9X, or some other interesting method in NT. Haven't finished the research yet.
Maggotboy
Sodom
11-22-2002, 10:31 AM
Originally posted by maggotboy
So ... I'm still researching ... but my next project will likely be hooking into Explorer.exe, and spawning a worker thread there to read EQ's memory
Hooking explorer is messy simply because the hook needs to be in place at the time of explorer being executed.
You can set it as a ShellExecuteHook but that leaves a blazing trail since it's got to be set in the registry, as explorer is executed by the operating system at boot/login as opposed to eqgame which is executed manually. Under XP, or 2k, explorer can be closed, and the relaunched with the hook set but it's far from a clean way of doing it.
maggotboy
11-22-2002, 10:44 AM
Not at all, Sodom. To hook into explorer.exe you use the same SetWindowsHookEx() method that I used to hook into the EQ game in my previos sniffer techniques.
You set the hook globally, which causes the DLL to load into explorer.exe. You then increment the reference count on yourself, unhook your global hook, spawn a worker thread in explorer's address space....and have a party.
Maggotboy
lostinspace
11-22-2002, 12:45 PM
Ok, I did some research connected with only real threat that I see in detecting key reader - and that is interception of API calls.
Basically, if SOE can intercept API calls, they can catch any variation of keyreader so far. Most probable APIs that they can hook on and detect keyreaders based on parametar (handle will be their process handle) are:
- OpenProcess
- readProcessMemory
- CloseHandle
- VirtualProtectEx
So, how can they (or anyone ) intercept API call? Well, there is long chain of pointers and calls between compiler API procedure and real procedure deep in for example NTOSKRNL. They can basically change any pointer in that chain, or change code that invoke those pointers. Simple example for ReadProcessMemory:
*** USER MODE ***
1) compiler has ReadProcessMemory mapped on some fixed address in its own code , where it only read its own process table and jump to next step (KERNEL32 part of same procedure). In case of my compiler, that is:
APP.readProcessMemory: $4xxxxx
jmp mem4[$4xxxxyy] -> 77e61a54 (KERNEL32.ReadProcessMemory)
2) in KERNEL32 it put parameters on stack andll just redirect to deeper level, ntdll, and still in user mode.
KERNEL32.ReadProcessMemory: 77e61a54
call mem4[$77e613fc] -> 77f7ef53 (ntdll.NtReadVirtualMemory)
3) in ntdll there is last element in chain in USER mode, it is basically just setting system procedure number into EAX (in this case it is $BA) and transferring control to real procedure in NTOSKRNL in ring 0 , with SYSENTER.
*** KERNEL MODE ****
4) service handler receive service call, and based on number in EAX it looks into System Service Dispatch Table, and jump to that address
5) finally, actuall code for procedure is reached and it does what it need to do, then return along chain back to user app.
-------------------------------------------------------------
Where they can intercept call? Well, they can do it in user or kernel mode.
In user mode they can do it at any of points 1-3, but since point 1 is usually compiler based, its more probable that they would change table that KERNEL32 is using. Note, even if its called KERNEL32, it works in ring 3 user mode, so any program like one we use to hook DLL can read that memory and write its own data.
In kernel mode they need ring 0 access, which they can gain either thru device driver that loads at ring 0, or using KeServiceDescriptorTable() . They can either change SSDT for $BA entry to point (redirect ) to their code (easiest), or change code where SSDT points normally.
---------------------------------------------------------------------
How can we protect ourselves? Well, one thing that i can think of is just by initially reading all this memory and pointers in chain for each of 4 APi functions and saving it to file. Later, with every execution of read, we first reread that chain memory footprint and pointers and see if it is identical to what it was when we originally saved ( since Kernel DLLs always go in same spot in memory, it is doable). If all is same as before, noone hooked and we are safe. If anything changed, just show Warning message and exit.
So far i did that part for USER mode, and I'm still looking for easy way to get to ring 0 to check KERNEL mode. I found that KeServiceDescriptorTable is most often used method , but since I'm not using C and its undocumented export, so far I couldnt link it in NTOSKERNL.EXE, but I'll keep trying.
Also, same technique that they can use for inserting API hook interceptor on kernel level, or that we need to use to check SSDT tables, could be used to make ring 0 key reader :)
lostinspace
11-25-2002, 09:36 AM
Finally I think that I have non-detectable keyreader.
Whatever SOE do to try and detect keyreaders , it can come up with three kinds of results:
- everything is OK ( whatever that means )
- there is something unusuall, but they are not sure what it is. Example is detecting unusuall DLL attached to their process. It is unusuall, but they can not draw any conclusion based on that - it could be anything like mouse drivers, virus scanner etc...
- there is something that is tempering with EQgame. Example is if they catch something using API calls related to their process, like ReadProcessMemory, OpenProcess ... presence of EQ process handle is positive proof enough that something is targeting EQgame.
As I pointed before, interception of APi call is only positive way for them to do it, and I tried to make sure they did not intercept. Then I hit ring 3 <-> ring 0 barrier ... basically, I couldnt check if they hook on service dispatch table in kernel.
So I finally installed that VC6 and DDK and made my simple driver which enabled me access to ring 0 and Service Dispatch tables. But while I was reading and comparing SSDT, I did small test - read of user process memory (like reading key from EQ) from my kernel driver . And I got very positive result :
Reading user process memory from kernel driver DOES NOT trigger VirtualProtect traps !
What does that means? Basically it means that now I can read EQ key memory without calling ANY API function that has any direct refference to EQgame process. Since I still didnt find out how to switch linear addressing from one proccess to other in kernel driver (they run in caller procces linear address space usually, at least mine does since its high level driver), I must call it from EQgame process. So, here is what I do:
1) set GlobalHookEx as I did previously, from hookshell.exe. Hook will be on hook.dll
2) in hook.dll initialization part I check if this is EQgame
3) if this was EQ, in hooked event I periodically read memory from key area, using ioCtl function from my driver
4) I return key data to outside program hookshell.exe
5) hookshell.exe does sending UDP packets or writing to shared samba folder or showing key on screen, according to INI file
Notice that NONE of those operations use handle of EQgame, of its process or anything else.
Previously my Hook version did same thing, only used normal memory read from in-process. Danger of that was if SOE VirtualProtect key area in next patch. Solution to that was to use my own VirtualProtect to unprotect/reprotect around my read. Danger to that was if SOE intercept calls to VirtualProtect API. But now i read memory area from kernel, and it will not trigger even if SOE use VirtualProtect.
maggotboy
11-25-2002, 10:55 AM
Lets have a look at it!
Maggotboy
RavenCT
11-25-2002, 11:21 AM
Very nice!
I'm no coder (As I've said as such before), but this sounds like quite a pickel for SoE (even if they can) to unravel!
:D
He he he, when are you going to post it? :rolleyes:
lostinspace
11-25-2002, 11:52 AM
Well, I didnt post source because:
a) if someone know how to install VC6 and DDK, and how to compile and use sample drivers , then previous text was enough. If someone dont know to do that, then source wont help
b) I changed about 5% of already existing sample driver code for random driver. So posting all source would be like posting Microsoft sample driver
But anyway, here is my procedure. You choose any sample driver, and in Dispatch( IN PDEVICE_OBJECT pDO, IN PIRP pIrp ) function add or change existing pIrpStack->MajorFunction to call your function : myIoctlReadTst.
NOTE: while read of memory from kernel will not trip guard, it still fails if memory is VirtualProtected, BUT that failure will not make hooked DLL detected. It is possible to use VirtualProtect counterparts from kernel to also unprotect memory if needed, but that so far is not needed. Important advantage is : if they suddenly add protection, this simple read will only fail reading, and will not trigger trap.
---------------------------------------------------------------------------------
// KERNEL DRIVER part (note, only part, rest is same as sample in ntddk/src
// INPUT buffer format
// I used 12 byte input buffer, in form
// DWORD callType
// PVOID adrToRead
// DWORD flags
// although flags are optional
// only type 8 is for memory read, others are for test
// OUTPUT buffer format
// for callType 8 = memory read, i simply return requested number of bytes
// CHAR result[N] , where N=OutBufferSize
//
// for other callTypes (like 1=version), i return
// PVOID adrFromWhichIReaded
// CHAR result[N] , whene N=4 for version
// --> TEST <--
NTSTATUS
myIoctlReadTst(
IN PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack,
IN ULONG IoctlCode )
{
PULONG pIOBuffer; // Pointer to transfer buffer
ULONG InBufferSize; // Amount of data avail. from caller.
ULONG OutBufferSize; // Max data that caller can accept.
ULONG nPort; // Port number to read
DWORD64 nOut; // return integer
ULONG flags; // input flags
ULONG DataBufferSize;
VOID UNALIGNED *pSource, *pDest;
PVOID pAddress;
PULONG pUlong;
PDWORD64 p64bit;
PAGED_CODE();
// Size of buffer containing data from application
InBufferSize = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
// Size of buffer for data to be sent to application
OutBufferSize = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
// NT copies inbuf here before entry and copies this to outbuf after
// return, for METHOD_BUFFERED IOCTL's.
pIOBuffer = (PULONG)pIrp->AssociatedIrp.SystemBuffer;
// check input & output
nPort = *pIOBuffer; // Get read(call) type
// return result based on type. Only ReadMemory call type (8) remained in listing
switch (nPort){
case 8: if (InBufferSize<8) return STATUS_INVALID_PARAMETER;
p64bit= (PDWORD64) *(pIOBuffer+1);
if (InBufferSize>=12) flags=*(pIOBuffer+2); else flags=0;
// should I check for Memory validation?
if ((flags& 0x01)==0){
pAddress= (PVOID) (((ULONG)p64bit & 0xFFFFF000));
if (!MmIsAddressValid(pAddress)) return STATUS_ACCESS_VIOLATION;
}
nOut= *p64bit;
pSource = (VOID UNALIGNED *)&nOut; // int64 from ptr->
if (OutBufferSize>8) DataBufferSize=8; else DataBufferSize=OutBufferSize;
break;
default:
return STATUS_INVALID_PARAMETER;
}
pDest=pIOBuffer;
if (DataBufferSize>OutBufferSize) return STATUS_INVALID_PARAMETER;
RtlCopyMemory (pDest, pSource , DataBufferSize);
//
// Indicate # of bytes read
//
pIrp->IoStatus.Information = DataBufferSize;
return STATUS_SUCCESS;
}
--------------------------------------------------------------------
//You use memory read from USER application (like hook.dll) with:
BOOL IoctlResult;
HANDLE hndFile; // Handle to device, obtain from CreateFile
LONG IoctlCode;
DWORD ReturnedLength; // Number of bytes returned
DWORD64 resultKey;
struct TinDrv // Declare inDrv struct type
{
DWORD callType;
PVOID adrToRead;
DWORD flags;
} drvIn; // Define object to hold input data for driver
hndFile = CreateFile(
"\\\\.\\nameOfDev", // Open the Device "file"
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hndFile == INVALID_HANDLE_VALUE) // Was the device opened?
{
printf("Unable to open the device.\n");
exit(1);
}
IoctlCode = IOCTL_GPD_READ_TST2;
drvIn.callType=8;
drvIn.adrToRead= <address that you want to read, = offset in EQ> ;
drvIn.flags=0;
DataLength = sizeof(DataBuffer.CharData);
IoctlResult = DeviceIoControl(
hndFile, // Handle to device
IoctlCode, // IO Control code for Read
&drvIn, // Buffer to driver.
sizeof(drvIn), // Length of buffer in bytes.
&resultKey, // return Buffer from driver.
8, // Length of buffer in bytes.
&ReturnedLength, // Bytes placed in DataBuffer.
NULL // NULL means wait till op. completes.
);
if (IoctlResult) // Did the IOCTL succeed?
{
// here do whatever you want to do with your key
printf("and the key is .... %x", resultKey);
}else
printf("Read failed with code %ld\n", GetLastError() );
if (!CloseHandle(hndFile)) printf("Failed to close device.\n");
exit(0);
}
----------------------------------------------------------------------------
throx
11-25-2002, 12:34 PM
Impressive. The next steps are:
i) Work through the steps to access another process's memory from ring0.
ii) Create a 2nd virtual memory page mapping to the memory inside eqgame.exe which has read access.
This would leave Sony the only options for detection:
- Obfuscate the key better. This can be reverse engineered but can be painful if changed frequently.
- Write specific code to detect the ioctl calls to your driver (probably another driver), which you could then detect and subvert, which they could detect etc.
- Switch to hardware based protection (USB or LPT dongle) which has the hardware itself doing the decrypt.
- Lobby for Palladium and register itself into the DRM trusted channel stuff which code unsigned from Microsoft won't be able to touch (how they are going to protect from ring 0 drivers is beyond me though). This is pretty irrelavent to the present problem, but is a problem in future.
maggotboy
11-25-2002, 01:58 PM
I'll install the DDK from my MSDN Universal subscription and have a look at the samples. Ideally, I'd like to start a timer in kernel mode to read the memory location, which would allow the original hook DLL to terminate after initiating the driver.
I've also done some research on accessing memory (all of it, not just from a specific process) from kernel mode ... Virtual Memory internals are not for the weak of heart, though.
Maggotboy
lostinspace
11-25-2002, 03:39 PM
Yes, there are kernel equivalents of functions that enumerate processes ( like Process32First) , or read other process memory. Maybe I'll look into those, or any other way to switch linear address space to other process from kernel.
About those options that Sony have, I see only first one as viable option, because:
- even if they can detect ioctl call, they will never know what is driver that is called. Name of driver is arbitrary, so are ioctl codes. And tons of windows stuff that application normally use, also use ioctl calls ( to network drivers, graphic drivers....). With all those layered drivers etc, application can never know for sure what some ioctl call really does, even if they somehow catch it.
- dongle does not seem as option, because it would require 400000 people to buy it ... and that was not advertised on box when you originally bought it. Not to mention that dongle would be high pain to SOE support stuff , and it would not be profit to Sony but to dongle maker
- about Palladium and DRM I dont know anything, but I believe that is not option they have now
About memory accessing under win2000, I also searched a lot on web, and found one book that looked very interesting, judging on articles posted on this url:
http://www.awprofessional.com/catalog/article.asp?product_id=%7B6D5ACA10-5AB0-4627-81E5-65B9F6E080AC%7D
lostinspace
11-26-2002, 04:33 AM
Well, today I tried few other approaches from kernel driver, and one of them looks interesting, and even works :)
I added two functions to my driver:
case 9: if (InBufferSize<8) return STATUS_INVALID_PARAMETER;
p64bit= (PDWORD64) *(pIOBuffer+1);
pa= MmGetPhysicalAddress(p64bit);
pSource = (VOID UNALIGNED *)&pa; // get Physical address
DataBufferSize=sizeof(pa);
break;
case 10: if (InBufferSize<12) return STATUS_INVALID_PARAMETER;
pa= * (PPHYSICAL_ADDRESS) (pIOBuffer+1);
p64bit2= MmMapIoSpace(pa, 8, MmNonCached );
nOut= *p64bit2;
MmUnmapIoSpace( p64bit2, 8);
pSource = (VOID UNALIGNED *)&nOut; // read from Phys addr
DataBufferSize=sizeof(nOut);
break;
Function (9) return Physical address based on Virtual address, and I call it only once when my hook.DLL detect that it is EQ process. Then resulting Physical address is returned to hookshell.exe and dll terminate immediatelly.
Function (10) read from given Physical Address. It can be called from ANY process, and in my case i call it from hookshell.exe whenewer i want to read key.
Important advantages of this approach:
- no need for attached DLL to EQgame
- ability to read memory from more comfortable external EXE (which also send UDP etc..)
- reading does not trigger eventual VirtualProtect, and .....
- reading CAN READ key even if it is VirtualProtected !
Possible disadvantages:
- IF physical memory is paged out, key reading will be wrong. BUT it wont make any exception or trigger anything, it will just return wrong key. And it is highly unlikely that code/data part of running active process will get paged out :) and I can always reattach temporarily hook.dll to read new address and detach immediattely. LIke having <Refresh> button in hookshell.exe
LordCrush
11-26-2002, 04:45 AM
OMG - i was away from forum few hrs :D - You guys rock !!!!
Thank you /bow
wiz60
11-26-2002, 08:57 AM
I picked up a copy of this book - it has an excellent chapter on memory management.
http://www.readmedoc.com/bookdetails.asp?ISBN=0201721872
The memory management issue may not be too tough. Given a virtual address - the corresponding PTE used to access memory is known.
The process has a vector containing the PTEs that can be quickly loaded into hardware during context switch.
As I recall there are working pages available at driver level - these are normally mapped to the calling process to manage buffers etc.
You should be able to force the proper mapping by copying the correct PTE from eq - into your ring-0 context. This is a little dicey. Most drivers I have seen allocate internal buffers and copy memory to or from user context in chunks.
Once you have your own VM mapping - nothing can see you. However - you also have NO way of knowing if the page is in memory, or worse yet if the physical location of the page changes. To be safe - you really need to access the PTE vector for EQ on every read. This is not a huge issue. EQ trys to dominate the machine - and if you have enough memory chances are there is no swapping occurring. But to be safe - it should probably be handled.
throx
11-26-2002, 07:22 PM
wiz, messing with page tables isn't a great idea. There's better ways.
lostinspace, look at firing an APC in the eqgame.exe process to read the memory location and then return it to your driver. Will probably require a bit more modification of the stock driver but will essentially eliminate the need for any DLL injections in user space.
For the attacks, I was just tossing things out there that were possible, however improbable. Any of them could be *made* to happen but whether they are practical is Sony's problem. I would never expect them to take the obvious route to securing their process though...
Ioctl from another unrelated process may as well be undetectable if the process and driver are polymorphic. However the driver must be compiled with the DDK so there may be something there to hook onto in a virusscan style way?
Dongles are a customer service pain, but a possibility for EQ2. I certainly doubt they will send them out to current clients.
Palladium won't be out for a while. Still interested to see what they do with it.
Moving the key around is just silliness but probably the only real option left that will be effective for long.
lostinspace
11-27-2002, 11:49 AM
After testing my new implementation of key reader, i found few problems ... guess anyone who try to implement it that way will hit those so i'll post solution.
Namely, my new keyreader did following:
1) Hookshell.exe is main prog, it set hook with hook.dll
2) when EQgame starts, in hook.dll i detect it, read Physical Address of key area [ using function 9 of myDriver.sys] , return it to Hookshell.exe and set ExitCode <>0 so DLL does not load at all
3) in Hookshell.exe I use physical address to read key whenever i need it (i do it every N sec) [ using function 10 of myDriver.sys], and send it to SEQ when it change
That is very good solution since , as I already mentioned, I dont use any detectable API call, DLL does not attach to process, and I can read key even if it is VirtualProtected.
But .... as I mentioned, I experienced 2 problems:
1) first one is fact that if I return <>0 exit code so DLL does not attach, and if hook is still active (as it is), DLL will attach again on EQgame...and again...and again - until I unhook .
First solution was to unhook in DLL when i detect EQgame.... but I didnt want to use any exotic API call from same process as eqgame, and UnhookWindowsHookEx is sure exotic :)
Second solution was not to detach DLL with exit code <>0, but as soon as i receive Phys address in Hookshell.exe, I detach hook there - detaching hook detach DLL too. So DLL remains attached for about 1sec, but I'm ok with that
2) second problem was far more serious ... and was not manifested in EQgame, but while reading key from my appTest.exe where I tested variations with relocated keys and virtualProtected keys.
Problem was that sometimes i ccould read key even when it was VirtualProtected, and sometimes I could not. Further investigation showed that i can ALWAYS read key from physical memory regardless of protect status, BUT I can not get initiall physical address from virtual key address if key was already protected.
Function I used was MmGetPhysicalAddress, and there is no lower kernel function that can do that virtual<->physical conversion. No VirtualProtect function is exported in kernel. So I thought I'm stuck there, until I started experimenting with my own GetPhysicalAddress function ... that needed to decode physical address in same way that Intel processor does. After reading some of already posted materials about paging memory system , I finally made my own function that returns physical address EVEN if virtual address is locked :)
Here is code, I added it into kernel driver as function #13:
case 13: // my custom GetPhysicalAddress
if (InBufferSize<8) return STATUS_INVALID_PARAMETER;
dw= *(pIOBuffer+1); // Virtual(linear) addres for which we want PA
if ((dw >= 0x80000000)&&(dw<0xA0000000)){
// here is 4MB paged kernel range. I dont need it, but ...
nOut= dw & 0x1FFFFFFF;
} else {
// here is part for 4kb pages, read PTE at 0xC0000000
pUlong= (PULONG) (0xC0000000 + ((dw >> 12) * 4)) ; // calculate PTE address
if (!MmIsAddressValid(pUlong)) return STATUS_ACCESS_VIOLATION;
nOut= *pUlong; // read start of Phys page from PTE
nOut= (nOut & 0xFFFFF000) | (dw & 0xFFF); // last bits are retained from virt. addr
}
pSource = (VOID UNALIGNED *)&nOut;
DataBufferSize=sizeof(nOut);
break;
Pokesfan
11-27-2002, 12:26 PM
Just a note:
Dongles are not even a possibility as more and more motherboards (like mine) are coming without ANY legacy ports. That means no serial/PS2 ports and no parallel ports. Just USB and Firewire. I've never heard of a dongles for those ports.
I have major doubts that Sony will do anything of the sort.
Pokesfan
bonkersbobcat
11-27-2002, 01:28 PM
While I don't think that SoE would go to the trouble and expense of a hardware dongle, they do exist for USB.
Google (search) for "usb dongle copy protect" and you will get many, here is the first I found:
http://www.marx1.com/
LordCrush
11-28-2002, 04:28 PM
lostinspace just a question:
I don´t want to seem greedy, but do you plan to post the source of the driver and the calling app in complete ?
I have not a great skill in programming, i am a networking consultant and i just wanted to know before i try to make it work ... what is not guranteed that i will come to that point ...
As said above, its just a question - if you want to keep your work its fine to me you gave great hints.
Thank you again :)
aha, and yes i never looked on the demo of MS - i am using Borland and have to get the MS-Compiler first, because my personal edition of Borland cannot compile a driver :P
lostinspace
11-29-2002, 03:22 AM
As I already posted before, I didnt post complete driver code because only one line needs to be added in any small driver example in DDK, which will invoke my procedure for which I posted code. But if it will be easier for those who are trying to get their kernel driver working, I will post here complete code.
I'll take as base DDK demo for portio at NTDDK\src\general\portio. There are 3 subfolders there, 2 are for sample read/write clients (ignore them), and 3rd is 'sys' - where driver source is.
You actually need to change only 3 files:
NTDDK\src\general\portio\gpioctl.h
NTDDK\src\general\portio\sys\genport.h
NTDDK\src\general\portio\sys\genport.c
You need to do following:
1- install VC 6
2- install DDK. I used DDK 2000, but compiled on XP
3- test if you are able to compile sample portio driver as it is
4- Replace listed files with files that I will post in following post
5- start 'Free build environment' DOS prompt from DDK
6- cd \NTDDK\src\general\portio
7- build -cewZ
8- after that, new driver is portio.sys in portio\sys\objfre\i386
9- install new driver from ControlPanel/AddNewHardware (there is explanation how to do as HTML in root of DDK CD, explaining how to do step 3, test DDK )
10- change your existing keyreader to use driver in any way that you think is appropriate
11- when its working, change source for driver, add / rearrange things so your driver is different, and repeat steps 5-9
I will not post source for my keyreader program, from several reasons, like:
- it is very large and complex now, since I'm using same program to test all my read options and to simulate program that needs to be read, to simulate VirtualProtect usage etc....
- it is not written in C , so most people here who use C-compiler would not be able to use it
- using of kernel driver in existing posted sniffer versions (maggotboys for example) is very simple, and I posted few lines of code that invoke ioctl call. You use function 8 from driver instead of directly reading memory. Or you go more creative and use function 9 only once to read Physical Address (PA), and later use function 10 to read from PA. I posted options that I use in my reader
But if someone will have questions/problems about using kernel driver, post them here and I'll try to help.
lostinspace
11-29-2002, 03:23 AM
gpioctl.h
/*++
Copyright (c) 1990-2000 Microsoft Corporation, All Rights Reserved
Module Name:
gpioctl.h
Abstract: Include file for Generic Port I/O Example Driver
Author: Robert R. Howell January 8, 1993
Revision History:
Robert B. Nelson (Microsoft) March 1, 1993
--*/
#if !defined(__GPIOCTL_H__)
#define __GPIOCTL_H__
//
// Define the IOCTL codes we will use. The IOCTL code contains a command
// identifier, plus other information about the device, the type of access
// with which the file must have been opened, and the type of buffering.
//
//
// Device type -- in the "User Defined" range."
//
#define GPD_TYPE 40000
// The IOCTL function codes from 0x800 to 0xFFF are for customer use.
#define IOCTL_GPD_READ_PORT_UCHAR \
CTL_CODE( GPD_TYPE, 0x900, METHOD_BUFFERED, FILE_READ_ACCESS )
#define IOCTL_GPD_READ_PORT_USHORT \
CTL_CODE( GPD_TYPE, 0x901, METHOD_BUFFERED, FILE_READ_ACCESS )
#define IOCTL_GPD_READ_PORT_ULONG \
CTL_CODE( GPD_TYPE, 0x902, METHOD_BUFFERED, FILE_READ_ACCESS )
#define IOCTL_GPD_WRITE_PORT_UCHAR \
CTL_CODE(GPD_TYPE, 0x910, METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_GPD_WRITE_PORT_USHORT \
CTL_CODE(GPD_TYPE, 0x911, METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_GPD_WRITE_PORT_ULONG \
CTL_CODE(GPD_TYPE, 0x912, METHOD_BUFFERED, FILE_WRITE_ACCESS)
#define IOCTL_GPD_READ_TST2 \
CTL_CODE(GPD_TYPE, 0x974, METHOD_BUFFERED, FILE_READ_ACCESS)
typedef struct _GENPORT_WRITE_INPUT {
ULONG PortNumber; // Port # to write to
union { // Data to be output to port
ULONG LongData;
USHORT ShortData;
UCHAR CharData;
};
} GENPORT_WRITE_INPUT;
#endif
lostinspace
11-29-2002, 03:24 AM
genport.h
/*++
Copyright (c) 1990-2000 Microsoft Corporation, All Rights Reserved
Module Name:
genport.h
Abstract: Include file for Generic Port I/O Example Driver
Author: Robert R. Howell January 6, 1993
Environment:
Kernel mode
Revision History:
Eliyas Yakub Dec 29, 1998
Converted to Windows 2000
--*/
#include <ntddk.h>
#include "gpioctl.h" // Get IOCTL interface definitions
#if !defined(__GENPORT_H__)
#define __GENPORT_H__
// NT device name
#define GPD_DEVICE_NAME L"\\Device\\mtst0"
// File system device name. When you execute a CreateFile call to open the
// device, use "\\.\GpdDev", or, given C's conversion of \\ to \, use
// "\\\\.\\GpdDev"
#define DOS_DEVICE_NAME L"\\DosDevices\\mtstDev"
#define PORTIO_TAG 'TROP'
// driver local data structure specific to each device object
typedef struct _LOCAL_DEVICE_INFO {
PVOID PortBase; // base port address
ULONG PortCount; // Count of I/O addresses used.
ULONG PortMemoryType; // HalTranslateBusAddress MemoryType
PDEVICE_OBJECT DeviceObject; // The Gpd device object.
PDEVICE_OBJECT NextLowerDriver; // The top of the stack
BOOLEAN Started;
BOOLEAN Removed;
BOOLEAN PortWasMapped; // If TRUE, we have to unmap on unload
BOOLEAN Filler[1]; //bug fix
IO_REMOVE_LOCK RemoveLock;
} LOCAL_DEVICE_INFO, *PLOCAL_DEVICE_INFO;
#if DBG
#define DebugPrint(_x_) \
DbgPrint ("PortIo:"); \
DbgPrint _x_;
#define TRAP() DbgBreakPoint()
#else
#define DebugPrint(_x_)
#define TRAP()
#endif
/********************* function prototypes ***********************************/
//
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
);
NTSTATUS
GpdDispatch(
IN PDEVICE_OBJECT pDO,
IN PIRP pIrp
);
NTSTATUS
GpdIoctlReadPort(
IN PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack,
IN ULONG IoctlCode
);
NTSTATUS
GpdIoctlReadTst(
IN PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack,
IN ULONG IoctlCode
);
NTSTATUS
GpdIoctlWritePort(
IN PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack,
IN ULONG IoctlCode
);
VOID
GpdUnload(
IN PDRIVER_OBJECT DriverObject
);
NTSTATUS
GpdAddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
);
NTSTATUS
GpdDispatchPnp (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
NTSTATUS
GpdStartDevice (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
NTSTATUS
GpdDispatchPower(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
NTSTATUS
GpdDispatchSystemControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
PCHAR
PnPMinorFunctionString (
UCHAR MinorFunction
);
#endif
lostinspace
11-29-2002, 03:26 AM
genport.c
/*++
Copyright (c) 1990-2000 Microsoft Corporation, All Rights Reserved
Module Name:
genport.c
Abstract: Generic Port I/O driver for Windows 2000
Author: Author: Robert R. Howell January 8, 1993
Environment:
Kernel mode
Revision History:
Robert B. Nelson (Microsoft) January 12, 1993
Cleaned up comments
Enabled and tested resource reporting
Added code to retrieve I/O address and port count from the Registry.
Robert B. Nelson (Microsoft) March 1, 1993
Added support for byte, word, and long I/O.
Added support for MIPS.
Fixed resource reporting.
Robert B. Nelson (Microsoft) May 1, 1993
Fixed port number validation.
Robert B. Nelson (Microsoft) Oct 25, 1993
Fixed MIPS support.
Eliyas Yakub
Fixed AddressSpace Bug Nov 30, 1997
Eliyas Yakub
Converted to Windows 2000 Dec 29, 1998
Fixed bugs Feb 17, 2000
--*/
#include "genport.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, GpdAddDevice)
#pragma alloc_text (PAGE, GpdDispatchPnp)
#pragma alloc_text (PAGE, GpdDispatchSystemControl)
#pragma alloc_text (PAGE, GpdUnload)
#pragma alloc_text (PAGE, GpdDispatch)
#pragma alloc_text (PAGE, GpdIoctlReadPort)
#pragma alloc_text (PAGE, GpdIoctlReadTst)
#pragma alloc_text (PAGE, GpdIoctlWritePort)
#pragma alloc_text (PAGE, GpdStartDevice)
#endif
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
Installable driver initialization entry point.
This entry point is called directly by the I/O system.
Arguments:
DriverObject - pointer to the driver object
RegistryPath - pointer to a unicode string representing the path,
to driver-specific key in the registry.
Return Value:
STATUS_SUCCESS
--*/
{
UNREFERENCED_PARAMETER (RegistryPath);
DebugPrint (("Entered Driver Entry\n"));
//
// Create dispatch points for the IRPs.
//
DriverObject->MajorFunction[IRP_MJ_CREATE] = GpdDispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = GpdDispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = GpdDispatch;
DriverObject->DriverUnload = GpdUnload;
DriverObject->MajorFunction[IRP_MJ_PNP] = GpdDispatchPnp;
DriverObject->MajorFunction[IRP_MJ_POWER] = GpdDispatchPower;
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = GpdDispatchSystemControl;
DriverObject->DriverExtension->AddDevice = GpdAddDevice;
return STATUS_SUCCESS;
}
NTSTATUS
GpdAddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
/*++
Routine Description:
The Plug & Play subsystem is handing us a brand new PDO, for which we
(by means of INF registration) have been asked to provide a driver.
We need to determine if we need to be in the driver stack for the device.
Create a functional device object to attach to the stack
Initialize that device object
Return status success.
Remember: we can NOT actually send ANY non pnp IRPS to the given driver
stack, UNTIL we have received an IRP_MN_START_DEVICE.
Arguments:
DeviceObject - pointer to a device object.
PhysicalDeviceObject - pointer to a device object created by the
underlying bus driver.
Return Value:
NT status code.
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT deviceObject = NULL;
PLOCAL_DEVICE_INFO deviceInfo;
UNICODE_STRING ntDeviceName;
UNICODE_STRING win32DeviceName;
PAGED_CODE();
RtlInitUnicodeString(&ntDeviceName, GPD_DEVICE_NAME);
//
// Create a device object.
//
status = IoCreateDevice (DriverObject,
sizeof (LOCAL_DEVICE_INFO),
&ntDeviceName,
GPD_TYPE,
0,
FALSE,
&deviceObject);
if (!NT_SUCCESS (status)) {
//
// Either not enough memory to create a deviceobject or another
// deviceobject with the same name exits. This could happen
// if you install another instance of this device.
//
return status;
}
RtlInitUnicodeString(&win32DeviceName, DOS_DEVICE_NAME);
status = IoCreateSymbolicLink( &win32DeviceName, &ntDeviceName );
if (!NT_SUCCESS(status)) // If we we couldn't create the link then
{ // abort installation.
IoDeleteDevice(deviceObject);
return status;
}
deviceInfo = (PLOCAL_DEVICE_INFO) deviceObject->DeviceExtension;
deviceInfo->NextLowerDriver = IoAttachDeviceToDeviceStack (
deviceObject,
PhysicalDeviceObject);
if(NULL == deviceInfo->NextLowerDriver) {
IoDeleteSymbolicLink(&win32DeviceName);
IoDeleteDevice(deviceObject);
return STATUS_NO_SUCH_DEVICE;
}
IoInitializeRemoveLock (&deviceInfo->RemoveLock ,
PORTIO_TAG,
1, // MaxLockedMinutes
5); // HighWatermark, this parameter is
// used only on checked build.
//
// Set the flag if the device is not holding a pagefile
// crashdump file or hibernate file.
//
deviceObject->Flags |= DO_POWER_PAGABLE;
deviceInfo->DeviceObject = deviceObject;
deviceInfo->Removed = FALSE;
deviceInfo->Started = FALSE;
deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
//
// This values is based on the hardware design.
// Let us assume the address is in I/O space.
//
deviceInfo->PortMemoryType = 1;
DebugPrint(("AddDevice: %p to %p->%p \n", deviceObject,
deviceInfo->NextLowerDriver,
PhysicalDeviceObject));
return STATUS_SUCCESS;
}
NTSTATUS
GpdCompletionRoutine(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
/*++
Routine Description:
The completion routine for plug & play irps that needs to be
processed first by the lower drivers.
Arguments:
DeviceObject - pointer to a device object.
Irp - pointer to an I/O Request Packet.
Context - pointer to an event object.
Return Value:
NT status code
--*/
{
PKEVENT event;
event = (PKEVENT) Context;
UNREFERENCED_PARAMETER(DeviceObject);
if (Irp->PendingReturned) {
IoMarkIrpPending(Irp);
}
//
// We could switch on the major and minor functions of the IRP to perform
// different functions, but we know that Context is an event that needs
// to be set.
//
KeSetEvent(event, 0, FALSE);
//
// Allows the caller to reuse the IRP
//
return STATUS_MORE_PROCESSING_REQUIRED;
}
NTSTATUS
GpdDispatchPnp (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
The plug and play dispatch routines.
Most of these the driver will completely ignore.
In all cases it must pass the IRP to the next lower driver.
Arguments:
DeviceObject - pointer to a device object.
Irp - pointer to an I/O Request Packet.
Return Value:
NT status code
--*/
{
PIO_STACK_LOCATION irpStack;
NTSTATUS status = STATUS_SUCCESS;
KEVENT event;
UNICODE_STRING win32DeviceName;
PLOCAL_DEVICE_INFO deviceInfo;
PAGED_CODE();
deviceInfo = (PLOCAL_DEVICE_INFO) DeviceObject->DeviceExtension;
irpStack = IoGetCurrentIrpStackLocation(Irp);
status = IoAcquireRemoveLock (&deviceInfo->RemoveLock, Irp);
if (!NT_SUCCESS (status)) {
Irp->IoStatus.Status = status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return status;
}
DebugPrint(("%s\n",PnPMinorFunctionString(irpStack->MinorFunction)));
switch (irpStack->MinorFunction) {
case IRP_MN_START_DEVICE:
//
// The device is starting.
//
// We cannot touch the device (send it any non pnp irps) until a
// start device has been passed down to the lower drivers.
//
IoCopyCurrentIrpStackLocationToNext(Irp);
KeInitializeEvent(&event,
NotificationEvent,
FALSE
);
IoSetCompletionRoutine(Irp,
(PIO_COMPLETION_ROUTINE) GpdCompletionRoutine,
&event,
TRUE,
TRUE,
TRUE);
status = IoCallDriver(deviceInfo->NextLowerDriver, Irp);
if (STATUS_PENDING == status) {
KeWaitForSingleObject(
&event,
Executive, // Waiting for reason of a driver
KernelMode, // Must be kernelmode if event memory is in stack
FALSE, // No allert
NULL); // No timeout
}
if (NT_SUCCESS(status) && NT_SUCCESS(Irp->IoStatus.Status)) {
status = GpdStartDevice(DeviceObject, Irp);
if(NT_SUCCESS(status))
{
//
// As we are successfully now back from our start device
// we can do work.
//
deviceInfo->Started = TRUE;
deviceInfo->Removed = FALSE;
}
}
//
// We must now complete the IRP, since we stopped it in the
// completion routine with STATUS_MORE_PROCESSING_REQUIRED.
//
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
break;
case IRP_MN_QUERY_STOP_DEVICE:
//
// Fail the query stop to prevent the system from taking away hardware
// resources. If you do support this you must have a queue to hold
// incoming requests between stop and subsequent start with new set of
// resources.
//
Irp->IoStatus.Status = status = STATUS_UNSUCCESSFUL;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
break;
case IRP_MN_QUERY_REMOVE_DEVICE:
//
// The device can be removed without disrupting the machine.
//
Irp->IoStatus.Status = STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(deviceInfo->NextLowerDriver, Irp);
break;
case IRP_MN_SURPRISE_REMOVAL:
//
// The device has been unexpectedly removed from the machine
// and is no longer available for I/O. Stop all access to the device.
// Release any resources associated with the device, but leave the
// device object attached to the device stack until the PnP Manager
// sends a subsequent IRP_MN_REMOVE_DEVICE request.
// You should fail any outstanding I/O to the device. You will
// not get a remove until all the handles open to the device
// have been closed.
//
deviceInfo->Removed = TRUE;
deviceInfo->Started = FALSE;
if (deviceInfo->PortWasMapped)
{
MmUnmapIoSpace(deviceInfo->PortBase, deviceInfo->PortCount);
deviceInfo->PortWasMapped = FALSE;
}
RtlInitUnicodeString(&win32DeviceName, DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&win32DeviceName);
IoSkipCurrentIrpStackLocation(Irp);
Irp->IoStatus.Status = STATUS_SUCCESS;
status = IoCallDriver(deviceInfo->NextLowerDriver, Irp);
break;
case IRP_MN_REMOVE_DEVICE:
//
// Relinquish all resources here.
// Detach and delete the device object so that
// your driver can be unloaded. You get remove
// either after query_remove or surprise_remove.
//
if(!deviceInfo->Removed)
{
deviceInfo->Removed = TRUE;
deviceInfo->Started = FALSE;
if (deviceInfo->PortWasMapped)
{
MmUnmapIoSpace(deviceInfo->PortBase, deviceInfo->PortCount);
deviceInfo->PortWasMapped = FALSE;
}
RtlInitUnicodeString(&win32DeviceName, DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&win32DeviceName);
}
//
// Wait for all outstanding requests to complete
//
DebugPrint(("Waiting for outstanding requests\n"));
IoReleaseRemoveLockAndWait(&deviceInfo->RemoveLock, Irp);
Irp->IoStatus.Status = STATUS_SUCCESS;
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(deviceInfo->NextLowerDriver, Irp);
IoDetachDevice(deviceInfo->NextLowerDriver);
IoDeleteDevice(DeviceObject);
return status;
case IRP_MN_STOP_DEVICE:
// Since you failed query stop, you will not get this request.
case IRP_MN_CANCEL_REMOVE_DEVICE:
// No action required in this case. Just pass it down.
case IRP_MN_CANCEL_STOP_DEVICE:
//No action required in this case.
Irp->IoStatus.Status = STATUS_SUCCESS;
default:
//
// Please see PnP documentation for use of these IRPs.
//
IoSkipCurrentIrpStackLocation (Irp);
status = IoCallDriver(deviceInfo->NextLowerDriver, Irp);
break;
}
IoReleaseRemoveLock(&deviceInfo->RemoveLock, Irp);
return status;
}
NTSTATUS
GpdStartDevice (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
Get the resources, map the resources if required
and initialize the device.
Arguments:
DeviceObject - pointer to a device object.
Irp - pointer to an I/O Request Packet.
Return Value:
NT status code
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PCM_PARTIAL_RESOURCE_DESCRIPTOR resource;
PCM_PARTIAL_RESOURCE_DESCRIPTOR resourceTrans;
PCM_PARTIAL_RESOURCE_LIST partialResourceList;
PCM_PARTIAL_RESOURCE_LIST partialResourceListTranslated;
PIO_STACK_LOCATION stack;
ULONG i;
PLOCAL_DEVICE_INFO deviceInfo;
deviceInfo = (PLOCAL_DEVICE_INFO) DeviceObject->DeviceExtension;
stack = IoGetCurrentIrpStackLocation (Irp);
PAGED_CODE();
//
// We need to check that we haven't received a surprise removal
//
if (deviceInfo->Removed) {
//
// Some kind of surprise removal arrived. We will fail the IRP
// The dispatch routine that called us will take care of
// completing the IRP.
//
return STATUS_DELETE_PENDING;
}
//
// Do whatever initialization needed when starting the device:
// gather information about it, update the registry, etc.
//
if ((NULL == stack->Parameters.StartDevice.AllocatedResources) &&
(NULL == stack->Parameters.StartDevice.AllocatedResourcesTranslate d)) {
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Parameters.StartDevice.AllocatedResources points to a
// CM_RESOURCE_LIST describing the hardware resources that
// the PnP Manager assigned to the device. This list contains
// the resources in raw form. Use the raw resources to program
// the device.
//
partialResourceList =
&stack->Parameters.StartDevice.AllocatedResources->List[0].PartialResourceList;
resource = &partialResourceList->PartialDescriptors[0];
//
// Parameters.StartDevice.AllocatedResourcesTranslate d points
// to a CM_RESOURCE_LIST describing the hardware resources that
// the PnP Manager assigned to the device. This list contains
// the resources in translated form. Use the translated resources
// to connect the interrupt vector, map I/O space, and map memory.
//
partialResourceListTranslated =
&stack->Parameters.StartDevice.AllocatedResourcesTranslate d->List[0].PartialResourceList;
resourceTrans = &partialResourceListTranslated->PartialDescriptors[0];
for (i = 0;
i < partialResourceList->Count; i++, resource++, resourceTrans++) {
switch (resource->Type) {
case CmResourceTypePort:
switch (resourceTrans->Type) {
case CmResourceTypePort:
deviceInfo->PortWasMapped = FALSE;
deviceInfo->PortBase = (PVOID)resourceTrans->u.Port.Start.LowPart;
deviceInfo->PortCount = resourceTrans->u.Port.Length;
DebugPrint(("Resource Translated Port: (%x) Length: (%d)\n",
resourceTrans->u.Port.Start.LowPart,
resourceTrans->u.Port.Length));
break;
case CmResourceTypeMemory:
//
// We need to map the memory
//
deviceInfo->PortBase = (PVOID)
MmMapIoSpace (resourceTrans->u.Memory.Start,
resourceTrans->u.Memory.Length,
MmNonCached);
deviceInfo->PortCount = resourceTrans->u.Memory.Length;
deviceInfo->PortWasMapped = TRUE;
DebugPrint(("Resource Translated Memory: (%x) Length: (%d)\n",
resourceTrans->u.Memory.Start.LowPart,
resourceTrans->u.Memory.Length));
break;
default:
DebugPrint(("Unhandled resource_type (0x%x)\n", resourceTrans->Type));
status = STATUS_UNSUCCESSFUL;
TRAP ();
}
break;
case CmResourceTypeMemory:
deviceInfo->PortBase = (PVOID)
MmMapIoSpace (resourceTrans->u.Memory.Start,
resourceTrans->u.Memory.Length,
MmNonCached);
deviceInfo->PortCount = resourceTrans->u.Memory.Length;
deviceInfo->PortWasMapped = TRUE;
DebugPrint(("Resource Translated Memory: (%x) Length: (%d)\n",
resourceTrans->u.Memory.Start.LowPart,
resourceTrans->u.Memory.Length));
break;
case CmResourceTypeInterrupt:
default:
DebugPrint(("Unhandled resource type (0x%x)\n", resource->Type));
status = STATUS_UNSUCCESSFUL;
break;
} // end of switch
} // end of for
return status;
}
NTSTATUS
GpdDispatchPower(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine is the dispatch routine for power irps.
Does nothing except forwarding the IRP to the next device
in the stack.
Arguments:
DeviceObject - Pointer to the device object.
Irp - Pointer to the request packet.
Return Value:
NT Status code
--*/
{
PLOCAL_DEVICE_INFO deviceInfo;
deviceInfo = (PLOCAL_DEVICE_INFO) DeviceObject->DeviceExtension;
//
// If the device has been removed, the driver should not pass
// the IRP down to the next lower driver.
//
if (deviceInfo->Removed) {
PoStartNextPowerIrp(Irp);
Irp->IoStatus.Status = STATUS_DELETE_PENDING;
IoCompleteRequest(Irp, IO_NO_INCREMENT );
return STATUS_DELETE_PENDING;
}
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
return PoCallDriver(deviceInfo->NextLowerDriver, Irp);
}
NTSTATUS
GpdDispatchSystemControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine is the dispatch routine for WMI irps.
Does nothing except forwarding the IRP to the next device
in the stack.
Arguments:
DeviceObject - Pointer to the device object.
Irp - Pointer to the request packet.
Return Value:
NT Status code
--*/
{
PLOCAL_DEVICE_INFO deviceInfo;
PAGED_CODE();
deviceInfo = (PLOCAL_DEVICE_INFO) DeviceObject->DeviceExtension;
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(deviceInfo->NextLowerDriver, Irp);
}
VOID
GpdUnload(
IN PDRIVER_OBJECT DriverObject
)
/*++
Routine Description:
Free all the allocated resources, etc.
Arguments:
DriverObject - pointer to a driver object.
Return Value:
VOID.
--*/
{
PAGED_CODE ();
//
// The device object(s) should be NULL now
// (since we unload, all the devices objects associated with this
// driver must have been deleted.
//
ASSERT(DriverObject->DeviceObject == NULL);
DebugPrint (("unload\n"));
return;
}
NTSTATUS
GpdDispatch(
IN PDEVICE_OBJECT pDO,
IN PIRP pIrp
)
/*++
Routine Description:
This routine is the dispatch handler for the driver. It is responsible
for processing the IRPs.
Arguments:
pDO - Pointer to device object.
pIrp - Pointer to the current IRP.
Return Value:
STATUS_SUCCESS if the IRP was processed successfully, otherwise an error
indicating the reason for failure.
--*/
{
PLOCAL_DEVICE_INFO pLDI;
PIO_STACK_LOCATION pIrpStack;
NTSTATUS Status;
PAGED_CODE();
pIrp->IoStatus.Information = 0;
pLDI = (PLOCAL_DEVICE_INFO)pDO->DeviceExtension; // Get local info struct
DebugPrint (("Entered GpdDispatch\n"));
Status = IoAcquireRemoveLock (&pLDI->RemoveLock, pIrp);
if (!NT_SUCCESS (Status)) {
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = Status;
IoCompleteRequest (pIrp, IO_NO_INCREMENT);
return Status;
}
if (!pLDI->Started) {
//
// We fail all the IRPs that arrive before the device is started.
//
pIrp->IoStatus.Status = Status = STATUS_DEVICE_NOT_READY;
IoCompleteRequest(pIrp, IO_NO_INCREMENT );
IoReleaseRemoveLock(&pLDI->RemoveLock, pIrp);
return Status;
}
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
// Dispatch based on major fcn code.
switch (pIrpStack->MajorFunction)
{
case IRP_MJ_CREATE:
case IRP_MJ_CLOSE:
// We don't need any special processing on open/close so we'll
// just return success.
Status = STATUS_SUCCESS;
break;
case IRP_MJ_DEVICE_CONTROL:
// Dispatch on IOCTL
switch (pIrpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_GPD_READ_PORT_UCHAR:
case IOCTL_GPD_READ_PORT_USHORT:
case IOCTL_GPD_READ_PORT_ULONG:
Status = GpdIoctlReadPort(
pLDI,
pIrp,
pIrpStack,
pIrpStack->Parameters.DeviceIoControl.IoControlCode
);
break;
case IOCTL_GPD_READ_TST2:
Status = GpdIoctlReadTst(
pLDI,
pIrp,
pIrpStack,
pIrpStack->Parameters.DeviceIoControl.IoControlCode
);
break;
case IOCTL_GPD_WRITE_PORT_UCHAR:
case IOCTL_GPD_WRITE_PORT_USHORT:
case IOCTL_GPD_WRITE_PORT_ULONG:
Status = GpdIoctlWritePort(
pLDI,
pIrp,
pIrpStack,
pIrpStack->Parameters.DeviceIoControl.IoControlCode
);
break;
default:
Status = STATUS_INVALID_PARAMETER;
}
break;
default:
Status = STATUS_NOT_IMPLEMENTED;
break;
}
// We're done with I/O request. Record the status of the I/O action.
pIrp->IoStatus.Status = Status;
// Don't boost priority when returning since this took little time.
IoCompleteRequest(pIrp, IO_NO_INCREMENT );
IoReleaseRemoveLock(&pLDI->RemoveLock, pIrp);
return Status;
}
NTSTATUS
GpdIoctlReadPort(
IN PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack,
IN ULONG IoctlCode )
/*++
Routine Description:
This routine processes the IOCTLs which read from the ports.
Arguments:
pLDI - our local device data
pIrp - IO request packet
IrpStack - The current stack location
IoctlCode - The ioctl code from the IRP
Return Value:
STATUS_SUCCESS -- OK
STATUS_INVALID_PARAMETER -- The buffer sent to the driver
was too small to contain the
port, or the buffer which
would be sent back to the driver
was not a multiple of the data size.
STATUS_ACCESS_VIOLATION -- An illegal port number was given.
--*/
{
// NOTE: Use METHOD_BUFFERED ioctls.
PULONG pIOBuffer; // Pointer to transfer buffer
// (treated as an array of longs).
ULONG InBufferSize; // Amount of data avail. from caller.
ULONG OutBufferSize; // Max data that caller can accept.
ULONG nPort; // Port number to read
ULONG DataBufferSize;
PAGED_CODE();
// Size of buffer containing data from application
InBufferSize = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
// Size of buffer for data to be sent to application
OutBufferSize = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
// NT copies inbuf here before entry and copies this to outbuf after
// return, for METHOD_BUFFERED IOCTL's.
pIOBuffer = (PULONG)pIrp->AssociatedIrp.SystemBuffer;
// Check to ensure input buffer is big enough to hold a port number and
// the output buffer is at least as big as the port data width.
//
switch (IoctlCode)
{
case IOCTL_GPD_READ_PORT_UCHAR:
DataBufferSize = sizeof(UCHAR);
break;
case IOCTL_GPD_READ_PORT_USHORT:
DataBufferSize = sizeof(USHORT);
break;
case IOCTL_GPD_READ_PORT_ULONG:
DataBufferSize = sizeof(ULONG);
break;
default:
return STATUS_INVALID_PARAMETER;
}
if ( InBufferSize != sizeof(ULONG) || OutBufferSize < DataBufferSize )
{
return STATUS_INVALID_PARAMETER;
}
// Buffers are big enough.
nPort = *pIOBuffer; // Get the I/O port number from the buffer.
if (nPort >= pLDI->PortCount ||
(nPort + DataBufferSize) > pLDI->PortCount ||
(((ULONG_PTR)pLDI->PortBase + nPort) & (DataBufferSize - 1)) != 0)
{
return STATUS_ACCESS_VIOLATION; // It was not legal.
}
if (pLDI->PortMemoryType == 1)
{
// Address is in I/O space
switch (IoctlCode)
{
case IOCTL_GPD_READ_PORT_UCHAR:
*(PUCHAR)pIOBuffer = READ_PORT_UCHAR(
(PUCHAR)((ULONG_PTR)pLDI->PortBase + nPort) );
break;
case IOCTL_GPD_READ_PORT_USHORT:
*(PUSHORT)pIOBuffer = READ_PORT_USHORT(
(PUSHORT)((ULONG_PTR)pLDI->PortBase + nPort) );
break;
case IOCTL_GPD_READ_PORT_ULONG:
*(PULONG)pIOBuffer = READ_PORT_ULONG(
(PULONG)((ULONG_PTR)pLDI->PortBase + nPort) );
break;
default:
return STATUS_INVALID_PARAMETER;
}
}
else if (pLDI->PortMemoryType == 0)
{
// Address is in Memory space
switch (IoctlCode)
{
case IOCTL_GPD_READ_PORT_UCHAR:
*(PUCHAR)pIOBuffer = READ_REGISTER_UCHAR(
(PUCHAR)((ULONG_PTR)pLDI->PortBase + nPort) );
break;
case IOCTL_GPD_READ_PORT_USHORT:
*(PUSHORT)pIOBuffer = READ_REGISTER_USHORT(
(PUSHORT)((ULONG_PTR)pLDI->PortBase + nPort) );
break;
case IOCTL_GPD_READ_PORT_ULONG:
*(PULONG)pIOBuffer = READ_REGISTER_ULONG(
(PULONG)((ULONG_PTR)pLDI->PortBase + nPort) );
break;
default:
return STATUS_INVALID_PARAMETER;
}
}
else
{
return STATUS_UNSUCCESSFUL;
}
//
// Indicate # of bytes read
//
pIrp->IoStatus.Information = DataBufferSize;
return STATUS_SUCCESS;
}
NTSTATUS
GpdIoctlWritePort(
IN PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack,
IN ULONG IoctlCode
)
/*++
Routine Description:
This routine processes the IOCTLs which write to the ports.
Arguments:
pLDI - our local device data
pIrp - IO request packet
IrpStack - The current stack location
IoctlCode - The ioctl code from the IRP
Return Value:
STATUS_SUCCESS -- OK
STATUS_INVALID_PARAMETER -- The buffer sent to the driver
was too small to contain the
port, or the buffer which
would be sent back to the driver
was not a multiple of the data size.
STATUS_ACCESS_VIOLATION -- An illegal port number was given.
--*/
{
// NOTE: Use METHOD_BUFFERED ioctls.
PULONG pIOBuffer; // Pointer to transfer buffer
// (treated as array of longs).
ULONG InBufferSize ; // Amount of data avail. from caller.
ULONG nPort; // Port number to read or write.
ULONG DataBufferSize;
PAGED_CODE();
// Size of buffer containing data from application
InBufferSize = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
// NT copies inbuf here before entry and copies this to outbuf after return,
// for METHOD_BUFFERED IOCTL's.
pIOBuffer = (PULONG) pIrp->AssociatedIrp.SystemBuffer;
pIrp->IoStatus.Information = 0;
// Check to ensure input buffer is big enough to hold a port number as well
// as the data to write.
//
// The relative port # is a ULONG, and the data is the type appropriate to
// the IOCTL.
//
switch (IoctlCode)
{
case IOCTL_GPD_WRITE_PORT_UCHAR:
DataBufferSize = sizeof(UCHAR);
break;
case IOCTL_GPD_WRITE_PORT_USHORT:
DataBufferSize = sizeof(USHORT);
break;
case IOCTL_GPD_WRITE_PORT_ULONG:
DataBufferSize = sizeof(ULONG);
break;
default:
return STATUS_INVALID_PARAMETER;
}
if ( InBufferSize < (sizeof(ULONG) + DataBufferSize) )
{
return STATUS_INVALID_PARAMETER;
}
nPort = *pIOBuffer++;
if (nPort >= pLDI->PortCount ||
(nPort + DataBufferSize) > pLDI->PortCount ||
(((ULONG_PTR)pLDI->PortBase + nPort) & (DataBufferSize - 1)) != 0)
{
return STATUS_ACCESS_VIOLATION; // Illegal port number
}
if (pLDI->PortMemoryType == 1)
{
// Address is in I/O space
switch (IoctlCode)
{
case IOCTL_GPD_WRITE_PORT_UCHAR:
WRITE_PORT_UCHAR(
(PUCHAR)((ULONG_PTR)pLDI->PortBase + nPort),
*(PUCHAR)pIOBuffer );
break;
case IOCTL_GPD_WRITE_PORT_USHORT:
WRITE_PORT_USHORT(
(PUSHORT)((ULONG_PTR)pLDI->PortBase + nPort),
*(PUSHORT)pIOBuffer );
break;
case IOCTL_GPD_WRITE_PORT_ULONG:
WRITE_PORT_ULONG(
(PULONG)((ULONG_PTR)pLDI->PortBase + nPort),
*(PULONG)pIOBuffer );
break;
default:
return STATUS_INVALID_PARAMETER;
}
}
else if (pLDI->PortMemoryType == 0)
{
// Address is in Memory space
switch (IoctlCode)
{
case IOCTL_GPD_WRITE_PORT_UCHAR:
WRITE_REGISTER_UCHAR(
(PUCHAR)((ULONG_PTR)pLDI->PortBase + nPort),
*(PUCHAR)pIOBuffer );
break;
case IOCTL_GPD_WRITE_PORT_USHORT:
WRITE_REGISTER_USHORT(
(PUSHORT)((ULONG_PTR)pLDI->PortBase + nPort),
*(PUSHORT)pIOBuffer );
break;
case IOCTL_GPD_WRITE_PORT_ULONG:
WRITE_REGISTER_ULONG(
(PULONG)((ULONG_PTR)pLDI->PortBase + nPort),
*(PULONG)pIOBuffer );
break;
default:
return STATUS_INVALID_PARAMETER;
}
}
else
{
return STATUS_UNSUCCESSFUL;
}
//
// Indicate # of bytes written
//
pIrp->IoStatus.Information = DataBufferSize;
return STATUS_SUCCESS;
}
#if DBG
PCHAR
PnPMinorFunctionString (
UCHAR MinorFunction
)
{
switch (MinorFunction)
{
case IRP_MN_START_DEVICE:
return "IRP_MN_START_DEVICE";
case IRP_MN_QUERY_REMOVE_DEVICE:
return "IRP_MN_QUERY_REMOVE_DEVICE";
case IRP_MN_REMOVE_DEVICE:
return "IRP_MN_REMOVE_DEVICE";
case IRP_MN_CANCEL_REMOVE_DEVICE:
return "IRP_MN_CANCEL_REMOVE_DEVICE";
case IRP_MN_STOP_DEVICE:
return "IRP_MN_STOP_DEVICE";
case IRP_MN_QUERY_STOP_DEVICE:
return "IRP_MN_QUERY_STOP_DEVICE";
case IRP_MN_CANCEL_STOP_DEVICE:
return "IRP_MN_CANCEL_STOP_DEVICE";
case IRP_MN_QUERY_DEVICE_RELATIONS:
return "IRP_MN_QUERY_DEVICE_RELATIONS";
case IRP_MN_QUERY_INTERFACE:
return "IRP_MN_QUERY_INTERFACE";
case IRP_MN_QUERY_CAPABILITIES:
return "IRP_MN_QUERY_CAPABILITIES";
case IRP_MN_QUERY_RESOURCES:
return "IRP_MN_QUERY_RESOURCES";
case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
return "IRP_MN_QUERY_RESOURCE_REQUIREMENTS";
case IRP_MN_QUERY_DEVICE_TEXT:
return "IRP_MN_QUERY_DEVICE_TEXT";
case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:
return "IRP_MN_FILTER_RESOURCE_REQUIREMENTS";
case IRP_MN_READ_CONFIG:
return "IRP_MN_READ_CONFIG";
case IRP_MN_WRITE_CONFIG:
return "IRP_MN_WRITE_CONFIG";
case IRP_MN_EJECT:
return "IRP_MN_EJECT";
case IRP_MN_SET_LOCK:
return "IRP_MN_SET_LOCK";
case IRP_MN_QUERY_ID:
return "IRP_MN_QUERY_ID";
case IRP_MN_QUERY_PNP_DEVICE_STATE:
return "IRP_MN_QUERY_PNP_DEVICE_STATE";
case IRP_MN_QUERY_BUS_INFORMATION:
return "IRP_MN_QUERY_BUS_INFORMATION";
case IRP_MN_DEVICE_USAGE_NOTIFICATION:
return "IRP_MN_DEVICE_USAGE_NOTIFICATION";
case IRP_MN_SURPRISE_REMOVAL:
return "IRP_MN_SURPRISE_REMOVAL";
default:
return "IRP_MN_?????";
}
}
#endif
// --> TEST <--
NTSTATUS
GpdIoctlReadTst(
IN PLOCAL_DEVICE_INFO pLDI,
IN PIRP pIrp,
IN PIO_STACK_LOCATION IrpStack,
IN ULONG IoctlCode )
{
// NOTE: Use METHOD_BUFFERED ioctls.
PULONG pIOBuffer; // Pointer to transfer buffer
// (treated as an array of longs).
ULONG InBufferSize; // Amount of data avail. from caller.
ULONG OutBufferSize; // Max data that caller can accept.
ULONG nPort; // Port number to read
DWORD64 nOut; // return integer
ULONG flags; // input flags
ULONG DataBufferSize;
VOID UNALIGNED *pSource, *pDest;
PVOID pAddress;
PULONG pUlong;
ULONG dw;
PDWORD64 p64bit, p64bit2;
PHYSICAL_ADDRESS pa;
PAGED_CODE();
// Size of buffer containing data from application
InBufferSize = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
// Size of buffer for data to be sent to application
OutBufferSize = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
// NT copies inbuf here before entry and copies this to outbuf after
// return, for METHOD_BUFFERED IOCTL's.
pIOBuffer = (PULONG)pIrp->AssociatedIrp.SystemBuffer;
// Check to ensure input buffer is big enough to hold a port number and
// the output buffer is at least as big as the port data width.
// Used terms:
// VA - virtual address = normal address used in programs
// PA - physical address
//
// check input & output
nPort = *pIOBuffer; // Get read type
// return result
switch (nPort){
case 8: // Read 8 bytes from VA, using normal memory read
if (InBufferSize<8) return STATUS_INVALID_PARAMETER;
p64bit= (PDWORD64) *(pIOBuffer+1);
if (InBufferSize>=12) flags=*(pIOBuffer+2); else flags=0;
// should I check for Memory validation?
if ((flags& 0x01)==0){
pAddress= (PVOID) (((ULONG)p64bit & 0xFFFFF000));
if (!MmIsAddressValid(pAddress)) return STATUS_ACCESS_VIOLATION;
}
nOut= *p64bit;
pSource = (VOID UNALIGNED *)&nOut; // int64 from ptr->
if (OutBufferSize>8) DataBufferSize=8; else DataBufferSize=OutBufferSize;
break;
case 9: // return 8-byte long PA based on input VA
if (InBufferSize<8) return STATUS_INVALID_PARAMETER;
pAddress= (PVOID) *(pIOBuffer+1);
//pa = (PHYSICAL_ADDRESS) 1234; // test
pa= MmGetPhysicalAddress(pAddress); // get Physical address
pSource = (VOID UNALIGNED *)&pa;
DataBufferSize=sizeof(pa);
break;
case 10: // read 8 bytes from PA
if (InBufferSize<12) return STATUS_INVALID_PARAMETER;
pa= * (PPHYSICAL_ADDRESS) (pIOBuffer+1);
p64bit2= MmMapIoSpace(pa, 8, MmNonCached );
nOut= *p64bit2; // read from Phys addr
MmUnmapIoSpace( p64bit2, 8);
pSource = (VOID UNALIGNED *)&nOut;
DataBufferSize=sizeof(nOut);
break;
case 11: // return <>0 if input VA is valid
if (InBufferSize<8) return STATUS_INVALID_PARAMETER;
pAddress= (PVOID) *(pIOBuffer+1);
nOut = 0;
nOut = MmIsAddressValid(pAddress); // isAddressValid
pSource = (VOID UNALIGNED *)&nOut;
DataBufferSize=sizeof(nOut);
break;
case 12: // read 4 bytes from input VA
if (InBufferSize<8) return STATUS_INVALID_PARAMETER;
pUlong= (PULONG) *(pIOBuffer+1);;
if (!MmIsAddressValid(pUlong)) return STATUS_ACCESS_VIOLATION;
nOut= *pUlong;
pSource = (VOID UNALIGNED *)&nOut; // read 4 bytes from ptr->
DataBufferSize=4;
break;
case 13: // my custom GetPhysicalAddress, terurn PA based on input VA
if (InBufferSize<8) return STATUS_INVALID_PARAMETER;
dw= *(pIOBuffer+1); // Virtual(linear) addres for which we want PA
if ((dw >= 0x80000000)&&(dw<0xA0000000)){
// here is 4MB paged kernel range. I dont need it, but ...
nOut= dw & 0x1FFFFFFF;
} else {
// here is part for 4kb pages, read PTE at 0xC0000000
pUlong= (PULONG) (0xC0000000 + ((dw >> 12) * 4)) ; // calculate PTE address
if (!MmIsAddressValid(pUlong)) return STATUS_ACCESS_VIOLATION;
nOut= *pUlong; // read start of Phys page from PTE
nOut= (nOut & 0xFFFFF000) | (dw & 0xFFF); // last bits are retained from virt. addr
}
pSource = (VOID UNALIGNED *)&nOut;
DataBufferSize=sizeof(nOut);
break;
default:
return STATUS_INVALID_PARAMETER;
}
// copy results
pDest=pIOBuffer;
if (DataBufferSize>OutBufferSize) return STATUS_INVALID_PARAMETER;
RtlCopyMemory (pDest, pSource , DataBufferSize);
//
// Indicate # of bytes read
//
pIrp->IoStatus.Information = DataBufferSize;
return STATUS_SUCCESS;
}
lostinspace
11-29-2002, 03:54 AM
Examples of using driver functions from user code
NOTE: I have this functions in language that is not C, so I just wrote them here doing on-line translation to C .... therefore they are not compile-checked :)
//You use memory read from USER application (like hook.dll) with:
// *************************************************
// function that invoke driver
BOOL requestDrv
( PVOID inAdr, int inSz, PVOID outAdr, int outSz)
{
HANDLE hndFile; // Handle to device, obtain from CreateFile
BOOL IoctlResult;
DWORD ReturnedLength; // Number of bytes returned
hndFile = CreateFile(
"\\\\.\\mtstDev", // Open the Device "file"
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hndFile == INVALID_HANDLE_VALUE) // Was the device opened?
{
return 0;
//"Unable to open the device.\n";
}
IoctlResult = DeviceIoControl(
hndFile, // Handle to device
IOCTL_GPD_READ_TST2, // IO Control code for Read
inAdr, // Buffer to driver.
inSz, // Length of buffer in bytes.
outAdr, // return Buffer from driver.
outSz, // Length of buffer in bytes.
&ReturnedLength, // Bytes placed in DataBuffer.
NULL // NULL means wait till op. completes.
);
if (!CloseHandle(hndFile)) ; //printf("Failed to close device.\n");
if (IoctlResult) // Did the IOCTL succeed?
{
return 1;
}else
return 0;
//printf("Read failed with code %ld\n", GetLastError() );
}
} // end of requestDrv
// *************************************************
// Example of function that read 8-bytes from memory
BOOL readDrvMem8
(PVOID ptr, PDWORD64 res)
{
LONG IoctlCode;
DWORD64 resultKey
struct TinDrv // Declare inDrv struct type
{
DWORD callType;
DWORD64 inParam;
} drvIn; // Define object to hold input data for driver
IoctlCode = IOCTL_GPD_READ_TST2;
drvIn.callType=8;
drvIn.inParam= (DWORD64) ptr;
// call driver
return requestDrv(&drvIn,sizeof(drvIn),res,sizeof(DWORD64));
}
// *************************************************
// Example of function that read Phys Addr (PA) into res
// based on input VA from ptr
BOOL readDrvPA
(PVOID ptr, PDWORD64 res)
{
LONG IoctlCode;
DWORD64 resultKey
struct TinDrv // Declare inDrv struct type
{
DWORD callType;
DWORD64 inParam;
} drvIn; // Define object to hold input data for driver
IoctlCode = IOCTL_GPD_READ_TST2;
drvIn.callType=13; // could use 9 too, but my custom f-on is better
drvIn.inParam= (DWORD64) ptr;
// call driver
return requestDrv(&drvIn,sizeof(drvIn),res,sizeof(DWORD64));
}
// *************************************************
// Example of function that read memory from Phys Addr pa
// and store in 8-bytes pointed by res
BOOL readDrvPA
(DWORD64 pa, PDWORD64 res)
{
LONG IoctlCode;
DWORD64 resultKey
struct TinDrv // Declare inDrv struct type
{
DWORD callType;
DWORD64 inParam;
} drvIn; // Define object to hold input data for driver
IoctlCode = IOCTL_GPD_READ_TST2;
drvIn.callType=10;
drvIn.inParam= pa;
// call driver
return requestDrv(&drvIn,sizeof(drvIn),res,sizeof(DWORD64));
}
LordCrush
11-29-2002, 08:56 AM
Thank you very much :) /bow
throx
12-02-2002, 01:59 PM
Just another suggestion for the kernel mode driver:
Do a google search for KeAttachProcess. It's part of ntifs.h which you get with the IFS kit but it switches the user mode memory to that of a specified driver so you can then play with it...
lostinspace
12-11-2002, 04:21 PM
Ok, after week or so dedicated to RL issues, I returned to my kernel driver. Last version of that driver had ability to read key from any PHYSICAL memory location, thus being completely undetectable, even if that memory is VirtualProtected.
But one slight drawback was fact that in order to find out what Physical memory corresponds to actual Offset ( = Virtual memory address), I had to call GetPhysicalAddress from context of EQGAME thread. I needed to do that only once, so I used my previously made hook.dll and if i detect EQGAME upon hooking, I call GetPhysicalAddress( Offset), and return resulting PhysAddr to my hookshell.exe. After that hooking dll detach and is not needed any more, all reads could be done from separate exe using mentioned ReadFromPhysical.
Of course, I wanted to be able to do complete process from separate EXE - which would eliminate need for hooking dll. I found on web examples of using KeAttachProcess, but I could not at first get prototype of its only parameter - EPROCESS, which is in kernel something that resemble HANDLE of process that is result of OpenProcess. Then I managed to find some headers (from some Sven guy) with defined EPROCESS structure. But main problem was ... how to find that process handle.
BTW, thanks Throx for mentioning that ntifs.h :) I managed to find it on net, and it has much better defined structures that previous headers with undocumented things that i had. But my main problem still remained - how to find EPROCESS.
First example that i tried to implement was:
http://www.windowsitlibrary.com/Content/356/04/7.html
This approach use undocumented PsInitialSystemProcess, which is not included even in ntifs.h ... but could be just addes as extern in ntifs.h and it can link:
extern PEPROCESS PsInitialSystemProcess;
Problem that I had with this approach is that it use fixed ListEntryOffset , which is hardcoded for NT and win2k, but does not work for XP. But looking at EPROCESS for XP ( build>=2600)definition in header , I was able to find correct values for XP too:
ListEntryOffset=0x88;
NameOffset=0x174;
Other problem was that PsInitialSystemProcess was NOT pointing at anything meaningfull ... and that problem I could not solve.
Second example that i tried to implement was:
http://www.phrack.com/phrack/59/p59-0x10.txt
This example also used fixed ListEntry and Named offsets which were wrong for XP, but I had that problem already solved. Difference is in that second example use (again undocumented) kernel variable PsActiveProcessHead to find start of EPROCESS list. And unlike PsInitialSystemProcess , this one I could not just add as extern because it wouldnt link. So i had to hard code address, just like it was done in example. BUT , again, that address was for win2k and not for XP. I used windbg> ? PsActiveProcessHead , and resulting address was 0x80547b58. Using that value, I finally got working link to EPROCESS list, and from that I was able to find address of Eprocess for EQGAME task.
Result is that I added in kernel driver function #16, which takes as parameter name of application (up to 8 chars), and offset ... and it does following (this is pseudo code):
DWORD64 myReadKey(char* appName, DWORD32 Offset){
ep= myFindEprocess(appName)
KeAttachProcess(ep)
pa= myGetPhysicalAddress ( Offset)
Key= ReadFromPhysAddr( pa );
KeDetachProcess()
return Key;
}
So, I just call this function from my EXE with 'EQGAME' and 7912632 as parameters, and I get key ... unless driver can not find process or read, in which case i get zero. I call function periodically, every N seconds, and if key is different, I sent UDP or write file, as I did before.
This approach seems to be noticeably better that hooking version - there isnt ANY interraction with target process (eqgame). And I'm not calling any API or even ntdll function that can be hooked. Only function I call with eq-related parameter is KeAttachProcess, but that function is linked directly in driver, not using gateways (meaning, not using SSDT tables) - so its impossible to hook unless someone rewrite kernel code to put his hook at start of function code. BUT even then, windows itself use KeAttachProcess, so that information wouldnt be any positive proof of keysniffer.
I think that this approach is practically undetectable.
throx
12-11-2002, 07:25 PM
I think that this approach is practically undetectable.
Agreed. And you know I'm not about to say that lightly given my previous statements.
gawker
12-12-2002, 05:05 PM
Any chance of posting case #16?
LordCrush
12-13-2002, 01:30 AM
and "myFindEprocess()" ;)
thnx /duck
lostinspace
12-13-2002, 08:45 AM
Ok, here is code.
You also need to include ntifs.h in source.
// myFindProcess
void* myFindProcess(char* ProcName){
char ProcessName[16];
ULONG BuildNumber,ListEntryOffset,NameOffset;
ULONG p,sz1;
PLIST_ENTRY ProcessListPtr, PsActiveProcessHead;
void *Peb;
BuildNumber= (*NtBuildNumber) & 0x0000FFFF;
if ((BuildNumber==0x421) || (BuildNumber==0x565) ) { // NT 3.51; 4.0
PsActiveProcessHead = (PLIST_ENTRY) 0x8046A180; // not sure for this number
ListEntryOffset=0x98;
NameOffset=0x1DC;
} else if (BuildNumber==0x755) {// Windows 2000
PsActiveProcessHead = (PLIST_ENTRY) 0x8046A180;
ListEntryOffset=0xA0;
NameOffset=0x1FC;
} else {// treat anything else as Windows XP, BuildNumber==0xA28
PsActiveProcessHead = (PLIST_ENTRY) 0x80547b58;
ListEntryOffset=0x88;
NameOffset=0x174;
}
p=0;
sz1=strlen(ProcName); // input process name length must be < 16
ProcessListPtr=PsActiveProcessHead;
while ((p<250)&&((ProcessListPtr!=PsActiveProcessHead)||(p==0))) {
// get process name
Peb=(void *)(((char *)ProcessListPtr)-ListEntryOffset);
memset(ProcessName, 0, sizeof(ProcessName));
memcpy(ProcessName, ((char *)Peb)+NameOffset, 16);
// compare names. Limit process name to length of our input parameter. Case sensitive
ProcessName[sz1]=0;
if (strcmp(ProcessName,ProcName)==0)){
// process found!
return Peb;
}
// next process
p++;
if (!MmIsAddressValid(ProcessListPtr->Flink)) {
return 0;
}
ProcessListPtr=ProcessListPtr->Flink;
}
return 0;
}
// case #16 part
case 16: // read key completely, based on app name and offset
// first parameter (8 bytes) is still function (=16)
// second parameter (8 bytes) is offset of key
// third parameter (8 bytes) is process name up to 7 chars (0 at end)
if (InBufferSize<24) return STATUS_INVALID_PARAMETER;
DWORD32 d32;
void* pv; // declaration of d32, pv and cs need to be done at start of function, not in case
char* cs= (char*) (pIOBuffer+2);
cs[7]=0;
pv=myFindProcess(cs); // find process, name in first param
nOut=0;
if (pv){
KeAttachProcess(pv); // attach to process
d32=myGetPA((DWORD32)*(pIOBuffer +1)); // first get PA for given VA. For myGetPA see case #13
nOut=myReadPA(d32); // then return result read from PA. For myReadPA see case #10
KeDetachProcess();
}
break;
LordCrush
12-13-2002, 12:27 PM
Wooot thank you :D
i did read the article about the PhysicalMemory access that you posted as second link (http://www.phrack.com/phrack/59/p59-0x10.txt ) while driving home from work in the metro - puh i think i got a light idea what to do but no chance to make it my own ;). But it is a very interesting article :)
/bow and thank you again
fez_ajer
12-14-2002, 07:39 PM
Hey, Lost...
Nice job with what you're doing. I think this is heading in the right direction.
I thought I'd drop this lil tidbit to you, as I think it could be useful. Basically, it's an undocumented feature of the ObReferenceObjectByHandle function to return the address of an EPROCESS block when you pass in a process id.
Take a look here for details:
http://www.ntdev.org/archive/ntdev9908/msg0108.html
The user level process can enum all processes then call a driver IOCTL with the target process id and memory address as the parameters.
Fez Ajer
EDIT: Ok, this wants a PROCESS HANDLE instead of a PROCESS ID... Back to the reference ;)
lostinspace
12-16-2002, 11:22 AM
fez_ajer, that is interesting function, but has one major drawback:
- I need to obtain process handle before using function, so that means I need to use OpenProcess in User mode for example . And that is very same thing that we are trying to avoid with kernel driver
But it could be possibly used safely in following way:
1) i get my keysniffer.EXE process Handle (exe name is just example, this is program that use kernel driver to read key, and then send it to SEQ)
2) I use that handle in ObReferenceObjectByHandle to get EPROCESS address of keysniffer.exe
3) now, I have valid pointer to ONE EPROCESS struct, and I can walk whole EPROCESS same way as in myFindProcess
So, what would be benefit? I wouldn't need to hardcode address of PsActiveProcessHead, which is good thing, since any hardcoding fixed numbers is HIGLY OS-version specific, and may need to be updated even for OS service pack, and surely for new OS version ( ie. I didnt try it on win XP SP1 )
What is drawback? I would still need to hardcode offset of List pointers and Process name within EPROCESS . This is not exactly 'drawback' of using ObReferenceObjectByHandle , but since offsets change with new OS version same way as PsActiveProcessHead does, any benefit would be lost -> new OS version would need added new 'case BuildNumber == xxx' anyway.
Conclusion:
- previous solution works for win2k and XP (maybe for NT4.0, but that is totaly unimportant since no one is playing EQ on NT40 without DX support).
- New OS version would need function to be upgraded, but that will be easy. And so far I dont have version independent solution.
- this solution provide me with all functionality that i needed from kernel driver: undetectable reading of part of memory from given process name and address
- as of my latest post (with FindProcess in kernel), I consider driver as finished - I can think of nothing new to add to it that would help increase security for keyreader.
Of course, I will accept any good suggestion for improvement. At this moment I see possible improvements in technical details (like making more OS independent solution) and not in main functionality ( like making more secure and indetectable memory read ).
fez_ajer
12-16-2002, 01:06 PM
I believe there is a kernel function:
KeGetCurrentProcess()
which returns PEPROCESS for the process which calls the IOCTL. That should indeed do the trick to avoid hardcoding the list head. You'd still need to hardcode the offsets of the list pointers, so you don't really gain that much.
As for ObReferenceObjectByHandle, I posted that because I was lead to believe it worked by passing in a PROCESS ID as opposed to a PROCESS HANDLE. When I noticed the distinction I edited my post.
gawker
12-16-2002, 02:10 PM
Having trouble implmenting myGetPA() and myReadPA() routines. Could you post examples please. What a spounge I am.
LordCrush
12-17-2002, 02:26 AM
lostinspace wrote:
and may need to be updated even for OS service pack, and surely for new OS version ( ie. I didnt try it on win XP SP1 )
I am trying to get it work on xp sp1 - will post wether it works :)
lostinspace
12-17-2002, 05:46 AM
fez_ajer, that was good hint :)
While function KeGetCurrentProcess does not exists, there is one named PsGetCurrentProcess. I just used result of that function in myFindProcess to determine PsActiveProcessHead .
New code to determine offsets looks like:
BuildNumber= (*NtBuildNumber) & 0x0000FFFF;
if ((BuildNumber==0x421) || (BuildNumber==0x565) ) { // NT 3.51; 4.0
ListEntryOffset=0x98;
NameOffset=0x1DC;
} else if (BuildNumber==0x755) {// Windows 2000
ListEntryOffset=0xA0;
NameOffset=0x1FC;
} else {// treat anything else as Windows XP, BuildNumber==0xA28
ListEntryOffset=0x88;
NameOffset=0x174;
}
PsActiveProcessHead = (PLIST_ENTRY)(((char*) PsGetCurrentProcess() )+ListEntryOffset);
gawker, myGetPA and myReadPA should be same functionality as already existing case #10 and #13 parts, but anyway, here is code:
// my custom GetPhysicalAddress from Virtual Address
DWORD32 myGetPA(DWORD32 vAdr){
PDWORD32 p32bit;
if ((vAdr<0xA0000000)&&(vAdr >= 0x80000000)){
// here is 4MB paged kernel range. I dont need it, but ...
return vAdr & 0x1FFFFFFF;
}else {
// here is part for 4kb pages, find PTE starting at 0xC0000000 + ...
p32bit= (PDWORD32) ( 0xC0000000 + ( 4*(vAdr >> 12)) ) ;
// check if PTE is accessible
if (!MmIsAddressValid(p32bit)) return 0;
// last bits are retained from virt. addr
return (*p32bit & 0xFFFFF000) | (vAdr & 0xFFF);
}
}
// my Read from PhysicalAddress
DWORD64 myReadPA(DWORD32 inAdr){
PDWORD64 p64bit;
DWORD64 nOut=0;
PHYSICAL_ADDRESS pa;
if (inAdr==0) return 0;
pa.QuadPart = inAdr;
p64bit= MmMapIoSpace( pa, 8, MmNonCached );
if (p64bit==NULL) return 0;
nOut= *p64bit;
MmUnmapIoSpace( p64bit, 8);
return nOut;
}
fez_ajer
12-18-2002, 12:30 AM
Lost,
I think that KeGetCurrentProcess *MAY* be an undocumented export. I also know that this function:
PsLookupProcessByProcessId(IN ULONG ulProcId,OUT struct _EPROCESS ** pEProcess);
Is definitely undocumented. You can find some info on it here:
http://www.beyondlogic.org/porttalk/porttalk.htm
and it is also exported by the public version of NTIFS.H which is maintained at:
http://www.acc.umu.se/~bosse/
Cheers and good luck :)
- Fez
LordCrush
12-18-2002, 02:50 AM
fez_ajer,
i found a nice proggy for installing the driver
http://www.beyondlogic.org/dddtools/dddtools.htm
http://www.beyondlogic.org/dddtools/installer.zip
- i have not tested it yet :D
Ty :)
fez_ajer
12-18-2002, 03:19 AM
I've implemented an IOCTL function which uses nothing but win32 kernel calls to read the memory. This means no hardcoding of list pointers and such. It also means it should work without modification for all the NT based platforms.
The idea is you have your usermode program enumerate the processes then call the IOCTL passing in the PROCESS ID and OFFSET of the memory you want to read.
The code uses just one undocumented function, PsLookupProcessByProcessId, but as I mentioned it's properly handled by the free version of NTIFS.H. (see above)
Here's the code snippet:
// Get a pointer to the requested process EPROCESS block
if ((ntStatus = PsLookupProcessByProcessId(request->procid, &pEProc)) == STATUS_SUCCESS)
{
// Attach to the target process memory space
KeAttachProcess(pEProc);
// Assume we're going to fail
ntStatus = STATUS_INVALID_PARAMETER;
// Is the requested virtual address valid?
if (MmIsAddressValid(request->offset))
{
// Point to the key value physical address
pPhysAddress = MmGetPhysicalAddress(request->offset);
// Map the address space
pKeyVal = MmMapIoSpace(pPhysAddress, 8, MmNonCached);
// Put the lime in the coconut
request->memval = *pKeyVal;
// Unmap the address space
MmUnmapIoSpace(pKeyVal, 8);
// Tell them they have mail
Irp->IoStatus.Information = sizeof(REQUEST);
// We actually succeeded!
ntStatus = STATUS_SUCCESS;
}
// Detach from the process memory space
KeDetachProcess();
}
Fez
lostinspace
12-18-2002, 08:28 AM
Yep, after first post about PsLookupProcessByProcessId, I added new function call (#17) to my driver where as parameters i expect process ID and Offset, and after obtaining EPROCESS, remaining is same as in #16.
But I still use previous function, #16, because I consider it harder to detect. Namely, PsLookupProcessByProcessId has following potential risks:
- PsLookupProcessByProcessId adds another function which, if hooked, would undeniably show that someone is tempering with EQ (that is, function has as parameter process ID of EQ)
- both #16 and #17 has function KeAttachProcess that, if somehow hooked, would show that someone is tempering with EQ (again, has eprocess of EQ as parameter). But this function is probably called far more frequently than PsLookupProcessByProcessId
- necessary enumeration function in user mode , if hooked, is not positive proof for sniffer, but still can add user mode application to 'suspect list'
Now, I know that 'hooking' on kernel functions is very hard to implement, so solution that fez_ajer posted is quite good and quite secure. I guess same reason that initiated me to write kernel driver is also reason why I still use myFindProcess - I want solution that has best probability to be undetected.
On a side note, kernel function MmGetPhysicalAddress will not work if adress is VirtualProtected. That is reason for my custom GetPhysicalAddress function.
fez_ajer
12-18-2002, 09:59 AM
Hrm... I've never actually checked but I would think that MmMapIoSpace would fail on virtual protect rather than MmGetPhysicalAddress... The reasons being:
1) MmGetPhysicalAddress supposedly just converts a virtual address into a physical address given the context of a process. That can be done even if it is protected because it needs to be mapped for access. It would make more sense to tell you where something is before telling you that you can't get there.
2) MmMapIoSpace returns a PVOID vs. a PHYSICAL_ADDRESS for MmGetPhysicalAddress. The PVOID can be null on failure whereas I don't think PHYSICAL_ADDRESS is equivalent to a pointer.
Not that I would expect anything in this M$ API to make logical sense though :)
It just seems funny to me that Microsoft would protect something by just not letting you figure out where it is and then allowing you to call a MmMapIoSpace on it regardless of privilege as long as you could find it yourself.
lostinspace
12-18-2002, 12:45 PM
Hehe ... well, I also did not expect any consistency from M$ API, so I tested all those combinations with my own program simulating eqgame. Namely, I generated 8 byte key in that program and then tested several options:
- key at fixed offset (like it is now in EQ)
- only pointer to key is fixed, key itself at random offset (GlobalAlloc)
- pointer to key is fixed, and key is at random offset (VirtualAlloc) and protected by VirtualProtect
As it can be seen in my previous posts, if key is VirtualProtected, it has following effect on kernel driver:
- can be read with normal memory read, will not trigger trap, but will fail read
- can be read any time with MapIoSpace reading physical memory ... i think that it is logical after all, since you VirtualProtect virtual address , and in MapIoSpace you use completely diferent physical address
- can not obtain physical address with MmGetPhysicalAddress if protected ... this also seems to be logical, since this function use virtual address as parameter, and process is protecting that virtual address
Again, to overcome that obstacle, I wrote my own GetPhysicalAddress, and everything works ok on my test aplication - I can read both protected and unprotected memory any time, without triggering anything.
Another reason why MapIoSpace works fine is fact that virtual memory is protected on process basis. Meaning , if one process is protecting 0x450000, same address can be safely read on other process. Information about VirtualProtect is actually handled by processor itself, and it is in PTEs, or virtual memory paging entries - area from 0xc0000000 where for each 4kb (or 4MB) of virtual memory there is one 32bit (4 byte) PTE pointing at physical memory address. Since least granularity is 4kb ( 0x1000), lower 3 hex digits (12 bits) are not needed for physical address - and that is place where processor store information about that virtual page: is it in memory at all, or at disk; is it read only or r/w or..; is it protected or not; etc...
And considering that mmGetPhysicalAddress actually only read that PTE and return result, it was logical and only spot to put control of VirtualProtection from Windows point of view - considering that actual trap triggering is done by processor itself.
I even wrote my own setVirtualProtect and readVirtualProtect that just write specific bits in PTE for given memory. Those are missing function numbers in my case example if you wondered: #14 and #15 :)
fez_ajer
12-18-2002, 04:46 PM
It makes much more sense as to WHY ;)
It just doesn't really offer any real protection. That's good for our case, but suxxor for the OS itself.
Fez
wiz60
12-19-2002, 06:58 AM
Nice job LostInSpace - this was the concept I was describing in my earlier post.
It has taken some time - but in the end this solution will prove to be totally immune to detection. It might still suffer from efforts by SOE to obfuscate the key - but those can be picked off as they occur.
The memory management registers are manipulated a lot more than most people know. It is not uncommon for "Watchpoints" in a debuggers to tweak them to make it easier and more efficient to perform their task.
As to the worry about intrusion and OS safety. The key issue is the means by which these hooks/kernel mode programs can be run. I have been a bit surprised that they go in so easy. It seems like there should be more protection against allowing them to be inserted into the OS, or does everyone always run all their stuff from max privilege mode?
Somewhere on another post - it seems that people were investigating whether EQ can be run with less privileges. Not sure what they finally concluded. But it brings up an interesting thought. Why in the heck does a game need access to the kernel?
Why should they presume to attach to the OS and do who knows what to our computer?
I suspect someplace in SOE a lawyer is asking an enthusiastic bunch of zealots why in the heck they want to do something so intrusive to the computers of their customers. The legal riskls are enormous.
They install some piece of code - and accidentally Quicken fires off a Checkpoint transaction that empties my bank account (ok - far fetched) - think I won't be looking for recourse? And don't tell me about EULA terms and conditions - those are meaningless until tested in a court - something SOE certainly does not want to do.
I doubt SOE will ever hook the OS. Too much to lose.
Having said that - I would be interested in a posted copy of the final version of the driver. I like looking at the solution - it gives me ideas for other uses.
Finally - does your approach do the Virtual-to-Physical conversion every time it gets a key? There really is no guarantee that the page with the key in it is in memory - or even in the same physical location from call to call?
throx
12-19-2002, 03:12 PM
They install some piece of code - and accidentally Quicken fires off a Checkpoint transaction that empties my bank account (ok - far fetched) - think I won't be looking for recourse? And don't tell me about EULA terms and conditions - those are meaningless until tested in a court - something SOE certainly does not want to do.
Running under Win9x you can do that anyway with just a stray pointer. I don't see any greater legal risk there than there would be from simply allowing a game to run under Win9x.
If you think about the things a virus protection program or home firewall could potentially do (both hook kernel drivers) then something that hooks a few APIs is tame in comparison.
I seriously doubt SOE would ever bother with the effort required to detect lostinspace's sniffer. They'd pretty much have to scan the driver list and check each one's code space looking for a signature. Very, very unlikely and way too much effort. I'd expect something simpler like more obfuscation of the key and potentially encrypting eqgame.exe so a simply debug dump of it isn't so easy.
LordCrush
12-19-2002, 04:18 PM
my 2cp ... they will not put very much effort in this .. perhaps try to find sniffers that are compiled 1:1 from this forum, but not more. Why ? there is no much ROI in this - if they very much complicate the storage of the key they build themself traps that could make EQ not work properly. This would cause much help calls and much trouble ...
I think they got more or less what they wanted: To cut down the users that have a fully functional SEQ
/wave
LordCrush who will have colored dots back soon :)
LordCrush
12-20-2002, 09:29 AM
Hmm i maybe stupid, but i cannot figure out how i have to combine the inputbuffer
BOOL requestDrv(PVOID inAdr, int inSz, PVOID outAdr, int outSz, long IOCtlReq)
i have to put the function number (in this case 16)
the offsert and the processname into that buffer at adr inAdr so that i can be extracted here:
...
pIOBuffer = (PULONG)pIrp->AssociatedIrp.SystemBuffer;
// Check to ensure input buffer is big enough to hold a port number and
// the output buffer is at least as big as the port data width.
// Used terms:
// VA - virtual address = normal address used in programs
// PA - physical address
//
// check input & output
nPort = *pIOBuffer; // Get read type
// return result
switch (nPort){
...
// first parameter (8 bytes) is still function (=16)
// second parameter (8 bytes) is offset of key
// third parameter (8 bytes) is process name up to 7 chars (0 at end)
if (InBufferSize<24) return STATUS_INVALID_PARAMETER;
cs= (char*) (pIOBuffer+2);
cs[7]=0; //Get Proc Name mx Len 7
pv=myFindProcess(cs); // find process, name in first param
nOut=0;
if (pv){
KeAttachProcess(pv); // attach to process
d32=myGetPA((DWORD32)*(pIOBuffer +1)); // first get PA for given VA. For myGetPA see case #13
nOut=myReadPA(d32); // then return result read from PA. For myReadPA see case #10
KeDetachProcess();
how is that buffer filled ?
any hints are greatly apreciated :D
LordCrush
01-09-2003, 05:09 AM
Lostinspace or anyone of the driver Guru´s are you still around ?
I have some little questions about the implementation of the usermode sniffer
Thank you much in advance :D
gawker
02-18-2003, 06:18 PM
Ok Finally got time to work on my little kernnel driver. With Win2K SP3, my build # is 0x893. This is not one of your examples.
My question is: In Find Process... How do you determine ListEntryOffset and NameOffset values for an OS?
The values in your example ListEntryOffset=0xA0; NameOffset=0x1FC for Win2K are returning mush.
Any hints would be appreciated.
LordCrush
02-18-2003, 09:59 PM
gawker - i fear lostinspace is not longer on the boards :(
i have given up on working for the kerneldriver ... perhaps later again
fester
02-18-2003, 11:44 PM
Gawker, I can assure for build 0x893, that the numbers you have 0xA0 and 0X1FC are accurate and work fine, as that is the build I use on all my machines.
I can not post my version of the driver. I use a one time pad encryption to pass the information in and out of my driver. There is not a one to one relationship between the size of the information passed and the size of the key etc. I also do not include any constants in the driver, and all constants are decrypted on the fly and used then destroyed.
I highly doubt this is needed, but it took only minutes to add and will help to make the driver much less likely of being listed under the "interesting events" that SOE might report to HQ (like say if they snooped all ioctl calls and noticed their key or eqgame.exe embedded in the passed data.) VERY unlikely, but possible.
Please post more information about what your problem is, and I will attempt to determine what problem your having.
LordCrush
02-19-2003, 01:05 AM
Fester just one quick question ...
to debug a driver you need 2 boxes and do debuging from the 2nd box ?
my devicedriver compiles but i get nothing back :rolleyes: now i am looking into options to get more information out of the driver.
Hmm anyone knows if the numbers, that come from lostinspace work for XP Sp1 ?
lostinspace
02-19-2003, 05:36 AM
Gawker, that offset is what i found in few posts around net, but i checked them against some header files like ntifs.h, and they seemed ok.
I did testing only on XP though, but I think offset is same for XP sp1 too.
LordCrush, about previous question: make structure with 3 data variables 8 byte long, and fill those parameters according to which function you call. For example :
typedef struct _input_buff {
DWORD64 Param1;
DWORD64 Param2;
char Param3[8];
} drvBuff;
For function 16, you would do something like :
Param1=16;
Param2= KeyOffset;
strcpy(Param3, "EQGAM");
Also, for debugging, if you have XP, you DO NOT need second box and cables. If you have WinDbg, you have Kernel debug option, and it has two standard options (COM, 1394) that you have under win2k also, and have one additional option on XP: 'Local' - kernel debug on local machine. That is very useful option, for debugging, and also for finding OFFSETS of kernel variables ... if you download all necessary symbol files.
Another, maybe even better option, is to use OutputDebugString in kernel driver to send your debug messages at various places in your driver code, and to use DebugView from SysInternals to watch those messages.
LordCrush
02-19-2003, 06:45 AM
Wooot,
thank you - i ll try if i get an hour time :)
fester
02-19-2003, 10:11 AM
WinDbg is the best method to debug.
If you can not do that, you can debug by passing information back to your userland program in a format you create.
You will need to compiling and installing a new device driver with each version of your test driver. Use a bit field (32 bits) of flags or pass enough information that you can return a lot of debug text in a large buffer (8192 bytes or more.)
There are two ways to check the values for XP sp1. The first is to link against (or just read) the ntifs.h and capture the address (&) of the needed elements in the structure and subtract the address of your test struct beginning. The other is to use a search routine to look for valid values for the elements you need and return these, to which you later use to verify they are correct.
OK, also more important notes:
1) You need to make a driver inf file to install a new driver. Installing the .sys file in windows/system is generally not a reliable way to update the loaded driver and can run into problems with windows file protection (or force you to reboot for each new version of the driver.)
2) You need a new Globally unique identifier because SoE may find it interesting that a large number of people use the same identifier that the published (or sample driver) has. Each new snooper kernel driver person needs a GUID (use GUIDGEN.exe to make yours.)
throx
02-19-2003, 01:24 PM
There's no need to use an inf file. You can dynamically load drivers using the same methods used by the utilities at sysinternals.com. Installing utility drivers in system32/drivers is just overkill.
gawker
02-20-2003, 08:59 AM
I am having trouble with the following section of code.
if (pv){
KeAttachProcess(pv); // attach to process
d32=myGetPA((DWORD32)*(pIOBuffer +2)); //changed to +2 because we are using DWORD64 parameters
nOut=myReadPA(d32);
KeDetachProcess();
}
I have checked to see that pv is returning different data for each process tested, but nOut returns the same value for all apps.
It appears that I am not connecting to the process because nOut remains the same if I comment out the KeAttachProcess and KeDetachProcess lines ( I suppose that the values are for my current memory space.
A few of questions:
How can I verify if KeAttachProcess was successful?
How can I verify that pv is returning valid process addresses?
Is there anything I need to do different since I am making calls to functions to return nOut (am i somehow losing my attachement to process pv)?
Thanks for any help. I will continute to fiddle with the code and figure what I am doing wrong. BTW thanks, this exercise is teaching much about the DDK.
fester
02-20-2003, 06:42 PM
if (pv){
typedef struct _input_buff {
DWORD64 Param1;
DWORD64 Param2;
char Param3[8];
} *idp;
idp = (typedef struct _input_buff *) pIOBuffer;
KeAttachProcess(pv); // attach to process
d32=myGetPA((DWORD32) idp->Param2);
nOut=myReadPA(d32);
KeDetachProcess();
}
You were always assigning d32 to myGetPA((DWORD32) 0);
The pIOBuffer is PULONG, so it is 32 bits and that put the source of the *() to the high 32 bits of the Param1 field (which is 0, because Param1=16).
fester
02-20-2003, 07:07 PM
>> How can I verify if KeAttachProcess was successful?
If pv is valid, it is very likely working.
>> How can I verify that pv is returning valid process addresses?
If build 0x893 and *(((uchar*)pv)+NameOffset) == 'e' then it is valid. ('e' of "eqgame.exe")
>> Is there anything I need to do different since I am making calls to functions to return nOut (am i somehow losing my attachement to process pv)?
If Attach succeeded, you can't "lose" attachment unless you call Detach.
>> It appears that I am not connecting to the process because nOut remains the same if I comment out...
This is likely because the first 8 bytes (at offset 0x0) in each process is likely the same and there is ALWAYS a current process during kernel calls, it is just that it may not be the process you think. This would be a means by which removing the calls (keattach and kedetach) would result in the same output for nout.
lostinspace
02-24-2003, 09:43 AM
I use something like following code.
If you use following struct as input buffer, and pIOBuffer is of type PULONG :
ULONG funcNumber;
ULONG KeyOffset;
char processName[8];
case 16: // read key completely, based on app name and offset
// next 3 lines may vary according to your parameter struct
DWORD64 fnNum = *(pIOBuffer +0); // function number
DWORD64 inArg2 = *(pIOBuffer +1); // offset
char* cs = (char*)(pIOBuffer +2); // process name
// once parameters are read, following is same
cs[7]=0;
DbgPrint("Function number %x", fnNum);
DbgPrint("Process name %s",cs);
DbgPrint("Key offset %x",inArg2);
pv=FindProcess(cs); // find process, name in first param
if (pv){
DbgPrint("Process found %s at %p",cs,pv);
KeAttachProcess(pv); // attach to process
d32=myGetPA((DWORD32)inArg2); // first get PA for given VA
DbgPrint("Physicall address=%x",d32);
nOut=myReadPA(d32); // then return result read from PA
DbgPrint("read Key=%x",nOut);
KeDetachProcess();
}else{
nOut=0;
DbgPrint("Not found process!");
}
break;
//----
If you use this struct as input buffer, and pIOBuffer is of type PULONG :
DWORD64 funcNumber;
DWORD64 KeyOffset;
char processName[8];
... then first 3 lines should be:
DWORD64 fnNum = *(pIOBuffer +0); // function number
DWORD64 inArg2 = *(pIOBuffer +2); // offset
char* cs = (char*)(pIOBuffer +4); // process name
//----
If you use this struct as input buffer, and pIOBuffer is of type PDWORD64 :
DWORD64 funcNumber;
DWORD64 KeyOffset;
char processName[8];
... then first 3 lines should be:
DWORD64 fnNum = *(pIOBuffer +0); // function number
DWORD64 inArg2 = *(pIOBuffer +1); // offset
char* cs = (char*)(pIOBuffer +2); // process name
//... and so on :)
You can set any input structure you like, as long as it starts with function number. First example is in line with last posted code example, where parameters were 4-byte long (ULONG), and pIOBuffer is of type PULONG.
Use DebugView from Sysinternals to check on data displayed with DbgPrint if you want to test/debug this easily. Once debuged, comment all those DbgPrints ...
LordCrush
02-24-2003, 09:50 AM
The bad is that i don't get any msg in DebugView so far. I have written a little usermode pgm with DebugPrint, i can see the debugoutput, but not from the driver.
when i call the driver i only get Error code 87 (GetLast ErrorCode)
i assume the driver is dooing nothing ...
gawker
02-24-2003, 10:22 AM
Thanks for all the help. Got it working this weekend!
fester
02-24-2003, 03:50 PM
Lord Crush,
My understanding is that if you use kernel debugs, you need to have a debugger attached to your kernel via a serial port (running on another machine) *OR* you need XP to debug locally (mentioned above).
I didn't bother with this, and just assigned certain values in the pIOBuffer and had my userland program understand these error codes.
LordCrush
02-25-2003, 09:11 AM
from lostinspace
Another, maybe even better option, is to use OutputDebugString in kernel driver to send your debug messages at various places in your driver code, and to use DebugView from SysInternals to watch those messages
Hmm is that function really called OutputDebugString ?
/shrug ... will keep on trying ..
lostinspace
02-25-2003, 10:45 AM
Function is not called OutputDebugString. It is one i used in last example, called DbgPrint. It has declaration in ntifs.h i think. It is possible that there is some other function with same name, but i used DbgPrint and it worked.
And no, you dont have to be in any debbuging kernel mode for that to work, DebugView will show it. Only situation when DbgView will not show debug message is when another debugger is attached to executable.
LordCrush
02-25-2003, 05:45 PM
Thank you for clarify ... i will work on getting any debug messages out of the driver.
But i have not much free time to fool around with this - kids want to play with daddy ;)
Thanks again for answer ... i am gaining hope again, to get it to work someday :D
Powered by vBulletin® Version 4.1.9 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.