从内核模式启动进程

Linux内核-进程 进程的目的就是担当分配资源的实体,一个进程又几个用户线程程。每个线程代表一个执行流。线程的实现方法有三种:1、用户级线程;2、内核级线程;3、混合线程模型。Linux内核中的进程和线程都用相同的数据结构task_struct表示;线程是特殊的进程,共享同一地址空间、共同合作。        进程描述符都是task_struct,包含了与进程相关的所有信息。一般来说能被调度的每个执行上下文都必须拥 阅读详情
 从内核模式启动进程


我想很多人都会有这样的问题,能否从内核驱动中启动用户应用程序呢?在Win9x(win95, 98, ME)中有专门的导出函数ShellVxd_ShellExecute,此函数能够启动应用程序。而在NT系统中没有专门的导出函数。

单数不存在导出函数决不意味这种想法不可能实现。恰恰相反,这种想法是完全可能实现的。当然,您可能会觉得这有如恶梦一般,分析一大堆系统对象,仔细的研究它们的属性与方法等等。不用紧张,事实上要简单得多。来,让我们放松一下,并用几种非标准的观点看一下这个问题。

我们先推开现实不管。仔细的查看模块ntoskrnl.exe的导出函数,我们没有找到能直接赋予新进程生命的函数。DDK对此也保持沉默。但是我们知道,进程是由用户模块kernel32.dll的导出函数kernel32.dll:CreateProcessA, CreateProcessW, CreateProcessInternalA, CreateProcessInternalW创建的,对于这些函数的描述以及参数可以很容易的在PSDK的文档中找到。当然,绕过kernel32.dll,尝试自己模拟这些函数是可以成功的。对这种方法的讲解和相应的代码可以在Garry Nebetta的WinNT Native Api指南中找到。实际上这也没有什么不可思议的。进程仍然从用户模式启动,只不过是直接调用了ntdll.dll模块里的导出函数。但是我们的问题依然没有答案,内核中的哪些东东能实现同样的效果呢?

其实,我所给出的方法也没有显出什么太古怪的地方来。我们不会自己构造内核对象并靠自己的力量和智慧来模拟出相应的内核行为和机制。我们知道,没有人能比操作系统做得更好,而且内核恐怕也不会对我们的“自己动手”表示赞赏。但是,我们还是要在内核模式驱动中把应用程序启动起来,只是……是在用户模式下。下面我们就来开始吧。



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

方法很简单。现在我对机制进行一点解释,然后继续对内核代码进行分析。主要的思想就是,要“抓住”某个进程的用户线程,并将其与我们自己的条件“捆绑”,也就是说修改执行的代码并让它转向到预先准备好的用户地址空间中的代码中,这段代码调用函数kernel32.dll:CreateProcessA。

怎样才能做到呢?首先需要找到线程从内核模式转向用户模式的地方。进程是如何从用户模式切换到内核模式再切换回来的呢?这是通过中断门/快速系统调用接口进行的。对于本例,经过思考,我们没有找到比INT 0x2E/SYSENTER更好的地方,而正是在这里线程改变了自己的优先级。

当然,如果有其它类似的服务,比如调试用的int 0x2d,或是timer int 0x2a。但是我们需要稳定的方法,对大多数进程能够使用的,可以频繁使用的方法。这样,在一番寻找之后,最好的候选方法就是系统调用门接口INT 0x2E/SYSENTER。我们如何才能使用这个后门呢?无疑需要知道,在中断调用时会发生向段寄存器重新加载事先保存在TSS中的选择子,主要是CS和SS。在通过门到达内核后,在堆栈中保存着中断的返回地址。使用指令sysenter(winXP+)的情况类似。这个地址是我们所需要的,而取得它完全不是问题。之后,我们必需拦截函数_KiSystemService,向其中插入我们处理程序的代码。问题又来了,我们如何找到_KiSystemService函数呢?

有一点是显然的,中断向量INT 0x2E指向这个函数。但是,在WinXP+中情况并非只此一种。由于最新处理器的新特性,新版本的系统都使用了快速系统调用指令SYSENTER/SYSEXIT。SYSENTER指令与INT指令类似,但有一点区别,就是它调用的代码的地址位于MSR寄存器中的某一个里。为什么这种情况下我们需要多余的查找呢?另外,我们还知道,MSR的内容是经常变化的。尽管如此,IDT表中的中断向量int 0x2e不管怎样都还是指向_KiSystemService的起始代码。尽管int 0x2e没有被使用,但找到它显然不是问题。

害怕失望——您错了。拦截掉INT 0x2E会怎样呢?例如,ntice就好干这个。在代码调试时我们没有可能“亲眼”见到这个函数。除此之外,在ntoskrnl.exe的导出中,此函数也没有被提及。但是,还不是太坏。我们来想一想,如何能找到它。扫描全部内存查找含有_KiSystemService函数的一组指令没有什么意义。我们这里的解决方案十分简单。我们知道,_KiSystemService是转向内核函数的入口,所以在函数的内部有直接调用内核服务的代码。我们来看下面的代码。

    0008:804DA113    5A        POP    EDX
    0008:804DA114    FF0538F6DFFF    INC    DWORD PTR [FFDFF638]
    0008:804DA11A    8BF2        MOV    ESI,EDX
    0008:804DA11C    8B5F0C        MOV    EBX,[EDI+0C]
    0008:804DA11F    33C9        XOR    ECX,ECX
    0008:804DA121    8A0C18        MOV    CL,[EBX+EAX]
    0008:804DA124    8B3F        MOV    EDI,[EDI]
    0008:804DA126    8B1C87        MOV    EBX,[EAX*4+EDI]
    0008:804DA129    2BE1        SUB    ESP,ECX
    0008:804DA12B    C1E902        SHR    ECX,02
    0008:804DA12E    8BFC        MOV    EDI,ESP
    0008:804DA130    3B35D4C75480    CMP    ESI,[ntoskrnl!MmUserProbeAddress]
    0008:804DA136    0F83A1010000    JAE    804DA2DD
    0008:804DA13C    F3A5        REPZ    MOVSD
    0008:804DA13E    FFD3        CALL    EBX
    0008:804DA140    8BE5        MOV    ESP,EBP

我们看到了_KiSystemService的一段代码,指令CALL EBX调用了某个内核函数,这个函数是预先从服务表中选出的,然后在EBX寄存器中加载这个函数的地址(详细过程可以阅读我前一篇文章《跟踪Native Api函数调用》)。

函数_KiSystemService并没有在ntoskrnl.exe的导出表中,但是我们有指向表KeServiceDescriptorTable的指针,从这个表中可以取出所要的Native Api函数的地址,比如说,NtReadFile。接下来,函数将在发出CALL EBX指令的进程上下文中调用并返回至_KiSystemService的下一条指令。

前一篇文章中有个问题涉及到了拦截NativeApi函数,所用的方法是将自己的处理程序添加到服务表中替换原来的函数,然后依次调用这两个函数。这里使用的也是这种办法。比如,我们拦截了NtReadFile这个最常调用的函数,然后,在我们的处理程序内部的堆栈中有函数返回到_KiSystemService的地址。以下是演示代码:

    DWORD  FindKiSystemServiceOriginalEntryPoint()
    {
      KPRIORITY CurrentThreadPriority;
      // 拦截 NtReadFile这个最常调用的函数
      CurrentThreadPriority = KeQueryPriorityThread(KeGetCurrentThread());
      
      // so that the system not degraded        
      KeSetPriorityThread(KeGetCurrentThread(),1);   


      if( *NtBuildNumber == 2195 ) OriginalNtReadFile = *KeServiceDescriptorTable->NtoskrnlTable.ServiceTable[_NtReadFile_2195];
      if( *NtBuildNumber == 2600 ) OriginalNtReadFile = *KeServiceDescriptorTable->NtoskrnlTable.ServiceTable[_NtReadFile_2600];

      disableinterruptions
      clearwp

      if( *NtBuildNumber == 2195 ) KeServiceDescriptorTable->NtoskrnlTable.ServiceTable[_NtReadFile_2195] = ArtificialNtReadFile;
      if( *NtBuildNumber == 2600 ) KeServiceDescriptorTable->NtoskrnlTable.ServiceTable[_NtReadFile_2600] = ArtificialNtReadFile;

      restorewp
      enableinterruptions

      while( !FindingForWhile );
// wait, NtReadFile will be called, надеюсь не вечно :)

// 相似的, 地址被找到, 取下ArtificialNtReadFile()处理程序
      disableinterruptions
      clearwp

      if( *NtBuildNumber == 2195 )
KeServiceDescriptorTable->NtoskrnlTable.ServiceTable[_NtReadFile_2195]  =
*OriginalNtReadFile;
      if( *NtBuildNumber == 2600 )
KeServiceDescriptorTable->NtoskrnlTable.ServiceTable[_NtReadFile_2600]  =
*OriginalNtReadFile;

      restorewp
      enableinterruptions

      KeSetPriorityThread(KeGetCurrentThread(),CurrentThreadPriority);
// 恢复原先状态
// 现在计算KiSystemService原来的入口点
      return NtReadFileReturnAddress - 0xC4;
// KiSystemService内部入口点与返回点的差
    }

NtReadFile的处理程序:

    __declspec(naked) ArtificialNtReadFile()
    {  __asm
      {
        push dword ptr [esp]             // !!!
        pop NtReadFileReturnAddress
        inc dword ptr FindingForWhile    // 找到所需函数的地址
        jmp dword ptr OriginalNtReadFile
      }
    }

这段代码的含义您可能已经猜到了。对不同版本Win NT的_KiSystemService函数的分析显示,除了WinXPSP2和2K3,在所有NT内核中这个函数都是相同的(为了保证兼容性,在驱动的代码中有某些不大的改动)。这些都可以利用起来。我想现在大家都会明白,如果我们找到了某个内部函数的地址,而函数又有确定的大小,就不难找到它的startup。call ebx后面一条指令的地址我们可在处理程序的堆栈中找到,而从这条指令到startup间的距离是0xC4字节(Win XP, 2K)。现在_KiSystemService的起始地址就不难计算了。

// такова разница м/д точкой входа и точкой возврата внутри KiSystemService
return NtReadFileReturnAddress - 0xC4;

由此我们就得到了_KiSystemService的原始入口点。

这样,第一步就走完了,我们继续向下进行。下面向实现目标迈出的这一大步就应该是找到放置调用kernel32:CreateProcessA的代码的位置。自然,我们可以将代码数据只放在用户地址空间中,而且是放在大多数进程都能访问到的地方。对此没什么太好的办法,我们所能做的就是利用修正后的kernel32.dll模块第一页中的空闲空间,这块空间紧挨着首部。在被加载到内存并进行修正后,从0x1000字节PE首部占据了某些地方的0x400字节,剩余的地方都由0填充。这些地方完全可以为我们所用。但是,这还是个次要的问题,主要的问题是要在内核模式下找到kernel32.dll的基地址。

这个问题也并不困难。实际上,我们所需要的一切都能在进程PEB指向的结构体中找到。有些结构体的描述可以在Свен Шрайбер的那本关于undocunmented win nt的书中找到。有个结构体指向了进程加载的模块的列表,但是我没有在此书中找到具体的对此结构体的描述,所以只好祭起调试器来小小的挖掘一下。看过PEB,我们立即被pPeb->ProcessModuleInfo->ModuleHeader.List3域所吸引。书中所提到的正是这个成员(List3)。经过试验我明白了,这个ListEntry类型的结构体大概是这个样子的(与MS的源码可能会有区别):

    typedef struct  _KMODULEINFOLISTENTRY {
      DWORD  Flink;
      DWORD  Blink;
      DWORD  ModuleIBase;
      DWORD  DllEntryPoint;
      DWORD  Unknown2;
      DWORD  Unknown3;
      PWCHAR ModuleName;
    } KMODULEINFOLISTENTRY, *PKMODULEINFOLISTENTRY, **PPKMODULEINFOLISTENTRY;

某些域对于我来说还是个迷,实际上我们对这些域并不感兴趣,其余的我想也不需要解释了。这样,使用这个结构体,我们就可以用
DWORD kwsGetModule(PWCHAR ModuleNameW)函数找到kernel32.dll的基地址,此函数可参见驱动源代码。实际上,这个函数可以用来查找进程中的各个模块。

现在我们回到查找最适宜放置调用kernel32:CreateProcessA代码的位置这个问题上来。我们前边提到,代码将位于kernel32.dll模块首部后紧接着的第一页中。这样我们一箭双雕:一是嵌入这个位置中的代码能被所有的进程看到,二是我们有足够的空间来放置植入的CreateProcessA调用代码。别的倒也没什么,只是模块的PE的第一页在用户空间中是只读的,属性为readonly。为了详细的了解此问题,我们来打开PSDK文档并对CreateProcess函数作更细致的研究:

    BOOL CreateProcess(
      LPCTSTR lpApplicationName,                 // name of executable module
      LPTSTR lpCommandLine,                      // command line string
      LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
      LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD
      BOOL bInheritHandles,                      // handle inheritance option
      DWORD dwCreationFlags,                     // creation flags
      LPVOID lpEnvironment,                      // new environment block
      LPCTSTR lpCurrentDirectory,                // current directory name
      LPSTARTUPINFO lpStartupInfo,               // startup information
      LPPROCESS_INFORMATION lpProcessInformation // process information
    );

    typedef struct _STARTUPINFO {
      DWORD   cb;
      LPTSTR  lpReserved;
      LPTSTR  lpDesktop;
      LPTSTR  lpTitle;
      DWORD   dwX;
      DWORD   dwY;
      DWORD   dwXSize;
      DWORD   dwYSize;
      DWORD   dwXCountChars;
      DWORD   dwYCountChars;
      DWORD   dwFillAttribute;
      DWORD   dwFlags;
      WORD    wShowWindow;
      WORD    cbReserved2;
      LPBYTE  lpReserved2;
      HANDLE  hStdInput;
      HANDLE  hStdOutput;
      HANDLE  hStdError;
    } STARTUPINFO, *LPSTARTUPINFO;

    typedef struct _PROCESS_INFORMATION {
      HANDLE hProcess;
      HANDLE hThread;
      DWORD  dwProcessId;
      DWORD  dwThreadId;
    } PROCESS_INFORMATION;

我们看到,函数在堆栈中接收10个双字,多数都是指针。其中某些并不是必需的,可以传递NULL,而例如lpStartupInfo,lpProcessInformation和ProgExeNameAddrinUser则是必需要确定的(关于调用权限和传递参数更详细的信息我这里就不给出了,对此可参阅MSDN)。相应的需要为这些参数分配空间。但这只是问题的一部分。

问题的另一部分在于,除了要为结构体提供空间之外,CreateProcess还应该向它们的域进行写入。我们这里不能出现这种情况,因为我们的页是只读的,并且任何向其中写入的尝试都会引起异常,这个异常会关掉当前进程。为了消除在用户模式下向此页写入的可能性,我们应该修改该页PTE的属性。对此有两种办法:一是在内核模式下调用NtProtectVirtualMemory,这时应考虑到这个函数没有导出,需要找到SST表的入口点;二是直接找到该页的descriptor(PTE)并修正这个恼人的“错误”。我们实际上调用了专门为此而写的函数PageAccessProp(…),在堆栈中接收4个参数,这些参数我想就不必解释了。

除此之外,此函数还可以用在如下情况,比如必须在UserMode下操作系统内存页的时候。为此需要相应的翻转U位,但是如果内核使用的是4K字节的页,则在descriptor里还有一个G位。此位的意思是这一页是全局的,而且在任务切换或重置CR3时不应在TLB中更新或替换此PTE。也就是说,如果翻转U位,关掉G位,在用户模式下向高2G空间中写入,就会引起异常。一定要关掉CR4中的PGE位,然后进行类似的操作并向系统页进行写入。除此之外,不要忘了CR0中的WP位。从原理上讲会出现的问题到这里就讲完了。

接下来还有一个有趣的任务。我们必须找到导出函数kernel32:CreateProcessA。在我们的驱动中这项工作是由函数GetExportedFuncAddr(DWORD ModuleImageBase,PCHAR FuncName)来负责的,此函数的参数是模块在内存中的地址和所要找函数的名字。若找到,则返回其在内存中的基地址。代码我就不解释了,更详细的信息请参见PE的相关文档,同时还可以直接研究GetExportedFuncAddr。驱动中用到的所有结构体都位于struc.h中。一般来讲,在跟踪CreateImplant()函数时,前面讲到的各个部分会按顺序出现。

前面我们讨论了所有所需的辅助,相关的函数,并被引导到了神秘的目标面前。现在我们从大体上来依次研究驱动的算法。

驱动中最主要的函数就是ReplaceKiSystemServiceCode()。此函数首先用前面讲到的方法找到startup _KiSystemService,然后直接切入代码的核心来取得控制权。为此我们必须:第一,取得_KiSystemService返回时的返回点来分析地址并为后面替换此地址提供方便,而替换后的地址就指向我们植入kernel32.dll第一页中的代码;第二,处理程序将植入代码注入到用户模式内存中。总共会有两处拦截。

现在来更详细的看一下第一处拦截。我们来看代码:

    0008:804DA07C    6A00            PUSH    00
    0008:804DA07E    55            PUSH    EBP
    0008:804DA07F    53            PUSH    EBX
    0008:804DA080    56            PUSH    ESI
    0008:804DA081    57            PUSH    EDI
    0008:804DA082    0FA0            PUSH    FS
    0008:804DA084    BB30000000        MOV    EBX,00000030
    0008:804DA089    668EE3            MOV    FS,BX
    0008:804DA08C    FF3500F0DFFF        PUSH    DWORD PTR [FFDFF000]
    0008:804DA092    C70500F0DFFFFFFFFFFF    MOV    DWORD PTR [FFDFF000],FFFFFFFF
    0008:804DA09C    8B3524F1DFFF        MOV    ESI,[FFDFF124]
    0008:804DA0A2    FFB640010000        PUSH    DWORD PTR [ESI+00000140]
    0008:804DA0A8    83EC48            SUB    ESP,48

原始的 startup _KiSystemService.

我们直接在代码起始处注入第一个处理程序,方法就是splicing,保存原始地址,之后将其恢复。这样就得到了下面的代码:

    0008:804DA07C    FF25008578FC        JMP    [ArtificialKiSystemService]
    0008:804DA082    0FA0            PUSH    FS
    0008:804DA084    BB30000000        MOV    EBX,00000030
    0008:804DA089    668EE3            MOV    FS,BX
    0008:804DA08C    FF3500F0DFFF        PUSH    DWORD PTR [FFDFF000]
    0008:804DA092    C70500F0DFFFFFFFFFFF    MOV    DWORD PTR [FFDFF000],FFFFFFFF
    0008:804DA09C    8B3524F1DFFF        MOV    ESI,[FFDFF124]
    0008:804DA0A2    FFB640010000        PUSH    DWORD PTR [ESI+00000140]
    0008:804DA0A8    83EC48            SUB    ESP,48

下面是处理程序KiSystemServiceHandler(),这个函数的地址就保存在ArtificialKiSystemService里。

    __declspec(naked) KiSystemServiceHandler()
    {
      SaveKISSRetAddr // 保存由系统服务向用户模式返回的返回点
      _asm{
        OriginalKiSystemServiceInlineStartUpCode  // 恢复原始代码     
        push dword ptr [OriginalKiSystemService]
        add dword ptr [esp],OriginalKiSystemServiceStartUpCodeSize
        ret
      }
    }

我希望您能明白,为什么拦截被实现在startup代码的起始处。正是在这里我们能从线程堆栈中取出所需的地址而不用多费力气。但是我不主张在KiSystemServiceHandler()处理程序内部进行更为复杂的操作。这段代码是在关中断的情况下执行的,也就是说IRQL处于最高级,且除此之外fs也不要动。处理程序中不当的行为必然会引起系统崩溃。现在来看第二处拦截,代码如下:

    0008:804DA07C    FF25008578FC        JMP    [ArtificialKiSystemService]
    0008:804DA082    0FA0            PUSHFS
    0008:804DA084    BB30000000        MOV    EBX,00000030
    0008:804DA089    668EE3            MOV    FS,BX
    0008:804DA08C    FF3500F0DFFF        PUSH    DWORD PTR [FFDFF000]
    0008:804DA092    C70500F0DFFFFFFFFFFF    MOV    DWORD PTR [FFDFF000],FFFFFFFF
    0008:804DA09C    8B3524F1DFFF        MOV    ESI,[FFDFF124]
    0008:804DA0A2    FFB640010000        PUSH    DWORD PTR [ESI+00000140]
    0008:804DA0A8    83EC48            SUB    ESP,48
    0008:804DA0AB    8B5C246C        MOV    EBX,[ESP+6C]
    0008:804DA0AF    83E301            AND    EBX,01
    0008:804DA0B2    889E40010000        MOV    [ESI+00000140],BL
    0008:804DA0B8    8BEC            MOV    EBP,ESP
    0008:804DA0BA    8B9E34010000        MOV    EBX,[ESI+00000134]
    0008:804DA0C0    895D3C            MOV    [EBP+3C],EBX
    0008:804DA0C3    89AE34010000        MOV    [ESI+00000134],EBP
    0008:804DA0C9    FC            CLD
    0008:804DA0CA    F6462CFF        TEST    BYTE PTR [ESI+2C],FF
    0008:804DA0CE    0F85D6FEFFFF        JNZ    804D9FAA
    0008:804DA0D4    FB            STI    // 降IRQL
    0008:804DA0D5    FF25F08478FC        JMP    [ArtificialKiSystemServiceSafedCode]
    0008:804DA0DB    CC            INT    3
    0008:804DA0DC    CC            INT    3
    0008:804DA0DD    8BCF            MOV    ECX,EDI

现在我们来看处理程序KiSystemServiceHandler2(),其地址就在ArtificialKiSystemServiceSafedCode:

    __declspec(naked)KiSystemServiceHandler2()
    {
      __asm mov CanUnload,FALSE   // 设置不能卸载驱动的标志
      saveregisters         // 保护寄存器
      if (!isP)
      {
        isP++;              // 建立不可多次进入的标志
        CreateImplant();    // 注入植入代码
      }
      restoreregisters    // 恢复寄存器
      __asm
      {
        OriginalKiSystemServiceInLineSafedCode
        push dword ptr [KiSystemServiceSafedCode]
        add dword ptr [esp],OriginalAfterStiCodeSize
        mov CanUnload,TRUE       // 现在可以放心的卸载驱动了:)
        ret
      }
    }

在这个处理程序中我们可以做所有我们想到的事情。这里的IRQL是最低的,所以我们在这里调用CreateImplant()函数。这个函数完成前面讲过的操作,包括注入植入代码。在此之后,在_KiSystemService返回后将调用植入代码,在其处理程序之后线程重新返回到这个中断之前,更精确讲,就是返回到ntdll.dll内部。下面是CreateImplant()函数中创建植入代码部分的代码。

// 将从KiSystemService返回时的返回点
// 改为用户模式下植入代码的地址
    mov eax,ImpStartAddr 
    mov ebx,[KISS_SP]
    // 将参数由后向前塞入堆栈
    mov [ebx],eax

// pushad
    mov bl,0x60 
    mov [eax],bl
    inc eax

    mov bl, 0xb8
    mov [eax], bl
    inc eax
    mov ebx,pUprocessInformation  // PI
    mov [eax],ebx                 // mov eax,PI
    add eax,4
    mov [eax],0x50                // push eax
    inc eax

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,pUstartUpInfo
    mov [eax],ebx                 // mov eax,SI
    add eax,4
    mov [eax],0x50                // push eax
    inc eax

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,0
    mov [eax],ebx                 // mov eax,0
    add eax,4
    mov [eax],0x50                // push eax
    inc eax

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,0
    mov [eax],ebx                 // mov eax,0
    add eax,4
    mov [eax],0x50                // push eax
    inc eax 

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,0x04000000
    // mov eax,0x04000000 = Create_default_error_mode
    mov [eax],ebx
    add eax,4
    // push eax
    mov [eax],0x50
    inc eax 

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,0
    // mov eax,0
    mov [eax],ebx
    add eax,4
    // push eax
    mov [eax],0x50
    inc eax 

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,0
    // mov eax,0
    mov [eax],ebx
    add eax,4
    // push eax
    mov [eax],0x50
    inc eax 

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,0
    // mov eax,0
    mov [eax],ebx
    add eax,4
    // push eax
    mov [eax],0x50
    inc eax 

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,ProgExeNameAddrinUser
    // mov eax,ProgExeNameAddrinUser
    mov [eax],ebx
    add eax,4
    // push eax
    mov [eax],0x50
    inc eax 

    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,0
    // mov eax,0
    mov [eax],ebx
    add eax,4
    // push eax
    mov [eax],0x50
    inc eax

    // 现在是过程调用本身
    mov bl,0xb8
    mov [eax],bl
    inc eax
    mov ebx,CreateProcessA_OEP
    // mov eax,CreateProcessA_OEP
    mov [eax],ebx
    add eax,4
    mov bx,0xD0FF
    mov [eax], bx
    // call eax
    add eax,2

    // popad
    mov bl,0x61
    mov [eax],bl
    inc eax

    mov bl,0xbb
    mov [eax],bl
    inc eax
    mov ebx,KiSystemServiceReturnAddress
    // mov ebx,KiSystemServiceReturnAddress
    mov [eax],ebx
    add eax,4
    mov bx,0xE3FF
    // jmp ebx ... 现在重新恢复
    // KiSystemService的原始返回点
    mov [eax], bx

我们看到,借助于数十行的汇编指令,我们在kernel32.dll的首部中建立起了植入代码,这段代码看上去如下:


图中所示为kernel32.dll模块的一段内存dump。红框内的是代码,而蓝框内的是结构体USTARTUPINFO、UPROCESS_INFORMATION和ProgExeName,对应于CreateProcessA中相应的结构体。红框中的代码的实际意义如下:

    0010:77E6047A    60        PUSHAD
    0010:77E6047B    B84C04E677    MOV    EAX,77E6044C - UPROCESS_INFORMATION
    0010:77E60480    50        PUSH    EAX
    0010:77E60481    B80804E677    MOV    EAX,77E60408 - USTARTUPINFO
    0010:77E60486    50        PUSH    EAX
    0010:77E60487    B800000000    MOV    EAX,00000000
    0010:77E6048C    50        PUSH    EAX
    0010:77E6048D    B800000000    MOV    EAX,00000000
    0010:77E60492    50        PUSH    EAX
    0010:77E60493    B800000004    MOV    EAX,04000000 - Create_default_error_mode
    0010:77E60498    50        PUSH    EAX
    0010:77E60499    B800000000    MOV    EAX,00000000
    0010:77E6049E    50        PUSH    EAX
    0010:77E6049F    B800000000    MOV    EAX,00000000
    0010:77E604A4    50        PUSH    EAX
    0010:77E604A5    B800000000    MOV    EAX,00000000
    0010:77E604AA    50        PUSH    EAX
    0010:77E604AB    B85C04E677    MOV    EAX,77E6045C - ProgExeNameAddrinUser
    0010:77E604B0    50        PUSH    EAX
    0010:77E604B1    B800000000    MOV    EAX,00000000
    0010:77E604B6    50        PUSH    EAX
    0010:77E604B7    B8BC1BE677    MOV    EAX,KERNEL32!CreateProcessA
    0010:77E604BC    FFD0        CALL    EAX
    0010:77E604BE    61        POPAD
    0010:77E604BF    BB0403FE7F    MOV    EBX,7FFE0304 - 向ntdll.dll中返回的地址
    0010:77E604C4    FFE3        JMP    EBX

总的来说都比较简单。在线程用指令ret/sysexit离开_KiSystemService时,通过改换返回线程内核堆栈的地址,就去执行前面编写的汇编植入代码。这段代码在CreateProcessA中调用,之后JMP EBX指令重新返回到ntdll.dll。由此,如果一切顺利,就会调用创建进程的植入代码。

之后从类似于Mark Russinovich的Process Explorer这样的程序中就可以看到所创建的进程。如果,比如说,我们创建了一个GUI进程,则在某些情况下看不到应用程序窗口,尽管Process Explorer能正常显示其存在。不用担心,事实上,在某些我所不了解的情况下进程没有连接上WindowStation并获得比如说Desktop,这会发生在父进程成为Services进程时。

例如,我们看到,“产生”在WinAmp内部的CMD.EXE进程,取得了Desktop并在“表面”上可见。

这又是另一种情况了。

我想就不必多说了。

通过本文所讲,我想我们已经体会到,我们实现目的的方法是多么的简单。说起来,从内核模式启动用户进程不算是什么鬼把戏。现在在读过所有说明与补充之后,您可以着手研究驱动代码了。加载驱动可以用Four-F的KmdKit里的KmdManager。其实,要感谢他帮助解决了文末的一个问题,同时还要感谢lial先生对本文的热情帮助。

批评建议请发至troguar@yandex.ru。
windows系统调用int 2e处理过程 在X86体系架构中,中断处理向量由一个中断描述符表保存。中断描述符表的每个描述符项包括一个中断处理函数的地址、一个32位的描述符属性和中断之后中断所在代码的特权级别。下面是一个描述符项的宏定义,每个处理器包含256个这样的宏定义用于组成一个数组变量kidt。在系统初始化时,kidt当中的元素经过一些处理之后会填充到IDT表项当中。其中,当bits被定义为INT_32_DPL0时,表明发生的是异常; 阅读详情

相关推荐

CentOS 7升级GCC踩坑记:明明装了centos-release-scl,为啥找不到devtoolset-9-gcc-c++?

本文详细记录了在CentOS 7系统中升级GCC时遇到的`devtoolset-9-gcc-c++`不可用问题及其解决方案。通过分析`centos-release-scl`安装不完整的原因,提供了从清理残留、重新安装仓库配置到完整安装devtoolset-9工具链的全套操作指南,帮助开发者彻底解决类似环境配置难题。

weixin_29252171的博客 246

Linux-内核-学习笔记(12):内核启动过程分析

Linux-内核-学习笔记(12):内核启动过程分析 在uboot启动后,会将各种参数通过三个寄存器的方式传递给内核函数,并在执行启动内核后自动结束。内核启动过程会解析参数并初始化各种设备,最终进入到了一种能够实现进程间调度的多进程状态,这些进程里面只要有哪个需要被运行,调度系统就会终止cpu_idle死循环进程(空闲进程)转而去执行有意义的干活的进程,从而实现内核的运转。 一、链接脚本vmlin...

qq_42826337的博客 5255

【无标题】 Python自动化法律文书撰写

Jinja2是一个功能强大的模板引擎,支持丰富的语法和功能。除了条件判断和循环语句外,Jinja2还支持过滤器、宏、继承等高级特性,非常适合处理复杂的文书模板。

master_chenchen的博客 2065

从核心模式启动一个进程----怎样从内部的驱动程序启动一个Win32进程

从核心模式启动一个进程----怎样从内部的驱动程序启动一个Win32进程[Romania]Stan Alex著   [China]rxxi 译 文章原址:http://www.codeproject.com/useritems/KernelExec.aspDownload source files - 55 Kb Download demo project - 27 Kb

C/C++ World 5319

Spring Boot整合Apache Hop 2.3.0实战:从零搭建数据管道(附完整代码)

本文详细介绍了如何在Spring Boot项目中整合Apache Hop 2.3.0,从零开始构建可嵌入的数据管道。内容涵盖环境初始化、依赖配置、管道设计,并通过完整代码示例演示了如何通过REST API触发执行,实现数据同步与ETL流程的自动化管理,为微服务架构提供灵活的数据集成方案。

web99的专栏 633

CMMI AIM 人工智能成熟度框架:31个过程域的157项AI实践集成

CMMI AIM人工智能成熟度框架为企业提供了系统化管理AI项目全生命周期的解决方案,包含31个过程域的157项AI实践。该框架通过四个演进阶段(混沌与风险、结构与凝聚、验证与信任、价值与增长),帮助组织建立可持续的AI能力成长路径,降低项目失败率并提升商业价值。

weixin_42613501的博客 355

在linux内核中启动任务

内核中启动时间比较长的模块,我们可以用任务的形式来启动。 static void ssd1322_TstDemoShow(struct work_struct *work) {         struct ssd1322_par *par = container_of(work, struct ssd1322_par, TstDisp_work);           ssd1325_

Power of technology will free your body and spirit 690

基于YOLOv11的建筑图纸物体检测系统设计与实现

包含多种建筑平面图,标注了墙体、门窗等元素:CAD格式的建筑图纸数据集:专门针对建筑元素的标注数据集自定义数据集:使用LabelImg等工具自行标注的建筑图纸本文以FloorPlanDataset为基础,结合部分自定义标注数据构建混合数据集。wall (墙体)door (门)window (窗)stair (楼梯)furniture (家具)text (文字标注)dimension (尺寸标注)column (柱子)

YOLO 537

Ubuntu下更改Mysql默认字符集

在ubuntu安装Mysql Server比较简单,一条命令就可以搞定,如下:<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br ...

光子CI之旅 171

数据挖掘实战-基于决策树算法构建北京市空气质量预测模型

通过分析这些数据,我们将利用决策树算法建立预测模型。决策树算法具有直观易懂、分类效果好等优点,适合用于此类预测问题。我们将采用适当的方法对模型进行训练和优化,以提高预测精度。最终,我们将评估模型的预测效果,并探讨其在实际应用中的可行性和潜在价值。希望通过本实验,能为北京市的空气质量改善工作提供一定的支持。通过基于决策树算法构建的北京市空气质量预测模型的实验研究,我们在多方面取得了显著的进展。首先,我们成功地整合了来自气象、环保、交通等多个领域的大量数据,形成了一个全面而综合的空气质量分析框架。

m0_64336780的博客 2万+

10分钟搞定manim安装与vscode配置

manim安装与vscode配置

帅帅de三叔 3413

ubuntu下更改mysql默认编码(字符集)

ubuntu下更改mysql默认编码(字符集)http://blog.youkuaiyun.com/zzuduoduo/archive/2009/05/02/4142504.aspx 安装mysqlsudo apt-get install mysql-server #直接自动获得可用版本也可以这样写sudo apt-get install mysql-server-5.0 #安装mysql服务器5

StamHe的专栏 996

《FreeRTOS 入门与实战》第2章:开发环境搭建(实战)

STM32CubeIDE工程创建与FreeRTOS移植J-Link调试环境搭建SystemView可视化跟踪配置。

2301_79143213的博客 1733

ubuntu下更改MYSQL数据库默认字符集

环境:ubuntu12.04 LTS 1. 备份/etc/mysql/my.cnf 2. 修改/etc/mysql/my.cnf      a.)[client]     default-character-set=utf8      b.)[mysql]    default-character-set=utf8      c.)[mysqld]  character-set-serv...

Acaleph的专栏 322

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

linux mysql 修改字符集编码,Linux mysql改变字符集编码与ubuntu下更改mysql默认编码...

Linux mysql教程改变字符集编码与ubuntu下更改mysql默认编码找到配置文件/etc/mysql/my.cnf在[client]下面加上default-character-set=utf8在[mysqld]下面加上default-character-set=utf8重启服务器service mysql restart在mysql中键入命令:show variables like 'c...

weixin_28848317的博客 121

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Ubuntu中更改MySQL字符集

在/etc/mysql/my.cnf里添加如下两项 [client] default-character-set=utf8 [mysqld] character_set_server=utf8 可参考 http://gm100861.blog.51cto.com/1930562/896173

技亦灵怪矣哉 358

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Ubuntu更改MySQL字符集UTF-8,正确显示中文

首先停mysql服务,编辑配置文件my.cnf $ sudo stop mysql $sudo vim /etc/mysql/my.cnf [client] default-character-set=utf8 #增加这一行,指定字符集 port = 3306 socket = /var/run/mysqld/mysqld.sock .......

JSPang的博客 580

Ubuntu下更改Mysql默认字符集 遇到问题

网上解决方法: 查看mysql现在的编码:mysql> show variables like 'character%';+--------------------------+----------------------------------------------+| Variable_name | Value ...

weixin_30268071的博客 149

ubuntu更改mysql中文版_Ubuntu更改MySQL字符集UTF-8,正确显示中文_MySQL

Ubuntu首先停mysql服务,编辑配置文件my.cnf $ sudo stop mysql$sudo vim /etc/mysql/my.cnf[client] default-character-set=utf8 #增加这一行,指定字符集port = 3306 socket = /var/run/mysqld/mysqld.sock ............

weixin_34696006的博客 240

ubuntu数据库默认字符集_Ubuntu下更改Mysql默认字符集 遇到问题

网上解决方法:查看mysql现在的编码:mysql> show variables like 'character%';+--------------------------+----------------------------------------------+| Variable_name | Value ...

weixin_42504833的博客 145

Ubuntu更改MySQL字符集utf8,以支持中文

网上看了好几篇设置Ubuntu下Mysql支持中文,结果改完配置文件后,sudo /etc/init.d/mysql restart,重启Mysql失败 最后自己找到了一个方法,结果成功 首先关闭Mysql sudo stop mysql 接着修改配置文件 sudo vim /etc/mysql/my.cnf 重启Mysql sudo star

erlei2014的博客 557

ubuntu mysql 中午_Ubuntu更改MySQL字符集UTF-8,正确显示中文

首先停mysql服务,编辑配置文件my.cnf$sudostopmysql$sudovim/etc/mysql/my.cnf[client]default-character-set=utf8#增加这一行,指定字符集port=3306socket=/var/run/mysqld/mysqld.sock.............[mysql...

weixin_31861847的博客 174
上一篇: 关于杀软的杀毒引擎
下一篇: 杀毒引擎外一篇--讨论杀软卡机的原理
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值