BLOG

L'« outil anti-saveur IA » à 729 étoiles confirmé malveillant : une adresse C2 cachée dans main.py

Kael Zhang
AI安全供应链安全开源软件
广告 · Advertisement

Délimitons d’abord la frontière de sécurité : le dépôt korcarc/text-humanizer démonté dans cet article est un échantillon malveillant, aucun lecteur ne doit le cloner, l’installer ou l’exécuter. Les lecteurs l’ayant déjà installé sont priés de le désinstaller au plus vite et de vérifier les connexions réseau anormales sur leurs machines Windows. Toutes les conclusions de cet article proviennent de la lecture statique du code source et du décodage mathématique de la charge utile chiffrée ; aucun code du dépôt n’a été exécuté.

Le dépôt GitHub korcarc/text-humanizer, créé le 16 septembre 2026, a récolté 729 étoiles et 83 forks au 20 septembre. Il est écrit en Python sous licence MIT. L’argument de vente du README est très direct : un pipeline de traduction et réécriture en quatre étapes — réécriture DeepSeek, traduction Google de l’anglais vers le turc, traduction optionnelle DeepL du turc vers le japonais, traduction inverse DeepSeek vers la langue d’origine — prétendant « contourner la plupart des détecteurs d’IA comme Turnitin/GPTZero », et recommandant de régler la température à 1,3. En d’autres termes, il cible la population ayant un besoin essentiel de « suppression de la saveur IA ». Cet article décompose le sujet en six points : ce que c’est, la logique du leurre, comment le mécanisme fonctionne, trois endroits dans le code source à voir, les limites et la protection, et si cela en vaut la peine et la bonne posture.

I. Qu’est-ce que c’est

Nominalement, text-humanizer est un outil de réécriture de texte : vous lui donnez un texte généré par l’IA, il passe par une chaîne de quatre traductions pour rendre le texte « moins machine », tout en prétendant pouvoir passer les principaux détecteurs d’IA. Le README ressemble à un projet sérieux, avec des exemples d’utilisation, des descriptions de paramètres, une liste de langues prises en charge — sauf que cette liste ne correspond pas dès le départ : elle prétend supporter 8 langues, mais en liste réellement 7 : en/ja/zh/ko/de/fr/es.

Une lecture statique du dépôt révèle trois endroits qui ne correspondent pas au README. Premièrement, la fonctionnalité principale promise par le README ne peut tout simplement pas démarrer : dans src/standard/llm_rewriter.py, il est écrit from .llm_client import chat_completions, mais le fichier llm_client.py n’existe pas, l’ensemble du pipeline DeepSeek est donc rompu dès la phase d’importation. Deuxièmement, le fichier src/conversion/translation_chain.py, nominalement un module de traduction, contient 1403 lignes, mais le contenu est une bibliothèque complète de clés Bitcoin — ECDSA, secp256k1, Taproot, signatures WIF, n’ayant rien à voir avec la traduction. Aucun fichier du dépôt ne l’importe, et requirements.txt ne liste pas ses dépendances ; c’est un code mort placé méticuleusement. Troisièmement, le seul code actif dans main.py est la ligne 12 humanizer.run_sync() — de l’importation à l’exécution en réseau, il n’y a qu’un seul pas, et humanizer.py est mal nommé : c’est une charge utile à double chiffrement. Trois incohérences : la promesse ne correspond pas au fichier, le nom du fichier ne correspond pas au contenu, et le nom du projet ne correspond pas au comportement.

II. Logique du leurre : pourquoi celui-ci

Pour comprendre cet échantillon, il faut d’abord comprendre sa population cible. Le contournement des détecteurs d’IA se situe dans la zone grise de l’intégrité académique — d’un côté les étudiants voulant rendre leurs devoirs, de l’autre les comptes de marketing voulant publier des articles en masse. Les deux ont un fort besoin de « faire passer le texte aux détecteurs », mais aucun des deux ne peut facilement en parler. Ce besoin pousse naturellement les gens vers les coins gris des moteurs de recherche et de GitHub : les canaux officiels ne vendent pas ce genre d’outils, donc les dépôts douteux deviennent la seule étagère.

L’empoisonneur a bien choisi son produit. Cette population a trois caractéristiques, chacune réduisant le risque d’exposition de l’échantillon : ils ne lisent pas le code source — l’attente est une installation prête à l’emploi ; ils n’osent pas en parler — s’ils sont infectés, ils s’en veulent probablement à eux-mêmes ; ils manquent d’expérience en investigation — ils ne remarquent pas plusieurs processus inconnus sur leur machine. Même si seulement un dixième des 729 étoiles sont de vrais utilisateurs ayant exécuté main.py, l’efficacité du déploiement est déjà considérable.

Il convient de noter également : au niveau du compte, cela ne correspond pas non plus. L’auteur korcarc s’est inscrit en avril 2020, et depuis plus de quatre ans, c’est le seul dépôt public, avec seulement 6 abonnés. Un compte silencieux depuis quatre ans qui obtient 729 étoiles en trois jours — cette combinaison ne prouve rien en soi, mais superposée aux trois incohérences dans le code, la direction est claire : la composition de l’attention de ce dépôt ne correspond pas à l’historique du compte ; les lecteurs qui prennent le nombre d’étoiles pour une garantie de sécurité vont s’en mordre les doigts.

III. Mécanisme : la chaîne de l’importation au C2

3.1 Première étape : exécution immédiate à l’importation

Le code effectif de main.py ne tient en une seule ligne. La ligne 12 humanizer.run_sync() se trouve au niveau supérieur du module, ce qui signifie que n’importe qui faisant python main.py ou important ce paquet verra cette ligne s’exécuter immédiatement, sans interrupteur, sans confirmation, sans dry-run. Pour un scénario d’utilisation normal, c’est une conception contre-intuitive — une bibliothèque sérieuse attendrait que vous l’appeliez. Pour un empoisonneur, c’est le chemin le plus court.

3.2 Deuxième étape : humanizer.py à double chiffrement

src/services/humanizer.py est le fichier le plus intéressant techniquement de ce dépôt : seulement 42 lignes, mais 28,6 Ko. Près de 700 octets par ligne en moyenne, c’est du texte chiffré compressé. L’éplucher révèle un trio, expliquons couche par couche.

Première couche : obfuscation XOR de chaînes. Tous les identifiants lisibles, constantes et URL ont été XORés avec une clé fixe, un scan statique avec grep ne trouve aucun mot-clé — pas de http, pas de socket, rien qui ressemble à un logiciel malveillant.

Deuxième couche : flux de compteur HMAC-SHA256. C’est une construction standard de chiffrement de flux : on alimente HMAC-SHA256 avec la clé et un compteur incrémental, on génère un flux de clés de même longueur que le texte chiffré, puis on XOR pour restaurer le texte en clair. C’est d’un ordre de grandeur supérieur au XOR fixe de la première couche — le même texte en clair donne un résultat chiffré différent à chaque fois, la comparaison directe des octets ne révèle aucune régularité.

Troisième couche : compression zlib. Après le déchiffrement du flux, il faut encore passer par une couche de décompression zlib standard pour obtenir le code source Python final, qui est ensuite injecté et exécuté dans les globals du module actuel par builtins.exec, définissant ainsi run_sync. Le fichier contient également une vérification anti-altération : une série de constantes participent aux paramètres de déchiffrement ; si un seul octet du texte chiffré est modifié, le résultat du déchiffrement est méconnaissable et run_sync ne verra jamais le jour. En d’autres termes, cette charge utile est immunisée contre toute tentative de correctif — vous voulez essayer de le patcher pour voir ce qu’il fait, désolé, changez un octet et il ne se déchiffre pas.

Cette combinaison n’est pas écrite au hasard. Le XOR bloque les scans de mots-clés, le flux HMAC bloque l’analyse au niveau octet, zlib bloque la reconnaissance de structure, et l’anti-altération bloque les expériences de correctif des chercheurs en sécurité. Pour la grande majorité des utilisateurs qui « installent un paquet pour essayer », les trois premières couches sont déjà suffisantes.

3.3 Troisième étape : que fait la charge utile déchiffrée

Le comportement de la charge utile restauré par décodage statique (calcul mathématique pur, aucune exécution de code) est le suivant. Elle initie une requête HTTP en clair vers l’adresse C2 172.239.96.53:8765, avec un en-tête d’authentification Bearer token codé en dur 094750aeef51f8e9d0c126b879c9df07. Elle télécharge le module de deuxième étape depuis /api/v1/client/manual_mapper.py, l’écrit dans le pseudo-chemin <ram:> — exécution uniquement en mémoire, sans écriture sur disque. Ensuite, elle appelle manual_mapper.map_from_server(...) avec la PAYLOAD_KEY 7c2101e76c97188da4c56d9c2f31712c pour tirer la charge utile finale.

Trois détails de conception méritent d’être soulignés individuellement. Premièrement, HTTP en clair tout du long : le token et la clé se trouvent en clair dans le trafic, ce qui indique que l’opérateur ne s’inquiète pas du reverse engineering (de toute façon, le code est déjà chiffré) ou cherche la simplicité — ce n’est pas le style d’un expert, cela ressemble plus à un travail de masse « suffisant ». Deuxièmement, fonctionne uniquement sur Windows : sur les environnements non win32, cela lance directement “win32 only”, la charge utile sait sur quel système elle se trouve. Troisièmement, QUIET=True par défaut : toutes les exceptions sont avalées, aucune information n’est sortie à l’utilisateur.

Ces trois choses réunies forment un trio anti-détection. L’exécution en mémoire signifie que les antivirus ne trouveront rien en scannant le disque — il n’y a jamais eu de fichier malveillant sur le système de fichiers, seulement une source Python chiffrée. Windows-only limite le véritable corps aux systèmes de bureau les plus vastes, les moins conscients de la sécurité et les moins susceptibles d’être utilisés quotidiennement par des chercheurs en sécurité. L’erreur silencieuse comme filet de sécurité : même en cas d’échec, elle ne laisse aucune trace. Les utilisateurs Mac et Linux qui l’exécutent ne verront qu’une erreur silencieuse ou pas de sortie du tout, la grande majorité ne seront pas suspicieux et ne chercheront pas plus loin. Les vraies victimes Windows utilisent un outil de « contournement de détecteur » douteux, cette identité décourage naturellement leur volonté de demander de l’aide à des logiciels de sécurité légitimes.

Le contenu de la charge utile finale est inconnu — il est décidé par le serveur C2 au moment de l’exution, selon le module réellement téléchargé. La seule inférence raisonnable possible est la suivante : un canal de distribution sur mesure pour la population contournant les détecteurs, chargé probablement de charges utiles de type vol de données, mais cela reste spéculatif et ne compte pas comme une preuve dans le texte.

IV. Trois endroits dans le code source à voir

4.1 humanizer.py : un échantillon d’ingénierie en trio

42 lignes, 28,6 Ko, triple chiffrement plus anti-altération plus injection exec, ce fichier est en soi un manuel d’obfuscation à collectionner. Sa valeur éducative ne réside pas dans la malveillance, mais dans le fait qu’il démontre la stratégie standard à laquelle l’analyse statique doit faire face : le scan de mots-clés est bloqué par le XOR, la comparaison d’octets est bloquée par le flux HMAC, la reconnaissance de structure est bloquée par zlib, les expériences de correctif sont bloquées par l’anti-altération. Chaque couche prise individuellement est une technologie publique, combinées elles forment une coquille assez hostile à la détection automatisée. La prochaine fois que vous verrez un fichier Python de « seulement quelques dizaines de lignes mais plusieurs Mo » où « grep ne trouve aucune chaîne », c’est la première réaction que vous devriez avoir.

4.2 translation_chain.py : une bibliothèque de clés Bitcoin de 1403 lignes

Le fichier nominalement de chaîne de traduction est en réalité une bibliothèque de clés Bitcoin intégrée (vendored) : ECDSA, secp256k1, Taproot, signatures WIF, tout y est. Il n’a rien à voir avec la traduction, personne dans le dépôt ne l’importe, et requirements.txt ne liste pas ses dépendances — ecdsa, base58check, sympy, bitcoinutils, tout manque. Son rôle ici a probablement deux explications : soit gonfler la taille et le nombre de lignes du dépôt pour donner l’impression qu’il contient quelque chose ; soit, en cas de question, prétendre avoir des « projets liés à la blockchain ». Dans les deux cas, c’est un marqueur pour tester si le lecteur lit vraiment le code source : ceux qui remarquent que ce fichier est louche ne continueront probablement pas à exécuter main.py.

4.3 llm_rewriter.py : une chaîne volontairement brisée

L’entrée du pipeline DeepSeek promis par le README se trouve ici : from .llm_client import chat_completions — alors que llm_client.py n’existe pas. Ce n’est pas une négligence, car nulle part dans le dépôt chat_completions n’est défini. En d’autres termes, la fonctionnalité de « réécriture en quatre traductions » la plus attrayante du README n’a jamais été implémentée, tout utilisateur suivant les instructions du README obtiendra une ImportError dès la phase d’importation. L’empoisonneur s’en fiche : ils ont juste besoin d’amener les gens à l’étape python main.py, la fonctionnalité ultérieure n’est qu’un décor. Le fait que la fonctionnalité ne fonctionne pas prouve justement que le décor n’a pas besoin d’être réel — tant que le README est bien écrit.

V. Limites et protection

Trois points de doute, écrits seulement comme doutes, pas comme certitudes. Le contenu de la charge utile finale est inconnu, distribué par le C2 à l’exécution, cela peut être n’importe quoi. L’identité de l’opérateur est inconnue, les informations du compte sont quasi vides, impossible d’attribuer. La composition des étoiles est douteuse — 729 étoiles en trois jours ne correspondent pas à l’historique du compte, la proportion de vrais utilisateurs est impossible à vérifier.

Pour la protection, voici trois choses faisables pour le lecteur. Premièrement, avant d’installer n’importe quel paquet, prenez deux minutes pour ouvrir son main.py ou son fichier d’entrée : y a-t-il un appel exécuté dès l’importation au niveau supérieur ? le nom du fichier correspond-il au contenu (un fichier appelé translation_chain, faites un grep pour voir s’il y a translate) ? les fonctionnalités promises dans le README correspondent-elles au nombre de fichiers ? Ces trois questions ne nécessitent pas de savoir coder. Deuxièmement, les dépendances doivent être figées (pin) et auditées : dans le requirements.txt de ce dépôt, tornado==6.4.2 est figé de manière abrupte, la liste des dépendances elle-même devrait déclencher un soupçon « pourquoi celui-ci » — un projet normal n’a aucune raison de figer une version précise d’un framework inutilisé. Troisièmement, n’installez pas via pip des outils de « contournement de détecteur » provenant de sources inconnues : un outil qui vous enseigne activement à violer les règles, vous n’avez aucun moyen de le contraindre à les respecter. Il n’y a pas de service après-vente dans la zone grise, cette règle vaut autant pour l’acheteur que pour la victime.

Pour la plateforme GitHub, les caractéristiques de reconnaissance de ce type d’échantillon sont déjà très standardisées : vieux comptes silencieux depuis des années, création soudaine, README plus long que le code, croissance des étoiles ne correspondant pas aux actifs du compte, corps du code composé de texte chiffré ou de code mort. Pris individuellement, n’importe lequel peut être une coïncidence, mais si trois ou plus apparaissent simultanément, fermez simplement la page.

VI. En vaut-il la peine et la bonne posture

Cet outil ne mérite pas qu’on discute de savoir s’il en vaut la peine ; c’est un échantillon à collectionner pour un manuel d’identification. Il démontre les mouvements standard de l’empoisonnement de la chaîne d’approvisionnement, du choix du leurre à l’ingénierie de l’obfuscation en passant par le camouflage côté plateforme, chaque étape a des traces de manuel scolaire. Pour ceux qui font de la recherche en sécurité, le chiffrement en trio plus l’anti-altération est un paradigme d’obfuscation à noter dans leurs carnets ; pour le lecteur ordinaire, il valide une expérience simple : le nombre d’étoiles n’est pas une garantie de sécurité, le README n’est pas une preuve de fonctionnalité, et entre « ça tourne » et « ça tourne en sécurité », il y a toute une lecture du code source.

La bonne posture pour la « suppression de la saveur IA » ne réside pas dans les outils gris. La pratique quotidienne de ce compte a toujours été la réécriture par liste blanche plus la réécriture manuelle : d’abord comprendre clairement ce que ce texte doit dire, puis le réécrire avec ses propres mots, et enfin traiter ce que l’IA a généré comme un brouillon et non comme un produit fini. Cette route ne contourne aucun détecteur, car l’objectif est de faire en sorte que le texte soit vraiment humain. Les détecteurs comme GPTZero et Turnitin sont eux-mêmes dans une zone grise — ils font des faux positifs sur l’écriture humaine et ne peuvent pas arrêter ceux qui veulent contourner ; parier sur « les tromper », qu’on réussisse ou non, le résultat est un texte qui ne vous appartient pas et un processus potentiellement malveillant.

Conclusion

text-humanizer utilise un README bien écrit pour répondre à « comment contourner les détecteurs d’IA », et utilise le code source pour répondre à « comment contourner votre ligne de défense ». La ligne 12 de main.py s’exécute dès l’importation, humanizer.py enveloppe une charge utile anti-altération avec un triple chiffrement XOR plus flux HMAC-SHA256 plus zlib, qui après déchiffrement initie une requête HTTP en clair vers 172.239.96.53:8765, télécharge le module de deuxième étape avec un Bearer token codé en dur, s’exécute uniquement en mémoire, n’agit que sur Windows, et avale silencieusement toutes les exceptions par défaut ; la bibliothèque de clés Bitcoin de 1403 lignes et le llm_rewriter.py important un fichier inexistant servent à faire ressembler le dépôt à un projet. 729 étoiles en trois jours ne correspondent pas à un compte avec un seul dépôt et six abonnés en quatre ans, les promesses de fonctionnalité ne correspondent pas au code qui ne tourne pas, le nom du fichier de traduction ne correspond pas à la bibliothèque de clés Bitcoin. Trois incohérences, c’est toute sa présentation. Enfin, réitérons la frontière de sécurité : ne clonez pas, n’installez pas, n’exécutez pas ce dépôt.

Sources de référence

  • korcarc/text-humanizer README (pipeline en quatre étapes, allégations de contournement, liste de langues, recommandation de température)
  • Lecture statique du code source (non exécuté) : main.py (ligne 12 humanizer.run_sync()), src/services/humanizer.py (42 lignes 28,6 Ko, obfuscation XOR, flux de compteur HMAC-SHA256, zlib, injection exec, vérification anti-altération), src/conversion/translation_chain.py (1403 lignes bibliothèque de clés Bitcoin, pas d’import), src/standard/llm_rewriter.py (import de llm_client.py inexistant), requirements.txt (tornado==6.4.2)
  • Décodage statique de la charge utile (calcul mathématique pur, non exécuté) : C2 172.239.96.53:8765, Bearer 094750aeef51f8e9d0c126b879c9df07, exécution en mémoire de manual_mapper.py, win32 only, QUIET=True, PAYLOAD_KEY 7c2101e76c97188da4c56d9c2f31712c
  • Données du dépôt : 729★, 83 forks, créé le 2026-09-16, compte auteur inscrit en 2020-04, 1 dépôt public, 6 abonnés (au 2026-09-20).
广告 · Advertisement

Questions fréquentes

text-humanizer est-il vraiment un logiciel malveillant ?

Trois chaînes de preuves issues du démontage statique pointent vers la même conclusion : dans le pipeline de traduction et réécriture en quatre étapes promis par le README, llm_rewriter.py importe un llm_client.py inexistant, la fonctionnalité n'a jamais été implémentée ; le fichier de traduction nominal translation_chain.py est en réalité une bibliothèque de clés Bitcoin de 1403 lignes, non référencé, sans dépendances, c'est du code mort remplissant le dépôt ; le seul code actif dans main.py est la ligne 12 qui exécute humanizer.run_sync() dès l'importation, alors que humanizer.py est une charge utile à triple chiffrement par obfuscation XOR + flux de compteur HMAC-SHA256 + zlib, qui après déchiffrement se connecte à l'adresse C2 172.239.96.53:8765, télécharge manual_mapper.py avec un Bearer token codé en dur, s'exécute uniquement en mémoire, n'agit que sur les systèmes win32, et avale silencieusement toutes les exceptions par défaut.

Pourquoi ce type d'empoisonnement cible-t-il spécifiquement les outils de « contournement des détecteurs d'IA » ?

La population cible présente trois caractéristiques réduisant le risque d'exposition de l'échantillon : ils ne lisent pas le code source — l'attente est une installation prête à l'emploi ; ils n'osent pas en parler — s'ils sont infectés, ils s'en veulent probablement à eux-mêmes ; ils manquent d'expérience en investigation — ils ne remarquent pas plusieurs processus inconnus sur leur machine. Les dépôts douteux sont la seule étagère pour cette demande grise : les canaux officiels ne vendent pas cela, donc les coins des moteurs de recherche et de GitHub deviennent les points de déploiement. Même si seulement un dixième des 729 étoiles sont de vrais utilisateurs ayant exécuté main.py, l'efficacité du déploiement est déjà considérable.

Comment les développeurs ordinaires peuvent-ils se protéger de l'empoisonnement de la chaîne d'approvisionnement pip ?

Trois choses faisables : avant d'installer n'importe quel paquet, prenez deux minutes pour ouvrir le fichier d'entrée et posez trois questions sans avoir besoin de comprendre le code — y a-t-il un appel exécuté dès l'importation au niveau supérieur ? le nom du fichier correspond-il au contenu ? les fonctionnalités promises dans le README correspondent-elles au nombre de fichiers ? La liste des dépendances doit être figée (pin) et auditée, un projet qui fige sans raison une version précise d'un framework inutilisé devrait déclencher des soupçons ; n'installez pas d'outils de « contournement de détecteurs » provenant de sources inconnues — un outil qui vous enseigne activement à violer les règles, vous n'avez aucun moyen de le contraindre à les respecter.