Log in

View Full Version : Hooking using a Linked List to specify the params..


BanMe
May 19th, 2009, 14:45
im still in process of refining this so questions and comments are most welcome..

Code:

namespace hkHook
{
struct CLLHook
{
SLIST_ENTRY HookEntry;
bool Installed;//0 is not installed 1 if installed
BYTE Opcode;//e9 jmp e8 call
int Index;
LPVOID pHeap;
DWORD TProcessId;//Targets ProcessId
DWORD hkTAddress;//Target Function to Hook
DWORD hkAddress;//Hook address
PVOID hkAllocAddr;
DWORD hkSz;//our hook size generally 5 bytes byte+dword
};
typedef CLLHook* PCLLHook;
extern "C"
{
__checkReturn bool hkInitialize(__inout CLLHook*);//Initialization
__checkReturn bool hkUninitialize(void);//Destructor
void *RtlAllocateAlignedHeap(__in size_t, __in size_t);
void *hkAllocateEntry(void);//Allocate the proper structure and connects it to our list..
__checkReturn bool hkSetTargetExeName(__in wchar_t *);//Sets Global Exe Name used for process opening by exe name...
__checkReturn bool hkSetHookParams(__in_opt wchar_t *,__in LPVOID,__in LPVOID,__in DWORD,__in BYTE,__inout CLLHook *HkStruct);
__checkReturn bool hkInstallHook(PCLLHook,HK_ROUTINE);//installs the hook specified
__checkReturn bool hkDeleteNode(__in int);//Delete hook link off our list
wchar_t *cProcessName;//current target process name..
HANDLE hHeap,hWaitParams,pHeap;
PSLIST_ENTRY pFirstEntry = {0},pListEntry = {0};
PSLIST_HEADER pListHead = {0};
int InitPhase = 0;

void *RtlAllocateAlignedHeap(size_t size, size_t alignment )
{
void *pa, *ptr;
pa=RtlAllocateHeap(hHeap,HEAP_ZERO_MEMORY,(size+alignment-1)+sizeof(void *));
if(!pa)
{
return NULL;
}
ptr=(void*)( ((ULONG_PTR)pa+sizeof(void *)+alignment-1)&~(alignment-1) );
*((void **)ptr-1)=pa;
return ptr;
}
__checkReturn bool hkInitialize(__inout CLLHook* pCLL )
{
if(InitPhase > 0)
{
return false;
}
hHeap = RtlCreateHeap(HEAP_GROWABLE|HEAP_ZERO_MEMORY,0,0,0,0,0);//64 pages reserved, 1 page allocated
if(hHeap != INVALID_HANDLE_VALUE && hHeap != 0)
{
pListHead = (PSLIST_HEADER)RtlAllocateAlignedHeap(sizeof(SLIST_HEADER),MEMORY_ALLOCATION_ALIGNMENT);
if(!pListHead)
{
return false;
}
RtlInitializeSListHead(pListHead);
if(pListHead)
{
*pCLL = *(CLLHook*)hkAllocateEntry();
if(pCLL != 0)
{
return true;
}
}
}
return pCLL;
}
//the destructor
__checkReturn bool hkUninitialize(void)
{
if(InitPhase)
{
RtlInterlockedFlushSList(pListHead);
RtlDestroyHeap(hHeap);
InitPhase = 0;
return true;
}
return false;
}
//Allocates Memory for new cLL Structure..
void *hkAllocateEntry(void)
{
if(InitPhase)
{
return false;
}
else
{

pHeap = RtlAllocateAlignedHeap(sizeof(CLLHook),MEMORY_ALLOCATION_ALIGNMENT);
PCLLHook pCLL = (PCLLHook)pHeap;
pCLL->pHeap = pHeap;

if(pCLL)
{
if(!InitPhase)
{
pFirstEntry = RtlInterlockedPushEntrySList(pListHead,&(pCLL->HookEntry));
InitPhase++;
}
pListEntry = RtlInterlockedPushEntrySList(pListHead,&(pCLL->HookEntry));
return (void*)pCLL;
}
return false;
}
}
__checkReturn bool hkSetTargetExeName(wchar_t *TargetProcName)
{
if(TargetProcName)
{
cProcessName = TargetProcName;
return true;
}
return false;
}
__checkReturn bool hkSetHookParams(__in_opt wchar_t* TargetName,__in LPVOID TargetAddr,__in LPVOID HookAddr,__in DWORD HkSz,__in BYTE Opcode,__inout CLLHook *HkStruct)
{
if(Opcode && HkSz && HookAddr && TargetAddr && InitPhase)
{
if(InitPhase == 1)
{
HkStruct->Opcode = Opcode;
HkStruct->hkTAddress = (DWORD)TargetAddr;
HkStruct->hkAddress = (DWORD)HookAddr;
HkStruct->hkSz = HkSz;
HkStruct->Index = InitPhase;
InitPhase++;
if(TargetName != 0)
{
hkSetTargetExeName(TargetName);
HkStruct->TProcessId = GetProcessId(UsrDrOpenProcess(cProcessName));
}
return true;
}
else
{
HkStruct->Opcode = Opcode;
HkStruct->hkTAddress = (DWORD)TargetAddr;
HkStruct->hkAddress = (DWORD)HookAddr;
HkStruct->hkSz = HkSz;
HkStruct->Index = InitPhase;
InitPhase++;
if(TargetName != 0)
{
hkSetTargetExeName(TargetName);
HkStruct->TProcessId = GetProcessId(UsrDrOpenProcess(cProcessName));
}
return true;
}
}
return false;
}
bool hkInstallHook(PCLLHook HkTempPar,HK_ROUTINE HkType)
{
BYTE ByteArray[8] = {0};
BYTE HookArray[8] = {0};
if(InitPhase >= 2)
{
PVOID pHolder = 0;
ULONG ProtectSize = 0;
DWORD oProt = 0;
MEMORY_BASIC_INFORMATION mbi = {0};
struct CLLHook HkParams = *(CLLHook*)HkTempPar;
if(NT_SUCCESS(NtQueryVirtualMemory(NtCurrentProcess(),(PVOID)HkParams.hkTAddress,0,&mbi,sizeof(mbi),0)))
{
if(mbi.AllocationProtect & PAGE_EXECUTE_READWRITE)
{
__asm jmp sswitch;
}
else
{
pHolder = (PVOID)HkParams.hkTAddress;
ProtectSize = HkParams.hkSz;
if(NT_SUCCESS(NtProtectVirtualMemory(NtCurrentProcess(),&pHolder,&ProtectSize,PAGE_EXECUTE_READWRITE,&oProt)))
{
__asm jmp sswitch;
}
else
{
return false;
}
}
}
return false;
sswitch:
switch (HkType)
{
case HKMOV:
__asm
{
pushad
lea eax,HkParams
lea edi,ByteArray
mov esi,[eax]HkParams.hkTAddress
mov ecx,0x5
push esi
rep movsb
pop esi
push eax
mov al,byte ptr [eax]HkParams.Opcode
mov byte ptr [esi],al
pop eax
mov ebx,[eax]HkParams.hkAddress
add esi,5
sub ebx,esi
sub esi,4
mov dword ptr [esi],ebx
popad
}
return true;
case HKCMPXCHG:
__asm
{
pushad
lea eax,HkParams
lea edi,ByteArray//destination
mov esi,[eax]HkParams.hkTAddress//Source
mov ecx,0x5
push edi
push esi
rep movsb
pop esi
pop edi
lea ecx,HookArray
push eax
mov al,byte ptr [eax]HkParams.Opcode
mov byte ptr [esi],al
pop eax
mov ebx,[eax]HkParams.hkAddress
add esi,5
sub ebx,esi
sub esi,4
mov dword ptr [ecx],ebx
mov ebx,dword ptr [ecx]
mov eax,[edi]
mxchg:
lock cmpxchg dword ptr [esi],ebx
jne mxchg
popad
}
return true;
default:
break;
}
}
return false;
}
__checkReturn bool hkDeleteNode(int Index)
{
PCLLHook pCLL = (PCLLHook)pFirstEntry;
if(pCLL->Index == Index) //case 1 corpse = Head
{
RtlFreeHeap(hHeap,0,pCLL->pHeap);
return true;
}
else
{

while(pCLL->HookEntry.Next)
{
pCLL = (PCLLHook)pFirstEntry->Next;
if(pCLL->Index == Index)
{
RtlFreeHeap(hHeap,0,pCLL->pHeap);
return true;
}
}
return false;
}
}
}
}


kind regards BanMe

disavowed
May 20th, 2009, 00:02
doesn't look thread-safe
you may want to consider using this set of functions: http://msdn.microsoft.com/en-us/library/ms684121(VS.85).aspx

BanMe
May 20th, 2009, 21:17
please see first post..

Maximus
May 21st, 2009, 02:36
use cmpxchg8b to obtain atomicity when writing/removing your hook
(edit: while obvious, bettr add that you should use the lock prefix and use a fixed ollydbg to debug it)

BanMe
May 21st, 2009, 11:02
yes the actual testing phase is up and coming..today perhaps.. but what you are suggesting would only work for hooking locally.. this although a good idea and definitly a method of hooking "locally" i was unfamiliar with(thankx for that btw) and i may implement it with regards to hotpatching to implement global hooks that can be used with disgressionary tactics to only target certain processes..is not the original goal of this piece..but I think does deserve some incorparation and some looking into..although my ultimate goal with this is to rely on a Mapped shared section to have reliably constant Hook addresses to write our hook code with, this can and should be a logical step before I implement that part..

but what i was asking is if this looks any more thread safe then the original above ....(and i know the hkInstallhook routine has issues. currently i am working out those issues and putting together the tests..)

Regards BanMe

Maximus
May 21st, 2009, 13:29
The only way to do a (seriously) thread-safe change *outside* the local context is to stop all the thread of the process, do the hook and then restart them (which is what a debugger usually do). So, you have to rewrite the engine to be a debugger-alike, not exactly trivial (neither that hard to do, honestly).

BanMe
May 23rd, 2009, 21:17
Do you suggest NtSuspendProcess() as the method of suspending all the threads in the process,
or should I Enumerate the threads and use NtOpenThread(),NtSuspendThread(),Write my hooks and then resume the Thread.oh and btw it is trivial when the solution you mention comes down to only a few API's

If there is another way of suspending all the threads in a target process that I didnt mention above and that is reliable I would be highly interested in that piece of work..

Regards BanMe

BanMe
May 27th, 2009, 17:00
*bumpty* bump bump..

disavowed
May 27th, 2009, 22:57
you have to enumerate them until you confirm that all the threads in the process (other than your thread) are suspended, and you need to enumerate them at least twice to confirm this. consider the following race condition:

1. you enumerate the threads and see 2 threads: your thread (A) and another thread (B)
2. you call suspendthread(B)
3. before suspendthread is executed, a context switch happens to thread B
4. thread B launches a new thread (C)
5. context switch back to A, causing suspendthread to be called on B

you still have C running.

as such, keep iterating until you're sure no other threads were created while you were suspending the rest of them.

BanMe
May 28th, 2009, 12:40
this is a valid point if the hooks are being placed after BaseProcessStartThunk has executed.. but if the hooking is done in the hook of BaseProcessStartThunk,I dont think i have to worry about other threads :]

disavowed
May 28th, 2009, 23:00
good point

Kayaker
May 29th, 2009, 00:51
Quote:
[Originally Posted by BanMe;80797]if the hooking is done in the hook of BaseProcessStartThunk,I dont think i have to worry about other threads :]


That seems reasonable. If you need to be concerned about TLS callbacks however, they would have already executed by that point and would have to be hooked at an earlier point in time.

Just thunking out loud here... is it possible for a TLS callback to create a thread which say creates the main app/dialog box? BaseProcessStartThunk might still be called as normal, but do nothing except enter a empty message loop. The main app will be already be running in a thread you never had the chance to hook if you were keying in only on BaseProcessStartThunk.

BanMe
May 30th, 2009, 03:30
I could go lower or on equal grounds by hooking BaseThreadStartThunk,CsrNewThread,LdrInitializeProcess,LdrInitializeThread,
LdrInitializeTls,NtRequestWaitReplyPort..(as you suiggested) ,LdrLoadDll..
So on and so forth..Prolly a whole host of other user API's and kernel api's that could accomplish this this task..But the method that I'm contemplating using is to bypass WFP for ntdll entrypoint specifically for DLL_PROCESS_ATTACH and DLL_THREAD_ATTACH..This technique can then also be combined with a Hardware Data Breakpoint overwrite on the CsrApiPort in order to redirect it(similar to kayaker's idea)..Another bypass for csrss_walker by EP_X0ff could come of this,by using EliCz idea for a native subsystem service and nyneave's build on EliCz old idea for DllMainhooks and exploratory investigation of KernelMode to User Mode Callbacks :LdrInitializeThunk as reference's..No POC's yet,its just theory...this would work for TLS callbacks that do Create Thread(s) though im not sure if it will work for ones that dont at the moment..The more I look and think about this the more I think its going to have to be a multilayer solution that combines ideas from many people and some(if not all)of them built on from different origins of attack while still accomplishing what they pioneered..Please note that I highly respect all who post there idea's, thought's and in alot of case's Code and if forget to reference something properly please forgive me and send me a PM..I will promptly change/update the reference without question...I'm also thinking of making this project Open Source in order to hopefully gain the benefits of having a community developed project. Any thoughts on this?


http://www.nynaeve.net/?p=205

http://www.apihooks.com/EliCZ/export.htm

http://www.rootkit.com

Kayaker's post was in reply to creating a Process/Thread Profiler on this forum...

[side note:]this also caught my interest...
http://www.woodmann.com/forum/showthread.php?t=11545

regards BanMe

p.s. for the source for this and other things please see my blog the client source should be up with the week of 06-22 :]