Jump to content

2021年10月,我們的研究人員發現了一個安全漏洞,並在2021年11月舉行的Pwn2Own 2021大賽中成功利用了該漏洞。今年一月,Lexmark公司發布了該漏洞(CVE-2021-44737)的安全補丁。

最初,我們是打算以Lexmark MC3224i打印機為目標的,然而,由於該型號的打印機到處缺貨,所以,我們決定買一台Lexmark MC3224dwe打印機作為其替代品。它們的主要區別在於:Lexmark MC3224i型號提供了傳真功能,而Lexmark MC3224dwe型號則缺乏該功能。從漏洞分析的角度來看,這意味著兩者之間可能有一些差異,至少我們很可能無法利用某些功能。於是,我們下載了兩個型號的固件更新,發現它們竟然完全一樣,所以,我們決定繼續考察Lexmark MC3224dwe——反正我們也沒有選擇的餘地。

就像Pwn2Own所要求的那樣,該漏洞可被遠程利用,無需經過身份驗證,並存在於默認配置中。利用該漏洞,攻擊者就能以該打印機的root用戶身份遠程執行代碼。

關於該漏洞的利用過程,具體如下所示:

1、利用臨時文件寫入漏洞(CVE-2021-44737),對ABRT鉤子文件執行寫操作

2、通過遠程方式,令進程發生崩潰,以觸發ABRT的中止處理

3、中止處理將導致ABRT鉤子文件中的bash命令被執行

實際上,臨時文件寫入漏洞位於Lexmark特有的hydra服務(/usr/bin/hydra)中,該服務在Lexmark MC3224dwe打印機上是默認運行的。 hydra服務的二進製文件非常大,因為它需要處理各種協議。該漏洞存在於打印機工作語言(PJL)命令中,更具體地說,是在一個名為LDLWELCOMESCREEN的命令中。

我們已經分析並利用了CXLBL.075.272/CXLBL.075.281版本中的漏洞,但舊版本也可能存在該漏洞。在這篇文章中,我們將詳細介紹針對CXLBL.075.272的分析過程,因為CXLBL.075.281是在去年10月中旬發布的,並且我們一直在研究這個版本。

注意:Lexmark MC3224dwe打印機是基於ARM(32位)架構的,但這對漏洞的利用過程並不重要,只是對逆向分析有點影響。

由於該漏洞先是觸發了一個ABRT,隨後又中止了ABRT,所以,我們將其命名為“MissionAbrt”。

逆向分析您可以從Lexmark下載頁面下載Lexmark固件更新文件,但是,這個文件是加密的。如果您有興趣了解我們的同事Catalin Visinescu是如何使用硬件黑客技術來獲取該固件文件的,請參閱本系列的第一篇文章。

漏洞詳細信息背景知識正如維基百科所說:

打印機作業語言(PJL)是Hewlett-Packard公司開發的一種方法,用於在作業級別切換打印機語言,以及在打印機和主機之間進行狀態回讀。 PJL增加了作業級別控制,如打印機語言切換、作業分離、環境、狀態回讀、設備考勤和文件系統命令。

PJL命令如下所示:

@PJLSETPAPER=A4

@PJLSETCOPIES=10

@PJLENTERLANGUAGE=POSTSCRIPT眾所周知,PJL對攻擊者來說是非常有用的。過去,一些打印機曾經曝光過允許在設備上讀寫文件的漏洞。

PRET是這樣一款工具:借助於該軟件,我們就能在各種打印機品牌上使用PIL(以及其他語言),但它不一定支持所有的命令,因為每個供應商都提供了自己的專有命令。

找到含有漏洞的函數雖然hydra服務的二進製文件沒有提供符號,卻提供了許多日誌/錯誤函數,其中包含一些函數名。下面顯示的代碼是通過IDA/Hex-Rays反編譯的代碼,因為尚未找到該二進製文件的開放源代碼。其中,許多PJL命令由地址為0xFE17C的setup_pjl_commands()函數註冊的。這裡,我們對LDLWELCOMESCREEN PJL命令非常感興趣,因為它好像是Lexmark專有的,並且沒有找到相應的說明文檔。

int__fastcallsetup_pjl_commands(inta1)

{

//[COLLAPSEDLOCALDECLARATIONS.PRESSKEYPADCTRL-'+'TOEXPAND]

pjl_ctx=create_pjl_ctx(a1);

pjl_set_datastall_timeout(pjl_ctx,5);

sub_11981C();

pjlpGrowCommandHandler('UEL',pjl_handle_uel);

.

pjlpGrowCommandHandler('LDLWELCOMESCREEN',pjl_handle_ldlwelcomescreen);

.當接收到PJL LDLWELCOMESCREEN命令後,0x1012f0處的pjl_handle_ldlwelcomescreen()函數將開始處理該命令。我們可以看到,這個命令使用一個表示文件名的字符串作為第一個參數:

int__fastcallpjl_handle_ldlwelcomescreen(char*client_cmd)

{

//[COLLAPSEDLOCALDECLARATIONS.PRESSKEYPADCTRL-'+'TOEXPAND]

result=pjl_check_args(client_cmd,'FILE','PJL_STRING_TYPE','PJL_REQ_PARAMETER',0);

if(result=0)

returnresult;

filename=(constchar*)pjl_parse_arg(client_cmd,'FILE',0);

returnpjl_handle_ldlwelcomescreen_internal(filename);

}然後,0x10a200處的pjl_handle_ldlwelcomescreen_internal()函數將嘗試打開該文件。請注意,如果該文件存在,該函數並不會打開它,而是立即返回。因此,我們只能寫還不存在的文件。此外,完整的目錄層次結構必須已經存在,以便我們創建文件,同時,我們還需要具有寫文件的權限。

unsignedint__fastcallpjl_handle_ldlwelcomescreen_internal(constchar*filename)

{

//[COLLAPSEDLOCALDECLARATIONS.PRESSKEYPADCTRL-'+'TOEXPAND]

if(!filename)

return0xFFFFFFFF;

fd=open(filename,0xC1,0777);//open(filename,O_WRONLY|O_CREAT|O_EXCL,0777)

if(fd==0xFFFFFFFF)

return0xFFFFFFFF;

ret=pjl_ldwelcomescreen_internal2(0,1,pjl_getc_,write_to_file_,fd);//goeshere

if(!retpjl_unk_functionpjl_unk_function(filename))

pjl_process_ustatus_device_(20001);

close(fd);

remove(filename);

returnret;

}下面我們開始考察pjl_ldwelcomescreen_internal2()函數,但請注意,上面的文件最後會被關閉,並然後通過remove()調用完全刪除文件名。這意味著我們似乎只能暫時寫入該文件。

文件寫入原語現在,讓我們分析0x115470處的pjl_ldwelcomescreen_internal2()函數。由於使用了flag==0選項,因此,pjl_handle_ldlwelcomescreen_internal()函數最終將調用pjl_ldwelcomescreen_internal3()函數:

unsignedint__fastcallpjl_ldwelcomescreen_internal2(

intflag,

intone,

int(__fastcall*pjl_getc)(unsigned__int8*p_char),

ssize_t(__fastcall*write_to_file)(int*p_fd,char*data_to_write,size_tlen_to_write),

int*p_fd)

{

//[COLLAPSEDLOCALDECLARATIONS.PRESSKEYPADCTRL-'+'TOEXPAND]

bad_arg=write_to_file==0;

if(write_to_file)

bad_arg=pjl_getc==0;

if(bad_arg)

return0xFFFFFFFF;

if(flag)

returnpjl_ldwelcomescreen_internal3bis(flag,one,pjl_getc,write_to_file,p_fd);

returnpjl_ldwelcomescreen_internal3(one,pjl_getc,write_to_file,p_fd);//goeshereduetoflag==0

}我們花了一些時間,來逆向分析0x114838處的pjl_ldwelcomescreen_internal3()函數,以理解其內部機制。這個函數不僅很大,通過反編譯得到的代碼可讀性不是太高,但邏輯仍然很容易理解。

基本上,這個函數負責從客戶端讀取附加數據,並將其寫入前面打開的文件。

客戶端數據似乎是由另一個線程異步接收的,並保存到分配給pjl_ctx結構體的內存中。因此,pjl_ldwelcomescreen_internal3()函數每次從pjl_ctx結構體中讀取一個字符,並填充0x400字節的堆棧緩衝區。

1、如果接收到0x400字節數據,並且堆棧緩衝區已滿,則最終會將這些0x400字節寫入以前打開的文件中。然後,它重置堆棧緩衝區,並開始讀取更多數據以重複該過程。

2、如果接收到PJL命令的頁腳(“@PJL END data”),它將丟棄頁腳部分,然後將接收的數據(大小0x400字節)寫入文件,最後退出。

unsignedint__fastcallpjl_ldwelcomescreen_internal3(

intwas_last_write_success,

int(__fastcall*pjl_getc)(unsigned__int8*p_char),

ssize_t(__fastcall*write_to_file)(int*p_fd,char*data_to_write,size_tlen_to_write),

int*p_fd)

{

unsignedintcurrent_char_2;//r5

size_tlen_to_write;//r4

intlen_end_data;//r11

inthas_encountered_at_sign;//r6

unsignedintcurrent_char_3;//r0

intret;//r0

intcurrent_char_1;//r3

ssize_tlen_written;//r0

unsignedintret_2;//r3

ssize_tlen_written_1;//r0

unsignedintret_3;//r3

ssize_tlen_written_2;//r0

unsignedintret_4;//r3

intwas_last_write_success_1;//r3

size_tlen_to_write_final;//r4

ssize_tlen_written_final;//r0

unsignedintret_5;//r3

unsignedintret_1;//[sp+0h][bp-20h]

unsigned__int8current_char;//[sp+1Fh][bp-1h]BYREF

_BYTEdata_to_write[1028];//[sp+20h][bp+0h]BYREF

current_char_2=0xFFFFFFFF;

ret_1=0;

b_restart_from_scratch:

len_to_write=0;

memset(data_to_write,0,0x401u);

len_end_data=0;

has_encountered_at_sign=0;

current_char_3=current_char_2;

while(1)

{

current_char=0;

if(current_char_3==0xFFFFFFFF)

{

//getonecharacterfrompjl_ctx-pData

ret=pjl_getc(current_char);

current_char_1=current_char;

}

else

{

//apreviouscharacterwasalreadyretrieved,let'susethatfornow

current_char_1=(unsigned__int8)current_char_3;

ret=1;//success

current_char=current_char_1;

}

if(has_encountered_at_sign)

break;//exittheloopforever

//isitan'@'signforaPJL-specificcommand?

if(current_char_1!='@')

gotob_read_pjl_data;

len_end_data=1;

has_encountered_at_sign=1;

b_handle_pjl_at_sign:

//fromhere,current_char=='@'

if(len_to_write+130x400)//?

{

if(was_last_write_success)

{

len_written=write_to_file(p_fd,data_to_write,len_to_write);

was_last_write_success=len_to_write==len_written;

current_char_2='@';

ret_2=ret_1;

if(len_to_write!=len_written)

ret_2=0xFFFFFFFF;

ret_1=ret_2;

}

else

{

current_char_2='@';

}

gotob_restart_from_scratch;

}

b_read_pjl_data:

if(ret==0xFFFFFFFF)//error

{

if(!was_last_write_success)

returnret_1;

len_written_1=write_to_file(p_fd,data_to_write,len_to_write);

ret_3=ret_1;

if(len_to_write!=len_written_1)

return0xFFFFFFFF;//error

returnret_3;

}

if(len_to_write0x400)

__und(0);

//appenddatatostackbuffer

data_to_write[len_to_write++]=current_char_1;

current_char_3=0xFFFFFFFF;//resettoenforcereadinganothercharacter

//atnextloopiteration

//reached0x400bytestowrite,let'swritethem

if(len_to_write==0x400)

{

current_char_2=0xFFFFFFFF;//resettoenforcereadinganothercharacter

//atnextloopiteration

if(was_last_write_success)

{

len_written_2=write_to_file(p_fd,data_to_write,0x400);

ret_4=ret_1;

if(len_written_2!=0x400)

ret_4=0xFFFFFFFF;

ret_1=ret_4;

was_last_write_success_1=was_last_write_success;

if(len_written_2!=0x400)

was_last_write_success_1=0;

was_last_write_success=was_last_write_success_1;

}

gotob_restart_from_scratch;

}

}//endofwhile(1)

//wereachhereifweencounteredan'@'sign

//let'scheckitisavalid'@PJLENDDATA'footer

if((unsigned__int8)aPjlEndData[len_end_data]!=current_char_1)

{

len_end_data=1;

has_encountered_at_sign=0;//resetsowereaditagain?

gotob_read_data_or_at;

}

if(len_end_data!=12)//len('PJLENDDATA')=12

{

++len_end_data;

b_read_data_or_at:

//willgobacktothewhile(1)loopbutexitatthenext

//iterationdueto'break'andhas_encountered_at_sign==1

if(current_char_1!='@')

gotob_read_pjl_data;

gotob_handle_pjl_at_sign;

}

//wereachhereifall'PJLENDDATA'wasparsed

current_char=0;

pjl_getc(current_char);//read'\r'

if(current_char=='\r')

pjl_getc(current_char);//read'\n'

//writealltheremainingdata(len0x400),exceptthe'PJLENDDATA'footer

len_to_write_final=len_to_write-0xC;

if(!was_last_write_success)

returnret_1;

len_written_final=write_to_file(p_fd,data_to_write,len_to_write_final);

ret_5=ret_1;

if(len_to_write_final!=len_written_final)

return0xFFFFFFFF;

returnret_5;

}位於0xFEA18處的pjl_getc()函數,用於從pjl_ctx結構體中

0 Comments

Recommended Comments

There are no comments to display.

Guest
Add a comment...