CCR is a hybrid method for enabling practical and generic code randomization based on compiler-rewriter cooperation. CCR currently supports rapid cliend-side fine-grained code randomization (at both function level and basic block level) at installation time. The main concept behind CCR is to augment final executables with a minimal (pre-defined) set of transformation-assisting metadata. The following table briefly shows the essential information that is collected/adjusted at compilation/linking time.
Metadata | Collected Information | Collection time |
---|---|---|
Layout | Section offset to first object | Linking |
Section offset to main() function if any |
Linking | |
Total code size for randomization | Linking | |
Basic Block (BBL) | BBL size (in bytes) | Linking |
BBL boundary type (BBL, FUN, OBJ) | Compilation | |
Fall-through or not | Compilation | |
Section name that BBL belongs to | Compilation | |
Fixup | Offset from section base | Linking |
Dereference size | Compilation | |
Absolute or relative | Compilation | |
Type (c2c , c2d , d2c , d2d ) |
Linking | |
Section name that fixup belongs to | Compilation | |
Jump Table | Size of each jump table entry | Compilation |
Number of jump table entries | Compilation |
For more details, plese refer to our IEEE S&P 2018 paper.
We provide a handy build script (build.sh
) to automate the entire toolchain installation, which includes:
- modified
LLVM 3.9.0
andclang
compiler compilation binutils 2.27
and modified gold linker (ld-new
) compilation- Google's
protocol buffers 3.1.0
compiler (protoc
) compilation for metadata serialization/deserialization - shared object (
shuffleInfo.so
) generation with the shuffleInfo.proto definition - required packages installation
- python packages (
protobuf
andpyelftools
) installation - symbolic link creation to the modified compiler (dubbed as
ccr
andccr++
), the gold linker and the randomizer
Once the build script runs successfully, check out the following components.
- CCR C Compiler :
/usr/local/bin/ccr
symbolically links to./llvm-3.9.0/build/bin/clang
- CCR C++ Compiler:
/usr/local/bin/ccr++
symbolically links to./llvm-3.9.0/build/bin/clang++
- CCR Gold Linker :
/usr/bin/ld
symbolically links to./binutils-2.27/gold/ld-new
by default - Randomizer :
./randomizer/prander.py
(prander
is dubbed as a practical randomizer)
Finally, make sure that CCR can properly load shuffleInfo.so
, located in both /usr/lib
and /usr/local/lib
. If not, sudo ldconfig
may help.
$ ldd $(readlink -e `which ccr`) | grep shuffleInfo
shuffleInfo.so => /usr/lib/shuffleInfo.so (0x00007f42b4db9000)
$ ldd $(readlink -e `which ld`) | grep shuffleInfo
libshuffleInfo.so => /usr/local/lib/libshuffleInfo.so (0x00007f3185af9000)
The build script:
- Requires at least 8GB memory and 30GB HDD space
- Installs
protoc
,shuffleInfo.so
, and other necessary packages on your system - Does not install the compiler and linker, but creates symbolic links instead
- Changes the default linker to
ld.gold
at build time, and told-new
at the end
Note that the script has been tested on Ubuntu 16.04 only (using the default gcc 5.4 and gold 1.11). If you have failed to build everything at the first attempt, you may want to change the default linker to the system's gold linker before runing the build script again.
A Docker script is available for easily testing CCR within a Docker container. The following commands show how to install Docker and how to generate the CCR container.
$ curl -fsSL https://get.docker.com/ | sudo sh
$ sudo usermod -aG docker [user_id]
$ docker run ubuntu:16.04
Unable to find image 'ubuntu:16.04' locally
16.04: Pulling from library/ubuntu
Digest: sha256:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6
Status: Downloaded newer image for ubuntu:16.04
$ docker build -t ccr .
... (omitted)
CCR C Compiler : /usr/local/bin/ccr
CCR C++ Compiler : /usr/local/bin/ccr++
CCR Gold Linker : /CCR/binutils-2.27/gold/ld-new
CCR Randomizer : /CCR/randomizer/prander.py
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ccr latest 687322eff8f3 29 minutes ago 30.5GB
ubuntu 16.04 f975c5035748 10 days ago 112MB
Once the build has been successful, lunch the Docker image to test out CCR.
$ docker run --rm -it ccr:latest /bin/bash
root@c1aa9c064785:/CCR#
For more information about Docker, visit here
Once CCR has been successfully deployed, you can test it using the included examples.
By simply compiling the source code as shown below, you will obtain a
ShuffleInfo
(=metadata) message from the linker with the section .rand
, meaning
the metadata has been updated/generated within the final executable from all object files.
$ ccr -o ./examples/funcptr ./examples/funcptr.c
Successfully wrote the ShuffleInfo to the .rand section!
If you want to inspect the metadata (layout, jump table, and fixups) in detail,
use the -mllvm -debug-only=ccr-metadata
option.
$ ccr -o ./examples/funcptr -mllvm -debug-only=ccr-metadata ./examples/funcptr.c
<MF/MBB Layout Summary>
----------------------------------------------------------------------------------
Layout MF_MBB_ID MBBSize Align Fixups Offset MFSize Section
----------------------------------------------------------------------------------
2 [DF 0_0] 64B 11B 3 0x00000040 64B (.text)
----------------------------------------------------------------------------------
4 [DF 1_0] 64B 11B 3 0x00000080 64B (.text)
----------------------------------------------------------------------------------
6 [DF 2_0] 64B 11B 3 0x000000c0 64B (.text)
----------------------------------------------------------------------------------
8 [DF 3_0] 64B 11B 3 0x00000100 64B (.text)
----------------------------------------------------------------------------------
10 [DF 4_0] 48B 14B 2 0x00000130 48B (.text)
----------------------------------------------------------------------------------
12 [DF 5_0] 48B 14B 2 0x00000160 48B (.text)
----------------------------------------------------------------------------------
14 [DF 6_0] 48B 14B 2 0x00000190 48B (.text)
----------------------------------------------------------------------------------
16 [DF 7_0]* 32B 0B 1 0x000001b0 32B (.text)
16 [DF 7_1]* 10B 0B 1 0x000001ba 42B (.text)
16 [DF 7_2] 39B 0B 3 0x000001e1 81B (.text)
16 [DF 7_3]* 28B 0B 2 0x000001fd 109B (.text)
16 [DF 7_4]* 10B 0B 1 0x00000207 119B (.text)
16 [DF 7_5] 22B 0B 2 0x0000021d 141B (.text)
16 [DF 7_6]* 10B 0B 1 0x00000227 151B (.text)
16 [DF 7_7] 13B 0B 2 0x00000234 164B (.text)
16 [DF 7_8] 32B 0B 3 0x00000254 196B (.text)
16 [DF 7_9]* 5B 0B 1 0x00000259 201B (.text)
16 [DF 7_10]* 7B 0B 0 0x00000260 208B (.text)
16 [DF 7_11] 16B 7B 0 0x00000270 224B (.text)
----------------------------------------------------------------------------------
18 [DF 8_0]* 31B 0B 1 0x0000028f 31B (.text)
18 [DF 8_12] 14B 0B 1 0x0000029d 45B (.text)
18 [DF 8_1] 10B 0B 2 0x000002a7 55B (.text)
18 [DF 8_2] 10B 0B 2 0x000002b1 65B (.text)
18 [DF 8_3] 10B 0B 2 0x000002bb 75B (.text)
18 [DF 8_4] 10B 0B 2 0x000002c5 85B (.text)
18 [DF 8_5] 10B 0B 2 0x000002cf 95B (.text)
18 [DF 8_6] 10B 0B 2 0x000002d9 105B (.text)
18 [DF 8_7] 10B 0B 2 0x000002e3 115B (.text)
18 [DF 8_8] 10B 0B 2 0x000002ed 125B (.text)
18 [DF 8_9] 10B 0B 2 0x000002f7 135B (.text)
18 [DF 8_10]* 5B 0B 1 0x000002fc 140B (.text)
18 [DF 8_11] 6B 0B 0 0x00000302 146B (.text)
----------------------------------------------------------------------------------
Code(B) NOPs(B) MFs MBBs Fixups
770 93 9 33 56
Legend
(*) FallThrough MBB
<Jump Tables Summary>
[JT@Function#8_0] (Kind: 0, 18 Entries of 8B each)
[8_1] 0x0000029d
[8_2] 0x000002a7
[8_10] 0x000002f7
[8_10] 0x000002f7
[8_3] 0x000002b1
[8_10] 0x000002f7
[8_4] 0x000002bb
[8_10] 0x000002f7
[8_10] 0x000002f7
[8_5] 0x000002c5
[8_6] 0x000002cf
[8_7] 0x000002d9
[8_10] 0x000002f7
[8_10] 0x000002f7
[8_10] 0x000002f7
[8_10] 0x000002f7
[8_8] 0x000002e3
[8_9] 0x000002ed
#JTs #Entries
1 18
<Fixups Summary>
- Fixups Info (.text): 56
- Fixups Info (.rodata): 18
- Fixups Info (.data): 4
Note that the metadata for the final executable is gzipped to save space in the
.rand
section. The metadata can be separately extracted withobjcopy
. You can read the metadata with the reader scriptshuffleInfoReader.py
, which by default extracts the metadata into a[name].meta.txt
file. The script also can reads the metadata from the binary itself if the.rand
section is found.
$ readelf -SW ./examples/funcptr | grep rand
[28] .rand PROGBITS 0000000000000000 0010d0 000185 00 0 0 1
$ objcopy --dump-section .rand=./examples/funcptr.shuffle.bin.gz ./examples/funcptr
$ gzip -d ./examples/funcptr.shuffle.bin.gz
$ python ./randomizer/shuffleInfoReader.py ./examples/funcptr.shuffle.bin
Found the metadata at ./examples/funcptr.shuffle.bin
Main Addr Offset : 0x0020
Rand Object Offset : 0x0100
Rand Object Size : 0x0310
Total BBLs in .text: 32
Wrote the metadata to ./examples/funcptr.shuffle.bin.meta.txt...
The following command generates a program variant with the randomizer,
prander.py
, which takes the binary to be transformed as its only argument.
The default option (-f
) creates a transformed binary at the function level
(Use the -b
option for basic block level randomization). The name of the variant is
[filename]_shuffled
by default. The -s
option updates all symbols in
the symbol table (.symtab
) if the binary has not been stripped.
For additional options, try the -h
option for help. In this example,
the main()
function has been displaced from 0x400790 to 0x400600.
$ python ./randomizer/prander.py -s -b ./examples/funcptr
___ ___ __ ___ _
/ __\ / __\ /__\ / _ \_ __ __ _ _ __ __| | ___ _ __
/ / / / / \//_____ / /_)/ '__/ _` | '_ \ / _` |/ _ \ '__|
/ /___/ /___/ _ \_____/ ___/| | | (_| | | | | (_| | __/ |
\____/\____/\/ \_/ \/ |_| \__,_|_| |_|\__,_|\___|_/
Compiler-assisted Code Randomization: Practical Randomizer
(In the 39th IEEE Symposium on Security & Privacy 2018)
[INFO ] Reading the metadata from the .rand section... (shuffleInfoReader.py:139)
[INFO ] Offset to the object : 0x100 (shuffleInfoReader.py:140)
[INFO ] Offset to the main() : 0x20 (shuffleInfoReader.py:141)
[INFO ] Total Emitted Bytes : 0x0310 (shuffleInfoReader.py:142)
[INFO ] Number of Objects : 1 (shuffleInfoReader.py:143)
[INFO ] Number of Functions : 9 (shuffleInfoReader.py:144)
[INFO ] Number of Basic Blocks: 32 (shuffleInfoReader.py:145)
[INFO ] Fixups in .text : 56 (shuffleInfoReader.py:56)
[INFO ] Fixups in .rodata : 18 (shuffleInfoReader.py:56)
[INFO ] Fixups in .data : 4 (shuffleInfoReader.py:56)
[INFO ] Number of Jump Tables : 1 (shuffleInfoReader.py:173)
[INFO ] Building up the layout... (prander.py:43)
100% [>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]
[INFO ] Sanity check for ./examples/funcptr... (reorderInfo.py:580)
[INFO ] All sanity checks have been PASSED!! (reorderInfo.py:605)
[INFO ] Performing reordering (@FUN)... (prander.py:48)
[INFO ] # of Function Constraints: 0 (reorderEngine.py:216)
[INFO ] Instrumenting the binary... (prander.py:66)
[INFO ] Processing section [.dynsym] (binaryBuilder.py:694)
100% [>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]
[INFO ] Processing section [.rela.dyn] (binaryBuilder.py:694)
[INFO ] Processing section [.text] (binaryBuilder.py:694)
100% [>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]
[INFO ] Processing section [.rodata] (binaryBuilder.py:694)
100% [>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]
[INFO ] Processing section [.eh_frame] (binaryBuilder.py:694)
[INFO ] Processing section [.eh_frame_hdr] (binaryBuilder.py:694)
[INFO ] Processing section [.init_array] (binaryBuilder.py:694)
[INFO ] Processing section [.data] (binaryBuilder.py:694)
100% [>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]
[INFO ] Processing section [.symtab] (binaryBuilder.py:694)
100% [>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]
[INFO ] Summary of Binary Instrumentation (report.py:81)
[INFO ] Binary Name : ./examples/funcptr_shuffled (report.py:82)
[INFO ] Main() Addr : 0x400790 -> 0x400600 (report.py:85)
[INFO ] Symbol Patches : 9 (.dynsym|.symtab) (report.py:92)
[INFO ] InitArray Patches : 0 (.init_array) (report.py:93)
[INFO ] CIE / FDE : 2 / 13 (.eh_frame) (report.py:94)
[INFO ] FDE Patches : 9 (.eh_frame) (report.py:95)
[INFO ] Pair Patches : 9 (.eh_frame_hdr) (report.py:96)
[INFO ] Original MD5 : 59f6375f21e9c4884a63a827eac5c301 (report.py:98)
[INFO ] Shuffled MD5 : 320f8f59364c3bd26fabe9f0e43829ee (report.py:99)
[INFO ] Shuffled Size : 0x000310 (report.py:100)
[INFO ] Metadata size : 0x000185 (report.py:101)
[INFO ] Total Size : 0x0024e0 (report.py:102)
[INFO ] File Inc Rate : 4.298% (report.py:103)
[INFO ] Entropy [LB, UB] : [10^5.56, 10^15.24] possible versions (report.py:77)
[INFO ] Total elapsed time: 0.07 sec(s) (prander.py:77)
[INFO ] Success!! The log has been saved to ./examples/funcptr.log (prander.py:162)
To verify how functions have been relocated, compare the symbols of the original binary with
those of the variant. For example, the function switchcase()
has been relocated
(0x400870
->0x400720
). Note that the intact functions are from CRT (C Run-Time) libraries,
which are excluded from randomization.
$ readelf --syms ./examples/funcptr | grep switchcase
73: 0000000000400870 146 FUNC GLOBAL DEFAULT 12 switchcase
$ readelf --syms ./examples/funcptr_shuffled | grep switchcase
73: 0000000000400720 146 FUNC GLOBAL DEFAULT 12 switchcase
The following shows the disassembly of the switchcase()
function using radare2.
The operand of every jump/call instruction has been updated accordingly so that
the original semantics of the program are preserved.
[0x00400870 22% 335 funcptr]> pd $r @ sym.switchcase
/ (fcn) sym.switchcase 45
| sym.switchcase ();
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x0040082a (main)
| 0x00400870 55 push rbp
| 0x00400871 4889e5 mov rbp, rsp
| 0x00400874 4883ec20 sub rsp, 0x20
| 0x00400878 897dfc mov dword [local_4h], edi
| 0x0040087b ffcf dec edi
| 0x0040087d 89f8 mov eax, edi
| 0x0040087f 83ef11 sub edi, 0x11
| 0x00400882 488945f0 mov qword [local_10h], rax
| 0x00400886 897dec mov dword [local_14h], edi
| ,=< 0x00400889 0f8768000000 ja 0x4008f7 ;[1]
| | 0x0040088f 488b45f0 mov rax, qword [local_10h]
| | 0x00400893 488b0cc59809. mov rcx, qword [rax*8 + 0x400998] ; [0x400998:8]=0x40089d
\ | 0x0040089b ffe1 jmp rcx
| 0x0040089d e85efeffff call sym.f1 ;[2]
,==< 0x004008a2 e955000000 jmp 0x4008fc ;[3]
|| 0x004008a7 e884feffff call sym.f2 ;[4]
,===< 0x004008ac e94b000000 jmp 0x4008fc ;[3]
||| 0x004008b1 e84afeffff call sym.f1 ;[2]
,====< 0x004008b6 e941000000 jmp 0x4008fc ;[3]
|||| 0x004008bb e870feffff call sym.f2 ;[4]
,=====< 0x004008c0 e937000000 jmp 0x4008fc ;[3]
||||| 0x004008c5 e836feffff call sym.f1 ;[2]
,======< 0x004008ca e92d000000 jmp 0x4008fc ;[3]
|||||| 0x004008cf e85cfeffff call sym.f2 ;[4]
,=======< 0x004008d4 e923000000 jmp 0x4008fc ;[3]
||||||| 0x004008d9 e852feffff call sym.f2 ;[4]
========< 0x004008de e919000000 jmp 0x4008fc ;[3]
||||||| 0x004008e3 e818feffff call sym.f1 ;[2]
========< 0x004008e8 e90f000000 jmp 0x4008fc ;[3]
||||||| 0x004008ed e80efeffff call sym.f1 ;[2]
========< 0x004008f2 e905000000 jmp 0x4008fc ;[3]
||||||| ; JMP XREF from 0x00400889 (sym.switchcase)
||||||`-> 0x004008f7 e864feffff call sym.f3 ;[5]
|||||| ; XREFS: JMP 0x004008a2 JMP 0x004008ac JMP 0x004008b6 JMP 0x004008c0 JMP 0x004008ca
|||||| ; XREFS: JMP 0x004008d4 JMP 0x004008de JMP 0x004008e8 JMP 0x004008f2
``````--> 0x004008fc 4883c420 add rsp, 0x20
0x00400900 5d pop rbp
0x00400901 c3 ret
0x00400902 66666666662e. nop word cs:[rax + rax]
[0x00400720 19% 396 funcptr_shuffled]> pd $r @ sym.switchcase
/ (fcn) sym.switchcase 45
| sym.switchcase ();
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x004006da (main)
| 0x00400720 55 push rbp
| 0x00400721 4889e5 mov rbp, rsp
| 0x00400724 4883ec20 sub rsp, 0x20
| 0x00400728 897dfc mov dword [local_4h], edi
| 0x0040072b ffcf dec edi
| 0x0040072d 89f8 mov eax, edi
| 0x0040072f 83ef11 sub edi, 0x11
| 0x00400732 488945f0 mov qword [local_10h], rax
| 0x00400736 897dec mov dword [local_14h], edi
| ,=< 0x00400739 0f8768000000 ja 0x4007a7 ;[1]
| | 0x0040073f 488b45f0 mov rax, qword [local_10h]
| | 0x00400743 488b0cc59809. mov rcx, qword [rax*8 + 0x400998] ; [0x400998:8]=0x40074d
\ | 0x0040074b ffe1 jmp rcx
| 0x0040074d e84e010000 call sym.f1 ;[2]
,==< 0x00400752 e955000000 jmp 0x4007ac ;[3]
|| 0x00400757 e8a4000000 call sym.f2 ;[4]
,===< 0x0040075c e94b000000 jmp 0x4007ac ;[3]
||| 0x00400761 e83a010000 call sym.f1 ;[2]
,====< 0x00400766 e941000000 jmp 0x4007ac ;[3]
|||| 0x0040076b e890000000 call sym.f2 ;[4]
,=====< 0x00400770 e937000000 jmp 0x4007ac ;[3]
||||| 0x00400775 e826010000 call sym.f1 ;[2]
,======< 0x0040077a e92d000000 jmp 0x4007ac ;[3]
|||||| 0x0040077f e87c000000 call sym.f2 ;[4]
,=======< 0x00400784 e923000000 jmp 0x4007ac ;[3]
||||||| 0x00400789 e872000000 call sym.f2 ;[4]
========< 0x0040078e e919000000 jmp 0x4007ac ;[3]
||||||| 0x00400793 e808010000 call sym.f1 ;[2]
========< 0x00400798 e90f000000 jmp 0x4007ac ;[3]
||||||| 0x0040079d e8fe000000 call sym.f1 ;[2]
========< 0x004007a2 e905000000 jmp 0x4007ac ;[3]
||||||| ; JMP XREF from 0x00400739 (sym.switchcase)
||||||`-> 0x004007a7 e884000000 call sym.f3 ;[5]
|||||| ; XREFS: JMP 0x00400752 JMP 0x0040075c JMP 0x00400766 JMP 0x00400770 JMP 0x0040077a
|||||| ; XREFS: JMP 0x00400784 JMP 0x0040078e JMP 0x00400798 JMP 0x004007a2
``````--> 0x004007ac 4883c420 add rsp, 0x20
0x004007b0 5d pop rbp
0x004007b1 c3 ret
0x004007b2 66666666662e. nop word cs:[rax + rax]
In general, you may want to set the default compilers as ccr
and ccr++
when running the configure
script to generate the Makefile
. The CCR prototype supports most existing binary features when generating hardened variants, including (but not limited to) position independent code (PIC), shared objects, exception handling, lazy binding, link time optimization (LTO), and control flow integrity (CFI).
To demonstrate how the master executable can be transparently transformed at installation time,
we have modified the debian package source directory to trigger the randomizer.
Either of the apt-get install
or dpkg -i
commands trigger code randomization as part of the installation process.
For more details, see here.
- The current prototype only supports ELF binaries on the x86_64 platform.
- Too many basic blocks and fixups (100,000 or more) may impact the rewriting time.
- Symbol updates are expeirmental and may be slow, so we do not recommend using the
-s
option for large binaries. - Technically the latest
protobuf
should work, but different versions may result in unforeseen collisions.
The current CCR prototype has the following limitations.
- No support for self-modifying code.
- No support for debugging-relevant sections (i.e., .debug_*).
- No support for basic block reordering at the presence of custom exception handling (
.eh_frame
and.eh_frame_hdr
) (See the appendix of the paper). - No support for one of the six CFI protection mechanisms of LLVM, which is
cfi-icall
(See the appendix of the paper). - Although there is support for inline assembly (i.e.,
__asm
directive) and full assembly source code (*.s
files), it works only for function level randomization. More engineering effort is required for basic block randomization due to the various styles of writing assembly files. Randomization at the basic block level is still feasible by excluding non-compatible object files (written in assembly).
If your research employs our CCR prototype, please cite the following paper:
@INPROCEEDINGS{ccr,
author = {Hyungjoon Koo and Yaohui Chen and Long Lu and Vasileios~P. Kemerlis and Michalis Polychronakis},
title = {Compiler-assisted Code Randomization},
booktitle = {Proceedings of the 39th IEEE Symposium on Security \& Privacy (S\&P)},
pages = {472--488},
month = {May},
year = {2018},
location = {San Francisco, CA}
}