Came across this canary technique from http://blog.quarkslab.com/clang-hardening-cheat-sheet.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <string.h>
#include <stdint.h>
static char c;
void f()
{
size_t top = SIZE_MAX;
for (int i = 0; i < 5; ++i) {
printf("%p\n", (void*)((size_t*)&top)[i]);
}
char b[8];
strcpy(b, &c);
}

int main() {
size_t x = 0;
c = '0';
f();
void *bp;
asm ("mov %%rbp, %0;"
: "=r" (bp)
:
:
);
printf("bp is %p\n", bp);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
$ clang -fstack-protector test.c && ./a.out
0xffffffffffffffff
0x1
0xdeba1e5fb9c19200
0x7ffd419467a0
0x400694
bp is 0x7ffd419467a0
$ objdump -d a.out | grep -C 2 400694
40068e: 30
40068f: e8 4c ff ff ff callq 4005e0 <f>
400694: 48 bf 44 07 40 00 00 movabs $0x400744,%rdi
40069b: 00 00 00
40069e: 48 89 e8 mov %rbp,%rax

The memory layout looks like (address goes from low to high):

1
2
3
4
5
bp-24 : SIZE_MAX
bp-16 : char b[8]
bp-8 : canary
bp : old bp
bp+8 : return address

The return address is confirmed by the objdump above. Similarly, we could do the same analysis for the case without stack protector.

1
2
3
4
5
6
7
$ clang test.c && ./a.out
0xffffffffffffffff
0x7ffe7dc66df0
0x400604
0x400630 # content from previous stack frame, so ignored
0x400470
bp is 0x7ffe7dc66df0

The memory layout looks like (address goes from low to high):

1
2
3
bp-8 : SIZE_MAX
bp : old bp
bp+8 : return address
1
2
3
4
5
6
$ objdump -d a.out | grep -C 2 400604
4005fe: 30
4005ff: e8 6c ff ff ff callq 400570 <f>
400604: 48 bf b4 06 40 00 00 movabs $0x4006b4,%rdi
40060b: 00 00 00
40060e: 48 89 e8 mov %rbp,%rax