大理寺少卿 发表于 2025-3-9 17:00:15

WindowsPE文件格式入门09.RadAsm的bug和重定位表

RadAsm的bug

创建程序

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

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

!(./mdimg/pe/1656423076948-d456e364-8c17-4fa3-bb8e-32c34c81353b.png)

!(./mdimg/pe/1656423104999-bc284df4-df54-498e-a4ef-d3dbb2615565.png)

!(./mdimg/pe/1656423123677-0c8550fd-3573-4ce0-b7aa-ab9c7fa2e768.png)

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

●radasm工程选项:控制台

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

!(./mdimg/pe/1656423407823-f3aed2c2-5b08-4039-b872-65d3e1c56887.png)

!(./mdimg/pe/1656423374482-94b9f277-f599-4857-ad08-b326f26c35be.png)

获取并解决BUG

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

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

!(./mdimg/pe/1637112664347-e263bd02-52bc-48ea-ae56-fba0c5db0011.png)

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

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

!(./mdimg/pe/1637112886778-723f1578-c948-4488-8371-41eebf7ac119.png)

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

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

!(./mdimg/pe/1656423643569-907552fd-5ab8-42e3-a59d-6f1a700a5fb5.png)

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

!(./mdimg/pe/1637113753131-b9712e90-5069-462a-8159-94da28ce86aa.png)

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

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

!(./mdimg/pe/1656424116970-caa5131d-121c-45ba-90ea-e592e5e5140d.png)

**●**解决方案

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

!(./mdimg/pe/1637114902608-36a41b12-1ebf-4877-905f-4044fabfadcd.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。
- !(./mdimg/pe/1656424551512-f5c1b10a-d794-4622-825f-73c65949b772.png)
- **(4)**此时目录下未见新生成lib,研究一下inc2l.exe

### 分析程序:inc2l.exe

#### 1.OD调试 引入壳

!(./mdimg/pe/1656426597170-61ba9aa8-9d75-4551-8077-ad62cc967cd6.png)

此时点击否就可以

!(./mdimg/pe/1656426813667-225f98d3-b23d-4195-ac12-a16c4e81be48.png)

- 可以看到,汇编代码十分诡异,怀疑是加了壳,我们现在的代码不是程序真正的入口点,而是壳的代码
- **壳的作用:**对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 右键,数据窗口中跟随
```


!(./mdimg/pe/1656427386028-a3e7b44c-e659-48d2-9534-af3bc09a2e88.png)

!(./mdimg/pe/1637116959909-f9443d7f-646a-4865-88cd-a5ca73049afa.png)

F9

!(./mdimg/pe/1656427899370-b7e03ed4-9249-41c2-b29c-8832dfd328b5.png)

F8   入口点

!(./mdimg/pe/1656428110502-73f6f7ef-5524-469a-814d-0537a7aae38e.png)

### 脱壳

#### OllyDump

- 1.设置:插件 → OllyDump ()→ 脱壳在当前调试的进程 (Dump process)
- - 此处幺蛾子:自版本的OD只有OllyDumpEx插件,无法同步以下操作,需要使用radasm自带的OD。
- !(./mdimg/pe/1637118022834-8c53d86c-4664-4637-a564-0b0528910add.png)
- 2.选项:将EIP设为OEP,并确认转储为xxx_dump.exe。
- !(./mdimg/pe/1656428376007-2055b6bb-e657-41ed-a22d-173210795ef1.png)
- 用cff查看,发现导入表错误
- !(./mdimg/pe/1656428545572-07dbed44-659a-42ba-ac9c-05e8b4f2092b.png)
- 3.OD调试xxx_dump.exe,发现程序无法运行。单步调试找到崩溃处,右键"长型→地址"。
- !(./mdimg/pe/1637119448326-f9aa054a-1b9d-40e5-8bcd-fe1890e84bae.png)
- !(./mdimg/pe/1637119684271-e2ca022b-561a-473c-a3dd-a59cdc337154.png)
- 4.思考原因:发现是导入表缺少了IAT,可能是由于dump后数据格式会错位,导致插件无法正确读取进行转储。。
- !(./mdimg/pe/1637119809522-e09a3168-ee8e-48c5-93d0-5c6646c4ef70.png)

#### x32dbg

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

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

!(./mdimg/pe/1656429140310-0a30adcc-5217-4e5a-a7a0-4ef6e116bef9.png)

可以看到代码被改了

!(./mdimg/pe/1656429080819-507e45ef-a126-4d2b-bd08-c3480cd762c1.png)

- 设置在内存窗口中转到→常数,并将内存窗口区设置为地址。
- !(./mdimg/pe/1656429435925-c42efc93-feae-41a2-9879-178e395b99b5.png)
- 插件 → Scylla处理 → 自动搜索 → 获取导入 → Fix Dump 修复转储至原dump程序(生成xxx_dump_SCY.exe)。
- !(./mdimg/pe/1637120808780-4681f1c7-613d-4ef9-8687-0dfe0bbc7b98.png)
- !(./mdimg/pe/1656429536217-c1434741-a1b9-4d3e-8bfd-abc75d2ebc47.png)
- 在用cff查看   此时查看IA表已经导入成功。

!(./mdimg/pe/1656429588940-ec944f78-25a1-46bc-964e-02a01a8a9114.png)

#### ImportREC

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

!(./mdimg/pe/1656430287101-f4e868cb-a50f-4573-8f93-825c09c15267.png)

### 修复inc2l.exe

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

修改编译选项

!(./mdimg/pe/1656487319093-4ad5ca85-8d9e-40bf-8e92-b9401e90058e.png)

!(./mdimg/pe/1656487570233-bb689aee-d245-440b-b876-a96dcc0d61c0.png)

修改链接选项

!(./mdimg/pe/1656488385086-51b9a1b8-87e4-4460-bfb6-d004001e94f8.png)

!(./mdimg/pe/1656488242541-abc312a8-7301-4a49-af33-f12fe03e682f.png)

## 重定位表

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

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

### OS如何判定是否重定位?

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

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

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

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

### !(./mdimg/pe/1646895051442-ae0bc9ba-92ff-4616-95df-4e64a184e3cf.png)设计思路

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

00001023

00001028

00001128

00001228

00001328

00001428

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

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

00001000       分页基址

0000014         总大小

0023                分页偏移

0028

0128

0228

0328

0428

### 重定位表的结构

- 重定位表的位置:在数据目录的项,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        ;+0x08, 重定项位数组,个数=(SizeOfBlock-8)/2
        // TypeOffset解析:高4位两个取值--0无需重定位(多用于对齐),3需要重定位;低12位是页内偏移;
} IMAGE_BASE_RELOCATION;                                       
typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION;
```

!(./mdimg/pe/1656432605134-0d5cae8b-36ba-4764-9e65-88f093517096.png)

!(./mdimg/pe/1656432750448-55222ca4-ed14-499a-bb0e-c19f0c0c1f8a.png)

1F000 对应文件偏移 9200

!(./mdimg/pe/1656432913616-97ddd735-88e5-49e9-9331-9152fd31d766.png)

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

!(./mdimg/pe/1656433362015-bc6b9a75-05df-41d3-8671-3e0da29670d4.png)

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

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

!(./mdimg/pe/1656433566431-19011055-caaa-49af-a5a2-141100969673.png)

!(./mdimg/pe/1656434543557-21b79675-5517-4459-b699-e06965640ffd.png)

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

!(./mdimg/pe/1656434737018-16256b35-811b-4d73-a084-4b477fa2d01c.png)

!(./mdimg/pe/1656434808377-912648af-3ce3-4a46-83c4-f98dec34ce2d.png)

!(./mdimg/pe/1656435226000-15fe721a-b741-4a21-bc68-438e4705d358.png)

原基址: 10000000

新基址: 78b80000

偏移    : 68b80000

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

8c78b978

!(./mdimg/pe/1656434971438-acd99539-7e70-4e8b-b44b-e3eccb6e4c79.png)

LoadDll

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

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

```assembly
.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_szFuncdb "Add",0
   
   g_szDll db "user32.dll",0
   g_szFuncdb "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, .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, .OptionalHeader.DataDirectory.VirtualAddress
    add eax, hMod
    mov @pExpDir, eax

    mov esi, @pExpDir
    assume esi:ptr IMAGE_EXPORT_DIRECTORY

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

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


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


    ;判断是序号还是名称(序号是一个 word,对于 dword来说高位都是0)
    .if lpProcName & 0000ffffh   
      ;名称
      mov ebx, @pNameTbl
      xor ecx, ecx
      .while ecx < .NumberOfNames
            ;获取名称地址
            mov eax,
            add eax, hMod
         
            ;字符串比较
            push ecx
            invoke crt_strcmp, lpProcName, eax
            pop ecx
            .if eax == 0
                ;找到了, 从导出序号表取出函数地址下标
                mov edi, @pOrdTbl
                movzx eax, word ptr
            
            
                ;从导入地址表,下标寻址,获取导出函数地址
                mov ebx, @pAddrTbl
                mov eax,
            
                ;判断转发 。。。。解析函数名,递归判断
            
            
                ;返回地址
                .if eax != NULL
                  add eax, hMod
                  ret
                .endif
            
            .endif
         
            inc ecx
      .endw
      

    .else
      ;序号
      mov eax, lpProcName
      sub eax, .nBase ;获取索引值
      
      ;从导入地址表,下标寻址,获取导出函数地址
      mov ebx, @pAddrTbl
      mov eax,
      
      .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, .e_lfanew   ;获取nt头的偏移地址
    mov @pNTHdr, eax


    mov esi, @pNTHdr
    assume esi:ptr IMAGE_NT_HEADERS

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

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

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

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


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

    lea ebx, .OptionalHeader

    ;获取选项头大小:用于定位节表位置=选项头地址+选项头大小
    movzx eax, .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, .VirtualAddress;获取节的内存地址 + 模块地址 就是内存中的绝对地址
      
      ;源
      mov ebx, @pPEBuf
      add ebx, .PointerToRawData;获取指定进程的节数据的偏移地址映射的首地址 + 文件偏移地址
      
      ;大小.SizeOfRawData
      
      ;拷贝注意,很多 C 库函数 并不会 保存 ecx ,edx 环境,自己使用前记得先保存
      push ecx
      push edx
      invoke crt_memcpy, edi, ebx, .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, .OptionalHeader.DataDirectory.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 .Name1 == NULL || .FirstThunk == NULL
            .break
      .endif
   
   
       ;加载dll
      mov eax, .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, .FirstThunk
      add ebx, @dwImageBase
      
      ;获取导入名称表,INT
      mov edi, ebx
      .if .OriginalFirstThunk != NULL
            mov edi, .OriginalFirstThunk
            add edi, @dwImageBase         
      .endif
      
      
      ;遍历导入名称表
      .while dword ptr != 0
         
            .if dword ptr & 80000000h   ;判断最高位是否为1
                ;序号导入,获取序号
                mov edx, dword ptr
                and edx, 0ffffh               ;获取低 word
            .else
                ;名称导入
                mov edx, dword ptr
                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 , 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, .OptionalHeader.DataDirectory.VirtualAddress
    add eax, @dwImageBase
    mov @pReloc, eax

    mov eax, .OptionalHeader.DataDirectory.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, .SizeOfBlock
      sub ecx, 8
      shr ecx, 1    ;除以2就是右移1位
      
      ;遍历数组
      xor edx, edx
      .while edx < ecx
            ;取出一项
            movzx eax, word ptr
         
            ;判断是否是有效重定位项
            .if eax & 00003000h
                ;修正
                and eax, 0fffh ;页偏移
                add eax, .VirtualAddress ;RVA
                add eax, @dwImageBase;VA
            
                mov edi, @dwOff
                add dword ptr , edi
            .endif

            inc edx
      .endw
   
      pop ecx
      ;处理下一个分页   
      add ecx, .SizeOfBlock
      add esi, .SizeOfBlock
    .endw

    ;调用dllmain
    push 0
    push DLL_PROCESS_ATTACH
    push @dwImageBase
    call @dwOep

    mov eax, @dwImageBase
    ret

MyLoadLibary endp

start:
    invoke MyLoadLibary, offset g_szDll
    invoke MyGetProcAddress,eax, offset g_szFunc

    push MB_OK
    push offset g_szDll
    push offset g_szFunc
    push NULL
    call eax
    invoke ExitProcess,0



end start

```

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

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

### API模拟

```
api 模拟   在对抗中一般用来加载系统dll,防止别人下api断点
```


- 原理:IAT表填函数地址的时候不是填的系统DLL,自己处理,将IAT表中函数地址填自己加载的DLL导出函数地址(自己将系统DLL加载到堆内存里)
- - 一般不会装载全部的DLL,而是将需要的函数做处理后装载到堆里。
- 将API模拟到堆内,堆内获取地址调用。
- 新的注入方式,远程线程调用LoadDll,加载Dll。

注意:

1. 并不是所有API都能模拟。比如Kernel32 和 ntdll
2. 1. 优点:修导入表的时候特别难修

申请内存,拷贝数据,修复表这些行为太明显了,例如系统dll 要掉 createfile 这个 api ,他就把这块拷出来(从头到ret),申请一块内存,放在里面,再把里面信息,偏移,重定位等信息处理一下,这种还有可能看出来,更好的方法就是把数据放到节里面
页: [1]
查看完整版本: WindowsPE文件格式入门09.RadAsm的bug和重定位表