看了先知上的一篇文章,这题是在glibc2.29下文件结构的利用,复现一波
程序分析
checksec下,保护全开,拖进IDA,看见有seccomp_init
函数,程序开启了沙箱,只允许open read write close mmap mprotect brk rt_sigreturn exit exit_group
,这样看的话应该是要orw了
程序先安装了seccomp
,然后把_IO_2_1_stdout_+0x8A0
地址处设置为只读:
1 | .text:0000000000001399 nohack proc near ; CODE XREF: main+35↓p |
在这之后会把system
函数的地址告诉我们,再给两次任意地址写的机会,最后调用fclose
关闭stdin stdout stderr
1 | printf("gift : %p\n", &system, argv); |
漏洞利用
程序的漏洞很明显,就是两次的任意地址写,程序一开始给出了libc的地址,现在的问题是写哪,程序安装了seccomp
,one_gadget的方法肯定失效了,且程序会fclose
输入输出和错误流,故应该在fclose
的时候想办法劫持程序流
如果要靠fclose
来劫持程序流程的话,首先想到的肯定是能不能劫持vtable
,但是glibc在2.24的时候会对vtable
进行check,所以利用这两次任意地址写应该不太好利用。
那回到程序一开始,那个可疑的nohack
函数,为什么要把_IO_2_1_stdout+0x8A0
地址处0x1000
大小设置为可读呢,我们可以用gdb在程序刚刚开始的时候看下内存布局,也看下_IO_2_1_stdout+0x8A0
地址处是个啥:
1 | Breakpoint main |
是一些vtable,在我们以往的经验中,io_vtable对应的地址是不可写的,然而这里居然还要mprotect
把权限改为只读,说明可能这里原本是可写的,vmmap
看下:
1 | 0x7ffff7d7f000 0x7ffff7d82000 rw-p 3000 0 |
这vtable
在glibc2.29下是可写的,(有点神奇
所以我们可以改写_IO_2_1_stdout
原本的vtable
为其它的能通过vtable check
,且可写的vtable
回到程序,我们可以看到这题使用了scanf
,可以想到如果我们改写了_IO_2_1_stdin
的_IO_buf_end
,那我们就可以改写_IO_buf_base
到_IO_buf_end
区间的值了,这里的思路来自wannaheap
,比如我们可以把_IO_buf_end
改到_IO_2_1_stdout
的后面,就能连_IO_2_1_stdout
也一起改掉了
效果:
1 | [DEBUG] Received 0x1a bytes: |
改写_IO_buf_end
:
1 | pwndbg> p _IO_2_1_stdin_ |
当然无脑输入aaaaaaa
程序会奔溃,所以我们要保留那些地址原本的值,只修改我们想要修改的地方
思路
有了上面的分析,我们可以得到以下一个思路:
- 第一次任意地址写,我们覆盖
_IO_2_1_stdin
的_IO_buf_end
,使得我们能够改写_IO_buf_base
到_IO_buf_end
区间的值 - 修改
_IO_2_1_stdout_
的vtable,改为一个在可写区间的vtable,为什么改stdout
呢,因为程序第一个调用是fclose(stdout)
,所以就选择覆盖掉stdout
,而且如果stdout
真的被close
了,那输出流也就没了,远程的服务器一般不回显stderr
要注意的是我们要改写_IO_2_1_stdout_
,在覆盖的过程中会覆盖掉很多的指针,结构体啥的,有的要保留他们原本的值,以免程序出错
还有一个问题是怎么找到符合条件的vtable,我找了个带符号表的glibc2.29,写了个简单脚本:
1 | from pwn import * |
输出:
1 | _IO_file_jumps |
然后一个个看,(手动滑稽,_IO_helper_jumps
,_IO_cookie_jumps
这些都是可以的,且都在_IO_2_1_stdout_
后面,可以一起覆盖掉:
1 | pwndbg> p &_IO_helper_jumps |
下个触发调用vtable
处的断点:
1 | RAX 0xd68 |
此处call qword ptr [rbp + 0x88]
,其中rbp
指向**_IO_helper_jumps,(而且rdx
也恰好指向_IO_helper_jumps**,配合setcontext+53
处的指令,简直完美)这里对应源码:
1 | In file: /usr/src/glibc/glibc-2.29/libio/fileops.c |
于是乎我们只要把_IO_helper_jumps+0x88
处修改为setcontext+53
,然后布置好数据,进行ROP即可
最终exp:
1 | from pwn import * |
总结
vtable居然在glibc2.29下可写了,真的神奇