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.