MemoryModule阅读与PE文件解析(三)

前面我们提到一个函数 CopySections ,该函数将文件中的段拷贝到我们申请的内存中,并按照内存页面的大小进行对齐。拷贝过程中设置了每个段的PhysicalAddress的值为该段的虚拟地址,用于后面的操作。

要理解这个段,首先应该理解_IMAGE_SECTION_HEADER  的联合体

union {

            DWORD   PhysicalAddress;

            DWORD   VirtualSize;

} Misc;

其中VirtualSize(程序中使用的是PhysicalAddress)我觉得VirtualSize更加准确。这个VirtualSize 指的是这个段,在加载进内存之后的实际大小,而_IMAGE_SECTION_HEADER 中的SizeOfRawData 指的是该段磁盘中已经初始化了的数据大小,该大小按照文件粒度对齐之后的大小,如果该节只包含未初始化的数据,大小为0。

 

static BOOL

CopySections(const unsigned char *data, size_t size, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module)

{

   int i, section_size;

   unsigned char *codeBase = module->codeBase;

   unsigned char *dest;

   PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers);

   for (i=0; i<module->headers->FileHeader.NumberOfSections; i++,section++) {

       if (section->SizeOfRawData == 0) {

            // 这个段所包含的数据都没有初始化, 为该段申请一个页面的大小,然后直接填充零即可。

            section_size = old_headers->OptionalHeader.SectionAlignment;

            if (section_size >0) {

                dest = (unsigned char *)module->alloc(codeBase+ section->VirtualAddress,

                    section_size,

                    MEM_COMMIT,

                    PAGE_READWRITE,

                    module->userdata);

                if (dest == NULL) {

                    return FALSE;

                }

 

                // 这里申请内存的时候直接使用了codeBase+section->VirtualAddress,之所以这样做,是为了使PE文件在加载入内存之后保持其格式,函数之所以成功,因为前面已经从codeBase开始预留了整个文件的空间

                dest = codeBase +section->VirtualAddress;

                // NOTE: On 64bit systems we truncate to 32bit here butexpand

                // again later when "PhysicalAddress" is used.

               section->Misc.PhysicalAddress = (DWORD) ((uintptr_t) dest &0xffffffff);

               // 在这之后,PhysicalAddress就是这个段的虚拟地址。

                memset(dest, 0, section_size);

            }

 

            // section is empty

            continue;

       }

 

       if (!CheckSize(size, section->PointerToRawData +section->SizeOfRawData)) {

            return FALSE;

       }

 

       // 提交内存块并拷贝数据

       dest = (unsigned char *)module->alloc(codeBase + section->VirtualAddress,

                           section->SizeOfRawData,

                            MEM_COMMIT,

                            PAGE_READWRITE,

                            module->userdata);

       if (dest == NULL) {

            return FALSE;

       }

 

       dest = codeBase + section->VirtualAddress;

       memcpy(dest, data + section->PointerToRawData,section->SizeOfRawData);

       // NOTE: On 64bit systems we truncate to32bit here but expand

       // again later when"PhysicalAddress" is used.

       section->Misc.PhysicalAddress = (DWORD) ((uintptr_t) dest &0xffffffff);

   }

 

   return TRUE;

}

 

题外话:我们都知道,PE 文件的加载是通过内存映射文件来实现的,但是不同于普通的作为数据的映射文件,PE 文件的内存映射文件对应的控制区对象后面的每个子内存区对象都对应于PE 文件中的一个段,而普通的数据型的内存映射文件对应的控制区对象后面的子内存区对象都对应的是一定大小的文件的一部分,详情请参考http://blog.csdn.net/qq_18218335/article/details/65626899

 

接下来我们看一个函数,FinalizeSections 该函数的注释为:根据段头标记内存页,并释放标记为“可废弃”的段。

 

static BOOL

FinalizeSections(PMEMORYMODULE module)

{

   int i;

   PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers);

#ifdef _WIN64

   // "PhysicalAddress" might havebeen truncated to 32bit above, expand to

   // 64bits again.

   uintptr_t imageOffset = ((uintptr_t)module->headers->OptionalHeader.ImageBase & 0xffffffff00000000);

#else

   static const uintptr_t imageOffset = 0;

#endif

   SECTIONFINALIZEDATA sectionData;

   sectionData.address = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress | imageOffset);

   sectionData.alignedAddress = AlignAddressDown(sectionData.address, module->pageSize);

   sectionData.size = GetRealSectionSize(module, section);

   sectionData.characteristics = section->Characteristics;

   sectionData.last = FALSE;

   section++;

 

   // 循环访问所有的段,然后改变其访问标识

   for (i=1; i<module->headers->FileHeader.NumberOfSections; i++,section++) {

       LPVOID sectionAddress = (LPVOID)((uintptr_t)section->Misc.PhysicalAddress| imageOffset);

       LPVOID alignedAddress = AlignAddressDown(sectionAddress, module->pageSize);

       SIZE_T sectionSize = GetRealSectionSize(module, section);

       // 如果前后两个段的其实地址相同,或者前一个段的末尾地址大于当前段起始地址,即有重叠的部分,将两个段的操作合并起来。我们知道,前面申请内存的时候其地址都是页面对齐的,而且申请得到的内存块的大小也是页面对齐的(VirtualAlloc函数的提交操作就是按照页面操作的),因此这个判断语句不会进去,不知道作者如何考虑的

       if (sectionData.alignedAddress == alignedAddress || (uintptr_t)sectionData.address + sectionData.size > (uintptr_t) alignedAddress) {

            // Section shares page with previous

            if((section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0 || (sectionData.characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0) {

                sectionData.characteristics =(sectionData.characteristics | section->Characteristics) & ~IMAGE_SCN_MEM_DISCARDABLE;

            } else {

                sectionData.characteristics |=section->Characteristics;

            }

            sectionData.size = (((uintptr_t)sectionAddress) +((uintptr_t) sectionSize)) - (uintptr_t)sectionData.address;

            continue;

       }

 

       if (!FinalizeSection(module, &sectionData)){

            return FALSE;

       }

       sectionData.address = sectionAddress;

       sectionData.alignedAddress = alignedAddress;

       sectionData.size = sectionSize;

       sectionData.characteristics = section->Characteristics;

   }

   sectionData.last = TRUE;

   if (!FinalizeSection(module, &sectionData)){

       return FALSE;

   }

   return TRUE;

}

 

上面的操作对每个段都进行了FinalizeSection操作,该函数如下,操作如注释所示

static BOOL

FinalizeSection(PMEMORYMODULE module, PSECTIONFINALIZEDATA sectionData) {

   DWORD protect, oldProtect;

   BOOL executable;

   BOOL readable;

   BOOL writeable;

 

   if (sectionData->size == 0) {

       return TRUE;

   }

   if (sectionData->characteristics & IMAGE_SCN_MEM_DISCARDABLE) {

        // 这个段是可以被回收的

       if (sectionData->address == sectionData->alignedAddress &&

            (sectionData->last ||

             module->headers->OptionalHeader.SectionAlignment== module->pageSize ||

            (sectionData->size % module->pageSize) == 0)

          ) {

            // 满足可以释放整个页面的要求之后才可以释放页面

            module->free(sectionData->address, sectionData->size, MEM_DECOMMIT, module->userdata);

       }

       return TRUE;

   }

 

   // 得到读写执行属性

   executable = (sectionData->characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;

   readable =   (sectionData->characteristics& IMAGE_SCN_MEM_READ) != 0;

   writeable =  (sectionData->characteristics& IMAGE_SCN_MEM_WRITE) != 0;

   protect = ProtectionFlags[executable][readable][writeable];

   if (sectionData->characteristics & IMAGE_SCN_MEM_NOT_CACHED) {

       protect |= PAGE_NOCACHE;

   }

 

   // 改变页面的访问标识

   if (VirtualProtect(sectionData->address, sectionData->size, protect,&oldProtect) == 0) {

       OutputLastError("Errorprotecting memory page");

       return FALSE;

   }

 

   return TRUE;

}

 

这样我们就理解了函数的注释的含义:改变每个段的保护属性,并将段头部中包含有“可以将此资源释放”标识的内存页释放。

我们可以看到

重定向段标记为可回收,这很好理解,我们使用它进行重定向项的修复之后,我们就可以将其释放。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要查看DLL的源码,你可以使用C#反编译工具,比如JetBrains dotPeek。首先,你需要下载并安装这个工具。然后,打开dotPeek并点击菜单栏的【File->Open】按钮选择要反编译的DLL文件。一旦你打开了DLL文件,你就可以在dotPeek中查看并浏览源码了。 另外,如果你想了解更多关于内存加载并运行DLL函数的内容,你可以查看MemoryModule的开源源码。这个工具可以在Windows系统中加载并运行DLL函数。你可以在VC2015中打开编译的源码文件。 总之,通过使用反编译工具和开源源码,你可以查看和修改已编译的DLL文件的源代码,从而获取你想要的结果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [关于 C# dll文件的反编译获取源码](https://blog.csdn.net/qq_36694133/article/details/116519118)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [MemoryModule开源源码,windows内存加载并运行DLL函数,VC++2015打开编译的](https://download.csdn.net/download/airen3339/88229044)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [几种工具反编译被编译好的DLL文件](https://blog.csdn.net/weixin_34015860/article/details/93233036)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值