Log in

View Full Version : Harlequin's "Protecting against TerminateProcess" - impossible to work


DakienDX
June 1st, 2001, 07:05
(First part, since my message is longer than 4098 bytes)

Hello everybody !

First of all, I don't want to talk about who is able to program good programs and who's not.
I haven't seen much of Harlequin so I can't judge him, this error could have happened to erverybody.

To come to the main subject of this post, you know that Harlequin tried to find a way to protect programs from being terminated by trojan horses. They use the API-function TerminateProcess() to terminate a process. This function doesn't send any warning to the application being terminated and just kills it.

Harlequin tried to fix this by injecting some code into Kernel32.dll to call an external library which asks if you wish to allow the program which tries to terminate an other one to do it.

He called GetCurrentProcessId to get the PID of the process calling TerminateProcess() and the creates a snapshop of all processes and compares the PID with them to get the path of the calling program.
This is a good idea and well implemented.

But... This isn't made at first.

At first he tries to get the path of the application to be terminated.
He does it the following way:

Term proc
Start:
mov eax,dword ptr[esp+4] ;get the PID from the stack
mov PID,eax ;and save it

and then compares the "PID" to the processes in the snapshot. But the "PID" isn't the ProcessID, it's the HANDLE of the process to be terminated, so you can't find any matches in the snapshot, because you would compare if the PID and a HANDLE are the same. If it doesn't find any match, it returns with EAX = 0. This forces the Kernel32.dll patched code to exit without terminating. This does mean two things: 1.) you can't terminate any process any longer, 2.) since the return to the calling process is made via a "ret" and not via a "ret 8" (since TerminateProcess() has two parameters on the stack), the calling process has the stack 8 bytes to low and will probably crash.

I don't know if there is a way to get the PID of a process if you have only it's HANDLE.


(Continued in the next part)

DakienDX
June 1st, 2001, 07:06
(Last part)

But now the question: Why did it work with the example PID.EXE included in the package together with the other sources? What made Harlequin think his code works?

TerminateProcess() jumps to this code:

pushad ; save all regs - IMPORTANT (*)
push 0AFFC5F96h ; lib: HTerm.dll
call 76D4h ; LoadLibraryA
test eax,eax
je @@1
push 0AFFC5FA0h ; func: Term
push eax ; lib base
call 6DACh ; GetProcAddress
test eax,eax
je @@1
call eax ; call Term - IMPORTANT (*)
test eax,eax
jne @@2
@@1:
popad
ret ; BUG - should be "ret 8"
@@2:
popad
push esi ;replaced instruction
push edi ;replaced instruction
call 0A298h ;replaced instruction
jmp 25CBBh ;back to TerminateProcess


Remember this?

Term proc
Start:
mov eax,dword ptr[esp+4] ;get the PID from the stack

This doesn't set EAX to the value passed to TerminateProcess(), but to the value of EDI (since the PushAD) !

In the PID.EXE example program he uses this code

mov edi,[PIDStruct.th32ProcessID] ; IMPORTANT (*)
push edi
push offset format
push offset PID
call _wsprintfA
call MessageBoxA,Hinst,offset PID,offset FileName,0
call OpenProcess,PROCESS_TERMINATE,0,[PIDStruct.th32ProcessID]
mov Phand,eax
call TerminateProcess,Phand,0 ; HANDLE, ExitCode

Since EDI isn't chanced by Windows, EDI contains the PID and so it can find the path of the process to be terminated in the snapshot.


Again, I didn't want to attack Harlequin, I only wanted to tell and explain you, why his patch doesn't work as it should and so doesn't protect our firewalls from being terminated by trojan horses.

hz
June 1st, 2001, 12:36
Hiya DakienDX,
I found it very informative, thanks.
"Again, I didn't want to attack Harlequin", I'm sure Harlequin won't
regard it as an attack, we are all here to learn, right?.
regards

Man that was a polite response, does that get me in the new "elite" forum? ;D

Lord Rhesus
June 1st, 2001, 13:51
Harlequin's working in dark dingy foreign lands at the moment so he probably won't be able to reply. But yes your absolutely right, we can assume that when Harlequin was writing the HTerm.dll and was looking for where the handle was located on the stack he must have noted down what the handle was and searched the stack for it. At the first occurrence (which was edi's value as left from the pusha) he must have assumed that this was the handle when actually it was the value further along the stack. A simple fix for this would be to change the code in Hterm.dll from:

;---code snippet start---
;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
; Term Exported function
;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Term proc
Start:
mov eax,dword ptr[esp+4] ;get the PID from the stack
mov PID,eax ;and save it
;---code snippet end---

to:

;---code snippet start---
;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
; Term Exported function
;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Term proc
Start:
mov eax,dword ptr[esp+38h] ;get the PID from the stack
mov PID,eax ;and save it
;---code snippet end---

Well spotted DakienDX!

DakienDX
June 1st, 2001, 14:12
Hello Lord Rhesus !

Your suggestion won't fix the problem.

mov eax,dword ptr[esp+38h] gets the parameter pushed originally on the stack to TernimateProcess(), but this is NOT the PID, but the HANDLE to the process.

A HANDLE is something like 10h,14h,18h,..., but a PID is something like FFFF5353h,FFEFA742h,FFFE8681h or similar.

I think (as far as I know) that it's impossible to get the path of a process if you have only it's HANDLE (and not it's PID) with standard API-calls.

Lord Rhesus
June 1st, 2001, 16:22
doh! Silly me, I didn't read the post properly and I thought that the only problem was the stack push. When I posted the *fix* (be it not an actual fix as it doesn't solve the underlying problem) I only tried it using the pid program before posting. Just before checking the message board again I tried to see if it would ask when I tried to kill something in procdump (which uses terminateprocess). It didn't, all I can say is doh!

Kayaker
June 1st, 2001, 19:13
Hi guys,

I see what you mean. It looks like the HANDLE of the current process, which is passed to TerminateProcess before term.dll is called and saved with the pushad, is being compared in Loop1 with the PID stored in the PROCESSENTRY32 structure after Process32First/Next is called. It's like comparing apples and oranges.


Start:
mov eax,dword ptr[esp+4] ;get the PID from the stack
mov PID,eax ;and save it

invoke CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0 ;create a snapshot
mov Shnd,eax
mov eax,SIZE PIDStruct
mov PIDStruct.dwSize,eax
invoke Process32First,Shnd,offset PIDStruct
Loop1:
mov eax,dword ptr[PIDStruct.th32ProcessID]
mov edx,PID
cmp eax,edx
je FoundIt
invoke Process32Next,Shnd,offset PIDStruct
test eax,eax
jne Loop1
RET


It's in the 2nd part where the actual PID is retrieved from the calling process with GetCurrentProcessID, compared *again* with the PIDStruct.th32ProcessID value, and when a match is found in Loop2 jumps to the MessageBox giving you the option to abort the TerminateProcess.

FoundIt:
invoke lstrcpyA,offset Proc1,offset PIDStruct.szExeFile
invoke GetCurrentProcessId
mov PID,eax
invoke Process32First,Shnd,offset PIDStruct
Loop2:
mov eax,dword ptr[PIDStruct.th32ProcessID]
mov edx,PID
cmp eax,edx
je GotYa
invoke Process32Next,Shnd,offset PIDStruct
test eax,eax
jne Loop2
.
.
invoke MessageBoxA


Since the current process is always going to be the one which called TerminateProcess in kernel32.dll, I'm wondering why Loop1 is there at all. Why not just call CreateToolhelp32Snapshot, which creates a handle to the snapshot which is used by the other Toolhelp32 API's, then go through the Process32First/Process32Next routine, comparing the PIDStruct.th32ProcessID value and the GetCurrentProcessID returned PID as it does anyway in Loop2?

It just sort of looks like
Start:
mov eax,dword ptr[esp+4] ;get the PID from the stack
mov PID,eax ;and save it

and Loop1 are redundant since we know we can get the current PID (the terminating program) with GetCurrentProcessID.

Kayaker

DakienDX
June 2nd, 2001, 03:14
Hello Kayaker !

The first loop was actually meant to get the name of the program which is to be terminated.

A "C:\WINDOWS\SYSTEM\ATI32KEY.EXE wants to terminate a program. Continue ?" message would not do the thing it was meant to do.
It should do a "C:\WINDOWS\SYSTEM\ATI32KEY.EXE wants to terminate C:\Program Files\FireWallAntiVirus\FireWallAntiVirus.exe. Continue ?" message to inform the user what's going on.

Therefore the first loop was implemented, but if it fails, it exits without asking if a program is allowed to terminate a UNKNOWN program.

Harlequin
June 29th, 2001, 16:55
Hi


First of all I need to say that if you are looking for high quality programming you will not find it in my work :-) I do not profess to be a programmer just someone who likes to play with a few bytes here and there. I have no expertise and am like many only learning as I go. My work is available only because I am prepared to make a fool of myself if it helps others in some way. Obviously my intention is to help others by sharing what I do know, but, if indeed some learn by my mistakes then thats ok too, the end result is the same :-) Whilst it is most certainly true that no matter how much you know there is always someone who knows more, the reverse is also true. As such I am a firm believer that the more information avaiable the easier it is to progress. Please forgive me if I make errors but the only sure way not to do so is to do nothing!

Secondly I would like to appologise for my delay in responding to your posts, as Lord Rhesus mentioned I have been away and have only just returned.

Ok.

The error which you point out is of course correct, I wrote this soution in a big hurry and then had to go away almost immediatly afterwards. However I also had noticed the error before I left and had made an attempt at a workaround. This I sent to +Tshep who replaced the original with the new one, as the original had only been up a few hours I did not think that anyone would have downloaded it, sorry. Download the current source code to see my changes.

You pointed out that there is no way to determine the process ID via its handle and on this I have to agree, I have not as yet managed to find a way to do this either. So I have been unable to find a hard and fast solution to the fault. What I have done is to ammend the code so that is searches through the last 40 dword values on the stack checking for a PID. This is not and ideal solution as the application which calls the TerminateProcess API may not have used this stack etc etc. It will trap more instances of the PID though and therefore more often display the name of the application which is to be terminated. The second change I made was to simply display a default message box which informs that an application is trying to Terminate another even though the name of the process to be terminated was not identified. This does work and has been working for me for some time without problem.

To be honest I am a little disappointed, I wrote this thing very quickly as one possible answer to a potential problem. It was not intended to be a final solution, I was hoping that during the time I was away mayby someone would have improved on this idea and/or bettered it. I accept that the faults you point out are valid, However I included all the source code, an explanation of what my intentions were and a full description of how kernel32.dll was injected. I intentionally did not make a patch program and sincerely believed that any one capable of implementing the changes to kernel32.dll would also be capable of altering and improving my code. Surely it would have been better to use this as an opportunity to not only point out a fault but to offer an alternative or a fix? this way others may have learnt more still, myself included :-)


Harlequin

tsehp
June 30th, 2001, 19:56
to have a processId with it's handle :
use what kayaker says : snapshots, then you'll get into the following structure :typedef struct tagPROCESSENTRY32
{
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID; // this process
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID; // associated exe
DWORD cntThreads;
DWORD th32ParentProcessID; // this process's parent process
LONG pcPriClassBase; // Base priority of process's threads
DWORD dwFlags;
CHAR szExeFile[MAX_PATH]; // Path
} PROCESSENTRY32;

so you can build a struct of processId running, then for each procId, use :
HANDLE OpenProcess(

DWORD dwDesiredAccess, // access flag
BOOL bInheritHandle, // handle inheritance flag
DWORD dwProcessId // process identifier
);

with each of processId's you have on first structure, you compare to the handle you've found on the stack and you're done.

Harlequin
July 1st, 2001, 20:30
Hi +Tshep

Thanks for the input, if only it were that simple!
I had previously tried using OpenProcess to determine the handle of the process to be terminated however...
While OpenProcess does indeed return a handle to the process it does not return THE handle. This can be demonstrated by sequencing several OpenProcess calls one immediatly after the other. Each call returns a new, unique handle.

As I need to make a direct correlation between the handle which was orignially passed to TerminateProcess and the process ID this method will not work.

This problem is still bothering me and a solution must be possible so any input is eagerly welcomed.

About the best I have at the moment is to inject Process32Next to save the PID of the last check. This still is not a good solution as the terminating process could have done the identification checks in bulk saving all the handles and then looping through to terminate them. There are also no guarantees that the process was not found on the first pass using Process32First.


Harlequin

Fake51
July 12th, 2001, 13:03
I think I found a working solution to the problem. If it's of any interest I can post it here, tho I haven't completed the code yet. I checked Harlequin's essay on Tsehp's mirror, but couldn't see any updates, so I figured the problem was still around.

Blue skies
Fake

Harlequin
July 12th, 2001, 18:24
HeHe

I would love to see your solution. You are correct in thinking that I haven't found a way around it yet. I am currently looking into API hooks as an option, hoping to avoid the necessity of patching the system dlls too.

Please post your code or email me you might just be in time to stop me becoming a bald, alchoholic pill popper.

Harlequin

Fake51
July 13th, 2001, 06:39
Well, I hope this will stop you in time, as otherwise you will end up like me, swearing, hissing, bitching at,not to forget cursing, threatening, and hating Microsoft, and their stupid company politics and/or lameass programmers.

Well, a quick sum up:
We have:
- handle to the process being terminated
We need:
- processId of process being terminated (actually just the name, but the processId is a quick way to the name)

So I started debugging windows, to figure out how to attain the goal. A good old gut feeling told me that TerminateProcess had to convert the handle given to it to something else. And after looking a bit into it, I found out that it does. The handle is turned into an address in memory that contains info about the process. If you do a "proc" when in softice, you'll see that in the first column after the name, there is a value, usually round 81xxxxxxh. This is the address of the particular process (referred to from here on as pProcess, since that's the softice name).
This pProcess is calculated from the handle given to TerminateProcess plus the equivalent pProcess for the current process. Ofcourse, this is worth more or less nothing, if it's not possible to relate the pProcess to the ProcessId. Here comes the beautiful part, the pProcess and the ProcessId are technically the same, only difference lies in an xor function. To get the ProcessId from the pProcess, you xor by a specific value, and vice versa with same value.
So, now the goals that need be attained have changed:
- Get pProcess of current process, to be able to get pProcess of process being terminated
- Get xor value needed to turn pProcess into ProcessId

Amazingly, this is actually quite easy. The handle for the process to be terminated we have. The rest we can actually get from the nice and simple GetCurrentProcessId function.
This function first pushes the pProcess of the current process onto the stack, and doesn't delete it from there (goal one achieved). Then it moves the xor value to eax, and xors eax with the stack. Thus the stack will contain the pProcess of the current process, and eax will contain the ProcessId. Xor the two and you get the xor value.
Now, in the address space of pProcess for the current process, pProcess+44h holds a pointer to an index of all the currently running processes. Add 8 to the pointer, plus the handle to the process to be terminated times two, and you get an address in the index, that holds the pProcess of the process to be terminated.
The best part of all this: this should actually be a universal way to achieve what we need, from win95 to winMe.

Now, if you'll excuse me for a minute:
MICROSOFT SUX!!!!!!!!!!!!
I spent more than eight hours debugging the system, searching the memory, analyzing the pProcess structure and stuff. All because Microsoft doesn't want you to be able to get your hands on the pProcess address (I'm lacking an api for converting the ProcessId into the pProcess in kernel32), or allow you to obtain a ProcessId for any given process. Bastards piss me off.
Sorry, steam venting.

Blue skies
Fake

Ps. I included the source of my program+the dll needed. The code to obtain the correct ProcessIds is in the dll. Do excuse the lousy coding discipline of mine, everything is a mess (which is why I wrote this lengthy and probably unnecessary and/or boring txt). Hope you find what you need tho.

Fake51
July 13th, 2001, 06:44
Sorry for posting again, found out it didn't include the source for the dll. Here it is.

Blue skies
Fake

the analyst / ucf
July 13th, 2001, 07:48
very nice
but :

Faketexe.asm is empty..
the dll one is ok
well, it maybe works for someone else, but not for me, so i thought it was good to note it
regards,

the analyst

Fake51
July 13th, 2001, 09:29
Thanx for noting, I'll try again.
Tho, in fact, the main program isn't of overly big importance here, and would probably just show the incredible mess a programmer can output. The hook inserted in kernel32.dll that calls the dll is almost exactly the same as Harlequins, and the rest is just a front for the program (create trayicon, check for other hooks in terminateprocess, that kinda stuff). Oh, and the program wouldn't actually work, seeing that it needs fakefix.vxd. Ah well, guess I'll include that one as well then.

Fake51
July 13th, 2001, 09:31
And here's the VxD.

Blue skies
Fake

kill3xx
July 13th, 2001, 17:21
hi Fake51..
u'r working in the right direction but u've made some mistakes:


--------------------------------------------------------------------------------

Now, in the address space of pProcess for the current process, pProcess+44h holds a pointer to an index of all the currently running processes. Add 8 to the pointer, plus the handle to the process to be terminated times two, and you get an address in the index, that holds the pProcess of the process to be terminated.

--------------------------------------------------------------------------------

1st pb)

as u can find f.e. in the Pietrek's book at offset 0x44 of pProcesss (actually PDB, Process Database) it stored the pointer to the process handles table.. that holds an array of structures with the infos for handles to kernel object opened by the process.. every entry has this simple struct:
DWORD Flags
LPVOID pKernelObject

pKernelObject is a pointer to the "header" of corresponding kernel object (PDB,TDB,KEvent,ecc.).. now u know that an handle is nothing less than a biased index in the process handle table..
so given a pPDB and a handle u can retrive the pointer to related kobj easely:
mov ebx,pDB
mov eax,Handle
mov edx,[ebx+eax*2+8]

so forgetting optimizations&&codingstyle this snippet is correct:

call GetCurrentProcessId
mov ecx,[esp-8]
mov edi,[ecx+44h]
mov edx,[esp+24h]
rol edx,1
mov edx,[edi+edx+8]

but (.. eheh there is alway a "but".. this indexing works only under > win98.. u can check f.e. the differences r.e. K32!OpenProcess..

win95

BFF7B6E7 ISetHandlesTableEntry proc near ; CODE XREF: sub_BFF7B728+1D25p
.text:BFF7B6E7 ; sub_BFF87477+8F25p
.text:BFF7B6E7 ; sub_BFF87477+E325p
.text:BFF7B6E7 ; sub_BFF93D55+E225p
.text:BFF7B6E7
.text:BFF7B6E7 pPDB = dword ptr 8
.text:BFF7B6E7 HandleEntry = dword ptr 0Ch
.text:BFF7B6E7 pK32Obj = dword ptr 10h
.text:BFF7B6E7 Flags = dword ptr 14h
.text:BFF7B6E7
.text:BFF7B6E7 push esi
.text:BFF7B6E8 mov eax, dword_BFFBC2A0
.text:BFF7B6ED mov esi, [esp+HandleEntry]
.text:BFF7B6F1 push eax
.text:BFF7B6F2 call KERNEL32_97
.text:BFF7B6F7 mov eax, [esp+pPDB]
.text:BFF7B6FB mov edx, [esp+pK32Obj]
.text:BFF7B6FF push edx
.text:BFF7B700 mov ecx, [eax+44h] ; // PDB.ProcessHandlesTable
.text:BFF7B703 mov [ecx+esi*8+8], edx ; // HandleEntry.pK32Object = pPDB
.text:BFF7B707 mov ecx, [eax+44h]
.text:BFF7B70A mov eax, [esp+Flags]
.text:BFF7B70E mov [ecx+esi*8+4], eax ; // HandleEntry.dwFlags = flags
.text:BFF7B712 call IIncrK32ObjRefCount ; increment kernel object reference count
> ....
.text:BFF7B725 ISetHandlesTableEntry endp ;


win98 or above
.text:BFF7DAB3 ISetHandlesTableEntry proc near ; CODE XREF: sub_BFF7DB02+1D25p
.text:BFF7DAB3 ; sub_BFF89AE0+8925p
.text:BFF7DAB3 ; sub_BFF89AE0+DE25p
.text:BFF7DAB3 ; sub_BFF9594C+E525p
.text:BFF7DAB3
.text:BFF7DAB3 pPDB = dword ptr 8
.text:BFF7DAB3 HandleEntry = dword ptr 0Ch
.text:BFF7DAB3 pK32Object = dword ptr 10h
.text:BFF7DAB3 Flags = dword ptr 14h
.text:BFF7DAB3
> ....
.text:BFF7DAB8 mov esi, [ebp+HandleEntry]
.text:BFF7DABB mov edi, esi
.text:BFF7DABD cmp esi, 0FFFFFFFFh
.text:BFF7DAC0 jz short loc_BFF7DAC7
.text:BFF7DAC2 mov edi, esi
.text:BFF7DAC4 shr edi, 2 ; // scale index
> ....
.text:BFF7DACD call KERNEL32_97
.text:BFF7DAD2 mov eax, [ebp+pPDB]
.text:BFF7DAD5 mov edx, [ebp+pK32Object]
.text:BFF7DAD8 push edx
.text:BFF7DAD9 mov ecx, [eax+44h] ; // PDB.ProcessHandlesTable
.text:BFF7DADC mov [ecx+edi*8+8], edx ; // HandleEntry.pK32Object = pPDB
.text:BFF7DAE0 mov ecx, [eax+44h]
.text:BFF7DAE3 mov eax, [ebp+Flags]
.text:BFF7DAE6 mov [ecx+edi*8+4], eax ; // HandleEntry.dwFlags = flags
.text:BF

so the handles biasing (scale) factor is changed under win98 (among other changes)... so u've to add the win95 case tu ur code.. a good idea is to make ur decision based on the different value of PDB.Type (0x5 win95 , > win98 0x6)..
if u want to see some of above considerations in action i suggest u to download the Elicz RTx lib:
he has exploited (hi Elicz! ..really cleaver idea indeed.. damn it was so obvious a internal kernel32 function to "emulate" remote threads but u can find the code for adjusting a process handle table entry too..

2nd pb)
u've assumed that the process handle is alway at index 0x1.. well call it safe programming but a little bit of paranoia is welcome here... so i suggest u to add some safety checks and if the ptr doesnt mach a table scanner =P


As usual : all credits to Matt Pietrek & sorry for my crappy english...

Best regards,

kill3xx

Fake51
July 14th, 2001, 12:35
Hi

Cheers for the info, didn't have the opportunity to check the prog on win95, only win98.
Anyway, thing works on my win, and I wasn't gonna release the program as such, just figured I'd post my solution to the problem if anybody could use it.

Blue skies
Fake

Harlequin
July 15th, 2001, 20:08
Hi Fake51

Thanks for the info I suffered a major crash at the hands of MS over the weekend and am in the process of re-formatting and rebuilding. Believe me there are few curses you could use for MS that I did not think of late sat night when i was sitting burning candles, inserting floppies and trying to remember everything i have forgotten about dos.

So i am here on a one legged system that could freeze any second just to let you know that I am not being ignorant by not getting back to you and will take a look at your code just as soon as I am stable again.

I don't know though, does it run on linux?? :-)

I have never been so tempted before.


Harlequin

Fake51
July 18th, 2001, 10:50
The best way of protecting yourself against Windows failing on you? Go Linux. Lol. Anyway, hope you can use it. With the info added by Kill3xx it should be possible to make a win95/98/ME general protection against the misuse of Terminateprocess.

Blue skies
Fake

DakienDX
July 21st, 2001, 04:46
Hello everybody !

I started this topic, but went four weeks to Spain right after opening it.
Sorry for that.
I haven't read all the replies in detail, but I'll do it in the next days. And continue working on the topic.