ELF .rodata

05 May 2015

Introduction

ELF files are known for their duality: a set of sections from a linking point of view and a set of segments from a runtime point of view. The interesting part is the mapping between these two perspectives. While working on an ELF parser I noted the unexpected position of the .rodata section.

Here is an excerpt of readelf output on an arbitrary binary:

$ readelf -S /bin/skype
There are 32 section headers, starting at offset 0x2236a00:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        00000154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            00000168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            00000188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        000001ac 0001ac 001bdc 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          00001d88 001d88 00c000 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0000dd88 00dd88 01b313 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0002909c 02909c 001800 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0002a89c 02a89c 0001e0 00   A  6   7  4
  [ 9] .rel.dyn          REL             0002aa7c 02aa7c 265ca8 08   A  5   0  4
  [10] .rel.plt          REL             00290724 290724 004948 08   A  5  12  4
  [11] .init             PROGBITS        0029506c 29506c 00002e 00  AX  0   0  4
  [12] .plt              PROGBITS        002950a0 2950a0 0092a0 04  AX  0   0 16
  [13] .text             PROGBITS        0029e340 29e340 139b708 00  AX  0   0 16
  [14] .fini             PROGBITS        01639a48 1639a48 00001a 00  AX  0   0  4
  [15] .rodata           PROGBITS        01639a80 1639a80 b42c20 00   A  0   0 32
  [16] .eh_frame_hdr     PROGBITS        0217c6a0 217c6a0 00b264 00   A  0   0  4
  [17] .eh_frame         PROGBITS        02187904 2187904 03eb20 00   A  0   0  4
  [18] .gcc_except_table PROGBITS        021c6424 21c6424 053a22 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      0221bde4 221ade4 000870 00  WA  0   0  4
  [...]

Note how .rodata is flagged with only 'A' for 'allocate'. No write flag is set, as expected, and no execute flag either. If we inspect the segments (or program headers):

$ readelf -l /bin/skype

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00000034 0x00000034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x00000154 0x00000154 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x00000000 0x00000000 0x2219e46 0x2219e46 R E 0x1000 
  LOAD           0x221ade4 0x0221bde4 0x0221bde4 0x1bafc 0x5d578 RW  0x1000
  [...]

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp [...] .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame .gcc_except_table
   03     .init_array .ctors .dtors .jcr .data.rel.ro .dynamic .got .got.plt .data .debug$s .data1 .bss 
   04     .dynamic 
  [...]

The .rodata section is mapped to the second segment. This segment is mapped at 0x0, has a size of 0x2219e46 bytes and protection of RE (read and execute). A closer look at the mapping shows that this segment also includes .text, .fini, .init and .plt, which explains the need for the execute flag.

Consequences

Although the option to execute read-only data may seem limited, it is actually relevant when building a ROP chain during binary exploitation. In the binary described above:

$ objcopy -j .rodata -O binary /bin/skype skype.rodata
$ ls -lh ./skype.rodata 
-rw-rw-r--. 1 user group 12M May  5 07:55 ./skype.rodata

That is 12M of potential extra gadgets! For instance, using rp++, here are some gadgets within .rodata:

0x0003d0e8: call eax ;  (511 found)
0x0003d28a: call ebp ;  (471 found)
0x0003ced2: call ebx ;  (684 found)
0x0003cca6: call ecx ;  (350 found)
0x0003dfa2: call edi ;  (1200 found)
0x00057e10: call edx ;  (165 found)
0x0003da8f: call esi ;  (228 found)
0x0003cdc2: call esp ;  (202 found)
[...]
0x0003cf40: jmp eax ;  (792 found)
0x0003ddfb: jmp ebp ;  (1598 found)
0x0003cd95: jmp ebx ;  (730 found)
0x0003cdaa: jmp ecx ;  (388 found)
0x0003d667: jmp edi ;  (2246 found)
0x0003cf6c: jmp edx ;  (372 found)
0x0003ceca: jmp esi ;  (277 found)
0x0003ee32: jmp esp ;  (209 found)
0x0000a8dc: ret  ;     (28681 found)

ROP tools

I did have a look at some ROP gadgets finder to see how the executables were parsed. Both rp++ and ROPGadget use the segments (program headers) to find gadgets.

Conclusion

This issue has already been raised on the binutils mailing list. The only answer is a reference to gold (new linker) option to set rodata into a separate segment. Unfortunately, the risk to come across compatibility issues makes the implementation of such functionality within the BFD linker unlikely.

References