ex0ns

about
A Computer Science Student's blog, about programming, algorithms, hack and a lot more.

[C] Détecteur de proximité

28 Jun 2014

Nous revoilà plongé dans le fantastique monde du Bluetooth et de Bluez. Il y a quelque temps j'avais commencé un projet de capteur de proximité, longtemps (1 an) abandonné, j'ai eu une soudaine envie de le continuer.

Pour résumer, le but de cet outil est simple: associer des scripts à la détection/arrêt de détection d'un appareil bluetooth, dans l'optique, par exemple, de verrouiller votre ordinateur lorsque vous vous en éloignez (téléphone en poche), ou alors de démarrer ThunderBird lorsque vous entrez dans la pièce. Il aurait probablement été plus court et efficace de développer ce genre d'outil dans un autre langage, mais il s'agit très bon entrainement pour faire un peu de C.

Jusque là, tout fonctionne très bien si ce n'est quelques détéctions infructueuses de temps en temps, le daemon fonctionne tout à fait correctement, cependant, je voulais rajouter un niveau de difficulté (et donc d'interêt) au projet en y intégrant une notion de distance, les scripts ne se baserai plus sur une détection binaire, mais pourrait être lancés en fonction de la proximité.

Phase de recherche

Rien ne permet cependant de déterminer avec précision la distance à un objet à partir du bluetooth, et cela à cause des interférences, des objets présents dans l'environnement. Pour autant, il existe un indicateur caractérisant la puissance d'un signal, à partir duquel nous pouvons (essayer) d'interpoler une distance plus ou moins (très peu) précise, il s'agit du Received Signal Strength Indication (RSSI). Cet indicateur semble parfait pour réaliser ce que je veux, bien que peu précis. Pour autant, ce n'est pas si facile, en effet, afin de récupérer la valeur du RSSI pour un périphérique bluetooth, il faut être pairé avec le périphérique, c'est ce que propose la fonction : hci_read_rssi. Cependant, compte tenu de la nature du projet je ne pouvais pas me permettre de pairer les périphériques (imagez un même téléphone devant être lié à plusieurs ordinateur, ou bien un ordinateur connecté à plusieurs périphériques). Il faut donc trouver un moyen de récuperer la valeur du RSSI sans pairer les périphériques, et c'est là que ça tout ce complique...

En effet, rien dans le code source de Bluez semble indiquer qu'il est possible d'effectuer une telle opération, et la documentation ne nous aide pas beaucoup nous plus. Cependant, des petits indices laissent penser qu'il existe un moyen de récupérer la valeur du RSSI au moment du scan, par exemple la structure inquiry_info_with_rssi définie dans hci.h

typedef struct {
       bdaddr_t      bdaddr;
       uint8_t       pscan_rep_mode;
       uint8_t       pscan_period_mode;
       uint8_t       dev_class[3];
       uint16_t      clock_offset;
       int8_t        rssi;
} __attribute__ ((packed)) inquiry_info_with_rssi;

Après de nombreuses heures de recherche, je n'ai rien trouvé de plus probant sur le sujet que la mailing list de Bluez (lien, dans lequel la fonction inquirywithrssi est nommée, le code fournit à la fin ne compile en tout cas pas chez moi mais la technique que je vais vous présenter ici en est très fortement inspirée.

Inquiry with rssi

Dans la suite de cet article, je vais vous présenter ma fonction int hci_inquiry_with_rssi(int dev_id, int len, int num_rsp, const uint8_t lap, inquiry_info_with_rssi *ii) , dont le but est le même que hci_inquiry, mais qui doit en plus, se charger de retourner les informations concernant le RSSI. Pour cela, il nous faut le code source de la fonction hci_inquiry (diponible ici. En bref, le code créer une socket de type bluetooth, la remplie avec les paramètres passés à la fonction puis fait un appel à IOCTL afin de transmettre l'information au driver de la carte bluetooth (par l'intermediaire du kernel), notre code est très ressemblant à l'exception même de IOCTL, que nous n'utiliserons pas. Sans plus tarder, voici le début de la fonction:

int hci_inquiry_with_rssi(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info_with_rssi **ii){
    int nrsp = num_rsp, dd, num, nrinfo = 0;
    struct hci_filter flt;
    inquiry_cp cp;
    struct pollfd p;
    unsigned char buf[HCI_MAX_EVENT_SIZE];
    unsigned char *ptr;
    hci_event_hdr *hdr;
    inquiry_info_with_rssi *start_info, *stop_info, *info;
    extended_inquiry_info *start_e_info, *stop_e_info, *e_info; // Petit détail dont je parlerai à la fin


    uint8_t default_lap[3] = { 0x33, 0x8b, 0x9e }; // Je n'ai pas trouvé la signification de ces valeurs, si une âme charitable est prête à m'expliquer !!!

    if(nrsp <= 0){ // Nombre de réponse invalide
        num_rsp = 0;
        nrsp = 255;
    }

    if(dev_id < 0){ // Périphérique bluetooth
        dev_id = hci_get_route(NULL);
        if(dev_id < 0){
            errno = ENODEV;
            return -1;
        }
    }

    dd = hci_open_dev(dev_id); // Ouverture de la socket sur ce périphérique
    if(dd < 0){
        return dd;
    }

    hci_filter_clear(&flt);
    hci_filter_all_events(&flt);
    hci_filter_set_ptype(HCI_EVENT_PKT, &flt);

    if (setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) // Ajoute les options prédéfinies à la socket
        return -1;

Pour ce qui suit, je dois avouer être dans le flou, je ne comprend pas la structure "inquiry_cp", ce que le champ 'lap' représente, il ne s'agit donc que d'une pâle copie de la source de hci_inquiry:

memset(&cp, 0, sizeof(cp));
if(lap  != NULL )
    memcpy(&(cp.lap), lap, 3);
else
    memcpy(&(cp.lap), default_lap, 3);
cp.length  = len;
cp.num_rsp = nrsp;

hci_send_cmd(dd, OGF_LINK_CTL, OCF_INQUIRY, INQUIRY_CP_SIZE, &cp);

p.fd = dd;
p.events = POLLIN | POLLERR | POLLHUP;

Maintenant que la socket est ouverte avec les options souhaitées, nous devons récupérer les évènements sont déclanchés sur cette dernière, c'est ce que nous faisons avec la fonction poll et la structure pollfd, que nous plaçons dans une boucle, dont voici le code:

while(1){
    p.revents = 0;
    if (poll(&p, 1, POLL_TIMEOUT) > 0) { // Retourne le nombre d'évenements déclanchés sur les sockets liée à la structure pollfd
        len = read(dd, buf, sizeof(buf));

        if (len < 0)
            continue; /* Malformed packet */
        else if (len == 0)
            break; /* EOF */

        hdr = (void *) (buf + 1);
        ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
        len -= (1 + HCI_EVENT_HDR_SIZE);

        switch (hdr->evt) {
            case EVT_INQUIRY_RESULT:
            break;
            case EVT_INQUIRY_RESULT_WITH_RSSI:
                num = ptr[0];
                start_info = (void*) ptr + 1;
                stop_info = start_info + num;
                for(info = start_info; info < stop_info; info++){
                    nrinfo = add_device(ii, info, nrinfo);
                }
            break;

            case EVT_EXTENDED_INQUIRY_RESULT:
                num = ptr[0];
                start_e_info = (void*)ptr+1;
                stop_e_info = start_e_info + num;
                for(e_info = start_e_info; e_info < stop_e_info; e_info++){
                    nrinfo = add_device(ii, (inquiry_info_with_rssi*) e_info, nrinfo);
                }
            break;

            case EVT_INQUIRY_COMPLETE:
            return nrinfo;
            break;
        }
    }
}

Une fois que nous avons recupéré le packet dans le buffer grâce à read, ils nous faut retrouver les informations à l'aide des différentes structures telles que: hci_event_hdr, contenant le type d'évènement qui s'est déroulé (dans notre cas, ce sont les INQUIRY_RESULT* qui nous intéressent).

Il existe plusieurs types de retour pour ces évènements, qui dépendent de la technologie utilsée par les périphériques (version bluetooth par exemple), les deux types de retour qui nous interessent sont EVT_INQUIRY_RESULT_WITH_RSSI et EVT_EXTENDED_INQUIRY_RESULT" car tout deux contiennent un champs dédié à la valeur du RSSI, le premier octet contenu dans la variable ptr (contenu du paquet) est le nombre de réponses reçues, il suffit de parcourir les données à l'aide des structures adéquates inquiry_info_with_rssi et extended_inquiry_info, la fonction add_device est la suivante:

int add_device(inquiry_info_with_rssi **list, inquiry_info_with_rssi *ii, int nrinfo){
    char addr[18];
    int found = 0, i;
    for(i = 0; i < nrinfo; i++){
        if(bacmp(&(((*list)+i)->bdaddr), &(ii->bdaddr)) == 0){
            found = 1;
            break;
        }
    }
    if(!found){
        ba2str(&(ii->bdaddr), addr);
        printf("Adding device %s\n", addr);
        memcpy((*list)+nrinfo, ii, sizeof(inquiry_info_with_rssi));
        nrinfo++;
    }
    return nrinfo;
}

Cette fonction prend en paramètre la liste des périphériques découverts jusque là, le dernier résultat du scan, ainsi que le nombre de périphériques déjà découvert (la taille de list), le but est de parcourir cette liste et de n'y ajouter que les périphériques qui n'y sont pas encore présent. Si le périphérique est déjà dans la liste, on ne fait rien, mais on pourrait très bien mettre à jour la valeur du RSSI afin d'avoir la valeur la plus récente possible à la fin du scan (exercice laissé au lecteur, tient en 4 lignes). À la fin, la fonction hci_inquiry_with_rssi retourne le nombre de périphériques détéctés le reste des informations ont étées ajoutées dans le tableau inquiry_info_with_rssi **ii passé en paramètre (exactement comme la fonction hci_inquiry de Bluez).

Et voilà ! Nous avons maintenant la possibilitée de récupérer la valeur RSSI simplement grâce à un scan. J'espère qu'il n'y avait pas une fonctionnalité caché de Bluez qui permet de faire ca en quelques lignes, cependant si vous le connaissez, n'hésitez par à le partager.

Sources

[Android] Liste noire SMS

15 Jun 2014

Après avoir retrouvé au fin fond d'un dossier une vielle application Android, j'ai décidé d'écrire un article à son sujet. A l'heure ou cet article est publié Android KitKat est déjà disponible et toutes les techniques présentées ici ne fonctionnent plus sous cette version (une seule et unique application doit être choisie pour recevoir des SMS). Je n'ai pas encore trouvé d'alternative pour faire fonctionner ces fonctions sur cette version, mais je suis certain que ceci est possible. Pour en revenir à l'application, son but (simple) était de pouvoir bloquer la réception de messages (SMS) en fonction du numéro, ou bien d'un inficatif de pays.

Mise en place de l'application

D'un point de vue des permissions, cette application nécessite android.permission.RECEIVE_SMS , afin de pouvoir récuperer les SMS entrants. Il faut également ajouter un receiver (BroadcastReceiver à notre manifest, afin de définir quelle classe à le droit de procéder à la récupération du message.

Le receveur

Cette classe est définie dans com.example.smslistener.SmsReceived, elle hérite de BroadcastReceiver. La méthode : public void onReceive(Context context, Intent arg1), est appelée lors de l'émission d'un signal par le système (ou une application), correspondant au signal declaré dans le manifest (SMSRECEIVED_). Toutes les informations contenues dans le signal se trouvent dans les "extras" de notre Intent (cf documentation android), et en particulier la clé pdus (protocol data unit) contenant toutes les informations relatives au message.

La méthode createFromPdu de la classe android.telephony.SmsMessage permet de créer un objet du type SmsMessage à partir de ces informations, ce dernier contient, entre-autre le numéro de l'expéditeur : getOriginatingAddress. Une fois ce numéro en notre possession, nous avons achevé la plus grosse partie du travail, il ne nous reste plus qu'a vérifier qu'il n'est lié ni à un pays bloqué ni d'un numéro bloqué. Afin de déterminer le pays d'origine, la méthode la plus rapide est de comparer le MCC (Mobile Country Code), il n'y a pas de méthode fournie par android pour effectuer cette comparaison, cependant, nous savons que leur taille est comprise entre 1 et 4 chiffres. Ainsi, il nous suffit d'essayer toutes les longueurs possibles de code dans l'ordre décroissant et de regarder s'ils existent, c'est ce que fait la méthode String getPrefix(String number):

private String getPrefix(String number){
    for(int i=4; i > 1; i--){
        String prefix = number.substring(1, i);
        if(this.db.findByPrefix(prefix)){
            return prefix;
        }
    }
    return null;
}

Dans notre exemple, nous utilisons une base de donnée afin de stocker tous les indicatifs des pays, ceci sera detaillé par la suite, mais l'important est de comprendre la détéction d'un MCC. Il faut maintenant comparer le numéro de téléphone ceux bloqués, contenus en base de donnée, dans le cas ou il s'y trouve, on arrête le broadcast à l'aide de la fonction abortBroadcast, ainsi, aucun autre application ayant un récepteur du type SMSRECEIVED_ ne l'obtiendra (sous hypothèse d'avoir la prioritée la plus grande possible sur cet évenement).

La base de donnée

Le modèle

Je voulais écrire cet article en grande partie afin de présenter le fonctionnement d'une base de donnée sous Android, qui, même si bien illustré dans la documentation, manque un peu d'exemples pratiques. Tout d'abord, il nous faut un modèle (un objet qui modélisé par les différentes colonnes de notre base de donnée), ici, le modèle est Country, modélisant un pays, par son nom, son MCC (indicatif téléphonique) ainsi que par un champs "blocked", indiquant si le messages provenant de ce pays doivent ou non, être bloqués.

Création de la table

Afin de créer notre table modélisant nos pays, il faut créer une classe héritant de SQliteOpenHelper, donc l'objectif est de créer et mettre à jour la base de donnée, la méthode onCreate ne sera appelée qu'une unique fois si la base de donnée n'est pas présente (au premier lancement de l'application par exemple), la méthode onUpgrade, elle sera appelée dans le cas d'un changement de version de la base de donnée (par exemple l'ajout ou la suppression d'un champs lors de la mise à jour de l'application), nous n'utiliserons pas cette méthode. La création de la base de donnée se déroule à l'aide de commandes SQL déjà bien connues (rien de spécifique pour Android):

db.execSQL("CREATE TABLE blockednumbers (_id INTEGER PRIMARY KEY AUTOINCREMENT, number TEXT unique);");
db.execSQL("CREATE TABLE blockedmcc (_id INTEGER PRIMARY KEY AUTOINCREMENT, country TEXT, mcc INTEGER unique, blocked INTEGER DEFAULT 0);");

Deux tables sont crées, la première contenenant les numéros de la liste noire, et l'autre contenant la liste des pays (nous devons extraire la liste des pays existants ainsi que leur code MCC correspondant d'un fichier XML récupéré sur Internet, c'est ce que fait la classe XMLCountries, cette classe sera detaillée par la suite, mais son unique méthode retourne une liste de pays qui peuvent par la suite être inserés en base de donnée (le XML est donc à usage unique, tout comme la classe DatabaseHelper).

Gestionnaire de base de donnée

Afin de faciliter l'intéraction avec la base de donnée, la documentation Android recommande la création d'une classe "DatabaseManager", qui sera chargée d'effectuer les requêtes. Par exemple, la fonction:

public void triggerMCC(Country country){
    ContentValues values = new ContentValues();
    values.put("blocked", country.triggerBlocked());
    this.database.update("blockedmcc", values, "mcc=?", new String[]{Integer.toString(country.getMcc())});
}

Se charge d'ajouter/retirer un pays de la liste noire, ContentValues est une classe ressemblant fortement à une Map, permettant d'associer une valeur à une clé. La fonction update prend en paramètre le nom de la table à modifier, une table associative colonnes/valeurs des colonnes à mettre à jour ainsi qu'une chaine de caractère représentant le selecteur "WHERE" d'une requête SQL (notez que la requête est "préparée", la rendant ainsi invulnérable aux attaques d'injection de code SQL), ainsi qu'un dernier argument, un tableau contenant la liste des paramètres requis par le "WHERE" (peut être null); Les méthodes query, insert et delete fonctionnent d'une façon très similaire.

Extraction des données XML

La classe XMLCountries sert à extraire les données relatives aux pays, c'est à dire nom et MCC. Pour cela, il faut se diriger du coté de XmlPullParser, et d'itérer sur tous les "tags" disponibles, en regardant s'il s'agit du nom ou d'un identifiant de pays, à partir de ces informations, nous pouvons créer une liste de Country, qui sera utilisée pour l'insertion en base de donnée. Une fois de plus, cette classe est à usage unique, et n'est instanciée que dans la méthode onCreate de DatabaseHelper.

Tous les éléments sont maintenant en place, l'application se contente de lister les pays disponible qui peuvent être bloqués/débloqués par un simple clique, ainsi qu'une entrée de texte afin d'ajouter à la liste noire un numéro spécifique. L'application sera par la suite "reveillée" lors de la réception d'un SMS et vérifiera si oui ou non ce message doit être transmis aux autres applications.

Quelques idées d'amélioration

  • Compteur du nombre de messages bloqués
  • Liste des numéros bloqués
  • Une option pour exporter les messages bloqués (pour gérer les exceptions).
Bibliographie

Codes auto-modifiables (self modifying)

12 Apr 2014

Afin de rester dans un contexte de reverse-engineering et de programmation bas niveau, voici un article traitant des codes "auto-modifiables" (self modifying codes), en d'autre termes, il s'agit de rendre un exécutable capable de muter par lui même lors de l'exécution. Cette technique peut être utilisée au lancement d'un programme afin d'optimiser sa vitesse d'exécution. Wikipedia cite en exemple Java et sa compilation à la volée. Dans le cadre de cet article notre objectif est de charger une portion de code, stockée sous forme d'octet dans une variable de notre programme (section .data), directement en mémoire, puis de l'exécuter. Tout comme dans l'article précédent, la base de notre programme est une fenêtre (on ne peut plus basique), et notre but est de modifier son titre, voici le code de base:

.386
.model flat, stdcall
option casemap:none

include windows.inc

include user32.inc
includelib user32.lib

include kernel32.inc
includelib kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName   BYTE    "SimpleWinClass", 0
MyTitle     BYTE    "A great title",0
Secret      BYTE    "This is a secret title",0
ShellCode   BYTE    0C7h, 05h, 48h, 30h, 40h, 00h, 01Dh, 30h, 40h, 00h, 0C3h

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
AppName LPCTSTR ?
ExPermission PDWORD ?

.code

start:
 invoke GetModuleHandle,NULL
 mov hInstance, eax
 invoke GetCommandLine
 mov CommandLine, eax
 invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax


 WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND

 mov wc.cbSize, SIZEOF WNDCLASSEX
 mov wc.style, CS_HREDRAW OR CS_VREDRAW
 mov wc.lpfnWndProc, OFFSET WndProc
 mov wc.cbClsExtra, NULL
 mov wc.cbWndExtra, NULL
 push hInstance
 pop wc.hInstance
 mov wc.hbrBackground, COLOR_WINDOW+1
 mov wc.lpszMenuName, NULL
 mov wc.lpszClassName, OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov wc.hIcon, eax
 mov wc.hIconSm, eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov wc.hCursor, eax
 invoke RegisterClassEx, addr wc
 mov AppName, offset MyTitle

 startWindow:
 invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\ 
                NULL
 mov hwnd, eax
 invoke ShowWindow,hwnd,CmdShow
 invoke UpdateWindow, hwnd
 .WHILE TRUE
    invoke GetMessage, ADDR msg, NULL, 0, 0
    .BREAK .IF (!eax)
    invoke TranslateMessage, ADDR msg
    invoke DispatchMessage, ADDR msg
 .ENDW
 mov eax, msg.wParam
 invoke ExitProcess,eax
 ret
 WinMain ENDP


 WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage, NULL
    .ELSE
        invoke DefWindowProc, hWnd, uMsg, wParam, lParam 
        ret
    .ENDIF
    xor eax, eax
    ret 
 WndProc endp

end start

Notez toutefois la variable nommée ShellCode, qui contient une liste d'instructions sous forme d'opcode (équivalent hexadécimal), et qui est le code que nous devons charger en mémoire. Tout d'abord, il faut savoir que sur tous les systèmes récents, un programme n'a pas les droits d'écriture n'importe ou dans la RAM, et doit demander l'autorisation pour pouvoir modifier ses droits sur une portion de mémoire (read-only, write, execute), sous Windows, la fonction qui permet de faire ces demandes au système se nomme VirtualProtect, notez que sa cousine : VirtualProtectEx permet de faire la même chose, mais pour n'importe quel exécutable chargé en mémoire.

Nous devons passer à cette fonction un pointeur vers l'adresse à partir de laquelle nous voulons modifier les droits, la taille de la région à modifier, les nouveaux droit souhaités (écriture, exécution, lecture) ainsi qu'un pointeur vers une variable afin de stocker l'état précédent de cette région en mémoire (pour restaurer dans son état initial).

Maintenant que nous savons comment récupérer les droits d'écriture sur une portion du programme, il nous manque tout de même un élément important un endroit où écrire notre ShellCode. La première idée consiste à remplir une portion de notre code avec du "Junk code", qui ne sera jamais exécuté mais qui nous permet de réserver une place en mémoire dans laquelle nous pourrons insérer notre ShellCode, il nous faut rajouter une nouvelle étiquette dans notre code:

toBeRewrited:
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop

NOP est une instruction tenant sur un byte (opcode : 0x90) qui ne fait absolument rien, nous avons donc maintenant, en mémoire, une place de quelques octets sur laquelle nous allons pouvoir écrire. Appellons la fonction VirtualProtect pour, par la suite y écrire notre ShellCode:

cld ; Clear direction flag (REP increases the EDI value by 1)
mov edi, offset toBeRewrited 
invoke VirtualProtect, edi, sizeof ShellCode, PAGE_READWRITE, addr ExPermission
mov esi, offset ShellCode
mov ecx, sizeof ShellCode
rep movsb ; Copy ECX bytes from ESI to EDI
call toBeRewrited ; Jump to our new code (and push eip)

Placez ce code juste avant l'étiquette startWindow. Pour résumer, ce bout de code nous donne les permissions d'écrire sur la mémoire à l'adresse de toBeRewrited, puis nous copions simplement, byte par byte, notre shell code à cette adresse. Explication du shellcode :

ShellCode   BYTE    0C7h, 05h, 48h, 30h, 40h, 00h, 01Dh, 30h, 40h, 00h, 0C3h

Ce shell code représente les instructions suivantes :

mov AppName, offset Secret
ret ; POP and JMP

Lancez l'application, et voilà : notre titre a été changé en "This is a secret title", notre code a donc bien été exécuté (notez par ailleurs qu'étant donné que nous écrivions dans une section de notre code qui était déjà exécutable, nous n'avons eu a demander que la permission PAGE_READWRITE à VirtualAlloc, afin de pouvoir y écrire). Cependant cette technique est loin d'être pratique car il faut écrire autant de "junk code" que la taille de notre ShellCode, et nous allons donc tenter de récupérer une place en mémoire au moment de l'exécution, ce qui peut être réalisé à l'aide de la fonction VirtualAlloc.

VirtualAlloc, donne moi un endroit ou écrire

Dans cette partie, nous reprenons un code "propre", celui donné au début de l'article, nous devons ajouter, avant l'étiquette startWindow, les instructions suivantes:

mov ecx, sizeof ShellCode
push ecx
invoke VirtualAlloc, NULL, ecx, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE
mov edi, eax ; EAX contains the address of the allocated memory
pop ecx ; VirtualAlloc changes ecx
push edi ; Store the allocated address (needed for the CALL)
cld
mov esi, offset ShellCode ; Set source 
rep movsb ; Copy ECX byte from ESI to EDI
pop edi ; Restore the pointer to the allocated memory
call edi

Ce code ressemble fortement au précedent, si ce n'est que nous n'appellons pas VirtualProtect, mais VirtualAlloc, afin de récuperer un endroit ou écrire notre shellcode. Notez ici que l'on précise à VirtualAlloc, qu'en plus des droits de lecture et d'écriture, nous voulons également les droits d'exécution sur cette partie de la mémoire.

Et voilà nous en avons fini avec cet article sur les codes capable de se re-écrire (nous aurions très bien pu remplacer, au lancement une fonction par une autre, en utilisant la même méthode), ou encore décoder des instructions à certaines conditions (présence d'un debugger par exemple).

Une fois de plus, tous les codes utilisés dans cet article sont disponibles ici.