为什么我可以访问 OllyDbg 中的 FS:[0] 但不能访问 CS,DS,SS 的偏移量?

逆向工程 ollydbg 恶意软件 风袋 补丁反转
2021-07-03 09:47:30

我可以访问指向 SEH 链的 FS:[0h] 但不能对其他段寄存器执行相同的操作。这是什么原因?

另外,我正在调试一个 exe,它的 PTRD 为 0x600,AEP 为 0x1000(与 PTRD 相同),但在偏移量 0x400 处我看到了一些我在其他一些文件中也看到过的指令。在我的示例中,它是无法访问的代码,但在我使用 WINASM/MASM 编写的一个测试 exe 中,它是相同的指令序列,只是有更多的指令。如果我将 AEP 更改为 400,则程序集如下:

00400400    6A 00           PUSH 0  
00400402    68 05304000     PUSH 00403005  
00400407    68 00304000     PUSH 00403000  
0040040C    6A 00           PUSH 0  
0040040E    E8 17040000     CALL 0040082A  
00400413    6A 00           PUSH 0  
00400415    E8 16040000     CALL 00400830  
0040041A    E8 17040000     CALL 00400836  
0040041F    E8 1E040000     CALL 00400842  
00400424    E8 13040000     CALL 0040083C  
00400429    C3              RETN     

我在 MASM 中编写了一个 Hello world 程序,它的 AEP 为 0x1000,代码看起来很相似,但我可以调试它,即它不像以前的那样无法访问/死代码。它如下:

00401000    6A 00           PUSH 0                                   
00401002    68 00304000     PUSH OFFSET 00403000                     
00401007    68 06304000     PUSH OFFSET 00403006                     
0040100C    6A 00           PUSH 0                                   
0040100E    E8 0D000000     CALL <JMP.&user32.MessageBoxA>           
00401013    A3 14304000     MOV DWORD PTR DS:[403014],EAX
00401018    33C0            XOR EAX,EAX
0040101A    50              PUSH EAX                                 
0040101B    E8 06000000     CALL <JMP.&kernel32.ExitProcess>         
00401020    FF25 08204000   JMP DWORD PTR DS:[<&user32.MessageBoxA>]
00401026    FF25 00204000   JMP DWORD PTR DS:[<&kernel32.ExitProcess 

所以我的问题是代码怎么会在我的示例中以 0x400 的偏移量出现,这段代码有什么用处,是否有一些编译器将它放在那里?

注意:它是病毒样本,我是初学者。感谢您提前回答...

2个回答

在 Windows 中,进程希望看到的总空间在 CS、DS 和 SS 寄存器中是相同的。Windows 将可执行文件以及任何 DLL、打开的文件、分配的内存段等放入此地址范围,并且您将看到相同的内存 - 以及相同的内容 - 无论您是否访问 cs:[something] , ds:[something] 或 ss:[something]*。

在 32 位系统上,这个“大空间”中的可达内存为 4 GB。

FS不同;它是正常地址空间之外的一个单独的内存部分。您无法使用任一普通寄存器访问该内存。它的大小只有几个字节 - 例如,您无法访问 FS:[100000h],因为 PEB 并没有那么大。

回到线性 4 GB 空间:4 GB“可达”的事实来自 32 位指针 (2^32);但这并不意味着该范围内的每个地址实际上都映射到物理内存。每当 Windows 需要一块内存时,它就会在该空间中选择一个地址,将物理内存映射到它,并使用该内存。例如,当一个可执行文件被加载时,windows 总是会**将它加载到400000h. 这就是为什么你的程序开始于401000h- 你的入口点1000h 是相对于加载地址,而不是 0。或者,当 Windows 加载一个 DLL 时,它将80000000以为 ASLR),并使用该地址。如果您的 DLL 的入口点在1000h,并在 加载63000000h,则执行将从 开始63001000h

您无法访问 cs:[0] 或 ds:[0] 的事实是故意的 - Windows 不会将任何内容映射到地址空间的第一个字节以使您的程序崩溃(技术上:抛出执行)如果它尝试通过 NULL 指针访问内存。这也是可执行文件在内存中加载更高的原因,您不希望空指针访问意外成功。

接下来要考虑的是windows如何加载 PE 文件 - 而不是解析文件,并且只将区域复制到实际使用的内存中,它只是将整个文件中的内容拖到起始地址***。这意味着,PE 标头被加载到加载地址。您看到的伪代码只是 PE 标头的一部分,恰好400h位于 PE 文件中的偏移处

为了熟悉“大”cs/ds/ss 段的内存布局,我推荐vmmap使用 sysinternals 集合中工具;将它附加到一些正在运行的进程并检查它们的内存映射。与任何理论解释相比,细节可能会变得更加清晰。

(*) 但是,根据访问权限,您可能不允许写入该空间中映射到代码的部分,或执行映射到数据的部分。

(**) 对于总是有点模糊的定义,细节现在并不重要

(***) 从技术上讲,它内存映射文件并依赖分页子系统从磁盘读取它,但同样,现在这并不重要。

如果使用链接器开关 /ALIGN:16,可执行代码可能从 base+0x2b0 开始

在此处输入图片说明

编辑

死代码可能是一个被编译和链接但从未调用的函数,如果你像这样修改上面的 src 并编译第一次调用 messagebox 将在二进制文件中可用但没有人会引用它

#include <windows.h>
#pragma comment(lib , "user32.lib")
void deadcode(void)
{
  MessageBox(0,"iam dead","yep deady dead deady dead",0);
}
void main (void) {
  MessageBoxA(0,"disme to see me low",
  "using align 16 and merge sections iam slim(e)y",
  0);
} 

这是从未被引用但仍然存在的死代码

>cdb -c "x msgbox!*;q" msgbox.exe | grep main
004002d0 msgbox!main (void)

>cdb -c "u msgbox+2d0-20;q" msgbox.exe | grep -A 4 55
004002b0 55              push    ebp
004002b1 8bec            mov     ebp,esp
004002b3 6a00            push    0
004002b5 680c024000      push    offset msgbox!⌂USER32_NULL_THUNK_DATA+0x28 (004
0020c)
004002ba 6828024000      push    offset msgbox!⌂USER32_NULL_THUNK_DATA+0x44 (004
00228)

>cdb -c "da msgbox+20c;q" msgbox.exe | grep dead
0040020c  "yep deady dead deady dead"