martes, 22 de agosto de 2017

peligro movil

Alrededor de 500 apps de android estarían robando datos de usuarios


Muchos usuarios conocieron lo que pasó con Droidjack que dejo de ser legal para convertirse en una app criminal y es que hacia muchas cosas "malas"(sin autorización) y empezó a ser tan buena en lo que hacia que se empezó a ofertar a 210 euros en su página puesto que daba los resultados que se ofrecían, lo curioso es que se insertaba en apps legales y de otros proveedores haciendose pasar por app legítima. sin embargo tras los esfuerzos de desarrolladores y nuevas plataformas android se habia inutilizado en gran medida el droidjack. pero recientemente se ha encontrado un kit de software de publicidad integrado en muchas aplicaciones legítimas de Android. Su tarea consiste en robar datos de usuarios y enviarlos a una empresa china. En total ha sido encontrado en más de 500 aplicaciones de este sistema operativo y que estaban en Google Play disponibles para su descarga. Ha sido desarrollado por la empresa china Igexin y ha sido descargado más de 100 millones de veces.

Apps en riesgo

La investigación comenzó tras ciertas solicitudes sospechosas. Los investigadores aseguran que comenzaron su trabajo después de darse cuenta de que estaban descargando muestras de malware conocidos en teléfonos limpios después de que éstos hicieran una petición al servidor de Igexin.
Tras meses de investigación, investigadores de la firma móvil de seguridad Lookout descubrieron que los desarrolladores de Igexin estaban usando funciones legítimas de SDK para enviar comandos maliciosos a aplicaciones
Según los permisos que las aplicaciones legítimas recibieron de los usuarios durante la instalación, Lookout dice que observó que el SDK recopilaba todo tipo de datos de los dispositivos de los usuarios. Especialmente recopilaban los registros de llamadas.
Además, el SDK también descargó de forma forzosa y ejecutó el código contenido en grandes archivos cifrados. Este código ayudó al comportamiento malicioso.

no más descargas

Los investigadores describieron las aplicaciones que contenían este malware y se pusieron en contacto con Google y los desarrolladores. Desactivaron las aplicaciones maliciosas para que no se pudieran descargar de Google Play hasta que los desarrolladores de estos programas hubieran actualizado las versiones y fueran seguras para los usuarios.
Los expertos de Lookout no mencionaron los nombres de las aplicaciones que incluían el SDK de Igexin, ya que no consideraban que esto fuera culpa suya. Sin embargo, proporcionaron una lista genérica de aplicaciones donde encontraron el SDK de Igexin.
Esta lista incluye juegos orientados a adolescentes, aplicaciones para conocer el tiempo, radio por Internet, editores de imágenes y otras aplicaciones relacionadas con la educación, la salud, gimnasio, viajes o de cámara de vídeo. Algunas de estas aplicaciones por sí solas fueron descargadas varios cientos de miles de veces.

Recordemos

Un hecho parecido ocurrió a finales del pasado año. En ese momento, los investigadores de Kryptowire descubrieron que Adups, un desarrollador de firmware chino, había incluido código malicioso en el firmware que entregaron a los fabricantes de teléfonos Android.
El código malicioso recogió un gran número de detalles de usuario y envió los datos a servidores en China. Varias marcas de dispositivos Android de bajo coste se vieron afectadas, principalmente BLU, pero fueron otras.
A principios de este mes, Adups dijo que había detenido el comportamiento de recopilación de datos, pero los expertos no estuvieron de acuerdo.
Estamos por tanto ante una nueva vulnerabilidad para los dispositivos Android y las aplicaciones que podemos encontrar para descargar. Como solemos recomendar siempre, lo mejor es mantener nuestros dispositivos seguros. Tenerlos actualizados y con software que nos permita hacer frente a posibles amenazas es importante.

miércoles, 22 de febrero de 2017

Last Pass

Obteniendo cuentas cifradas con análisis forense de memoria. LastPass

 

Descripción


Esta publicación se me ocurrió debido a que esta semana leí la noticia PSA: LastPass Does Not Encrypt Everything In Your Vault básicamente LastPass no está cifrando todos los datos del usuario.
LastPass es una de las plataformas más utilizadas para almacenar nuestras cuentas de usuario y contraseñas y poder acceder a ellas siempre que lo necesitemos.
LastPass guarda con un cifrado AES de 256 bits toda la información más sensible de sus usuarios, como sus nombres, el usuario y la contraseña de una página web, la dirección URL de la página web asociada a dichos datos no se guarda de forma cifrada.



En la imagen anterior se puede visualizar que el parámetro URL está cifrado de forma diferente que los parámetros anteriores a él, esto es debido a que esta pasado por hexadecimal si se utiliza un conversor se podrá ver en texto plano la URL.


Básicamente de esto se trata la investigación...

Para lograr obtener algo “hacer hack” no necesariamente debes ir en contra y romper puedes hacer bypass y en este caso le haremos honor al título de esta publicación utilizaremos el análisis forense para obtener todas las cuentas y contraseñas del usuario.

¿Pensamos?
En algún momento toda esta data debe ser descifrada, debe mostrarse tal cual es para poder ser tratada, pues aquí viene una de las cosas que más me gusta jugar y es investigar la memoria y descubrir que pasa en ella como en la publicación Hackeando la memoria de Windows en tiempo real (POC)

Si estas en una investigación y en el equipo te encuentras con el siguiente directorio:
C:\Users\El_Usuario\AppData\Local\Google\Chrome\User Data\Default\Extensions\hdokiejnpimakedhajhdlcegeplioahd
Debes saber que está presente LastPass en Chrome y si el equipo aún se encuentra encendido debemos hacer un dump de memoria *.raw/*.mem como ya se ha realizado en otras publicaciones.

Para este análisis utilizaremos Volatility y el siguiente plugin de Kevin Breen que utiliza reglas de Yara para poder llevar a cabo la tarea.
# Donated under Volatility Foundation, Inc. Individual Contributor Licensing Agreement
# LastPass - Recover memory resident account information.
# Author: Kevin Breen
# Thanks to the guide on http://www.ghettoforensics.com/2013/10/dumping-malware-configuration-data-from.html

import volatility.plugins.taskmods as taskmods
import volatility.win32.tasks as tasks
import volatility.utils as utils
import volatility.debug as debug
import volatility.plugins.malware.malfind as malfind
import volatility.conf as conf
import re
import json
import string

try:
    import yara
    has_yara = True
except ImportError:
    has_yara = False


signatures = {
    'lastpass_struct_a': 'rule lastpass_strcuta {strings: $a = /{"reqinfo":.*"lplanguage":""}/ condition: $a}\n'
                         'rule lastpass_strcutb {strings: $a = /"tld":".*?","unencryptedUsername":".*?","realmmatch"/ condition: $a}\n'
                         'rule lastpass_strcutc {strings: $a = /{"cmd":"save"(.*?)"tld":"(.*?)"}/ condition: $a}\n'
                         'rule lastpass_strcutd {strings: $a = /"realurl":"(.*?)"domains":"(.*?)"/ condition: $a}\n'
                         'rule lastpass_strcute {strings: $a = /{"cmd":"save"(.*?)"formdata"(.*?)}/ condition: $a}\n'
                         'rule lastpass_priv1 {strings: $a = /LastPassPrivateKey<(.*?)>LastPassPrivateKey/ condition: $a}'
}

config = conf.ConfObject()
config.add_option('CONFSIZE', short_option='C', default=4000,
                           help ='Config data size',
                           action ='store', type='int')
config.add_option('YARAOFFSET', short_option='Y', default=0,
                           help ='YARA start offset',
                           action ='store', type='int')

class LastPass(taskmods.PSList):
    """ Extract lastpass data from process. """

    def calculate(self):
        """ Required: Runs YARA search to find hits """
        if not has_yara:
            debug.error('Yara must be installed for this plugin')

        addr_space = utils.load_as(self._config)
        rules = yara.compile(sources = signatures)
        for task in self.filter_tasks(tasks.pslist(addr_space)):
            if not task.ImageFileName.lower() in ['chrome.exe', 'firefox.exe', 'iexplore.exe']:
                continue
            scanner = malfind.VadYaraScanner(task=task, rules=rules)
            for hit, address in scanner.scan():
                yield task, address

    def string_clean_hex(self, line):
        line = str(line)
        new_line = ''
        for c in line:
            if c in string.printable:
                new_line += c
            else:
                new_line += '\\x' + c.encode('hex')
        return new_line


    def clean_json(self, raw_data):
        # We deliberately pull in too much data to make sure we get it all.
        # Now parse it out again

        if raw_data.startswith('LastPassPrivate'):
            pattern = 'LastPassPrivateKey<(.*?)>LastPassPrivateKey'
            key_data = re.search(pattern, raw_data).group(0)
            if not any(substring in key_data for substring in ['"+a+"', 'indexOf']):
                return self.string_clean_hex(key_data)

        else:

            if raw_data.startswith("{\"cmd"):
                if 'formdata' in raw_data:
                    pattern = '{"cmd":"save"(.*?)"formdata"(.*?)}'
                    val_type = 'mixedform'
                else:
                    pattern = '{"cmd":"save"(.*?)"tld":"(.*?)"}'
                    val_type = 'mixed'

            elif raw_data.startswith("\"realurl"):
                pattern = '"realurl":"(.*?)"domains":"(.*?)"'
                val_type = 'mixed'

            elif raw_data.startswith("\"tld"):
                pattern = '"tld":".*?","unencryptedUsername":".*?","realmmatch"'
                val_type = 'username'

            elif raw_data.startswith('{"reqinfo"'):
                pattern = '{"reqinfo":.*?"lplanguage":""}'
                val_type = 'password'
            else:
                pass

            match = re.search(pattern, raw_data)
            real_data = self.string_clean_hex(match.group(0))

            if val_type == 'username':
                vars = real_data.split(',')
                tld = vars[0].split(':')[1].strip('"')
                username = vars[1].split(':')[1].strip('"')
                return {'type': 'username', 'username': username, 'tld': tld}

            elif val_type == 'mixedform':
                if '"tld":"' in real_data:
                    # Try to parse as json
                    try:
                        json_data = json.loads(real_data)
                        tld = json_data['tld']
                        password = json_data['password']
                        username = json_data['username']
                        clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password}
                    except ValueError:
                        # Json fails manual parse
                        tld = re.search('"tld":"(.*?)"', real_data)
                        if not tld:
                            tld = re.search('"domains":"(.*?)"', real_data)
                        if tld:
                            tld = tld.group(0).split(':')[-1].strip('"')
                        else:
                            tld = 'unknown'
                        try:
                            password = re.search('"password":"(.*?)"', real_data).group(0).split(':')[-1].strip('"')
                        except:
                            password = 'Unknown'
                        try:
                            username = re.search('"username":"(.*?)"', real_data).group(0).split(':')[-1].strip('"')
                        except:
                            username = 'Unknown'
                        clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password}

                else:
                    tld = re.search('"url":"(.*?)"', real_data).group(0).split(':')[-1].strip('"')
                    username = re.search('login(.*?)text', real_data).group(0).split('\\t')[1]
                    password = re.search('password(.*?)password', real_data).group(0).split('\\t')[1]
                    clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password}

                return clean_data

            elif val_type == 'mixed':
                # Try to parse as json
                try:
                    json_data = json.loads(real_data)
                    tld = json_data['tld']
                    password = json_data['password']
                    username = json_data['username']
                    clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password}
                except ValueError:
                    # Json fails manual parse
                    tld = re.search('"tld":"(.*?)"', real_data)
                    if not tld:
                        tld = re.search('"domains":"(.*?)"', real_data)

                    if tld:
                        tld = tld.group(0).split(':')[-1].strip('"')
                    else:
                        tld = 'unknown'

                    try:
                        password = re.search('"password":"(.*?)"', real_data).group(0).split(':')[-1].strip('"')
                    except:
                        password = 'Unknown'
                    try:
                        username = re.search('"username":"(.*?)"', real_data).group(0).split(':')[-1].strip('"')
                    except:
                        username = 'Unknown'

                    clean_data = {'type': 'mixed', 'username': username, 'tld': tld, 'password': password}
            else:
                # Try to parse as json
                try:
                    json_data = json.loads(real_data)
                    tld = json_data['domains']
                    password = json_data['value']
                    clean_data = {'type': 'password', 'password': password, 'tld': tld}
                except ValueError:
                    # Json fails manual parse
                    password = re.search('"value":"(.*?)"', real_data).group(0).split(':')[-1].strip('"')
                    tld = re.search('"domains":"(.*?)"', real_data).group(0).split(':')[-1].strip('"')
                    clean_data = {'type': 'password', 'password': password, 'tld': tld}

            return clean_data

    def render_text(self, outfd, data):
        """ Required: Parse data and display """
        outfd.write("Searching for LastPass Signatures\n")

        rules = yara.compile(sources=signatures)

        results = {}
        priv_keys = []

        for task, address in data:  # iterate the yield values from calculate()
            outfd.write('Found pattern in Process: {0} ({1})\n'.format(task.ImageFileName, task.UniqueProcessId))
            proc_addr_space = task.get_process_address_space()
            raw_data = proc_addr_space.read(address + self._config.YARAOFFSET, self._config.CONFSIZE)
            if raw_data:
                clean_data = self.clean_json(raw_data)
                if not clean_data:
                    continue
                if 'PrivateKey' in clean_data:
                    priv_keys.append(clean_data)
                else:
                    # If we already created the dict
                    if clean_data['tld'] in results:
                        if 'username' in clean_data:
                            if results[clean_data['tld']]['username'] == 'Unknown':
                                results[clean_data['tld']]['username'] = clean_data['username']
                        if 'password' in clean_data:
                            if results[clean_data['tld']]['password'] == 'Unknown':
                                results[clean_data['tld']]['password'] = clean_data['password']
                    # Else create the dict
                    else:
                        if 'username' in clean_data:
                            username = clean_data['username']
                        else:
                            username = 'Unknown'
                        if 'password' in clean_data:
                            password = clean_data['password']
                        else:
                            password = 'Unknown'
                        results[clean_data['tld']] = {'username': username, 'password': password}

        for k, v in results.iteritems():
            outfd.write("\nFound LastPass Entry for {0}\n".format(k))
            outfd.write('UserName: {0}\n'.format(v['username']))
            outfd.write('Pasword: {0}\n'.format(v['password']))
        outfd.write('\n')

        for key in priv_keys:
            outfd.write('\nFound Private Key\n')
            outfd.write('{0}\n'.format(key))
El comando que utilizaremos es el siguiente:
volatility --plugins='/root/VolPlugins' --profile=Win7SP1x64 -f '/root/Escritorio/POC_CASE01 /TESTING-PC-20170125-023936.raw' lastpass
Donde invocamos a volatility, le indicamos la ruta del plugin, indicamos el perfil del OS, la ruta en donde se encuentra el volcado y finalmente invocamos el plugin lastpass.

Después de algunos minutos obtendremos las cuentas del usuario almacenadas con el Plugin LastPass en Chrome Browser

 

jueves, 17 de noviembre de 2016

 Ingeniería del rendimiento

¿Cómo anticiparse a que los problemas de escalabilidad ocurran en producción?



Por fin hemos pasado a producción, la aplicación ha terminado de ser desplegada en la plataforma accesible a los usuarios y podemos dar por terminada la iteración actual llena de crisis, nervios y sobreesfuerzos; que han culminado en un fin de semana de trabajo continuado.

Disfrutamos de un breve y merecido descanso antes de volver temprano a la oficina para arrancar la siguiente iteración, planificando las tareas a realizar y coordinando los equipos de desarrollo, UX y calidad.

Pero, la primera llamada a media mañana, ha despertado la pesadilla que se ha ido gestando según el número de accesos ascendía siguiendo al sol.

Y ahora – cuando los problemas se están escalando hacia Dirección como la espuma de un mal cava – un sistema sobrecargado ha terminado por presentar un rendimiento inutilizablemente lento.

Las pruebas como garantes del rendimiento

Cuando estoy describiendo el concepto de Ingeniería del rendimiento, una vez más, no estoy hablando de conceptos nuevos o recientes. Ya en 2004 Microsoft, el equipo de Patterns & Practices, publicó un detallado documento sobre este tipo de Ingeniería, que sería actualizado en el 2012 con una versión Agile.

Básicamente lo que se documenta es la necesidad de realizar una programación orientada al rendimiento, en donde se integre este tipo de pruebas en el ciclo de vida de las aplicaciones, asegurando que - en cada una de las etapas - el sistema funcione dentro del rango de prestaciones esperadas para que su funcionamiento sea el correcto.

Siendo el objetivo final de estos patrones y prácticas (procesos) la transformación desde una aproximación reactiva a otra proactiva, en relación al rendimiento de la plataforma de hardware y software sobre la que funcionan las aplicaciones.

APROXIMACIÓN
CARACTERÍSTICAS
Reactivo
  • Generalmente no se puede optimizar un sistema que ha sido diseñado erróneamente, a diferencia de los correctamente construidos desde el principio.
  • Se soporta un coste creciente del hardware necesario.
  • Se soporta un coste creciente de propiedad.
Proactivo
  • Se conoce dónde está el foco para reforzar la optimización.
  • Se reducen los costes al reducir las necesidades de optimizar y rediseñar.
  • El hardware necesario es de menos coste, y requiere de actualizaciones menores.
  • Los costes operacionales totales se reducen.
Los test unitarios, de integración, de sistemas o, incluso, los funcionales del Interfaz gráfico, están orientados a asegurar el funcionamiento correcto del código y de la aplicación con respecto a los requisitos funcionales.

Sin embargo, la Ingeniería del rendimiento se refiere a la necesidad de tener planes de prueba, lo más automatizados posibles, centrados en los requisitos no funcionales de la plataforma en donde se despliega y funciona el software.

Eso significa que estas pruebas están orientadas y dirigidas por el área de negocio. Quien realiza las priorizaciones de acuerdo a valores críticos del “business” como es el valor de los beneficios, la optimización de los costes o el impacto de los fallos del servicio.

Incluso puedo dar la vuelta a la tortilla, y utilizar los resultados de las baterías de pruebas como marco e indicador de la infraestructura necesaria para conseguir cumplir las expectativas. Definiendo el rendimiento de la aplicación primero y, luego, desarrollar el software orientado a cumplir el Plan de pruebas, para obtener unas prestaciones de ejecución determinadas.

Los cuatro test contra el apocalipsis

Como obliga cualquier proceso de construcción de software, lo primero que se necesita para definir el Plan de pruebas de rendimiento son: unos buenos requisitos. Con Criterios de Aceptación claros y bien detallados, que no dependan de interpretación y que – idealmente – puedan ser descritos de forma algorítmica con valores numéricos.

Por ejemplo: “Siendo un tipo de usuario determinado, el sistema debe devolver la respuesta de una búsqueda de clientes en menos de 5 segundos cuando están realizando búsquedas 250 usuarios de forma concurrente (no simultanea) en menos de 1 minuto. No superando los 10 segundos de respuesta cuando haya cargas puntuales de hasta 500 usuarios concurrentes.”

A estos requisitos, habría que añadirle aquellos que sean más técnicos, dependientes del hardware o de otras plataformas que deban ser integradas, y que se irán descubriendo durante todo el ciclo de vida de la aplicación.

En definitiva, deberé de construir una Pila de pruebas orientada a los cuatro tipos de test – y sus variantes – que se utilizarán para asegurar las prestaciones.

Test de carga

Los Test de Carga se llevan a cabo para asegurar que la aplicación cumple los objetivos de rendimiento esperados.

Con esta prueba se obtienen métricas sobre los tiempos de respuesta, las tasas de rendimiento y el consumo de recursos. Pudiendo señalar los puntos en donde empieza a fallar la aplicación, si aún no se ha llegado a los límites de sobrecarga.

Un subconjunto de este tipo de test son las pruebas de resistencia, en donde se sostiene durante un largo periodo de tiempo al uso normal esperado, y se comprueba el comportamiento del sistema.

Así se puede obtener indicadores como el “Tiempo entre fallos”, el “Tiempo medio de fallo” o similares, que permiten realizar acciones preventivas en el futuro.

Test de estrés

El objetivo de estas pruebas es revelar errores y bloqueos en la aplicación cuando es llevada a situaciones de carga alta. Es decir, un gran número de transacciones, de transferencias, de peticiones, de cálculos, etc.

El sistema se estresa llevándolo a los valores máximos esperados en las operaciones en producción, y se mantiene una monitorización constante del hardware y el software para tomar métricas sobre el comportamiento del sistema tanto durante la prueba de carga como en la recuperación del mismo al relajar la prueba de estrés.

Especialmente durante aquellas, en donde se lleva el sistema a superar varias veces su capacidad teórica y esperada - incluso llegando al colapso de la plataforma - durante breves espacios de tiempo. Pudiendo observar y medir cómo se comporta el sistema en esas condiciones, cuales son los subsistemas que primero fallan, de qué forma y cómo se recupera al volver a valores nominales.
---------------------------------------------------------------------------
UN DESARROLLO SIN TEST DE RENDIMIENTO ES JUGARTE A LA RULETA RUSA CON TODO EL TRABAJO Y COSTE QUE HA REQUERIDO EL PONER LA APLICACIÓN EN PRODUCCIÓN

Test de capacidad

Como indica su nombre, estas pruebas están centradas en apoyar la planificación de capacidades del sistema.

Partiendo de la base actual, se incrementa la carga del sistema para hallar el punto en donde inicia la degradación del rendimiento. Viendo las métricas se puede realizar una planificación de crecimiento del sistema para poder absorber más carga de trabajo (por ejemplo, incrementar el número o potencia de los procesadores, aumentar el ancho de banda, incrementar la memoria RAM, etc).

También son imprescindibles para poder definir un plan de escalado horizontal o vertical y, con entornos virtual izados, poder hacer pruebas piloto para validar las diferentes hipótesis de crecimiento.

Test de rendimiento

Son aquellas pruebas que validan los Criterios de Aceptación de rendimiento que describen la capacidad de respuesta, velocidad, escalabilidad y estabilidad de la aplicación.

Estas pruebas identifican las discrepancias entre las expectativas de rendimiento y las realmente obtenidas, permitiendo a negocio el tomar decisiones estratégicas sobre las plataformas y que se pueda planificar las acciones necesarias para optimizar las prestaciones de la solución.

Cuándo y cómo integrarlos



Muchos equipos de desarrollo tienen la impresión de que las pruebas de rendimiento se realizan cuando el código está estabilizado y durante los procesos de QA que desembocan en el despliegue. Incluso, en que su sitio natural es más cerca de producción que en construcción.

Pero eso es un error; las pruebas de rendimiento deben estar integradas desde el primer momento en el proceso de construcción de la construcción del software. Incluso antes.

Cuando se está realizando la toma de requisitos o incepción, antes de empezar a tirar una sola línea, durante la redacción de la Pila de Producto, se deben definir las historias, objetivos, y requisitos de rendimiento. Incluso se pueden diseñar los pilotos necesarios para validar las propias pruebas y las medidas que se quieren obtener.

Una vez arrancadas las iteraciones, en conjunto con el resto del equipo, se compromete las historias y requisitos que se van a desarrollar, se construyen las pruebas y los escenarios necesarios, y se realizan los test que puedan aportar valor a la propia construcción.

Durante el proceso de Despliegue se lanzan las pruebas de Aceptación, ten en cuenta que no todos los test están orientados a las validaciones de negocio como hemos visto con anterioridad, y se documentan los resultados . Por último, como momento crítico de toda iteración Agile, se incluye una retrospectiva de rendimiento dentro del proceso general de Mejora Continua.

Como puedes ver, es crítica la integración completa del equipo de test con el resto de los miembros del equipo. Ya que, solamente así, se podrá realizar un desarrollo que tenga el rendimiento como una de sus prioridades, evitando un precioso código de alta calidad que esté por debajo de la eficiencia exigida.

No hay balas de plata


Como bien sabe cualquiera que haya trabajado en desarrollo de software, no existen soluciones únicas que valgan para todas las ocasiones. Y los test de rendimiento también tienen sus ventajas e inconvenientes.

Ventajas

  • Incremento de los ingresos al funcionar correctamente el sistema, procesando las transacciones dentro del plazo comprometido.
  • Evitar que un problema de rendimiento implique el tener que desechar parte del sistema para ser reconstruido.
  • Evitar que un problema de rendimiento implique retrasos en la puesta en producción de una pieza de negocio comprometida.
  • Evitar innecesarios costes de adquisición de hardware adicional, a causa de problemas de escalabilidad o rendimiento.
  • Evitar el incremento de costes de mantener un software en producción a causa de problemas de rendimiento creciente.
  • Reducir el coste de operaciones para la gestión de problemas en el sistema originados por un rendimiento por debajo de las expectativas.
  • Identificar cuellos de botella, en el hardware y software, que puedan tener impacto en el rendimiento o escalabilidad a un futuro.

Pero puntualizando a este frio listado, siempre me sorprende la cantidad de errores que suceden cuando sometemos a un código “sólido” - desde el punto de vista de codificación- a pruebas de estrés. Se lanzan los test y van surgiendo errores por muchas causas, pero que empiezan casi siempre por la saturación del pipeline de las comunicaciones vía hhttp request al servidor.

Entre los fallos que te vas a encontrar, puede haber problemas con las sesiones de sql o fugas de memoria (memory leaks) a causa de no utilizar un close o un dispose a tiempo. Porque hoy en día nos apoyamos demasiado en la “magia” del sempiterno recolector de basura.

Otro será un condicional ineficaz, un cuello de botella en un servicio que produce timeout en los casos de carga máxima; un sistema que no se recupera al ser llevado a límite, que no libera la memoria o el procesador y que tiene un límite temporal demasiado estrecho antes de llevar a la infraestructura a sus límites o a un funcionamiento degradado.

LA DEUDA TÉCNICA PRODUCE LOS MAYORES PROBLEMAS DE RENDIMIENTO, Y ES MUY COMPLEJO DE CORREGIR SIN MÉTRICAS

Por ello, para mí, la gran ventaja de aplicar pruebas de rendimiento lo antes posible es que se prueban servicios, módulos y plataformas completas. No solamente el hardware, sino también el software, la arquitectura, la escalabilidad y la visión global del desempeño de la aplicación.

Y me aseguro de que todo el esfuerzo y trabajo realizado sea utilizable por los usuarios.

Inconvenientes

El probar el sistema completo, llevándolo a sus límites, implica que requieren un trabajo extra en su planificación, construcción, ejecución y análisis de los resultados.

Además, por la taxonomía de la ejecución, se requiere una gran cantidad de tiempo y la presencia de los desarrolladores. Este tipo de test es muy complicado, o imposible, de integrar en un proceso de integración continua, más allá de los de validación.

De hecho, este tipo de pruebas son parte de la “pieza” de monitorización de las prácticas DevOps, orientadas a la salud de las aplicaciones. Y que se merecen, por si solas, un artículo.

Siendo el mayor inconveniente, la dificultad de transmitir al equipo de negocio y de gestión del proyecto el valor que aporta estas prácticas de aseguramiento de la calidad, y que aprueben el incremento del coste asociado durante la construcción y despliegue a cambio de todas las ventajas referenciadas anteriormente.

Más información













 Ingeniería del rendimiento

¿Cómo anticiparse a que los problemas de escalabilidad ocurran en producción?



Por fin hemos pasado a producción, la aplicación ha terminado de ser desplegada en la plataforma accesible a los usuarios y podemos dar por terminada la iteración actual llena de crisis, nervios y sobreesfuerzos; que han culminado en un fin de semana de trabajo continuado.

Disfrutamos de un breve y merecido descanso antes de volver temprano a la oficina para arrancar la siguiente iteración, planificando las tareas a realizar y coordinando los equipos de desarrollo, UX y calidad.

Pero, la primera llamada a media mañana, ha despertado la pesadilla que se ha ido gestando según el número de accesos ascendía siguiendo al sol.

Y ahora – cuando los problemas se están escalando hacia Dirección como la espuma de un mal cava – un sistema sobrecargado ha terminado por presentar un rendimiento inutilizablemente lento.

Las pruebas como garantes del rendimiento

Cuando estoy describiendo el concepto de Ingeniería del rendimiento, una vez más, no estoy hablando de conceptos nuevos o recientes. Ya en 2004 Microsoft, el equipo de Patterns & Practices, publicó un detallado documento sobre este tipo de Ingeniería, que sería actualizado en el 2012 con una versión Agile.

Básicamente lo que se documenta es la necesidad de realizar una programación orientada al rendimiento, en donde se integre este tipo de pruebas en el ciclo de vida de las aplicaciones, asegurando que - en cada una de las etapas - el sistema funcione dentro del rango de prestaciones esperadas para que su funcionamiento sea el correcto.

Siendo el objetivo final de estos patrones y prácticas (procesos) la transformación desde una aproximación reactiva a otra proactiva, en relación al rendimiento de la plataforma de hardware y software sobre la que funcionan las aplicaciones.

APROXIMACIÓN
CARACTERÍSTICAS
Reactivo
  • Generalmente no se puede optimizar un sistema que ha sido diseñado erróneamente, a diferencia de los correctamente construidos desde el principio.
  • Se soporta un coste creciente del hardware necesario.
  • Se soporta un coste creciente de propiedad.
Proactivo
  • Se conoce dónde está el foco para reforzar la optimización.
  • Se reducen los costes al reducir las necesidades de optimizar y rediseñar.
  • El hardware necesario es de menos coste, y requiere de actualizaciones menores.
  • Los costes operacionales totales se reducen.
Los test unitarios, de integración, de sistemas o, incluso, los funcionales del Interfaz gráfico, están orientados a asegurar el funcionamiento correcto del código y de la aplicación con respecto a los requisitos funcionales.

Sin embargo, la Ingeniería del rendimiento se refiere a la necesidad de tener planes de prueba, lo más automatizados posibles, centrados en los requisitos no funcionales de la plataforma en donde se despliega y funciona el software.

Eso significa que estas pruebas están orientadas y dirigidas por el área de negocio. Quien realiza las priorizaciones de acuerdo a valores críticos del “business” como es el valor de los beneficios, la optimización de los costes o el impacto de los fallos del servicio.

Incluso puedo dar la vuelta a la tortilla, y utilizar los resultados de las baterías de pruebas como marco e indicador de la infraestructura necesaria para conseguir cumplir las expectativas. Definiendo el rendimiento de la aplicación primero y, luego, desarrollar el software orientado a cumplir el Plan de pruebas, para obtener unas prestaciones de ejecución determinadas.

Los cuatro test contra el apocalipsis

Como obliga cualquier proceso de construcción de software, lo primero que se necesita para definir el Plan de pruebas de rendimiento son: unos buenos requisitos. Con Criterios de Aceptación claros y bien detallados, que no dependan de interpretación y que – idealmente – puedan ser descritos de forma algorítmica con valores numéricos.

Por ejemplo: “Siendo un tipo de usuario determinado, el sistema debe devolver la respuesta de una búsqueda de clientes en menos de 5 segundos cuando están realizando búsquedas 250 usuarios de forma concurrente (no simultanea) en menos de 1 minuto. No superando los 10 segundos de respuesta cuando haya cargas puntuales de hasta 500 usuarios concurrentes.”

A estos requisitos, habría que añadirle aquellos que sean más técnicos, dependientes del hardware o de otras plataformas que deban ser integradas, y que se irán descubriendo durante todo el ciclo de vida de la aplicación.

En definitiva, deberé de construir una Pila de pruebas orientada a los cuatro tipos de test – y sus variantes – que se utilizarán para asegurar las prestaciones.

Test de carga

Los Test de Carga se llevan a cabo para asegurar que la aplicación cumple los objetivos de rendimiento esperados.

Con esta prueba se obtienen métricas sobre los tiempos de respuesta, las tasas de rendimiento y el consumo de recursos. Pudiendo señalar los puntos en donde empieza a fallar la aplicación, si aún no se ha llegado a los límites de sobrecarga.

Un subconjunto de este tipo de test son las pruebas de resistencia, en donde se sostiene durante un largo periodo de tiempo al uso normal esperado, y se comprueba el comportamiento del sistema.

Así se puede obtener indicadores como el “Tiempo entre fallos”, el “Tiempo medio de fallo” o similares, que permiten realizar acciones preventivas en el futuro.

Test de estrés

El objetivo de estas pruebas es revelar errores y bloqueos en la aplicación cuando es llevada a situaciones de carga alta. Es decir, un gran número de transacciones, de transferencias, de peticiones, de cálculos, etc.

El sistema se estresa llevándolo a los valores máximos esperados en las operaciones en producción, y se mantiene una monitorización constante del hardware y el software para tomar métricas sobre el comportamiento del sistema tanto durante la prueba de carga como en la recuperación del mismo al relajar la prueba de estrés.

Especialmente durante aquellas, en donde se lleva el sistema a superar varias veces su capacidad teórica y esperada - incluso llegando al colapso de la plataforma - durante breves espacios de tiempo. Pudiendo observar y medir cómo se comporta el sistema en esas condiciones, cuales son los subsistemas que primero fallan, de qué forma y cómo se recupera al volver a valores nominales.
---------------------------------------------------------------------------
UN DESARROLLO SIN TEST DE RENDIMIENTO ES JUGARTE A LA RULETA RUSA CON TODO EL TRABAJO Y COSTE QUE HA REQUERIDO EL PONER LA APLICACIÓN EN PRODUCCIÓN

Test de capacidad

Como indica su nombre, estas pruebas están centradas en apoyar la planificación de capacidades del sistema.

Partiendo de la base actual, se incrementa la carga del sistema para hallar el punto en donde inicia la degradación del rendimiento. Viendo las métricas se puede realizar una planificación de crecimiento del sistema para poder absorber más carga de trabajo (por ejemplo, incrementar el número o potencia de los procesadores, aumentar el ancho de banda, incrementar la memoria RAM, etc).

También son imprescindibles para poder definir un plan de escalado horizontal o vertical y, con entornos virtual izados, poder hacer pruebas piloto para validar las diferentes hipótesis de crecimiento.

Test de rendimiento

Son aquellas pruebas que validan los Criterios de Aceptación de rendimiento que describen la capacidad de respuesta, velocidad, escalabilidad y estabilidad de la aplicación.

Estas pruebas identifican las discrepancias entre las expectativas de rendimiento y las realmente obtenidas, permitiendo a negocio el tomar decisiones estratégicas sobre las plataformas y que se pueda planificar las acciones necesarias para optimizar las prestaciones de la solución.

Cuándo y cómo integrarlos



Muchos equipos de desarrollo tienen la impresión de que las pruebas de rendimiento se realizan cuando el código está estabilizado y durante los procesos de QA que desembocan en el despliegue. Incluso, en que su sitio natural es más cerca de producción que en construcción.

Pero eso es un error; las pruebas de rendimiento deben estar integradas desde el primer momento en el proceso de construcción de la construcción del software. Incluso antes.

Cuando se está realizando la toma de requisitos o incepción, antes de empezar a tirar una sola línea, durante la redacción de la Pila de Producto, se deben definir las historias, objetivos, y requisitos de rendimiento. Incluso se pueden diseñar los pilotos necesarios para validar las propias pruebas y las medidas que se quieren obtener.

Una vez arrancadas las iteraciones, en conjunto con el resto del equipo, se compromete las historias y requisitos que se van a desarrollar, se construyen las pruebas y los escenarios necesarios, y se realizan los test que puedan aportar valor a la propia construcción.

Durante el proceso de Despliegue se lanzan las pruebas de Aceptación, ten en cuenta que no todos los test están orientados a las validaciones de negocio como hemos visto con anterioridad, y se documentan los resultados . Por último, como momento crítico de toda iteración Agile, se incluye una retrospectiva de rendimiento dentro del proceso general de Mejora Continua.

Como puedes ver, es crítica la integración completa del equipo de test con el resto de los miembros del equipo. Ya que, solamente así, se podrá realizar un desarrollo que tenga el rendimiento como una de sus prioridades, evitando un precioso código de alta calidad que esté por debajo de la eficiencia exigida.

No hay balas de plata


Como bien sabe cualquiera que haya trabajado en desarrollo de software, no existen soluciones únicas que valgan para todas las ocasiones. Y los test de rendimiento también tienen sus ventajas e inconvenientes.

Ventajas

  • Incremento de los ingresos al funcionar correctamente el sistema, procesando las transacciones dentro del plazo comprometido.
  • Evitar que un problema de rendimiento implique el tener que desechar parte del sistema para ser reconstruido.
  • Evitar que un problema de rendimiento implique retrasos en la puesta en producción de una pieza de negocio comprometida.
  • Evitar innecesarios costes de adquisición de hardware adicional, a causa de problemas de escalabilidad o rendimiento.
  • Evitar el incremento de costes de mantener un software en producción a causa de problemas de rendimiento creciente.
  • Reducir el coste de operaciones para la gestión de problemas en el sistema originados por un rendimiento por debajo de las expectativas.
  • Identificar cuellos de botella, en el hardware y software, que puedan tener impacto en el rendimiento o escalabilidad a un futuro.

Pero puntualizando a este frio listado, siempre me sorprende la cantidad de errores que suceden cuando sometemos a un código “sólido” - desde el punto de vista de codificación- a pruebas de estrés. Se lanzan los test y van surgiendo errores por muchas causas, pero que empiezan casi siempre por la saturación del pipeline de las comunicaciones vía hhttp request al servidor.

Entre los fallos que te vas a encontrar, puede haber problemas con las sesiones de sql o fugas de memoria (memory leaks) a causa de no utilizar un close o un dispose a tiempo. Porque hoy en día nos apoyamos demasiado en la “magia” del sempiterno recolector de basura.

Otro será un condicional ineficaz, un cuello de botella en un servicio que produce timeout en los casos de carga máxima; un sistema que no se recupera al ser llevado a límite, que no libera la memoria o el procesador y que tiene un límite temporal demasiado estrecho antes de llevar a la infraestructura a sus límites o a un funcionamiento degradado.

LA DEUDA TÉCNICA PRODUCE LOS MAYORES PROBLEMAS DE RENDIMIENTO, Y ES MUY COMPLEJO DE CORREGIR SIN MÉTRICAS

Por ello, para mí, la gran ventaja de aplicar pruebas de rendimiento lo antes posible es que se prueban servicios, módulos y plataformas completas. No solamente el hardware, sino también el software, la arquitectura, la escalabilidad y la visión global del desempeño de la aplicación.

Y me aseguro de que todo el esfuerzo y trabajo realizado sea utilizable por los usuarios.

Inconvenientes

El probar el sistema completo, llevándolo a sus límites, implica que requieren un trabajo extra en su planificación, construcción, ejecución y análisis de los resultados.

Además, por la taxonomía de la ejecución, se requiere una gran cantidad de tiempo y la presencia de los desarrolladores. Este tipo de test es muy complicado, o imposible, de integrar en un proceso de integración continua, más allá de los de validación.

De hecho, este tipo de pruebas son parte de la “pieza” de monitorización de las prácticas DevOps, orientadas a la salud de las aplicaciones. Y que se merecen, por si solas, un artículo.

Siendo el mayor inconveniente, la dificultad de transmitir al equipo de negocio y de gestión del proyecto el valor que aporta estas prácticas de aseguramiento de la calidad, y que aprueben el incremento del coste asociado durante la construcción y despliegue a cambio de todas las ventajas referenciadas anteriormente.

Más información