miércoles, 13 de julio de 2016

Siguiendo la pista a los spammers

Motivado por la charla de Defcon de este vídeo:

https://www.youtube.com/watch?v=ytDamqTjPwg

hice este script para hacer gráficas como la que se ve en el minuto 17. En esta charla explican cómo de forma manual categorizaron miles de correos para hacer esas gráficas, ya que el contenido era visual y no era fácil buscar texto en ellos de forma automática (aunque... ¿no podrían haber cogido la dirección de quien lo enviaba, y la fecha?)

Eso es lo que hace el siguiente programa precisamente, busca en la carpeta de spam de un correo usando SMTP y saca todos los correos existentes y hace gráficas de número de correos acumulados por fecha para cada uno de los distintos spammers. Es de destacar que a veces entran en la carpeta de spam correos legítimos, y estos también aparecerán en la gráfica.

Lo interesante de la charla de Defcon es que a partir de las pendientes de estas gráficas eran capaces de caracterizar la frecuencia con la que los bots de cada spammer enviaban correo basura.

Llevo probándolo poco tiempo, un mes, como para tener gráficas muy significativas, pero se ve una pendiente clara, por ejemplo en los correos enviados por facebook, lo que indica que tienen un bot que envía cada día correos con aproximadamente la misma frecuencia. Por eso llegan tantos correos de facebook sobre cosas que no tienen nada que ver con mi actividad.

El script en python cuenta con varias funciones que hacen tareas específicas, llamadas por una función principal. Por lo que se ve, sacar información de los correos en el servidor es lento y no hay forma de sacar únicamente la cabecera, hay que extraerla del mensaje completo. Por esto, el script crea un archivo donde guarda los remitentes y fechas.Hay dos funciones para extraer correo del servidor: la primera, read_server_fast() cuenta cuántos spams hay leídos previamente, y almacenados en el archivo de texto (cuyo nombre es el nombre de usuario de la cuenta de email en cuestión). Entonces empieza a recoger correos del servidor tomando como correo inicial aquél cuya id coincida con el número de correos en el archivo de texto +1.
La función read_server_slow() no hace falta a menos que se haya borrado manualmente algo del servidor. Lo que hace es comparar uno a uno los correos del archivo con cada uno del servidor, para decidir si lo añade o no a la lista. Pero esta función es mucho más lenta y en principio no hay que usarla.

Antes de usar el script hay que activar IMAP en nuestro servidor. En este ejemplo, se muestra cómo hacerlo en Gmail:

1- Primero se activa IMAP en la configuración de la cuenta:


2- Después hay que habilitar el acceso a la cuenta desde aplicaciones que no sean la web de Gmail. Esto se encuentra en https://www.google.com/settings/security/lesssecureapps



Una vez hecho esto, el script funciona. Hace falta instalar matplotlib para las gráficas. La variable show por defecto tiene valor False, y si se pone a True hace además un gráfico tipo "tarta" para ver quién ha enviado cuántos spams.

La variable crit fija un criterio para decidir si se representa o no un spammer en la gráfica en función del tiempo. Como se puede ver, muchos spammers solo envían un correo, así que tendremos muchos puntos individuales que liaran mucho todo, y la leyenda quedará enorme. Subiendo crit a 3, por ejemplo, se deja de ver a todos estos spammers ocasionales:
criterio crit=0. Se muestran todos los spammers



Criterio crit=3. Se filtran todos los que han enviado menos de 3 correos

Gráfico "tarta" de todos los spammers, cada uno con su contribución.

Por último, un test con el buzón principal, con correo más antiguo y numeroso. Aquí es totalmente evidente quién envía correo con un bot, de forma regular, más o menos frecuente:


A continuación el script, pensado para extraer spam de Gmail. Para otras cuentas, habrá que modificar los nombres de las carpetas del servidor.
IMPORTANTE: hay que añadir en las variables user y pwd, en la función principal, nuestras credenciales (entre comillas):

###########################################


import email, imaplib
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt
import numpy as np
import math

folder = "Todos"

days = {'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'}

def readfile():
    list_senders = []
    list_dates = []
    list_lines = []
    g = open('{}-{}.txt'.format(user,folder),'r')
    for i, line in enumerate(g.readlines()):
        list_lines.append(line)
    g.close()
    return list_lines

def read_server_fast():
        m = imaplib.IMAP4_SSL("imap.gmail.com")
        m.login(user,pwd)
        print(m.list())
        a = m.select("[Gmail]/"+folder)
        lis = m.list()
        lis1 = a
        existing_lines = readfile()   

        resp, items = m.search(None, "ALL")
        items = items[0].split()
        already_there = False
      
        print(items)
      
        g = open('{}-{}.txt'.format(user,folder),'a')
        for emailid in range(len(existing_lines)+1,len(items)):
                resp, data = m.fetch(emailid, "(RFC822)")
                email_body = data[0][1]
                mail = email.message_from_string(email_body)
      
                print emailid
                try:
                    sender = mail["From"].split('<')[1].split('>')[0]
                    date = mail["Date"]
                    print(sender, date)
                    g.write(sender + ';' + date + '\n')
                except:
                    g.write('error;error\n')
                  
        g.close()

def read_server_slow():
    m = imaplib.IMAP4_SSL("imap.gmail.com")
    m.login(user,pwd)
    a = m.select("[Gmail]/"+folder)
    lis = m.list()
    lis1 = a
    existing_lines = readfile()  

    resp, items = m.search(None, "ALL")
    items = items[0].split()
    print(items)
    already_there = False
    g = open('{}.txt'.format(user),'a')
    for emailid in items:
        resp, data = m.fetch(emailid, "(RFC822)")
        email_body = data[0][1]
        mail = email.message_from_string(email_body)
  
        print emailid
        sender = mail["From"].split('<')[1].split('>')[0]
        date = mail["Date"]
        print(sender, date)
        for i in range(len(existing_lines)):
            check = existing_lines[i].split(';')
            if (sender == check[0] and date == check[1].split('\n')[0]):
                already_there = True
                print "Already in file!"
                break
            else:
                already_there = False

        if already_there == False:
            g.write(sender + ';' + date + '\n')
    g.close()

def get_sen_dat():
    """Get senders and dates from lines in file and put them in arrays senders and dates
        The senders arrays contains the number of messages per sender.
        The dates dic contains the dates when a message from a certain sender (dates.keys are the senders
        ), with all the dates in a list for each sender"""
    senders = {}
    dates = {}
    f = open('{}-{}.txt'.format(user,folder),'r')
  
    for i, line in enumerate(f.readlines()):
        sender, date = line.split(';')
        if sender in senders.keys():
            senders[sender] += 1
        else:
            senders[sender] = 1
        if sender != "error":
            if sender in dates.keys():
                dates[sender].append(date)
        else:
            dates[sender] = [date]
                  
        if i == 0: #for calculating the day span and adjust the ticks in graph
            first_date = date
        last_date = date
      
  
    f.close()
    first = first_date.split(',')[1].split(' ')
    last = last_date.split(',')[1].split(' ')
    f = first[1] + ' ' + months_dic[first[2]] + ' ' + first[3]
    l = last[1] + ' ' + months_dic[last[2]] + ' ' + last[3]
    a = dt.datetime.strptime(f, '%d %m %Y')
    b = dt.datetime.strptime(l, '%d %m %Y')
    day_interval = (b - a).days
    print(day_interval)
    return (senders, dates, day_interval)

def pie_chart(senders, show = False):
    l_labels = []
    l_values = []
  
    for sender in senders.keys():
        #print (sender, senders[sender])
        l_labels.append(sender)
        l_values.append(senders[sender])
  
    if show == True:
        plt.pie(l_values, labels=l_labels)
        plt.show()
      
    return(l_values, l_labels)
  
def time_graph(dates, l_values, crit, day_interval):
    k=0
    for value in l_values:
        if value > crit:
            k += 1
  
    num_plots = len(dates.keys())
    colormap = plt.cm.gist_ncar
    plt.gca().set_color_cycle([colormap(i) for i in np.linspace(0, 0.9, k)])
    i = 0
    sender_address = []
    ax = plt.subplot(111)
  
    for sender in dates.keys():
        dates1 = []
        vals1 = []
        val = 0
        for date_list in dates[sender]:
            week_day = date_list.split()[0].strip(',')
            if week_day in days:
                day = date_list.split()[1]
                month = months_dic[date_list.split()[2]]
                year = date_list.split()[3]
                hours = date_list.split()[4]
            else:                          
                day = date_list.split()[0]
                month = months_dic[date_list.split()[1]]
                year = date_list.split()[2]
                hours = date_list.split()[3]
            date = month + ' ' + day + ' ' + year + ' ' + hours
            date_fmt = dt.datetime.strptime(date,'%m %d %Y %H:%M:%S')
            val += 1
            dates1.append(date_fmt)
            vals1.append(val)
            sender_address.append(sender)
        if(len(vals1) > crit):
            plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m/%d/%Y'))
            plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval = int(math.floor(day_interval * 0.2))))
            plt.plot(dates1,vals1,'-o',label=sender)
            plt.gcf().autofmt_xdate()
  
    box = ax.get_position()
    ax.set_position([box.x0, box.y0, box.width * 0.6, box.height])
    #plt.legend(loc='center left',bbox_to_anchor=(1, 0.5))
    plt.show()

if __name__ == '__main__':
    user = "user"
    pwd = "pass"

    f = open('{}-{}.txt'.format(user,folder),'a')
    f.close()

    months_dic = {
    'Jan':'1','Feb':'2', 'Mar':'3','Apr':'4',
    'May':'5','Jun':'6', 'Jul':'7','Aug':'8',
    'Sep':'9','Oct':'10', 'Nov':'11','Dec':'12'}
      
      
      
    read_server_fast()
    #read_server_slow() #use after deleting spam to start all over
    senders, dates, day_interval = get_sen_dat()
    show = False
    l_values, l_labels = pie_chart(senders, show)
    crit = 3
    time_graph(dates, l_values, crit, day_interval)