Transcript Bughunting
Killing the myth of Cisco IOS
rootkits: DIK
(Da Ios rootKit)
Sebastian 'topo' Muñiz
EuSecWest
London, March 2008
Speaker info
Name: Sebastian 'topo' Muñiz
Work: Sr. Exploit Writer at CORE
Likes: Reverse Engineering & Vuln Research
Contact: [email protected]
Agenda
Introduction
IOS characteristics and internal structure
IOS file format structure
IOS analysis phase
Binary patching technique
Rootkit source code advantages and limitations
Setting image ready
Countermeasures
What is a rootkits?
A program that seizes control of the entire OS by hiding files,
processes, network connections and everything else.
Allows unauthorized users to act as with super user privileges (in
IOS this means level 15, like root in Linux).
• Stealth: hides attacker's presence indefinitely.
• This is achieved by intercepting OS low level functions and
manipulating internal structures.
Rootkits along history
• Existed on other OS's like Windows, Unix and Linux distros for
several years.
• Natural evolution of backdoors.
• Present in AV & virii race to avoid one to locate each other.
• More common now a days, specially in malware.
Rootkits on IOS
Are IOS rootkits new? No
Did it begin years ago? Yes, before stolen IOS source code.
Any publicly known IOS rootkit? No, until today.
Can a generic rootkit be created for multiple IOS version? Yes
Does it matter if they have different arch (MIPS or PowerPC)? No
Do I need to know a specific assembly code? No, just plain C :)
Consequences
• Image your company network (ISP, bank, .gov or whoever you
are) compromised on strategic points.
• Network traffic concentrators owned by the attacker.
• Attackers able to see and manipulate all network traffic.
• Consequences in one word: Disastrous.
Introduction to IOS architecture
Monolithic architecture which runs as a single image.
All processes have access to each others memory.
No memory protection between processes.
Uses 'run to completion' priority scheduling.
FIFO (First In First Out) processes queue.
This model reduces local security and system stability.
Completely different to modern OS's.
IOS image file format
Modern Cisco devices use ELF (Extensible File Format).
It's a standard file format on Linux.
File contains CODE segments (instructions) and DATA segments
(strings).
Lot's of information available about it on the net.
Why ELF as IOS format? Implementations available, relocatable, etc.
Values on IOS ELF headers are not equal to the standards.
ELF infection methods (binary modification) were well known by
virus writers for years.
Initial setup on memory
Bootloader performs POST and invokes IOS image.
Downloaded image is not really the image that runs IOS.
Image contains a self decompressing (SFX) header code that
unpacks the fully functional IOS.
A simple ZIP utility can decompress it.
It's compressed because it contains lots of strings that occupy
precious memory.
c2600-i-mz.123-24 occupies 7.4MB ->18.6 MB Decomp (2.51%)
ELF structure as IOS image
+------------------------------+
|
ELF header
|
+------------------------------+
|
SFX code
|
+------------------------------+
|
Magic (0xFEEDFACE)
|
+------------------------------+
|
Compressed image length
|
+------------------------------+
|
Compressed image checksum |
+------------------------------+
| Uncompressed image checksum |
+------------------------------+
|
Uncompressed image length |
+------------------------------+
|
Compressed image
|
+------------------------------+
Self decompressing code
--+
|
|
|
| Magic
| Structure
|
|
|
|
--+
Compressed IOS image
ELF structure as IOS image (cont.)
SFX code uses Magic structure along the entire decompression
process.
Magic values are employed to validate decompression result.
Magic structure length are expressed in words (4 bytes)
Ex. Length of 1024 words = 4096 (1024 * 4) bytes
Magic length values help to verify available memory and size of
buffer to checksum.
Image is checksummed before and after decompression.
ELF structure as IOS image (cont.)
Simple (unsecure) checksum algorithm:
int nwords = compressed_size / sizeof(ulong);
unsigned long sum = 0; // contains the checksum result
unsigned long val
= 0; // temporary value
unsigned char* bufp = (uchar*) ptrData; // pointer to
// data to verify
while (nwords--) { // Read every 4 bytes
val = *bufp++;
sum += val;
if (sum < val) // There was a carry
sum++;
}
Obtain IOS image file
If decompression was successful, resulting (uncompressed) will
be running on device memory.
This image will contain our rootkit.
It's needed for analysis, so download using the known CLI's copy
command (from flash to ftp or any another destination)
Unzip the compressed (downloaded) image with ZIP.
With the decompressed image in our hands, move to analysis
phase.
Analysis phase
Why?
What tool should we use?
Why IDA (Interactive Disassembler Debugger) ?
Analysis phase (cont.)
IOS image contains lots of debug strings with information about
the internal OS working.
Take advantage of debug strings info to locate interesting
functions for an attacker.
Feed IDA with the uncompressed IOS image (will throw warnings)
Come back several minutes (sometimes hours) later :)
Analysis phase (cont.)
IDA will do a good job, but not enough.
Several functions and string won't be recognized
Part of the IOS image was not correctly analyzed.
On a c2600-i-mz.123-24 IDA detected:
28121 Functions
126379 Strings
An enhanced analysis is needed.
Enhanced image analysis
Let's use IDA-Python (python support for IDA scripting).
Create a script to detect remaining functions and strings.
Remember that MIPS and PowerPC instructions are aligned to a 4
byte boundary.
Fixed instruction size of 4 bytes.
Enhanced image analysis for functions
Iterate on every CODE section (like .text)
Compiler writes one function after another.
Strings are not inlined in CODE sections.
Move on a 4 byte boundary and tell IDA to create functions.
Rely on IDA magic.
Enhanced image analysis for strings
Iterate on every DATA section (like .data)
Strings length not 4 byte modulo are padded with zeros.
DATA segments may include references to other sections.
Move on a 4 byte boundary and analyse the memory content.
Try to resolve if current value is the begin of a string or a pointer
to another location (instruction or string).
Enhanced image analysis for strings (cont.)
TIPS:
Printable chars (between 0x20 and 0x7f) are probably strings.
Also check for Tab (0xD), CR (0xA) and LF ('0xD').
Bytes with values between CODE sections range may be pointers.
What if both happens? Like in a CODE section from 0x61000000
to 0x61700000?
Is 0x61616120 (“AAA ”) a pointer or just the begin of a string
from the AAA subsystem?
Enhanced image analysis results
After enhanced analysis:
46296 Functions (18175 new functions)
143603 Strings (17224 new strings)
IOS image is now successfully analysed and ready to give us info.
Locating low-level IOS functions
Locate functions of interest for the attacker like:
Password checking.
File manipulation.
Logging information.
Packet handling functions.
Access lists manipulation function.
Locating low-level IOS functions (cont.)
Strings are used to diagnose key functions result.
Some of them are usually displayed as output.
Locating their references, we locate those functions.
Iterate through every string to locate the ones of attacker
interest.
For each one, use string XREF property from IDA to know it's
callers (IDA-Python magic again :)
Locating low-level IOS functions (cont.)
What if function does not use strings?
Functions are compiled in the same order as source file.
Other functions (neighbors) next to it will surely do (whether
other IOS function call them or not).
Apply this procedure to every function that we want to fin using
simple IDA-Python functions.
Rootkit code home
IOS contains lot's of debug strings.
Sacrifice one large (probably never used) string to put code.
Also another CODE section could be added (known ELF infection
technique).
String overwrite is the easier to implement.
Remember to change section's permissions on ELF section
header.
Analyze low-level functions
Rootkit contains IOS functions counterpart that do attacker's will.
Recompile rootkit code every time is not an option.
Cross-compile it once (for MIPS and PowerPC).
How to locate compiled counterpart functions offsets?
Use 'objdump' to obtains symbolic info and offsets.
Name of a counterpart functions resolves to it's offset in
compiled code.
Redirect IOS execution flow
Knowing counterpart function offsets means that they are
callable now.
Intercept IOS functions and redirect it's execution flow.
Hook every interesting IOS function with a 'trampoline' code.
Trampoline code is located at function first instruction.
Jump to rootkit code location in memory (sacrificed string).
Trampoline code (like jmp on x86) for:
PowerPC -> unconditional branch instruction (1 inst.)
MIPS
-> unconditional jump instruction (2 inst.)
Redirect IOS execution flow (cont.)
Can I just jump to compiled C code? No, bad boy, you can't.
A 'glue' code is needed to create a bridge between IOS assembly
code and plain C code.
The glue code should work either on MIPS or PowerPC.
Redirection must occur without crashing current process state.
Gluing all together
Save the return address
Store the function parameters currently allocated
Allocate stack space for extra parameter needed by rootkit code.
Call the rootkit plain C code.
Decide whether to continue or not to hooked function.
If it continues, restore original parameters, execute instruction at
trampoline address and jump after trampoline
If it goes back to caller, set extra parameter value as hooked
function return value and go the saved return address.
Glueing all together (cont.)
IOS caller
chk_pass_IOS(p)
+-----------------+
+-----------------+
|
|
|
|
| r=chk_pass(p)
|--->| trampoline
|-->
|
|
|
|
| if r == true:
|
| rest of code
|
| login()
|
| ...
|
| else:
|
| return legal_res|
| deny_login()
|
|
|
| ...
|
+-----------------+
+-----------------+
Glueing all together (cont.)
Glue code
chk_pass_DIK(p,i)
+----------------------+
+-----------------+
| store parent RA
| +-->| if p == 'l337': |
| store params p
| |
| i = true
|
| create param i
| | +-| return RET
|
| add stack
| | | | else:
|
| o = chk_pass_DIK(p,i)|-+ |-| return CONT
|
| fix stack
|
| |
|
| if o == CONT:
|<--+ |
|
| exec orig instruct |
+-----------------+
| return params p
|
| cont chk_pass_IOS
|
| else:
|
| r = i
|
| jump to RA
|
+----------------------+
Glueing all together (cont.)
IOS caller
chk_pass_IOS(p)
+-----------------+
+-----------------+
|
|
|
|
| r=chk_pass(p)
|
| trampoline
|
|
|
|
|
| if r == true:
|<-+ | rest of code
|
| login()
| | | ...
|
| else:
| +-| return legal_res|
| deny_login()
| | |
|
| ...
| | +-----------------+
+-----------------+ |
+----<-------<-----------<--
Advantages of this method
A few lines of shellcode called 'trampoline' and
'glue' code filled the gap between a C function
compiled and existing IOS code.
Only one C code is maintained instead of two
assembly codes that perform the same actions on
different architectures (a MIPS code and a PowerPC
code).
Limitation of C code
Strings are put in another a DATA section.
All code together is needed.
Code must be PIC (Position independent Code).
Only relevant code to rootkit is needed.
Workaround the Limitations
Strings are declared with a MACRO like this:
char* pszPassword(void)
// String pointer name
{
jump_to_next_inst
var = ret_addr
// prev inst address
var += after function's end // string begins there
return var
}
asm(".ascii \"my backdoor password\");
asm(".byte 0");
// NULL terminator
Workaround the Limitations
Set 'flags' in C code to mark rootkit code region
#define BOF_DIK_CODE
#define EOF_DIK_CODE
asm(".ascii \"BOF_\"")
asm(".ascii \"EOF_\"")
Find those markers on generated code:
BOF_ = 0x424f465f
EOF_ = 0x454f465f
IOS functions for rootkit usage
Not only intercept low-level functions.
Obtain other IOS functions addresses.
Use them as functions pointers.
Enhance rootkit functionality using them inside our
code.
Raise rootkit stealth level of the rootkit.
IOS functions for rootkit usage (cont.)
Implement functions/structures pointer table.
Call functions as tables indexes.
Pointers to internal structures, too.
Putting it all together
Dump modified IDA sections.
Merge changed bytes with original decompressed IOS
image on disk.
Compress using Zlib / ZIP.
Calculate new checksums.
Merge with compressed IOS image.
Ready to rock n' roll :)
Simple runtime patching
GDB inside every image.
GDB can read/writer memory and processor registers.
Debugging one process can write entire memory.
Entire system compromised by simple process debug.
Use TCL to automate local GDB usage and self-
patching after every boot.
This TCL file will be hidden by the rootkit once
it's running.
Resulting IOS image
Binary patched IOS image.
No new processes created (“show proc” won't help).
Rootkit code execution is triggered by events.
Cannot trust router info if it's compromised.
Only external clean up methods will work.
Countermeasures
Follow Cisco guidelines.
Periodically cheksum images using MD5, SHA1, etc. to
see binary changes!!!
Try CIR (Cisco Incident Response) from Recurity-Labs
Harden network infrastructure.
DEMO