Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Pointeur de fonction & shellcode

14 réponses
Avatar
kregg3r
Bonjours,
Je commence a etudier la conception de shellcode et afin de le tester ,
j'ai le code suivant:

char sh[]="les instructions a realiser"

int main()
{
(*(void (*)()) sh)();
return (0);
}

Je ne comprend pas tres bien la ligne l'instruction qui permet "l'appel"
au shellcode a proprement parle.
J'ai l'impression que c'est un appel via un pointeur de fonction mais qui
n'aurait pas de nom.
Pourrais je avoir quelques explications ?
Merci.

10 réponses

1 2
Avatar
-ed-
On 26 oct, 00:59, kregg3r wrote:
Bonjours,



Y'a pas de 's' à 'Bonjour'

Je commence a etudier la conception de shellcode et afin de le tester ,



Hum, ce genre de pratique est douteuse et d'un intérêt limité (autre
que nuisible ...).

j'ai le code suivant:

char sh[]="les instructions a realiser"

int main()
{
        (*(void (*)()) sh)();
        return (0);

}

Je ne comprend pas tres bien la ligne l'instruction qui permet "l'appel"
au shellcode a proprement parle.
J'ai l'impression que c'est un appel via un pointeur de fonction mais qui
n'aurait pas de nom.
Pourrais je avoir quelques explications ?



sh est l'adresse d'une variable. Celle-ci est convertie en adresse de
code via un cast de type pointeur de fonction au nombre de paramètres
indéfini et ne retournant rien : (void (*)()). Ensuite (* ...)() sert
à faire l'appel de la fonction via son adresse.

Nota : cette pratique est interdite par la norme :

char sh[] = "les instructions a realiser";

int main (void)
{
(*(void (*)(void)) sh) ();

return 0;
}

donne :


-------------- Build: Debug in hello ---------------

Compiling: main.c
Linking console executable: binDebughello.exe
C:devhellomain.c: In function `main':
C:devhellomain.c:5: warning: ISO C forbids conversion of object
pointer to function pointer type
Output size is 16.86 KB
Process terminated with status 0 (0 minutes, 0 seconds)
0 errors, 1 warnings

de plus, pour des raisons évidentes de sécurité, chercher à exécu ter
du code placé dans une zone de données est impossible sur les machines
disposant d'une MMU, car c'est une des premières choses que tout
système décent interdit...

Bref, que veux-tu faire exactement ?
Avatar
espie
In article ,
-ed- wrote:
de plus, pour des raisons évidentes de sécurité, chercher à exécuter
du code placé dans une zone de données est impossible sur les machines
disposant d'une MMU, car c'est une des premières choses que tout
système décent interdit...



Non, ca n'est pas impossible, c'est juste desactive par defaut. Heureusement
que ca n'est pas impossible, tu aurais beaucoup de mal a faire fonctionner
un editeur de liens dynamiques et plein d'autres choses sympathiques...
... et c'est encore legerement pire sur l'archi la plus repandue du
marche (i386) puisque tu n'as pas de separation propre entre les droits
d'execution et d'ecriture et qu'il faut ruser.

confere mprotect(2) sur les systemes Unix. Et le papier de Theo sur W^X
sur le site d'open, quelque part dans www.openbsd.org/papers
Avatar
kregg3r
On Mon, 26 Oct 2009 00:34:55 -0700, -ed- wrote:

On 26 oct, 00:59, kregg3r wrote:
Bonjours,



Y'a pas de 's' à 'Bonjour'

Je commence a etudier la conception de shellcode et afin de le tester ,



Hum, ce genre de pratique est douteuse et d'un intérêt limité (autre que
nuisible ...).



Apprendre pour le plaisir ? C'est ce qui m'amuse, chercher, comprendre ...
En fait, des que l'on fait un peu de programmation en C, on fait tres
vite nos 1er "segfault" et autres erreurs du genre. Et sur tous les cours
(dont les votres, qui m'ont beaucoup aide au debut) on nous signale un
danger sur ce genre d'erreurs mais rarement plus.
Et il y a un moment ou pour savoir ce qu'on risque il faut tester, ce que
j'ai fait. Alors oui le fait que cela puisse être dangereux m'attire,
mais le meme genre d'attrait que l'on a enfant pour toutes les choses
dangereuses et plus ou moins interdites.


j'ai le code suivant:

char sh[]="les instructions a realiser"

int main()
{
        (*(void (*)()) sh)();
        return (0);

}

Je ne comprend pas tres bien la ligne l'instruction qui permet
"l'appel" au shellcode a proprement parle.
J'ai l'impression que c'est un appel via un pointeur de fonction mais
qui n'aurait pas de nom.
Pourrais je avoir quelques explications ?



sh est l'adresse d'une variable. Celle-ci est convertie en adresse de
code via un cast de type pointeur de fonction au nombre de paramètres
indéfini et ne retournant rien : (void (*)()). Ensuite (* ...)() sert à
faire l'appel de la fonction via son adresse.




Ok, merci.

Nota : cette pratique est interdite par la norme :

char sh[] = "les instructions a realiser";

int main (void)
{
(*(void (*)(void)) sh) ();

return 0;
}

donne :


-------------- Build: Debug in hello ---------------

Compiling: main.c
Linking console executable: binDebughello.exe C:devhellomain.c: In
function `main': C:devhellomain.c:5: warning: ISO C forbids
conversion of object pointer to function pointer type
Output size is 16.86 KB
Process terminated with status 0 (0 minutes, 0 seconds) 0 errors, 1
warnings

de plus, pour des raisons évidentes de sécurité, chercher à exécuter du
code placé dans une zone de données est impossible sur les machines
disposant d'une MMU, car c'est une des premières choses que tout système
décent interdit...

Bref, que veux-tu faire exactement ?



Apprendre.
Avatar
candide
kregg3r a écrit :

Je commence a etudier la conception de shellcode et afin de le tester ,



Tiens, je ne connaissais pas ce truc de shellcode mais c'est vraiment
rigolo.

j'ai le code suivant:

char sh[]="les instructions a realiser"

int main()
{
(*(void (*)()) sh)();
return (0);
}




Ce code n'a pas d'effet visible chez moi (sous Ubuntu).


Par contre, celui-ci fonctionne :

int main(void)
{
int z = (int) "x31xc0x31xdbxb0x06xcdx80"
"x53x68/ttyx68/devx89xe3x31xc9x66xb9x12x27xb0x05xcdx80"

"x31xc0x50x68//shx68/binx89xe3x50x53x89xe1x99xb0x0bxcdx80"
int (*f) () = "x31xc0x31xdbxb0x06xcdx80"
"x53x68/ttyx68/devx89xe3x31xc9x66xb9x12x27xb0x05xcdx80"

"x31xc0x50x68//shx68/binx89xe3x50x53x89xe1x99xb0x0bxcdx80";
void (*f) (void) = z;

f();
}


(il m'ouvre un shell) et je crois que le code respecte la Norme (même si
c'est "implementation defined").




J'ai l'impression que c'est un appel via un pointeur de fonction mais qui
n'aurait pas de nom.



Oui, c'est un appel de fonction.

"Fonction qui n'a pas de nom" ? Hum, ça peut se comprendre de
différentes façons :

a) pas besoin d'avoir un nom pour accéder à une fonction ou un objet, il
suffit d'une adresse, par exemple :

#include <stdio.h>

int main(void)
{
printf("%cn","toCtoC"[2]);

return 0;
}

c'est idem, pour ton appel, dès lors que tu as fait le cast, sh est
interprété comme une fonction (en fait un pointeur vers une fonction).

b) peut-être fais-tu allusion à l'expression (void (*)()). Les
parenthèses extérieures sont un cast. L'intérieur, à savoir l'expression
void (*)() m'a longtemps semblé mystérieux et les explications qu'ont
trouve à ce sujet dans les livres (ou les sites web...) sont
insuffisantes ou inexistantes. En fait c'est un "nom de type" et il
s'obtient en imaginant la déclaration du machin correspondant et en
retirant le nom du machin. Ici, tu convertis s en un pointeur sur
fonction de type void (ie de fonction ne renvoyant rien) et ayant, comme
l'a dit -ed-, un nombre indéterminé de paramètres. Imaginons qu'on ait à
déclarer un tel pointeur p. On écrirait la déclaration du machin comme
ceci :

void (*p)();

Pour avoir le type correspondant, tu effaces le nom de l'objet déclaré,
ici p et tu obtiens l'expression suivante

void (*)()

qui désigne donc le type pointeur vers une fonction de type void.






Sinon quand tu écris *(void (*)())s

[noter au passage que cela fait jouer l'associativité droite-gauche dans
la précédence des opérateurs * par rapport à l'opérateur de cast,
autrement dit l'expression écrite est en fait équivalente à *((void
(*)())s)]

tu fais une indirection (la première étoile) mais je crois qu'elle n'est
pas utile.

Pour résumer, ton code (qui contient une conversion non autorisée par la
Norme comme l'a dit-ed-) est équivalent au code suivant :

char sh[]="les instructions a realiser";

int main()
{
void (*f)()=sh;
(*f)();
return (0);
}

et qui selon moi est encore équivalent à

char sh[]="les instructions a realiser";

int main()
{
void (*f)()=sh;
f();
return (0);
}

et je dirais même à


int main()
{
((void (*)())"les instructions a realiser")();

return (0);
}


Par contre, je pensais que ceci aurait marché :


int main()
{
((void ())"les instructions a realiser")();

return (0);
}

mais je vois qu'on ne peut caster en "type fonction" (mais je suis quand
même étonné que le compilateur m'arrête).

Au passage, on écrit return 0 plutôt que return (0).
Avatar
candide
candide a écrit :


Par contre, celui-ci fonctionne :

int main(void)
{
int z = (int) "x31xc0x31xdbxb0x06xcdx80"
"x53x68/ttyx68/devx89xe3x31xc9x66xb9x12x27xb0x05xcdx80"

"x31xc0x50x68//shx68/binx89xe3x50x53x89xe1x99xb0x0bxcdx80"
int (*f) () = "x31xc0x31xdbxb0x06xcdx80"
"x53x68/ttyx68/devx89xe3x31xc9x66xb9x12x27xb0x05xcdx80"

"x31xc0x50x68//shx68/binx89xe3x50x53x89xe1x99xb0x0bxcdx80";
void (*f) (void) = z;

f();
}




Petite erreur de recopie. Lire :


int main(void)
{
int z = (int) "x31xc0x31xdbxb0x06xcdx80"
"x53x68/ttyx68/devx89xe3x31xc9x66xb9x12x27xb0x05xcdx80"
"x31xc0x50x68//shx68/binx89xe3x50x53x89xe1x99xb0x0bxcdx80";
void (*f) (void) = (void (*)(void)) z;

f();
}

et qui se compile et s'exécute ainsi :

:~$ gcc -W -Wall -stdÉ9 -pedantic -o x fclc.c
:~$ ./x
$ :~$
Avatar
espie
In article <4ae5b0b4$0$22861$,
candide wrote:
kregg3r a écrit :

Je commence a etudier la conception de shellcode et afin de le tester ,



Tiens, je ne connaissais pas ce truc de shellcode mais c'est vraiment
rigolo.



Parfois, on se demande un peu dans quel coin paume tu habites quand tu
ne viens pas poster sur ce groupe...
Avatar
Pierre Maurette
Marc Espie, le 26/10/2009 a écrit :
In article <4ae5b0b4$0$22861$,
candide wrote:
kregg3r a écrit :

Je commence a etudier la conception de shellcode et afin de le tester ,



Tiens, je ne connaissais pas ce truc de shellcode mais c'est vraiment
rigolo.



Parfois, on se demande un peu dans quel coin paume tu habites quand tu
ne viens pas poster sur ce groupe...



Remarquez, fin 2009, il n'est pas nécessaire de déménager pour
intervenir sur fr.comp.lang.c

--
Pierre Maurette
Avatar
kregg3r
On Mon, 26 Oct 2009 15:22:43 +0100, candide wrote:

kregg3r a écrit :

Je commence a etudier la conception de shellcode et afin de le tester ,



Tiens, je ne connaissais pas ce truc de shellcode mais c'est vraiment
rigolo.

j'ai le code suivant:

char sh[]="les instructions a realiser"

int main()
{
(*(void (*)()) sh)();
return (0);
}




Ce code n'a pas d'effet visible chez moi (sous Ubuntu).


Par contre, celui-ci fonctionne :

int main(void)
{
int z = (int) "x31xc0x31xdbxb0x06xcdx80"
"x53x68/ttyx68/devx89xe3x31xc9x66xb9x12x27xb0x05xcd


x80"

"x31xc0x50x68//shx68/binx89xe3x50x53x89xe1x99xb0x0bxcd


x80"
int (*f) () = "x31xc0x31xdbxb0x06xcdx80"
"x53x68/ttyx68/devx89xe3x31xc9x66xb9x12x27xb0x05xcd


x80"

"x31xc0x50x68//shx68/binx89xe3x50x53x89xe1x99xb0x0bxcd


x80";
void (*f) (void) = z;

f();
}


(il m'ouvre un shell) et je crois que le code respecte la Norme (même si
c'est "implementation defined").




J'ai l'impression que c'est un appel via un pointeur de fonction mais
qui n'aurait pas de nom.



Oui, c'est un appel de fonction.

"Fonction qui n'a pas de nom" ? Hum, ça peut se comprendre de
différentes façons :

a) pas besoin d'avoir un nom pour accéder à une fonction ou un objet, il
suffit d'une adresse, par exemple :

#include <stdio.h>

int main(void)
{
printf("%cn","toCtoC"[2]);

return 0;
}

c'est idem, pour ton appel, dès lors que tu as fait le cast, sh est
interprété comme une fonction (en fait un pointeur vers une fonction).

b) peut-être fais-tu allusion à l'expression (void (*)()). Les
parenthèses extérieures sont un cast. L'intérieur, à savoir l'expression
void (*)() m'a longtemps semblé mystérieux et les explications qu'ont
trouve à ce sujet dans les livres (ou les sites web...) sont
insuffisantes ou inexistantes. En fait c'est un "nom de type" et il
s'obtient en imaginant la déclaration du machin correspondant et en
retirant le nom du machin. Ici, tu convertis s en un pointeur sur
fonction de type void (ie de fonction ne renvoyant rien) et ayant, comme
l'a dit -ed-, un nombre indéterminé de paramètres. Imaginons qu'on ait à
déclarer un tel pointeur p. On écrirait la déclaration du machin comme
ceci :

void (*p)();

Pour avoir le type correspondant, tu effaces le nom de l'objet déclaré,
ici p et tu obtiens l'expression suivante

void (*)()

qui désigne donc le type pointeur vers une fonction de type void.






Sinon quand tu écris *(void (*)())s

[noter au passage que cela fait jouer l'associativité droite-gauche dans
la précédence des opérateurs * par rapport à l'opérateur de cast,
autrement dit l'expression écrite est en fait équivalente à *((void
(*)())s)]

tu fais une indirection (la première étoile) mais je crois qu'elle n'est
pas utile.

Pour résumer, ton code (qui contient une conversion non autorisée par la
Norme comme l'a dit-ed-) est équivalent au code suivant :

char sh[]="les instructions a realiser";

int main()
{
void (*f)()=sh;
(*f)();
return (0);
}

et qui selon moi est encore équivalent à

char sh[]="les instructions a realiser";

int main()
{
void (*f)()=sh;
f();
return (0);
}

et je dirais même à


int main()
{
((void (*)())"les instructions a realiser")();

return (0);
}


Par contre, je pensais que ceci aurait marché :


int main()
{
((void ())"les instructions a realiser")();

return (0);
}

mais je vois qu'on ne peut caster en "type fonction" (mais je suis quand
même étonné que le compilateur m'arrête).

Au passage, on écrit return 0 plutôt que return (0).



Merci pour ces précision, qui m'ont bien aidées.
Mais je voulais revenir sur le propos d'-ed- qui disait

chercher à exécuter
du code placé dans une zone de données est impossible sur les machines
disposant d'une MMU, car c'est une des premières choses que tout
système décent interdit...



Effectivement je ne comprend pas pourquoi il est possible d'éxécuter ces
instructions. De plus j'ai vérifié, à l'aide d'objdump, ma chaine est
bien dans le segment .data (ou .rodata selon la déclaration de la
chaine). Alors que se passe-t-il ? Le segment .data n'est pas vraiment un
segment de données ? J'ai involontairement changé le comportement par
défaut dont parlait Marc Espie ?

Et encore merci pour toutes ces précisions.
Avatar
espie
In article <4ae5c3d9$0$999$,
kregg3r wrote:
Effectivement je ne comprend pas pourquoi il est possible d'éxécuter ces
instructions. De plus j'ai vérifié, à l'aide d'objdump, ma chaine est
bien dans le segment .data (ou .rodata selon la déclaration de la
chaine). Alors que se passe-t-il ? Le segment .data n'est pas vraiment un
segment de données ? J'ai involontairement changé le comportement par
défaut dont parlait Marc Espie ?



Peut-etre que tu ne travailles pas avec un systeme decent ?
Avatar
kregg3r
On Mon, 26 Oct 2009 15:49:22 +0000, Marc Espie wrote:

In article <4ae5c3d9$0$999$, kregg3r
wrote:
Effectivement je ne comprend pas pourquoi il est possible d'éxécuter ces
instructions. De plus j'ai vérifié, à l'aide d'objdump, ma chaine est
bien dans le segment .data (ou .rodata selon la déclaration de la
chaine). Alors que se passe-t-il ? Le segment .data n'est pas vraiment
un segment de données ? J'ai involontairement changé le comportement par
défaut dont parlait Marc Espie ?



Peut-etre que tu ne travailles pas avec un systeme decent ?



Debian lenny avec juste les dépôts stable. C'est indécent ?
1 2