Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86384349

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

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結構體中