Computadores executam softwares para realizar tarefas. Na computação moderna, esses softwares são divididos em duas grandes categorias: os kernels de sistemas operacionais (sobre os quais aprenderemos bem mais adiante) e os processos, que discutiremos aqui. Quando o Linux inicializa, ele executa um processo de init (abreviação de inicializador) que, por sua vez, dispara vários outros processos, os quais iniciam ainda mais processos até que, finalmente, você esteja diante da sua shell na linha de comando — que também é um processo! A shell, naturalmente, inicia processos em resposta aos comandos que você digita.

Neste módulo, aprenderemos a visualizar e interagir com processos de diversas maneiras fascinantes!


Primeiro, aprenderemos a listar os processos em execução utilizando o comando ps. Dependendo de a quem você perguntar, ps significa "process snapshot" (captura de processos) ou "process status" (estado dos processos), e serve para listar processos. Por padrão, o ps lista apenas os processos em execução no seu terminal atual, o que, francamente, não é muito útil:

hacker@dojo:~$ ps
    PID TTY          TIME CMD
    329 pts/0    00:00:00 bash
    349 pts/0    00:00:00 ps
hacker@dojo:~$

No exemplo acima, temos a própria shell (bash) e o próprio processo ps, sendo apenas isso o que está rodando naquele terminal específico. Também vemos que cada processo possui um identificador numérico (o Process ID, ou PID), que é um número único para identificar cada processo em execução em um ambiente Linux. Vemos também o terminal no qual os comandos estão rodando (neste caso, a designação pts/0) e a quantidade total de tempo de CPU que o processo consumiu até o momento (como esses processos são muito leves, não consumiram sequer 1 segundo!).

Na maioria dos casos, isso é tudo o que você verá com um ps padrão. Para torná-lo realmente útil, precisamos passar alguns argumentos.

Como o ps é um utilitário bastante antigo, o uso de seus argumentos tem algumas peculiaridades históricas. Existem duas formas principais de especificar argumentos:

Sintaxe "Padrão" (Standard/POSIX): nesta sintaxe, você pode usar -e para listar "todos" (every) os processos e -f para uma saída em "formato completo" (full format), incluindo os argumentos. Eles podem ser combinados em um único argumento: -ef.

Sintaxe "BSD": nesta sintaxe, você usa a para listar processos de todos os usuários (all), x para listar processos que não estão vinculados a um terminal e u para uma saída legível voltada ao usuário (user-readable). Eles podem ser combinados em um único argumento: aux.

Esses dois métodos, ps -ef e ps aux, produzem saídas ligeiramente diferentes, mas bastante equivalentes e facilmente reconhecíveis.

Vamos testar no dojo:

hacker@dojo:~$ ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
hacker         1       0  0 05:34 ?        00:00:00 /sbin/docker-init -- /bin/sleep 6h
hacker         7       1  0 05:34 ?        00:00:00 /bin/sleep 6h
hacker       102       1  1 05:34 ?        00:00:00 /usr/lib/code-server/lib/node /usr/lib/code-server --auth=none -
hacker       138     102 11 05:34 ?        00:00:07 /usr/lib/code-server/lib/node /usr/lib/code-server/out/node/entr
hacker       287     138  0 05:34 ?        00:00:00 /usr/lib/code-server/lib/node /usr/lib/code-server/lib/vscode/ou
hacker       318     138  6 05:34 ?        00:00:03 /usr/lib/code-server/lib/node --dns-result-order=ipv4first /usr/
hacker       554     138  3 05:35 ?        00:00:00 /usr/lib/code-server/lib/node /usr/lib/code-server/lib/vscode/ou
hacker       571     554  0 05:35 pts/0    00:00:00 /usr/bin/bash --init-file /usr/lib/code-server/lib/vscode/out/vs
hacker       695     571  0 05:35 pts/0    00:00:00 ps -ef
hacker@dojo:~$

Você pode ver aqui que existem processos rodando para a inicialização do ambiente do desafio (docker-init), um temporizador para encerrar o ambiente e preservar recursos computacionais (sleep 6h para encerrar após 6 horas), o ambiente do VSCode (vários processos auxiliares do code-server), a shell (bash) e o próprio comando ps -ef. É essencialmente o mesmo resultado com ps aux:

hacker@dojo:~$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
hacker         1  0.0  0.0   1128     4 ?        Ss   05:34   0:00 /sbin/docker-init -- /bin/sleep 6h
hacker         7  0.0  0.0   2736   580 ?        S    05:34   0:00 /bin/sleep 6h
hacker       102  0.4  0.0 723944 64660 ?        Sl   05:34   0:00 /usr/lib/code-server/lib/node /usr/lib/code-serve
hacker       138  3.3  0.0 968792 106272 ?       Sl   05:34   0:07 /usr/lib/code-server/lib/node /usr/lib/code-serve
hacker       287  0.0  0.0 717648 53136 ?        Sl   05:34   0:00 /usr/lib/code-server/lib/node /usr/lib/code-serve
hacker       318  3.3  0.0 977472 98256 ?        Sl   05:34   0:06 /usr/lib/code-server/lib/node --dns-result-order=
hacker       554  0.4  0.0 650560 55360 ?        Rl   05:35   0:00 /usr/lib/code-server/lib/node /usr/lib/code-serve
hacker       571  0.0  0.0   4600  4032 pts/0    Ss   05:35   0:00 /usr/bin/bash --init-file /usr/lib/code-server/li
hacker      1172  0.0  0.0   5892  2924 pts/0    R+   05:38   0:00 ps aux
hacker@dojo:~$

Há muitas semelhanças entre o ps -ef e o ps aux: ambos exibem o usuário (coluna UID/USER), o PID, o TTY, o horário de início do processo (STIME/START), o tempo total de CPU utilizado (TIME) e o comando executado (CMD/COMMAND). O ps -ef exibe adicionalmente o Parent Process ID (PPID), que é o PID do processo pai que iniciou o processo em questão, enquanto o ps aux mostra o percentual de uso de CPU e de memória que o processo está utilizando no sistema. Além disso, há diversos outros detalhes nos quais não precisamos nos aprofundar agora.

De qualquer forma, vamos praticar! Neste nível, renomeei novamente /challenge/run para um nome de arquivo aleatório e, desta vez, fiz com que você não possa listar (ls) o diretório /challenge! No entanto, eu também o coloquei em execução. Portanto, você pode encontrá-lo na lista de processos ativos, descobrir o nome do arquivo executável e executá-lo diretamente com seu caminho completo para obter a flag! Boa sorte!

NOTA: Tanto o ps -ef quanto o ps aux truncam a exibição do comando de acordo com a largura do seu terminal (razão pela qual os exemplos acima ficam alinhados certinhos no canto direito da tela). Se você não conseguir ler o caminho completo do processo, aumente a largura da janela do terminal (ou redirecione a saída para um arquivo ou pipe para evitar esse truncamento)! Como alternativa, você pode passar a opção w duas vezes (por exemplo, ps -efww ou ps auxww) para desativar o truncamento de linha.

Você já iniciou processos e já visualizou processos; agora você aprenderá a encerrar processos! No Linux, isso é feito através do comando de nome expressivo: kill. Com as opções padrão (que é o que abordaremos neste nível), o kill encerra um processo de forma que ele ainda tenha a oportunidade de salvar pendências e finalizar adequadamente antes de deixar de existir (enviando o sinal SIGTERM).

Digamos que você tenha um incômodo processo sleep (sleep é um utilitário que simplesmente fica esperando durante a quantidade de segundos especificada na linha de comando; neste caso, 1337 segundos) iniciado em outro terminal:

hacker@dojo:~$ sleep 1337

Como nos livramos dele? Você usa o kill para finalizá-lo, passando o identificador do processo (o PID obtido via ps) como argumento:

hacker@dojo:~$ ps -e | grep sleep
 342 pts/0    00:00:00 sleep
hacker@dojo:~$ kill 342
hacker@dojo:~$ ps -e | grep sleep
hacker@dojo:~$

Agora é hora de finalizar seu primeiro processo! Neste desafio, /challenge/run se recusará a rodar enquanto /challenge/dont_run estiver em execução! Você precisa encontrar o processo de dont_run e usar o kill nele. Se falhar, o pwn.college negará qualquer conhecimento sobre sua missão. Boa sorte!

Você aprendeu como finalizar outros processos com o comando kill, mas às vezes você só quer se livrar do processo que está travando o seu próprio terminal! Felizmente, os terminais contam com um atalho de teclado para isso: Ctrl-C (ou seja, segurar a tecla Ctrl e pressionar C) envia um sinal de "interrupção" (interrupt, SIGINT) para qualquer aplicativo que esteja aguardando entrada no terminal e, normalmente, isso faz com que o aplicativo seja encerrado imediatamente.

Experimente aqui! /challenge/run se recusará a lhe entregar a flag até que você o interrompa. Boa sorte!


Para quem quiser se aprofundar, confira este artigo sobre terminais e "códigos de controle" (como o Ctrl-C).

Às vezes, processos com comportamento indevido podem interferir no seu trabalho. Esses processos precisam ser finalizados...

Neste desafio, há um processo de despiste (decoy) monopolizando um recurso crítico: um named pipe (FIFO) em /tmp/flag_fifo no qual (assim como no desafio de FIFO de Practicing Piping) /challenge/run quer gravar a sua flag. Você precisará encerrar (kill) esse processo.

Seu fluxo geral de trabalho deve ser:

  1. Verificar quais processos estão em execução.
  2. Encontrar /challenge/decoy na lista e identificar o seu ID de processo (PID).
  3. Finalizá-lo com kill.
  4. Executar /challenge/run para obter a flag sem ser inundado por mensagens falsas (você não precisa redirecionar a saída dele; ele gravará no FIFO por conta própria).

Boa sorte!


NOTA: Você pode ver algumas flags falsas aparecerem mesmo após finalizar o processo despiste. Isso acontece porque os pipes no Linux têm buffer (buffered): conceitualmente, eles possuem uma extensão pela qual os dados fluem, e você pode ter finalizado o processo enquanto ainda havia dados trafegando no pipe. Esses dados, já tendo entrado no pipe, seguirão até a outra ponta (o seu cat). Se esperar um segundo, você verá que os despistes cessarão, e então você poderá rodar /challenge/run e vencer!

Você aprendeu a interromper processos com Ctrl-C, mas existem medidas menos drásticas para recuperar o controle do seu terminal! Você pode suspender processos para o segundo plano com Ctrl-Z. Neste nível, exploraremos como isso funciona e, no próximo nível, descobriremos como retomar esses processos suspensos!

O binário run deste nível quer ver outra cópia de si mesmo em execução utilizando o mesmo terminal. Como fazer isso? Use o terminal para iniciá-lo, suspenda-o com Ctrl-Z e, em seguida, inicie outra cópia enquanto a primeira permanece suspensa!

Normalmente, quando você suspende um processo, você vai querer retomá-lo em algum momento. Caso contrário, por que simplesmente não finalizá-lo? Para retomar processos, a sua shell oferece o comando fg (de foreground), um comando interno (builtin) que pega o processo suspenso, retoma sua execução e o coloca de volta em primeiro plano no seu terminal.

Vá em frente e experimente! O binário run deste desafio exige que você o suspenda e, logo em seguida, o retome. Boa sorte!

Você aprendeu a retomar processos em primeiro plano (foreground) com o comando fg. Você também pode retomar processos em segundo plano (background) com o comando bg! Isso permite que o processo continue rodando ativamente, ao mesmo tempo em que devolve o prompt da shell para você executar outros comandos enquanto ele trabalha.

O binário run deste nível quer ver outra cópia de si mesmo em execução, não suspensa, e utilizando o mesmo terminal. Como fazer isso? Use o terminal para iniciá-lo, suspenda-o com Ctrl-Z, envie-o para segundo plano com o comando bg e, então, inicie outra cópia enquanto a primeira continua rodando em background!


CURIOSIDADE TÉCNICA: Se você tiver interesse em se aprofundar, veja como inspecionar as diferenças entre processos suspensos e processos rodando em segundo plano! Permita-me demonstrar. Primeiro, vamos suspender um comando sleep:

hacker@dojo:~$ sleep 1337
^Z
[1]+  Stopped                 sleep 1337
hacker@dojo:~$

O processo sleep agora está suspenso em segundo plano. Podemos verificar isso com o ps, ativando a coluna stat com a opção -o:

hacker@dojo:~$ ps -o user,pid,stat,cmd
USER         PID STAT CMD
hacker       702 Ss   bash
hacker       762 T    sleep 1337
hacker       782 R+   ps -o user,pid,stat,cmd
hacker@dojo:~$ 

Viu aquele T? Ele significa que o processo está suspenso (stopped/traced) devido ao nosso Ctrl-Z. O S na coluna STAT do bash indica que ele está dormindo (sleeping), aguardando novas entradas de comandos. O R na coluna do ps indica que ele está ativamente em execução (running), e o sinal + indica que ele está em primeiro plano (foreground)!

Veja o que acontece quando retomamos o sleep em segundo plano:

hacker@dojo:~$ bg
[1]+ sleep 1337 &
hacker@dojo:~$ ps -o user,pid,stat,cmd
USER         PID STAT CMD
hacker       702 Ss   bash
hacker       762 S    sleep 1337
hacker      1224 R+   ps -o user,pid,stat,cmd
hacker@dojo:~$

Pronto! O sleep agora possui o status S. Ele está dormindo no sentido de aguardar o tempo passar, mas não está suspenso! Ele também está em segundo plano (background) e, por isso, não tem mais o sinal +.

Imagine que você tem um processo rodando em segundo plano e deseja interagir novamente com ele no terminal. O que você faz? Bem, você pode trazer um processo em segundo plano de volta para o primeiro plano usando o comando fg, exatamente da mesma forma como traria um processo suspenso! Este nível guiará você por essa prática!

Naturalmente, você não precisa iniciar um processo, suspendê-lo e depois enviá-lo para segundo plano: é possível iniciá-lo diretamente em segundo plano desde o primeiro instante! É muito simples: basta adicionar o caractere & ao final do comando:

hacker@dojo:~$ sleep 1337 &
[1] 1771
hacker@dojo:~$ ps -o user,pid,stat,cmd
USER         PID STAT CMD
hacker      1709 Ss   bash
hacker      1771 S    sleep 1337
hacker      1782 R+   ps -o user,pid,stat,cmd
hacker@dojo:~$ 

Aqui, o sleep já inicia rodando ativamente em segundo plano (background), sem jamais ter sido suspenso. Agora é a sua vez de praticar! Inicie /challenge/run em segundo plano para capturar a flag!

Todo comando na shell, incluindo cada programa executável e cada comando interno (builtin), retorna um código de saída (exit code ou return code) ao terminar sua execução. Isso pode ser usado pela shell ou pelo próprio usuário (você!) para verificar se o processo concluiu com sucesso a sua tarefa (essa interpretação depende, claro, do propósito original do comando).

Você pode acessar o código de saída do último comando encerrado utilizando a variável especial ? (não se esqueça de prefixá-la com $ para ler o seu valor: $?):

hacker@dojo:~$ touch test-file
hacker@dojo:~$ echo $?
0
hacker@dojo:~$ touch /test-file
touch: cannot touch '/test-file': Permission denied
hacker@dojo:~$ echo $?
1
hacker@dojo:~$

Como você pode ver, comandos bem-sucedidos normalmente retornam 0, enquanto comandos que falham retornam um valor diferente de zero — comumente 1, mas por vezes um código específico que identifica o tipo de erro ocorrido.

Neste desafio, você deve capturar o código de saída retornado por /challenge/get-code e, em seguida, executar /challenge/submit-code passando esse código como argumento. Boa sorte!


30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score