A study in software modification and automation by integrating Paterva’s CaseFile and Maltego

Maltego is an excellent intelligence and data visualization tool, but the need of being online(and the requirement of an registered account) make it pretty much useless for more sophisticated usages(like crime investigation or any other private and offline usage). For this, CaseFile was created, which is, basically, Maltego offline without the transforms. But transforms(offline and over closed-source data) would make the life of analysts easier in checking/validating information. To have the best of two worlds, I’ve extracted the transform engine from Maltego and inserted on CaseFile, also, removing the need of login and information leakage that the tool usually allow, making trustable and usable even by the government.

First, Maltego is a great tool and everyone who likes it should support Paterva and buy licenses. This is a study in software modification, automation and should not be used to cause harm to Paterva’s copyrights/busines model by any means. That’s why I’m using old versions of Maltego and Casefile.

The versions used was:

  • Maltego 3.1: maltego-3.1.1_CE-2012-04-11.zip md5 400b427652ca3e8ed60a6d6b7a457e81
  • CaseFile 1.0: maltego-CF.1.0.1_community-2012-03-14.zip md5 8d009eae5c899d74458712fe0e1458e1

They were originally downloaded from:

The base tools used was:

  • Jasmin ( http://jasmin.sourceforge.net/ ), an assembler for the Java Virtual Machine. It takes ASCII descriptions of Java classes, written in a simple assembler-like syntax using the Java Virtual Machine instruction set. It converts them into binary Java class files, suitable for loading by a Java runtime system.
  • ClassFileAnalyzer ( http://classfileanalyzer.javaseiten.de/ ), an analyzer and disassembler (Jasmin syntax 2) for Java class files.

First, I removed the annoying background image at maltego/modules/locale/com-paterva-maltego-ui-graph_maltego.jar.

Then, I’ve copied some files related to transform from Maltego to CaseFile:

  from maltego-ui/
    com-paterva-maltego-transforms-standard
    com-paterva-maltego-transform-protocol-v2
    com-paterva-maltego-transform-manager
    com-paterva-maltego-transform-finder     # needed by com-paterva-maltego-transform-manager
    com-paterva-maltego-transform-discovery  # needed by com-paterva-maltego-transform-protocol-v2
    com-paterva-maltego-transform-runner     # needed by com-paterva-maltego-transform-protocol-v2
  from maltego-core-platform/
    com-paterva-maltego-typing               # for com.paterva.maltego.typing.TypeNameValidator

The next step was define a series of modifications and fine tunning on CaseFile:

  • remove savetoserver and fake transform
  • remove startpage website
  • remove server discovery from manage transforms toolbar
  • always use trivialurldisplay
  • make showURL a stub in trivialurldisplay
  • remove google-me and wikipedia-me actions
  • remove all discover transforms actions
  • remove lots of webbrowser actions
  • enable transform limit toolbar

For that, I’ve put all modifications in .diff, .jdiff or .java where .diff is applied by standard POSIX patch utility, .jdiff is recompiled with jasmine and .java is compiled with jdk’s javac and integrated into target application.

Instructions:

  • Run cfpatch:
    ./cfpatch maltego-3.1.1_CE-2012-04-11.zip maltego-CF.1.0.1_community-2012-03-14.zip CF-custom.zip
  • Extact CF-custom.zip to the place where you want to install the custom CaseFile.

Extra
Create an .java file with the header:

//target-contains: com/paterva/maltego/graph/MaltegoGraph.class
//filename: org/hopto/im/Test.java

to integrate a new piece of software on Casefile. Example:

//target-contains: com/paterva/maltego/graph/MaltegoGraph.class
//filename: org/hopto/im/Test.java

// the above lines are to indicate that this class will be added
// in the same JAR module that file com/paterva/maltego/graph/MaltegoGraph.class
// and that the original name of this file is org/hopto/im/Test.java

package org.hopto.im;
public class Test {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

All the working scripts and tools can be found at: https://github.com/paoloo/casefile-extender

Advertisements

Python + SSL para aplicações simples

Precisei relembrar uma forma de gerar uma conexão SSL(não apenas a camada de aplicação) para um projeto. O python faz isso de forma muito fluida e simples. Não requer prática, tampouco habilidade haha.

Para gerar o certificado, primeiro geramos uma chave com o openssl. Depois a usamos para gerar um certificado. Uma forma direta e automatica(e insegura, ja que não ha dados no certificado gerado) de fazer isso é:

openssl genrsa -out key.pem
yes '' | openssl req -new -x509 -key key.pem -out crt.pem -days 36500 || exit 1
cat key.pem crt.pem > cert.pem
rm key.pem crt.pem

Lembrando que estou ignorando as entradas para gerar o cert. Este tipo de coisa é mais usado para automatizar e gerar um cert novo a cada sessão. Depois disto é necessario pegar o fingerprint da chave, assim:

openssl x509 -in cert.pem -md5 -fingerprint -noout | sed 's/^[^=]\+=//;s/://g' | tr 'A-Z' 'a-z'

Agora o servidor, extremamente simples, para apenas uma conexão e printar o dado enviado:

import socket, ssl
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 8081))
sock.listen(1)
newsock, fromaddr = sock.accept()
conn = ssl.wrap_socket(newsock, server_side=True, certfile='cert.pem', keyfile='cert.pem')
conn.setblocking(0)
while True:
    try:
        buf = conn.read(512)
        if buf == '':
            break
        else:
            print buf
    except:
        pass

O cliente é tão simples quanto o servidor. Só lembrar de pegar o fingerprint gerado e usar no client. Ele verifica e só loga no servidor se bater o fingerprint.

import socket, ssl, hashlib, time
CERT="fingerprint que voce pegou"
sock = socket.socket()
sock.settimeout(2)
sock.connect(('localhost', 8081))
conn = ssl.wrap_socket(sock)
if hashlib.md5(conn.getpeercert(True)).hexdigest() != CERT:
    conn.close()
    print 'CERT diferente do esperado, tentando me hackearrrrrrr'
else:
    print '200 OK'
    time.sleep(2)
    conn.write('CHUPA ESSA MANGA, WIRESHARK!1!!')
    print 'enviado'
    conn.close()

E é só isso, bem simples. É uma forma de encriptação e autenticação bem razoavel e resolve para a maioria das aplicações práticas contemporaneas.

link dos sources em : https://github.com/paoloo/simple-ssl

Serviço de checagem de CPF em bash script+python

Analisando um app de iPhone(depois mostro o passo a passo e as ferramentas), me vi precisando escrever um teste rápido no bash com o curl, que é uma ferramente absolutamente excepcional, mas me ví com problemas deevido a geração do HMAC e fazer parsing do resultado(sed+awk+cut é meio doloroso ne).
Acabei fazendo algo meio hibrido assim:

#!/bin/bash
cpfHASH=`echo "$1 $2" | python -c "from hashlib import sha1; from hmac import new as hmac; import sys; v=sys.stdin.read().strip().split(' '); print hmac('Sup3RbP4ssCr1t0grPhABr4sil','%s%s' % (v[0],v[1]),sha1).digest().encode('hex')"`

saidA=`curl https://movel01.receita.fazenda.gov.br/servicos-rfb/v2/IRPF/cpf -s -k -H "token: $cpfHASH" -H "plataforma: iPhone OS" -H "dispositivo: iPhone" -H "aplicativo: Pessoa Física" -H "versao: 8.3" -H "versao_app: 4.1" -d "cpf=$1&dataNascimento=$2" 2>&1`

python -c "null=None; a=$saidA; print ''.join(['%s = %s\n' % (c,a[c]) for c in a])"

Ficou meio porco usar o python para gerar o hash e para fazer o parsing, mas resolveu o meu problema. A execução é simples, o chato é que requer, além do CPF, a data de nascimento. Mas futuramente integrarei uma ferramenta para conseguir esta informação ;D

$ ./cpf.sh 13326724691 14121947
horaConsulta = 20:49:31
exception = None
codigoRetorno = 00
mensagemRetorno = OK
dataIsncricao = anterior a 10/11/1990
digitoVerificador = 00
nome = DILMA VANA ROUSSEFF
dataEmissao = 20/11/2015
horaEmissao = 08:49:32
mensagemObito = None
anoObito = 0000
codigoControle = CCA9.63A0.C92D.1C0D
codSituacaoCadastral = 0
dataConsulta = 20/11/2015
descSituacaoCadastral = REGULAR
dataNascimento = 14/12/1947

O código se encontra em https://github.com/paoloo/servicos/blob/master/cpf.sh

o/

Bottle.py – a solução de um problema que nao deveria existir

Bottle( http://bottlepy.org/docs/dev/index.html ) é um micro web framwork em python leve e muito simples. O mais louco é que ele é distribuido em apenas um arquivo e não tem nenhuma dependencia, alem do interpretador python, o que facilita absurdamente o deploy em qualquer ambiente!!!
Ok, e dai?
Bem, vi muita gente fazendo deploy no raspberryPi de aplicações com front end web, usando apache, php, mysql, postgres, redis, ou até mesmo obscenidades como o tomcat. Por enquanto, isto não é punido com a morte, mas deve ser evitado a todo custo e se buscar leveza e simplicidade. Quando conheci o bottle, tive a certeza que isto que procurava!

Com suporte a templates, roteamento simplificado, gerenciamento de dados e um servidor built-in, não fica faltando nada para sua aplicação subir.

para instalar (deixar o .py no diretorio ou instalar via pip, com # pip install bottle) e ter várias ideias de uso, é muito bom perder um tempinho na página oficial, mas uma aplicação que me deixou emocionado foi fazer uma pseudo-API do GPIO para web, simples assim:

cat > webgpio.py << _EOF_
from bottle import route
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD)

@route('/le/<pino:int>')
def readgpio(pino):
    GPIO.setup(pino, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
    saida = GPIO.input(pino)
    return '<pinos><%d>%s</%d></pinos>' % (pino,str(saida),pino)

@route('/escreve/<pino:int>/<val:int>')
def writegpio(pino,val):
    GPIO.setup(pino, GPIO.OUT)
    if val==0 or val ==1:
        GPIO.output(pino, (GPIO.LOW , GPIO.HIGH)[val])
        return 'escrito %d no pino %d' % (val, pino)
    else:
        return 'valores validos para escrita: 0 e 1'
_EOF_

Rodando…

$ python webgpio.py 
Bottle v0.13-dev server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.

127.0.0.1 - - [19/Nov/2015 02:20:48] "GET /le/13 HTTP/1.1" 200 26
127.0.0.1 - - [19/Nov/2015 02:21:04] "GET /escreve/15/0 HTTP/1.1" 200 20
127.0.0.1 - - [19/Nov/2015 02:21:14] "GET /escreve/15/1 HTTP/1.1" 200 20

E fazendo os requests…

$ curl http://localhost:8080/le/13
<pinos><13>12.5</13></pinos>
$ curl http://localhost:8080/escreve/15/1
escrito 1 no pino 15
$ curl http://localhost:8080/escreve/15/2
valores validos para escrita: 0 e 1

Obviamente este código não é seguro nem otimizado, é apena um PoC. Mas ainda assim, este treco é lindo, leve e tem suporte a muita coisa. Se a interface de template dele não agradar, ele é facilmente integravel com o jinja2( http://jinja.pocoo.org/docs/dev/ ) e, principalmente para linux embarcado é o framework web definitivo(ok, isso é minha opinião pessoal, mas é o que realmente parece haha).

Lua + C + Python – Mais que uma combinação macabra, uma gambiarra sinistra

Antes de mais nada: Por que?
Eu precisava fazer deploy de uma shared library em um ARM com linux(um raspberry Pi) extremamente capado, e não podia instalar todas as tools de dev, pois estava no limite de espaço do cartão, devido a aplicação dele. Mas precisava desta shared lib para carregar no python, via ctypes, pois o mesmo usaria as informações extraidas do hardware pelo .c, interpretaria e daria ordens ao resto da eletrônica. Acontece que os parametros do C variavam muito, precisavam ser tunados constantemente. Foi daí que rolou a ideia do LUA.
A ideia foi boa? Bom, funcionou. E até que faz um pouco de sentido, de alguma forma haha

vamos la, o loader, em python, é simplezão:

$ cat > loader.py << _EOF_
import ctypes
biblioteca=ctypes.CDLL('./zoalib.so')
biblioteca.zoeira.restype = ctypes.c_int
biblioteca.zoeira.argtypes = [ctypes.c_int]
biblioteca.zoeira(5)
_EOF_

O loader em C:

$ cat >zoalib.c << _EOF_
#include "lua.h"
#include "lauxlib.h"

int zoeira(int a)
{
    double z;
    lua_State *L = lua_open();
    luaL_openlibs(L);
    luaL_loadfile(L, "zoeira.lua");	/* arquivo lua */
    lua_pcall(L, 0, 0, 0);		/* limpa a pilha */
    lua_getglobal(L, "modelamotor");	/* nome da função do lua */
    lua_pushnumber(L, 3);		/* envia primeiro argumento */
    lua_pushnumber(L, 4);		/* envia segundo argumento */
    lua_pcall(L, 2, 1, 0);
    z = lua_tonumber(L, -1);		/* pega resultados */
    printf("Resultado: %f\n",z);
    lua_pop(L, 1);
    lua_close(L);
    return (a+1);
}
_EOF_

E o script em lua:

$ cat > zoeira.lua << _EOF_
function modelamotor (param1, param2)
    return math.sqrt(param1^2 + param^2)
end
_EOF_

Agora, vamos compilar com estes poucos parametros e rodar haha:

$ gcc -I/usr/include/lua5.1 zoalib.c -llua5.1 -lm -fPIC -shared -Wl,-soname,zoalib -o zoalib.so
$ python l.py
Resultado: 5.000000
6

Hell yeah!
Embora isso seja absurdamente mais util nos raspberryPi/beaglebone, talvez haja algum uso no desktop, vai saber haha.

Acessando PCI Express com python

Usando lspci para retornar o valor do dispositivo:
01:00.0 Non-VGA unclassified device: Altera Corporation Device 0de4 (rev 01)
Encontra-o em /sys/devices/
/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0
Habilita-o com:

 echo -n 1 > /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/enable 

Depois, só rodar o script(salvar como placapci.py):

import mmap, sys
with open('/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource0', 'r+b') as f:
    mm = mmap.mmap(f.fileno(), 32)
    mm[:1] = chr(int(sys.argv[1], 16))

no caso, ativar os leds de uma altera lindona:
python placapci.py 0xAA
01
python placapci.py 0x55
02

É isso aí.

Minha primeira tentativa de decodificar um apk android e usar seu serviço

UPDATE 15/maio/2014 no final do arquivo!
UPDATE 20/novembro/2015 no final do arquivo!

Escolhi como meu primeiro alvo a aplicaçao:
https://play.google.com/store/apps/details?id=br.gov.sinesp.cidadao.android
que checa os dados das placas dos carros e diz qual carro é. Algo bem inocente ;D
Depois de baixar o br.gov.sinesp.cidadao.android.apk e checar sua estrutura interna, pude ver que apenas classes.dex, resources.arsc e AndroidManifest.xml eram interessantes.
Primeiro procurei uma ferramente para transformar o dex em jar. Pela ordem das pesquisas no google, tentei inicialmente o apktool( http://android-apktool.googlecode.com/files/apktool1.5.2.tar.bz2 ), mas nao funcionou, deu trocentos erros. Passei para o dex2jar( http://dex2jar.googlecode.com/files/dex2jar-0.0.9.15.zip )… esse funcionou como mágica e gerou o classes_dex2jar.jar, que abri com o jd-gui ( http://jd.benow.ca/jd-gui/downloads/jd-gui-0.3.5.linux.i686.tar.gz ). Comecei a explorar.
Iniciando em br.gov.sinesp.cidadao, vi que android.* nao tinha nada interessante. Mas tanto util.Hash quanto cordova.plugin.InfoAplicacaoPlugin pareciam promissores.
Comecei, lógico, pelo Hash.

public class Hash
{
public static String generateHash(String paramString1, String paramString2)
{
SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramString1.getBytes(), "HmacSHA1");
try
{
Mac localMac = Mac.getInstance("HmacSHA1");
localMac.init(localSecretKeySpec);
byte[] arrayOfByte = localMac.doFinal(paramString2.getBytes());
new Hex();
String str = new String(Hex.encode(arrayOfByte), "UTF-8");
return str;
}
...

Um método bem simples que usa HMAC(Hash-based Message Authentication Code) para gerar o hash encodado em hexa vindo de dois parametros (paramString1 e paramString2). Implementar HMAC em python e hex encodar a saida é trivial:

from hashlib import sha1
from hmac import new as hmac
generateHash = lambda(placa): hmac(paramString1, paramString2, sha1).digest().encode('hex')

Agora é achar o diacho destes paramString1 e paramString2.
Através da documentação do PhoneGap( http://docs.phonegap.com/en/2.0.0/guide_plugin-development_android_index.md.html ), cheguei a classe Plugin, que deve ser chamado no javascript da view da aplicação. O formato do chamado ao plugin é:

exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);

Ou seja, a função chamada caso o request seja um sucesso, caso ele falhe, o serviço, ação e os parametros. Não encontrei no código original os parametros tais quais estão definidos, porem na classe InfoAplicacaoPlugin que extende CordovaPlugin e que está em /br/gov/sinesp/cidadao/cordova/plugin/, encontrei as definiçoes dos plugins, especialmente:

public final String ACTION_GET_APP_INFO = "getAppInfo";
public final String ACTION_GET_TOKEN = "getToken";
...
private String chave = "sheacsrhet";

E no método execute encontrei:

if ("getToken".equals(paramString))
{
String str1 = paramJSONArray.getString(0);
localJSONObject.put("token", Hash.generateHash(this.chave, str1));
}
paramCallbackContext.success(localJSONObject);

Huhuhu… já tenho a chave, so preciso descobrir o que raios é paramJSONArray.getString(0).
Dando mais uma passeada pelo javascript, encontrei em /assets/www/js/plugin/InfoApp.js o seguinte:
InfoApp.prototype.getToken = function(funcaoRetornoSucesso, funcaoRetornoErro, dados)
Ou seja, uma redefinição do exec original onde apenas 3 parametros eram necessarios! Procurando um pouco mais, achei em /assets/www/js/sinesp-cidadao.js o seguinte:

window.plugins.infoApp.getToken(
function(retorno){
sucessoGetToken(retorno);
var ws = new WebService(URL_SERVICO);
var parametrosHeader = getParametrosHeader();
var parametrosBody = getParametrosBody();
ws.call(METODO_SERVICO, parametrosHeader, parametrosBody, retornoServico, retornoServicoErro);
},
erroGetToken,
[placa]
);

Então, pude saber que paramJSONArray.getString(0) era a própria placa. Ufa.
Agora fica fácil reescrever a função de HASH em python:

from hashlib import sha1
from hmac import new as hmac
generateHash = lambda(placa): hmac("sheacsrhet",placa,sha1).digest().encode('hex')

Sniffei a saida do app e meu código. O hash bateu ;D
Porém, tambem tem como parametro IP e localizacao geografica(lat e long). Não sei se o sistema pode restringir a checagem por local/IP, então, porque nao randomizar tambem? ;D
Latitute e longitude é facil fazer um esquema para randomizar o valor dentro de um raio de cobertura, evitando de por um local inexistente ou, sei lá, no Japão.

rLat = lambda(raio): '%.7f' % (( raio/111000.0 * math.sqrt(random.random()) ) * math.cos(2 * 3.141592654 * random.random()) + (-3.7506985))

rLong = lambda(raio): '%.7f' % (( raio/111000.0 * math.sqrt(random.random()) ) * math.sin(2 * 3.141592654 * random.random()) + (-38.5290245))

Usei a latitude e longitude de Fortaleza/Ceará como referencia.
O próximo passo é montar tudo:

#coding: utf-8
import socket
import random, math
from hashlib import sha1
from hmac import new as hmac

generateHash = lambda(placa): hmac("sheacsrhet",placa,sha1).digest().encode('hex')

rLong = lambda(raio): '%.7f' % (( raio/111000.0 * math.sqrt(random.random()) ) * math.sin(2 * 3.141592654 * random.random()) + (-38.5290245))

rLat = lambda(raio): '%.7f' % (( raio/111000.0 * math.sqrt(random.random()) ) * math.cos(2 * 3.141592654 * random.random()) + (-3.7506985))

pacote = lambda(placa): 'POST /sinesp-cidadao/ConsultaPlaca HTTP/1.1\nHost: sinespcidadao.sinesp.gov.br\nContent-Length: %d\nOrigin: file://\nSOAPAction: \nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\nAccept: text/plain, */*; q=0.01\nx-wap-profile: http://wap.samsungmobile.com/uaprof/GT-S7562.xml\nUser-Agent: Mozilla/5.0 (Linux; U; Android 4.1.4; pt-br; GT-S1162L Build/IMM76I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30\nAccept-Encoding: gzip,deflate\nAccept-Language: pt-BR, en-US\nAccept-Charset: utf-8, iso-8859-1, utf-16, gb2312, gbk, *;q=0.7\n\n%s' % ( len(payload(placa)), payload(placa) )

payload = lambda(placa): '<?xml version="1.0" encoding="utf-8" standalone="yes" ?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ><soap:Header><dispositivo>GT-S1312L</dispositivo><nomeSO>Android</nomeSO><versaoAplicativo>1.1.1</versaoAplicativo><versaoSO>4.1.4</versaoSO><aplicativo>aplicativo</aplicativo><ip>177.206.169.90</ip><token>%s</token><latitude>%s</latitude><longitude>%s</longitude></soap:Header><soap:Body><webs:getStatus xmlns:webs="http://soap.ws.placa.service.sinesp.serpro.gov.br/"><placa>%s</placa></webs:getStatus></soap:Body></soap:Envelope>\n/sinesp-cidadao/ConsultaPlaca HTTP/1.1\r<br>\r\n' % (generateHash(placa),rLat(20000),rLong(20000), placa)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("sinespcidadao.sinesp.gov.br", 80))
s.send(pacote('XXX0000'))
print s.recv(1024)
s.close()

Pronto. Funciona ;D Para efeito de beleza, pode-se usar alguma lib de parsing de XML ou mete um belo e bruto regex da escuridão para parsear tudo com a sutileza de um macaco-ogro do pântano.

 

[UPDATE 15/maio/2014] Conforme foi percebido pelo Lúcio Corrêa( @luciofcorrea ), o sistema mudou e a sinesp tentou obfuscar o resultado. Mas não foi muito difícil gerar novamente o HASH e obter os novos parametros.
Primeiro, em /br/gov/sinesp/cidadao/android/f/k.class foi mantida a chave antiga, para enganar uma busca por strings, porem em /br/gov/sinesp/cidadao/android/f/j.class a localSecretKeySpec mudou, ao invés de usar o valor em k.java, ele seta a senha estáticamente:

SecretKeySpec localSecretKeySpec = new SecretKeySpec("shienshenlhq".getBytes(), "HmacSHA1");

Desta forma, a nova chave é “shienshenlhq”.
Em /br/gov/sinesp/cidadao/android/f/a.class temos

public static final String a = "http://sinespcidadao.sinesp.gov.br/sinesp-cidadao/ConsultaPlacaNovo27032014";

ou seja, muda a string a se fazer o POST.
Já em /br/gov/sinesp/cidadao/android/e/c.class, encontramos o novo campo a ser incluído: versaoAplicativo, com conteudo fixo “1.1.1” e o parametro aplicativo agora tem o valor fixo “aplicativo”.
Com tudo isto incluído, o script voltou a funcionar. e está postado no meu github: https://github.com/paoloo/servicos/blob/master/placa.py

[UPDATE 20/NOVEMBRO/2015] Como da ultima vez, a string de request estava obfuscada, mas foi encontrada pelo grande Junior Kaibro, que a postou no comentario. O novo POST é feito para /sinesp-cidadao/ConsultaPlacaNovo e só, nada mais mudou. Atualizei no github. Depois de tanto tempo é legal ver o pessoal mantendo a ferramenta viva. E se colocarem captcha, relaxem, eu quebro ;D