Résumé

Slow Pisces (alias Jade Sleet, TraderTraitor, PUKCHONG) est un groupe d’acteurs malveillants parrainé par l’État nord-coréen. Il cible principalement la génération de revenus pour le régime de la RPDC, généralement en ciblant de grandes organisations dans le secteur des crypto-monnaies. Cet article analyse leur campagne qui, selon nous, est liée aux récents vols de crypto-monnaies.

Dans le cadre de cette campagne, Slow Pisces a contacté des développeurs de crypto-monnaies sur LinkedIn, en se faisant passer pour des employeurs potentiels et en envoyant des logiciels malveillants déguisés en défis de codage. Dans le cadre de ces défis, les développeurs doivent exécuter un projet compromis qui infectent leurs systèmes à l’aide de logiciels malveillants : RN Loader et RN Stealer.

Le groupe aurait volé plus de 1 milliard de dollars US au secteur des crypto-monnaies en 2023. Ils y sont parvenus en utilisant diverses méthodes, notamment des fausses applications commerciales, des logiciels malveillants distribués via le Node Package Manager (NPM) et des compromissions sur la supply chain.

En décembre 2024, le FBI pointe du doigt Slow Pisces pour le vol de 308 millions de dollars à une société de cryptomonnaies basée au Japon. Plus récemment, le groupe a fait les gros titres pour son implication présumée dans le vol de 1,5 milliard de dollars à une bourse de cryptomonnaies de Dubaï.

Nous avons mis notre threat intelligence à disposition des analystes de GitHub et de LinkedIn afin de supprimer les comptes et les dépôts concernés.

En réponse, ils ont produit la déclaration suivante :

GitHub et LinkedIn ont supprimé ces comptes malveillants pour violation de leurs conditions d’utilisation respectives. Nous utilisons une technologie automatisée pour l’ensemble de nos produits, à laquelle s’ajoutent nos équipes d’experts en investigation et en signalement des membres, afin de lutter contre les mauvais acteurs et de faire respecter les conditions de service. Nous actualisons et améliorons constamment nos processus et nous encourageons nos clients et nos membres à signaler toute activité suspecte.

Informations complémentaires

Ce rapport montre comment Slow Pisces dissimule des logiciels malveillants dans ses défis de codage et décrit les outils mis au point par le groupe, afin de permettre à l’ensemble du secteur de mieux appréhender cette menace.

Les clients de Palo Alto Networks sont mieux protégés contre les menaces évoquées dans cet article grâce à nos abonnements Next-Generation Firewall avec Advanced URL Filtering et Advanced DNS Security.

Si vous pensez avoir été compromis ou en cas de question urgente, contactez l’équipe de réponse à incident de l’Unit 42.

Thématiques en lien avec l’Unit 42 Cryptocurrency, DPRK

Analyse technique

Cette campagne suit globalement trois étapes, illustrées ci-dessous dans la figure 1.

Diagramme illustrant des menaces de cybersécurité impliquant des leurres PDF, des dépôts GitHub et un serveur C2. Il montre : 1) des fichiers PDF tels que des descriptions d'emploi et des feuilles de questions agissant comme des leurres, 2) des référentiels GitHub JavaScript et Python avec de multiples API externes, pouvant potentiellement récupérer des données malveillantes, et 3) un serveur C2 configuré pour envoyer des données bénignes ou une charge utile malveillante dans certaines conditions. Les logos de Palo Alto Networks et d'UNIT 42 sont inclus.
Figure 1. Aperçu de la campagne « défi de codage » de Slow Pisces.

Étape 1 - Les leurres au format PDF

Les membres de Slow Pisces commencé par se faire passer pour un recruteur sur LinkedIn, avant de contacter des cibles potentielles en leur envoyant un fichier PDF anodin contenant une description de poste (voir figure 2). Si les cibles potentielles se portaient candidates, les attaquants leur présentaient un défi de codage comprenant plusieurs tâches décrites dans un questionnaire.

Image présentant deux documents côte à côte. À gauche, une "description de poste" pour un coordinateur d'équipe de conception UX. À droite, une "feuille de questions" contenant des questions techniques et générales relatives à la conception de l'expérience utilisateur (UX).
Figure 2. Leurres bénins au format PDF.

Nous avons identifié que Slow Pisces se faisait passer pour plusieurs organisations en utilisant ces leurres, principalement dans le secteur des cryptomonnaies. Les questions comprennent des tâches génériques de développement logiciel et un défi de codage sous forme de « projet réel », qui renvoie vers un dépôt GitHub présenté dans la figure 3 ci-dessous.

Capture d'écran d'un document intitulé « Coding and Problem-Solving Skills With Real Project ». Il inclut un lien vers un dépôt GitHub et décrit une tâche de codage impliquant les taux de change du Bitcoin et de l'Ethereum à partir de sources API. Le texte demande d'améliorer le projet en ajoutant plus d'API de marché et en améliorant la communication réseau dans le code.
Figure 3. Défi de codage du « projet réel » contenu dans le leurre PDF.

Étape 2 - Dépôts GitHub

Slow Pisces a présenté aux cibles ce que l’on appelle des défis de codage sous forme de projets issus de dépôts GitHub. Ces dépôts contenaient des adaptations de code open source, notamment des applications de visualisation et d’analyse :

      • De données boursières
      • De statistiques des ligues de football européennes
      • De données météorologiques
      • Du prix des cryptomonnaies

Le groupe a principalement utilisé des projets en Python ou en JavaScript, sans doute en fonction de la nature des profils (développeur front-end ou back-end). Nous avons également identifié des dépôts basés sur Java dans cette campagne, bien qu’ils soient beaucoup moins courants, avec seulement deux cas d’usurpation d’identité d’une application de cryptomonnaie appelée jCoin.

Cette rareté laisse penser que les attaquants ont pu créer des dépôts à la demande, en fonction du langage de programmation préféré de la cible. Par conséquent, le groupe utilise plus fréquemment des langages plus populaires dans le secteur des cryptomonnaies, tels que JavaScript et Python. De même, il reste peut-être des dépôts non découverts à ce jour dans d’autres langages de programmation.

Étape 3a - Dépôt Python

Fin 2024, le groupe a utilisé un projet illustré ci-dessous dans la figure 4 : le « Stocks Pattern Analyzer », adapté d’un dépôt légitime.

Capture d'écran d'un dépôt GitHub nommé « Stocks Pattern Analyzer » montrant la structure du fichier à gauche et le contenu du fichier README à droite expliquant comment exécuter l'application directement et avec Docker.
Figure 4. Dépôt Github « Stocks Pattern Analyzer ».

La majorité du code de ce dépôt est bénin. Lorsque les cibles tentent d’exécuter le projet conformément au questionnaire, les données sont récupérées à partir de trois sites distants :

      • hxxps://en.wikipedia[.]org/wiki/List_of_S%26P_500_companies
      • hxxps://en.wikipedia[.]org/wiki/Currency_pair
      • hxxps://en.stockslab[.]org/symbols/sp500

Deux des URL tirent des données de Wikipédia. La troisième redirige vers un domaine contrôlé par Slow Pisces. Cette approche, qui utilise plusieurs sources de données, la plupart légitimes, mais dont une est malveillante, est courante dans les dépôts Python du groupe.

Le serveur de commande et de contrôle (C2) malveillant est configuré pour imiter le format des sources légitimes. Ici, il utilise le sous-domaine .en et le domaine de premier niveau .org comme pour le domaine légitime de Wikipédia ci-dessus.

Désérialisation YAML

Slow Pisces pouvait simplement placer des logiciels malveillants directement dans le dépôt ou exécuter du code depuis le serveur C2 à l’aide des fonctions Python eval ou exec. Cependant, ces techniques sont facilement détectées, tant par une inspection manuelle que par des solutions antivirus.

Au lieu de cela, Slow Pisces s’assure d’abord que le serveur C2 répond avec des données d’application valides. Par exemple, le dépôt mentionné ci-dessus attend une liste des symboles des entreprises S&P 500. L’URL C2 répond d’abord avec ces données dans une liste au format JSON.

Les attaquants n’envoient qu’une charge utile malveillante à des cibles validées, probablement en fonction de l’adresse IP, de la géolocalisation, de l’heure et des en-têtes des requêtes HTTP. Le fait de se concentrer sur des personnes contactées via LinkedIn, par opposition à de vastes campagnes d’hameçonnage, permet au groupe de contrôler étroitement les dernières étapes de la campagne et de ne livrer les payloads qu’aux victimes attendues.

Pour éviter les fonctions suspectes eval et exec , Slow Pisces utilise la désérialisation YAML pour exécuter sa payload, comme le montre la figure 5.

Capture d'écran du code Python définissant une fonction « fetch_symbols » qui récupère les symboles boursiers du S&P 500 à l'aide d'un appel API, gère différents types de contenu et traite les réponses en fonction de leur type de contenu. La dernière ligne comporte une section mise en évidence dans un encadré rouge.
Figure 5. Code Python montrant le point d’entrée du logiciel malveillant Slow Pisces utilisant la désérialisation YAML.

Ce code récupère les données du serveur C2 via HTTPS et vérifie l’en-tête de réponse Content-Type. Si l’en-tête indique des données au format JSON (application/json), le code analyse et renvoie le JSON à l’application.

Si la réponse indique des données au format YAML (application/yaml), le code utilise la fonction yaml.load() de la bibliothèque PyYYAML pour analyser les données. Cette fonction est intrinsèquement non sécurisée : la documentation PyYAML recommande explicitement yaml.safe_load() pour les entrées non fiables.

Le format YAML est généralement utilisé pour les fichiers de configuration, comme dans l’exemple ci-dessous :

Cependant, yaml.load() peut sérialiser et désérialiser des objets Python arbitraires, et pas seulement des données YAML valides. Par exemple, le code Python suivant imprime les chiffres de 0 à 4 :

Si ce code était sérialisé à l’aide de yaml.dump(), il prendrait la forme suivante :

Enfin, lorsque ces données sont transmises à yaml.load(), le code original est exécuté : range(0, 5).

Cela met en évidence un point de détection potentiel, car les payloads du dépôt Python (et les logiciels malveillants en général) qui utilisent la désérialisation YAML contiennent !!python/object/apply:builtins si la payload utilise une fonction Python intégrée.

Les étapes suivantes du tableau 1 ont principalement lieu dans la mémoire et n’ont généralement aucune empreinte sur le disque. Pour aider la communauté à détecter et à sensibiliser, nous avons téléchargé ces payloads sur VirusTotal. La payload de désérialisation YAML exécute les logiciels malveillants RN Loader et RN Stealer sur la base du format de jeton C2 que nous avons identifié dans RN Stealer, et que nous analysons dans les sections suivantes.

Stade SHA256 Hash
Payload de désérialisation YAML 47e997b85ed3f51d2b1d37a6a61ae72185d9ceaf519e2fdb53bf7e761b7bc08f
RN Loader 937c533bddb8bbcd908b62f2bf48e5bc11160505df20fea91d9600d999eafa79
RN Stealer e89bf606fbed8f68127934758726bbb5e68e751427f3bcad3ddf883cb2b50fc7

Tableau 1. Payloads du dépôt Python.

La payload de désérialisation YAML de Slow Pisces commence par créer le dossier Public dans le répertoire personnel de la victime, puis crée un nouveau fichier dans ce répertoire : __init__.py. Les données Base64 intégrées sont décodées et écrites dans ce fichier, qui contient l’étape suivante de l’infection (RN Loader), qui est ensuite exécutée.

RN Loader

Ce fichier nouvellement créé pour RN Loader dans ~/Public/__init__.py se supprime lui-même après l’exécution : ainsi, il n’existe que dans la mémoire. Il envoie des informations de base sur la machine et le système d’exploitation de la victime par HTTPS au même C2 (sur en.stockslab[.]org), puis exécute une boucle de commande avec les options visibles dans le tableau 2.

Code Description
0 Mise en sommeil de 20 secondes
1 Base64- décode le contenu envoyé et l’enregistre dans le fichier init.dll (sur Windows) ou init (pour les autres OS).

Définit une variable d’environnement X_DATABASE_NAME sur une chaîne vide.

Charge et exécute la DLL téléchargée à l’aide de ctypes.cdll.LoadLibrary.

2 Base64- décode le contenu envoyé et l’exécute à l’aide du composant Python intégré exec.
3 Base64- décode le contenu envoyé et un paramètre. Le contenu est enregistré dans le fichier dockerd, tandis que le paramètre est enregistré sous docker-init.

dockerd est alors exécuté dans un nouveau processus, avec docker-init en tant qu’argument de ligne de commande.

9 Fin de l’exécution.

Tableau 2. Tableau des commandes RN Loader.

Les payloads de la boucle de commande du tableau 2 utilisant les options 1 et 3 sont actuellement inconnues et sont probablement déclenchées par des conditions spécifiques. Cependant, nous avons identifié qu’un logiciel malveillant basé sur Python était livré par l’option 2 afin de subtiliser des informations : RN Stealer.

RN Stealer

RN Stealer génère d’abord un identifiant aléatoire de la victime, utilisé ensuite comme cookie dans toutes les communications avec le serveur C2. Il demande ensuite au serveur une clé XOR pour chiffrer les données exfiltrées.

La communication avec le serveur C2 se fait via HTTPS, en utilisant des jetons codés en Base64 pour identifier les types de demandes et de réponses. La payload analysée comprend quatre types de jetons :

  • R0 - demande de clé XOR
  • R64 - exfiltration de données
  • R128 - exfiltration de données compressées
  • R256 - fin du vol d’informations

C’est le format de ces jetons - la lettre R suivie d’un nombre entier N - qui a donné son nom à cette payload. (RN Stealer pour la payload, RN Loader pour l’étape précédente).

Nous avons récupéré le script de cet échantillon de RN Stealer sur un système macOS. Les attaquants ont donc adapté cet échantillon pour voler des informations spécifiques aux appareils macOS, et notamment :

  • Des informations de base sur la victime : Nom d’utilisateur, nom de la machine et architecture
  • Applications installées
  • Liste de répertoires et le contenu de premier niveau du répertoire personnel de la victime.
  • Le fichier login.keychain-db qui stocke les informations d’identification sauvegardées dans les systèmes macOS.
  • Les clés SSH stockées
  • Fichiers de configuration pour AWS, Kubernetes et Google Cloud.

Les données recueillies par RN Stealer déterminent probablement si un accès permanent est nécessaire. Si c’est le cas, nous pouvons déduire les étapes suivantes pour cette chaîne d’infection Python :

  1. Le serveur C2 vérifie les victimes de balisage en fonction de critères inconnus. Les victimes valides reçoivent une payload de désérialisation YAML. Les victimes non valides reçoivent des données JSON bénignes.
  2. La payload de désérialisation établit une boucle de commande avec le serveur C2, exfiltre les informations de base de la victime et délivre un infostealer Python personnalisé via le code d’option 2 du tableau 2.
  3. L’infostealer recueille des informations plus détaillées sur la victime, que les attaquants ont probablement utilisées pour déterminer s’ils avaient besoin d’un accès permanent.
    1. Si c’est le cas, le serveur C2 délivre une payload via les codes d’option 1 ou 3.
    2. Si l’accès n’est plus requis, le code d’option 9 met fin à l’exécution du logiciel malveillant et supprime tous les accès, puisque la payload réside uniquement dans la mémoire.

Étape 3b - Dépôt JavaScript

Si les victimes ciblées ont postulé pour un poste de développeur JavaScript, elles découvrent plutôt un projet de « tableau de bord des cryptomonnaies », similaire à l’exemple de la figure 6 ci-dessous.

Capture d'écran d'un dépôt GitHub nommé « Cryptocurrency Dashboard », avec un fichier README.md affiché. Ce fichier README comprend les sections suivantes : Caractéristiques, Installation, Utilisation, Structure du projet, Configuration, Dépendances et Licence. Il décrit le projet comme une application construite avec Node.js, Express et EJS qui affiche des données historiques et en temps réel pour diverses crypto-monnaies.
Figure 6. Dépôt JavaScript.

Cette application contient un fichier .env avec le serveur C2 et une source de données C2 légitime :

  • PORT=3000
  • COINGECKO_API_URL=hxxps://api.coingecko[.]com/api/v3
  • JQUERY_API_URL=hxxps://update.jquerycloud[.]io/api/v1

La valeur COINGECKO_API_URL est utilisée pour récupérer des données pour le tableau de bord des cryptomonnaies, tandis que la valeur JQUERY_API_URL représente un serveur C2 contrôlé par Slow Pisces. Comme pour le dépôt Python, le serveur C2 JavaScript ne délivre des payloads qu’aux cibles validées. À défaut, il répond par un numéro de version.

Le dépôt utilise l’outil de modélisation Embedded JavaScript (EJS), en transmettant les réponses du serveur C2 à la fonction ejs.render(), comme le montre la figure 7 ci-dessous.

Capture d'écran montrant un extrait de code en JavaScript. Il comprend un commentaire et un appel de fonction pour rendre une page d'accueil avec des paramètres et des éléments par page. res.render est mis en évidence dans un encadré rouge.
Figure 7. JavaScript code showing the entry point of Slow Pisces’ malware using the EJS render function.

Tout comme l’utilisation de yaml.load(), il s’agit d’une autre technique employée par Slow Pisces pour dissimuler l’exécution d’un code arbitraire à partir de ses serveurs C2. Cette méthode n’est peut-être apparente que lors de l’affichage d’une payload valide.

La fonction de rendu EJS accepte divers paramètres, dont l’un est appelé view options. Dans ce cadre, un code JavaScript arbitraire peut être fourni et exécuté par le biais de la clé escapeFunction.

Un chercheur taïwanais qui se présente sous le nom de Huli a discuté des détails techniques de l’exécution de code arbitraire dans un post CTF. Cependant, nous pouvons comprendre qu’une payload structurée comme le montre la figure 8 entraînera l’exécution du code contenu dans escapeFunction lorsqu’il sera transmis à ejs.render().

Capture d'écran d'un extrait de code JavaScript impliquant des fonctions avec « escapeFunction » mis en évidence dans un cadre rouge.
Figure 8. Payload partielle du rendu EJS.

Malheureusement, nous n’avons pas pu récupérer la totalité de cette payload. Ainsi, nous ne pouvons que supposer qu’un nouveau répertoire .jql est créé dans le répertoire personnel de l’utilisateur, où est déposé un fichier appelé helper.js, qui contient des données Base64-.

Infrastructure

Le calendrier ci-dessous (figure 9) détaille l’infrastructure C2 utilisée dans cette campagne entre février 2024 et février 2025, en fonction du type de dépôt utilisé (JavaScript ou Python).

Chronologie de l'infrastructure de suivi des commandes et contrôles JavaScript (en haut, étiquette jaune) et des commandes et contrôles Python (en bas, étiquette orange). La chronologie commence à la fin du premier trimestre 2024 et se poursuit jusqu'au deuxième trimestre 2025.
Figure 9. Calendrier de l’infrastructure C2.

Comme indiqué précédemment, les domaines de l’infrastructure de cette campagne peuvent imiter le format des sources légitimes qui sont également utilisées, en utilisant fréquemment des sous-domaines tels que api ou cdn. Nous avons découvert des infrastructures associées à cette campagne jusqu’au moment de la rédaction de cet article.

Conclusion

Ce rapport a couvert la campagne la plus récente de Slow Pisces, qui consistait à se faire passer pour un recruteur sur LinkedIn pour cibler les développeurs du secteur des cryptomonnaies en leur proposant des défis de codage malveillants. Bien que nous n’ayons pas été en mesure de récupérer la chaîne d’attaque complète pour les dépôts JavaScript, la version Python nous a permis d’obtenir deux nouvelles payloads : RN Loader et RN Stealer.

Une telle utilisation de LinkedIn et de GitHub n’est pas exceptionnelle. De nombreux groupes affiliés à la RPDC ont utilisé des tactiques similaires, comme Alluring Pisces et Contagious Interview.

Ces groupes ne se chevauchent pas sur le plan opérationnel. Il convient toutefois de noter que ces campagnes utilisent des vecteurs d’infection initiale similaires.

Slow Pisces se démarque des campagnes de ses pairs en matière de sécurité opérationnelle. La livraison des payloads à chaque étape est fortement surveillée et n’existe qu’en mémoire. Et le tooling des étapes suivantes n’est déployé qu’en cas de nécessité.

Le groupe a notamment utilisé deux techniques pour dissimuler la fonctionnalité :

  • La désérialisation YAML
  • La fonction EJS escapeFunction

Ces deux techniques entravent considérablement l’analyse, la détection et la chasse. De même, les développeurs relativement nouveaux ou inexpérimentés dans le secteur des cryptomonnaies auraient des difficultés à identifier ces dépôts comme étant malveillants.

D’après les rapports publics sur les vols de cryptomonnaie, cette campagne semble très réussie et devrait se poursuivre en 2025. Bien que cet article ait mis en évidence deux possibilités de détection pour la désérialisation YAML et les payloads EJS escapeFunction, la mesure d’atténuation la plus efficace reste la séparation stricte des appareils professionnels et personnels. Cela permet d’éviter la compromission des systèmes d’entreprise par des campagnes d’ingénierie sociale ciblées.

Palo Alto Networks : protection et atténuation

Les clients de Palo Alto Networks sont mieux protégés contre les menaces évoquées ci-dessus grâce aux produits suivants :

Si vous pensez avoir été compromis ou en cas de question urgente, prenez contact avec l’équipe de réponse à incident de l’Unit 42 ou appelez les numéros suivants :.

  • Amérique du Nord : Sans frais : +1 (866) 486-4842 (866.4.UNIT42)
  • ROYAUME-UNI : +44.20.3743.3660
  • Europe et Moyen-Orient : +31.20.299.3130
  • Asie : +65.6983.8730
  • Japon : +81.50.1790.0200
  • Australie : +61.2.4062.7950
  • Inde : 00080005045107

Palo Alto Networks a partagé ces résultats avec les membres de la Cyber Threat Alliance (CTA). Les membres de la CTA utilisent ces renseignements pour déployer rapidement des mesures de protection auprès de leurs clients et pour perturber systématiquement les cyberacteurs malveillants. En savoir plus sur la Cyber Threat Alliance.

Indicateurs de compromission

Domaine Adresse IP Première apparition Dernière apparition Dépôt
getstockprice[.]com 70.34.245[.]118 2025-02-03 2025-02-20 Python
cdn[.]clubinfo[.]io 5.206.227[.]51 2025-01-21 2025-02-19 Python
getstockprice[.]info 131.226.2[.]120 2025-01-21 2025-01-23 Python
api[.]stockinfo[.]io 136.244.93[.]248 2024-10-30 2024-11-11 Python
cdn[.]logoeye[.]net 54.39.83[.]151 2024-10-29 2024-11-03 Python
en[.]wfinance[.]org 195.133.26[.]32 2024-10-12 2024-11-01 Python
en[.]stocksindex[.]org 185.236.231[.]224 2024-09-11 2024-10-04 Python
cdn[.]jqueryversion[.]net 194.11.226[.]16 2024-08-23 2024-09-23 JavaScript
en[.]stockslab[.]org 91.103.140[.]191 2024-08-19 2024-09-12 Python
update[.]jquerycloud[.]io 192.236.199[.]57 2024-07-03 2024-08-22 JavaScript
cdn[.]soccerlab[.]io 146.70.124[.]70 2024-08-07 2024-08-21 Python
api[.]coinpricehub[.]io 45.141.58[.]40 2024-05-06 2024-08-06 Java
cdn[.]leaguehub[.]net 5.133.9[.]252 2024-07-15 2024-07-21 Python
cdn[.]clublogos[.]io 146.19.173[.]29 2024-06-24 2024-07-12 Python
api[.]jquery-release[.]com 146.70.125[.]120 2024-06-10 2024-06-28 JavaScript
cdn[.]logosports[.]net 185.62.58[.]74 2024-05-08 2024-06-23 Python
skypredict[.]org 80.82.77[.]80 2024-05-06 2024-06-16 JavaScript
api[.]bitzone[.]io 192.248.145[.]210 2024-04-25 2024-05-13 Python
weatherdatahub[.]org 194.15.112[.]200 2024-04-05 2024-05-03 JavaScript
api[.]ethzone[.]io 91.234.199[.]90 2024-04-16 2024-04-24 Python
api[.]fivebit[.]io 185.216.144[.]41 2024-04-08 2024-04-14 Python
blockprices[.]io 91.193.18[.]201 2024-03-15 2024-04-09 JavaScript
api[.]coinhar[.]io 185.62.58[.]122 2024-03-26 2024-04-09 Python
mavenradar[.]com 23.254.230[.]253 2024-02-21 2024-03-26 JavaScript
indobit[.]io 146.70.88[.]126 2024-03-19 2024-03-20 Python
api[.]thaibit[.]io 79.137.248[.]193 2024-03-07 2024-03-09 Python
chainanalyser[.]com 38.180.62[.]135 2024-02-23 2024-03-06 JavaScript

Ressources complémentaires

Enlarged Image