Hacking Printers at Pwn2Own Toronto 2022
Based on our previous research, we also discovered Pre-auth RCE vulnerabilities((CVE-2023-0853、CVE-2023-0854) in other models of Canon printers. For the HP vulnerability, we had a collision with another team. In this section, we will detail the Canon and HP vulnerabilities we exploited during Pwn2own Toronto.
- 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
Same as 2021, you can refer to Part I. The current version is v11.04.
HP
The firmware can be obtained from HP’s official website. However, unlike in 2021, it cannot be directly extracted using binwalk. The firmware is encrypted with AES, and it’s hard to decrypt directly from the information.
At first, our thought was to look for the firmware of the same series to see if there was an unencrypted version. However, there was no such firmware on HP’s official website that met our criteria. We initially considered tearing down the printer to dump the firmware, but during our search on Google, we stumbled upon an older mirror site. This site enabled directory listing, allowing us to access all the firmware stored on that mirror website.
However, the problem was that the mirror site only mirrored up to 2016 and didn’t have the latest information. Still, we later managed to glean the official directory structure from the website information, which helped us to obtain an unencrypted firmware from a similar series.”
After our analysis, we found decryption-related information in the Firmware from fwupd
. By reverse engineering, we were able to identify the encryption method and the Key. We can use the key to decrypt the target version of the Firmware.
HP Collor LaserJet Pro M479fdw
- OS - Linux Base
- ARMv7 32bit little-endian
Vulnerability & Exploitation
Canon
mDNS (CVE-2023-0853)
We found a stack overflow on mDNS. mDNS protocol resolves hostnames to IP address within small networks that do not include a local name server and are usually used for Apple and IoT devices.
It is enabled on Canon ImageCLASS MF743Cdw(Version 11.04) by default.
Before we look at the detail of the vulnerability we need to talk about mDNS Packet Structure.
mDNS is based on the DNS packet format defined in RFC1035 Section 4 for both queries and responses. mDNS queries and responses utilize the DNS header format defined in RFC1035 with exceptions noted below:
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)
The answer section contains RRs that answer the question.
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)
The RDATA section varies depending on the ‘type’. When type=NSEC, its format is as follows:
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)
More details can reference to RFC6762.
Other element is not important in this vulnerability, so we won’t explain more here. More detail can be found at RFC6762, RFC1035 and RFC4034.
Where is the bug
When Canon ImageCLASS MF743Cdw is parsing the Answer field (type NSEC) in mDNS header, there is a stack overflow.
In the function bnMdnsParseAnswers
, it will parse answer section.
int__fastcallbnMdnsParseAnswers(netbios_header*mdns_packet,unsignedint*ppayloadlen,netbios_header*pmdns_header,_WORD*anwser_rr,rrlist**payload,_DWORD*pinfo){...charnsec_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)gotoLABEL_36;if...if...if(type!=0x21){if(type!=47)// NSEC{...gotoLABEL_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);return3;}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;elsev62=7-j+8*i;}}}}*(_WORD*)(nsec+4)=v62;...}*pinfo=&cur_ptr[-_mdns_packet-0xC];*anwser_cnt-=v66;return0;}}
When it is parsing the NSEC(type 47) record, it does not check the length of the record. It will copy the data to a local buffer(nsec_buf[256]
) at [1]
, which leads to a stack buffer overflow.
Exploitation
We can construct an mDNS packet to trigger the stack overflow. It does not have Stack Guard, so we can overwrite the return address directly. It also does not implement DEP. We can overwrite the return address with a global buffer which we can control to run our shellcode.
We finally chose BJNP session buffer as our target. It will copy our payload when we start a BJNP session.
We can run shellcode to do anything, such as modifying the website, changing the LCD screen, etc.
NetBIOS (CVE-2023-0854)
We found a heap overflow on NetBIOS. NetBIOS is a protocol for Network Basic Input/Output System. It provides services related to the session layer of the OSI model allowing applications on separate computers to communicate over a local area network. . Canon implemented the NetBIOS daemon by themselves.
It is enabled on Canon ImageCLASS MF743Cdw(Version 11.04) by default.
NetBIOS provides three distinct services:
- Name service (NetBIOS-NS) for name registration and resolution.
- Datagram distribution service (NetBIOS-DGM) for connectionless communication.
- Session service (NetBIOS-SSN) for connection-oriented communication.
We will focus on NetBIOS-NS (port 137).
Before we look at the detail of the vulnerability we need to talk about NetBIOS-NS Packet Structure.
NetBIOS-NS is based on the DNS packet format. It is defined in RFC1002 for both queries and responses. NetBIOS queries and responses utilize the NS header format defined in RFC1002 with exceptions noted 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NAME_TRN_ID | OPCODE | NM_FLAGS | RCODE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| QDCOUNT | ANCOUNT |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NSCOUNT | ARCOUNT |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(diagram from https://datatracker.ietf.org/doc/html/rfc1002)
The query will be placed after the header. The first element is QNAME which is a domain name represented as a sequence of labels, where each label consists of a length character followed by that number of characters. Other element is not important in this vulnerability, so we won’t explain more here. More details can be found at RFC1002.
Where is the bug
When Canon ImageCLASS MF743Cdw is parsing the NetBIOS in NetBIOS packets, there is a heap overflow. The vulnerability is in cmNetBiosParseName
function. We can trigger it from ndNameProcessExternalMessage
.
When NetBIOS service starts, it will initial netbios_ns_buffer
. The buffer would be allocated 0xff
bytes from the heap.
intndNameInit(){sub_41C47A20((int)"netcifsnqendapp/IMP/nq/ndnampro.c",0x44ED3194,97,0x64u);netbios_ns_buffer=calloc(1,0xFF);...return-1;}
When parsing the NetBIOS-NS in NetBIOS packets, it will use ndNameProcessExternalMessage
to process it.
int__fastcallndNameProcessExternalMessage(Adapter*a1){netbios_header*packet;// r0unsigned__int8*v3;// r6intflag;// r0intv5;// r5intv6;// r0intv8;// r4charnbname[40];// [sp+8h] [bp-28h] BYREFsub_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);gotoLABEL_17;}if(flag>0xA800){switch(flag){case0xA801:v6=ndInternalNameNegativeRegistration(a1,(int)nbname);gotoLABEL_17;...}gotoLABEL_14;}...ndInternalNameNegativeQuery((int)a1,(int)nbname);v6=ndExternalNameNegativeQuery((int)a1,nbname);LABEL_17:v8=v6;assset("netcifsnqendapp/IMP/nq/ndnampro.c",0x44ED31AC,238,100);returnv8;}
At [1]
, the function cmNetBiosParseName
does not calculate the length of the domain name correctly. It will copy the domain name to netbios_ns_buff
, which leads to a heap overflow.
Let’s take a look at cmNetBiosParseName
function.
unsigned__int8*__fastcallcmNetBiosParseName(netbios_header*netbios_packet,unsigned__int8*netbios_label,intnetbios_name,_BYTE*domain_name,unsignedintmaxlen){charv5;// r9unsigned__int8*v11;// r0_BYTE*v12;// r1unsignedinti;// r0charv15;// r3charv16;// r2intv17;// r0unsigned__int8*v18;// r0unsignedintv19;// r3char*label_;// r0unsignedintlabellen_;// r4unsignedintlabellen;// t1char*v23;// r5unsigned__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);returnnext[0]+1;}else{logg("netcifsnqecorelib/IMP/nq/cmnbname.c",0x44A86D7C,595,10);return0;}}
The function cmNetBiosParseName
will parse the domain from the label in the NetBIOS packet to the domain_name buffer and it has a verification. The verification will check that the total length of the label could not larger than maxlen, and a "."
will be added between each label. But it does not subtract the length of "."
characters so that the total length of the label can be larger than maxlen
. It will lead to overflow.
Exploitation
Luckily, there is a useful structure nb_info
to achieve our goal. We can use the heap overflow to overwrite the structure of nb_info
.
The layout of heap memory:
The structure of nb_info
and Adapter
:
structnb_info{intactive;charnbname[16];intx;inty;shortz;shortsrc_port;shorttid;shortw;Adapter*adapter;char*ptr;intstate;...}
The structure is used to store NetBIOS name information, it also has a member Adapter
to store the information of connection.
structAdapter{intidx;_BYTEgap0[16];intx;intfd_1022;intfd_1023;inty;_WORDsrc_port;_DWORDsrc_ip;intvvv;intpacket;_DWORDrecv_bytes;char*response_buf;_DWORDdword3C;};
Let’s back to ndNameProcessExternalMessage
, if the flag of NetBIOS-NS packet is set to 0xA801
, it will use ndInternalNameNegativeRegistration
to process our NetBIOS name. The result will be written to Adapter->responsebuf
.
case0xA801:v6=ndInternalNameNegativeRegistration(a1,(int)nbname);gotoLABEL_17;
At ndInternalNameNegativeRegistration
:
int__fastcallndInternalNameNegativeRegistration(Adapter*adapter,inta2){...if(v8){returnNegativeRegistrationResponse((nb_info*)v6,adapter,3);}...}
If the conditions are met, it will use ‘returnNegativeRegistrationResponse’ to handle the Response.
int__fastcallreturnNegativeRegistrationResponse(nb_info*nbinfo,Adapter*adapter,inta3){intv6;// r2netbios_header*response_buf;// r5intNameWhateverResponse;// r2unsigned__int8v10[20];// [sp+4h] [bp-2Ch] BYREF__int16v11;// [sp+18h] [bp-18h] BYREFintv12;// [sp+1Ah] [bp-16h] BYREFmaybe_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,(constchar*)response_buf,NameWhateverResponse,v10,(unsigned__int16)nbinfo->src_port)<=0){logg("netcifsnqendapp/IMP/nq/ndinname.c",0x44ED30DC,2392,10);v6=2393;}else{v6=2396;}gotoLABEL_9;}assset("netcifsnqendapp/IMP/nq/ndinname.c",0x44ED30DC,2372,100);return-1;}
In [3]
, it will overwrite response_buf->id
with nbinfo->id
.
That is, if we can overwrite the nb_info
structure and forge the structure of the Adapter
, we can do arbitrary memory writing. We need to find a global buffer to forge the structure. We finally chose BJNP session buffer as our target. It will copy our payload when we start a BJNP session.
After we have arbitrary memory writing. We can overwrite the function pointer of SLP service with BJNP session buffer pointer.
int__fastcallsub_4159CF90(unsigned__int8*a1,unsignedinta2,inta3,int*a4){...result=((int(__fastcall*)(int*,char*))dword_45C8FF14[2*v20])(&v38,v47);// SLP functionif(!result)gotoLABEL_46;}returnresult;}
It does not implement DEP. After overwriting the function pointer, we can use the BJNP session buffer again to put our shellcode. After that, we can use the SLP attribute request to control the PC and run our shellcode.
HP
Our target this time is the HP Color LaserJet Pro M479fdw
printer, which is primarily Linux-based. This makes the analysis relatively simpler. Under the Web Service, there are numerous ‘cgi’ files providing various printer operations. These operate via the FastCGI method. You can refer to the nginx config to see which path corresponds to which port and service. The config can be found at rootfs/sirius/rom/httpmgr_nginx.
/Sirius/rom/httpmgr_nginx/ledm.conf
Where is the bug
/usr/bin/local/slanapp
is responsible for handling scan-related operations and primarily listens on 127.0.0.1:14030.
We can see from rootfs/sirius/rom/httpmgr_nginx/rest_scan.conf :
If we access /Scan/Jobs, the request is forwarded to a FastCGI listening on the 14030 port. After analysis, we found that it was handled by /rootfs/usr/local/bin/slangapp. When we send a request to /Scan/Jobs, it will call scan_job_http_handler in slangapp.
Where is the bug
There is a stack overflow at rest_scan_handle_get_request in slangapp
.
int__fastcallscan_job_http_handler(inta1){...intrequest_method;// [sp+14h] [bp-2Ch]char*host;// [sp+18h] [bp-28h] BYREFintport;// [sp+20h] [bp-20h] BYREFchar*uri;// [sp+28h] [bp-18h] BYREFintpathinfo;// [sp+30h] [bp-10h] BYREFinturilen[2];// [sp+38h] [bp-8h] BYREFintpathinfo_len;// [sp+40h] [bp+0h] BYREFchars[132];// [sp+44h] [bp+4h] BYREFchardest[260];// [sp+C8h] [bp+88h] BYREFhost=0;memset(s,0,0x81u);port=-1;uri=0;pathinfo=0;urilen[0]=0;pathinfo_len=0;memset(byte_5DBD0,0,0x9C4u);v2=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->acceptRequestHelper)(rest_scan_req_ifc_tbl,a1);if...if(((int(__fastcall*)(structhttpmgr_fptrtbl**,int,char**,int*,int*,int*,_DWORD,_DWORD))(*rest_scan_req_ifc_tbl)->getURI)(rest_scan_req_ifc_tbl,a1,&uri,urilen,&pathinfo,&pathinfo_len,0,0)<0)_DEBUG_syslog((int)"REST_SCAN_DEBUG",0,1193517589,0,0);if...v17=1;if...LABEL_7:request_method=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->getVerb)(rest_scan_req_ifc_tbl,a1);if...v3=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->getContentLength)(rest_scan_req_ifc_tbl,a1);v4=v3;if((unsignedint)(v3-1)<=0x9C2){v14=v3;v15=0;do{if(v14>=2500)v14=2500;v16=((int(__fastcall*)(structhttpmgr_fptrtbl**,int,char*,int))(*rest_scan_req_ifc_tbl)->httpmgr_recvData)(rest_scan_req_ifc_tbl,v2,&byte_5DBD0[v15],v14);if(v16<=0)break;v15+=v16;v14=v4-v15;}while(v4-v15>0);*((_BYTE*)&dword_5DBC8+v4+8)=0;if(v15<0){v17=1;_DEBUG_syslog((int)"REST_SCAN_DEBUG",0,0x475DA215,v15,a1);}}elseif(v3>0x9C3){v5=0;do{while(2500-v5>0){v6=((int(__fastcall*)(structhttpmgr_fptrtbl**))(*rest_scan_req_ifc_tbl)->httpmgr_recvData)(rest_scan_req_ifc_tbl);if(v6<=0)break;v5+=v6;}v7=v5<=0;v5=0;}while(!v7);}v8=((int(__fastcall*)(structhttpmgr_fptrtbl**,int,char**,int*))(*rest_scan_req_ifc_tbl)->getHost)(rest_scan_req_ifc_tbl,a1,&host,&port);if...v9=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->completeRequestHelper)(rest_scan_req_ifc_tbl,a1);if(v9>0){do{v10=0;do{while(2500-v10>0){v11=((int(__fastcall*)(structhttpmgr_fptrtbl**))(*rest_scan_req_ifc_tbl)->httpmgr_recvData)(rest_scan_req_ifc_tbl);if(v11<=0)break;v10+=v11;}v7=v10<=0;v10=0;}while(!v7);v12=((int(__fastcall*)(structhttpmgr_fptrtbl**,int))(*rest_scan_req_ifc_tbl)->completeRequestHelper)(rest_scan_req_ifc_tbl,a1);}while(v12>0);v9=v12;}...result=(*(int(__fastcall**)(int))(*(_DWORD*)dword_65260+20))(dword_65260);dword_594F0=result;switch(request_method){case1:result=rest_scan_handle_get_request(a1,v4,uri,(unsigned__int8*)pathinfo,pathinfo_len);// ----- [1]break;...default:returnresult;}returnresult;
If the request method is GET
, it will use rest_scan_handle_get_request
at [1]
to handle it. It also passes the pathinfo to this function.
int__fastcallrest_scan_handle_get_request(inta1,inta2,char*s1,unsigned__int8*pathinfo,intpathinfo_len){structhttpmgr_fptrtbl**v8;// r0intv9;// r1intv10;// r2structhttpmgr_fptrtbl**v11;// r0intv12;// r1intresult;// r0intv14;// r0intnext_char;// r4unsigned__int8*v16;// r3intv17;// r1intv18;// t1char*v19;// r5intv20;// r5intv21;// r0intv22;// r7size_tv23;// r8intv24;// r0charfirst_path_info[32];// [sp+8h] [bp-D8h] BYREFcharsecond_path_info[32];// [sp+28h] [bp-B8h] BYREFcharpagenumber[152];// [sp+48h] [bp-98h] BYREFif...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;gotoLABEL_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))gotoLABEL_37;v24=strtol(pagenumber,0,10);result=rest_scan_send_scan_data(a1,v24)+1;if(result)rest_scan_vp_thread_created=1;elsereturnrest_scan_send_err_reply(a1,400);}returnresult;}
But when it parse the pathinfo
at [2]
, it does not check the length of pathinfo
. Then copy the pathinfo
to the local buffer(first_path_info[32]
), which leads to a stack overflow.
Exploitation
We can construct the request to /Scan/Jobs/
to trigger it. It does not have Stack Guard, so we can overwrite the return address directly. But it has DEP, we need to do ROP to achieve our goal. Finally, we use ROP to overwrite the GOT of strncmp
. After overwriting it, we can execute arbitrary commands when we access /Copy/{cmd}
However, in the end, this vulnerability collided with another team’s discovery.
Summary
Based on the results from Pwn2Own Austin 2021 to Pwn2Own Toronto 2022, printer security remains an easily overlooked issue. In just one year, the number of teams capable of compromising printers has significantly increased. Even in the third year, at Pwn2Own Toronto 2023, many teams still found vulnerabilities. It is recommended for everyone using these IoT devices to turn off unnecessary services, set up firewalls properly, and ensure appropriate access controls to reduce the risk of attacks.