簡介在這篇文章中,我們將為讀者詳細介紹我們的小組成員Alex Plaskett、Cedric Halbronn和Aaron Adams於2021年9月發現的一個基於堆棧的溢出漏洞,目前,該漏洞已通過Netgear的固件更新得到了相應的修復。
該漏洞存在於KC_PRINT服務(/usr/bin/KC_PRINT),該軟件默認運行於Netgear R6700v3路由器上。雖然這是一個默認服務,但只有啟用ReadySHARE功能(即打印機通過USB端口物理連接到Netgear路由器)時,該漏洞才有可能被觸發。由於該服務不需要進行任何配置,因此,一旦打印機連接到路由器,攻擊者就利用默認配置下的這個安全漏洞。
此外,攻擊者還能在路由器的局域網端利用這個安全漏洞,並且無需經過身份驗證。如果攻擊得手,攻擊者就能在路由器上以admin用戶(具有最高權限)的身份遠程執行代碼。
我們的利用方法與這裡(https://github.com/pedrib/PoC/blob/master/advisories/Pwn2Own/Tokyo_2019/tokyo_drift/tokyo_drift.md)使用的方法非常相似,只是我們可以修改admin密碼並啟動utelnetd服務,這使我們能夠在路由器上獲得具有特權的shell。
儘管這里分析和利用的是V1.0.4.118_10.0.90版本中的安全漏洞(詳見下文),但舊版本也可能存在同樣的漏洞。
注意:Netgear R6700v3路由器是基於ARM(32位)架構的。
我們將該漏洞命名為“BrokenPrint”,這是因為“KC”在法語中的發音類似於“cassé”,而後者在英語中意味著“broken”。
漏洞詳情關於ReadySHARE這個視頻對ReadySHARE進行了很好的介紹,簡單來說,借助它,我們就能通過Netgear路由器來訪問USB打印機,就像打印機是網絡打印機一樣。
到達易受攻擊的memcpy()函數需要說明的是,雖然KC_PRINT二進製文件沒有提供符號信息,卻提供了很多日誌/錯誤函數,其中包含一些函數名。下面顯示的代碼是通過IDA/Hex-Rays反編譯得到的代碼,因為我們沒有找到這個二進製文件的開放源代碼。
KC_PRINT二進製文件創建了許多線程來處理不同的特性:
我們感興趣的第一個線程處理程序是地址為0xA174的ipp_server()函數。我們可以看到,它會偵聽端口631;並且接受客戶端連接後,它會創建一個新線程,以執行位於0xA4B4處的thread_handle_client_connection()函數,並將客戶端套接字傳遞給這個新線程。
void__noreturnipp_server()
{
//[COLLAPSEDLOCALDECLARATIONS.PRESSKEYPADCTRL-'+'TOEXPAND]
addr_len=0x10;
optval=1;
kc_client=0;
pthread_attr_init(attr);
pthread_attr_setdetachstate(attr,1);
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock0)
{
.
}
if(setsockopt(sock,1,SO_REUSEADDR,optval,4u)0)
{
.
}
memset(sin,0,sizeof(sin));
sin.sin_family=2;
sin.sin_addr.s_addr=htonl(0);
sin.sin_port=htons(631u);//listensonTCP631
if(bind(sock,(conststructsockaddr*)sin,0x10u)0)
{
.
}
//acceptupto128clientssimultaneously
listen(sock,128);
while(g_enabled)
{
client_sock=accept(sock,addr,addr_len);
if(client_sock=0)
{
update_count_client_connected(CLIENT_CONNECTED);
val[0]=60;
val[1]=0;
if(setsockopt(client_sock,1,SO_RCVTIMEO,val,8u)0)
perror('ipp_server:setsockoptSO_RCVTIMEOfailed');
kc_client=(kc_client*)malloc(sizeof(kc_client));
if(kc_client)
{
memset(kc_client,0,sizeof(kc_client));
kc_client-client_sock=client_sock;
pthread_mutex_lock(g_mutex);
thread_index=get_available_client_thread_index();
if(thread_index0)
{
pthread_mutex_unlock(g_mutex);
free(kc_client);
kc_client=0;
close(client_sock);
update_count_client_connected(CLIENT_DISCONNECTED);
}
elseif(pthread_create(
g_client_threads[thread_index],
attr,
(void*(*)(void*))thread_handle_client_connection,
kc_client))
{
.
}
else
{
pthread_mutex_unlock(g_mutex);
}
}
else
{
.
}
}
}
close(sock);
pthread_attr_destroy(attr);
pthread_exit(0);
}客戶端處理程序將調用地址為0xA530的do_http函數:
void__fastcall__noreturnthread_handle_client_connection(kc_client*kc_client)
{
//[COLLAPSEDLOCALDECLARATIONS.PRESSKEYPADCTRL-'+'TOEXPAND]
client_sock=kc_client-client_sock;
while(g_enabled!do_http(kc_client))
;
close(client_sock);
update_count_client_connected(CLIENT_DISCONNECTED);
free(kc_client);
pthread_exit(0);
}do_http()函數將讀取一個類似HTTP的請求,為此,它首先要找到以\r\n\r\n結尾的HTTP頭部,並將其保存到一個1024字節的堆棧緩衝區中。然後,它繼續搜索一個POST /USB URI和一個_LQ字符串,其中usblp_index是一個整數。然後,調用0x16150處的函數is_printer_connected()。
為了簡潔起見,這裡並沒有展示is_printer_connected()的代碼,其作用就是打開/proc/printer_status文件,試圖讀取其內容,並試圖通過尋找類似usblp%d的字符串來查找USB端口。實際上,只有當打印機連接到Netgear路由器時才會發現上述行為,這意味著:如果沒有連接打印機,它將不會繼續執行下面的代碼。
unsignedint__fastcalldo_http(kc_client*kc_client)
{
//[COLLAPSEDLOCALDECLARATIONS.PRESSKEYPADCTRL-'+'TOEXPAND]
kc_client_=kc_client;
client_sock=kc_client-client_sock;
content_len=0xFFFFFFFF;
strcpy(http_continue,'HTTP/1.1100Continue\r\n\r\n');
pCurrent=0;
pUnderscoreLQ_or_CRCL=0;
p_client_data=0;
kc_job=0;
strcpy(aborted_by_system,'aborted-by-system');
remaining_len=0;
kc_chunk=0;
//buf_readisonthestackandis1024bytes
memset(buf_read,0,sizeof(buf_read));
//Readin1024bytesmaximum
count_read=readUntil_0d0a_x2(client_sock,(unsigned__int8*)buf_read,0x400);
if((int)count_read=0)
return0xFFFFFFFF;
//ifreceived'100-continue',sendsback'HTTP/1.1100Continue\r\n\r\n'
if(strstr(buf_read,'100-continue'))
{
ret_1=send(client_sock,http_continue,0x19u,0);
if(ret_1=0)
{
perror('do_http()write100Continuexx');
return0xFFFFFFFF;
}
}
//IfPOST/USBisfound
pCurrent=strstr(buf_read,'POST/USB');
if(!pCurrent)
return0xFFFFFFFF;
pCurrent+=9;//pointsafter'POST/USB'
//If_LQisfound
pUnderscoreLQ_or_CRCL=strstr(pCurrent,'_LQ');
if(!pUnderscoreLQ_or_CRCL)
return0xFFFFFFFF;
Underscore=*pUnderscoreLQ_or_CRCL;
*pUnderscoreLQ_or_CRCL=0;
usblp_index=atoi(pCurrent);
*pUnderscoreLQ_or_CRCL=Underscore;
if(usblp_index10)
return0xFFFFFFFF;
//bydefault,willexithereasnoprinterconnected
if(!is_printer_connected(usblp_index))
return0xFFFFFFFF;//exitifnoprinterconnected
kc_client_-usblp_index=usblp_index;然後,它將解析HTTP的Content-Length頭部,並開始從HTTP內容中讀取8個字節。並根據這8個字節的值,調用0x128C0處的do_airippWithContentLength()函數——這正是我們的興趣之所在。
///!\doesnotreadfrompCurrent
pCurrent=strstr(buf_read,'Content-Length:');
if(!pCurrent)
{
//HandlechunkedHTTPencoding
.
}
//nochunkencodinghere,normalhttprequest
pCurrent+=0x10;
pUnderscoreLQ_or_CRCL=strstr(pCurrent,'\r\n');
if(!pUnderscoreLQ_or_CRCL)
return0xFFFFFFFF;
Underscore=*pUnderscoreLQ_or_CRCL;
*pUnderscoreLQ_or_CRCL=0;
content_len=atoi(pCurrent);
*pUnderscoreLQ_or_CRCL=Underscore;
memset(recv_buf,0,sizeof(recv_buf));
count_read=recv(client_sock,recv_buf,8u,0);//8bytesarereadonlyinitially
if(count_read!=8)
return0xFFFFFFFF;
if((recv_buf[2]||recv_buf[3]!=2)(recv_buf[2]||recv_buf[3]!=6))
{
ret_1=do_airippWithContentLength(kc_client_,content_len,recv_buf);
if(ret_10)
return0xFFFFFFFF;
return0;
}
.do_airippWithContentLength()函數分配了一個堆緩衝區來容納整個HTTP的內容,並複制之前已經讀取的8個字節,並將剩餘的字節讀入該新的堆緩衝區。
注意:只要malloc()不因內存不足而失敗,實際的HTTP內容的大小就沒有限制,這在後面進行內存噴射時很有用。
然後,代碼繼續根據最初讀取的8個字節的值,來調用其他函數。就這裡來說,我們對位於0x102C4處的Response_Get_Jobs()比較感興趣,因為它包含我們要利用的基於堆棧的溢出漏洞。請注意,雖然其他Response_XXX()函數也可能包含類似的堆棧溢出漏洞,但Response_Get_Jobs()是最容易利用的一個函數,所以,我們就先撿最軟的一個柿子來捏。
unsignedint__fastcalldo_airippWithContentLength(kc_client*kc_client,intcontent_len,char*recv_buf_initial)
{
//[COLLAPSEDLOCALDECLARATIONS.PRESSKEYPADCTRL-'+'TOEXPAND]
client_sock=kc_client-client_sock;
recv_buf2=malloc(content_len);
if(!recv_buf2)
return0xFFFFFFFF;
memcpy(recv_buf2,recv_buf_initial,8u);
if(toRead(client_sock,recv_buf2+8,content_len-8)=0)
{
if(recv_buf2[2]||recv_buf2[3]!=0xB)
{
if(recv_buf2[2]||recv_buf2[3]!=4)
{
if(recv_buf2[2]||recv_buf2[3]!=8)
{
if(recv_buf2[2]||recv_buf2[3]!=9)
{
if(recv_buf2[2]||recv_buf2[3]!=0xA)
{
if(recv_buf2[2]||recv_buf2[3]!=5)
Job=Response_Unk_1(kc_client,recv_buf2);
else
//recv_buf2[3]==0x5
Job=Response_Create_Job(kc_client,recv_buf2,content_len);
}
else
{
//recv_buf2[3]==0xA
Job=Response_Get_Jobs(kc_client,recv_buf2,content_len);
}
}
else
{
.
}易受攻擊的Response_Get_Jobs()函數開頭部分的代碼如下所示:
//recv_bufwasallocatedontheheap
unsignedint__fastcallResponse_Get_Jobs(kc_client*kc_client,unsigned__int8*recv_buf,intcontent_len)
{
charcommand[64];//[sp+24h][bp-1090h]BYREF
charsuffix_data[2048];//[sp+64h][bp-1050h]BYREF
charjob_data[2048];//[sp+864h][bp-850h]BYREF
unsignedinterror;//[sp+1064h][bp-50h]
size_tcopy_len;//[sp+1068h][bp-4Ch]
intcopy_len_1;//[sp+106Ch][bp-48h]
size_tcopied_len;//[sp+1070h][bp-44h]
size_tprefix_size;//[sp+1074h][bp-40h]
intin_offset;//[sp+1078h][bp-3Ch]
char*prefix_ptr;//[sp+107Ch][bp-38h]
intusblp_index;//[sp+1080h][bp-34h]
intclient_sock;//[sp+1084h][bp-30h]
kc_client*kc_client_1;//[sp+1088h][bp-2Ch]
intoffset_job;//[sp+108Ch][bp-28h]
charbReadAllJobs;//[sp+1093h][bp-21h]
charis_job_media_sheets_completed;//[sp+1094h][bp-20h]
charis_job_state_reasons;//[sp+1095h][bp-1Fh]
charis_job_state;//[sp+1096h][bp-1Eh]
charis_job_originating_user_name;//[sp+1097h][bp-1Dh]
charis_job_name;//[sp+1098h][bp-1Ch]
charis_job_id;//[sp+1099h][bp-1Bh]
charsuffix_copy1_done;//[sp+109Ah][bp-1Ah]
charflag2;//[sp+109Bh][bp-19h]
size_tfinal_size;//[sp+109Ch][bp-18h]
intoffset;//[sp+10A0h][bp-14h]
size_tresponse_len;//[sp+10A4h][bp-10h]
ch
Recommended Comments