近年來,打印機已成為企業內網中不可或缺的設備之一,其功能也隨著科技的發展日益增多,除了一般的打印或傳真功能外,也開始支持AirPrint等云打印服務,使其更易于使用。從移動設備直接打印現已成為物聯網(IoT)時代的基本要求。鑒于其便捷性,人們也常用它來打印公司內部的一些敏感文件,這使得保證打印機的安全變得更加重要。
2021年,研究人員在佳能(Canon)和惠普(HP)打印機中發現了Pre-auth RCE漏洞(CVE-2022-24673和CVE-2022-3942),另外還在Lexmark中發現了漏洞CVE-2021-44734,并在Pwn2Own Austin 2021中順利取得了Canon ImageCLASS MF644Cdw、HP Color LaserJet Pro MFP M283fdw及 Lexmark MC3224i的控制權。這篇研究將詳述Canon和HP漏洞的細節及其利用方式。
早期在使用打印機時,通常需要使用IEEE1284或USB打印機電纜將打印機連接到計算機,并且在使用時會額外裝上廠商提供的打印機驅動程序。而如今,市場上的大多數打印機都不需要USB或傳統的電纜,只要將打印機通過網線連接到內網,就可以立即找到并使用打印機。
目前市面上的打印機新增了各種各樣的服務,如FTP、AirPrint、Bonjour等,目的無非是使打印變得更加便捷。
動機:為什么要研究打印機?
紅隊
在做紅隊評估時,我們發現打印機普遍出現在公司內網,但幾乎總是會有不止一臺打印機常常被忽視且缺乏更新。這也是紅隊隱藏動作的絕佳目標,因為它通常很難被發現。值得一提的是,比較大型的企業也很有可能將其連接到AD,使其成為惡意行為者獵取機密信息的入口點。
Pwn2Own Austin 2021
另一個原因是打印機在2021年首次成為Pwn2Own Mobile的主要目標之一。我們也準備再次挑戰Pwn2Own舞臺,便決定一探究竟。

起初,我們以為這一過程非常簡單,與大多數物聯網設備一樣,能輕易找到其中的命令注入漏洞,殊不知有許多打印機使用RTOS而非一般的Linux系統,這促使我們決心挑戰它。
本文將重點介紹較為精彩的佳能和惠普部分,Lexmark有機會再談。
分析
剛開始研究的時候,我們參考了很多文章,都是需要拆解硬件進行分析,才能獲得調試控制臺,再用內存轉儲方法獲取原始固件。但最終,我們選擇了另一種方式,沒有拆除任何一臺打印機。
佳能-固件提取
初始分析版本為v6.03,我們一開始使用binwalk去解析它,但固件是經過模糊處理的,我們無法直接分析。

【經過模糊處理的佳能ImageCLASS MF644Cdw固件】
我們還嘗試了Synacktiv的《TREASURE CHEST PARTY QUEST: FROM DOOM TO EXPLOIT》及Contextis research的《Hacking Canon Pixma printer - DOOM Encryption》等文章。但這次是一個完全不同的系列,我們無法使用相同的方法來消除固件的混淆。
所以我們開始分析混淆過的固件格式和內容。

我們大致上可以從被混淆的固件中看到,每個混淆過的固件的開頭都會是Magic NCFW,并帶有該固件的大小,而其他部分都是被混淆的數據。
所以我們開始猜想,也許這臺打印機的舊固件沒有被混淆,直到某個特定的版本才開始混淆。如果我們能得到中間版本,也許就有機會得到去混淆的方法。而這個Magic也可以讓我們區分它是否被混淆了。
以下這個網址是通過官網或更新包獲取的固件下載URL。
https://pdisp01.c-wss.com/gdl/WWUFORedirectTarget.do?id=MDQwMDAwNDc1MjA1&cmp=Z01&lang=EN
經過分析,它可以分為三個部分。

我們可以大致推斷出下載URL的規則。借此方法,我們便能下載所有版本的固件。我們當時下載的版本包括:
- V2.01
- V4.02
- V6.03
- V9.03
- V10.02
而V10.02是幾周會才會發布的版本,可以先從這里優先下載。在下載了所有版本之后,我們發現這個系列的固件都是經過混淆的,并且無法從以前版本獲得去混淆的方法。
但我們可以嘗試下載佳能的其他系列固件,看看是否有類似的混淆算法。下載完所有固件后,文件總大小為130GB。我們可以通過查找NCFW和servicemode.html找到未混淆的固件。

最后,我們找到了四個滿足條件的固件,并選擇WG7000系列打印機進行分析,并找到了疑似去混淆的函數。

幸運的是,通過重寫這個函數,可以解出明文的MF644Cdw固件。

在提取固件之后,我們需要映像基址(image base address),以便IDA能夠有效地識別和引用字符串。首先,我們通過常用的分析工具rbasefind找到基址(image base)。

我們找到的第一個base是0x400b0000。但在用IDA反編譯后,大部分函數與調試消息字符串不對應。

如上圖所示,loc_4489AC08應該指向函數名的字符串,但是這個地址不是一個常規字符串。相反地,它被識別為代碼段,其內容也不是字符串。這表明該位置不是實際地址,而是有輕微的偏移,但它并沒有對反編譯函數造成太大問題。
如何解決這個問題?我們首先找到一個已知函數名的函數和屬于它的函數名字符串進行調整。找到偏移量后,我們將image base調整到正確的地址即可。最終找到的image base是0x40affde0。調整后,可以看到原來的函數名可以正確識別。

接下來就可以正常分析固件,而經過初步分析可得知,佳能ImageCLASS MF644Cdw架構如下:
OS - DryOSV2
Customized RTOS by Canon
ARMv7 32bit little-endian
Linked with application code into single image
Kernel
Service
惠普-固件提取
惠普的固件相對容易獲得。我們可以使用binwalk -Z來獲取正確的固件。這一過程大約花了3-4天左右的時間。而其他尋找image base address 等步驟,則與佳能相同,此處不再贅敘。經過初步分析,我們發現HP Color LaserJet Pro MFP M283fdw的架構如下:
OS
RTOS - Modify from ThreadX/Green Hills
ARM11 Mixed-endian
Code - little-endian
Data - Big-endian
攻擊面
目前市場上的大多數打印機都默認啟用了許多服務。
服務 端口 描述 RUI TCP 80/443 Web接口 PDL TCP 9100 頁面描述語言 PJL TCP 9100 打印機作業語言 IPP TCP 631 Internet打印協議 LPD TCP 515 行式打印機(LPD)協議 SNMP UDP 161 簡單網絡管理協議(SNMP) SLP TCP 427 服務定位協議 mDNS UDP5353 多播DNS(mDNS) LLMNR UDP 5355 鏈路本地組播名稱解析(LLMNR) |
一般來說,為了方便管理,通常都會打開RUI(web界面),再者9100端口也是打印機常用的接口,主要用于傳輸打印數據。
其他選項因供應商而異,但通常都有列出的選項,并且大多數都是默認啟用的。在評估了整個架構之后,我們將重點關注服務發現和DNS系列服務。因為我們的長期經驗經常觀察到,制造商實現的此類協議往往容易出現漏洞。我們分析的主要服務是SLP、mDNS和LLMNR。
接下來,我們就以Pwn2Own Austin 2021作為案例研究,看看這些協議通常存在哪些問題。
在Pwn2Own大會上入侵打印機
佳能-服務定位協議
SLP是一種服務發現協議,它允許計算機和其他設備在局域網中找到服務。在過去,EXSI的SLP中存在許多漏洞。而佳能的SLP服務主要由自己實現。在研究SLP的細節之前,我們需要討論一下SLP數據包的結構。

【SLP包結構圖】
這里我們只需要注意function-id。該字段會決定請求類型和有效負載部分的格式。而佳能只會實現服務請求和屬性請求兩種。

在屬性請求(Attribute Request, AttrRqst)中,用戶可以根據服務和范圍獲取屬性列表。我們可以指定要查找的范圍,例如Canon打印機。
示例:

而屬性請求結構大致如下所示:

它主要包括長度(length)和值(value)。解析這種格式需要特別注意,因為這里很容易出問題。事實上,佳能在解析這個結構時就出了問題。
漏洞
在解析作用域列表時,它將轉義字符轉換為ASCII。例如,\41將被轉換為a。但是這個簡單的轉換有什么問題呢?讓我們看一下偽代碼(Pseudo code)。
int parse_scope_list(...){
char destbuf[36];
unsigned int max = 34;
parse_escape_char(...,destbuf,max);
}
如上面的代碼所示,在parse_scope_list中,它將固定大小的緩沖區destbuf和最大大小34傳遞給parse_escape_char。這里沒什么問題,讓我們看一下parse_escape_char。
int __fastcall parse_escape_char(unsigned __int8 **pdata, _WORD *pdatalen, unsigned __int8 *destbuf, _WORD *max)
{
unsigned int idx; // r7
int v7; // r9
int v8; // r8
int error; // r11
unsigned __int8 *v10; // r5
unsigned int i; // r6
int v12; // r1
int v13; // r0
unsigned int v14; // r1
bool v15; // cc
int v16; // r2
bool v17; // cc
unsigned __int8 v18; // r0
int v19; // r0
unsigned __int8 v20; // r0
unsigned int v21; // r0
unsigned int v22; // r0
idx = 0;
v7 = 0;
v8 = 0;
error = 0;
v10 = *pdata;
for ( i = (unsigned __int16)*pdatalen; i && !v7; i = (unsigned __int16)(i - 1) )
{
v12 = *v10;
if ( v12 == ',' )
{
if ( i < 2 )
return -4;
v7 = 1;
}
else
{
if ( v12 == '\\' ) //----------------------[1]
{
if ( i < 3 )
return -4;
v13 = v10[1];
v14 = v13 - '0';
v15 = v13 - (unsigned int)'0' > 9;
if ( v13 - (unsigned int)'0' > 9 )
v15 = v13 - (unsigned int)'A' > 5;
if ( v15 && v13 - (unsigned int)'a' > 5 )
return -4;
v16 = v10[2];
v17 = v16 - (unsigned int)'0' > 9;
if ( v16 - (unsigned int)'0' > 9 )
v17 = v16 - (unsigned int)'A' > 5;
if ( v17 && v16 - (unsigned int)'a' > 5 )
return -4;
if ( v14 <= 9 )
v18 = 0x10 * v14;
else
v18 = v13 - 0x37;
if ( v14 > 9 )
v18 *= 0x10;
*destbuf = v18; //-------------------[2]
v19 = v10[2];
v10 += 2;
v20 = (unsigned int)(v19 - 0x30) > 9 ? (v19 - 55) & 0xF | *destbuf : *destbuf | (v19 - 0x30) & 0xF;
*destbuf = v20;
LOWORD(i) = i - 2;
if ( !strchr((int)"(),\\!<=>~;*+", *destbuf) )
{
v21 = *destbuf;
if ( v21 > 0x1F && v21 != 0x7F )
return -4;
}
goto LABEL_40;
}
if ( strchr((int)"(),\\!<=>~;*+", v12) ) //-----------------------[3]
return -4;
v22 = *v10;
if ( v22 <= 0x1F || v22 == 0x7F )
return -4;
if ( v22 != ' ' )
{
v8 = 0;
goto LABEL_35;
}
if ( !v8 )
{
v8 = 1;
LABEL_35:
if ( (unsigned __int16)*max <= idx ) //----------------------[4]
{
error = 1;
goto next_one;
}
if ( v8 )
LOBYTE(v22) = 32;
*destbuf = v22;
LABEL_40:
++destbuf;
idx = (unsigned __int16)(idx + 1);
}
}
next_one:
++v10;
}
if ( error )
{
*max = 0;
debugprintff(3645, 4, "Scope longer than buffer provided");
LABEL_48:
*pdata = v10;
*pdatalen = i;
return 0;
}
if ( idx )
{
*max = idx;
goto LABEL_48;
}
return -4;
}
可以看到[3]是一個不處理轉義字符的case。檢查長度是否超過最大值[4]。但是,如果[1]處理轉義字符,則不進行長度檢查,轉換后的結果直接復制到目標緩沖區[2]。
一旦給定一個長轉義字符串,就會導致堆棧溢出。
在找到漏洞后,第一件事就是看它本身有什么保護,來決定后續的利用計劃。但經過分析,我們發現佳能打印機沒有任何與內存相關的保護。
保護機制:
- 無堆棧保護;
- 無DEP;
- 無ASLR;
沒有堆棧保護,沒有DEP和ASLR,妥妥的“黑客友好型”!就像回到90年代,一個堆棧溢出就能控制整個世界。接下來,與過去的堆棧溢出利用方法一樣,我們只需要找到一個固定地址來存儲shellcode,覆蓋返回地址,然后跳轉到shellcode。最后,我們找到了BJNP服務來存儲我們的shellcode。
BJNP
BJNP本身也是Canon設計的服務發現協議,過去存在許多漏洞。它會將可控的會話數據存儲在全局緩沖區中。我們可以使用這個函數將我們的shellcode放在一個固定的位置,基本上也沒什么嚴格的限制。
漏洞利用步驟
- 使用BJNP將shellcode存儲在全局緩沖區中;
- 觸發SLP中的堆棧溢出并覆蓋返回地址;
- 返回到全局緩沖區
Pwn2Own Austin 2021
一般來說,Pwn2Own組織者(ZDI)會要求參與者證明我們已經攻破了目標。這里的呈現方法取決于玩家。最初,我們想的是直接在LCD屏幕上打印logo。

然而,我們花了很多時間來弄清楚如何在屏幕上打印logo,這比發現漏洞和編寫漏洞耗費的時間還要長。最后,由于時間限制,我們采用了更安全的方法,直接更改Service Mode字符串并將其打印在屏幕上。
調試
有些人可能想知道如何在這種環境中進行調試。通常有以下幾種調試方法:
- 拆卸打印機并獲得調試控制臺。
- 使用舊漏洞安裝自定義調試器
但是,我們已經更新到當時的最新版本。這個版本沒有已知的漏洞,所以我們需要降級回版本。拆卸硬件也需要額外的時間和成本。但我們當時已經有了一個漏洞,拆除硬件或降級并不劃算。最后,我們仍然使用最傳統的睡眠調試(sleep debug)方法。

在ROP或執行shellcode之后,將結果打印到網頁或其他可見的地方,然后調用sleep。我們可以從網頁上讀取結果,最后重啟機器來重復這個過程。不過,實際上更好的做法還是接上調試控制臺會方便一點。
接下來,讓我們談談惠普打印機。
惠普-鏈路本地組播名稱解析
LLMNR與mDNS非常相似。它在相同的本地鏈接上提供基本名稱解析。但它比mDNS更直接,通常還與一些服務發現協議合作。這里簡單介紹一下這個機制:
在局域網域名解析中,客戶端A將首先使用組播來查找客戶端C在局域網中的位置。

Client C接收到報文后,將報文返回給Client A,由Client A進行鏈路本地域名解析。

LLMNR主要基于DNS報文格式,其格式如下:

主要格式是標題后面跟著查詢,Count表示不同類型查詢的數量。

每個DNS查詢由許多標簽組成,每個標簽將包含長度和字符串,如上圖所示。還有一個消息壓縮(Message Compression)機制。處理這些問題很容易出現漏洞。在BlackHat 2021的《復雜性的代價:實現相同RFC的不同漏洞》(THE COST OF COMPLEXITY:Different Vulnerabilities While Implementing the Same RFC)中,也提到了類似的問題。
漏洞
讓我們來看看惠普的實操:
int llmnr_process_query(...){
char result[292];
consume_labels(llmnr_packet->qname,result,...);
...
}
這里可以看到,HP在處理LLMNR數據包時,會將一個固定大小的緩沖區傳入,用來放處理后的結果,而consumer_lables則是主要用來處理DNS標簽。
int __fastcall consume_labels(char *src, char *dst, llmnr_hdr *a3)
{
int v3; // r5
int v4; // r12
unsigned int len; // r3
int v6; // r4
char v7; // r6
bool v8; // cc
int v9; // r0
unsigned __int8 chr; // r6
int result; // r0
v3 = 0;
v4 = 0;
len = 0;
v6 = 0;
while ( 1 )
{
chr = src[v3]; //-------------[1]
if ( !chr )
break;
if ( (int)len <= 0 )
{
v8 = chr <= 0xC0u;
if ( chr == 0xC0 )
{
v9 = src[v3 + 1];
v6 = 1;
v3 = 0;
src = (char *)a3 + v9;
}
else
{
len = src[v3++];
v8 = v4 <= 0;
}
if ( !v8 )
dst[v4++] = '.';
}
else
{
v7 = src[v3++];
len = (char)(len - 1);
dst[v4++] = v7; //----------[2]
}
}
result = v3 + 1;
dst[v4] = 0;
if ( v6 )
return 2;
return result;
}
而在 consume_labels 中的 [1] 會得到標簽長度,然后根據類型進行處理。而[2]則用于處理一般長度的情況,此處并沒有對長度進行檢查,就直接將標簽寫入dst緩沖區,導致了堆棧溢出。至此,我們原以為差不多結束了,接下來就可以用類似于佳能的方法來利用它。然而,當我們編寫這個漏洞時,我們發現惠普打印機有更多的保護機制。
保護機制:
- 無堆棧保護;
- 有XN(DEP);
- 有內存保護單元(MPU);
- 無ASLR;
在這種情況下,多了XN和MPU內存保護機制,該漏洞便有了更多的限制。在沒有空字節的情況下,我們只能溢出大約0x100字節,這極大地限制了我們的ROP,并使其更具挑戰性。我們需要找到其他漏洞或方法來實現我們的目標。
在一段時間后,我們開始思考惠普打印機是如何實現XN(DEP)和MPU的?讓我們回顧一下惠普的RTOS:
- 所有服務代碼及內核代碼都在同一個二進制中。
- 大多數的任務都運行在同一虛擬地址空間中(沒有進程隔離),也幾乎都運行在高權限模式下。
看完以上兩點,我們想到是否可以通過了解HP RTOS中的MMU和MPU來繞過它?
接下來,我們來看一下HP RTOS MMU機制。
HP M283fdw MMU
在HP M283fdw中使用的是一階翻譯表(Translation table)來做地址翻譯,每個翻譯表項都表示1MB的部分,而翻譯表則是固定在0x4003c000這個位置上。

每個翻譯表項都會對應到一個物理地址和該節的權限。CPU就是根據這些表項決定是否可以執行或修改權限。這里涉及的權限是AP、APX和XN。我們還可以通過這個轉換表項映射任何物理地址。

我們可以從上述的漏洞中注意到,如果在高權限下存在堆棧溢出,可以通過ROP修改翻譯表項。但是當我們試圖直接寫入翻譯表時,惠普打印機崩潰了。

我們檢查發現,內存故障異常的主要原因是內存保護單元(MPU)保護翻譯表。
接下來,我們再來看MPU的機制。
HP M283fdw MPU
MPU可以將內存劃分為多個區域,并為每個區域設置單獨的保護屬性。它是一種與MMU完全不同的機制,經常出現在物聯網設備中。HP在引導時啟用MPU并定義每個區域的權限,因此我們不能操作頁表。

經過長時間的逆向工程和參考ARM手冊后,我們發現可以通過清除MPU_CTRL關閉MPU。我們發現該位置為0xE0400304,與ARM的規格略有不同。

漏洞利用
在了解HP的MMU和MPU機制后,我們可以很容易地使用ROP關閉MPU并成功修改翻譯表項。我們可以任意修改任何服務的代碼,并最終選擇了Line Printer Daemon(LPD)。我們將其修改為后門,將更多的有效負載讀取到指定位置,最后執行shellcode。
但有一點必須特別注意:在覆蓋翻譯表項和LPD代碼之后,請確保Flush TLB并清掉I-cache和D-cache。否則,它很可能在舊版本中執行,導致漏洞利用失敗。
Flush TLB
flush_tlb:
mov r12, #0
mcr p15, 0, r12, c8, c7, 0
清掉I-cache
disable_icache:
mrc p15, 0, r1, c1, c0, 0
bic r1, r1, #(1 << 12)
mcr p15, 0, r1, c1, c0, 0
漏洞利用步驟
- 觸發LLMNR中的堆棧溢出并覆蓋返回地址;
- 使用有限的ROP關閉MPU;
- 利用ROP修改翻譯表項并獲得讀寫執行權限;
- Flush TLB;
- 修改LPD服務的代碼;
- 清掉I-cache和D-cache;
- 使用修改后的LPD讀取我們的shellcode并執行;
Pwn2Own Austin 2021
當我們可以執行shellcode時,我們只剩下一周的時間,我們最終選擇使用確切的字符串在LCD上顯示Pwned by DEVCORE。

在此之后,我們還嘗試將后門改為調試控制臺,以方便查看內存信息,播放音樂等功能。
緩解建議
更新
首先是定期更新。上述所有提到的打印機都已有補丁,但它經常被忽視。我們經常發現打印機幾年不更新,甚至直接在公司內網留下默認密碼。
禁用不使用的服務
另一種緩解方法是關閉不經常使用的服務。大多數打印機默認啟用了太多通常不使用的服務。我們甚至認為你可以關閉發現(discovery)服務,只打開你想使用的服務。
部署防火墻
如果能應用防火墻就更好了。大多數打印機都提供相關功能。
原文鏈接:
https://devco.re/blog/2023/10/05/your-printer-is-not-your-printer-hacking-printers-pwn2own-part1-en/
cayman
一顆小胡椒
一顆小胡椒
ManageEngine卓豪
FreeBuf
嘶吼專業版
一顆小胡椒
一顆小胡椒
一顆小胡椒
一顆小胡椒
看雪學苑
FuzzWiki