- Em Magic: The Gathering Arena, era possível forçar o pedido de rendição do oponente no momento desejado, tornando possível não perder em partidas de matchmaking
- Jogos de cartas normalmente se encaixam bem em uma arquitetura autoritativa de servidor, na qual o servidor gerencia todo o estado, mas o oponente bot Sparky no MTGA era implementado com lógica local no cliente
- O cliente MTGA, baseado em C#, permitia acessar objetos em tempo de execução e campos private por reflexão, e no código decompilado apareciam nomes como
JoinMatch,ConnectAndJoinMatcheHeadlessClient - Em partidas contra bots, era usada uma estrutura que conectava aos dois assentos com o mesmo
PersonaIDda conta e o mesmo JWT; aplicando isso a partidas normais, foi possível anexar um cliente headless ao assento do oponente e chamarConcedeGame() - O servidor depois foi corrigido para impedir que os dois assentos usassem a mesma conta e o mesmo JWT em partidas de matchmaking, tornando este um caso em que o limite entre a implementação de bots no cliente e a validação de autenticação dos assentos afetou diretamente a segurança em produção
Por que hackear jogos de cartas é difícil
- Jogos de cartas são baseados em turnos e não trocam muita informação entre cliente e servidor, então se encaixam bem em uma arquitetura autoritativa de servidor
- O servidor pode gerenciar todo o estado do jogo e enviar ao cliente apenas as informações necessárias
- Informações não públicas, como a mão ou o deck do oponente, não existem localmente
- Tentativas como mudar a ordem do deck ou manipular a próxima compra são difíceis, porque o servidor executa a ação e só informa o resultado
- Ao contrário de jogos de tiro em primeira pessoa, jogos de cartas têm ações de jogador limitadas e em momentos definidos
- Em FPS, informações como a posição dos modelos inimigos podem ficar em cache no cliente, possibilitando coisas como wallhack
- O blog anti-wallhack da Riot aborda uma estratégia de reduzir os dados de posição de jogadores no cliente
- Também é relativamente mais fácil detectar ações inválidas
- Jogar uma carta que não está na mão
- Agir fora do próprio turno
Ponto de partida da análise: rede e cliente C#
- Foi escolhida como ponto de partida para o hacking de jogo a abordagem de examinar a comunicação de rede
- A palestra de Manfred na DEF CON, sobre casos de hacking de MMOs, mostra como a engenharia reversa de protocolos de rede é central para analisar vários bugs
- Como o MTGA é escrito em C#, foi possível manipular objetos em tempo de execução em vez de fazer hook nas funções de envio e recebimento de tráfego
- Com reflexão em .NET, também é possível acessar campos e métodos private
- Como exemplo introdutório relacionado, há o tutorial de hacking por reflexão em jogos Unity
- Assemblies .NET não ofuscados permitem verificar nomes de funções, variáveis e classes em formato legível graças ao metadata token
- Por isso, o resultado da decompilação fica praticamente próximo de uma revisão de código-fonte
A estrutura do Sparky encontrada em JoinMatch
- Ao procurar nomes de funções relacionados para encontrar a inicialização de entrada em partidas, foi encontrada a função
JoinMatch JoinMatchera uma função longa, com mais de 200 linhas, e perto do fim foi identificada a chamada aConnectAndJoinMatch- Essa função parecia ser o fluxo que recebia as configurações da partida e se conectava ao servidor do jogo
- No mesmo fluxo de código havia ramificações para
MatchType.NPEeMatchType.FamiliarNPEparece significar a experiência de novo jogador, isto é, partidas do tipo tutorialFamiliarera a partida padrão contra bot
- A lógica chamada nesse ramo estava ligada ao Sparky, o oponente bot do MTGA
- Sparky é o oponente mascote do MTGA usado em tutoriais e partidas contra bot
- Em partidas contra bot, a lógica do bot roda dentro do cliente do jogo na máquina local
Bot local e HeadlessClient
- O manipulador da lógica real do bot estava na classe
HeadlessClient HeadlessClientera um cliente headless que não renderiza o tabuleiro do jogo, mas se conecta ao servidor e joga a partida- O cliente de bot criado localmente usava as mesmas credenciais de autenticação do cliente do jogo
PersonaID, que funciona como ID do usuário- JSON web token atribuído ao jogo após o login
- Em partidas contra bot, a estrutura era tal que o servidor não considerava um problema que o mesmo cliente se conectasse efetivamente aos dois lados da partida
- Assento (seat) é a forma de distinguir qual jogador você é dentro do jogo
- Em partidas contra bot, era possível preencher assentos diferentes com as mesmas credenciais
Como partidas normais eram sequestradas
- A lógica das partidas contra bot foi aplicada a partidas normais para testar se seria possível se conectar aos dois assentos
- As informações necessárias foram obtidas dos objetos do jogo em tempo de execução
- Configuração da partida atual
- Host e porta do servidor da partida
controllerFabricUrimatchIdPersonaIDJwt- Banco de dados de cartas
- Gerenciador de partida
- Sistema de consulta de assets
- Com base no próprio assento, foi calculado o assento do oponente
- No código,
man.LocalPlayerSeatId % 2U + 1Uera usado para obter o outro assento
- No código,
UnityFamiliar.SpawnFamiliar_DEBUG(...)foi usado para conectar o bot ao assento do oponente- Em seguida, foi localizado o objeto
UnityFamiliarcriado e chamadacheatbot.Client.Gre.ConcedeGame()para forçar rendição imediata
Resultado e correção
- Esse método também funcionava em partidas normais
- Funcionava mesmo quando o oponente já estava conectado e a partida em andamento
- Como eram partidas de matchmaking, era possível receber recompensas como se tivesse vencido um oponente humano
- O cerne da vulnerabilidade era que o servidor de partidas normais permitia a situação em que os dois assentos se conectavam com a mesma conta e o mesmo JWT
- Depois, o servidor foi corrigido para impedir que os dois assentos usassem a mesma conta e o mesmo JWT em partidas de matchmaking
- O código
InstaWin, no apêndice, usa umMonoBehaviourdo Unity para criar um botão de GUI e, ao clicar nele, coleta as informações da partida atual, conecta o bot ao assento do oponente e então força a rendição
1 comentários
Opiniões no Hacker News
O que me levou a mergulhar de verdade no Linux pela primeira vez foi examinar o tráfego de rede com o ShowEQ para EverQuest
Na época, o tráfego não era criptografado e continha muita informação útil. Com um hub, eu duplicava o tráfego para uma máquina Linux e desenhava um mapa em tempo real da zona, mostrando a localização de monstros, NPCs e usuários, além até do loot que os monstros carregavam, o que permitia escolher só monstros específicos para matar. A vantagem era que, por ser um método passivo, era impossível de detectar; no fim, a SOE percebeu e começou a criptografar o tráfego
Aí o outro lado introduz assinaturas baseadas em chaves, você tenta roubar a chave do cliente de novo, quebra a criptografia, e depois entram sistemas anti-cheat, dando início ao jogo de gato e rato
Valeu a pena a ponto de ganhar vantagem no jogo? Não. Eu nem jogava tão a sério, então passei 2 semanas instalando e usei por mais ou menos 1 semana, mas como experiência de aprendizado foi excelente
Por exemplo, a existência dos hell levels, o fato de que não eram humanos, mas halflings que recebiam bônus de experiência, e que havia mesmo diferenças de experiência por raça/classe, além de que a alquimia inicial dos Shamans estava de fato quebrada. E acho que isso acabou levando ao eqemulator.org
O atual dono de EQ não parece ligar muito, então é relativamente tranquilo usar. Porém, tirando o equipamento visível, nenhum dos dois apps jamais mostrou o loot que um monstro carregava. Com o passar dos anos, alguns dados também mudaram: antes, a vida exata dos monstros era transmitida, mas hoje só enviam a porcentagem
Não entendi bem a parte de que “um bot quase completo capaz de jogar uma partida arbitrária de Magic: The Gathering é pequeno o bastante para rodar em uma máquina local”
Se uma IA de MTG for pesada demais para rodar na máquina do cliente, acho que também não a rodariam no servidor. Partidas contra bots em jogos de cartas normalmente não são cobradas por partida, então o custo cresce bastante. Servidores também não são mágicos: em geral usam as mesmas CPUs x86 de máquinas locais, e muitas vezes com clocks menores que desktops. Para reduzir o tempo dos turnos do bot em relação ao local, seria preciso usar muito mais núcleos que o cliente; se forem 8 a 16 núcleos alocados por jogador, isso parece um pesadelo nos picos de usuários simultâneos. Se o jogador controlado pela CPU não tiver suporte a múltiplos núcleos, rodar localmente deveria ser mais rápido de qualquer forma
Quer dizer que o motor de regras do bot é pequeno o bastante para caber na memória de um iPhone antigo ou de um dispositivo Android. Num servidor, daria para manter muitas máquinas de estado ou motores de regras na memória, e executar uma solicitação específica talvez quase não use capacidade de processamento
Daniel, parabéns mais uma vez por chegar ao topo. Depois que este texto foi publicado, tentei hackear o MTGA e também conversei um pouco no GitHub [0]
Para quem tiver interesse: atualmente estou trabalhando, de forma inativa, em um cliente não oficial de MTGA que quase não tem funcionalidades. O objetivo é automatizar partidas ranqueadas e oferecer adversários bots mais fortes. Ultimamente outras coisas tomaram a frente, e ainda estou meio perdido sobre como criar uma boa UI que seja clara de visualizar, então é difícil até estimar quando isso ficará minimamente útil. Além disso, continuo interessado em ouvir histórias do Daniel ou de outras pessoas sobre hacking de jogos: como encontram bugs assim, como não se preocupar em ser banido depois de hackear, formas de divulgar bugs como a @aethros mencionou, estruturação de clientes não oficiais para jogos de cartas etc.
[0] https://github.com/MayerDaniel/mayerdaniel.github.io/issues/...
Dizer que imaginava que criar um oponente de IA para um jogo complexo como MTG teria uma grande sobrecarga é um baita eufemismo
O jogo é praticamente Turing-completo, e mesmo excluindo loops infinitos, as pessoas brincam bastante com isso. Ainda assim, imagino que já exista muita pesquisa sobre a parte de estratégia de IA. E, como o próprio autor publicou o texto, acho que o título poderia receber um “Show HN:”
A dificuldade para quem escreve bots parece ser a segunda
Por exemplo, criando e matando criaturas-token com efeitos de entrada/saída/desvirar. Esses loops nem sempre causam dano a algum dos lados
Nunca joguei MTG, mas essa afirmação parece significar que, se você criar de propósito uma estrutura com estado, em vez de tentar vencer, isso se torna tecnicamente possível
Jogar Old School Magic 93/94 com meu filho usando cards físicos é muito divertido
Todo ano vamos a Madrid para participar do Campeonato Mundial de 7pts Singleton. Fiquei muito orgulhoso porque neste verão meu filho ficou em 9º lugar. 7pts Singleton é um formato excelente, que permite uma grande variedade de construções de decks e oferece uma jogabilidade equilibrada a um custo relativamente acessível (https://7pts-singleton.com)
Mesmo que em 7pts Singleton não dê para usar todos eles, só um já custa de milhares a dezenas de milhares de dólares. Ainda assim, é ótimo que você aproveite o jogo com seu filho, e parabéns pela colocação
Lógica de jogo puramente do lado do cliente? Quando fiz um joguinho no passado, houve momentos em que precisei colocar a lógica de jogo no cliente por responsividade, mas nada impedia de rodar a mesma lógica de novo no servidor, então foi isso que fiz
Em jogos em tempo real como FPS ou RTS isso pode ser difícil, mas em um jogo de cartas não há desculpa. Num jogo de cartas assim, o certo é não enviar ao cliente mais informações do que um jogador real poderia ver. Por exemplo, não enviar o conteúdo das cartas na mão do oponente, apenas a quantidade de cartas. As ações enviadas ao servidor também deveriam dizer respeito apenas a si mesmo; portanto, não deveria ser possível declarar a rendição do oponente. Se eu digo “rendo-me!”, o servidor deveria interpretar isso como a minha rendição
A vulnerabilidade era que dava para abrir um segundo cliente e entrar na partida em andamento no assento do oponente. Uma vez feito isso, era possível enviar ações do oponente, incluindo rendição
O que o autor explorou não foi lógica de jogo no cliente, mas um problema no código de autorização e de entrada na partida. A frase “não deveria ser possível declarar a rendição no lugar do oponente” é basicamente uma repetição de como o exploit que o autor encontrou funcionava
Em League of Legends, havia um bug de divisão por zero com uma combinação específica de campeão e itens que fazia o servidor expulsar todos os jogadores e depois travar
Como o abusador era o último a ser expulso, a equipe dele recebia a vitória, enquanto a equipe adversária recebia Loss Prevented em vez de um resultado normal
O artigo é acessível e, ao mesmo tempo, tem detalhes perspicazes
Mas não entendo o caso de conectar um bot durante uma partida real. Por que o jogo permite entrar no meio da partida, e por que, quando o bot se rende, isso é tratado como a rendição do oponente? Se fosse para criar uma partida de 3 jogadores, a rendição do jogador 3 não deveria fazer o jogador 2 se render também
O código descobre o índice do assento da minha conta e então faz o bot entrar com outro índice de assento. O problema é que ele não verifica se o usuário que está entrando é o usuário correto que deveria estar naquele assento. Também dá para pensar que não deveriam permitir uma nova conexão a um assento já conectado, mas, se o jogo também dá suporte a mobile, provavelmente você vai querer um timeout de desconexão relativamente longo. Isso porque você não quer bloquear um jogador que caiu por um instante e reconectou antes de o timeout perceber que a conexão anterior morreu. Já vi outros jogos mostrarem algo como “não é possível entrar em uma partida em andamento” por uns 10 segundos quando se tenta reconectar, e só depois a reconexão acontece. Como analogia, seria como aparecer em um torneio de MTG numa loja de cartas local, empurrar alguém para fora da cadeira, sentar e gritar “Eu concedo!”, e o juiz aceitar que o jogador B desistiu porque a declaração veio da pessoa sentada naquela cadeira
Por isso parece que era possível se render no lugar do oponente. Talvez os desenvolvedores não tenham imaginado que alguém teria motivo para tentar isso e, por isso, não fizeram a verificação
Isso me lembra a época de Diablo 2, quando era possível reutilizar o mesmo pacote de conexão para colocar um personagem de servidor aberto (LAN) nos servidores oficiais de internet da bnet
Como os dados do servidor aberto ficavam todos salvos localmente, dava para criar todo tipo de item que originalmente não deveria existir, e o servidor oficial aceitava
Recentemente voltei a entrar em MTG pelo MTGA. É um jogo em Unity e não usa il2cpp, então fiz uma descompilação rápida; mesmo se usasse il2cpp, acho que não seria grande proteção, e encontrei algumas coisas bem interessantes
Havia coisas como chaves para a build do Epic Launcher e APIs não documentadas. Não quero usar isso para trapacear; eu só queria que houvesse histórico de partidas. Algo como ver quais partidas ganhei ou perdi e rever o estado do campo de batalha de uma partida encerrada. Também vou conferir este artigo e espero que seja corrigido logo
Se você quer ver histórico, dê uma olhada em https://untapped.gg/en. Conversei um pouco com eles, e basicamente fazem o que você quer. A maior parte das informações vem dos logs de depuração do MTG no diretório da aplicação MTGA, então, se quiser, você também pode criar seu próprio tracker. O site também explica isso: https://help.hearthsim.net/en/articles/3620440-how-do-i-supp...
Lembro que havia vários aplicativos que faziam rastreamento usando esse log