Aggregator
萤火V2.10功能更新快报
赛博水电费
赛博水电费
互联网白嫖的生存之道
互联网白嫖的生存之道
大型闪电式红蓝对抗主流攻击行为的研判分析万字总结
StateFuzz: System Call-Based State-Aware Linux Driver Fuzzing
StateFuzz: System Call-Based State-Aware Linux Driver Fuzzing
Your printer is not your printer ! - Hacking Printers at Pwn2Own Part II
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
Same as 2021, you can refer to Part I. The current version is v11.04.
HPThe 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
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 __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; } }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.
int ndNameInit() { 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 __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; }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 *__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; } }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:
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; ... }The structure is used to store NetBIOS name information, it also has a member Adapter to store the information of connection.
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; };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.
case 0xA801: v6 = ndInternalNameNegativeRegistration(a1, (int)nbname); goto LABEL_17;At ndInternalNameNegativeRegistration :
int __fastcall ndInternalNameNegativeRegistration(Adapter *adapter, int a2) { ... if ( v8 ) { returnNegativeRegistrationResponse((nb_info *)v6, adapter, 3); } ... }If the conditions are met, it will use ‘returnNegativeRegistrationResponse’ to handle the Response.
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; }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 __fastcall sub_4159CF90(unsigned __int8 *a1, unsigned int a2, int a3, int *a4) { ... result = ((int (__fastcall *)(int *, char *))dword_45C8FF14[2 * v20])(&v38, v47);// SLP function if ( !result ) goto LABEL_46; } return result; }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.
HPOur 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 __fastcall scan_job_http_handler(int a1) { ... int request_method; // [sp+14h] [bp-2Ch] char *host; // [sp+18h] [bp-28h] BYREF int port; // [sp+20h] [bp-20h] BYREF char *uri; // [sp+28h] [bp-18h] BYREF int pathinfo; // [sp+30h] [bp-10h] BYREF int urilen[2]; // [sp+38h] [bp-8h] BYREF int pathinfo_len; // [sp+40h] [bp+0h] BYREF char s[132]; // [sp+44h] [bp+4h] BYREF char dest[260]; // [sp+C8h] [bp+88h] BYREF host = 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 *)(struct httpmgr_fptrtbl **, int))(*rest_scan_req_ifc_tbl)->acceptRequestHelper)( rest_scan_req_ifc_tbl, a1); if... if ( ((int (__fastcall *)(struct httpmgr_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 *)(struct httpmgr_fptrtbl **, int))(*rest_scan_req_ifc_tbl)->getVerb)( rest_scan_req_ifc_tbl, a1); if... v3 = ((int (__fastcall *)(struct httpmgr_fptrtbl **, int))(*rest_scan_req_ifc_tbl)->getContentLength)( rest_scan_req_ifc_tbl, a1); v4 = v3; if ( (unsigned int)(v3 - 1) <= 0x9C2 ) { v14 = v3; v15 = 0; do { if ( v14 >= 2500 ) v14 = 2500; v16 = ((int (__fastcall *)(struct httpmgr_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); } } else if ( v3 > 0x9C3 ) { v5 = 0; do { while ( 2500 - v5 > 0 ) { v6 = ((int (__fastcall *)(struct httpmgr_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 *)(struct httpmgr_fptrtbl **, int, char **, int *))(*rest_scan_req_ifc_tbl)->getHost)( rest_scan_req_ifc_tbl, a1, &host, &port); if... v9 = ((int (__fastcall *)(struct httpmgr_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 *)(struct httpmgr_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 *)(struct httpmgr_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 ) { case 1: result = rest_scan_handle_get_request(a1, v4, uri, (unsigned __int8 *)pathinfo, pathinfo_len); // ----- [1] break; ... default: return result; } return result;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 __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; }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.
SummaryBased 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.
Your printer is not your printer ! - Hacking Printers at Pwn2Own Part II
Hacking Printers at Pwn2Own Toronto 2022
延續之前的研究,去年我們也在 Canon 的其他型號中,找到了 Pre-auth RCE 漏洞 (CVE-2023-0853、CVE-2023-0854),同時 HP 的印表機也有找到 Pre-auth RCE 的漏洞,然而最終與其他隊伍撞洞。我們將在本文講述我們在 Pwn2own Toronto 中所使用的 Canon 及 HP 漏洞的細節,以及我們的利用方式。
- Pwn2Own Toronto 2022 Target
與 2021 年相同,可參考前述部分,本次版本為 v11.04 。
HPFirmware 本身可以從 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
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)其餘部分在這個漏洞中不太重要,不另外多做詳細解釋,更多細節可以參考 RFC6762 、 RFC1035 以及 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 設備,盡量把不必要的服務關閉並且設好防火牆及做好權限控管,以減少被攻擊的可能。
CTF | 2022 PKU GeekGame 2nd WriteUp
《云安全攻防入门》教材
论“数字化”及“数字化转型”背景下企业信息安全建设路径
要让未来要发生的事情在今天思考,要让今天已做的事情成就未来。
————Micropoor
摘要
随着数字化转型逐渐进入深水区,已呈现多元融合、碎片化、复杂化发展等泛化趋势。企业需要及时调整网络安全战略定位及战略布局,从被动开展到需要打响“主动保卫战”,依托技术手段及技术工具,注重加快人才、流程、经验建设,为“业务数字化”夯实网络安全基石,最终实现与业务共同稳步发展。同时需要拓宽网络安全视角,积极探索内外交互联动方式及一体化合作发展的新型模式,最终真正实现网络安全“看得见、守得住、重沉淀、促发展、赢未来”,助力保障企业强劲竞争力。
关键字:业务数字化;网络安全;主动保卫战。
背景:
在十四五规划明确“加快数字化发展”在“加快发展现代产业体系、推动经济体系优化”目标中作为重要指导方针后,数字化浪潮便席卷而来。数字化转型对于企业来说不再是一道选择题,而是一道生存题。网络安全防护工作作为数字化转型的前提基础和坚实保障,全面提升网络安全保障能力,能够切实为企业数字化转型保驾护航,推动业务高质量发展。
引言:
“数字化”及“数字化转型”背景下的企业网络安全建设与推动发展,从技术的视角上看,数字化是IT(Information Technology)向DT(Data Technology)转化的过程。因此,就不得不提“三化”,既信息化、数字化、智能化,才能更好的理解在此背景下“企业网络安全”建设路径与重要意义。
信息化:信息化的本质是“连接”,既是将物理世界的信息和数据转换为“0与1”的二进制代码录入信息系统,以“数据”形式保存,将线下的流程和数据迁移到电脑上进行处理,以此提高效率、降低成本并提升可靠性。
信息化特点:人“主”机“辅”,即以人为主、以机器为辅。
数字化:数字化的本质是“生产力与生产关系的重构”,即是将许多复杂多变的信息转变为可以度量的数字、数据,再以这些数字、数据建立起适当的数字化模型,把它们转变为一系列二进制代码,引入计算机内部,进行统一处理,利用数字技术来改变原有商业模式并提供新的收入或新价值创造,这就是数字化的基本过程。
数字化特点:以“信息”转化为“数据”,再将数据转化成“可用”信息。
智能化:智能化的本质是“主次关系重构”,即是事物在计算机网络、大数据、物联网和人工智能等技术的支持下,所具有的能满足人的各种需求的属性。智能化是自动化技术当前和今后的发展动向之一,逐步具备类似于人类的感知能力、记忆和思维能力、学习能力、自适应能力和行为决策能力,在各种场景中,以人类的需求为中心,能动地感知外界事物,按照与人类思维模式相近的方式和给定的知识与规则,通过数据的处理和反馈,对随机性的外部环境做出决策并付诸行动。
智能化特点:机“主”人“辅”,即以机器为主、以人为辅。
正文:
在数字化转型的过程中,不仅仅要把“关注点”放在人才、流程、组织、技术等上,要清晰清楚所在的公司“数字化”转型的目的、难点、阶段等。也要从全局观去了解人类在历史中所经历的四次科技变革,只有这样,才能更清晰清楚在本次的“数字化”时代的变革中,网络安全所处于的位置、分工、重要程度、方向,以及如何能更贴近“数字化”本身而做好网络安全工作本身,才能合理的分配资源、合理的设计规划、合理有序的推进网络安全工作。
从十八世纪六十年代至今,全人类经历了4次技术变革,每一次的技术变革都极大提高了生产力,改变了人与人、人与物、物与物之间的纽带,也改变了世界的面貌,而本次技术变革的过程中,恰恰也是信息安全在全人类历史的长河中,开始产生广泛和深远影响的重要阶段,也逐步从“背后”走到了“舞台中央”。今天,信息安全已成为一个世界性的问题,“信息安全”已从起初的军事领域迅速地扩展到了数字化时代社会生活的方方面面,已关乎每一个人。信息安全问题所造成的影响和后果,轻则影响公民的正常生活、威胁企业生存发展,重则危及国家和社会的稳定与安全。信息技术的辐射性、衍生性,信息资源的共享性,信息传播的跨界性,通信网络手段的多样性等,决定了信息安全环境的开放性、复杂性。而在这么一个开放、复杂的环境中,信息安全注定是一个动态变化的过程,也永远将会是一种相对的安全。这个过程需要国际社会、国家、企业组织和公民必须共同努力,共同担责,不仅解决技术与产业问题,也要解决组织合作、法律法规、安全监管的问题。
第一次工业革命时期,瓦特改良蒸汽机,迅速在许多行业中使用,把人类带进了“蒸汽时代”,既是“蒸汽化”。
第二次工业革命时期,由于发电机和电动机的发明和使用,电力逐步取代了蒸汽,把人类带进了“电气时代”,既是“电气化”。
第三次工业革命时期,以物联网、云计算、大数据为核心的新一代信息技术把人类带进了“信息技术时代”,既是“信息化”。
第四次工业革命则是利用信息化技术促进产业变革的时代,以信息物理系统为基础,以生产高度数字化、网络化、机器自组织为标志。既是“智能化”。
无论是信息安全还是网络安全,亦或数据安全等,都要深刻的理解“安全”的基本含义,即是客观上不存在威胁,主观上不存在恐惧,即客体不担心其正常状态受到影响。可以把网络安全定义为:一个网络系统不受任何威胁与侵害,能正常地实现资源共享功能。要使网络能正常地实现资源共享功能,首先要保证网络的硬件、软件能正常运行,然后要保证数据信息交换的安全。
在了解了“数字化”及“数字化转型”的历史后,那么企业网络安全的建设与推进路径也就明朗了,即是15字方针,“看得清、守得住、当沉淀、促发展、赢未来”。
看得清:看得清数字化资产的分布与暴露面,看得清业务场景的风险隐患,看得清“云网端数用边”安全形势,看得清宏观层面跨时间与空间的安全态势,看得清微观层面的攻击线索。抽象总结——“两体一道”,既是访问主体、访问通道、访问客体。
守得住:守得住数字化资产,守得住客户隐私数据,守得住商业秘密,守得住公司信誉。抽象总结——“四问”,即是外部客户访问、内部客户访问、运维管理访问、系统间访问。
当沉淀:沉淀安全分析规则、沉淀安全脚本、沉淀安全工具与平台、沉淀安全方法论、沉淀安全标准规范流程、沉淀自有安全人员。抽象即是——基础安全保护沉淀、身份与策略管控沉淀、检测响应与运营沉淀、源生安全集成沉淀。
促发展:促数据有序流通、促创新业务发展、促数字化转型。
赢未来:赢在业务创新,赢在数据开放,赢在数字化人才,赢在安全生态与安全文化,赢在未来。
数字化与数字化转型背景下企业网络安全建设的5个阶段路径:
第一个阶段,以资产和流量为视角,做看得清。
资产层面,做好资产管理是安全运营最基础性的工作,是安全事件处置、漏洞补丁修复等流程能够顺利开展、精准定位的关键要素。这一阶段,开展多维度全方位的资产探查和测绘,实现资产价值管理和精细化管理。通过明确安全运营资产对象的重要性(包括终端、重要的服务器、核心的业务系统等),并针对资产的不同维度(如类型、IP、端口、组件、组件版本、是否对外发布)进行可视化的展示和维护,提取容易造成混淆或干扰的“噪音”资产(如正向代理、反向代理、扫描器、内部DNS、不规范的应用服务器),动态分析高敏感系统和集权平台(如VPN/OA/邮件/运维监控/堡垒机/财务系统)潜在风险。
流量层面,立体化覆盖边界流量(不限于互联网边界、安全域边界)、横向流量、重要服务器流量(不限于Web/DNS/Email/database服务器)等,精准区分内对外、外对内、内对内流量,充分运用SSL卸载以及加密流量分析引擎,根据规则匹配、语义分析、流量特征、关联分析等技术手段,快速识别漏洞、恶意软件,切实做到流量可见、风险可视。
第二个阶段,以威胁狩猎和高效闭环为视角,做守得住。
在看清安全形势的同时,需要一双火眼睛睛抓住高风险事件,并进行及时、恰当地处置,不断推动安全能力的持续提升,方能守得住。第二阶段,以威胁狩猎和高效闭环为视角,充分利用当前的安全资源,提升安全监测的有效性、安全响应的高效性,守住网络安全和数据安全。
威胁狩猎的首要任务是做好安全数据治理、建立威胁建模。安全数据治理,是对数据理解的过程,包含了数据采集-数据标准化-数据增强-数据分析-响应处置-复盘这个迭代过程中的数据治理部分。依据运营预期、需求、基础设施、预算、客观条件,明确采集范围、采集方式、采集格式、采集目的、数据接口管理、处置接口管理、预估存储大小、明确存储时间、数据源统一监控、变更管理等,明确前端数据源标准,明确数据模型。在贴合业务的场景下,建立威胁规则模型,基于攻防对抗、攻击模拟、历史安全事件、外部情报等,持续进行编排和优化,并利用各种验证框架提升威胁规则模型的有效性。在建立威胁规则模型的基础上,进一步开展数据强化分析,即威胁狩猎工作,利用各种方式区分正常与异常行为,例如分析影响范围和意图,加快事件调查和对根本原因的理解。
高效闭环,需要高效的事件闭环流程,一方面,结合业务特点,制定自动化处置策略,如对业务时段、非业务时段制定不同封禁策略;另一方面,通过标准化的应急处置、通知预警、协同联动流程,辅以可视化的类工单系统进行事件的跟踪与管理,直至事件闭环。
第三个阶段,以人和工具为视角,做当沉淀。
看得清、守得住的最终目标是为了促发展、赢未来,而沉淀是其中必不可少的桥梁。小到沉淀一个安全分析规则、一个安全脚本,大到沉淀一套安全方法论、一套安全体系,都将给企业高质量创新发展注入新动力。
因为沉淀,才能培养高精专的自有人才,打造特色的自有安全团队;因为沉淀,才能将常态化运营和实战化经验相结合,转化为可以传承、共享的知识,通过不断地流动与迭代加以完善,形成公司级或者行业级标准、规范、最佳实践等,从赋能一个系统扩展到赋能多个系统,从赋能一个创新业务扩展到赋能多个创新业务,对各个团队、各个业务部门甚至整个行业产生积极的影响。
第四个阶段,以促进数字化转型为视角,促发展。
数字化转型是企业围绕“数据赋能业务”为核心进行全面布局并长期坚持的工作。数字化转型必须从技术、战略和人才的层面去思考,以技术赋能企业运营为起点,以战略重塑商业模式为核心,以人才打造组织能力为根本,实现这三个方面的有机结合。
有了安全技术、安全人才相关的沉淀,方能在各类创新业务飞速发展的当下,不会因为不熟悉的应用组件、多样的API接口、复杂的数据流动而心生恐惧,盲目阻断创新业务的上线;也不会因为缺少有效、可落地的安全管控,而发生数据安全泄露等事件。
总之,以促进数字化转型为视角,通过持续加深对业务安全的理解,建立健全数据安全规范体系,构建确保数据要素有序流通的基础能力,健全数据安全技术保障能力,谋求安全与业务共发展。
第五个阶段,以共建安全开放生态为视角,赢未来。
安全与创新是一个恒久的话题。只有快速适应未来安全形势的变化和细分安全能力的需求,为开放创新提供基础支撑,共建安全开放生态与安全文化,深度赋能业务发展与业务升级,企业才能在快速发展过程中树立行业地位、增强企业自身竞争力,赢在当下,且赢在未来。
企业数字化、数字化转型中网络安全建设六点建议
(一)企业网络安全建设是重大战略问题,是典型的“一把手”工程
“以安全保发展、以发展促安全”的要求,充分体现了马克思主义的辩证法,体现了科学的发展观。要解决数字化转型安全问题,需要企业从经营战略视角进行统一规划,建立系统性的安全防御机制,安全不再只是某个部门的工作范畴,更需要管理层战略关注,数字化时代,安全更成为“一把手”工程。
(二)加强“顶层设计”,提升体系化安全能力
要对安全体系建设进行统一的规划,制定安全体系框架,明确保障体系中所包含的内容。同时,还要制定统一的信息安全建设标准和管理规范,使得信息安全体系建设能够遵循一致的标准,管理能够遵循一致的规范。同步强调“统一规划建设、全面综合防御、技术管理并重、保障运营安全”,规划中要体现安全保障的重要性,利用多种安全保障机制,保障网络和信息系统的运行安全,在实战中保障业务的持续性和业务数据的安全性。
(三)善从外部借力,形成联动联合、互动互联
企业数字化转型过程中业务环境将更加开放,业务生态将更加复杂,这将对未来安全管理与保障带来巨大的挑战。未来“多云共存”“异构管理”等新安全挑战将成为企业数字化安全建设和发展面临的新常态,可以通过安全研究机构、产商等联合赋能,聚合企业数字化转型所需的安全能力,促进企业数字化相关安全系统形成一体化安全保障机制。探索安全管控赋能主动对接各系统安全态势感知及安全大数据支撑平台,实现相互交互联动,形成“一盘棋”的安全管理局面。
(四)加强人才培养,网络安全领域不拘一格降人才
数字化转型涉及的技术和场景众多,需要培养既懂网络安全技术,又了解数字化转型方法论,业务场景相关技术的复合型人才,才能更好地完成数字化转型下的网络安全相关工作,于企业而言,应逐步建立网络安全专业人才的招聘选拔培养任用机制,通过安排技术技能培训与考核,参与技能大赛和红蓝对抗演练等方式,实现人才队伍从信息安全等级保障,保护合规测评能力,向网络攻防专业技术能力及实践能力的“双轮驱动”提升。
(五)探索建设企业网络安全大数据中心
利用“实时、全样、精准”的安全大数据建立全程在线、跨厂商、全域覆盖、实时反馈的“企业网络与数据安全态势全景图”,全天候全方位监测预警网络安全威胁,提高对网络安全风险的感知、预警和防护能力,实现集约化、常态化的威胁发现和应急处置,实现“用数据感知、用数据决策、用数据管理”的网络安全治理新模式。
(六)把“网络安全”融入企业文化,助力企业“数字化转型”
企业文化是一个组织的核心价值观,体现在企业日常运行中的各个方面,是一个企业的基因。与传统企业内部笃信“领导经验”不同,一个数字化的企业上下皆以“数据”作为衡量决策及结果的唯一标准。要想让企业顺利完成“数字化转型”,需要在企业内部打造“网络安全”这样的文化,可以起到事倍功半的效果。
经过多年的信息化建设,企业进入到在线化、协同化、数字化、智能化的发展阶段,而数据是推动数字化、智能化的核心基础,数字化不仅带来企业运营效率提升、成本降低,更重要的还是在于商业模式的变革与新价值创造。而前置条件便是网络安全、信息安全、数据安全。
一切企业不注重“网络安全”就谈数字化转型都是纸老虎。
一切企业不注重“数字化”就谈发展的都会被时代所抛弃。
企业安全建设一定要有战略思维、战略眼光,不能仅停留在“技术本身”,信息安全不是一个或几个部门的任务,而是数字化时代对于整个公司提出的新要求。在不同时期,对网络安全有过不同的称谓和解释,其内涵在不断深化,外延在不断扩展。而我们每一个人、每一个企业、每一个组织都需要重视并顺应“数字化转型”的趋势,并投身其中,为产业升级、建设数字中国贡献一份力量。