Bonjour,
je cherche un moyen, si possible en C (de l'asm inline pourrait m'aller) d'obtenir un point d'entrée arbitraire dans une fonction d'un programme.
C'est-à-dire un pointeur vers une instruction précise d'une fonction et non pas vers la fonction. On va supposer que je sais ce que je fait avec les conventions d'appels de fonction et que je cherche donc un moyen de modifier le pointeur d'instruction (a priori sans toucher au pointeur de pile) de manière arbitraire (comme si on appelait une fonction "naked").
d'autre part, existe-t-il un équivalent au "__declspec( naked )" de visual studio sous gcc ? la documentation précise que l'attribut "naked" n'est valable que sur quelques architecture (et pas x86).
"C'est-à-dire un pointeur vers une instruction précise d'une fonction"
C'est possible ça
? si oui le C m'impressionnera toujours...
c'est sûr que ce n'est pas possible en C "pur", mais il existe peut-être une astuce qui permettrait de faire ça.
La solution tiendrait en un mot.
goto
en C, tu n'as pas le droit de faire un goto hors de ton scope.
Ce dont tu parles dnob me fait penser au technique de buffer overflow. l'idée est de faire sauté ton pointeur d'instruction sur une adresse en pile en écrasant le pointeur de pile.
J'ai donc envie de dire que si tu met ton code binaire dans un champs data, tu dois pouvoir faire comme dans une technique de depassement de buffer et sauter dans ton champs data. (pour peu que le segment soit executable)
C'est raconté des tonnes de fois dans le magazine MISC. Ca doit donc se retrouver...
J'ai demandé a un ami.
on peut faire un jump (jmp en assembleur intel 32 bit) vers un label n'importe ou.
voici un code c et un code assembleur:
http://godrik.mandragor.org/~godrik/toto.c
http://godrik.mandragor.org/~godrik/toto.s
le code assembleur est une version legerement modifié du code c pour sortir sauter a la fin de main des que l'on rentre dans zog.
j'espere que ca t'aide...
Oui, ça doit pouvoir se faire. Le résultat va être atroce parce que je ne peut pas utiliser de label et les adresse sont créées à l'exécution. Donc il faut lire le registre IP et le sauvegarder. ça va me faire des bout d'asm inline un peu partout.
Mais ça c'ait le cas facile. Là il faut que je prenne un train, mais je vous raconterais prochainement à quoi ressemble le cas difficile.
En fait, le problème c'est (entre autre) que les points de départ et d'arrivée du saut ne sont pas dans le même fichier (ils ne sont même pas compilé ensemble).
Donc je ne peut pas utiliser de label. Seulement, un truc genre asm("movl %eip,%eax;") ne fonctionne pas car le registre eip ne semble pas être reconnu.
J'avoue que le temps où je plongeais joyeusement dans la doc intel est derrière moi et je ne me souviens plus s'il y a une astuce pour faire ça. Quelqu'un a une idée ?
<troll> l'asm AT&T, qu'est ce que c'est moche.</troll>
Par pure curiosité, dans le cadre de quel type de projet as-tu besoin de faire de telles attrocitées?
Est-ce que ça n'est pas un problème de conception?
Ca m'étonne que tu n'ais pas d'autre solution plus conventionelle.
Je viens d'aller sur le site de la MSDN, bon ça répondra pas à ta question mais ça me permet d'en poser une autre ![]()
Ca sert à quoi "naked" ? Parce que j'ai beau lire et relire la doc, j'en vois franchement pas l'utilité.
Pour ta question, si j'ai bien compris, tu veux avoir dans un fichier un pointeur vers une instruction d'une fonction compilée absolument à part ?
C'est tordu ton truc, on peut savoir ce qui te pousse à faire ça ?
Parce que là à moins de connaître chacune des adresses des instructions de ton programme en mémoire...
________________________________________
Ma vidéo du moment :
http://youtube.com/watch?v=96Fm5SPsjD0 (Les Kiss Kool, à voir absolument
)
"Suicide par défénestration : encore une victime de Qt
"
Pour le problème principal je n'ai pas d'idée autre que de l'assembleur inline dégueulasse.
Pour la deuxième question __attribute(cdecl)__ fonctionne à mon sens de la même manière que le declspec(naked) de MS.
Sankukai : mais justement si en asm inline tu sais le faire, ça m'interesse. Car j'ai quelques lacune en assembleur et je ne parviens pas à récupérer la valeur du registre EIP dans une variable (le registre en question ne semble pas exister pour l'assembleur (GAS)).
Je crois que naked est un peu plus que cdecl. Car avec naked si tu écrit une fonction :
declspec(naked) int f()
{
__asm{
...
}
}
Alors le seule code dans la fonction sera celui dont ton asm inline. Le compilo ne rajouttera ni prologue ni épilogue au code. Alors que je pense que même avec la convention C il faut que la fonction elle même déplace le pointeur de pile de la taille dont elle a besoin pour ses variables locale par exemple.
Quel est le projet tordu qui me demande d'écrire de telle horreurs ?
J'essaye (sans avoir la prétention d'aller jusqu'au bout, mais j'aimerais vois ce que ça donne) d'écrire un compilo JIT pour OCaml. Ça bouffe donc du bytecode et ça en fait du code machine. Mais évidemment, l'allocation de closure est le point le plus difficile à réaliser. Puisque je ne "comprend" pas le code que je traduit, mais j'ai quand même besoin d'avoir des pointeurs vers les "fonctions" du programme que je compile. Or ces pointeurs ne sont créé qu'à l'exécution.
Étant donné que le language est interprété et compilé, je pense qu'il vaut mieux traduire le tout en C puis compiler en C. L'interprétation vient d'elle même.
Maintenant je ne connais pas du tout le processus de compilation de base de l'OCAML, donc...
ben, c'est ce que je fait, j'écris du C depuis le bytecode (ce qui est bien plus simple que depuis le code OCaml qui est fonctionnel alors que le bytecode est complètement impératif).
Seulement, pour aller une closure (c'est-à-dire une sorte de pointeur de fonction) il faut que je connaisse l'adresse de la fonction. Mais cette fonction correspond juste à un morceau de code, pas à une "vraie" fonction au sens du C, donc je ne peut pas juste récupérer l'adresse d'une fonction pour faire ça, mais j'ai besoin d'une adresse arbitraire dans le code. D'autre part, comme l'adresse en question n'est pas connue avant la compilation, il faut que je puisse lire cette adresse à l'exécution. D'où mon problème de copier le registre eip dans une variable. J'ai bien une idée qui est que CALL empile eip, mais ça ferait encore un truc moche ...
T'as pas honte d'écrire des trucs pareils ? ![]()
S'pèce de tordu !
Pire que ça, car voici le résultat dans un petit exemple.
Donc avec un niveau d'indirection supplémentaire (1 call et 2 ret dont j'aurais bien voulu me passer) ça fonctionne.
http://repository.sectionpc.info/C/change_eip.c
(attention les enfants, ce que vous voyez là est fait par des professionel, n'essayer pas de le faire chez vous)
Ça ne compilera que sous gcc, même si une version pour visual studio doit être facile à produire.
On remarque d'ailleurs tout de suite à quel point l'asm inline C'Est Mal (TM) : même sans aucune optimisation activée, gcc ne "voit" pas les jump dans mon code assembleur et si je mets un return au milieu du code (à la place du Jump(fin) qui est un peu inutile) alors il "optimise en dehors" (comme disent les anglais) tout le reste du code y compris les macros en assembleur. Ça ne compile donc plus parce que (entre autre) le label "suite" n'est pas trouvé etc.
Bref, il faut être prudent. Même si je trouve ça abusif de la part de gcc, alors que le __volatile__ lui dit de mettre le code à cet endroit sans se poser de question.
"Je ne parviens pas à récupérer la valeur du registre EIP dans une variable"
je crois que c 'est normal, tu n'as pas le droit d' y accéder. Pour le changer tu dois utiliser un jmp. Et pour le récupérer tu dois utilisé un truc du genre :
call lala ; quand tu réalises un call ton ip est pushé pour que la fonction puisse te revenir
lala:
pop eax ; tu as ton ip au point lala
p:
Pour l'avoir au point p tu dois ajouter la taille d'un pop. Qui doit faire autour de 2 octets.
mmm, pourquoi ne pas regarder comment fonctionne le chargement de lib dynamique ? Ca ressemble a ce que tu veux faire...
"Et pour le récupérer tu dois utilisé un truc du genre :
call lala ; quand tu réalises un call ton ip est pushé pour que la fonction puisse te revenir "
Oui, si tu regarde le code que j'ai donné dans mon précédent post, c'est bien cette technique que j'ai utilisé. Pour modifier le pointeur, par contre un jmp ne va pas car il utilise une adresse relative, donc il faut pousser l'adresse de destination et faire un ret (en espérant que l'assembleur en fasse un "near ret" mais je pense que c'est le défaut maintenant).
Godrik : dans le chargement de bibliothèque dynamique je pense qu'il y a une table au début de la bibliothèque qui donne des correspondances entre les noms des fonctions qui sont exportées et leur adresse. Donc là c'est "facile", mais dans mon cas, je ne connais pas à l'avance les points d'entrée.
pour des addresses absolues tu peux utiliser un far jmp jcrois.