Como roubar no random?

Recentemente o vitthin implementou o comando !amizade <username> em seu bot, que recebe o nome de outro usuário como parâmetro, respondendo no chat uma porcentagem randômica para qual seria sua amizade com aquela pessoa, apenas por diversão (e algumas brigas também, quem sabe?). Porém os resultados estavam questionáveis, principalmente se não existiria algum if no código para favorecer ou desfavorecer determinados usuários.

Existe um problema sobre a distribuição dos valores randômicos, principalmente quando se utiliza o resto da divisão, como demonstrado neste artigo. Porém isso só é observado quando se tem muitas execuções, e não é uma manipulação ativa do valor randômico, como estávamos questionando.

Para demonstrar que era possível manipular a mensagem enviada pelo bot, sem que isso apareça no código, eu fiz a demonstração a baixo:

Ou seja, é possível fazer essa manipulação. A questão que o vitthin não conseguiu responder foi: como isso foi feito?

Como manipular o código?

Através do decorador

Na própria live, enquanto estávamos discutindo, eu comentei que o decorador utilizado na função implementada pelo vitthin poderia manipular o resultado. Algo como o código a baixo:

def decorator(funcao):
    def wrapper():
        return 'Tchau'
    return wrapper

@decorator
def cumprimentar():
    return 'Oi'

print(cumprimentar())  # Resultado: Tchau

Ou como demonstrado pelo Fernando Masanori:

Com esse método, é realmente possível alterar o comportamento do código, porém fica explícito no código a existência desse decorador, o que pode facilmente levantar suspeitas. Porém não foi o caso da minha demonstração, uma vez que eu também mostro o código desse decorador, e não tem nada lá fazendo essa manipulação.

Alterar a biblioteca padrão

Em outra demonstração também feita pelo Fernando Masanori, ele altera a biblioteca padrão do Python, no caso alterando o código da função randint:

Com esse método também é possível alterar o comportamento do código, mas diferente do anterior, não deixa explícito a manipulação que foi feita. Porém na minha demonstração, o comportamento da função muda conforme o nome de usuário informado, o qual não é acessível dentro da função randint, apenas se mudasse a assinatura da função para receber o nome do usuário também, mas isso seria visível no código, então não seria possível fazer a mesma coisa que na demonstração.

Alterar de onde carregar bibliotecas

Durante uma apresentação no FISL 14, o Cascardo comentou sobre o desafio de fazer um programa Java aceitar outro certificado (começando a discussão aos 23:05), onde de 26:43 até 28:50 ele comentou como trocou a classe utilizada pelo Java, retomando ao assunto aos 37:29 até 37:55:

Essa opção funciona para o Java (pergunto se funcionaria para o Kotlin também morgiovanelli?). No Python, esse comportamento pode ser replicado utilizando a variável de ambiente PYTHONPATH, bastando criar uma cópia alterada da biblioteca em outro lugar, e informando isso nessa variável de ambiente quando fosse executado o código.

Esse método também permite alterar o comportamento do código, mas apenas para bibliotecas em outros diretórios, como a biblioteca padrão e de terceiros, o que é demonstrado a baixo pela ordem de precedência dos diretórios, onde a string vazia ('') representa o diretório atual:

import sys
from pprint import pprint

pprint(sys.path)
# Resultado:
# ['',
#  '/home/eduardo/pythonlibmod',
#  '/usr/lib/python37.zip',
#  '/usr/lib/python3.7',
#  '/usr/lib/python3.7/lib-dynload',
#  '/usr/local/lib/python3.7/dist-packages',
#  '/usr/lib/python3/dist-packages']

Em relação ao código da minha demonstração, ele só permitiria alterar a função randint, que como já foi discutido, não permitiria repetir o mesmo comportamento da demonstração. Como a biblioteca lib encontra-se no mesmo diretório da aplicação, não seria possível utilizar esse método para alterá-la.

Comprometendo o kernel

Outra técnica seria comprometendo o sistema, alterando diretamente o kernel onde o código é executado, como feito pelo Seth Schoen numa apresentação no FISL 15 dos 25:17 até 33:03:

Definitivamente esse método permite alterar o comportamento do código e é o mais difícil de se detectar. Porém no final da minha demonstração eu usei o próprio Python para ler e exibir o arquivo, então seria possível reproduzir o comportamento do código, porém a alteração seria visível ao imprimir o código na tela pelo Python.

Edição de vídeo

É possível que o comportamento da minha demonstração seja uma edição de vídeo, ainda mais que ele foi gravado com o asciinema, bastando trocar os resultados conforme desejado, visto que ele está em plaintext no arquivo. Porém isso não seria aplicável ao chat na Twitch, como estava sendo usado, apenas no vídeo.

Como foi feito

Para fazer isso, eu primeiramente olhei no python -h, onde foi possível encontrar algumas opções interessantes para interagir como o interpretador do Python, onde o parâmetro PYTHONSTARTUP permitiria que código de outro lugar fosse executádo automaticamente, sem que fosse visto na tela.

Desta forma fiz o seguinte código, que para executá-lo basta entrar no diretório botmod/meubot e rodar PYTHONSTARTUP=../editlib.py python3, ou ainda definir a variável de anteriormente, com export PYTHONSTARTUP=../editlib.py, assim ao executar python3 o meu script carregaria a biblioteca e faria monkey patching para aplicar a alteração na lib (o que lembra da discussão sobre macacos digitando na live da bug_elseif).

Considerações

A minha demonstração é verdadeira e facilmente reproduzível, sem a utilização de algum recurso como edição de vídeo. Embora não foi possível fazer a mesma demonstração com os outros métodos mostrados, eles também permitem fazer manipulação no comportamento do código, podendo serem utilizados em outros casos. A área de metaprogramação trabalha bastante com técnicas apresentadas, e recomendo essa palestra sobre o assunto.

É importante observar também as implicações de segurança das demonstrações. Na mesma apresentação do Seth, ele comenta outras questões bastante interessantes, como aos 19:00 sobre a possibilidade do compilador inserir falhas de segurança nos programas, ou erros como descrito aos 13:38, que podem atingir linguagens de mais alto nível como o próprio Python. Também sobre uma falha de segurança no OpenSSH, que a correção consistia na diferença de apenas um bit no código binário. Vale observar também que muitas vezes um programa malicioso pode não ter permissão para fazer alguma operação, mas se aproveitar de permissões incorretas de arquivos e diretórios (principalmente chmod 777) para criar códigos, que poderiam ser executados sem perceber por outros usuários, onde esses teriam permissão para fazer a operação.

Felizmente ou infelizmente não é possível para um terceiro alterar o comportamento do random de um bot diretamente com o uso desses métodos, ele precisaria de acesso ao ambiente onde o código é executado, mas é possível para o dono do bot.