Obtener token OTP del BROU

2022/04/05

Motivación

El BROU posee una aplicación de llave digital para realizar transacciones que permite generar un one-time password.

Ya que no deseo tener instalada la aplicación en un dispositivo android…, ¿es posible replicar la generación del token en una aplicación de escritorio como KeePassXC que utilizo para gestionar mis contraseñas?

Inspección de la aplicación

Para inspeccionar la aplicación primero se debe descargar la aplicación. Para ello se me ocurren dos alternativas:

  1. Instalar en un celular android la aplicación Aurora Store. Desde allí buscar la aplicación brou llave digital. Una vez encontrada, elegir la opción Manual download y cuando aparezca la opción de instalar la aplicación elegir Cancel.

    Luego conectamos el celular al PC y utilizando el programa adb copiamos al pc el apk descargado:

    $ doas apt-get install -Vy adb
    $ mkdir aurorastore;  cd aurorastore
    $ adb pull /sdcard/Android/data/com.aurora.store/files/Downloads/uy.com.brou.token/17/uy.com.brou.token.apk .
    /sdcard/Android/data/com.aurora.store/files/Downloa...led, 0 skipped. 18.3 MB/s (4955654 bytes in 0.258s)
    

    Nota: ya que confiamos en la aplicación Aurora Store confiamos en que el apk haya sido descargado directamente del Play Store y la aplicación no haya sido adulterada agregandole malware.

  2. Se busca en internet el apk en base al nombre del paquete. Para ello vamos primero al Play Store y buscamos la página de la aplicación: https://play.google.com/store/apps/details?id=uy.com.brou.token. Luego buscamos en internet el apk de la aplicación uy.com.brou.token. Como ejemplo se encuentra el link https://apkdownload.com/down_BROU-Llave-Digital/uy.com.brou.token.v7a.html desde el cual descargamos la aplicación guardandola en un directorio que llamaremos apkdownload.

    Nota: hay que tener en cuenta que un apk descargado de una fuente que no sea confiable puede haber sido adulterado y contener malware.

Por curiosidad se comparan los apk’s descargados, confirmandose que son el mismo:

$ sha256sum $(find aurorastore apkdownload -type f | sort)
d6608fd47be228bd81ed706e4438682346dd4ac116d8c6af8c01a0fa21071eec  apkdownload/uy.com.brou.token.17.v7a.apk
d6608fd47be228bd81ed706e4438682346dd4ac116d8c6af8c01a0fa21071eec  aurorastore/uy.com.brou.token.apk

(O sea, ya que confiamos en Aurora Store confirmamos que en este caso el apk no fué modificado por APK Downloader.)

A continuación se descarga y extrae la aplicación con apktool:

$ wget 'https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.6.1.jar'
$ java -jar apktool_2.6.1.jar d aurorastore/uy.com.brou.token.apk
I: Using Apktool 2.6.1 on uy.com.brou.token.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/jmpc/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

Ya se puede inspeccionar el código.

Lo primero que se nota es que es una aplicación hecha en cordova, o sea, es una aplicación que está escrita en parte utilizando HTML, CSS y JavaScript.

Se comienza a inspeccionar el código fuente javascript de la aplicación.

Código relacionado a la registración:

Código relacionado a la generación del password:

Prueba de generación del código HOTP usando información de test

En este paso se instala la aplicación y se confirma que el cupón y password (pin) de pruebas funcionan y generan un token. Se va a Configuración > Gestión de Cuentas > +Agregar. Se aceptan los términos y condiciones y en la pantalla de Nuevo Usuario se ingresa:

Al aceptar aparece un mensaje que indica que el token se ha activado correctamente. Ahora podemos elegir el usuario Test, ingresar como pin 999999 y se genera un código de seguridad.

Lo que se busca ahora es, partiendo de que se tiene el código y que tenemos el seed codificado correspondiente al token, jNnbU15eXl5dMsxWZ5alkd9nFoWo1Eb1t0Izj4nh5PKVMGI0hOQLBQMv8k2t, encontrar si es posible generar el mismo código de seguridad.

Tests utilizando código hardcoded en programa

Se codifica un script en nodejs para replicar la funcionalidad de generación del password.

NOTA: No he encontrado los parámetros ni la librería para reemplazar la funcionalidad del archivo assets/www/js/lib/crypto/aes-ctr-min.js, por lo que este se utilizará en el código de prueba pero no se agrega al post ya que se desconoce la licencia que posee.

Para realizar el código se utiliza nvm para manejar las versiones de node, la versión de node lts/gallium y las utilidades nodemon y js-beautify:

$ nvm install lts/gallium
$ echo `lts/gallium` > .nvmrc
$ nvm use
$ npm install --global nodemon
$ npm install --global js-beautify

Se genera el archivo aes.js:

{
    cat uy.com.brou.token/assets/www/js/lib/crypto/utf8-min.js
    cat uy.com.brou.token/assets/www/js/lib/crypto/base64-min.js
    cat uy.com.brou.token/assets/www/js/lib/crypto/aes-min.js
    cat <<'END'
var ctrTxt = "";
END
    cat uy.com.brou.token/assets/www/js/lib/crypto/aes-ctr-min.js
    cat <<'END'
export { Aes };
END
} | js-beautify - > aes.js

Aquí agregamos las dependencias necesarias para utilizar Aes.Ctr.decrypt y desofuscamos el código para que sea más legible al momento de querer ver que acciones realiza.

Se crea el archivo package.json para importar los módulos e incluir dependencias que serán utilizadas más adelante:

{
  "name": "replicate-brou",
  "version": "0.0.1",
  "type": "module",
  "dependencies": {
    "crc-32": "^1.2.2",
    "node-fetch": "^3.2.3",
    "otplib": "^12.0.1"
  }
}

Se instalan las dependencias:

$ npm install

El código de prueba para generar el token se escribe en el archivo test.js:

import { Aes } from "./aes.js";
import crc32 from 'crc-32/crc32.js';
import { hotp } from 'otplib';

const key = "999999";
const ciphertext = "jNnbU15eXl5dMsxWZ5alkd9nFoWo1Eb1t0Izj4nh5PKVMGI0hOQLBQMv8k2t";

// validate seed
const plaintext = Aes.Ctr.decrypt(ciphertext, key, 256);
const [ crc, seed ] = plaintext.split(" ");

if (crc != crc32.str(seed)) {
    console.log("Invalid seed - abort");
    process.exit(1);
}
console.log("Seed: " + seed);

// generate token
const counter = Math.floor(new Date().getTime() / 40000)
const token = hotp.generate(seed, counter);
console.log("Code: " + token);

Confirmamos que el token generado es correcto, o sea, que teniendo la semilla codificada y el pin se puede generar el mismo token que genera la aplicación del banco ejecutando a la vez la aplicación android y haciendo en la consola:

$ node test.js

para ejecutar el código anterior.

Replicación de aplicación android

En el archivo script.js se crea el programa para la obtención de la semilla a partír del código de activación y la posterior generación del password a partir de la semilla.

Durante las pruebas se constató que:

  1. El banco permite cambiar la llave virtual la cantidad de veces que se desee.
  2. En caso de error al capturar la salida del response al servicio del banco durante el intercambio del código de activación por la semilla codificada no hay otra opción más que ir al cajero más cercano a resetear la llave virtual.

Generación del password

A partir del código anterior, si tenemos el valor de la semilla es trivial generar el password:

function generatePassword(seed) {
  const counter = Math.floor(new Date().getTime() / 40000)
  return hotp.generate(seed, counter);
}

Decodificación de la semilla

Como se vió en el ejemplo previo del test, para decodificar la semilla se puede utilizar el siguiente código:

function obtainSeed(encryptedSeed, key) {
  // decrypt seed and validate
  const decryptedSeed = Aes.Ctr.decrypt(encryptedSeed, key, 256);
  const [ crc, seed ] = decryptedSeed.split(" ");

  if (crc != crc32.str(seed)) {
    console.error("Decrypted invalid seed: " + decryptedSeed);
    process.exit(1);
  }
  return seed;
}

Aquí nos interesa que en caso de error se nos muestre el valor decodificado de la semilla que consiste en la salida del CRC y el valor de la semilla.

Intercambio del código de activación por la semilla codificada

Los dos pasos previos los pudimos realizar sin necesitar invocar ningún servicio del banco y (por suerte) con la información de test que se encontraba en el código.

El paso que nos queda es obtener la semilla a partir del código de asociación que nos envía el banco vía email invocando un servicio web.

Ya se vió del código javascript de la aplicación que se realiza un request GET a <https://servicios.brou.com.uy/etoken/a.php?cupon={código de asociación}&callback=?> el cual devuelve el seed codificado.

Aquí es importante imprimir en caso de error la respuesta del servicio, ya que el código de activación solo puede ser utilizado una vez y si ocurre algún error en el programa hay que pedir otro código de activación y confirmar el pedido yendo a un cajero.

La respuesta esperada al intercambiar un código de activación es algo como esto:

?(["jNnbU15eXl5dMsxWZ5alkd9nFoWo1Eb1t0Izj4nh5PKVMGI0hOQLBQMv8k2t"])

A continuación el código:

async function retrieveEncryptedSeed(activationCode) {
  // retrieve encrypted seed
  const response = await fetch(`https://servicios.brou.com.uy/etoken/a.php?cupon=${activationCode}&callback=?`);
  const body = await response.text();

  // extract from response
  try {
    const [ , encryptedSeed ] = /"([^\"]+)"/.exec(body);
    return encryptedSeed;
  } catch(error) {
    console.error(`Error extracting encrypted seed from: \`${body}\``);
    console.error(error);
    process.exit(1);
  }
}

Como se indicó previamente, en caso de error al extraer la semilla codificada se muestra el contenido del mensaje.

Conclusiones

Positivo:

Negativo:

Apendice - origen de la aplicación

A partir de la url inválida: <http://www.comafi.com.ar/tokenempresas /descarga/solucionpc.aspx> que se encuentra en los archivos first_registration.js, help-terms.js, help.js y registration.js del directorio /assets/www/js/app suponemos que la aplicación fué adquirida a BANCO COMAFI. Siguiendo el link a la aplicación android en https://www.comafi.com.ar/tokenempresas/ y comparando las pantallas de las aplicaciones Comafi Token Empresas y BROU Llave Digital se encuentra un parecido importante.

Apendice - modificación de aplicación cordova

Una alternativa que finalmente no fué utilizada consiste en decompilar, modificar, construir y ejecutar un apk modificado de la aplicación.

Para ello los pasos a seguir son:

  1. Extraer archivos con apktool.

  2. Renombrar directorio de trabajo y nombre de archivo apk generado.

  3. Modificar nombre del paquete en el manifest y hacer la aplicación debuggable para poder inspeccionar los logs mediante logcat. También modificar el nombre de la aplicación en los recursos para no confundirla si se tiene la original instalada.

    Los cambios anteriores están resumidos en el archivo 01-change-app-name.patch.

  4. Modificar el código de la aplicación.

    El cambio está resumido en el archivo 02-show-log-message.patch.

  5. Construir el apk correspondiente a la aplicación modificada.

  6. Crear debug keystore y firmar la aplicación.

  7. Instalar la aplicación, ejecutar utilizando am y ver la salida de logcat.

Se creó el script modify.sh que realiza los pasos 1. a 6. Para el paso 7. se utilizan los comandos:

$ adb logcat -c
$ adb install uy.jumapico.brou.token/dist/uy.jumapico.brou.token-signed.apk
Performing Streamed Install
Success
$ adb shell am start -D -n uy.jumapico.brou.token/uy.com.brou.token.MainActivity
$ adb logcat 2>&1 | tee logcat.out

Apendice - generación de código

Mientras no investigue como cargar el seed para generar el OTP con KeePassXC es posible utilizar la aplicación javascript de esta sección:

Code: