代码之家  ›  专栏  ›  技术社区  ›  Martin Ba

在Windows XP下用普通C++程序覆盖代码段是可能的吗?

  •  3
  • Martin Ba  · 技术社区  · 14 年前

    我正在调查一个windows崩溃转储,Visual Studio调试器显示了一个“非法指令 0xC000001D “打开转储文件时。显示此错误的代码位置显示了以下示例中的反汇编:

     void g(int x) {
     00401E80  push        ebp  
     00401E81  mov         ebp,esp 
        if(x > 20) {
     00401E83  cmp         dword ptr [x],14h 
     00401E87  jle         g+14h (401E94h) 
            x *= 4;
    >00401E89  db          0fh  // illegal instruction here
     00401E8A  db          0fh  
     00401E8B  xadd        eax,esp 
     00401E8E  add         cl,byte ptr [ecx+9EB0845h] 
            x += 42;
     00401E94  mov         ecx,dword ptr [x]
     ...
    

    我在调试器中手动创建了上述示例,方法是在调试器内存窗口中用一些无效值覆盖函数代码,但我正在调查的崩溃转储显示了相同的结果 db 0fh 条目,显然指示无效指令。该代码也类似于我的转储文件所显示的内容,即在无效指令之前的指令似乎都是有效的,并且与源代码匹配。

    现在 问题是 在一个正常编译的C++程序中,在内存页面访问限制(Windows XP上的VisualC++ 2005)中是否会弄乱进程的代码段,这是完全可能的吗?

    如果我试图从代码写入上述示例中的函数地址,则总是会遇到访问冲突,即代码段内存页似乎受到写保护。

    {
        void* fnAddr = &g; // non-portable but OK in VC++
        unsigned int x = 0xDEADBEEF;
        // Simulate memory corruption: Try to write something to the code segment:
        memcpy((char*)fnAddr+4, &x, sizeof(x)); // generated 0xC0000005 Access Violation
        g(42); // call messed up function - never get here
    }
    

    您是否知道在任何情况下,实际上可能会无意中覆盖代码段中的某些内容?

    我要补充的是,真正的程序要复杂得多,有很多虚拟函数,一些成员函数指针等等。不幸的是,这个问题是不可复制的,我们目前只有一个转储文件,看起来很好,否则。--尽管如此,转储文件在代码段中显示了一条非法指令,我不认为有可能弄乱代码段。

    4 回复  |  直到 14 年前
        1
  •  3
  •   Hans Passant    14 年前

    不,包含代码的内存页是写保护的。这种损坏只能在进程初始化时发生。但更可能的来源是软RAM错误。要求客户运行RAM测试程序。认为文件损坏是可重复的错误。

        2
  •  1
  •   ruslik    14 年前

    默认情况下 .text 部门有 RX 访问权限。如何,使用 VirtualProtect 您可以获得这些内存页的写入权限。但是,看看这个。。

    我想这应该是代码:

    code:00401000 55                                push    ebp
    code:00401001 89 E5                             mov     ebp, esp
    code:00401003 81 7D 08 14 00 00+                cmp     dword ptr [ebp+8], 14h
    code:0040100A 7E 0B                             jle     short loc_401017
    code:0040100A                   ; ---------------------------------------------------------------------------
    code:0040100C 0F                                db  0Fh // here should be x += 10.5; ??
    code:0040100D 0F                                db  0Fh 
    code:0040100E 0F                                db  0Fh 
    code:0040100F                   ; ---------------------------------------------------------------------------
    code:0040100F C1 E0 02                          shl     eax, 2 ; //x *= 4
    code:00401012
    code:00401012                   loc_401012:
    code:00401012 89 45 08                          mov     [ebp+8], eax // save x
    code:00401015 EB 09                             jmp     short near ptr unk_401020
    code:00401017                   ; ---------------------------------------------------------------------------
    code:00401017
    code:00401017                   loc_401017:                             ; CODE XREF: code:0040100Aj
    code:00401017 8B 4D 08                          mov     ecx, [ebp+8]
    

    如果我猜到了操作码 丢失的 部分应该在eax中添加10.5,这是不太可能的。你能试着用好奇心来做吗

    if (x > 20){
          x *= 4; 
          x += 40;
    }; 
    

    你能把整个 g(int) 函数及其asm列表?(如果可能,使用二进制操作码值,如我的示例所示)。

        3
  •  1
  •   Jon Trauntvein    14 年前

    最有可能造成困难的原因是溢出了一个局部变量(比如字符串)并覆盖了堆栈帧。当子例程退出时,它将从堆栈中弹出返回地址,并开始执行它在那里找到的任何指令。这是利用缓冲区溢出攻击的机制。

        4
  •  0
  •   Alex Brown    14 年前

    是的,在应用程序使部分代码段可写的情况下,例如在Java JITs中,Java字节码被动态编译为本机代码。