mercredi 22 août 2012

[2/3] Python bot irc - Kit de première Analyse d'un fichier

Alors grâce à la partie [1/3] de ce tutoriel nous avons un bot connecté à un channel, mais qui ne sait rien faire d'autres.

Comment faire réagir notre bot:


Alors sur irc depuis mon client, j'ai écris : "test" voilà ce que le bot reçoit :

  • :nowz!nowz@I.saw.you.on.a.s3xtapz PRIVMSG #lechannel :test
  • :<user> PRIVMSG <chan> :<message>
Donc s'il y a plus de 3 mots et que le deuxième est PRIVMSG, nous avons donc reçu un message, mais nous allons aussi prendre le 3eme mots car, si par exemple vous êtes sur plusieurs channel il va falloir savoir sur lequel répondre, mais nous allons surtout le vérifier car quand votre bot reçoit un message privé il le reçoit de cette manière: 

  • :nowz!nowz@I.saw.you.on.a.s3xtapz PRIVMSG lePseudo :test
  • :<user> PRIVMSG <pseudoDuBot> :<message>
Comme vous pouvez le constater il y a seulement le 3ème mot qui change, c'est pour cela qu'on met une condition sur le mot n°3 (mot[2]) :

if len(mot) > 3 and mot[1] == 'PRIVMSG':
 if mot[2] == bChannel and mot[3] == ':test':
  irc.send('PRIVMSG %s :reponse au test\r\n' % mot[2])

J'ai créais deux conditions, afin que on évite de répéter la première condition pour toutes les commandes du mêmes types qu'on aimerait faire.

Screen ci-dessous: 




Téléchargement d'un fichier:

Nous allons réaliser une commande qui va télécharger un fichier à l'aide de la commande "!info <url>" que nous allons créer.

Depuis mon client j'ai tapé : !info http://site.fr/file.png, le bot reçoit :

  • :nowz!nowz@I.saw.you.on.a.s3xtapz PRIVMSG #lechannel :!info http://site.fr/file.png
On compte 5mots et le 4ème est !info et le 5ème est une adresse 


if len(mot) == 5 and mot[2] == bChannel and mot[3] == ':!info':
 get_file(mot[4])


Maintenant il faut créer cette fonction qui va télécharger notre fichier à l'adresse:

Donc pour ca on va utiliser la lib urllib

On ouvre le lien:


def get_file(chan, url):
 webFile = urllib.urlopen(url)


On met le nom du fichier dans une variable:


file_name = url.split('/')[-1]


On créait un fichier avec le même nom et y écrit le fichier distant et on ferme les 2 fichiers


 localFile = open('files/'+file_name, 'w')
 localFile.write(webFile.read())
 webFile.close()
 localFile.close()


Vous pouvez constater que je créais le fichier dans un dossier "files/", donc pensez à créer votre dossier.

Note importante
Je déconseille l'usage de cette fonction pour les utilisateurs car n'importe qui pourrait écrire n'importe quoi et ce n'importe où :).
Par exemple si il tape : !info http://site.fr/\..\..\file.exe , il arrivera à créer un fichier file.exe dans le dossier en amont de celui où est votre fichier .py

Comme vous pouvez le constater dans le screen ci-dessous, une fois que j'ai tapé !info <url> il a téléchargé le fichier et la bien enregistrer dans le dossier files.




Maintenant nous allons depuis ce fichier grâce à la lib hashlib trouver le hash md5 et sha1 de ce fichier tout fraichement téléchargé

On creait donc une nouvelle fonction (qui sera appellé depuis la première)

def get_info(chan, file_name):
 fmd5 = hashlib.md5(open('files/'+file_name, 'rb').read()).hexdigest()
 fsha1 = hashlib.sha1(open('files/'+file_name, 'rb').read()).hexdigest()


Une autre information qui pourrait être interessante est la réponse de la commande GNU/Linux file qui permet de determiner quel type de fichier nous faisons face.

Exemple de réponse de file sur le fichier fait téléchargé précedemment au bot:

$ file arduino_uno_test.jpg
arduino_uno_test.jpg: JPEG image data, JFIF standard 1.01

Et pour effectuer cette commande et récuperer le resultat on va utiliser la lib os .


  ffile = os.popen('file %s' % 'files/'+file_name).read()
  ffile = ffile.replace('files/%s: ' % file_name, '', 1)
  
  irc.send('PRIVMSG %s :md5: %s\r\n' % (chan, fmd5))
  irc.send('PRIVMSG %s :sha1: %s\r\n' % (chan, fsha1))
  irc.send('PRIVMSG %s :file: %s\r\n' % (chan, ffile))


Voici un screen du rendu:


Dans la partie suivante nous utiliserons yara-packer pour verifier si l'executable est passé sous les mains d'un packer, ainsi qu'une verification du hash md5 dans la bdd de VirusTotal, et d'autres petites infos qui pourraient être interessantes 

Code source entier: (Lien: pastie )


# -*- coding: utf-8 -*-

#import
import time
import socket
import string
import urllib
import hashlib
import os
 
#variables
bHost = 'irc.rizon.net'
bIp = socket.gethostbyname(bHost)
bPort = 6667
bPseudo = 'lePseudo'
bChannel = '#lechannel'

#autres variables
loop = 1

#fonctions
def get_file(chan, url):
 webFile = urllib.urlopen(url)
 file_name = url.split('/')[-1]

 localFile = open('files/'+file_name, 'w')
 localFile.write(webFile.read())
 webFile.close()
 localFile.close()
 
 get_info(chan, file_name)
 
def get_info(chan, file_name):
 fmd5 = hashlib.md5(open('files/'+file_name, 'rb').read()).hexdigest()
 fsha1 = hashlib.sha1(open('files/'+file_name, 'rb').read()).hexdigest()
 
 ffile = os.popen('file %s' % 'files/'+file_name).read()
 ffile = ffile.replace('files/%s: ' % file_name, '', 1)
 
 irc.send('PRIVMSG %s :md5: %s\r\n' % (chan, fmd5))
 irc.send('PRIVMSG %s :sha1: %s\r\n' % (chan, fsha1))
 irc.send('PRIVMSG %s :file: %s\r\n' % (chan, ffile))
 

#connection
irc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
irc.connect((bIp, bPort))

#message pour établir la connexion
irc.send('NICK %s\r\n' % bPseudo)
irc.send('USER %s %s %s :Python bot irc\r\n' % (bPseudo, bPseudo, bPseudo))

#boucle infinie
while loop == 1:
 data = irc.recv(4096)

 ligne = data.split("\r")

 for i in ligne:
  i = i.strip("\n")
  print "[%s] %s" % (time.strftime('%X'), i)
  mot = i.split(' ')

  if len(mot) > 2 and mot[1] == '376':
   irc.send('JOIN %s\r\n' % bChannel)
  

  if len(mot) == 2 and mot[0] == 'PING':
   irc.send('PONG %s\r\n' % mot[1])

  if len(mot) > 3 and mot[1] == 'PRIVMSG':
   if mot[2] == bChannel and mot[3] == ':test':
    irc.send('PRIVMSG %s :reponse au test\r\n' % mot[2])

   if len(mot) == 5 and mot[2] == bChannel and mot[3] == ':!info':
    get_file(mot[2], mot[4])

[1/3] Python bot irc - Kit de première Analyse d'un fichier

Bonjour,

Voici la première partie de mon tutoriel sur la réalisation de mon bot irc d'analyse d’exécutable.

Beaucoup d'entre vous, vont se dire que c'est totalement inutile, pourquoi faire cela par irc, pourquoi pas juste un petit script python personnel.
Effectivement, le passage par le protocole irc aurait pu être très largement évité, mais étant donné que j'ai besoin de me familiariser avec, je me suis dit pourquoi pas faire un bot. (ce qui permettrait à tout le monde de pouvoir utiliser le bot, et ce depuis n'importe où).

Donc dans ce premier tutoriel, on va voir comment établir la connexion à un serveur irc, et faire en sorte que le bot réponde à toute la petite routine de ce protocole.

Résumé pour faire court:


  • Langage: Python
  1. Établir une connexion: en utilisant socket
  2. Traiter les réponses
  3. Rejoindre un channel
C'est parti:

Avant tout, je tiens à rappeler que j'utilise les sockets et donc qu'on va faire nos connexions à la main, il existe des libs qui traite déjà "très" bien les connexions au protocole irc (ex: irclib , tuto sdz ircbot, twisted) mais pour apprendre un protocole j'avais besoin de le voir de par moi même.

On pense à ajouter la lib socket:


import socket


Nous créons nos variables importantes:


bHost = 'irc.rizon.net'
bPort = 6667
bPseudo = 'PseudoDuBot'
bChannel = '#lechannel'


On créait une nouvelle connexion:


irc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)


On resolve l'hostname:


bIp = socket.gethostbyname(bHost)


On établie enfin la connexion:


irc.connect((bIp, bPort))


A savoir que lorsqu'on se connecte à irc, il y a deux commandes que le serveur s'attend à recevoir avant de vous accepter : NICK et USER

NICK <votrePseudo>
USER <nomd'Utilisateur> <hote> <nomDeServeur> <nomReel>


irc.send('NICK %s\r\n' % bPseudo)
irc.send('USER %s %s %s :Python bot irc\r\n' (bPseudo, bPseudo, bPseudo))


J'ai mis un ":" devant Python pour lui dire qu'il va y avoir des espaces et qu'il doit en tenir compte.
Après cela, vous allez recevoir le motd du serveur de cette manière:

:server raw votrePseudo :message

Etant donnée qu'il est sur plusieurs lignes vous aurez différent raw. (exemple: 001, 002, etc..)

Les raws sont très important, c'est un nombre à 3chiffres, qui vous sont envoyé par le serveur lors de certains évènement:

Exemple:

  • 001 : Welcome du serveur
  • 375 : Début du motd
  • 376 : Fin du motd
  • 366 : Fin du names list : Quand on rejoint un channel, le serveur envoi toutes les personnes présentes sur le channel et pour signaler qu'il a fini de lister les personnes sur le channel il envoit un message avec le raw 366
Les raws sont un très bon moyen pour automatiser certaines actions.

Exemple:
  • A la fin du motd, rejoindre un channel
  • A la fin du names list, dire bonjour sur le channel
On va créer une variable 'loop' avec la valeur 1 pour ensuite réaliser une boucle infinie, qu'on pourra échapper en changeant sa valeur.

loop = 1

while loop == 1:

et on écoute dans la boucle ce que la socket a à nous dire:

data = irc.recv(4096)

Maintenant on va séparer le data par ligne et cela en utilisant split de la librairie string, et puis l'afficher


 ligne = data.split("\r")

 for i in ligne:
  i = i.strip("\n")
  print "[%s] %s" % (time.strftime('%X'), i)
  mot = i.split(' ')


J'en ai aussi profité pour créer une liste nommé 'mot' , dans laquelle on met chacun des mots de la ligne.
Ce qui va nous permettre de répondre au ping du serveur

Voici un exemple de ping reçu par le serveur:

  • PING :irc.shakeababy.net
  • PING :<serveur>
Un ping est une méthode pour vérifier que le client est toujours "vivant" en répondant "PING :serveur". Si vous ne répondez pas au bout d'un temps configuré par le serveur, alors votre connexion sera considéré comme morte et donc vous serez déconnecté.

Donc on reçoit en quelques sortes 2 mots, dont le premier est 'PING', on créait une condition pour y répondre :


  if len(mot) == 2 and mot[0] == 'PING':
   irc.send('PONG %s\r\n' % mot[1])


Voilà maintenant on se connecte, on répondra à tout les ping envoyé. Nous devons joindre le channel et c'est là qu'on va utilisé nos raws vu précédemment pour nous connecter à la fin du motd.

La ligne est de cette forme:

  • :irc.shakeababy.net 376 nowzzz :End of /MOTD command.
  • :<serveur> 376 <pseudo> :End of /MOTD command.
Donc on a plus de 2mots, dont le deuxième est 376

  if len(mot) > 2 and mot[1] == '376':
   irc.send('JOIN %s\r\n' % bChannel)

Et voilà ! 
Vous êtes connecté au serveur, vous avez rejoint le channel et vous répondez au ping.

J'admets qu'il y aurait beaucoup d'amélioration possible, comme par exemple créer une liste pour la variable bChannel, et rejoindre plusieurs channel.
Créer une fonction qui se reconnecterait en cas de perte de connexion et d'autres fonctions bien sympathique.
Vous pouvez participer en répondant à cette article.

Dans la seconde partie, nous entrerons un peu plus dans le sujet.

Code source entier: (Lien : pastie )

# -*- coding: utf-8 -*-

#import
import time
import socket
import string
 
#variables
bHost = 'irc.rizon.net'
bIp = socket.gethostbyname(bHost)
bPort = 6667
bPseudo = 'lePseudo'
bChannel = '#lechannel'

#autres variables
loop = 1

#connection
irc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
irc.connect((bIp, bPort))

#message pour établir la connexion
irc.send('NICK %s\r\n' % bPseudo)
irc.send('USER %s %s %s :Python bot irc\r\n' % (bPseudo, bPseudo, bPseudo))

#boucle infinie
while loop == 1:
 data = irc.recv(4096)

 ligne = data.split("\r")

 for i in ligne:
  i = i.strip("\n")
  print "[%s] %s" % (time.strftime('%X'), i)
  mot = i.split(' ')

  if len(mot) > 2 and mot[1] == '376':
   irc.send('JOIN %s\r\n' % bChannel)
  

  if len(mot) == 2 and mot[0] == 'PING':
   irc.send('PONG %s\r\n' % mot[1])
nowz