技術專欄 #CVE #Pwn2Own #RCE #IoT

Your printer is not your printer ! - Hacking Printers at Pwn2Own Part II

Angelboy 2023-11-06

English Version, 中文版本

Hacking Printers at Pwn2Own Toronto 2022

延續之前的研究,去年我們也在 Canon 的其他型號中,找到了 Pre-auth RCE 漏洞 (CVE-2023-0853CVE-2023-0854),同時 HP 的印表機也有找到 Pre-auth RCE 的漏洞,然而最終與其他隊伍撞洞。我們將在本文講述我們在 Pwn2own Toronto 中所使用的 Canon 及 HP 漏洞的細節,以及我們的利用方式。

  • Pwn2Own Toronto 2022 Target
Target Price Master of Pwn Points
HP Collor LaserJet Pro M479fdw $20000 2
Lexmark MC3224i $20000 2
Canon imageCLASS MF743Cdw $20000 2

Analysis

Canon

Firmware Extract

與 2021 年相同,可參考前述部分,本次版本為 v11.04 。

HP

Firmware 本身可以從 HP 的 Firmware 網站 中取得,但與 2021 年不同,並無法直接用 binwalk 解出,這邊的 Firmware 是透過 AES 加密的,從現有的資訊中不太好直接解開。

而這邊起初想法是找相同系列的 Fimware 看看是否有未加密版本,然而 HP 官方的 Firmware 中,並沒有符合條件的 Firmware,原本打算拆印表機想辦法 Dump firmware,但我們後來在 Google 的過程中,找到了舊版的 mirror 站,而該網站有開 index of,我們可以從中獲得所有在 mirror 網站中的 Firmware。

但這邊問題是該 Mirror 網站只有 mirror 到 2016 並沒有最新版本的資訊,不過後來可以從網站資訊中,獲得官方的目錄結構,從而取得相同系列的但沒有加密的 Firmware。

在分析過後,我們從 Firmware 中找到 fwupd 中有解密相關資訊,透過逆向可以知道加密方法及 Key,進而解出目標版本的 Firmware。

HP Collor LaserJet Pro M479fdw

  • OS - Linux Base
  • ARMv7 32bit little-endian

Vulnerability & Exploitation

Canon

mDNS (CVE-2023-0853)

mDNS 協定主要提供了區網中的域名解析功能,並且不需要有 Name Server 的介入,常用於 Apple 及 IoT 設備中。

而在 Canon 中,預設情況下,也提供了相同的功能,方便使用者尋找區網中的印表機。

該協定主要以 DNS 為基礎,基本上 mDNS 也大多建立在 DNS 封包格式 (RFC1035) 上,格式如下:

The packet format:

    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

The header contains the following fields:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

主要可以拆分為 Header 及 body 部分,主要的請求都放在 body 中,後面三個欄位為同樣的格式。 Answer 欄位主要紀錄針對 Question 的 Resource records (RRs),

Resource record format:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
(diagram from https://www.ietf.org/rfc/rfc1035.txt)

RDATA 部分會根據 type 不同而有所不同,而當 type=NSEC 其格式如下

   The RDATA of the NSEC RR is as shown below:

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   /                      Next Domain Name                         /
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   /                       Type Bit Maps                           /
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://www.ietf.org/rfc/rfc4034.txt)

其餘部分在這個漏洞中不太重要,不另外多做詳細解釋,更多細節可以參考 RFC6762RFC1035 以及 RFC4034

漏洞位置 當 Canon ImageCLASS MF743Cdw 在處理 Answer 欄位(type=NSEC)時,並沒有檢查長度導致 stack overflow 。

bnMdnsParseAnswers function 是主要負責處理封包中 answer 欄位

int __fastcall bnMdnsParseAnswers(
        netbios_header *mdns_packet,
        unsigned int *ppayloadlen,
        netbios_header *pmdns_header,
        _WORD *anwser_rr,
        rrlist **payload,
        _DWORD *pinfo)
{
  ...
  char nsec_buf[256]; // ------ fixed size on the stack
  ...
    
  _mdns_packet = (int)mdns_packet;
  p_payloadlen = ppayloadlen;
  p_mdns_header = pmdns_header;
  anwser_cnt = anwser_rr;
  v66 = 0;
  cur_ptr = &mdns_packet->payload[*pinfo];
  v9 = *payload;
  v10 = *payload;
  do
  {
    v11 = v10 == 0;
    if ( v10 )
    {
      v9 = v10;
      v10 = (rrlist *)v10->c;
    }
    else
    {
      v6 = aBnmdnsparseans;
      v10 = 0;
      v67 = 0;
    }
  }
  while ( !v11 );
  while ( (unsigned __int16)*anwser_cnt > v67 )
  {
    ...
    if...
    type = (unsigned __int16)pname->type;
    if ( type == 28 )
      goto LABEL_36;
    if...
    if...
    if ( type != 0x21 )
    {
      if ( type != 47 )                         // NSEC
      {
        ...
        goto LABEL_95;
      }
      v62 = 0;
      v63 = 0;
      zeromemory(nsec_buf, 256, v19, v20);
      v47 = bnMdnsMalloc(8);
      rrlist->pname->nsec = v47;
      if ( !v47 )
      {
        bnMdnsFreeRRLIST((int)rrlist);
        v50 = 2720;
LABEL_76:
        debugprintff(
          3610,
          3,
          "[bnjr] [%s] <%s:%d> bnMdnsParseAnswers error in malloc(NSEC)\n",
          "IMP/mdns/common/tcBnMdnsMsg.c",
          v6,
          v50);
        return 3;
      }
      maybe_realloc(v47, 8);
      nsec = rrlist->pname->nsec;
      nsec_len = bnMdnsGetDecodedRRNameLen(cur_ptr, *ppayloadlen, (char *)_mdns_packet, &dwbyte);
      if...
      if...
      v51 = (_BYTE *)bnMdnsMalloc(nsec_len);
      *(_DWORD *)nsec = v51;
      if...
      consume_label(cur_ptr, *ppayloadlen, _mdns_packet, v51, nsec_len);
      v52 = dwbyte;
      v53 = &cur_ptr[dwbyte];
      v54 = *ppayloadlen - dwbyte;
      *ppayloadlen = v54;
      v55 = (unsigned __int8)v53[1];
      v56 = (unsigned __int8)*v53;
      nsec_ = v53 + 2;
      *ppayloadlen = v54 - 2;
      v57 = v56 | (v55 << 8);
      nsec_len_ = __rev16(v57);
      if...
      memcpy((int)nsec_buf, nsec_, nsec_len_, v57); //-------- [1]  stack overflow 
      for ( i = 0; i < (int)nsec_len_; ++i )
      {
        if ( nsec_buf[i] )
        {
          for ( j = 0; j < 8; ++j )
          {
            if ( 1 << j == (unsigned __int8)nsec_buf[i] )
            {
              if ( v62 )
                v63 = 7 - j + 8 * i;
              else
                v62 = 7 - j + 8 * i;
            }
          }
        }
      }
      *(_WORD *)(nsec + 4) = v62;
   ...

  }
  *pinfo = &cur_ptr[-_mdns_packet - 0xC];
  *anwser_cnt -= v66;
  return 0;
}
}

當他在處理 NSEC(47) 的 Record 時,並沒有檢查長度就直接複製 data 到 local buffer(nsec_buf[256]) ,如上述程式碼的 [1],導致 stack overflow

Exploitation

這裡利用方法與 Pwn2Own 2021 Austin 時相同,這邊就不在多做敘述。

NetBIOS (CVE-2023-0854)

在 NetBIOS 中主要提供下列三種不同的服務:

  • Name service (NetBIOS-NS) : Port 137/TCP and 137/UDP
  • Datagram distribution service (NetBIOS-DGM) : Port 138/UDP
  • Session service (NetBIOS-SSN) : Port 139/TCP

這邊我們將會把重點放在 NetBIOS-NS 中,NetBIOS-NS 也會提供區網中域名解析的服務,常見於 Windows 作業系統中,而該封包格式也是基於 DNS 的封包。其詳細內容定義於 RFC1002

The packet format:

     1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           | OPCODE  |   NM_FLAGS  | RCODE |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          QDCOUNT              |           ANCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          NSCOUNT              |           ARCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://datatracker.ietf.org/doc/html/rfc1002)

而 Query 則會被放在 header 之後

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   + ------                                                ------- +
   |                            HEADER                             |
   + ------                                                ------- +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                       QUESTION ENTRIES                        /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                    ANSWER RESOURCE RECORDS                    /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                  AUTHORITY RESOURCE RECORDS                   /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                  ADDITIONAL RESOURCE RECORDS                  /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://datatracker.ietf.org/doc/html/rfc1002)

其中我們只須關注於 Question Entries 欄位

Question Section:

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                         QUESTION_NAME                         /
   /                                                               /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         QUESTION_TYPE         |        QUESTION_CLASS         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Question Name 都是由許多 label 組成,每個 label 都如同前述 LLMNR 所述,都是長度加上字串的組合。其餘欄位則不另外多加敘述,詳細內容可參考 RFC1002

漏洞位置 當 Canon ImageCLASS MF743Cdw 在處理 NetBIOS 封包的 Question 欄位時,沒有正確檢查長度導致 Heap Overflow 。

其漏洞位置在 cmNetBiosParseName 中,我們可透過 ndNameProcessExternalMessage 觸發。

我們這邊就稍微來分析一下漏洞成因:

當 Canon 中的 NetBIOS 服務啟動時,會先去初始化 netbios_ns_buffer ,並分配 0xff 大小空間給該 buffer。

int ndNameInit()
{
  sub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndnampro.c", 0x44ED3194, 97, 0x64u);
  netbios_ns_buffer = calloc(1, 0xFF);
  ...
  return -1;
}

當接收到來自 137/UDP 的 NetBIOS 封包時,就會透過 ndNameProcessExternalMessage 來處理封包

int __fastcall ndNameProcessExternalMessage(Adapter *a1)
{
  netbios_header *packet; // r0
  unsigned __int8 *v3; // r6
  int flag; // r0
  int v5; // r5
  int v6; // r0
  int v8; // r4
  char nbname[40]; // [sp+8h] [bp-28h] BYREF

  sub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndnampro.c", 0x44ED31AC, 178, 0x64u);
  packet = (netbios_header *)a1->packet;
  LOWORD(a1->vvv) = LOBYTE(packet->id) | (HIBYTE(packet->id) << 8);
  v3 = cmNetBiosParseName(packet, (unsigned __int8 *)packet->payload, (int)nbname, netbios_ns_buffer, 0xFFu);  //---- [1]
  //heap overflow at netbios_ns_buffer  
  if...
  flag = getname_query_flag((netbios_header *)a1->packet);
  v5 = flag;
  if ( flag == 0xA800 )
  {
    v6 = ndInternalNamePositiveRegistration(a1, (int)nbname, (int)v3);
    goto LABEL_17;
  }
  if ( flag > 0xA800 )
  {
    switch ( flag )
    {
      case 0xA801:
        v6 = ndInternalNameNegativeRegistration(a1, (int)nbname);
        goto LABEL_17;
   ...
    }
    goto LABEL_14;
  }
   
  ...
  ndInternalNameNegativeQuery((int)a1, (int)nbname);
  v6 = ndExternalNameNegativeQuery((int)a1, nbname);
LABEL_17:
  v8 = v6;
  assset("netcifsnqendapp/IMP/nq/ndnampro.c", 0x44ED31AC, 238, 100);
  return v8;
}


在上述程式碼[1] 中,該 cmNetBiosParseName 函式,會去處理 Question 欄位中的名稱,也提供了 buffer 大小給該函式,然而該函式並沒有正確檢查長度,導致複製過多的資料到 netbios_ns_buff 導致 heap overflow

我們來看一下 cmNetBiosParseName 函式

unsigned __int8 *__fastcall cmNetBiosParseName(
        netbios_header *netbios_packet,
        unsigned __int8 *netbios_label,
        int netbios_name,
        _BYTE *domain_name,
        unsigned int maxlen)
{
  char v5; // r9
  unsigned __int8 *v11; // r0
  _BYTE *v12; // r1
  unsigned int i; // r0
  char v15; // r3
  char v16; // r2
  int v17; // r0
  unsigned __int8 *v18; // r0
  unsigned int v19; // r3
  char *label_; // r0
  unsigned int labellen_; // r4
  unsigned int labellen; // t1
  char *v23; // r5
  unsigned __int8 *next[9]; // [sp+4h] [bp-24h] BYREF

  ...
  if ( *v11 == 0x20 )
  {
   ...
    v17 = *next[0];
    if ( *next[0] )
      v5 = '.';
    else
      *domain_name = 0;
    if ( v17 )
    {
      do
      {
        v18 = resolveLabel(netbios_packet, next);
        labellen = *v18;
        label_ = (char *)(v18 + 1);
        labellen_ = labellen;
        if ( maxlen > labellen )
        {
          memcpy((int)domain_name, label_, labellen_, v19);
          v23 = &domain_name[labellen_];
          maxlan -= labellen_;    // ---------- [2]              // it does not subtract the length of "."
          *v23 = v5;
          domain_name = v23 + 1;
        }
      }
      while ( *next[0] );
      *(domain_name - 1) = 0;
    }
    assset("netcifsnqecorelib/IMP/nq/cmnbname.c", 0x44A86D7C, 634, 100);
    return next[0] + 1;
  }
  else
  {
    logg("netcifsnqecorelib/IMP/nq/cmnbname.c", 0x44A86D7C, 595, 10);
    return 0;
  }
}


從這個函式中,可以看出他在處理 domain name 時,有按照所提供的參數來檢查長度,並且會在每個 label 間加入 .,然而在 [2] 的部分並沒有去檢查 . 這個字元的長度,實際上的長度可以比原本的 buffer 還要長,導致 buffer overflow。

Exploitation

原本以為會需要更詳細去逆向 Heap internal,不過幸運的是,後來發現到 buffer 後面有好用的結構可以利用。

netbios_ns_buffer 後,存在一個結構,這邊先命名為 nb_info

The layout of heap memory:

The structure of nb_info :

struct nb_info {
    int active;
    char nbname[16];
    int x;
    int y;
    short z;
    short src_port;
    short tid;
    short w;
    Adapter *adapter;
    char *ptr;
    int state;
    ...
}

該結構主要用來儲存 NetBIOS 的名稱資訊,而其中也包含另外一個結構,這裡命名為 Adapter,主要儲存該 NetBIOS 的連線資訊。

The structure of Adapter :

struct Adapter
{
  int idx;
  _BYTE gap0[16];
  int x;
  int fd_1022;
  int fd_1023;
  int y;
  _WORD src_port;
  _DWORD src_ip;
  int vvv;
  int packet;
  _DWORD recv_bytes;
  char* response_buf;
  _DWORD dword3C;
};

在初步了解這些結構之後,我們可以先回頭看一下 ndNameProcessExternalMessage,如果將封包中的 flag 欄位設成 0xA801,將會使用 ndInternalNameNegativeRegistration 去處理 NetBIOS name. 該結果將會寫入Adapter->responsebuf.

  case 0xA801:
        v6 = ndInternalNameNegativeRegistration(a1, (int)nbname);
        goto LABEL_17;

在 ndInternalNameNegativeRegistration 中:

int __fastcall ndInternalNameNegativeRegistration(Adapter *adapter, int a2)
{
...
     if ( v8 )
    {
      returnNegativeRegistrationResponse((nb_info *)v6, adapter, 3);
    }
   
...
}

只要滿足條件就會去 returnNegativeRegistrationResponse 處理 Response,而在 returnNegativeRegistrationResponse 中:


int __fastcall returnNegativeRegistrationResponse(nb_info *nbinfo, Adapter *adapter, int a3)
{
  int v6; // r2
  netbios_header *response_buf; // r5
  int NameWhateverResponse; // r2
  unsigned __int8 v10[20]; // [sp+4h] [bp-2Ch] BYREF
  __int16 v11; // [sp+18h] [bp-18h] BYREF
  int v12; // [sp+1Ah] [bp-16h] BYREF

  maybe_memcpy_s(v10, 0x44ED3100, 20);
  sub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndinname.c", 0x44ED30DC, 2349, 0x64u);
  if...
  v11 = 0;
  sub_40B06FD8(*(_DWORD *)adapter->gap0, &v12);
  response_buf = *(netbios_header **)nbinfo->adapter->responsebuf;
  NameWhateverResponse = ndGenerateNameWhateverResponse(response_buf, nbinfo->name, 0x20u, (char *)&v11, 6u);
  if ( NameWhateverResponse > 0 )
  {
    response_buf->id = nbinfo->id; //------[3]
    response_buf->flag = __rev16(a3 | 0xA800);
    if ( sySendToSocket(
           nbinfo->adapter->fd_1022,
           (const char *)response_buf,
           NameWhateverResponse,
           v10,
           (unsigned __int16)nbinfo->src_port) <= 0 )
    {
      logg("netcifsnqendapp/IMP/nq/ndinname.c", 0x44ED30DC, 2392, 10);
      v6 = 2393;
    }
    else
    {
      v6 = 2396;
    }
    goto LABEL_9;
  }
  assset("netcifsnqendapp/IMP/nq/ndinname.c", 0x44ED30DC, 2372, 100);
  return -1;
}
   

可以看到 [3] 會把 response_buf->id 寫成 nbinfo->id。

也就是說,如果我們可以覆蓋掉 nb_info 結構,並且構造 Adapter 我們就會有一個任意記憶體寫入,而實際上構造方式很簡單,只要找個 Global Buffer 去構造就可以了,我們這邊選擇了 BJNP Session Buffer 去構造我們結構。

而在我們有任意寫入之後,我們可以覆蓋 SLP 的函數指針來達成 RCE,後續利用就與前述相同,這邊就不另外多做介紹了。

HP

這次目標是 HP Collor LaserJet Pro M479fdw 這台印表機,其主要是 Linux Base 的,分析起來相對單純很多,而其中 Web Service 底下有許多的 cgi 來提供各種不同的印表機操作,這些都是透過 FastCGI 方式來運作,可參考 nginx config 來看每個 path 分別對應到哪個 Port 及哪個 Service

/Sirius/rom/httpmgr_nginx/ledm.conf

/usr/bin/local/slanapp 負責處理 scan 相關的操作,主要 listen 在 127.0.0.1:14030

當我們存取 /Scan/Jobs 路徑時,就會透過這個 cgi 來處理

漏洞位置

當 HP 處理 /Scan/Jobs 底下的 get 請求時,會使用 rest_scan_handle_get_request 來處理,同時也會將 pathinfo 一起傳入

int __fastcall rest_scan_handle_get_request(int a1, int a2, char *s1, unsigned __int8 *pathinfo, int pathinfo_len)
{
  struct httpmgr_fptrtbl **v8; // r0
  int v9; // r1
  int v10; // r2
  struct httpmgr_fptrtbl **v11; // r0
  int v12; // r1
  int result; // r0
  int v14; // r0
  int next_char; // r4
  unsigned __int8 *v16; // r3
  int v17; // r1
  int v18; // t1
  char *v19; // r5
  int v20; // r5
  int v21; // r0
  int v22; // r7
  size_t v23; // r8
  int v24; // r0
  char first_path_info[32]; // [sp+8h] [bp-D8h] BYREF
  char second_path_info[32]; // [sp+28h] [bp-B8h] BYREF
  char pagenumber[152]; // [sp+48h] [bp-98h] BYREF

  if...
  if...
  if ( !strncmp(s1, "/Scan/UserReadyToScan", 0x15u) )
  {
   ...
  }
  else
  {
    v14 = strncmp(s1, "/Scan/Jobs", 0xAu);
    if ( v14 )
    {
     ...
    }
    ...
    next_char = *pathinfo;
    if ( (next_char & 0xDF) == 0 )
    {
      first_path_info[0] = 0;
LABEL_37:
      _DEBUG_syslog("REST_SCAN_DEBUG", 0, 0x411FA215, 400, 0);
      v8 = rest_scan_req_ifc_tbl;
      v9 = a1;
      v10 = 400;
      goto LABEL_6;
    }
    v16 = pathinfo;
    v17 = 0;
    do  //------------------------------------------------------ [2]  
    {
      if ( next_char != '/' )
        first_path_info[32 * v17 + v14] = next_char;
      v19 = &first_path_info[32 * v17];
      if ( next_char == '/' )
      {
        v20 = 32 * v17++;
        pagenumber[v20 - 64 + v14] = 0;
        v19 = &first_path_info[v20 + 32];
        v14 = 0;
      }
      else
      {
        ++v14;
      }
      v18 = *++v16;
      next_char = v18;
    }
    while ( (v18 & 0xDF) != 0 );
    v19[v14] = 0;
    if ( v17 != 2 || strcmp(second_path_info, "Pages") || dword_5DBC8 != strtol(first_path_info, 0, 10) )
      goto LABEL_37;
    v24 = strtol(pagenumber, 0, 10);
    result = rest_scan_send_scan_data(a1, v24) + 1;
    if ( result )
      rest_scan_vp_thread_created = 1;
    else
      return rest_scan_send_err_reply(a1, 400);
  }
  return result;
}

但當在 [2] 處理 pathinfo 時,並沒有檢查長度,並且直接複製到 local buffer(first_path_info[32]) 中,導致 stack overflow。

Exploitation

我們可以構造很長的 request 到 /Scan/Jobs/ 來觸發漏洞,並且該處沒有 Stack Guard 也沒有 ASLR,可以直接覆蓋 return address,這邊只需要做 ROP 覆蓋掉 strncmp 的 GOT 到 system 後,就可以透過 /Copy/{cmd} 來執行任意指令了。

不過最終這個漏洞與其他隊伍撞洞了。

Summary

從 Pwn2Own Austin 2021 到 Pwn2Own Toronto 2022 的結果看下來,印表機安全依舊是容易被忽略的,短短一年間,能打下印表機的隊伍也大幅增加,甚至到第三年 Pwn2Own Toronto 2023 也還是被許多隊伍找到漏洞,最後也建議大家如果有使用到這些 IoT 設備,盡量把不必要的服務關閉並且設好防火牆及做好權限控管,以減少被攻擊的可能。