change / proc / PID / environ após o início do processo

7
$ k=v p &
[1] 3028

existe alguma maneira de p alterar o conteúdo de /proc/3028/environ para não mencionar k=v enquanto p ainda está em execução?

    
por Cetin Sert 12.08.2016 / 10:13

2 respostas

10

No Linux, você pode sobrescrever o valor das strings do ambiente na pilha.

Assim, você pode ocultar a entrada sobrescrevendo-a com zeros ou qualquer outra coisa:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char* argv[], char* envp[]) {
  char cmd[100];

  while (*envp) {
    if (strncmp(*envp, "k=", 2) == 0)
      memset(*envp, 0, strlen(*envp));

    envp++;
  }

  sprintf(cmd, "cat /proc/%u/environ", getpid());

  system(cmd);
  return 0;
}

Executar como:

$ env -i a=foo k=v b=bar ./wipe-env | hd
00000000  61 3d 66 6f 6f 00 00 00  00 00 62 3d 62 61 72 00  |a=foo.....b=bar.|
00000010

o k=v foi substituído por setenv("k", "", 1)"k="k .

Observe que setenv() para sobrescrever o valor não funcionará como nesse caso, uma nova string putenv() é alocada.

Se você não modificasse a variável de ambiente k=v com k=v / k=v1 , também deveria ser capaz de fazer algo assim para obter o endereço da string k=v2 na pilha ( bem, de um deles):

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(int argc, char* argv[]) {
  char cmd[100];
  char *e = getenv("k");

  if (e) {
    e -= strlen("k=");
    memset(e, 0, strlen(e));
  }

  sprintf(cmd, "cat /proc/%u/environ", getpid());

  system(cmd);
  return 0;
}

Observe, entretanto, que remove apenas uma das entradas k=v recebidas no ambiente. Normalmente, há apenas um, mas nada impede que alguém passe tanto execve() como bash (ou /proc/pid/environ duas vezes) na lista de env passada para /proc/pid/cmdline . Essa foi a causa de vulnerabilidades de segurança no passado, como CVE-2016-2381 . Ele poderia realmente acontecer com /proc/pid/environment antes do shellshock ao exportar uma variável e uma função com o mesmo nome.

Em qualquer caso, sempre haverá uma pequena janela durante a qual a string env var ainda não foi sobrescrita, então você pode querer encontrar outra maneira de passar as informações do segredo para o comando ( como um pipe, por exemplo) se expor via /proc/pid/environ é uma preocupação.

Observe também que ao contrário de %code% , %code% é acessível apenas por processos com o mesmo euid ou raiz (ou raiz apenas se euid e ruid do processo não forem os mesmos).

Você pode ocultar esse valor deles em %code% , mas eles ainda poderão obter qualquer outra cópia que você tenha feito da string na memória, por exemplo, anexando um depurador a ela.

Consulte o link para saber como evitar que usuários não-root façam isso.

    
por 12.08.2016 / 11:26
5

Não foi necessário sobrescrever as strings acima (não realmente ) da pilha do thread principal no Linux desde 2010.

Tanto /proc/self/cmdline como /proc/self/environ são modificáveis pelo processo em si durante a execução, por meio de chamar a função prctl() com respectivamente PR_SET_MM_ARG_START + PR_SET_MM_ARG_END ou PR_SET_MM_ENV_START + PR_SET_MM_ENV_END . Eles definem diretamente os ponteiros de memória no espaço de memória do aplicativo do processo, mantido pelo kernel para cada processo, que são usados para recuperar o conteúdo de /proc/${PID}/cmdline e /proc/${PID}/environ e, portanto, a linha de comando e o ambiente relatados pelo ps comando.

Portanto, basta construir um novo argumento ou string de ambiente (não vector, notice - a memória apontada deve ser os dados reais da string, concatenados e -delimited) e informar ao kernel onde está.

Isso está documentado na página de manual do Linux para a função prctl(2) , bem como a página de manual environ(7) . O que não é documentado é que o kernel rejeita qualquer tentativa de definir o endereço inicial acima do endereço final, ou o endereço final abaixo do endereço inicial; ou (re) definir o endereço para zero. Além disso, este não é o mecanismo original proposto por Bryan Donlan em 2009, que permitiu definir o início e o fim em uma única operação, atomicamente. Além disso, o kernel não oferece nenhuma maneira de obter os valores atuais desses ponteiros.

Isso torna complicado modificar o ambiente e as áreas da linha de comando com prctl() . É preciso chamar a função prctl() até quatro vezes, porque as primeiras tentativas podem resultar em tentativas de definir o ponteiro inicial mais alto que o ponteiro final, dependendo de onde os dados antigos e novos estão na memória. É preciso chamá-lo de mais quatro vezes, se quisermos garantir que isso não resulte em uma janela de oportunidade para outros processos no sistema inspecionarem um intervalo arbitrário do espaço de memória do processo no sistema. período em que o novo início / fim foi definido, mas o novo fim / início não foi.

Uma única chamada de sistema atômico que define o intervalo inteiro de uma só vez teria sido muito mais fácil para os programas de aplicativos usarem com segurança.

Uma outra ruga é que, por nenhuma boa razão (dadas as verificações no kernel, a sobregravação das áreas de dados originais de qualquer maneira , e o fato de os equivalentes não serem operações privilegiadas em qualquer dos BSDs), no Linux isso requer privilégios de superusuário.

Eu escrevi funções setprocargv() e setprocenvv() razoavelmente simples para meus conjuntos de ferramentas, que empregam isso. Os programas de carregamento em cadeia dos conjuntos de ferramentas integrados, como setenv e foreground , refletem os argumentos e ambiente de comando encadeados para onde o Linux permite.

# /package/admin/nosh/command/clearenv setenv WIBBLE wobble foreground pause \; true &
[1] 1057
# hexdump -C /proc/1057/cmdline
00000000  66 6f 72 65 67 72 6f 75  6e 64 00 70 61 75 73 65  |foreground.pause|
00000010  00 3b 00 74 72 75 65 00                           |.;.true.|
00000018
# hexdump -C /proc/1057/environ
00000000  57 49 42 42 4c 45 3d 77  6f 62 62 6c 65 00        |WIBBLE=wobble.|
0000000e
# hexdump -C /proc/1058/cmdline
00000000  70 61 75 73 65 00                                 |pause.|
00000006
# hexdump -C /proc/1058/environ
00000000  57 49 42 42 4c 45 3d 77  6f 62 62 6c 65 00        |WIBBLE=wobble.|
0000000e
# 

Note que isto não milita contra coisas que rastreiam o processo e acessam sua memória diretamente por outros meios (e não através destes dois pseudo-arquivos), e naturalmente deixa uma janela antes que as cadeias sejam modificadas onde esta informação pode ser visto, assim como sobrescrevendo os dados acima da pilha do thread principal. E, assim como ocorre com a sobrescrita dos dados, isso não leva em conta as bibliotecas de tempo de execução de idioma que fazem cópias do ambiente (na pilha) em várias circunstâncias. Em geral, não considere que este seja um bom mecanismo para passar "segredos" a um programa como (digamos) herdar um descritor de arquivo aberto para o final de leitura de um canal não-nomeado, lido em um buffer de entrada totalmente sob seu controle que você limpe.

Leitura adicional

por 16.04.2018 / 08:20