跟着病毒学技术--学习WannaCry自己实现LoadLirbrary

最近逆了一下WannaCry病毒,发现里边加载动态库是自己实现的,所以我也学着实现了一下。

0x001 读取动态库到内存

首先,需要将目标动态库读取的到内存,然后再进行下一步工作。

       HANDLE hDll = CreateFile(L"..//Debug//TestDll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);

       if (hDll == INVALID_HANDLE_VALUE)
       {
              printf("打开文件错误:%d\n", GetLastError());
       }
       DWORD dwFileSize = 0;
       dwFileSize = GetFileSize(hDll, NULL);
       if (dwFileSize <= 0)
       {
              printf("获得文件大小失败!\n");
              return -1;
       }
       unsigned char* DllBuffer = (unsigned char*)malloc(dwFileSize);
       DWORD dwDataLength = 0;
       if (!ReadFile(hDll, DllBuffer, dwFileSize, &dwDataLength, NULL))
       {
              printf("读取文件错误:%d\n", GetLastError());
              return -1;
       }
       HMEMORYMODULE hModule = MemoryLoadLibrary(DllBuffer, dwDataLength);
       pfnTestProc TestProc = (pfnTestProc)MemoryGetProcAddress(hModule, "TestProc");
       TestProc();
       MemoryFreeLibrary(hModule);
 

0x002 在目标加载地址处申请内存空间

实现LoadLirbrary其实就是将Dll文件由文件格式映射为内存格式,我们首先要做的就是在Dll建议加载地址处申请内存空间。

       //从Dll建议加载基地址处申请内存空间
       Code = (unsigned char *)MyAllocMemory((LPVOID)(Nt_Header->OptionalHeader.ImageBase),
              AlignedImageSize,
              MEM_RESERVE | MEM_COMMIT,
              PAGE_READWRITE,
              UserData);
       if (Code == NULL)
       {
              //如果不成功,则随意找个地儿加载
              Code = (unsigned char *)MyAllocMemory(NULL,
                     AlignedImageSize,
                     MEM_RESERVE | MEM_COMMIT,
                     PAGE_READWRITE,
                     UserData);
              if (Code == NULL)
              {
                     SetLastError(ERROR_OUTOFMEMORY);
                     return NULL;
              }
       }

这个申请多大呢?实际上要考虑系统的内存页大小,对齐粒度。

GetNativeSystemInfo(&SystemInfo);
AlignedImageSize = AlignValueUp(Nt_Header->OptionalHeader.SizeOfImage, SystemInfo.dwPageSize);
static inline uintptr_t AlignValueDown(uintptr_t Value, uintptr_t Alignment)
{
       return Value & ~(Alignment - 1);
}

0x003 拷贝PE头部信息到目标地址

接下来我们要做的便是将PE头(Dos头+Nt头,具体结构可参考PE权威指南)拷贝到指定地区。

       //申请头部空间
       Headers = (unsigned char *)MyAllocMemory(Code,
              Nt_Header->OptionalHeader.SizeOfHeaders,
              MEM_COMMIT,
              PAGE_READWRITE,
              UserData);
       //将Dll中的Nt头拷贝到指定加载内存处
       memcpy(Headers, Dos_Header, Nt_Header->OptionalHeader.SizeOfHeaders);
       hModule->NtHeaders = (PIMAGE_NT_HEADERS)&((const unsigned char *)(Headers))[Dos_Header->e_lfanew];
       // 更新Dll加载基地址
#ifdef _WIN64
       hModule->NtHeaders->OptionalHeader.ImageBase = (DWORD64)Code;
#else
       hModule->NtHeaders->OptionalHeader.ImageBase = (DWORD)Code;
#endif // _WIN64     

0x004 拷贝节区信息到目标地址

static BOOL CopySections(const unsigned char *Data, size_t Size, PIMAGE_NT_HEADERS Nt_Headers, PMEMORYMODULE Module)
{
       int i, SizeOfSection;
       unsigned char *CodeBase = Module->pCodeBase;
       unsigned char *SectionBase;        //节区地址
       PIMAGE_SECTION_HEADER Section_Header = IMAGE_FIRST_SECTION(Module->NtHeaders);
       for (i = 0; i<Module->NtHeaders->FileHeader.NumberOfSections; i++, Section_Header++)
       {
              //这里有节区,但是没内容
              if (Section_Header->SizeOfRawData == 0)
              {
                     //节大小为0的情况下,直接按照粒度对齐
                     SizeOfSection = Nt_Headers->OptionalHeader.SectionAlignment;
                     if (SizeOfSection > 0)
                     {
                           SectionBase = (unsigned char *)Module->MyAlloc(CodeBase + Section_Header->VirtualAddress,
                                  SizeOfSection,
                                  MEM_COMMIT,
                                  PAGE_READWRITE,
                                  Module->pUserData);
                           if (SectionBase == NULL)
                           {
                                  return FALSE;
                           }
                           SectionBase = CodeBase + Section_Header->VirtualAddress;
                           Section_Header->Misc.PhysicalAddress = (DWORD)((uintptr_t)SectionBase & 0xffffffff);
                           memset(SectionBase, 0, SizeOfSection);
                     }
                     // 节区为空
                     continue;
              }
              if (!CheckSize(Size, Section_Header->PointerToRawData + Section_Header->SizeOfRawData))
              {
                     return FALSE;
              }
              //拷贝节区内容
              SectionBase = (unsigned char *)Module->MyAlloc(CodeBase + Section_Header->VirtualAddress,
                     Section_Header->SizeOfRawData,
                     MEM_COMMIT,
                     PAGE_READWRITE,
                     Module->pUserData);
              if (SectionBase == NULL)
              {
                     return FALSE;
              }

              SectionBase = CodeBase + Section_Header->VirtualAddress;
              memcpy(SectionBase, Data + Section_Header->PointerToRawData, Section_Header->SizeOfRawData);

              Section_Header->Misc.PhysicalAddress = (DWORD)((uintptr_t)SectionBase & 0xffffffff);
       }
       return TRUE;
}

0x005 调整重定向信息

重定向表是一个特别重要的结构,我们要先弄好它,否则接下来的各种数据地址有可能会错误。

       // 调整重定向数据位置
       LocationDelta = (ptrdiff_t)(hModule->NtHeaders->OptionalHeader.ImageBase - Nt_Header->OptionalHeader.ImageBase);
       if (LocationDelta != 0)
       {
              hModule->bIsRelocated = PerformBaseRelocation(hModule, LocationDelta);
       }
//重定向
static BOOL PerformBaseRelocation(PMEMORYMODULE Module, ptrdiff_t Delta)
{
       unsigned char *CodeBase = Module->pCodeBase;
       PIMAGE_BASE_RELOCATION Relocation;
       //获得重定向表目录项
       PIMAGE_DATA_DIRECTORY BaseRelocDirectory = GET_HEADER_DICTIONARY(Module, IMAGE_DIRECTORY_ENTRY_BASERELOC);
       if (BaseRelocDirectory->Size == 0)
       {
              return (Delta == 0);
       }
       Relocation = (PIMAGE_BASE_RELOCATION)(CodeBase + BaseRelocDirectory->VirtualAddress);
       while (Relocation->VirtualAddress > 0)
       {
              DWORD i;
              unsigned char  *RelocationBase = CodeBase + Relocation->VirtualAddress;   //重定向表RVA
              unsigned short *RelocationInfo = (unsigned short*)OffsetPointer(Relocation, IMAGE_SIZEOF_BASE_RELOCATION);
              for (i = 0; i<((Relocation->SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, RelocationInfo++)
              {
                     // 高4位为Type
                     int Type = *RelocationInfo >> 12;
                     // 低12位为偏移
                     int Offset = *RelocationInfo & 0xfff;
                     switch (Type)
                     {
                     case IMAGE_REL_BASED_ABSOLUTE:
                           //这个无意义,仅对齐用
                           break;
                     case IMAGE_REL_BASED_HIGHLOW:
                           //32位都需要修正
                     {
                           DWORD *PatchAddrHL = (DWORD *)(RelocationBase + Offset);
                           *PatchAddrHL += (DWORD)Delta;
                     }
                     break;
#ifdef _WIN64
                     case IMAGE_REL_BASED_DIR64:
                           //64位
                     {
                           ULONGLONG *PatchAddr64 = (ULONGLONG *)(RelocationBase + Offset);
                           *PatchAddr64 += (ULONGLONG)Delta;
                     }
                     break;
#endif
                     default:
                           break;
                     }
              }
              // 下一个重定向Block
              Relocation = (PIMAGE_BASE_RELOCATION)OffsetPointer(Relocation, Relocation->SizeOfBlock);
       }
       return TRUE;
}

0x006 建立导入表

//导入表
static BOOL BuildImportTable(PMEMORYMODULE Module)
{
       unsigned char *CodeBase = Module->pCodeBase;
       PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor;
       BOOL bOk = TRUE;
       //获得导入表表项
       PIMAGE_DATA_DIRECTORY ImportDirectory = GET_HEADER_DICTIONARY(Module, IMAGE_DIRECTORY_ENTRY_IMPORT);
       if (ImportDirectory->Size == 0)
       {
              return TRUE;
       }
       ImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(CodeBase + ImportDirectory->VirtualAddress);  //导出表RVA
       for (; !IsBadReadPtr(ImportDescriptor, sizeof(IMAGE_IMPORT_DESCRIPTOR)) && ImportDescriptor->Name; ImportDescriptor++)
       {
              uintptr_t *ThunkRef;
              FARPROC   *FuncRef;
              HCUSTOMMODULE *v1;
              HCUSTOMMODULE hModule = Module->MyLoadLibrary((LPCSTR)(CodeBase + ImportDescriptor->Name), Module->pUserData);
              if (hModule == NULL)
              {
                     SetLastError(ERROR_MOD_NOT_FOUND);
                     bOk = FALSE;
                     break;
              }
              v1 = (HCUSTOMMODULE *)realloc(Module->pModules, (Module->nNumberOfModules + 1)*(sizeof(HCUSTOMMODULE)));
              if (v1 == NULL)
              {
                     Module->MyFreeLibrary(hModule, Module->pUserData);
                     SetLastError(ERROR_OUTOFMEMORY);
                     bOk = FALSE;
                     break;
              }
              Module->pModules = v1;
              Module->pModules[Module->nNumberOfModules++] = hModule;
              if (ImportDescriptor->OriginalFirstThunk)
              {
                     //注:导入表双桥结构,OriginalFirstThunk指向INT表,FirstThunk指向IAT表,最终两个表中的表项指向同一个函数地址
                     ThunkRef = (uintptr_t *)(CodeBase + ImportDescriptor->OriginalFirstThunk); //INT
                     FuncRef = (FARPROC *)(CodeBase + ImportDescriptor->FirstThunk);           //IAT
              }
              else
              {
                     // 无INT,有的程序只保留一个桥,如Borland公司的Tlink只保留桥2
                     ThunkRef = (uintptr_t *)(CodeBase + ImportDescriptor->FirstThunk);
                     FuncRef = (FARPROC *)(CodeBase + ImportDescriptor->FirstThunk);
              }
              for (; *ThunkRef; ThunkRef++, FuncRef++)
              {
                     if (IMAGE_SNAP_BY_ORDINAL(*ThunkRef))
                     {
                           *FuncRef = Module->MyGetProcAddress(hModule, (LPCSTR)IMAGE_ORDINAL(*ThunkRef), Module->pUserData);
                     }
                     else
                     {
                           //INT
                           PIMAGE_IMPORT_BY_NAME ThunkData = (PIMAGE_IMPORT_BY_NAME)(CodeBase + (*ThunkRef));
                           *FuncRef = Module->MyGetProcAddress(hModule, (LPCSTR)&ThunkData->Name, Module->pUserData);
                     }
                     if (*FuncRef == 0)
                     {
                           bOk = FALSE;
                           break;
                     }
              }
              if (!bOk)
              {
                     //如果不成功,释放动态库
                     Module->MyFreeLibrary(hModule, Module->pUserData);
                     SetLastError(ERROR_PROC_NOT_FOUND);
                     break;
              }
       }
       return bOk;
}

0x007 调整节属性

//更改内存页属性等
static BOOL FinalizeSection(PMEMORYMODULE Module, PSECTIONFINALIZEDATA SectionData)
{
       DWORD dwProtect, dwOldProtect;
       BOOL  bExecutable;
       BOOL  bReadable;
       BOOL  bWriteable;
       if (SectionData->Size == 0)
       {
              return TRUE;
       }
       //节区数据在进程启动后将被丢弃,如.reloc
       if (SectionData->dwCharacteristics & IMAGE_SCN_MEM_DISCARDABLE)
       {
              //节区数据不再需要就释放
              if (SectionData->lpAddress == SectionData->lpAlignedAddress &&
                     (SectionData->bIsLast ||
                           Module->NtHeaders->OptionalHeader.SectionAlignment == Module->dwPageSize ||
                           (SectionData->Size % Module->dwPageSize) == 0)
                     )
              {
                     Module->MyFree(SectionData->lpAddress, SectionData->Size, MEM_DECOMMIT, Module->pUserData);
              }
              return TRUE;
       }
       //
       bExecutable = (SectionData->dwCharacteristics & IMAGE_SCN_MEM_EXECUTE) != 0; //可执行
       bReadable = (SectionData->dwCharacteristics & IMAGE_SCN_MEM_READ) != 0;      //可读
       bWriteable = (SectionData->dwCharacteristics & IMAGE_SCN_MEM_WRITE) != 0;    //可写
       dwProtect = ProtectionFlags[bExecutable][bReadable][bWriteable];
       if (SectionData->dwCharacteristics & IMAGE_SCN_MEM_NOT_CACHED)   //节区数据不会经过缓存
       {
              dwProtect |= PAGE_NOCACHE;
       }
       // 改变内存页属性
       if (VirtualProtect(SectionData->lpAddress, SectionData->Size, dwProtect, &dwOldProtect) == 0)
       {
              return FALSE;
       }
       return TRUE;
}
//将一些变量由文件中数值转化为内存中数值
static BOOL FinalizeSections(PMEMORYMODULE Module)
{
       int i;
       PIMAGE_SECTION_HEADER Section_Header = IMAGE_FIRST_SECTION(Module->NtHeaders);
#ifdef _WIN64
       //有可能超32位
       uintptr_t ImageOffset = ((uintptr_t)Module->NtHeaders->OptionalHeader.ImageBase & 0xffffffff00000000);
#else
       static const uintptr_t ImageOffset = 0;
#endif
       //将文件中的属性转化为内存中的属性
       SECTIONFINALIZEDATA SectionData;
       SectionData.lpAddress = (LPVOID)((uintptr_t)Section_Header->Misc.PhysicalAddress | ImageOffset);
       SectionData.lpAlignedAddress = AlignAddressDown(SectionData.lpAddress, Module->dwPageSize);
       SectionData.Size = GetRealSectionSize(Module, Section_Header);
       SectionData.dwCharacteristics = Section_Header->Characteristics;
       SectionData.bIsLast = FALSE;
       Section_Header++;
       // 依次更改各个节中属性
       for (i = 1; i<Module->NtHeaders->FileHeader.NumberOfSections; i++, Section_Header++)
       {
              LPVOID SectionAddress = (LPVOID)((uintptr_t)Section_Header->Misc.PhysicalAddress | ImageOffset); //将文件RVA转为内存RVA
              LPVOID AlignedAddress = AlignAddressDown(SectionAddress, Module->dwPageSize);                    //将节区对齐粒度改为系统内存页大小
              SIZE_T SectionSize = GetRealSectionSize(Module, Section_Header);                              //将文件中节区大小转为内存中节区大小
              if (SectionData.lpAlignedAddress == AlignedAddress || (uintptr_t)SectionData.lpAddress + SectionData.Size >(uintptr_t) AlignedAddress)
              {
                     // 节的数据在进程启动后将丢弃
                     if ((Section_Header->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0 || (SectionData.dwCharacteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0)
                     {
                           SectionData.dwCharacteristics = (SectionData.dwCharacteristics | Section_Header->Characteristics) & ~IMAGE_SCN_MEM_DISCARDABLE;
                     }
                     else
                     {
                           SectionData.dwCharacteristics |= Section_Header->Characteristics;
                     }
                     SectionData.Size = (((uintptr_t)SectionAddress) + ((uintptr_t)SectionSize)) - (uintptr_t)SectionData.lpAddress;
                     continue;
              }
              if (!FinalizeSection(Module, &SectionData))
              {
                     return FALSE;
              }
              SectionData.lpAddress = SectionAddress;
              SectionData.lpAlignedAddress = AlignedAddress;
              SectionData.Size = SectionSize;
              SectionData.dwCharacteristics = Section_Header->Characteristics;
       }
       SectionData.bIsLast = TRUE;
       if (!FinalizeSection(Module, &SectionData))
       {
              return FALSE;
       }
       return TRUE;
}

0x008 实现TLS回调函数

因为TLS回调函数会在DllEntry函数之前运行,所以要先实现这个函数

//执行TLS回调函数
static BOOL ExecuteTLS(PMEMORYMODULE Module)
{
       unsigned char *CodeBase = Module->pCodeBase;
       PIMAGE_TLS_DIRECTORY TLSDirectory;
       PIMAGE_TLS_CALLBACK* CallBack;
       PIMAGE_DATA_DIRECTORY Directory = GET_HEADER_DICTIONARY(Module, IMAGE_DIRECTORY_ENTRY_TLS);
       if (Directory->VirtualAddress == 0)
       {
              return TRUE;
       }
       TLSDirectory = (PIMAGE_TLS_DIRECTORY)(CodeBase + Directory->VirtualAddress);
       CallBack = (PIMAGE_TLS_CALLBACK *)TLSDirectory->AddressOfCallBacks;
       if (CallBack)
       {
              while (*CallBack)
              {
                     //当进程开始时执行
                     (*CallBack)((LPVOID)CodeBase, DLL_PROCESS_ATTACH, NULL);
                     CallBack++;
              }
       }
       return TRUE;
}

0x009 最终在函数入口处执行代码

       //获得Dll函数执行入口地址
       if (hModule->NtHeaders->OptionalHeader.AddressOfEntryPoint != 0)
       {
              if (hModule->bIsDLL)
              {
                     //函数执行入口
                     DllEntryProc DllEntry = (DllEntryProc)(LPVOID)(Code + hModule->NtHeaders->OptionalHeader.AddressOfEntryPoint);
                     BOOL bOk = DllEntry((HMODULE)Code, DLL_PROCESS_ATTACH, 0);
                     //BOOL bOk = (*DllEntry)((HINSTANCE)Code, DLL_PROCESS_ATTACH, 0);
                     if (!bOk)
                     {
                           SetLastError(ERROR_DLL_INIT_FAILED);
                           goto error;
                     }
                     hModule->bInitialized = TRUE;
              }
              else
              {
                     hModule->ExeEntry = (ExeEntryProc)(LPVOID)(Code + hModule->NtHeaders->OptionalHeader.AddressOfEntryPoint);
              }
       }
时间: 2024-10-24 02:18:03

跟着病毒学技术--学习WannaCry自己实现LoadLirbrary的相关文章

跟着杨中科学习asp.net之dom

Dom教程 使用javascript操作dom进行dhtml开发,目标:能够使用javascript操作dom实现常见的dhtml效果 Dom就是html页面的模型,将每个标签都做成为一个对象 ,javascript通过调用dom中的属性.方法就可以对网页中的文本框.层等元素进行编程控制,比如通过操作文本框的dom对象,就可以读取文本框中的值.设置文本框中的值 Dom也像winform一样,通过事件.属性.方法进行编程 Javascript→dom就是c#→.net framework. Css

跟着杨中科学习asp.net之javascript

Dom教程 使用javascript操作dom进行dhtml开发,目标:能够使用javascript操作dom实现常见的dhtml效果 Dom就是html页面的模型,将每个标签都做成为一个对象 ,javascript通过调用dom中的属性.方法就可以对网页中的文本框.层等元素进行编程控制,比如通过操作文本框的dom对象,就可以读取文本框中的值.设置文本框中的值 Dom也像winform一样,通过事件.属性.方法进行编程 Javascript→dom就是c#→.net framework. Css

跟着刚哥学习Spring框架--AOP(五)

AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善.OOP引入封装.继承.多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合.不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能.日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性.异常处理和透明的持续性也都是如此,这种散布在各

【vue】跟着老马学习vue-数据双向绑定

学习了node.js教程,只能说是有了一定的了解,之前也了解了webpack和es6的核心内容,也看过vue2.0的官网教程,并结合视频看过项目,但是理解和运用仍然存在很多问题,接下来的一段时间,跟着老马学习vue 学习链接:http://aicoder.com/vue/preview/all.html#1 vue最大的特点就在于它的双向绑定,是一个前端的双向绑定类的框架. 一说到vue我们就应该立刻想到以下几部分:1.数据双向绑定:2.列表渲染.条件渲染:3.事件处理:4.生命周期:5.组件化

我的2015技术学习流水账

我的2015技术学习流水账 2015年马上就要过去了,匆匆忙忙地又是一年.回头总结整理,发现这一年还挺充实的.在正常上班工作之余,学习到了不少新东西,不禁感到很欣慰!一个多月前就开始写,终于赶在2016年来临之前写完了这篇文章-- 关于本文,尽管叫做流水账,但是出于程序员条理性的"强迫症",还是进行系统分类,分类方法参照Thoughtworks技术雷达的Tecniques.Languages & Frameworks.Tools.Platforms,将其中的Tecniques改

EMV技术学习和研究(转)

刚开始学习EMV&PBOC,磕磕碰碰,感谢xuture的<EMV技术学习和研究>给了很大帮助,让我少走了很多弯路,也感谢广俊.surge.艾零.小SO.Spinach.龙行天下的帮助,尤其要感谢广俊!!! 分享也收藏<EMV技术学习和研究>链接: EMV技术学习和研究(一)开篇 EMV技术学习和研究(二)应用选择 EMV技术学习和研究(三)应用初始化&&读应用数据 EMV技术学习和研究(四)脱机数据认证之SDA EMV技术学习和研究(五)脱机数据认证之DDA

Linux技术学习要点,您掌握了吗---初学者必看

Linux技术学习要点,您掌握了吗---初学者必看 1.如何做好嵌入式Linux学习前的准备? 要成为一名合格的嵌入式Linux工程师,就需要系统的学习软.硬件相关领域内的知识,需要在最开始就掌握开发的规范和原则,养成良好的工作习惯.为了确保学习的效果,信盈达安排的整个课程体系是非常集中.高效的,这就要求准备参加嵌入式Linux就业课程学习的同学要调整好自己的时间,务必要确保在4个多月的学习时间内能够高度集中精力.兄弟连Linux培训. 在整个集中学习过程中,既要保证课上认真听讲.实战演练的时间

技术学习规划

技术学习规划: ? 2015年 2016年 2017年 基础理论 TCP/IP协议: ? ? ? ? ? ? ? ? ? ? ? ?<TCP/IP详解卷I>(第1遍) ? ? ? ? ? ? ? ? ? ? ? ?<图解TCP/IP>. 数据结构与算法: ? ? ? ? ? ? ? ? ? ? ? ? ?<算法> TCP/IP协议: ? ? ? ? ?<TCP/IP详解卷I>(第2遍) ? ? ? ? ? HTTP协议:<图解HTTP> 数据结构

Java多线程技术学习笔记(二)

目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和wait的区别 停止线程的方式 守护线程 线程的其他知识点 一.线程间的通信示例 返目录回 多个线程在处理同一资源,任务却不同. 假设有一堆货物,有一辆车把这批货物往仓库里面运,另外一辆车把前一辆车运进仓库的货物往外面运.这里货物就是同一资源,但是两辆车的任务却不同,一个是往里运,一个是往外运. 下面