Dll injection using APC

Hi

I am injecting a dll into a process by using APC. I am sending Process ID, Thread ID, dll path and virtual address of LdrLoadDll in the target process to my driver.
Below is the code

void APCKernelRoutine(PKAPC pKAPC,  
 PKNORMAL_ROUTINE pUserAPC,  
 PVOID pContext,  
 PVOID pSysArg1,  
 PVOID pSysArg2)  
{  
 DbgPrint("APCKernelRoutine Entered\n");  
 ExFreePool(pKAPC);  
}  
  
NTSTATUS InjectDllByAPC(ULONG TargetPid, ULONG TargetTid, PUNICODE_STRING usDllPath, ULONG LdrMethodAddress)  
{  
 ULONG size;  
 PKTHREAD TargetThread;  
 PEPROCESS TargetProcess;  
 KAPC_STATE ApcState;  
  
ULONG arg1 = 0;  
 ULONG arg2 = 0;  
 ULONG arg3 = 0;  
  
DbgPrint("Inside InjectDllByAPC...\n");  
  
size = (unsigned char\*)APCMdlCodeEnd - (unsigned char\*)APCMdlCode;  
  
DbgPrint("Allocating MDL (1)...\n");  
  
pMDLApcCode = IoAllocateMdl(APCMdlCode, size, FALSE, FALSE, NULL);  
  
if (!pMDLApcCode)  
 {  
 return(STATUS_UNSUCCESSFUL);  
 }  
  
MmProbeAndLockPages(pMDLApcCode, KernelMode, IoWriteAccess);  
  
RtlZeroMemory(pAPCData, sizeof( pAPCData));  
 memcpy( (char\*) pAPCData, usDllPath-\>Buffer, usDllPath-\>Length);  
 unicodeLengthInfo = \*(ULONG\*) usDllPath;  
  
pMDLApcData = IoAllocateMdl (pAPCData, sizeof(pAPCData), FALSE,FALSE,NULL);  
 if (!pMDLApcData)  
 {  
 return STATUS_UNSUCCESSFUL;  
 }  
  
MmProbeAndLockPages(pMDLApcData, KernelMode, IoWriteAccess);  
  
PsLookupProcessByProcessId((HANDLE)TargetPid, &TargetProcess);  
 DbgPrint("Pid: %d, PEPROCESS: 0X%X\n", TargetPid, TargetProcess);  
  
PsLookupThreadByThreadId ((PVOID) TargetTid, &TargetThread);  
 DbgPrint("Tid: %d, PKTHREAD: 0X%X\n", TargetTid, TargetThread);  
  
KeStackAttachProcess((PKPROCESS) TargetProcess, &ApcState);  
  
pMappedCode = (PVOID\*) MmMapLockedPagesSpecifyCache(pMDLApcCode, UserMode, MmCached, NULL, FALSE, NormalPagePriority);  
 pMappedData = (PVOID\*) MmMapLockedPagesSpecifyCache(pMDLApcData, UserMode, MmCached, NULL, FALSE, NormalPagePriority);  
  
KeUnstackDetachProcess (&ApcState);  
  
arg1 = (ULONG) LdrMethodAddress;  
 arg2 = (ULONG) pMappedData;  
 arg3 = (ULONG) unicodeLengthInfo;  
  
pKAPC = (PKAPC) ExAllocatePool( NonPagedPool, sizeof(KAPC) );  
 RtlZeroMemory(pKAPC, sizeof(KAPC));  
  
KeInitializeApc(pKAPC, TargetThread, OriginalApcEnvironment,   
 (PKKERNEL_ROUTINE)APCKernelRoutine, NULL,   
 (PKNORMAL_ROUTINE) pMappedCode,  
 UserMode, (PVOID)arg1);  
  
KeInsertQueueApc(pKAPC, (PVOID)arg2, (PVOID)arg3, 0);   
  
//KETHREAD.ApcState.UserApcPending = 1  
 //\*((unsigned char \*)TargetThread + 0x4a) = 1; //XP, 2K3 RTM  
 //\*((unsigned char \*)TargetThread + 0x3e) = 1; //2K3 SP1, SP2  
 //\*((unsigned char \*)TargetThread + 0x4e) = 1; //Vista  
 \*((unsigned char \*)TargetThread + 0x56) = 1; //Win 7  
  
if (pMDLApcCode)  
 {  
 MmUnlockPages(pMDLApcCode);  
 IoFreeMdl(pMDLApcCode);  
 }  
  
if (pMDLApcData)  
 {  
 MmUnlockPages(pMDLApcData);  
 IoFreeMdl(pMDLApcData);  
 }  
  
ObDereferenceObject(TargetProcess);  
 ObDereferenceObject(TargetThread);  
  
return STATUS_SUCCESS;  
}  
  
void APCMdlCode(PVOID lpLdrLoadDll, PVOID pwsDllPath, PVOID pwsDllPathLength)  
{  
 UNICODE_STRING usDllName;  
 ULONG DllCharacteristics = 0;  
 PVOID DllHandle = 0;  
  
usDllName.Length = (USHORT) pwsDllPathLength;  
 usDllName.MaximumLength = usDllName.Length + 2;  
 usDllName.Buffer = (USHORT\*) pwsDllPath;  
  
__asm  
 {  
 pushad  
  
lea eax, DllHandle  
 push eax  
 lea eax, usDllName  
 push eax  
 lea eax, DllCharacteristics  
 push eax  
 push 0  
  
call [lpLdrLoadDll]  
  
nop  
 nop  
  
popad  
  
}  
}  
  
void APCMdlCodeEnd()  
{  
}  
  

This is working fine in XP and Vista but the target process crashes in Windows 7. I am not able to figure out why.
In user mode I am not checking if the thread is alertable or not. Is it necessary as I am setting KETHREAD.ApcState.UserApcPending to 1 in Kernel mode? Also in windows 7 the “APCKernelRoutine” is called 10-15 seconds after DeviceIoControl returns.

Thanks

xxxxx@gmail.com wrote:

This is working fine in XP and Vista but the target process crashes in Windows 7. I am not able to figure out why.

The most obvious first question is, are you sure you’re running Windows
7 32-bit? You can’t inject a 32-bit DLL into a 64-bit process.


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.

Yes. It is Windows 7 32-bit.
The sys file is 32 bit.

The obvious first question is:

What problem are you trying to solve with this horrible ugly kludge?

Nice use of the hard-coded offset into the ETHREAD. That’s very elegant and professional. And the in-line assembly language is nice, too.

Peter
OSR

I work with an anti virus company.
I want to inject my dll into every process that is created.

do NOT do it use APC. why U want inject dll to ALL process?
If other anti-virus software don’t let U inject ,how it?
At 2012-10-25 14:30:04,xxxxx@gmail.com wrote:

I work with an anti virus company.
I want to inject my dll into every process that is created.


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

I dont know where Mr. Burn as been. But jut in case he doesn’t check-in soon, allow me to steal ke of his signature lines:

(I would have put it all in caps but I’m typing on an iPad)

That such code would be part of a commercial product is both frightening and astonishing. Irresponsible doesn’t even begin to describe it.

Peter
OSR

Peter,

I was tied up this morning and appreciate that you sent the below.
I should point out to the OP that I actually experienced an AV product
that was this stupid once. Two companies merged and the IT bozo’s from
the new owner came out one weekend and installed the corporate standard
for AV on the systems, of course without checking if we already had AV.
The result was the original AV product detected the DLL injection and
labeled the new AV as a virus to be destroyed along with all the
corrupted processes, the new AV sees the attack, and decided anything
touched by the original AV is dangerous. The ensuing war meant we spent
the week restoring backups and putting the systems back together (of
course the bozo IT types did not help).

The corporate standard AV firm is gone now, they were later sued for
just such a situation. So please tell us the products name, and your
major customers, since not only do I want to be sure to never install
it, if there are public companies stupid enough to buy such crap, I want
to be sure to not own any stock in them.

Don Burn
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

xxxxx@osr.com” wrote in message news:xxxxx@ntdev:

> I dont know where Mr. Burn as been. But jut in case he doesn’t check-in soon, allow me to steal ke of his signature lines:
>
>


>
> (I would have put it all in caps but I’m typing on an iPad)
>
> That such code would be part of a commercial product is both frightening and astonishing. Irresponsible doesn’t even begin to describe it.
>
> Peter
> OSR

This seems like a Really Bad Idea, and the code is full of serious bugs.
What problem are you trying to solve? And the “antivirus” handwave merely
tries to justify why a bad solution might be desired, but still does not
indicate what problem this is solving. Also, I expect that for Vista and
eyod, integrity levels would be enforced.
joe

more below…

Hi

I am injecting a dll into a process by using APC. I am sending Process ID,
Thread ID, dll path and virtual address of LdrLoadDll in the target
process to my driver.
Below is the code

void APCKernelRoutine(PKAPC pKAPC,  
PKNORMAL_ROUTINE pUserAPC,  
PVOID pContext,  
PVOID pSysArg1,  
PVOID pSysArg2)  
{  
DbgPrint("APCKernelRoutine Entered\n");  
ExFreePool(pKAPC);  

And this free routine call will work correctly because…?)

}

This is allegedly a __stdcall function. So why does it not have __stdcall
or one of the multitudenous synonyms specified?

NTSTATUS InjectDllByAPC(ULONG TargetPid, ULONG TargetTid, PUNICODE_STRING
usDllPath, ULONG LdrMethodAddress)
{
ULONG size;
PKTHREAD TargetThread;
PEPROCESS TargetProcess;
KAPC_STATE ApcState;

ULONG arg1 = 0;
ULONG arg2 = 0;
ULONG arg3 = 0;

DbgPrint(“Inside InjectDllByAPC…\n”);

size = (unsigned char*)APCMdlCodeEnd - (unsigned char*)APCMdlCode;

DbgPrint(“Allocating MDL (1)…\n”);

pMDLApcCode = IoAllocateMdl(APCMdlCode, size, FALSE, FALSE, NULL);

if (!pMDLApcCode)
{
return(STATUS_UNSUCCESSFUL);
}

MmProbeAndLockPages(pMDLApcCode, KernelMode, IoWriteAccess);

Where do you unlock these pages? And why do you need to lock them?

RtlZeroMemory(pAPCData, sizeof( pAPCData));

Why do you feel a compulsion to zero out memory you are about to overwrite?

memcpy( (char*) pAPCData, usDllPath->Buffer, usDllPath->Length);

and when usDllPath->Length is > size?

unicodeLengthInfo = *(ULONG*) usDllPath;

WHAT??? WHAT WORLD DO YOU LIVE IN WHERE WRITING A LINE LIKE THIS DOES NOT
GET YOU FIRED???

If you want usDllPath->Length, then that is what you write.

pMDLApcData = IoAllocateMdl (pAPCData, sizeof(pAPCData),
FALSE,FALSE,NULL);
if (!pMDLApcData)
{

and what happened to the space allocated by the previous IoAllocateMdl?

return STATUS_UNSUCCESSFUL;
}

MmProbeAndLockPages(pMDLApcData, KernelMode, IoWriteAccess);

Again, why do you feel compelled to lock down the pages?

PsLookupProcessByProcessId((HANDLE)TargetPid, &TargetProcess);
DbgPrint(“Pid: %d, PEPROCESS: 0X%X\n”, TargetPid, TargetProcess);

Why do you know this call can NEVER fail? I would consider its failure to
be a highly-likely outcome. Exercise For The Reader (3 points, since it
should be screamingly obvious): why?

PsLookupThreadByThreadId ((PVOID) TargetTid, &TargetThread);
DbgPrint(“Tid: %d, PKTHREAD: 0X%X\n”, TargetTid, TargetThread);

Ditto.

KeStackAttachProcess((PKPROCESS) TargetProcess, &ApcState);

Given the possibility that one or both of the previous calls failed, why
do you think this one is going to succeed?

pMappedCode = (PVOID*) MmMapLockedPagesSpecifyCache(pMDLApcCode,
UserMode, MmCached, NULL, FALSE, NormalPagePriority);
pMappedData = (PVOID*) MmMapLockedPagesSpecifyCache(pMDLApcData,
UserMode, MmCached, NULL, FALSE, NormalPagePriority);

And why do you assume that either of these mappings coud possibly work?
Why are you not testing for errors?’

KeUnstackDetachProcess (&ApcState);

Given that you have no idea if the attach failed, why do you think a
detatch will succeed?

arg1 = (ULONG) LdrMethodAddress;
arg2 = (ULONG) pMappedData;
arg3 = (ULONG) unicodeLengthInfo;

In what fantssy world do you live in where sizeof(void*) will ALWAYS be
sizeof(ULONG)?

pKAPC = (PKAPC) ExAllocatePool( NonPagedPool, sizeof(KAPC) );
RtlZeroMemory(pKAPC, sizeof(KAPC));

Why do you zero out a structure you are abour to initialize?

KeInitializeApc(pKAPC, TargetThread, OriginalApcEnvironment,
(PKKERNEL_ROUTINE)APCKernelRoutine, NULL,
(PKNORMAL_ROUTINE) pMappedCode,
UserMode, (PVOID)arg1);

Why do you cast a pointer to a ULONG only to cast the ULONG as a pointer?
Why are these variables declared ULONG in the first place?

KeInsertQueueApc(pKAPC, (PVOID)arg2, (PVOID)arg3, 0);

Ditto arg2 and arg3? Special: for two points, explain why code like this
should never exist, and why it is wrong.

//KETHREAD.ApcState.UserApcPending = 1
//*((unsigned char *)TargetThread + 0x4a) = 1; //XP, 2K3 RTM
//*((unsigned char *)TargetThread + 0x3e) = 1; //2K3 SP1, SP2
//*((unsigned char *)TargetThread + 0x4e) = 1; //Vista
*((unsigned char *)TargetThread + 0x56) = 1; //Win 7

Please explain why you should not be fired for writing the above code.

if (pMDLApcCode)
{
MmUnlockPages(pMDLApcCode);
IoFreeMdl(pMDLApcCode);
}

I find it very odd that you would write the above test. Please explain
how execution can reach this point if the pointer *is* NULL.

if (pMDLApcData)
{
MmUnlockPages(pMDLApcData);
IoFreeMdl(pMDLApcData);
}

Ditto

I find it odd that you assume that the mappings are valid in the target
process, even though you are deleting them here ithout the foggiest idea
as to whether or not the APC has executed. There is no way to know if
these pages are still needed.

ObDereferenceObject(TargetProcess);
ObDereferenceObject(TargetThread);

return STATUS_SUCCESS;
}

This code is more than a little scary. It appears to me tat a beginner
has read a few DDI calls and has done a kit-bash of putting them together
with some poor programming practices and a fundamental failure to
understand the basic principes of concurrency, it the vague hope that this
might accomplish something whose design is questionable at best, and upon
deeper examination will probably turn out be completely wrong. The
rampant assumptions that calls must necessarily succeed doom it anyway,
but these superficial errors merely mask the deeper and more fundamental
design flaws.

There are several possible approaches here, and they all start with a
rewrite of this code tat is sufficiently extensive that it could be safely
stated to be “throw this away and start over”. Then look for the places
you make strong sequentiality assumptions where we know these can never be
thought of as even weak guarantees. Learn the basic princinples of modern
Best Practice coding, including why the assumption that sizeof(void*) ==
sizeof(UNLONG) is completely unjustified in modern practice. Learn what
“undocumented field” means. Learn why funky dereferencing instead of
structure member access screams “incompetent programmer” (I kid you not;
looking at this code, I see so many errors that if accompanied by your
resume would be reason enough not to hire you).

I echo Peter’s and (the in-absentia-proxied-by-Peter) Don’s plea: please,
please tell us what the product name is so we can avoid it! If other
parts of the “anti-virus” product resemble this code, it will be
successful because the machine will bluescreen before any virus can get
into it.

void APCMdlCode(PVOID lpLdrLoadDll, PVOID pwsDllPath, PVOID
pwsDllPathLength)
{
UNICODE_STRING usDllName;
ULONG DllCharacteristics = 0;
PVOID DllHandle = 0;

usDllName.Length = (USHORT) pwsDllPathLength;

Has anyone explained to you why storing the low-order 16 bits of the
address of a string length is probably not going to give you the string
length? Or why the somewhat more correct
usDllName.Length = *(USHORT *) pwsDllPathLength
is also semantic nonsense? Why do you think the Length field is a USHORT,
and therefore why iis it that truncating a 32-bit value to 16 bits is
going to make sense?

usDllName.MaximumLength = usDllName.Length + 2;

For one point: why is this statement nonsensical?

usDllName.Buffer = (USHORT*) pwsDllPath;

why is this not (WCHAR*)? And why is it that you know that this buffer is
writeable?

__asm
{
pushad

lea eax, DllHandle
push eax
lea eax, usDllName
push eax
lea eax, DllCharacteristics
push eax
push 0

call [lpLdrLoadDll]

Please, please, PLEASE explain why
lpLdrDll(&DllCharacteristics, &DllName, &DllHandle);
will not accomplish this in a platform-independent fashion, and why,
instead, you wrote that gross piece of assembly code to avvomplish the
same effect. And show me the prototype of lpLdrDll, and te declaration of
the variable lpLdrDll. And why you have chosen the use of Hungarian
Notation in the kernel, and not just using it, but using it incorrectly.

And I did not see where you have enforced the requirement that the called
function has __stdcall linkage.

nop
nop

and the purpose of these teo nops is what, exactly?

popad

}
}

void APCMdlCodeEnd()
{
}

 
This is working fine in XP and Vista but the target process crashes in  
Windows 7. I am not able to figure out why.  

Nothing short of a miracle could let this code execute correctly. You
critically depend upon undocumented artifacts of the scheduler, for
example, and “undocumented” means “we are free to change the behavior any
time we feel like it”

Note that you say “it works” without qualifying the statement with any
hardware information, such as number of cores, if hyperthreading is being
used, etc. What I see is a piece of code that is fundamentally incorrect
at very deep levels, but could present with the illusion of working as
long as the unstated assumptions are not vioated.

Also, processes do not “crash”. That is a word which, if unqualified,
marks the poster as a newbie. What actually happened was that the process
terminated prematurely due to a specific kind of error at a specific
address. It is your responsibility to give us that information if you
want an answer that is more helpful than “you have a bug. Fix it.” I’ve
got a pretty good guess where it is failing, and if you look for places
where you depend on nonexistent sequentiality guaranttes, you’ll spot them
as well.

In user mode I am not checking if the thread is alertable or not. Is it
necessary as I am setting KETHREAD.ApcState.UserApcPending to 1 in Kernel
mode? Also in windows 7 the “APCKernelRoutine” is called 10-15 seconds
after DeviceIoControl returns.

Thanks


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

> I dont know where Mr. Burn as been. But jut in case he doesn’t check-in

soon, allow me to steal ke of his signature lines:

(I would have put it all in caps but I’m typing on an iPad)

That such code would be part of a commercial product is both frightening
and astonishing. Irresponsible doesn’t even begin to describe it.

Go to the settings app
Find the General item in the left panel and click it
Scroll down in the right panel until you see the Keyboard item
Tap on it to get the Keyboard page
Find “Enable caps lock” and turn it on.

Then, you can DOUBLE-TAP the shift key. It will turn SOLID BLUE.
Everything you type after that will be ALL CAPS until you hit the !7’348.
/)8;5 (that’s "numeric shift with numeric shift on) or single-tap the
shift key to dismiss CAPS LOCK mode.

I have the Apple Bluetooth keyboard, and a $50 suspension arm, and do most
of my writing in bed. Also, for a case I found a $20 black case for an HP
NetBook that I bought at BestvBuy. With only a little bit of effort, I
can fit both the iPad and and the keyboard in it, and the charger and
screen-cleaner wipes (CostCo) go into the zippered side pocket.
joe

Peter
OSR


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

Peter is absolutely right. This sort of code has no business whatsoever in production code.

The interfaces for queuing kernel mode APCs are undocumented, subject to change without notice. Needless to say that groveling into undocumented kernel private structures to twiddle bits without any idea of what the proper synchronization semantics around said field is a terrible idea too.

  • S (Msft)

-----Original Message-----
From: xxxxx@lists.osr.com [mailto:xxxxx@lists.osr.com] On Behalf Of xxxxx@osr.com
Sent: Thursday, October 25, 2012 8:16 AM
To: Windows System Software Devs Interest List
Subject: RE:[ntdev] Dll injection using APC

I dont know where Mr. Burn as been. But jut in case he doesn’t check-in soon, allow me to steal ke of his signature lines:

(I would have put it all in caps but I’m typing on an iPad)

That such code would be part of a commercial product is both frightening and astonishing. Irresponsible doesn’t even begin to describe it.

Peter
OSR


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

Hi,
Thanks for your comments. I am new to Kernel mode programming and didn’t know that what I was doing is nothing but trash. I need to start it all over again. :slight_smile: