【问题标题】:C: How can I check if a memory address has been written?C:如何检查内存地址是否已写入?
【发布时间】:2020-07-30 16:15:46
【问题描述】:

C 中有没有办法检查我自己的进程的内存地址是否被写入?
例如:如果我的程序接受一个放在缓冲区中的输入,我是否可以通过检查缓冲区的下一个地址是否已写入来检查输入是否溢出缓冲区?
我知道我可以检查缓冲区的下一个地址的内容并验证它是否已被修改,但是这样对我正在处理的工作不利...

我正在研究一种保护措施,以防出现缓冲区溢出和格式字符串错误

【问题讨论】:

  • 严格来说,一旦发生缓冲区溢出(或任何 UB),所有赌注都将关闭。处理缓冲区溢出的唯一方法是确保它不会发生。
  • 我知道,但我的问题是不同的......
  • 您可以用某个值(0x00, 0xff ?)填充缓冲区,然后“使用”该缓冲区的一个子集 - 然后检查您的预设值是否已被覆盖超出您认为应该的位置.
  • 是的,我想过,但如果存在字符串错误格式漏洞,我可以读取堆栈的内容并输入准确的值(0x00、0xff)。因此,这种类型的控制不起作用。
  • @Mark 您需要详细说明您对“字符串错误格式漏洞”的最后评论。甚至可能是edit 你的问题并包括那个那里。您还应该告诉我们“您在做什么”。

标签: c stack memory-address buffer-overflow


【解决方案1】:

我知道我可以检查缓冲区的下一个地址的内容并验证它是否已被修改,但是这样对我正在处理的工作不利...

不幸的是,在没有平台和实现特定扩展的情况下使用 C 时唯一可能。

但是,有些 C 编译器可以生成机器代码,这些代码将在这些类型的上下文中检测 许多 缓冲区溢出 - 例如,GCC 有开​​关 -fsanitize=undefined-fsanitize=address 可以一起使用当发生缓冲区溢出时,它将向终端输出诊断信息。它们使程序运行速度慢得多。或者,您可以使用valgrind 来运行程序,它也可以调试多种分配缓冲区溢出的情况。

【讨论】:

    【解决方案2】:

    添加到您的研究中:

    如果在支持mprotect 调用的Linux 平台上进行编程并且仅处理动态分配时,您可以将内存过度分配到至少1 页以上,mprotect 在您返回的内存之后作为动态分配的内存。

    例如:

    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <assert.h>
    #include <string.h>
    #include <stdint.h>
    #include <stdalign.h>
    #include <sys/mman.h>
    #include <stddef.h>
    #include <signal.h>
    
    #if PROTECT_ME
    void *malloc2(size_t size) {
        const size_t maxalign = _Alignof(max_align_t);
        assert(size < getpagesize() - sizeof(size_t) - maxalign); // roughly, could be TODO
    
        // allocate memory
        char * const pnt = mmap(NULL, getpagesize() + 1,
            PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        assert(pnt != (void*)-1);
        assert((uintptr_t)pnt % getpagesize() == 0);
    
        // calculate a pointer right after the returned pointer that has
        // to be aligned
        char * const end = pnt + getpagesize();
        char * const beg = end - size - (size % maxalign);
    
        // I store size in the memory before the data
        memcpy(beg - sizeof(size), &size, sizeof(size));
    
        // protect the data behind the pointer
        assert((uintptr_t)end % getpagesize() == 0);
        const int err = mprotect(end, 1, 0);
        assert(err == 0);
    
        return beg;
    }
    
    void free2(void *beg) {
        // extract size for memory preceding the data
        size_t size;
        memcpy(&size, beg - sizeof(size), sizeof(size));
    
        // calculate mmap returned pointer
        const size_t maxalign = _Alignof(max_align_t);
        char * const end = beg + size + (size % maxalign);
        char * const pnt = end - getpagesize();
    
        assert((uintptr_t)pnt % getpagesize() == 0);
        int err = munmap(pnt, getpagesize() * 2);
        assert(err == 0);
    }
    
    void signal_segv(int a) {
        fprintf(stderr, "The process had written to a bad place\n");
        abort();
    }
    
    __attribute__((__constructor__))
    void _init_me(void) {
        signal(SIGSEGV, signal_segv);
    }
    
    #define malloc malloc2
    #define free free2
    
    #endif
    
    
    int main() {
        int *a = malloc(5 * sizeof(*a));
        for (int i = 0; i < 10; ++i) {
            a[i] = i;  // will write out-of-bounds for a array when i == 5
            fprintf(stderr, "a[%d]=%d\n", i, a[i]);
        }
        free(a);
    }
    

    使用gcc 编译并在没有保护的情况下运行:

    $ gcc 1.c && ./a.out
    a[0]=0
    a[1]=1
    a[2]=2
    a[3]=3
    a[4]=4
    a[5]=5
    a[6]=6
    a[7]=7
    a[8]=8
    a[9]=9
    

    但是在平台上启用保护后,在向数组写入“足够”(对齐)越界时应该生成 SIGSEGV:

    $ gcc -DPROTECT_ME=1 1.c && ./a.out
    a[0]=0
    a[1]=1
    a[2]=2
    a[3]=3
    a[4]=4
    a[5]=5
    The process had written to a bad place
    Aborted (core dumped)
    

    【讨论】:

    • 这真是个好主意。但不幸的是我不能使用动态分配。我必须保护堆栈,或者至少保护堆栈中缓冲区之后的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 2014-11-27
    • 1970-01-01
    • 2013-10-11
    • 1970-01-01
    • 2012-11-10
    • 2022-07-27
    相关资源
    最近更新 更多