首页
社区
课程
招聘
[翻译]第四部分:Use-After-Free(Pwnable -> uaf)
2019-5-27 11:08 6878

[翻译]第四部分:Use-After-Free(Pwnable -> uaf)

2019-5-27 11:08
6878

第四部分:Use-After-Free(Pwnable -> uaf)

   
在下面的讲述中,我们将研究 pawnable.kr 上的 UAF 挑战。这是一个 64 位的 Linux UAF 漏洞。把 UAF 放在此处像是给新手一记耳光(为什么你的技术没有 b33f 的好?)事实上它并没有想象中那么可怕,让我直接开始把。

展开新的挑战


我们同样地会向大家展示二进制文件的源代码,如下所示。
#include <fcntl.h>
#include <iostream> 
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;
 
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;
    }
};
 
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;
        }
};
 
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\n2. after\n3. free\n";
        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;   
}

多花费些时间仔细阅读代码。首先,当程序初始时,它会创建一个 "man" 和 "woman" 对象。请参阅下面主函数 prolog 的摘录。

       

注意分配给两个对象的大小 0x18(24字节)(这是 malloc 的最小分配?)。其中含有 str“jack” + 0x19(25) 和 str"jill" + 0x15(21)。
    
在 prolog 之后,我们到达带有分支选项的菜单。从源代码中明显可以看这里存在一个问题,如果先选择 "free" 再选择 "use",程序将尝试删除 "man" 和 "woman" 对象调用的  introduction 方法,进而导致一个段错误。

       

留下的 "after" 选项。它有个两个参数(在运行时提供)。第一个参数是整数,用于从文件中读取 x 个字节,第二个参数是文件路径,
    
       
   
现在,我们可以非常坦率地选择 "free" 选项,然后分配我们的自定义对象(具有相同的大小),我们应该可以在使用 "use" 菜单选项引用数据时获得某段代码的 exec 执行语句。

最后剩下的问题就是如何完成挑战目标? human 类有一个名为 "give_shell" 的私有方法,它将为我们生成一个 bash shell,这看起十分简单。

        

Pwn 过程


为了实现我们的目标,就需要更好地理解 "use" 选项。该选项的反汇编如下所示。

         

这里的两个调用看起来几乎完全相同,大概一个用于 "man" 对象,一个用于 "woman" 对象(反之亦然)。无论是哪种方式,让我们用 GDB 暂停在 “use” 选项中,看看会发现什么。

         

奇怪的是,我们可以在这里看到指向 'Human::give_shell' 方法的指针。注意,当 QWORD 指针加载到 RDX 之后, 我们将向 RAX 加 8 (IntPtr大小),然后在 main + 286 处执行。在添加 8 后,QWORD 指针指向 "Man::introduce"。
         
         

让我们尝试给程序缓冲区发送 24 个字符,看看会发生什么。我们可以试着按如下方式构造输入文件。
python -c'print(“\ x41”* 8 +“\ x42”* 8 +“\ x43”* 8)'> OutFile
         
经过一段时间的尝试,我们发现必须通过两次选择 "after" 选项才能获得 exec 执行语句。假设这是因为我们需要删除两个 24 节的对象,那么我们必须进行两次 24 字节的分配。或者更确切地说,当我们点击 "use" 选项时,第一个调用实际在引用第二个分配,而第二个调用却引用第一个分配。

         
        
现在这个教程差不多要结束了, 从最初我们发现两个 QWORDS 指向 'Human::give_shell' 方法,到我们可以调用任意地址。如果我们采取其中之一并减去 8 (需要记住进行补偿 "add rax, 8"),这样应该可以被重定向到 bash shell 中!

游戏结束

 
让我们进入程序并获得 flag !
         


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-5-28 09:15 被Liary编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回