O que define fs: [0x28] (stack canary)?

9

De esta postagem , é mostrado que FS:[0x28] é um stack-canary. Estou gerando o mesmo código usando o GCC nessa função,

void foo () {
    char a[500] = {};
    printf("%s", a);
}

Especificamente, estou recebendo este assembly.

    0x000006b5      64488b042528.  mov rax, qword fs:[0x28]                ; [0x28:8]=0x1978 ; '(' ; "x\x19"
    0x000006be      488945f8       mov qword [local_8h], rax
...stuff...
    0x00000700      488b45f8       mov rax, qword [local_8h]
    0x00000704      644833042528.  xor rax, qword fs:[0x28]
    0x0000070d      7405           je 0x714
    0x0000070f      e85cfeffff     call sym.imp.__stack_chk_fail           ; void __stack_chk_fail(void)
    ; CODE XREF from 0x0000070d (sym.foo)
    0x00000714      c9             leave
    0x00000715      c3             ret

O que está definindo o valor de fs:[0x28] ? O kernel, ou o GCC está jogando no código? Você pode mostrar o código no kernel ou compilado no binário que define fs:[0x28] ? O canário é regenerado - na inicialização ou no processo de desova? Onde isso é documentado?

    
por Evan Carroll 05.07.2018 / 23:50

2 respostas

10

É fácil acompanhar essa inicialização, pois (quase) todo processo strace mostra um syscall muito suspeito durante o início da execução do processo:

arch_prctl(ARCH_SET_FS, 0x7fc189ed0740) = 0

Isso é o que o man 2 arch_prctl diz:

   ARCH_SET_FS
          Set the 64-bit base for the FS register to addr.

Sim, parece que é o que precisamos. Para encontrar quem chama arch_prctl , vamos procurar um backtrace:

(gdb) catch syscall arch_prctl
Catchpoint 1 (syscall 'arch_prctl' [158])
(gdb) r
Starting program: <program path>

Catchpoint 1 (call to syscall arch_prctl), 0x00007ffff7dd9cad in init_tls () from /lib64/ld-linux-x86-64.so.2
(gdb) bt
#0  0x00007ffff7dd9cad in init_tls () from /lib64/ld-linux-x86-64.so.2
#1  0x00007ffff7ddd3e3 in dl_main () from /lib64/ld-linux-x86-64.so.2
#2  0x00007ffff7df04c0 in _dl_sysdep_start () from /lib64/ld-linux-x86-64.so.2
#3  0x00007ffff7dda028 in _dl_start () from /lib64/ld-linux-x86-64.so.2
#4  0x00007ffff7dd8fb8 in _start () from /lib64/ld-linux-x86-64.so.2
#5  0x0000000000000001 in ?? ()
#6  0x00007fffffffecef in ?? ()
#7  0x0000000000000000 in ?? ()

Portanto, a base do segmento FS é definida pelo ld-linux , que é uma parte do glibc , durante o carregamento do programa (se o programa estiver vinculado estaticamente, esse código será incorporado ao binário). É aqui que tudo acontece.

Durante a inicialização, o carregador inicializa o TLS . Isso inclui a alocação de memória e a configuração do valor base FS para apontar para o início do TLS. Isso é feito por meio de arch_prctl syscall . Após a inicialização do TLS security_init função é chamada, o que gera o valor do guarda de pilha e grava na localização da memória, que fs:[0x28] aponta para:

E 0x28 é o deslocamento do campo stack_guard na estrutura que está localizada no início do TLS.

    
por 06.07.2018 / 04:02
5

O que você está vendo é chamado (no GCC) de SSP (Stack Smashing Protector) , que é uma forma de < href="https://en.wikipedia.org/wiki/Buffer_overflow_protection"> proteção contra estouro de buffer gerada pelo compilador. O valor é um número aleatório gerado pelo programa na inicialização e, como o artigo da Wikipedia menciona, é colocado em Thread Local Storage (TLS) . Outros compiladores podem usar estratégias diferentes para implementar esse tipo de proteção.

Por que armazenar o valor no TLS? Como o valor está localizado lá, seu endereço não é acessível pelos registradores CS, DS e SS, tornando muito difícil adivinhar o valor armazenado se você estiver tentando alterar a pilha de código malicioso.

    
por 06.07.2018 / 03:03