Learn the common protections shown by checksec and practice identifying which protections a binary uses.

Start with the "Start here" resources below. They explain each protection from zero, show how to read checksec, and show how to check each protection by hand with readelf.


Every earlier module showed a way to take control of a program: overwrite a return address, run your own machine code, or write to a chosen address with a format string. Compilers and operating systems answer with defenses (also called mitigations): features that do not fix the bug, but make the bug harder to use.

This module teaches you to read which defenses a program has. That is the first step of any attack and of any security review: before you plan anything, find out what is in the way. There is no bug to exploit in this module's challenge.

By the end you should be able to:

  • say in one sentence what NX, a stack canary, PIE, ASLR and RELRO each do and which kind of attack each one stops;
  • read a checksec report;
  • confirm each protection yourself with readelf, without checksec;
  • say which tricks from the earlier modules each defense blocks.

You need a Linux terminal and the idea of the stack from the Your First Memory Bug module. Every command below was run for real on small practice programs that are not part of the challenge.

NX: the stack holds data, not code

Memory is split into regions with permissions: read, write, execute. NX ("no-execute", also called DEP or W^X) means a region that is writable is not executable.

Without NX                      With NX
+-------------------+           +-------------------+
| stack      rwx    |           | stack      rw-    |  <- data only
+-------------------+           +-------------------+
| heap       rwx    |           | heap       rw-    |
+-------------------+           +-------------------+
| program code r-x  |           | program code r-x  |  <- code only
+-------------------+           +-------------------+

What it stops: the classic attack of putting machine code (shellcode) in a buffer on the stack and jumping to it. The CPU refuses to run it and the program crashes with a segmentation fault.

What it does not stop: reusing code that is already in the program, and attacks that never run injected code (such as changing a variable).

Stack canary: a tripwire before the return address

A canary is a secret random number the compiler places between a function's local buffers and its saved return address. Before the function returns, it compares the number to the original. If they differ, the program calls __stack_chk_fail and stops.

higher addresses
+--------------------+
| saved return addr  |  <- what an overflow wants to overwrite
+--------------------+
| CANARY (secret)    |  <- an overflow must cross this first
+--------------------+
| char buf[32]       |  <- writing starts here and goes upward
+--------------------+
lower addresses

What it stops: a straight overflow of a stack buffer up to the return address. The write changes the canary on the way, and the check fails before the function returns.

What it does not stop: a bug that leaks the canary value first, a write that skips over it (for example, a format-string write to a chosen address), or overwriting data that sits below the canary, such as a neighbouring local variable.

ASLR and PIE: the addresses move

ASLR (address space layout randomization) is an operating-system feature: each run, the stack, the heap and the libraries are loaded at random addresses. By default it does not move the program's own code. PIE (position independent executable) is a property of the program that lets its own code move too.

Run 1                        Run 2
program   0x5555...1000      program   0x5612...1000   (PIE: moves)
libc      0x7f3a...0000      libc      0x7fd1...0000   (ASLR: moves)
stack     0x7ffc...a000      stack     0x7ffe...5000   (ASLR: moves)

Without PIE the program is at the same address every run (0x400000 is typical for 64-bit programs), and ASLR still moves everything else.

What it stops: attacks that hard-code an address, such as "jump to the function at 0x401196". The attacker does not know where anything is.

What it does not stop: the offsets inside a region stay the same, so one leaked address reveals the rest of that region.

RELRO: protecting the table of library addresses

When your program calls printf, it goes through a table called the GOT (global offset table) that holds the real address of each library function. RELRO (relocation read-only) controls whether that table can be changed while the program runs.

No RELRO:      GOT writable the whole run
Partial RELRO: GOT entries for functions are still writable
               (filled in lazily, on first call)
Full RELRO:    all addresses filled in at start-up, then the
               GOT is made read-only

What it stops: (Full RELRO) overwriting a GOT entry so a later library call runs code of the attacker's choosing.

What it does not stop: anything that does not write to the GOT. With only partial RELRO the function entries in the GOT can still be overwritten, so partial gives less than full.

Which earlier trick does each defense block?

Defense Blocks this trick Module
Stack canary Overflowing a buffer to reach the saved return address Your First Memory Bug
NX Running shellcode that sits on the stack Shellcode
PIE with ASLR Jumping to a fixed address, such as a function you want to call Your First Memory Bug, Shellcode
Full RELRO Overwriting a GOT entry with a format-string %n write Format Strings

The programs in those modules were built with the matching defense turned off on purpose. A real program usually has several defenses at once, and an attack then needs a second bug that leaks an address or a canary.

A worked example on a toy program

This toy program has a classic stack-overflow bug (strcpy into a 32-byte buffer). It is not a challenge program. It is built two ways:

#include <stdio.h>
#include <string.h>

void greet(const char *name) {
    char buf[32];
    strcpy(buf, name);
    printf("hello, %s\n", buf);
}

int main(int argc, char **argv) {
    greet(argc > 1 ? argv[1] : "world");
    return 0;
}
# every defense off
gcc toy.c -o toy_plain -fno-stack-protector -no-pie -z execstack -z norelro

# every defense on
gcc toy.c -o toy_hard -fstack-protector-strong -pie -fPIE -z noexecstack -z relro -z now

checksec --file=./toy_plain and checksec --file=./toy_hard print (the right-hand columns are trimmed here):

RELRO           STACK CANARY      NX            PIE
No RELRO        No canary found   NX disabled   No PIE          <- toy_plain
Full RELRO      Canary found      NX enabled    PIE enabled     <- toy_hard

Each column is one defense:

Column What you read
RELRO No RELRO, Partial RELRO or Full RELRO. Three answers, not two.
STACK CANARY Canary found means yes. No canary found means no.
NX NX enabled means the stack is not executable. NX disabled means it is.
PIE PIE enabled means the code moves. No PIE means it stays put.

Note that two of the four "good" answers are worded as a positive (enabled, found) and the bad ones as a negative. Read each column on its own; do not assume the same word means the same thing in every column.

pwn checksec toy_hard (from pwntools) reports the same facts in a list:

    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled

Checking by hand with readelf

checksec only reads fields in the ELF file. You can read the same fields yourself. Here is each one, with real output.

NX: the GNU_STACK line of readelf -lW. The last letters are the permissions the program asks for on its stack. RWE has an E (executable), so NX is off. RW has no E, so NX is on.

$ readelf -lW toy_plain | grep GNU_STACK
  GNU_STACK  0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
$ readelf -lW toy_hard | grep GNU_STACK
  GNU_STACK  0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

PIE: the Type line of readelf -h. EXEC is a program at a fixed address. DYN is relocatable, which is what PIE is.

$ readelf -h toy_plain | grep Type
  Type:                              EXEC (Executable file)
$ readelf -h toy_hard | grep Type
  Type:                              DYN (Position-Independent Executable file)

Stack canary: look for __stack_chk_fail. A function with a canary calls it when the check fails, so the program imports it.

$ nm -D toy_hard | grep stack_chk
                 U __stack_chk_fail@GLIBC_2.4
$ nm -D toy_plain | grep stack_chk
$

(U means "undefined here, supplied by a library". No output means the name is not there, so no canary.) You can also search a disassembly: a canary function reads a value from fs:0x28 at the start and compares it at the end.

RELRO: two clues together. First, is there a GNU_RELRO segment? Second, does the dynamic section contain BIND_NOW?

GNU_RELRO segment BIND_NOW in readelf -d RELRO level
absent absent None
present absent Partial
present present Full

Real output for the three toy builds (a third one, toy_partial, was built with -z relro -z lazy):

$ readelf -lW toy_plain   | grep GNU_RELRO        (no output: absent)
$ readelf -d  toy_plain   | grep -E 'BIND_NOW|FLAGS'   (no output)

$ readelf -lW toy_partial | grep GNU_RELRO
  GNU_RELRO  0x002df8 0x0000000000403df8 0x0000000000403df8 0x000208 0x000208 R   0x1
$ readelf -d  toy_partial | grep -E 'BIND_NOW|FLAGS'    (no output)

$ readelf -lW toy_hard    | grep GNU_RELRO
  GNU_RELRO  0x002da8 0x0000000000003da8 0x0000000000003da8 0x000258 0x000258 R   0x1
$ readelf -d  toy_hard    | grep -E 'BIND_NOW|FLAGS'
 0x000000000000001e (FLAGS)              BIND_NOW
 0x000000006ffffffb (FLAGS_1)            Flags: NOW PIE

(FLAGS_1 ... NOW PIE is another place the same facts show up.)

Seeing ASLR happen

ASLR is a setting of the system, not a field in the file, so checksec does not report it. cat /proc/sys/kernel/randomize_va_space prints 2 when full randomization is on. To see it work, a tiny program prints a few addresses (main, a local variable, and printf). Built without PIE and run twice:

main  at 0x401146         main  at 0x401146
stack at 0x7fffbf0d51c4   stack at 0x7ffd06a2d284
printf at 0x735528cdd100  printf at 0x78aa27eb5100

main stays put; the stack and the library move. Built with -pie and run twice:

main  at 0x607a927b9159   main  at 0x593e98625159
stack at 0x7ffece93d494   stack at 0x7ffc3c9232a4
printf at 0x7d211158e100  printf at 0x717ebbe53100

Now main moves too. Look at the last three hex digits of main (159): they do not change. The position inside the program is fixed; only the starting point moves.

A defense (also called a mitigation) is a feature added when a program is compiled or loaded that makes a memory bug harder to turn into an attack. It does not remove the bug. checksec --file=<program> (or pwn checksec <program>) reports four of them:

  • NX (no-execute): memory that holds data, such as the stack, cannot run as code.
  • Stack canary: a secret value placed on the stack before the saved return address. The function checks it before returning and stops the program if it changed.
  • PIE (position independent executable): the program's own code is loaded at a different address each run. This is part of ASLR (address space layout randomization), which also moves the stack and libraries.
  • RELRO (relocation read-only): the table of library function addresses (the GOT) becomes read-only after start-up. Partial RELRO protects part of it; full RELRO protects all of it.

The memory-bug, shellcode, and format-string programs in this dojo were built with some of these defenses turned off so the technique could work. Here is what each defense gets in the way of:

Defense What it gets in the way of
Stack canary Overflowing a stack buffer past the saved return address (Your First Memory Bug)
NX Running shellcode you placed on the stack (Shellcode)
PIE Jumping to a fixed address such as win(), because the address changes every run
Full RELRO Overwriting a GOT entry, for example with a format string %n write

Real attacks often need one bug to leak information (such as a canary or an address) and a second bug to use it. Before planning an exploit, run checksec and write down which defenses you will have to deal with.

Glossary

  • Defense / mitigation: a protection that makes a bug harder to exploit.
  • ELF: the file format of Linux programs. Defenses are recorded in its headers.
  • NX / DEP / W^X: memory cannot be both writable and executable.
  • Shellcode: machine code an attacker supplies as input.
  • Stack canary: a secret value checked before a function returns.
  • __stack_chk_fail: the library function a failed canary check calls.
  • ASLR: the operating system loads the stack, heap and libraries at random addresses.
  • PIE: a program built so its own code can be loaded at any address.
  • GOT: the table of real addresses of library functions the program calls.
  • Lazy binding: filling in a GOT entry the first time the function is called.
  • RELRO: making the relocation data, including the GOT, read-only.
  • Segment / program header: a region the loader maps into memory; readelf -l lists them.
  • Leak: a bug that reveals a value (an address or a canary) to the attacker.

Common mistakes

  • Reading "No canary found" or "NX disabled" as good news. They mean the defense is absent.
  • Treating RELRO as yes or no. It has three levels: none, partial, full.
  • Running checksec on one program and assuming the answer holds for the others.
  • Expecting checksec to report ASLR. It reports only what is in the file.
  • Assuming a defense makes a program safe. The bug is still there; the defense only raises the cost.
  • Confusing PIE and ASLR. ASLR randomizes the stack, heap and libraries; PIE lets the program's own code move too.

Where to go next

  • The pwn.college intro modules on memory errors and exploitation mitigations: https://pwn.college
  • man readelf, and checksec --help in the practice environment.
  • Come back to this page when you start the Your First Memory Bug and Shellcode modules and ask, for each program, which of these defenses it has.

Challenges

Three small programs sit in /challenge: mystery-a, mystery-b, and mystery-c. Each one was compiled with a different set of defenses (protections that make memory bugs harder to exploit).

Find out which defenses each program has, then run /challenge/checksec-quiz and answer its twelve questions: for each program, is NX enabled, does it use a stack canary, is it PIE, and which RELRO level does it have (none, partial, or full)? If every answer is right, the quiz prints the flag.

There is no bug to exploit here. This challenge is about reading a program's protections before you plan an attack.


30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score