首页
论坛
专栏
课程
1

[旧帖] [原创]两种插APC法实现 DLL注入 0.00元

李文靖 2010-6-6 13:20 4181
hi,各们前辈好。
小弟执迷看雪很久,对着逆向技术和WINDOWS内核编程有着痴迷的向往。
但因技术有限,一直以来都是在[新人交流区]默默无闻。
特写此文,希望看雪版主给以小弟前进学习的源动力,也希望各大牛拍砖,小弟感激不尽!

a)内核方式投递APC.
异步过程调用(APC)是NT异步处理体系结构中的一个基础部分。Alertable IO(告警IO)提供了更有效的异步通知形式,当IO请求完成后,一旦线程进入可告警状态,回调函数将会执行,也就是一个APC的过程.
线程进入告警状态时,内核将会检查线程的APC队列,如果队列中有APC,将会按FIFO方式依次执行。如果队列为空,线程将会挂起等待事件对象。以后的某个时刻,一旦APC进入队列,线程将会被唤醒执行APC.
投递APC是一个线程动作,最终由系统调用KiDeliverApc完成。所以,我们可以填充一个APC(KeInitializeapc,KeInsertQueueApc)插入到线程Alertable为TRUE的APC对列中。
任意一个DLL插入到进程执行的是用户空间代码,so,必定要使用LoadLibrayA加载才可访问用户地址空间。so这是个用户模式下的APC。用户模式可以传递(ULONG)LoadLibrayA地址,内核里就可使用这个地址作文章咯。

以下介绍下_KAPC_STATE结构,加深对APC队列的理解。

// _KTHREAD +0x034 ApcState         : _KAPC_STATE
nt!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x010 Process          : Ptr32 _KPROCESS
   +0x014 KernelApcInProgress : UChar
   +0x015 KernelApcPending : UChar
   +0x016 UserApcPending   : Uchar
APC队列头ApcListHead是数组指针,1组是存入用户模式下APC队列,另一组是存在内核模块APC队列.

// 还有一个_KTHREAD   +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE结构。这个是APC队列状态点,简单地说是当APC队列乱套时,WINDOWS用以备份或恢复的。

InsertApcDll(..)函数讲解如下:

   // 由PID得到EPROCESS结构地址
ntStatus = PsLookupProcessByProcessId(UlongToHandle(ulPid),&eProcess);

ASSERT_STATUS(ntStatus);

/*
线程头
    nt!_KPROCESS
        +0x000 Header           : _DISPATCHER_HEADER
        ...
        ...
        +0x050 ThreadListHead   : _LIST_ENTRY (_ETHREAD)
        */
        ulThreadHead = (ULONG)eProcess + 0x050;

        ulThNextEntry = *(ULONG *)ulThreadHead;

        // 遍历eProcess的线程
        while(ulThNextEntry != ulThreadHead)
        {
                /*
        lkd> dt _KTHREAD
        nt!_KTHREAD
        +0x000 Header           : _DISPATCHER_HEADER
        ...
        +0x1b0 ThreadListEntry  : _LIST_ENTRY

        EHREAD = ETHREAD - 0x1b0
        */

        ulAlertablekThread = ulThNextEntry - 0x1b0;

        /*
        找到这个进程中Alertable是TRUE的线程

        nt!_KTHREAD
        +0x000 Header           : _DISPATCHER_HEADER
        ...
        +0x164 Alertable        : UChar

        呵呵,有种暴力的插APC注入方式,是不检测不这种状态的,
        只要找到Alertable就把它设为TRUE。
        或者原本UserApcPending为TRUE的的线程时,被设置为FALSE,这样是很隐藏的。
        */
        if(*(char *)(ulAlertablekThread + 0x164))
        {
            // 找到时,将APC插入该线程的APC队列
           InsertApcByUserMode(szDllFullPath,ulAlertablekThread,(ULONG)eProcess);
                break;
        }

        ulThNextEntry = *(ULONG *)ulThNextEntry;
  }

InsertApcByUserMode(…)
  插入用户模式的APC。
最主要是用户模式的LoadLibraryA(..)函数的使用,如何把代码映射到用户空间。

  // -----------这里相当于应用层调用LoadLibraryA(szDllFullPath);

  // 分配用户模块地址空间。(PUCHAR)pMappedAddress + 0x14是唯一参数地址。
// 已在FillApc中分配,共个字节.
    memset((PUCHAR)pMappedAddress + 0x14,0,300);

// 空间填充DLL名
  memcpy((PUCHAR)pMappedAddress + 0x14, szDllFullPath,strlen(szDllFullPath));

   plDataAddress = (ULONG*)((char*)pMappedAddress + 0x09);
*plDataAddress = ulMappedAddress + 0x14;

// FillApc裸函数分配APC空间并填充。

// --------------------------------------------------------------

// 使线程处于警告状态.
if(!*(char *)(uleThread + 0x4a))
        *(char *)(uleThread + 0x4a) = TRUE;

或使用KAPC_STATE ks;(这个可在遍历线程时保存0
ks.UserApcPending = TRUE;插入的DLL就顺利执行了。

完整代码请看附件project.c

b)应用层方式插入APC.

使用DWORD QueueUserAPC(
PAPCFUNC pfnAPC, // APC function
HANDLE hThread,  // handle to thread
ULONG_PTR dwData // APC function parameter)
函数可以在应用层插入DLL。比CreateRemoteThread强多了。且CreateRemoteThread
已泛滥成灾了,很多杀软都有所对策。

从流程上看QueueUserAPC直接转入了系统服务NtQueueApcThread从而利用KeInsertQueueApc向给出的目标线程的 APC队列插入一APC对象。倘若KiDeliverApc顺利的去构造apc环境并执行我们的代码那一切就OK了,只可惜没有那么顺利的事, ApcState中UserApcPending是否为TRUE有重要的影响,结果往往是你等到花儿都谢了你的代码还是没得到执行。在核心态往往不成问 题,自己动手赋值.
   本例并没有等待UserApcPending = TRUE才执行,而是把所有线程全都QueueUserAPC。这样确实影响效率。解决方法是,使用目标线程调用 SleepEx(.,TRUE),然后QueueUserAPC插入DLL。

使用QueueUserAPC的内核实现方式与a)是一致的。呵呵,这看起来比a)更有优势。
看看吧。

应用层的InsertApcDll(..)函数如下。
            hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,ulPid);

        if(hProcess == NULL)
        {
                // 打开进程失败。
                // 呵呵,一般系统进程是打不开的。可以使用内核的NtOpenProcess,
                // 炉子的LzOpenProcess,LzOpenThread。
                // 在这就不搞这上啦。
                return bRet;
        }

                // 在进程空间中分配内存,存放Shellcode,就是DLL的进程空间.
                // 此内存块可读可写.
                PVOID pAlloc = VirtualAllocEx(…)

                // DLL写入内存空间.
WriteProcessMemory(hProcess,pAlloc,(LPVOID)szFullDllPath,ulDllFileLen,&ulRet);

POSITION ps = cThreadId.GetHeadPosition();

                while(ps != NULL)
                {
                        ulCurThreadId = cThreadId.GetNext(ps);
                // 打开进程失败。
                // 呵呵,一般系统进程是打不开的。可以使用内核的NtOpenThread,
                // 炉子的LzOpenProcess,LzOpenThread。
                // 在这就不搞这上啦。
                HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE,ulCurThreadId) ;
                if(hThread != NULL)
                {
                        //
                        // 注入DLL到指定进程
                        //
                        // 虽然是每个线程HANDLE都调用这函数。
                        // 但内核原理是和a)一致的,
                        // 只会查找UserApcPending = TRUE的线程插入。
                        QueueUserAPC((PAPCFUNC)LoadLibraryA,hThread,(ULONG_PTR)pAlloc);
                }
}

``// 不要释放呀。进程退出时系统就释放了。
``//  VirtualFreeEx(pAlloc)
  完整代码请看附件。

c)总结。

更多的思路可以参考sudami的<N种内核注入DLL的思路及实现>.
我这并没有Hook ZwmapviewOfSection等函数。一是我对ShellCode不熟,
二是对Hook本身就是很暴力的行为,很多杀软都会检测到的,再说在兼容x64
下处理内核hook,就是自找麻烦,没事找事。X64下应尽量走应用层道路。

不过,像SetWindowsHook,CreateRemoteThread这些都是泛滥成灾了。
用起来也没意思了。呵呵。
以上是我个人对DLL注入的一些看法和思想。花了我星期五整天。唉郁闷。

此致,失望版主给以鼓励!小弟我感激不尽!

快讯:科锐逆向工程师培训(第32期,10月10日开学)!

上传的附件:
最新回复 (12)
bupthui 2010-6-6 13:21
2
不错的说
支持了
是原创吗?
李文靖 2010-6-6 13:26
3
参考了一些牛人的思路,自己在不断学习中。
dragonltx 2010-6-6 22:26
4
不错,支持下!
痞子辉 2010-6-7 10:26
5
支持有码的。。。
杀不死 2010-6-24 23:56
6
oatzhang 2010-6-26 08:22
7
看不懂也要赞一个
meijingogo 2010-6-26 09:41
8
LZ你打错了一个字,应该是希望版主给以鼓励!

文章不错,思路很清晰!!
李文靖 2010-6-26 13:57
9
我已经成为会员咯,才发邀请码,郁闷!
jokersky 2010-10-29 18:24
10
这是R3级滴哈?

不知道这么做滴重要性在那里呢?
stoneljs 2010-12-2 10:39
11
OpenThread(THREAD_ALL_ACCESS, FALSE,ulCurThreadId)
OpenThread能通过ID得到另外一个进程的HANDLE吗?
stoneljs 2010-12-2 10:40
12
OpenThread(THREAD_ALL_ACCESS, FALSE,ulCurThreadId)
OpenThread能通过ID得到另外一个进程中线程的HANDLE吗?
guobing 2011-7-17 12:32
13
这么名字好熟悉,你是,嘿嘿,认识...
返回