Stack Canary

The Stack Canary is another mitigation designed to protect against things like stack based buffer overflows. The general idea is, a random value is placed at the bottom of the stack frame, which is below the stack variables where we actually have input. If had a buffer overflow to overwrite the saved return address, this value on the stack would be overwritten. Then before the return address is executed, it checks to see if that value is the same one it set. If it isn't then it knows that there is a memory corruption bug happening and terminates the program. Also the name comes from the use of canaries in a mine. If the canary stops singing, get out before you die from gas poisoning.

To understand this better, let's look at a binary compiled with a stack canary:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000401132 <+0>:    push   rbp
   0x0000000000401133 <+1>:    mov    rbp,rsp
   0x0000000000401136 <+4>:    sub    rsp,0x20
   0x000000000040113a <+8>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000401143 <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x0000000000401147 <+21>:    xor    eax,eax
   0x0000000000401149 <+23>:    mov    rdx,QWORD PTR [rip+0x2ef0]        # 0x404040 <stdin@@GLIBC_2.2.5>
   0x0000000000401150 <+30>:    lea    rax,[rbp-0x12]
   0x0000000000401154 <+34>:    mov    esi,0x9
   0x0000000000401159 <+39>:    mov    rdi,rax
   0x000000000040115c <+42>:    call   0x401040 <fgets@plt>
   0x0000000000401161 <+47>:    mov    DWORD PTR [rbp-0x18],0x5
   0x0000000000401168 <+54>:    nop
   0x0000000000401169 <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040116d <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000401176 <+68>:    je     0x40117d <main+75>
   0x0000000000401178 <+70>:    call   0x401030 <__stack_chk_fail@plt>
   0x000000000040117d <+75>:    leave  
   0x000000000040117e <+76>:    ret    
End of assembler dump.

Now let's look at a binary compiled from the same source code, but without a stack canary:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000401122 <+0>:    push   rbp
   0x0000000000401123 <+1>:    mov    rbp,rsp
   0x0000000000401126 <+4>:    sub    rsp,0x10
   0x000000000040112a <+8>:    mov    rdx,QWORD PTR [rip+0x2eff]        # 0x404030 <stdin@@GLIBC_2.2.5>
   0x0000000000401131 <+15>:    lea    rax,[rbp-0xe]
   0x0000000000401135 <+19>:    mov    esi,0x9
   0x000000000040113a <+24>:    mov    rdi,rax
   0x000000000040113d <+27>:    call   0x401030 <fgets@plt>
   0x0000000000401142 <+32>:    mov    DWORD PTR [rbp-0x4],0x5
   0x0000000000401149 <+39>:    nop
   0x000000000040114a <+40>:    leave  
   0x000000000040114b <+41>:    ret    
End of assembler dump.

We can see a few differences between the code, like when it checks the stack canary:

   0x0000000000401169 <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040116d <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000401176 <+68>:    je     0x40117d <main+75>
   0x0000000000401178 <+70>:    call   0x401030 <__stack_chk_fail@plt>

Let's actually take a look at the stack canary in memory:

Breakpoint 1, 0x0000000000401168 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdfde  →  0x7fffffffe0d0000a
$rbx   : 0x0               
$rcx   : 0xfbad2288        
$rdx   : 0x00007fffffffdfde  →  0x7fffffffe0d0000a
$rsp   : 0x00007fffffffdfd0  →  0x0000000000401180  →  <__libc_csu_init+0> push r15
$rbp   : 0x00007fffffffdff0  →  0x0000000000401180  →  <__libc_csu_init+0> push r15
$rsi   : 0x00007ffff7fb2590  →  0x0000000000000000
$rdi   : 0x0               
$rip   : 0x0000000000401168  →  <main+54> nop
$r8    : 0x00007ffff7fb2580  →  0x0000000000000000
$r9    : 0x00007ffff7fb7500  →  0x00007ffff7fb7500  →  [loop detected]
$r10   : 0x00007ffff7fafca0  →  0x0000000000405660  →  0x0000000000000000
$r11   : 0x246             
$r12   : 0x0000000000401050  →  <_start+0> xor ebp, ebp
$r13   : 0x00007fffffffe0d0  →  0x0000000000000001
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdfd0│+0x0000: 0x0000000000401180  →  <__libc_csu_init+0> push r15 ← $rsp
0x00007fffffffdfd8│+0x0008: 0x000a000000000005
0x00007fffffffdfe0│+0x0010: 0x00007fffffffe0d0  →  0x0000000000000001
0x00007fffffffdfe8│+0x0018: 0x92105577ff879300
0x00007fffffffdff0│+0x0020: 0x0000000000401180  →  <__libc_csu_init+0> push r15 ← $rbp
0x00007fffffffdff8│+0x0028: 0x00007ffff7df1b6b  →  <__libc_start_main+235> mov edi, eax
0x00007fffffffe000│+0x0030: 0x0000000000000000
0x00007fffffffe008│+0x0038: 0x00007fffffffe0d8  →  0x00007fffffffe3f7  →  "/tmp/tryc"
─────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401159 <main+39>        mov    rdi, rax
     0x40115c <main+42>        call   0x401040 <fgets@plt>
     0x401161 <main+47>        mov    DWORD PTR [rbp-0x18], 0x5
 →   0x401168 <main+54>        nop    
     0x401169 <main+55>        mov    rax, QWORD PTR [rbp-0x8]
     0x40116d <main+59>        xor    rax, QWORD PTR fs:0x28
     0x401176 <main+68>        je     0x40117d <main+75>
     0x401178 <main+70>        call   0x401030 <__stack_chk_fail@plt>
     0x40117d <main+75>        leave  
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "tryc", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401168 → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x92105577ff879300

Here we can see is the stack canary. We can tell that it is the stack canary from several different things. Firstly it is the value being used when it is doing the stack canary check. Also it is around the spot on the stack it should be. Also it matches the pattern of a stack canary. While they are random they do fit a general pattern.

For x64 elfs, the pattern is an 0x8 byte qword, where the first seven bytes are random and the last byte is a null byte.

For x86 elfs, the pattern is a 0x4 byte dword, where the first three bytes are random and the last byte is a null byte.

Let's change the value of the canary and see what happens!

gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x92105577ff879300
gef➤  set *0x7fffffffdfe8 = 0x0
gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x9210557700000000
gef➤  c
Continuing.
*** stack smashing detected ***: <unknown> terminated

As we can see, it saw that the value of the canary changed and it terminated the process.

So what's the bypass? If we need to overwrite the stack canary, then we just overwrite it with itself. For instance:

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401159 <main+39>        mov    rdi, rax
     0x40115c <main+42>        call   0x401040 <fgets@plt>
     0x401161 <main+47>        mov    DWORD PTR [rbp-0x18], 0x5
 →   0x401168 <main+54>        nop    
     0x401169 <main+55>        mov    rax, QWORD PTR [rbp-0x8]
     0x40116d <main+59>        xor    rax, QWORD PTR fs:0x28
     0x401176 <main+68>        je     0x40117d <main+75>
     0x401178 <main+70>        call   0x401030 <__stack_chk_fail@plt>
     0x40117d <main+75>        leave  
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "tryc", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x401168 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x62c8c8d34092fd00
gef➤  set *0x7fffffffdfe8 = 0x4092fd00
gef➤  x/g $rbp-0x8
0x7fffffffdfe8:    0x62c8c8d34092fd00
gef➤  c
Continuing.
[Inferior 1 (process 7134) exited normally]

Here we just wrote the value of the canary to itself, and it passed the check. Of course this requires us to know the value of the stack canary. This can be accomplished via leaking the canary (which we will see later). Also in some cases you might be able to do something like brute forcing that value.