Every OS includes a memory manager. Current OS uses protected memory architecture. The process x cannot read/write the memory of a process y. To enable communication between process x and y, it requires different techniques (Shared Memory, Message Queues, Pipes, Network Connections …). These techniques enhance inter-processes security but reduce performance. The IOS does not implement Shared Memory; any process can access all the memory without restrictions. A process is free to communicate with another one by writing directly into its memory. (Buffer Overflow = Crash, I’ll come back later on this by explaining the Memory Block Architecture). The IOS can mark memory as R/O or R/W. The IOS has memory pools, driven by the Pool Manager:
#show memory
Head       Total(b)     Used(b)    Free(b)    Lowest(b)   Largest(b)
653B8C20   155481056    86243592   69237464   68168948    67670028
EE800000    25165824     5269012   19896812   19819968    19871932
Above, a processor and an I/O memory pool:
  • Head : Start Address of the memory pool
  • Total : Pool size in bytes
  • Used : Total amount of bytes currently used in the pool
  • Free : Total amount of bytes currently free in the pool
  • Lowest : The history’s lowest amount of bytes free since last restart
  • Largest : The Largest free contiguous memory block
These memory pools belong to Memory Regions driven by the Region Manager:
#show region
Region Manager:
Start       End            Size(b)  Class  Media  Name
0x0E800000  0x0FFFFFFF    25165824  Iomem  R/W    iomem:(iomem)
0x60000000  0x6E7FFFFF   243269632  Local  R/W    main
0x6000F000  0x632FFFFF    53415936  IText  R/O    main:text
0x63300000  0x64DFFCFF    28310784  IData  R/W    main:data
0x64DFFD00  0x653B8C1F     6000416  IBss   R/W    main:bss
0x653B8C20  0x6E7FFFFF   155481056  Local  R/W    main:heap
0x80000000  0x8E7FFFFF   243269632  Local  R/W    main:(main_k0)
0xA0000000  0xAE7FFFFF   243269632  Local  R/W    main:(main_k1)
0xEE800000  0xEFFFFFFF    25165824  Iomem  R/W    iomem
The Processor Memory Pool belongs to main:heap region. This region belongs to the main region starting at 0×60000000 and ending at 0×6E7FFFFF. The I/O Memory Pool belongs to iomem region starting at 0xEE800000 and ending at 0xEFFFFFFF. Within the main region:
  • main:text : contains IOS’s code in R/O (IText)
  • main:data : contains Initialized variables R/W (IData)
  • main:bss : contains Uninitialized variables R/W (IBss)
  • main:heap : contains standard local memory structures R/W
  • iomem : contains devices memory (I/O bus memory)
We can notice that some of the regions are redundant: main:(main_k0); main:(main_k1); They are all equals to the main region. We’re also able to find iomem in two different addresses: 0×0E800000->0×0FFFFFFF and 0xEE800000->0xEFFFFFFF. But it’s still the same memory area. From a CISCO device to another, the type of memory used for each region may change. For a Router x, the iomem is SRAM, but for another the same region is DRAM. The Pool Manager defines its memory pools within the regions, regardless of the memory type used (hardware abstraction). Let’s go back to the Pool Manager. By entering the “show memory processor” command, we can notice that the memory is divided into memory blocks:
#show memory processor
Processor memory
Address      Bytes     Prev     Next Ref     PrevF    NextF  Alloc PC  what
65A817E0 0000000084 65A8175C 65A81864 001  -------- -------- 628215E8  Init
65A81864 0000001372 65A817E0 65A81DF0 001  -------- -------- 608E3218  Skinny Socket Server
65A81DF0 0000001156 65A81864 65A822A4 001  -------- -------- 608E3218  Skinny Socket Server
  • Address : Start of block
  • Bytes : Size of block
  • Prev : Previous block Address (linkage)
  • Next : Next Block Address (linkage)
  • Ref : How many process are using this block?
  • PrevF : Previous free block
  • NextF : Next free block
  • Alloc PC : Allocating Process
  • What : Block owner’s process name
Inside a Memory Block :
#show memory 0x65A81864
65A81860:          AB1234CD 010B0000 66B4EBEC      +.4M....f4kl
65A81870: 6395634C 608E3218 65A81DF0 65A817F4  c.cL`.2.e(.pe(.t
65A81880: 800002AE 00000001 605C3DD4 00000112  ........`=T.... ...
65A81DE0:                            FD0110DF              }.._
After some researches we can guess some of the fields from the Block’s header:
65A81860:            [MAGIC  ]  [PID   ] [?       ]
65A81870: [PTR_NAME] [ALLOCPC]  [NEXT  ] [PREV+20d]
65A81880: [SIZE*] [REF ] [?  ] [ DATA ->] ...
65A81DE0:                     [<- DATA] [MAGIC   ]
  • MAGIC_START is always 0xAB1234CD
  • PID : Process ID
  • PTR PS_ NAME : Pointer to the Memory Block’s owner process name
  • ALLOC_PC : Same value as in “show memory processor” command
  • NEXT : Next Block Pointer
  • PREV : Previous Block Pointer + 20d (pointing to the Next Block Pointer of the previous block)
  • SIZE : The MSB (Most Significant Bit) of this Field is a flag where 1 states that the block is in use. The value read in this field differ from the one displayed using the “show memory processor” command. For both to be equals I need to do : (value read)*2+4 ????
  • REF : How many process are using this block?
  • DATA : …
  • MAGIC_END is always 0xFD0110DF (palindrome in hexadecimal)
Starting with this Memory Block, if I want to go to the next block without using the Next pointer, I must do: Block Address + sizeof(magic_start) + sizeof(header) + (2*[value read in Size field]+4) + sizeof(magic_end) So: 0×65A81864+0×4+0×24+0×560+0×4=0×65A81DF0 Checking the PID field:
#show process 0x0000010B [010B <> 0000]
Process ID 267 [Skinny Socket Server],
Memory usage [in bytes] Holding: 89644, Maximum: 109468, Allocated: 6601380, Freed: 6514500
Getbufs: 0,
Retbufs: 0,
Stack: 8908/12000
CPU usage PC: 60846DE4, Invoked: 26, Giveups: 1, uSec: 9384 5Sec: 0.00%, 1Min: 0.00%, 5Min: 0.00%, Average: 0.00% Age: 5007208 msec,
Runtime: 244 msec
State: Waiting for Event, Priority: Normal
Checking the PTR PS_NAME field:
#show memory 0x6395634C
63956340:                            536B696E              Skin
63956350: 6E792053 6F636B65 74205365 72766572  ny Socket Server
63956360: 00000000 0A496E76 616C6964 20736B69  .....Invalid ski
->Skinny Socket Server
Checking that there is currently an end of block (must have the FD0110DF magic number):
0x65A81864 + 2*[SIZE]+4 + SIZEOF(MAGICSTART) + [HEADERSIZE] = 0x65A81864 + 0X560 + 0x4 + 0x24
#show memory 0x65A81DEC
65A81DE0:                            FD0110DF              }.._
Now let’s talk about the crash resulting from a Buffer Overflow: If we want to make a buffer overflow in a memory area, so writing more bytes than allocated for this area, we’ll overwrite the next block header thus breaking the IOS memory linkage mechanism. What a pity! Or not! The IOS constantly checks the memory structures, if it finds any inconsistency, it’s forcing a crash. So to success with a buffer overflow (crash free), you need to re-write a coherent header on the next block. Do not forget to add a “jmp” to your code to jump just after the next block header! image In the next post: The Chunk Manager!