On x86 Linux, stack canaries are kept in thread local storage and accessed through the gs segment register (e.g. gs:0x14). If you have ever found yourself in a debugging session and wondering how to print out that value then this is the post for you.

Thread local storage is initialized by the set_thread_area(2) systemcall which takes a user_desc struct as its only argument.

struct user_desc {
    unsigned int  entry_number;
    unsigned long base_addr;
    unsigned int  limit;
    unsigned int  seg_32bit:1;
    unsigned int  contents:2;
    unsigned int  read_exec_only:1;
    unsigned int  limit_in_pages:1;
    unsigned int  seg_not_present:1;
    unsigned int  useable:1;

The second field, base_addr is the address of a data structure which will hold our stack canary (among other things).

One way to find the canary value is to catch the set_thread_area syscall and save the base_addr. After that, base_addr + 0x14 will be where the canary is.

Here is an exmple program:

#include <stdio.h>

void a()
  printf("Hello, world!\n");

int main()

We can compile it with -fstack-protector-all to ensure that every function contains a canary, even our trivially safe a() function. And -m32 to ensure it is 32 bit.

†  gcc -m32 -fstack-protector-all canary-test.c
†  r2 -d a.out
[0xf77bf990]> dcs set_thread_area
child stopped with signal 133
--> SN 0xf77bf8fd syscall 243 set_thread_area (0xffffffda 0xffa20d90)
[0xf77bf8fd]> pd -18
       ,==< 0xf77bf8b4      7f00           jg 0xf77bf8b6
       `--> 0xf77bf8b6      0000           add byte [eax], al
        |   0xf77bf8b8      e83e860100     call 0xf77d7efb
        |   0xf77bf8bd      83c410         add esp, 0x10
        `=< 0xf77bf8c0      ebe5           jmp 0xf77bf8a7
            0xf77bf8c2      8b4004         mov eax, dword [eax + 4]    ; [0x4:4]=-1 ; 4
            0xf77bf8c5      89e3           mov ebx, esp
            0xf77bf8c7      898660080000   mov dword [esi + 0x860], eax
            0xf77bf8cd      8912           mov dword [edx], edx
            0xf77bf8cf      895208         mov dword [edx + 8], edx
            0xf77bf8d2      8b86e8feffff   mov eax, dword [esi - 0x118]
            0xf77bf8d8      c70424ffffff.  mov dword [esp], 0xffffffff
            0xf77bf8df      894210         mov dword [edx + 0x10], eax
            0xf77bf8e2      89542404       mov dword [esp + 4], edx
            0xf77bf8e6      b8f3000000     mov eax, 0xf3
            0xf77bf8eb      c7442408ffff.  mov dword [esp + 8], 0xfffff
            0xf77bf8f3      c744240c5100.  mov dword [esp + 0xc], 0x51
            0xf77bf8fb      cd80           int 0x80

0xf77bf8fb      cd80           int 0x80 This is the syscall invokation.

0xf77bf8e6      b8f3000000     mov eax, 0xf3 This is the syscall number, 0xf3 (243) which is set_thread_area (check `/usr/include/asm/unistd_32.h`).

0xf77bf8c5      89e3           mov ebx, esp This is the user desc struct, it is stored on the stack.

0xf77bf8e2      89542404       mov dword [esp + 4], edx This is where the base_addr field is set (user_desc+4).

So printing out edx should give us the base_addr we want.

[0xf77bf8fd]> dr edx

So user_addr is 0xf75f5700.

Next we break on a function (in this case a()), and single step until the cookie is in a register we can print out (eax).

[0xf77bf8fd]> db sym.a
[0xf77bf8fd]> c
Selecting and continuing: 3685
hit breakpoint at: 804843b
[0x0804843b]> ds ; pd 1 @ `dr eip`
|           0x0804843b b    55             push ebp
[0x0804843b]> ds ; pd 1 @ `dr eip`
|           0x0804843c      89e5           mov ebp, esp
[0x0804843b]> ds ; pd 1 @ `dr eip`
|           0x0804843e      83ec18         sub esp, 0x18
[0x0804843b]> ds ; pd 1 @ `dr eip`
|           0x08048441      65a114000000   mov eax, dword gs:[0x14]    ; [0x14:4]=-1 ; 20
[0x0804843b]> ds ; pd 1 @ `dr eip`
|           0x08048447      8945f4         mov dword [local_ch], eax
[0x0804843b]> dr eax

If we add 0x14 to the base_addr we noted earlier do we get the same cookie?

[0x0804843b]> pv4 @ 0xf75f5700 + 0x14


The same process can be done with gdb

(gdb) catch syscall set_thread_area
Catchpoint 1 (syscall 'set_thread_area' [243])
(gdb) r
Starting program: a.out

Catchpoint 1 (call to syscall set_thread_area), 0xf7fda8fd in ?? ()
(gdb) disassemble 0xf7fda8c5,0xf7fda8fd
Dump of assembler code from 0xf7fda8c5 to 0xf7fda8fd:
   0xf7fda8c5:  mov    %esp,%ebx
   0xf7fda8c7:  mov    %eax,0x860(%esi)
   0xf7fda8cd:  mov    %edx,(%edx)
   0xf7fda8cf:  mov    %edx,0x8(%edx)
   0xf7fda8d2:  mov    -0x118(%esi),%eax
   0xf7fda8d8:  movl   $0xffffffff,(%esp)
   0xf7fda8df:  mov    %eax,0x10(%edx)
   0xf7fda8e2:  mov    %edx,0x4(%esp)
   0xf7fda8e6:  mov    $0xf3,%eax
   0xf7fda8eb:  movl   $0xfffff,0x8(%esp)
   0xf7fda8f3:  movl   $0x51,0xc(%esp)
   0xf7fda8fb:  int    $0x80
(gdb) p/x $edx
$1 = 0xf7fd5440
(gdb) b a
Breakpoint 2 at 0x8048441
(gdb) c

Breakpoint 2, 0x08048441 in a ()
(gdb) disassemble
Dump of assembler code for function a:
   0x0804843b <+0>:     push   %ebp
   0x0804843c <+1>:     mov    %esp,%ebp
   0x0804843e <+3>:     sub    $0x18,%esp
=> 0x08048441 <+6>:     mov    %gs:0x14,%eax
   0x08048447 <+12>:    mov    %eax,-0xc(%ebp)
   0x0804844a <+15>:    xor    %eax,%eax
   0x0804844c <+17>:    sub    $0xc,%esp
   0x0804844f <+20>:    push   $0x8048530
   0x08048454 <+25>:    call   0x8048310 <puts@plt>
   0x08048459 <+30>:    add    $0x10,%esp
   0x0804845c <+33>:    nop
   0x0804845d <+34>:    mov    -0xc(%ebp),%eax
   0x08048460 <+37>:    xor    %gs:0x14,%eax
   0x08048467 <+44>:    je     0x804846e <a+51>
   0x08048469 <+46>:    call   0x8048300 <__stack_chk_fail@plt>
   0x0804846e <+51>:    leave
   0x0804846f <+52>:    ret
End of assembler dump.
(gdb) si
0x08048447 in a ()
(gdb) p/x $eax
$2 = 0x9b291e00
(gdb) p/x *(0xf7fd5440 + 0x14)
$4 = 0x9b291e00