fbpx

Injeção de código puro na memória (shell) na terra do usuário do Linux

Injeção de código puro na memória (shell) na terra do usuário do Linux

 

# Injeção pura de código na memória (shell) na terra do usuário do Linux

##### * data: 14-12-2018, autor: rb *

## Introdução

As atividades típicas pós-exploração incluem reconhecimento, coleta de informações e escalada de privilégios. Às vezes, um adversário pode precisar de funcionalidade adicional, como quando o sistema de destino não fornece as ferramentas necessárias por padrão ou quando precisa acelerar uma dessas ações pós-exploração.

Na maioria dos casos, ferramentas dedicadas são carregadas no sistema de destino e executadas. A maior ressalva dessa abordagem é que os artefatos deixados no disco, se detectados, podem revelar informações adicionais aos defensores e comprometer potencialmente toda a operação.

Muita pesquisa foi realizada nos últimos anos sobre a execução de injeção de código no sistema operacional Windows sem tocar no disco ([[1]] (# referências), [[2]] (# referências), [[3]] ( #References), [[4]] (# References), [[5]] (# References) para citar alguns). O mesmo não pode ser dito sobre o * NIX (e especificamente o Linux), mas existem alguns trabalhos excelentes do passado: skape e jt [[2]] (# referências), o grugq [[6]] (# referências), Z0MBiE [[7]] (# referências), Pluf e maduro [[8]] (# referências), Aseem Jakhar [[9]] (# referências), mak [[10]] (# referências) ou Rory McNamara [[ 11]] (# referências).

## Cenário

Imagine-se sentado na frente de um cursor piscando, usando um shell em um servidor Linux recentemente comprometido e deseja avançar sem deixar rastros para trás. Você precisa executar ferramentas adicionais, mas não deseja carregar nada na máquina. Ou você simplesmente não pode executar nada porque a opção * noexec * está definida em partições montadas. Que opções permanecem?

Este documento mostrará como ignorar as restrições de execução e executar o código na máquina, usando apenas as ferramentas disponíveis no sistema. É um pouco desafiador em um sistema operacional * tudo é um arquivo *, mas factível se você pensar fora da caixa e usar a energia que este sistema fornece.

O artigo a seguir é um resultado direto de experimentos conduzidos pelos laboratórios Sektor7, onde métodos ofensivos novos e aprimorados são pesquisados ​​e publicados.

## Entrega de carga útil (código de shell)

Encontrar uma maneira confiável e furtiva de fornecer uma carga / ferramenta a uma máquina de destino é sempre um desafio para um adversário.

O método mais comum é estabelecer uma nova conexão com o C2 ou um servidor de terceiros que hospede a ferramenta desejada e faça o download para a vítima. Isso potencialmente gera artefatos adicionais na infraestrutura de rede (por exemplo, Netflow, logs de proxy, etc.).

Em muitas situações, um invasor esquece que já existe um canal de controle aberto na máquina de destino – a sessão do shell. Esta sessão pode ser usada como um link de dados para carregar uma carga útil para a vítima sem a necessidade de estabelecer uma nova conexão TCP com sistemas externos. A desvantagem dessa abordagem é que uma falha na rede pode resultar na perda do canal de transferência e controle de dados.

Neste artigo, os dois métodos de entrega serão referidos como fora da banda e dentro da banda, respectivamente. A última opção será usada como a principal maneira de transferir o código (shell).

! [] (img / in-band.png “Cenário de entrega de carga útil”)

## Ambiente de demonstração

Nossas demonstrações e experimentos usarão a seguinte configuração:

* ** Máquina vítima ** executando o Kali Linux recente como uma máquina virtual
* ** Máquina atacante ** – Arch Linux executando como um sistema host para VMs
* ** Conexão SSH ** da máquina do atacante para a vítima, simulando ** acesso ao shell **
* Simples ‘Hello world’ ** shellcode ** para arquitetura x86_64 (consulte [Apêndice A] (# Apêndice_A))

## Métodos somente na memória

Tmpfs

 

O primeiro lugar em que um adversário pode armazenar arquivos é ** tmpfs **. Ele coloca tudo nos caches internos do kernel e aumenta e diminui para acomodar os arquivos que ele contém. Além disso, a partir do glibc 2.2, o * tmpfs * deve ser montado em * / dev / shm * para memória compartilhada POSIX (* shm_open (), shm_unlink () *).

Aqui está um exemplo de exibição em sistemas de arquivos virtuais * tmpfs * montados (do Kali):

! [] (img / tmpfs1.png)

Por padrão * / dev / shm * é montado sem o sinalizador * noexec * definido. Se um administrador paranóico ativá-lo, ele efetivamente mata esse método – podemos armazenar dados, mas não podemos executar (* execve () * falhará).

! [] (img / tmpfs2.png)

Voltaremos a * / dev / shm * mais tarde.

### GDB

** GNU Debugger ** é uma ferramenta de depuração padrão para Linux. ** Geralmente não é instalado ** em servidores de produção, mas às vezes pode ser encontrado em ambientes de desenvolvimento e em alguns sistemas embarcados / dedicados. De acordo com o manual * gdb (1) *:


O GDB pode fazer quatro tipos principais de coisas (além de outras coisas para apoiá-las) para
ajudá-lo a detectar erros no ato:
* Inicie seu programa, especificando qualquer coisa que possa afetar seu comportamento.
* Faça seu programa parar em condições especificadas.
* Examine o que aconteceu quando o seu programa parou.
* Altere as coisas no seu programa, para que você possa experimentar a correção dos efeitos
de um bug e continue aprendendo sobre outro.

O último aspecto do GDB pode ser usado para executar o shellcode apenas na memória, sem tocar no disco.

Primeiro, convertemos nosso código de shell em uma sequência de bytes:

! [] (img / gdb1.png)

Em seguida, execute * / bin / bash * sob o controle de * gdb *, defina um ponto de interrupção em * main () *, injete o código do shell e continue. Abaixo está um one-liner:

! [] (img / gdb2.png)

### Python

** Python ** é uma linguagem de programação interpretada muito popular e, ao contrário do GDB, é comumente encontrada em muitas implantações padrão do Linux.

Sua funcionalidade pode ser estendida com muitos módulos, incluindo * ** ctypes ** *, que fornece tipos de dados compatíveis com C e permite funções de chamada em DLLs ou bibliotecas compartilhadas. Em outras palavras, ** * ctypes * permite ** a construção de um script do tipo C, combinando o poder das bibliotecas externas e ** o acesso direto aos syscalls do kernel **.

Para executar nosso código de shell na memória com Python, nosso script deve:

* ** carrega a biblioteca * libc * ** no processo Python
* ** mmap () uma nova região de memória W + X ** para o código de shell
* ** copie o shellcode ** para um buffer alocado recentemente
* tornar o buffer ‘callable’ (** casting **)
* e ** chamam o buffer **

Abaixo está o script completo (Python 2):

! [] (img / py1.png)

O script inteiro é convertido em ** uma string codificada em Base64 **:

! [] (img / py2.png)

E entregue em uma máquina de destino com uma linha:

! [] (img / py3.png)

### dd auto-modificável

Em raras ocasiões, quando nenhum dos métodos acima é possível, há mais uma ferramenta instalada por padrão em muitos sistemas Linux (parte do pacote * coreutils *) que podem ser usados. A ferramenta é chamada * dd * e é comumente usada para converter e copiar arquivos. Se o combinarmos com um sistema de arquivos * procfs * e o arquivo especial * / proc / self / mem * – expondo a própria memória do processo -, há potencialmente uma pequena janela na qual somente o shellcode é executado na memória. Para fazer isso, ** precisamos forçar * dd * a se modificar rapidamente ** (também conhecido como * ** a se shinji-nize ** *).

O comportamento padrão do tempo de execução * dd * é mostrado abaixo:

! [] (img / dd1.png)

E é assim que um tempo de execução * dd * auto-modificável deve se parecer com:

! [] (img / dd2.png)

A primeira coisa necessária é ** um local para copiar o código do shell dentro do processo * dd *. Todo o procedimento deve ser estável e confiável nas execuções, pois é um processo em execução substituindo sua própria memória.

Um bom candidato é o código chamado após a cópia / substituição ser bem-sucedida. Traduz diretamente para ** saída do processo **. A injeção de código de shell pode ser feita na PLT (* Procedure Linkage Table *) ou em algum lugar dentro do segmento de código principal na chamada * exit () * ou imediatamente antes da * exit () *.

Sobrescrever o PLT é altamente instável, porque se o nosso código de shell for muito longo, ele poderá sobrescrever algumas partes críticas usadas antes da chamada * exit () *.

Após alguma investigação, parece que a função * fclose (3) * é chamada imediatamente antes da * exit () *:

! [] (img / dd3.png)

* fclose () * é chamado apenas de 2 lugares:

! [] (img / dd4.png)

Testes adicionais mostram que o código em ** 0x9c2b ** (“ jmp 1cb0“) é o usado em tempo de execução e é seguido por um grande pedaço de código que, potencialmente, pode ser sobrescrito sem travar o processo.

Existem ** dois obstáculos adicionais ** que precisamos resolver para que esta técnica funcione:

1. ** * stdin, stdout * ** e ** * stderr * ** os descritores de arquivo ** estão sendo fechados ** por * dd * após a cópia:
! [] (img / dd5.png)
2. ** Randomização do Layout do Espaço de Endereço **

O primeiro problema pode ser resolvido criando stdin e stdout ** descritores de arquivos duplicados ** com a ajuda do bash (consulte * bash (1) *):


Duplicando descritores de arquivo
O operador de redirecionamento

[n] <& palavra

é usado para duplicar descritores de arquivo de entrada. Se a palavra se expandir para um ou
mais dígitos, o descritor de arquivo indicado por n é feito para ser uma cópia do
esse descritor de arquivo.

e prefixando nosso código de shell com * dup () * syscalls:

! [] (img / dd6.png)

O segundo problema é mais sério. Atualmente, na maioria das distribuições Linux, os binários são compilados como objetos * PIE * (* Position Independent Executable *):

! [] (img / dd7.png)

e o ASLR está ativado por padrão:

! [] (img / dd8.png)

Felizmente, o Linux suporta diferentes * domínios de execução * (aka * ** personalidades ** *) para cada processo. Entre outras coisas, os domínios de execução informam ao Linux como mapear números de sinal em ações de sinal. O sistema de domínio de execução permite que o Linux forneça suporte limitado a binários compilados em outros sistemas operacionais do tipo UNIX. Desde o ** Linux 2.6.12 **, o sinalizador “ ADDR_NO_RANDOMIZE“ está disponível, o que desativa o ASLR em um processo em execução.

Para desativar o ASLR na terra do usuário em tempo de execução, a ferramenta * setarch * pode ser usada para definir diferentes sinalizadores de personalidade:

! [] (img / dd9.png)

Agora todas as peças necessárias estão no lugar para executar o * dd * auto-modificador:

! [] (img / dd10.png)

### Chamadas do sistema

Todos os métodos acima têm uma enorme desvantagem (exceto * tmpfs *) – eles permitem a execução de código de shell, mas não um objeto executável (arquivo ELF). ** O código de shell de montagem pura tem uso limitado e não é escalável ** se precisarmos de funcionalidades mais sofisticadas.

Mais uma vez, os desenvolvedores do kernel vieram ao resgate – a partir do Linux 3.17 ** uma nova chamada de sistema foi introduzida chamada * ** memfd_create () ** *. Ele cria um arquivo anônimo e retorna um descritor de arquivo que se refere a ele. O arquivo se comporta como um arquivo regular. No entanto, ele reside na RAM e é liberado automaticamente quando todas as referências a ele são descartadas.

** Em outras palavras, o kernel do Linux fornece uma maneira de criar um arquivo somente de memória que se parece com um arquivo normal e pode ser mmap () â / ed / execve () â.

O plano a seguir cobre a criação de um arquivo baseado em * memfd * em uma memória virtual e, eventualmente, o upload de nossas ferramentas de escolha na máquina vítima sem armazená-las em um disco:

– gerar um código de shell que criará um arquivo * memfd * na memória
– injete o código do shell em um processo * dd * (consulte a seção [Auto-modificando dd] (# Auto-modificando_dd))
– ‘suspende’ o processo * dd * (também feito pelo shellcode)
– preparar uma ferramenta de escolha a ser carregada (link estaticamente * uname * é usado como exemplo)
– transfira a ferramenta codificada em base64 para a máquina vítima através de um link de dados em banda (em uma sessão de shell) diretamente no arquivo * memfd *
– finalmente, execute a ferramenta

A primeira coisa é criar um novo código de shell (consulte [Apêndice B] (# Apêndice_B)). O novo shellcode reabre os descritores de arquivo * stdin * e * stdout * fechados, chama * memfd_create () * criando um arquivo somente de memória (chamado “ AAAA“) e chama o * pause () * syscall para ‘suspend ‘o processo de chamada (* dd *). A suspensão é necessária porque queremos impedir que o processo * dd * saia e, em vez disso, tornar seu arquivo * memfd * acessível a outros processos (via * procfs *). O syscall * exit () * no código do shell nunca deve ser alcançado.

Em seguida, shinjinize * dd *, suspenda-o e verifique se o arquivo * memfd * está exposto na memória:

! [] (img / sc1.png)

O próximo passo é preparar nossa ferramenta para upload. Observe que as ** ferramentas dos atacantes ** devem estar ** vinculadas estaticamente ** ou ** usar as mesmas bibliotecas dinâmicas ** que em uma máquina ** de destino.

! [] (img / sc2.png)

Agora basta “atribuir” a ferramenta codificada em Base64 ao arquivo * memfd * e execute-o:

! [] (img / sc3.png)

Observe que o arquivo * memfd * pode ser ‘reutilizado’; o mesmo descritor de arquivo pode ‘armazenar’ a próxima ferramenta, se necessário (substituindo a anterior):

! [] (img / sc4.png)

#### E se uma máquina vítima executar um kernel anterior à 3.17?

Existe uma função da biblioteca C chamada * shm_open (3) *. Ele cria um novo objeto compartilhado POSIX na memória. Um objeto de memória compartilhada POSIX é, na verdade, um identificador que pode ser usado por processos não relacionados para * mmap () * na mesma região da memória compartilhada.

Vamos dar uma olhada no código fonte da Glibc. * shm_open () * chama * open () * em alguns * shm_name *:
(de <a href=”https://code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html”> glibc / sysdeps / posix / shm_open.c </a>)

! [] (img / sc5.png)

Que, por sua vez, é alocado dinamicamente com * shm_dir *:
(de <a href=”https://code.woboq.org/userspace/glibc/sysdeps/posix/shm-directory.h.html”> glibc / sysdeps / posix / shm-directory.h </a>)

! [] (img / sc6.png)

* shm_dir * é uma concatenação de “ _PATH_DEV“` com “* shm / *”:
(de <a href=”https://code.woboq.org/userspace/glibc/sysdeps/posix/shm_open.c.html”> glibc / sysdeps / posix / shm_open.c </a>)

! [] (img / sc7.png)

e “ _PATH_DEV“` é definido como * / dev / *.

Portanto, o * shm_open () * apenas cria / abre um arquivo no sistema de arquivos * tmpfs *, mas isso já foi abordado na seção [tmpfs] (# Tmpfs).

Considerações sobre OPSEC

Qualquer atividade ofensiva na máquina alvo requer pensar em efeitos colaterais. Mesmo se tentarmos não tocar no disco com nenhum código, nossas ações ainda poderão deixar algum ‘resíduo’.

Isso inclui (mas não está limitado a):
1. ** Logs ** (ou seja, histórico do Shell). Nesse caso, o adversário deve garantir que os logs sejam removidos ou substituídos (às vezes não é possível devido à falta de privilégios).
2. ** Lista de processos ** – ocasionalmente, outro usuário que visualiza processos em execução na máquina vítima pode identificar nomes de processos estranhos (por exemplo, * / Proc / <num> / fd / 3 *). Isso pode ser contornado alterando a sequência * argv [0] * no processo de destino.
3. ** Swappiness ** – mesmo que nossos artefatos estejam na memória virtual, na maioria dos casos eles podem ser trocados para o disco (a análise do espaço de troca é um tópico separado). Pode ser evitado potencialmente com:

– * mlock (), mlockall (), mmap () * – requer capacidade “ root ” ou pelo menos “ CAP_IPC_LOCK“
– * sysctl vm.swappiness * ou * / proc / sys / vm / swappiness * – requer privilégios de “ root“`
– cgroups (* memory.swappiness *) – requer “ root“` ou privilégio para modificar o cgroup

O último não garante que, sob carga pesada, o gerenciador de memória não troque o processo para o disco de qualquer maneira (ou seja, o Root cgroup permite a troca e precisa de memória).

## Agradecimentos

Hasherezade para inspiração não intencional
faça discussões interessantes e revisão de conteúdo
hardkor para revisão de conteúdo

## Referências

1. Execução de PE EXE na memória por Z0MBiE / 29A
https://github.com/fdiskyou/Zines/blob/master/29a/29a-6.zip
2. Injeção Remota de Biblioteca por skape & jt
http://www.hick.org/code/skape/papers/remote-library-injection.pdf
3. Injeção reflexiva de DLL por Stephen Fewer
https://www.dc414.org/wp-content/uploads/2011/01/242.pdf
4. Carregando uma DLL da memória por Joachim Bauch

Loading a DLL from memory


5. Injeção reflexiva de DLL no PowerShell por clymb3r
Reflective DLL Injection with PowerShell
6. O Projeto e Implementação do Userland Exec pelo grugq
https://grugq.github.io/docs/ul_exec.txt
7. Injected Evil por Z0MBiE / 29A
http://z0mbie.daemonlab.org/infelf.html
8. Antiforensics avançado: AUTO pela Pluf & Ripe
http://phrack.org/issues/63/11.html
9. Injeção de Thread em Tempo de Execução A maneira Jugaad de Aseem Jakhar
http://www.securitybyte.org/resources/2011/presentations/runtime-thread-injection-and-execution-in-linux-processes.pdf
10. Implementação do SELF em python pelo mak
https://github.com/mak/pyself
11. Injeção de código entre processos baseada em Linux sem ptrace (2) por Rory McNamara
https://blog.gdssecurity.com/labs/2017/9/5/linux-based-inter-process-code-injection-without-ptrace2.html

—-

Apêndice A

Exemplo de código de shell ‘Hello world’ usado nas experiências:

<img src = “img / app1.png”>

Apêndice B

* Memfd-create () * shellcode:

<img src = “img / app2.png”>

28 de outubro de 2019

Sobre nós

A Linux Force Brasil é uma empresa que ama a arte de ensinar. Nossa missão é criar talentos para a área de tecnologia e atender com excelência nossos clientes.

CNPJ: 13.299.207/0001-50
SAC:         0800 721 7901

[email protected]

Comercial  Comercial: (11) 3796-5900

Suporte:    (11) 3796-5900
[email protected]

Copyright © Linux Force Security  - Desde 2011.