Jump to content

簡介在這篇文章中,我們將為讀者詳細介紹我們的小組成員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打印機,就像打印機是網絡打印機一樣。

1.png

到達易受攻擊的memcpy()函數需要說明的是,雖然KC_PRINT二進製文件沒有提供符號信息,卻提供了很多日誌/錯誤函數,其中包含一些函數名。下面顯示的代碼是通過IDA/Hex-Rays反編譯得到的代碼,因為我們沒有找到這個二進製文件的開放源代碼。

KC_PRINT二進製文件創建了許多線程來處理不同的特性:

1.png

我們感興趣的第一個線程處理程序是地址為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

0 Comments

Recommended Comments

There are no comments to display.

Guest
Add a comment...