0 0 0

科锐学习笔记-第三阶段-PE 09 RadAsm的bug和重定位表

admin
189 0

本文共计5541个字,预计阅读时长22.2分钟。

目录

RadAsm的bug  

创建程序  

1、创建程序1:C++工程:  

●项目选项:控制台"hello,World"程序,不使用预编译头 

image.png 

image.png 

image.png 

2、创建程序2:汇编工程:  

●radasm工程选项:控制台 

●将程序1中的obj拷贝至本工程中并添加至工程连接选项,用程序2调用程序1中的TestFunc函数 

image.png 

image.png 

获取并解决BUG  

1.非预期BUG:提示缺少lib,"LIBCD.LIB"、"OLDNAMES.lib"。  

●解决办法:从VC库中获取并拷贝过来。 

image.png 

2.预期BUG1:LIBCD.lib BUG:LNK2001:无法处理的外部符号 _main。  

●原因:虽然汇编当中未使用此库,但是在C++程序中使用到了C库PI"printf"。而程序2汇编没有main函数,所以就会在连接时报预期BUG。 

image.png 

●解决方案:增加"main",即把汇编程序的入口标号替换为main。 

●效果:报错消失,但程序依然报其他错误。 

image.png 

3.预期BUG2:指定函数未定义  

image.png 

●BUG探究:查找GetEnvironmentStrings函数定义(kernel32.lib),将lib包含后仍出现指定BUG。 

●BUG再探究:从kernel32.lib中查询报错API,发现对应接口不分A/W,而是将GetEnvironmentStrings作为定义,GetEnvironmentStringA作为宏,违反了我们过往对于微软命名风格的认知习惯。所以造成了预期bug2。 

image.png 

解决方案 

○(1)手动修改radasm的kernel32.inc文件; 

image.png


修改前: 
GetDriveTypeW PROTO :DWORD 
GetEnvironmentStringsA PROTO  
GetEnvironmentStrings equ <GetEnvironmentStringsA> 

修改后: 
GetDriveTypeW PROTO :DWORD 
GetEnvironmentStrings PROTO  
GetEnvironmentStringsA equ <GetEnvironmentStrings>
  •   -   (2)重新生成LIB的工具路径:..\RadASM\masm32\tools\inc2l\inc2l.exe     -   (3)将两者拷贝出来并cmd命令:inc2l.exe       kernel32.inc。     -   img     -   (4)此时目录下未见新生成lib,研究一下inc2l.exe 

分析程序:inc2l.exe 

1.OD调试 引入壳 

img 

此时点击否就可以 

img 

  •   可以看到,汇编代码十分诡异,怀疑是加了壳,我们现在的代码不是程序真正的入口点,而是壳的代码 

  •   壳的作用:对PE的保护。 

  •   壳的原理:在执行真正的PE前,壳会先跑一段对PE进行处理的壳代码 

  •   脱壳流程:1.查壳;2.有壳就去找OEP;3.dump进程 

  •   通杀pushad技巧(压缩壳和部分加密壳):ESP定律。 

2.ESP定律 

  •   所谓ESP定律就是利用了pushad的设计原理,在一开始的时候,去栈上数据进行硬件读处理,当壳执行完毕要恢复PE前,即可快速锁定OEP定位。 

3.OD分析定位OEP 

  •   (1)F8单步过pushad入栈,跟踪ESP,以DWORD类型,下硬件访问断点至第二组或第三组(王老师经验)。 

​       esp 右键,数据窗口中跟随 

img 

img 

F9 

img 

F8   入口点 

img 

脱壳 

OllyDump 

  •   1.设置:插件 → OllyDump ()→ 脱壳在当前调试的进程 (Dump process) 

  •   -   此处幺蛾子:自版本的OD只有OllyDumpEx插件,无法同步以下操作,需要使用radasm自带的OD。 

  •   img 

  •   2.选项:将EIP设为OEP,并确认转储为xxx_dump.exe。 

  •   img 

  •   用cff查看,发现导入表错误 

  •   img 

  •   3.OD调试xxx_dump.exe,发现程序无法运行。单步调试找到崩溃处,右键"长型→地址"。 

  •   img 

  •   img 

  •   4.思考原因:发现是导入表缺少了IAT,可能是由于dump后数据格式会错位,导致插件无法正确读取进行转储。。 

  •   img 

x32dbg 

解决办法:使用x32dbg重建导入表。 

  •   (1)定位指令于OEF处,并于此行设置硬件断点。这里的代码即将被改,所以下cc断点不行 

img 

可以看到代码被改了 

img 

  •   设置在内存窗口中转到→常数,并将内存窗口区设置为地址。 
  •   img 
  •   插件 → Scylla处理 → 自动搜索 → 获取导入 → Fix Dump 修复转储至原dump程序(生成xxx_dump_SCY.exe)。 
  •   img 
  •   img 
  •   在用cff查看   此时查看IA表已经导入成功。 

img 

ImportREC 

一般32位程序在32位系统中修复 

img 

修复inc2l.exe   

inc2l.exe里面编译和链接用的绝对路径,换成相对路径就可以了,还有改字符串对应长度 

修改编译选项 

img 

img 

修改链接选项 

img 

img 

重定位表 

定义:记录需要绝对地址修正的表,大多数绝对地址如果imagebase变化的话就无法使用,需要修正程序所调用的那些绝对地址。 

  •   修正方法:需要重定位的地址 + 偏移(当前基址 - PE的基址) 
  •   开了随机基址的程序才需要重定位,而DLL通常都有重定位表,因为不一定能够加载到DLL指定的ImageBase上。 

OS如何判定是否重定位? 

  •   先查看随机地址标志,标志开启,地址重定位 
  •   再查看数据目录项 5 是否位NULL,不为NULL,基址重定位。 

我们现在都是玩固定基址的PE,随机基址涉及到要修代码,如果有重定位信息,就可以在内存中随便申请一块内存,把代码放进去跑。 

我们知道随机基址需要重定位表来修代码, 那么是修什么代码呢。 

实际上我们修的是使用绝对地址的代码,例如API的调用,通过IAT调用,这里就是使用的绝对地址,当模块基址改变时,原VA地址并没有保存API函数地址,所以就需要修正到正确的位置去获取API地址。 

img设计思路 

假设以下 RVA 地址需要进行修正,最简单的方法是把下面的地址都记录下来,加载的时候直接去修正 

00001023 

00001028 

00001128 

00001228 

00001328 

00001428 

但是直接保存所有地址,那么数组的体积就会变得很大,那么如何减少体积呢 

可以按照  分页地址  +分页偏移 的方式记录,因为分页偏移只需要 2个字节就可以了 

00001000       分页基址 

0000014         总大小 

0023                分页偏移 

0028 

0128  

0228 

0328  

0428 

重定位表的结构 

  •   重定位表的位置:在数据目录的[5]项,IMAGE_BASE_RELOCATION,共8+N字节。 

IMAGE_BASE_RELOCATION


// IMAGE_BASE_RELOCATION 重定位结构体,以8字节全0结尾 
typedef struct _IMAGE_BASE_RELOCATION {       
DWORD   VirtualAddress; ;+0x00, 分页基址     
DWORD   SizeOfBlock; ;+0x04, 对应重定位数据块的大小,以字节为单位   
 // WORD TypeOffset[1] ;+0x08, 重定项位数组,个数=(SizeOfBlock-8)/2 
// TypeOffset解析:高4位两个取值--0无需重定位(多用于对齐),3需要重定位;低12位是页内偏移; 
} IMAGE_BASE_RELOCATION;       
typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION; 

image.png 

image.png 

1F000 对应文件偏移 9200 

image.png 

所有分页的数据大小合计就是  3B0  跟 结构体里面的总大小一致 

image.png 

便宜的最高位是 3 表示需要被重定位 ,  0表示不需要,代表要对齐 

OD中,有下划线的是代表修正后的地址 

image.png 

image.png 

3538 开头是3  表示是一个有效重定位项    偏移值是 538 ,分页是 12000 

image.png 

image.png 

image.png 

原基址: 10000000 

新基址: 78b80000   

偏移    : 68b80000   

地址 1001788c +  68b80000  = 78 B9 78 8C 

8c  78  b9  78 

image.png 

LoadDll  

一个PE 有重定位表, 那么我们就可以把它加载到任意地址,然后修复重定位表即可,就可以模拟    LoadLibrary 了 

user32.dll 的 MessageBoxA  要在xp中运行 win10无法运行 因为 win10的 user32.dll 需要初始化一个全局变量


.586 
.model flat,stdcall 
option casemap:none 

  include windows.inc 
  include user32.inc 
  include kernel32.inc 
  include msvcrt.inc 

  includelib user32.lib 
  includelib kernel32.lib 
  includelib msvcrt.lib 

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD 

.data 
  ;g_szDll db "Dll.dll",0 
  ;g_szFunc  db "Add",0 

  g_szDll db "user32.dll",0 
  g_szFunc  db "MessageBoxA",0 

.code 

;参数:   句柄   导出函数名 
MyGetProcAddress proc hMod:HMODULE, lpProcName:LPCSTR    

   LOCAL @pDosHdr:ptr IMAGE_DOS_HEADER          ;dos头 
   LOCAL @pNTHdr:ptr IMAGE_NT_HEADERS           ;Nt头 
   LOCAL @pExpDir:ptr IMAGE_EXPORT_DIRECTORY    ;导出表 

   LOCAL @pAddrTbl:DWORD     ;导出地址表地址 
   LOCAL @pNameTbl:DWORD     ;导出名称表地址 
   LOCAL @pOrdTbl:DWORD      ;导出序号表地址 

   ;解析 
   ;dos 头 
   mov eax, hMod 
   mov @pDosHdr, eax 

   ;nt头 
   mov esi, @pDosHdr 
   assume esi:ptr IMAGE_DOS_HEADER 
   mov eax, hMod 
   add eax, [esi].e_lfanew 
   mov @pNTHdr, eax 

   mov esi, @pNTHdr 
   assume esi:ptr IMAGE_NT_HEADERS 

   ;获取导出表 
   mov esi, @pNTHdr 
   assume esi:ptr IMAGE_NT_HEADERS 

   mov eax, [esi].OptionalHeader.DataDirectory[0].VirtualAddress 
   add eax, hMod 
   mov @pExpDir, eax 

   mov esi, @pExpDir 
   assume esi:ptr IMAGE_EXPORT_DIRECTORY 

   ;导出函数地址表 
   mov eax, [esi].AddressOfFunctions 
   add eax, hMod 
   mov @pAddrTbl, eax 

   ;导出函数名称表 
   mov eax, [esi].AddressOfNames 
   add eax, hMod 
   mov @pNameTbl, eax 

   ;导入序号表 
   mov eax, [esi].AddressOfNameOrdinals 
   add eax, hMod 
   mov @pOrdTbl, eax 

   ;判断是序号还是名称  (序号是一个 word,对于 dword来说高位都是0) 
   .if lpProcName & 0000ffffh    
       ;名称 
       mov ebx, @pNameTbl 
       xor ecx, ecx 
       .while ecx <[esi].NumberOfNames 
           ;获取名称地址 
           mov eax, [ebx+ecx*4] 
           add eax, hMod 

           ;字符串比较 
           push ecx 
           invoke crt_strcmp, lpProcName, eax 
           pop ecx 
           .if eax == 0 
               ;找到了, 从导出序号表取出函数地址下标 
               mov edi, @pOrdTbl 
               movzx eax, word ptr [edi+ecx*2] 

               ;从导入地址表,下标寻址,获取导出函数地址 
               mov ebx, @pAddrTbl 
               mov eax, [ebx+eax*4] 

               ;判断转发 。。。。解析函数名,递归判断 

               ;返回地址 
               .if eax != NULL 
                   add eax, hMod 
                   ret 
               .endif 

           .endif 

           inc ecx 
       .endw 

   .else  
       ;序号 
       mov eax, lpProcName 
       sub eax, [esi].nBase ;获取索引值 

       ;从导入地址表,下标寻址,获取导出函数地址 
       mov ebx, @pAddrTbl 
       mov eax, [ebx+eax*4] 

       .if eax != NULL 
           add eax, hMod 
           ret 
       .endif 

   .endif 

   xor eax, eax 
   ret 

MyGetProcAddress endp 

MyLoadLibary proc uses ebx ecx edx esi edi lpFileName:LPCTSTR 
    LOCAL @dwImageBase:DWORD       ;自己进程的模块基址 
   LOCAL @hFile:HANDLE            ;文件句柄 
   LOCAL @hFileMap:HANDLE         ;映射句柄 
   LOCAL @pPEBuf:LPVOID           ;映射文件的缓冲地址 
   LOCAL @pDosHdr:ptr IMAGE_DOS_HEADER      ;目标进程的dos头 
   LOCAL @pNTHdr:ptr IMAGE_NT_HEADERS       ;目标进程的NT头 
   LOCAL @pSecHdr:ptr IMAGE_SECTION_HEADER  ;目标进程的节表 
   LOCAL @dwNumOfSecs:DWORD                 ;目标进程的节表数量 
   LOCAL @pImpHdr:ptr IMAGE_IMPORT_DESCRIPTOR   ;目标进程的导入表 
   LOCAL @dwSizeOfHeaders:DWORD                 ;目标进程的选项头大小 
   LOCAL @dwOldProc:DWORD                       ;旧的内存属性 
   LOCAL @hdrZeroImp:IMAGE_IMPORT_DESCRIPTOR    ;导入表结束标志,所有项全0 
   LOCAL @hDll:HMODULE                          ;加载dll的句柄 
   LOCAL @dwOep:DWORD                           ;进程的入口地址 
   LOCAL @pReloc:ptr IMAGE_BASE_RELOCATION      ;重定位表 
   LOCAL @dwOfReloc:DWORD                       ;重定位数据块大小 
   LOCAL @dwOff:DWORD                           ;新旧的基址偏移值 

   ;判断导入表结束的标志清0 
   invoke RtlZeroMemory, addr @hdrZeroImp, size IMAGE_IMPORT_DESCRIPTOR 

   ;解析PE文件,获取表 

   ;打开文件 
   invoke CreateFile, lpFileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL 
   ;check .... 
   mov @hFile, eax   ;保存文件句柄 

   invoke CreateFileMapping, @hFile, NULL, PAGE_READONLY, 0, 0, NULL   ;创建文件映射 
   ;check 
   mov @hFileMap, eax    ;创建文件映射句柄 

   invoke MapViewOfFile, @hFileMap, FILE_MAP_READ, 0, 0, 0      ;将整个文件映射进内存 
   ;check  
   mov @pPEBuf, eax      ;保存映射文件内存的地址 

   ;解析目标进程 

   ;目标进程的 dos 头 
   mov eax, @pPEBuf       
   mov @pDosHdr, eax 

   ;目标进程的 nt头 
   mov esi, @pDosHdr 
   assume esi:ptr IMAGE_DOS_HEADER 
   mov eax, @pPEBuf 
   add eax, [esi].e_lfanew   ;获取nt头的偏移地址 
   mov @pNTHdr, eax 

   mov esi, @pNTHdr 
   assume esi:ptr IMAGE_NT_HEADERS 

   ;选项头信息 
   mov eax, [esi].OptionalHeader.SizeOfHeaders    ;获取选项头大小 
   mov @dwSizeOfHeaders, eax 

   invoke VirtualAlloc, NULL, [esi].OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE 
   mov @dwImageBase, eax 

   sub eax, [esi].OptionalHeader.ImageBase 
   mov @dwOff, eax ;新旧ImageBase的偏移差 

   ;进程的入口地址  =  进程的内存偏移地址 + 模块基址 
   mov eax, [esi].OptionalHeader.AddressOfEntryPoint 
   add eax, @dwImageBase 
   mov @dwOep, eax 

   ;节表  地址: 选项头地址+大小 
   movzx eax, [esi].FileHeader.NumberOfSections 
   mov @dwNumOfSecs,eax 

   lea ebx, [esi].OptionalHeader 

   ;获取选项头大小:用于定位节表位置=选项头地址+选项头大小 
   movzx eax, [esi].FileHeader.SizeOfOptionalHeader   ;把 word 转为 dword 
   add eax, ebx 
   mov @pSecHdr, eax   ;保存节表地址 

   ;拷贝PE头  从映射内存拷贝到 自己进程的最开始处  
   invoke crt_memcpy, @dwImageBase, @pPEBuf, @dwSizeOfHeaders 

   ;按照节表,拷贝节区数据 
   mov esi, @pSecHdr 
   assume esi:ptr IMAGE_SECTION_HEADER 
   xor ecx, ecx 
   .while ecx <@dwNumOfSecs   ;遍历节表 
       ;目标 
       mov edi, @dwImageBase 
       add edi, [esi].VirtualAddress  ;获取节的内存地址 + 模块地址 就是内存中的绝对地址 

       ;源 
       mov ebx, @pPEBuf 
       add ebx, [esi].PointerToRawData  ;获取指定进程的节数据的偏移地址  映射的首地址 + 文件偏移地址 

       ;大小[esi].SizeOfRawData 

       ;拷贝  注意,很多 C 库函数 并不会 保存 ecx ,edx 环境,自己使用前记得先保存 
       push ecx 
       push edx 
       invoke crt_memcpy, edi, ebx, [esi].SizeOfRawData   ;将目标进程的节数据拷贝进自己的进程 
       pop edx 
       pop ecx 

       inc ecx      ;计数++ 
       add esi, size IMAGE_SECTION_HEADER  ;指针移动 
   .endw 

   ;获取导入表  如果在前面获取导入表信息,那么就需要对内存地址和文件地址做转化比较麻烦 
               ;但是把数据拷贝到我们进程之后只需要访问内存进程就可以了 
   mov esi, @pNTHdr 
   assume esi:ptr IMAGE_NT_HEADERS 

   ;获取导入表地址 ,数组的第二个元素的第一个成员  
   mov eax, [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT*8].VirtualAddress 
   add eax, @dwImageBase   ;获取导入表在进程的绝对地址  内存偏移 + 模块基址 
   mov @pImpHdr, eax       ;保存导入表的地址 

   ;处理导入表 
   mov esi, @pImpHdr 
   assume esi:ptr IMAGE_IMPORT_DESCRIPTOR 

   .while TRUE     ;遍历导入表 

       ;判断结束,全0项结束 
       invoke crt_memcmp, esi, addr @hdrZeroImp 
       .if eax == 0 
           .break 
       .endif 

       ;判断字段,为空则结束 
       .if [esi].Name1 == NULL || [esi].FirstThunk == NULL 
           .break 
       .endif  

      ;加载dll 
       mov eax, [esi].Name1 
       add eax, @dwImageBase 
       push ecx 
       push edx 
       invoke LoadLibrary, eax   ;根据dll名加载 dll  
       pop edx 
       pop ecx 
       ;check              如果此时为空加说明无法找到dll 
       mov @hDll, eax      ;保存dll的模句柄 

       ;获取导入地址表,IAT 
       mov ebx, [esi].FirstThunk 
       add ebx, @dwImageBase 

       ;获取导入名称表,INT 
       mov edi, ebx 
       .if [esi].OriginalFirstThunk != NULL 
           mov edi, [esi].OriginalFirstThunk 
           add edi, @dwImageBase             
       .endif 

       ;遍历导入名称表 
       .while dword ptr [edi] != 0 

           .if dword ptr [edi] & 80000000h   ;判断最高位是否为1 
               ;序号导入,获取序号 
               mov edx, dword ptr [edi] 
               and edx, 0ffffh               ;获取低 word  
           .else 
               ;名称导入 
               mov edx, dword ptr [edi] 
               add edx, @dwImageBase 
               add edx, 2                  ;名称前面有2个无用字节 
           .endif 

           ;获取dll导入函数进程加载后地址 
           push ecx 
           push edx 
           invoke GetProcAddress, @hDll, edx 
           pop edx 
           pop ecx 
           ;check 

           ;把地址存入 INT 表 
           mov dword ptr [ebx], eax 

           add ebx, 4 
           add edi, 4 
       .endw 

       add esi, size IMAGE_IMPORT_DESCRIPTOR 
   .endw 

   ;处理重定位表 
   mov esi, @pNTHdr 
   assume esi:ptr IMAGE_NT_HEADERS 

   ;定位重定位表 
   mov eax, [esi].OptionalHeader.DataDirectory[5 * 8].VirtualAddress 
   add eax, @dwImageBase 
   mov @pReloc, eax 

   mov eax, [esi].OptionalHeader.DataDirectory[5 * 8].isize 
   mov @dwOfReloc, eax 

   xor ecx, ecx 
   mov esi, @pReloc 
   assume esi:ptr IMAGE_BASE_RELOCATION 

   .while ecx <@dwOfReloc 
       push ecx 

       ;数组首地址 
       mov ebx, esi 
       add ebx, 8 

       ;数组元素个数 
       mov ecx, [esi].SizeOfBlock 
       sub ecx, 8 
       shr ecx, 1    ;除以2就是右移1位 

       ;遍历数组 
       xor edx, edx 
       .while edx 

我们的  MyLoadLibary   去调系统   GetProcAddress  会崩,什么都拿不到,因为  GetProcAddress  有检查,他会取peb的 ldr 的链表中去验证dll在不在里面,是不是一个dll,如果不是那他就不去分析导入表,如果想要系统加载信息,就要把我们的信息加到peb里面去 

我们可以通过这这种方式把一个 exe 或者一个dll 注入到另一个进程里面去,在另一个进程申请一块内存,把节数据拷贝进去,把导入表和重定位表处理一下


最新回复 ( 0 )
全部楼主