Skip to content

Pwnable kr Note

复制本地路径 | 在线编辑

前言

竟然在开组会的时候不小心写出来了,感谢无私分享解法的一位外国小哥。

程序保证不开 ASLR ,也就是说栈地址固定。

漏洞点分析

漏洞点 1

首先,程序有一个明显的 One-byte overflow ,精简代码后如下:

void select_menu(){
    // menu
    char command[1024];
    int menu;

    scanf("%d", &menu);
    switch(menu){
        case 0x31337: 
            printf("welcome to hacker's secret menu\n"); 
            printf("i'm sure 1byte overflow will be enough for you to pwn this\n"); 
            fgets(command, 1025, stdin); 
            break;
    }
    select_menu();
}

然而很遗憾,我发现溢出这个 command 没有一丁点用处,因为检测发现溢出改变的是 menu 变量,而很显然我们看这次溢出后,程序就重新开始重新让用户输入 menu ,因此我们的溢出改变不会有结果。

漏洞点 2

观察上一个漏洞点的代码,有没有发现奇怪的地方? select_menu() 它用了 尾递归 !这种看起来好像没什么毛病,但是他会导致栈位置不断降低。每一次执行 select_menu ,都会用一点栈空间,然后又执行新的 select_menu 。放到 gdb 里面调试了一下,发现每一次栈降低的大小是 0x430 。

漏洞点 3

观察它调用的 mmap_s ,作用就是随机化地址,然后调用 mmap ,但是它错误的调用导致产生了漏洞。

void* mmap_s(void* addr, size_t length, int prot, int flags, int fd, off_t offset){
    // 改了许多,不过反正知道函数里有这句话就行
    // prot: 期望的内存保护标志(如r-x, rwx ..)
    return mmap(addr, length, prot, flags | MAP_FIXED, fd, offset);
}

对于 mmap 函数,其 flags 项里的 MAP_FIXED 是这样的含义:如果我们想要分配的地址在内存中已经被占用了,那么就覆盖掉内存原来的内容。所以这里的滥用可能会导致内存的覆盖。

漏洞点 4

这里不能算作是漏洞点,相当于程序的功能就是如此。

  • create_note 让用户使用 mmap_s 创建一个块,并且告诉我们这个块的具体的地址
  • write_note 让用户往创建的块里面写入数据,需要指定往哪一个块里面写数据,注意这些空间都是可以执行的!
  • delete_notes 用户指定删除哪一块数据,即直接 free 掉某一块。
    void create_note(){
        int i;
        void* ptr;
        for(i=0; i<256; i++){
            if(mem_arr[i] == NULL){
                ptr = mmap_s((void*)NULL, PAGE_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
                mem_arr[i] = ptr;
                printf("note created. no %d\n [%08x]", i, (int)ptr);    
                return;
            }
        }
        printf("memory sults are fool\n");
        return;
    }
    
    void write_note(){
        unsigned int no;    
        printf("note no?\n");
        scanf("%d", &no);   
        clear_newlines();   
        if(no>256){
            printf("index out of range\n");
            return;
        }
        if(mem_arr[no]==NULL){
            printf("empty slut!\n");
            return;
        }
        printf("paste your note (MAX : 4096 byte)\n");
        gets(mem_arr[no]);  
    }
    

流程分析

现在来讲讲具体的利用流程。首先因为程序的功能,如果我们不满意第一次随机分配的空间,还可以进行第二次第三次。又因为每次分配都告诉了我们分配的地址,所以我们可以一直分配,直到分配到我们想让他分配到的地址。

那么分配到哪里呢,首先肯定是有一个空间,里面是放着 shellcode ,最后要跳到那个地址。然后就是本题最精髓的地方了,利用漏洞点二所说的尾递归和漏洞点三所说的可覆盖原有内存,
我们将栈中的一块内存给覆盖掉,要求内存要高于当前 esp (注意栈是从高往低长哦),然后内容全部填充为存 shellcode 的那一块内存地址。

为什么这样可以,想象一下,此时我们选择返回,那么程序开始不断地 (pop ebp, ret) ,这样当 rip 到了我们修改的那一块时,就会执行 shellcode. 而且因为没有开 ASLR,所以栈地址固定,这就更加让覆盖栈地址这一想法非常可靠。

所以程序逻辑是这样:因为没有开 ASLR ,所以程序开始 esp 是一样的。记录下原始的 esp,然后再令当前 esp 等于这个原始的 esp,之后我们会每次执行 select_menu 后都会更新当前 esp.

我们每次进行这样的尝试:首先获取一个随机分配的内存,发现如果有内存块的地址是在 [原始 esp, 当前 esp] 中的,那么这个块就是我们所需要的。如果没有,那就继续获取下一个随机分配的内存,同时因为我们执行了依次 select_menu (程序流: select_menucreate_note),所以我们需要更新当前 esp ,即减去 0x430 (漏洞点二谈到这个值是根据 gdb 调试得出来的)。

如果发现已经分配到 255 了,说明我们必须要把这些内存块都删去,所以要执行 255 次 delete_notes ,所以更新当前 esp ,减去 0x430 * 255 。然后开始新一轮的一次一次获取内存块,直到我们的内存块成功在 [原始esp, 当前esp] 中。

最终我们把某个内存块写入 shellcode ,然后将上述所说在栈中的内存块中数据全部填充为 shellcode 地址,搞定。

总结

  • 尾递归可能带来什么危害
  • mmap 中一定要慎用 MAP_FIXED