"Brincando" de Gato e Rato com o SimSimi

Publicado às 23/03/2018 16:00

This content is not available in your language... So if you don't understand the language... well, at least you can appreciate the pictures of the post, right?

SimSimi tentando me matar por tentar usar a API do website deles

Você acreditaria que esse bichinho amarelinho iria te ameaçar de atividade ilegal e que ele está coletando evidências contra você? Se você conhece o SimSimi, você talvez até acreditaria nesta história, mas você já sabe que qualquer um pode ensinar frases novas para o SimSimi...

...mas e se eu te falasse que isto não era uma frase colocada por alguém que só ensinou algo para o SimSimi e sim uma mensagem pelos operadores do SimSimi? Aí sim que você começa a tremer na base.

Aqui vai a minha curta história de como os operadores do SimSimi descobriram que eu estava tentando fazer engenharia reversa da API do website deles, uma história bem curta que aconteceu durante algumas horas no dia 19/03/2018, mas que foi bem interessante e divertida enquanto durou.

/assets/img/brincando-de-gato-e-rato-com-o-simsimi/simsimi_example.png

Vários meses atrás eu estava navegando o /r/googleplaydeals e eu encontrei dois aplicativos de chat bot pagos que estavam de graça, resolvi baixar eles e usar um pouquinho, apenas para diversão... após alguns minutos utilizando um dos aplicativos eu resolvi tentar descobrir como realmente o aplicativo funcionava, usei o Packet Capturer para analisar os packets enviados pelo aplicativo e, impressionantemente, eles estavam usando a API do SimSimi diretamente, ou seja, a API key estava lá em plain text! Peguei a API key, brinquei com alguns requests para a API e após alguns minutos e coloquei um novo comando na Loritta, o +simsimi.

Para os curiosos, se você tem uma API key e você quer usar ela no seu aplicativo, o certo NÃO é colocar diretamente no aplicativo, e sim criar um servidor externo que o seu aplicativo faz um request para ele e depois fazer um request usando a API key no seu servidor.

Vários meses se passaram com o comando funcionando normalmente sem nenhum problema apararente... até que no dia 18/03/2018 o comando parou de funcionar e a API estava retornando UNAUTHORIZED, ou seja, pelo visto a key que eu estava usando foi removida ou o dono do aplicativo parou de pagar pelas calls... como os dois apps pararam de receber atualizações (e agora os bots no aplicativo apenas respondem mensagens como "Não pude pegar uma resposta para a sua pergunta") então eu tive que procurar alternativas para usar a API do Simsimi sem ter que pagar quase 90R$/100k calls pela API deles.

Tentei procurar vários aplicativos na Play Store de chat bot e felizmente (mas é infelizmente para mim) todos eles enviavam o request para um servidor externo que depois esse servidor externo retornava a resposta, ou seja, nenhuma API key em plain text para mim. :(

Então eu tentei fazer engenharia reversa de como o website do SimSimi funcionava (já que o aplicativo do SimSimi é complicado para conseguir interceptar packets já que o app detecta modificações no APK e ele utiliza SSL) usando a aba de Conexões do Mozilla Firefox.

Após alguns minutos brincando com os requests na aba de Conexões e após conseguir ter refeito o mesmo request em Kotlin, eu tinha conseguido "refazer" como o comando de +simsimi funcionar na Loritta novamente e coloquei para todo mundo usar novamente o comando, yay! Pelo visto o problema foi resolvido e agora todos viveram felizes para sempre!

val get = HttpRequest.get("https://simsimi.com/getRealtimeReq?lc=$locale&ft=1&normalProb=4&reqText=${query.encodeToUrl()}&status=W&talkCnt=0")
    .header("Cookie", "dotcom_session_key=session key; bbl_cnt=0; normalProb=4; user_displayName=${context.userHandle.name.encodeToUrl()}; user_photo=undefined; lc=pt;")
    .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0")
    .body()

Ou talvez não, após ~30 minutos eles bloquearam o IP do meu dedicado e colocaram para retornar isto para todos os requests na API deles.

{"status":200,"res":{"msg":"You are not a human, you are banned. If you are a human, contact us. http://blog.simsimi.com"}}

Okay, quais são as alternativas que eu posso fazer agora? Será que ainda existe algum jeito de eu burlar o bloqueio deles?

Sim, claro que sim.

Peguei qualquer proxy de uma lista de proxies e coloquei para a conexão ao website deles usar o proxy e pronto, agora o comando funciona novamente!

Para ficar mais "difícil" de detectar os requests que eu estava fazendo, eu também coloquei vários headers que um navegador comum (no caso, o Firefox) envia ao fazer a request no website deles.

val get = HttpRequest.get("https://simsimi.com/getRealtimeReq?lc=$locale&ft=1&normalProb=4&reqText=${query.encodeToUrl()}&status=W&talkCnt=0")
    .useProxy("proxy IP", "proxy port")
    .header("Accept", "application/json, text/javascript, */*; q=0.01")
    .header("Accept-Language", "en-US,en;q=0.5")
    .header("Content-Type", "application/json; charset=utf-8")
    .header("Cookie", "dotcom_session_key=session key; bbl_cnt=0; normalProb=4; user_displayName=Loritta; user_photo=undefined; lc=$locale; lname=Portugu%C3%AAs; currentChatCnt=0; _gat=1")
    .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0")
    .header("Host", "simsimi.com")
    .header("Referer", "https://simsimi.com/")
    .header("X-Requested-With", "XMLHttpRequest")
    .body()

Pena que após alguns minutos eles bloquearam o IP do proxy... hmmm, okay, isto era meio óbvio que iria acontecer, será que eu posso burlar o bloqueio de outro jeito?

Sim, claro que sim².

Eu peguei uma lista de proxies e fiz um sistema de proxy rotation toda hora que o IP era bloqueado, ou seja:

  1. Caso não tenha nenhum proxy registrado, pegue o primeiro proxy de uma lista de proxies
  2. Ao fazer a conexão ao website do Simsimi, ative o nosso proxy
  3. Caso o nosso IP tenha sido bloqueado pelo Simsimi, remova o proxy registrado e volte para o passo 1
  4. Envie a mensagem para o usuário.
fun renewProxy(): Pair<String, Int>? {
	logger.info("Renovando o proxy do Simsimi!")
	val document = Jsoup.connect("http://www.gatherproxy.com/proxylist/country/?c=Canada")
			.get()

	val classes = document.getElementsByTag("script")

	val firstProxy = classes.firstOrNull { it.html().contains("insertPrx") }

	if (firstProxy != null) {
		val jsonPayload = firstProxy.html().substring(13, firstProxy.html().length - 2)
		val json = JSON_PARSER.parse(jsonPayload)

		logger.info(json.toString())
		return Pair(json["PROXY_IP"].string, Integer.parseInt(json["PROXY_PORT"].string, 16).toInt())
	}
	logger.info("Oh no, nenhum proxy encontrado!")
	return null
}

E deu certo! Toda hora que o IP era bloqueado o novo proxy era utilizado sem nenhum problemas! Agora eles não vão conseguir me bloquear!

...infelizmente felicidade de reverse engineer dura pouco, eles descobriram o que eu estava fazendo e resolveram ser mais inteligentes e ousados, trocaram todas as mensagens que o SimSimi enviava para um pedido para que os usuários mandassem uma foto da "chat room" para eles investigarem, já que "parece" que a "chat room" que estava usando a API deles de uma forma ilegal.

/assets/img/brincando-de-gato-e-rato-com-o-simsimi/simsimi_oof.png

Quando isto aconteceu eu pensei "tá, eles são inteligentes demais, já possuem ferramentas para combater pessoas que utilizam a API deles de forma errada e não vão desistir de tentarem me bloquear tão cedo, melhor parar com esse jogo de gato e rato, foi divertido enquanto durou" e retirei o comando de +simsimi da Loritta... até eu pagar pela API deles.