A forum for reverse engineering, OS internals and malware analysis 

Ask your beginner questions here.
 #17004  by Dmitry Varshavsky
 Tue Dec 04, 2012 12:19 am
Okay, here is my approach:

1. Find non-exported nt!ExWorkerQueue. On pre-Win8 operating systems it could be easily done with static analysis ( or dynamic, if you have a suitable engine to do so ):
Code: Select all
//2000:
//--------------------------------------------------------------------------
//nt!ExQueueWorkItem:
//...
//80419040 6bc03c          imul    eax,eax,3Ch
//80419043 0520dc4680      add     eax,offset nt!ExWorkerQueue (8046dc20)
//80419048 50              push    eax
//80419049 e8575f0100      call    nt!KeInsertQueue (8042efa5)
//
//XP:
//--------------------------------------------------------------------------
//...
//804e4173 6bf63c          imul    esi,esi,3Ch
//804e4176 81c6c0225680    add     esi,offset nt!ExWorkerQueue (805622c0)
//804e417c 56              push    esi
//804e417d e8abffffff      call    nt!KeInsertQueue (804e412d)
//
//
//2003:
//--------------------------------------------------------------------------
//...
//80828d6c 6bf63c          imul    esi,esi,3Ch
//80828d6f 57              push    edi
//80828d70 81c660058b80    add     esi,offset nt!ExWorkerQueue (808b0560)
//80828d76 56              push    esi
//80828d77 e846000000      call    nt!KeInsertQueue (80828dc2)
//
//
//Vista SP2:
//--------------------------------------------------------------------------
//...
//e1c77184 6bf63c          imul    esi,esi,3Ch
//e1c77187 50              push    eax
//e1c77188 81c6c078d0e1    add     esi,offset nt!ExWorkerQueue (e1d078c0)
//e1c7718e 56              push    esi
//e1c7718f e8a3540000      call    nt!KeInsertQueue (e1c7c637)
//
//7:
//--------------------------------------------------------------------------
//...
//82877f40 6bf63c          imul    esi,esi,3Ch
//82877f43 57              push    edi
//82877f44 81c6406d9482    add     esi,offset nt!ExWorkerQueue (82946d40)
//82877f4a 50              push    eax
//82877f4b 8bc6            mov     eax,esi
//82877f4d e832000000      call    nt!KiInsertQueue (82877f84)
Win8 implementation is slightly different, you can obtain the sought-for pointer from KPRCB -> KNODE ( pls note, for each NODE head is different )
Code: Select all
//mov eax, dword ptr fs:[0x20]
//mov eax, dword ptr [ eax + 0x4CC ]
//add eax, 0xC0
2. Now you can traverse the ExWorkerQueue:
Code: Select all

void ProcessWorkItemQueues ( IN PWORKITEM_LIST_ENTRY WorkItemListHead )
{
    ULONG          Type;
    
    for ( Type = CriticalWorkQueue; Type < MaximumWorkQueue; Type++ )
    {
        ProcessWorkItemQueueInternal ( WorkItemListHead, (PKQUEUE)( (ULONG_PTR)g_ExWorkerQueue + Type * 0x3C /* sizeof ( EX_WORK_QUEUE ) */ ), Type );
    }
}

/// This function should be executed on each core
/// Please excuse my _asm inline, I really don't like __readfsdword intrinsic :D 
void ProcessWorkItemQueuesWin8 ( IN PWORKITEM_LIST_ENTRY WorkItemListHead )
{
    ULONG     Type;
    ULONG_PTR WorkerQueueHead;

    __asm
    {
        mov eax, dword ptr fs:[0x20]
        mov eax, dword ptr [ eax + 0x4CC ]
        add eax, 0xC0
        mov WorkerQueueHead, eax
    }

    for ( Type = CriticalWorkQueue; Type < MaximumWorkQueue; Type++ )
    {
        ProcessWorkItemQueueInternal ( WorkItemListHead, (PKQUEUE)( WorkerQueueHead + Type * 0x38 /* sizeof ( EX_WORK_QUEUE ) - Win8 */ ), Type );
    }
}
3. Usually all of worker queues are empty. That happens because all of worker threads are waiting on KQUEUE->Header ( which is DISPATCHER_HEADER ). nt!KiInsertQueue removes first waiting thread and schedule it for the execution. In the very rare case, when all of worker threads are busy, queue is put in signaled state and the new entry is being put to head or tail of the list. When such happens, system starts additional dynamic worker threads to serve the requests. Dynamic threads terminates after some idle time, while the static worker threads terminates only when shutdown occur.

Part of ProcessWorkItemQueueInternal routine:
Code: Select all
void ProcessWorkItemQueueInternal ( IN PWORKITEM_LIST_ENTRY WorkItemListHead, IN PKQUEUE Queue, IN ULONG QueueType )
{
    PLIST_ENTRY ListEntry;
    for ( ListEntry = Queue -> EntryListHead.Flink;
          ListEntry != &Queue -> EntryListHead;
          ListEntry = ListEntry -> Flink )
    {
        AddWorkItem ( WorkItemListHead, QueueType, (ULONG_PTR)((PWORK_QUEUE_ITEM)ListEntry) -> WorkerRoutine, FALSE, 0 /* Not Assigned Yet */, 0, 0 );
    }
    for ( ListEntry = Queue -> ThreadListHead.Flink;
          ListEntry != &Queue -> ThreadListHead;
          ListEntry = ListEntry -> Flink )
    {
        ULONG_PTR Thread = (ULONG_PTR)ListEntry - g_OSDependentOffsets._KTHREAD_QueueListEntry;
        if ( *(PUCHAR)( (ULONG_PTR)Thread + g_OSDependentOffsets._KTHREAD_KernelStackResident ) & KERNEL_STACK_RESIDENT_BIT  )
        {
               /// Some stack backtrace is performed here, sorry, i can't share this piece of code - pls use your brain
                AddWorkItem ( WorkItemListHead, QueueType, WorkerRoutine, ..., Thread,
                              *(PUCHAR)( Thread + g_OSDependentOffsets._KTHREAD_State ), *(PUCHAR)( Thread + g_OSDependentOffsets._KTHREAD_WaitReason ) );
        } else
        {
            /// Thread is not currently active
            AddWorkItem ( WorkItemListHead, QueueType, 0, FALSE, Thread,
                          *(PUCHAR)( Thread + g_OSDependentOffsets._KTHREAD_State ), *(PUCHAR)( Thread + g_OSDependentOffsets._KTHREAD_WaitReason ) );
        }
    }
}
And this is how it looks to the user ( pls see the attachment ).

You may notice, that all of routines are not active. That means that you won't get any of those by your "schedule APC and RtlCaptureStackBackTrace" method because usually work items are pretty small and executes extremely quick. Your APC simple interrupts worker thread waiting on new work items and actually you're getting nothing interested.
However, if we talk about some active rootkits, they usually don't return the execution from the worker routine - they perform an infinite loop inside. In this case, APC and RtlCaptureStackBackTrace will work, but then you don't need to know anything about work items internals. Schedule your APC to all system threads to see if anything is executing from pool memory or hidden module..

Tigzy, I gave you some hints. I respect your efforts, but honestly, your questions and attempts are so lame sometimes... You shouldn't "develop" anything ( especially in kernel-mode ) without deep understanding of what you're doing.
Attachments
KModeWorkItems.png
KModeWorkItems.png (50.78 KiB) Viewed 336 times
 #17006  by EP_X0FF
 Tue Dec 04, 2012 3:44 am
@Tigzy

What is the point of using this WorkerThreads method for detection of TDL3/4? OK, theoretically you listed all workitems and wow we have identified some workitem with worker routine pointed outisde visible drivers. Is it must to be TDL? No. Please don't tell me you want do signatures/heuristics for kernel memory scan.

Who will use this? Casual user? So you will tell him:

"Hey bro, go to this page and check if the any suspicious WorkerItems listed here, see ETHREAD values and WorkerRoutine addresses"?
"Worker-what?" - he will ask you.

Specialists?

"Hey bro, go to this page and check if the any suspicious WorkerItems listed here, see ETHREAD values and WorkerRoutine addresses"?
"Thanks, it BSODed, but I already did this with WinDBG/Volatility"

Moved.
 #17007  by Tigzy
 Tue Dec 04, 2012 6:49 am
What is the point of using this WorkerThreads method for detection of TDL3/4? OK, theoretically you listed all workitems and wow we have identified some workitem with worker routine pointed outisde visible drivers. Is it must to be TDL? No. Please don't tell me you want do signatures/heuristics for kernel memory scan.
This is NOT for detection.
TDL4 is having a worker thread that poll over the MBR to see if it has changed, and rewrites it.
My TDL4 detection is working fine, but due to this routine I can't rewrite the MBR. So what I want to do is, once TDL4 detected, and when the user attempts to trigger "fix MBR", search for a worker thread that matches something suspicious, and suspend it. Then Rewrite the MBR and reboot.

I don't find any "Newbie question" out there EP. Why moved?

PS: Take it easy... We're just discussing no?
 #17008  by Tigzy
 Tue Dec 04, 2012 7:30 am
It seems to better work when trying on TDL4
00000005 0.00032797 Idle [0]: 1 threads
00000006 0.00033831 System [4]: 55 threads
00000007 0.00037938 [0][5][-][0x821c8788] TID : 8 - 0x80685628 - 0x80685628 (\WINDOWS\system32\ntkrnlpa.exe)
00000008 0.00040648 [1][5][W][0x821c5020] TID : 16 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000009 0.00043246 [2][5][W][0x821c5da8] TID : 20 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000010 0.00045816 [3][5][W][0x821c5b30] TID : 24 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000011 0.00048442 [4][5][W][0x821c58b8] TID : 28 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000012 0.00050984 [5][5][W][0x821c5640] TID : 32 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000013 0.00053582 [6][2][W][0x821c53c8] TID : 36 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000014 0.00056124 [7][5][W][0x821c4020] TID : 40 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000015 0.00058555 [8][5][W][0x821c4da8] TID : 44 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000016 0.00061823 [9][5][W][0x821c4b30] TID : 48 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000017 0.00064310 [10][5][W][0x821c48b8] TID : 52 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000018 0.00066824 [11][5][W][0x821c4640] TID : 56 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000019 0.00069310 [12][5][W][0x821c43c8] TID : 60 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000020 0.00071797 [13][5][W][0x821c3020] TID : 64 - 0x80534b02 - 0x80534b02 (\WINDOWS\system32\ntkrnlpa.exe)
00000021 0.00074339 [14][5][-][0x821c3da8] TID : 68 - 0x8060a9cc - 0x8060a9cc (\WINDOWS\system32\ntkrnlpa.exe)
00000022 0.00076825 [15][5][-][0x821c2560] TID : 72 - 0x80509590 - 0x80509590 (\WINDOWS\system32\ntkrnlpa.exe)
00000023 0.00079312 [16][5][-][0x821c22e8] TID : 76 - 0x8064426e - 0x8064426e (\WINDOWS\system32\ntkrnlpa.exe)
00000024 0.00081826 [17][5][-][0x821bf020] TID : 80 - 0x8053c210 - 0x8053c210 (\WINDOWS\system32\ntkrnlpa.exe)
00000025 0.00084312 [18][5][-][0x821bfda8] TID : 84 - 0x8053c506 - 0x8053c506 (\WINDOWS\system32\ntkrnlpa.exe)
00000026 0.00086827 [19][5][W][0x821bf310] TID : 88 - 0x804ecedc - 0x804ecedc (\WINDOWS\system32\ntkrnlpa.exe)
00000027 0.00089313 [20][5][W][0x821ec020] TID : 92 - 0x804ecedc - 0x804ecedc (\WINDOWS\system32\ntkrnlpa.exe)
00000028 0.00091827 [21][5][-][0x821ac6a0] TID : 96 - 0xf857ab10 - 0xf857ab10 (ACPI.sys)
00000029 0.00094006 [22][5][-][0x821c9768] TID : 100 - 0x8050b928 - 0x8050b928 (\WINDOWS\system32\ntkrnlpa.exe)
00000039 0.00118954 7 frames captured
00000040 0.00121691 [0x821c48b8][0] 0xf8c32927 (\??\C:\Documents and Settings\tigzy\Bureau\mydriver.sys)
00000041 0.00124122 [0x821c48b8][1] 0x804fdae1 (\WINDOWS\system32\ntkrnlpa.exe)
00000042 0.00126524 [0x821c48b8][2] 0x80500cf4 (\WINDOWS\system32\ntkrnlpa.exe)
00000043 0.00128955 [0x821c48b8][3] 0x817fe7fa (Unknown) => TDL4 address space
00000044 0.00131385 [0x821c48b8][4] 0x80534c02 (\WINDOWS\system32\ntkrnlpa.exe)
00000045 0.00133788 [0x821c48b8][5] 0x805c6160 (\WINDOWS\system32\ntkrnlpa.exe)
00000046 0.00136190 [0x821c48b8][6] 0x80541dd2 (\WINDOWS\system32\ntkrnlpa.exe)
Now, how to determine which address is a potential entrypoint? It should be the number 3, but I see nothing that can help, accept the fact the module is unknown...
Maybe I should parse every frame to find an unknown module?

@Dmitry: Thanks for your approach, I'll have a look
 #17010  by EP_X0FF
 Tue Dec 04, 2012 9:03 am
Tigzy wrote:Why moved?
Because of the following:

1. There is no reverse real or virtual in this thread. Reverse is not about copy-pasting and not looking for solution based on Windows opensource.
2. There is no actual code, not user mode not kernel mode. Only pseudocode snippets and meaningless screenshots of something.
3. There are tons of basic questions you can answer yourself spending five minutes in WRK/MSDN.

However this topic is too specific to move it into General Discussion.

To paraphrase your initial question - you want to know how to bypass TDL MBR overwrite watchdog which is based on WorkItems. Simple isn't it? Much more simple and clear than picasso style screenshots with messed WinDBG, chinese bsod generator and DbgView.
 #17062  by Tigzy
 Fri Dec 07, 2012 7:10 am
You shouldn't "develop" anything ( especially in kernel-mode ) without deep understanding of what you're doing.
I don't know how you do. But me, I learn by coding...
I don't code what I don't know, I try to learn and then I code. I really dislike take code and build it without knowing what it does.
Here I'm not querying code snippets, but informations. As EP said, this is not coding, neither reverse, just noob questions.
Sometimes you're tough guys... This is a forum, who cares about BSoD its test machine?

This thread is solved, many thanks to all, especially to xdeadcode who gave me hints about the how and the why.

EDIT: See ya next time for the next noob question...