1
votes

Comment compiler et vider l'assembly pour une bibliothèque c (string.h)?

Pour un projet scolaire, je dois faire une grande quantité de manipulation de chaînes en assemblage. Comme c'est pénible à faire, j'essayais de trouver des moyens innovants d'utiliser des opérations de cordes déjà programmées. Mon idée est de compiler et de vider l'assembly de la bibliothèque string.h dans c. Ensuite, je copierais coller l'assembly sauvegardé dans mon programme. Après avoir déterminé l'emplacement mémoire de chaque fonction et ses paramètres, je pense que je pourrais essentiellement appeler la fonction.

Pour vider l'assemblage, j'ai d'abord écrit un programme qui comprenait les bibliothèques que je voulais:

lib:    file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
__text:
100000fa0:  55  pushq   %rbp
100000fa1:  48 89 e5    movq    %rsp, %rbp
100000fa4:  31 c0   xorl    %eax, %eax
100000fa6:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100000fad:  5d  popq    %rbp
100000fae:  c3  retq

_main:
100000fa0:  55  pushq   %rbp
100000fa1:  48 89 e5    movq    %rsp, %rbp
100000fa4:  31 c0   xorl    %eax, %eax
100000fa6:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100000fad:  5d  popq    %rbp
100000fae:  c3  retq

Ensuite, j'ai compilé et vidé l'assembly en utilisant

gcc -o lib lib.c
objdump -d  *o

Quand j'ai regardé le résultat, j'ai remarqué qu'il n'incluait aucun assemblage pour les bibliothèques. Je suppose qu'il y a soit une optimisation du compilateur qui n'inclut pas les fonctions inutilisées, soit la sortie de la bibliothèque est masquée lorsque j'utilise objdump:

#include <stdio.h>
#include <string.h>

int main() {

  return 0;
}

En remarque, j'utilise OSX Catalina, mais je peux passer à Ubuntu ou à un autre système d'exploitation si cela serait plus facile.

Comment puis-je faire le vidage de l'asm pour la string.h bibliothèque?


10 commentaires

Le simple fait d'inclure les fichiers .h n'ajoutera aucune fonction au programme à moins que vous ne les appeliez réellement. L'appel de fonctions de bibliothèque n'ajoute pas non plus l'assembly à votre programme, il est simplement lié à l'assembly dans le fichier de bibliothèque partagé.


Notez que gcc a des fonctions intégrées pour certaines fonctions de chaîne, donc avec l'optimisation activée, vous pouvez très bien insérer du code (si vous utilisez la fonction qui est). Si vous êtes intéressé par la version de la bibliothèque, vous pouvez bien sûr démonter la bibliothèque elle-même sans rien compiler.


(Un point pour sortir des sentiers battus. Sauf si vous étiez censé l'écrire vous-même pour cet exercice scolaire ...) Il devrait être possible de vider également le contenu des bibliothèques standard.


Au lieu de l'inclure, vous pouvez simplement regarder la source tout de suite et essayer de la compiler: Linux string.c


@Frederik ce lien n'a rien à voir avec la bibliothèque C standard.


hah je vois .. et par "innovant" vous voulez vraiment dire "contourner la mission"


Le professeur a dit qu'il pensait que c'était difficile et a dit de voir si je pouvais le faire fonctionner


string.h est un en-tête, pas une bibliothèque. Il ne contient aucun code pour aucune fonction. Le code est dans la libc liée dynamiquement.


@Jester Serait-il plus facile de démonter un binaire déjà compilé au lieu de le compiler moi-même et de faire un objdump?


Vous démontez la bibliothèque. Voir la réponse de Marco


3 Réponses :


4
votes

Tout d'abord, permettez-moi de commencer par dire qu'il s'agit vraiment d'un problème XY .

Mon idée est de compiler et de vider l'assembly de la bibliothèque string.h dans c. Ensuite, je copierais coller l'assembly sauvegardé dans mon programme.

Vous ne devriez pas faire cela. La bibliothèque standard a des fonctions très méticuleusement optimisées qui doivent être traitées avec soin et qui sont très , très compliquées. En d'autres termes, ils sont fondamentalement inutiles à des fins éducatives si vous apprenez l'assemblage.

Vous devriez vraiment simplement écrire votre implémentation préférée en C, puis la compiler.


Un fichier d'en-tête (tel que string.h ) ne contient généralement pas de définitions de fonction. Il ne contient que leur déclaration. Les fonctions réelles sont en fait déjà compilées dans un objet de bibliothèque dynamique qui est installé dans votre système (c'est-à-dire la bibliothèque elle-même).

Lorsque vous compilez un programme, le compilateur lie automatiquement à la bibliothèque C standard. Selon cette réponse , sous OS X, la bibliothèque standard doit se trouver à /usr/lib/libSystem.B .dylib . Sur Ubuntu, il s'agit généralement de /lib/x86_64-linux-gnu/libc.so.6 . Ce qui suit s'applique aux deux plates-formes sans problème.

Si vous voulez jeter un coup d'œil au désassemblage d'une fonction de bibliothèque particulière, vous pouvez exécuter objdump sur la bibliothèque en le redirigeant vers less , puis recherchez le nom de la fonction:

$ objdump -d /lib/x86_64-linux-gnu/libc.so.6 | less
...
0000000000080650 <strlen@@GLIBC_2.2.5>:
   80650:       66 0f ef c0             pxor   %xmm0,%xmm0
   80654:       66 0f ef c9             pxor   %xmm1,%xmm1
   80658:       66 0f ef d2             pxor   %xmm2,%xmm2
   8065c:       66 0f ef db             pxor   %xmm3,%xmm3
   80660:       48 89 f8                mov    %rdi,%rax
   80663:       48 89 f9                mov    %rdi,%rcx
   80666:       48 81 e1 ff 0f 00 00    and    $0xfff,%rcx
   8066d:       48 81 f9 cf 0f 00 00    cmp    $0xfcf,%rcx
   80674:       77 6a                   ja     806e0 <strlen@@GLIBC_2.2.5+0x90>
   80676:       f3 0f 6f 20             movdqu (%rax),%xmm4
   8067a:       66 0f 74 e0             pcmpeqb %xmm0,%xmm4
   8067e:       66 0f d7 d4             pmovmskb %xmm4,%edx
   80682:       85 d2                   test   %edx,%edx
   80684:       74 04                   je     8068a <strlen@@GLIBC_2.2.5+0x3a>
   80686:       0f bc c2                bsf    %edx,%eax
   80689:       c3                      retq
   8068a:       48 83 e0 f0             and    $0xfffffffffffffff0,%rax
   8068e:       66 0f 74 48 10          pcmpeqb 0x10(%rax),%xmm1
   80693:       66 0f 74 50 20          pcmpeqb 0x20(%rax),%xmm2
   80698:       66 0f 74 58 30          pcmpeqb 0x30(%rax),%xmm3
   8069d:       66 0f d7 d1             pmovmskb %xmm1,%edx
   806a1:       66 44 0f d7 c2          pmovmskb %xmm2,%r8d
   806a6:       66 0f d7 cb             pmovmskb %xmm3,%ecx
   806aa:       48 c1 e2 10             shl    $0x10,%rdx
   ...
   ...

À l'intérieur de less , vous pouvez rechercher en tapant / suivi du nom de la fonction, puis appuyez sur Entrée et utilisez n ou N pour parcourir les correspondances. p>

Alternativement, vous pouvez vider la sortie de objdump dans un fichier et l'inspecter avec un éditeur de texte:

$ gcc prog.c
$ objdump -d a.out
...
0000000000000720 <main>:
 720:   55                      push   %rbp
 721:   48 89 e5                mov    %rsp,%rbp
 724:   48 83 ec 70             sub    $0x70,%rsp
 728:   48 8d 45 90             lea    -0x70(%rbp),%rax
 72c:   48 89 c6                mov    %rax,%rsi
 72f:   48 8d 3d ae 00 00 00    lea    0xae(%rip),%rdi        # 7e4 <_IO_stdin_used+0x4>
 736:   b8 00 00 00 00          mov    $0x0,%eax
 73b:   e8 90 fe ff ff          callq  5d0 <__isoc99_scanf@plt>
 740:   48 8d 45 90             lea    -0x70(%rbp),%rax
 744:   48 89 c7                mov    %rax,%rdi
 747:   e8 74 fe ff ff          callq  5c0 <strlen@plt>
 74c:   48 89 45 f8             mov    %rax,-0x8(%rbp)
 750:   b8 00 00 00 00          mov    $0x0,%eax
 755:   c9                      leaveq
 756:   c3                      retq
 757:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
 75e:   00 00

Le problème lorsque faire ce genre de chose est que la bibliothèque standard a beaucoup de noms différents et plus compliqués pour les fonctions standard que ceux que vous voyez dans string.h . En interne, les symboles utilisés sont différents. Par exemple, sous Linux, lorsque vous utilisez printf , le symbole correspondant dans la libc est en fait __printf . Voir ici par exemple.

Vous pouvez trouver le vrai nom de symbole d'une fonction de bibliothèque standard en compilant un programme qui l'utilise et en regardant le code désassemblé, par exemple:

#include <string.h>
#include <stdio.h>

int main(void) {
    char s[100];

    scanf("%99s", s);
    size_t len = strlen(s);

    return 0;
}

Ensuite, lancez:

 $ objdump -d /usr/lib/libSystem.B.dylib > libSystem.disasm

Et vous pouvez voir que dans mon cas, scanf est en fait __isoc99_scanf , tandis que strlen est inchangé.

Je peux alors rechercher le désassemblage de strlen , qui sur mon système (Ubuntu) est le suivant:

$ objdump -d /usr/lib/libSystem.B.dylib | less

Comme vous pouvez le voir, même une fonction aussi simple est en fait une jungle apparemment impossible à comprendre d'instructions compliquées, en raison des nombreuses optimisations et réglages manuels appliqués par les auteurs de la glibc au fil des ans. p>


3 commentaires

«Impossible de comprendre la jungle des instructions compliquées» est une exagération. De nombreuses personnes font de la rétro-ingénierie de leur vie des logiciels malveillants, par exemple, ce qui est plus compliqué que strlen, car vous connaissez son objectif à l'avance. Bien sûr, je suis tout à fait d'accord que tenter de faire de l'ingénierie inverse du code déroulé en boucle pour gagner du temps sur un devoir scolaire est une très mauvaise idée.


@Gene: ou si vous voulez comprendre les fonctions de la glibc, lisez le source asm commenté manuscrit! Il utilise certaines macros mais ressemble autrement à la syntaxe AT&T normale. par exemple. la version SSE2 code.woboq.org/userspace/glibc/sysdeps /x86_64/strlen.S.html (Voir aussi Pourquoi le strlen de la glibc doit-il être si compliqué à exécuter rapidement? pour certains informations sur les versions de secours C par rapport aux versions asm manuscrites, avec des liens.) De plus, le memcpy / memset de la glibc est sympa, utilisant 2 magasins qui se chevauchent pour les petits tampons


@Gene Je sais, je fais du RE pour m'amuser moi-même, je parlais juste d'un point de vue novice puisque OP apprend l'assemblage. J'aurais peut-être exagéré avec l'hyperbole là-bas: ') Bien que les logiciels malveillants n'utilisent généralement pas tous ces opcodes optimisés, ils sont plus auto-modifiants et masqués que des instructions absurdes.




0
votes

Méthode générique:

  1. trouver le paquet, pour Linux de type Debian:
$ diff strstr1.asm strstr2.asm

Bien sûr, c'est la glibc.

  1. obtenir la source, par exemple glibc 2.31 et compilez-la:
$ cd strings
$ gcc ../sysdeps/x86_64/multiarch/strstr.c -c -std=gnu11 -fgnu89-inline  -g -O10 -Wall -Wwrite-strings -Wundef -Werror -fmerge-all-constants -frounding-math -fstack-protector-strong -Wstrict-prototypes -Wold-style-definition -fmath-errno      -ftls-model=initial-exec      -I../include -I/home/yury/LFSC/cross1/src/bglibcn/string  -I/home/yury/LFSC/cross1/src/bglibcn  -I../sysdeps/unix/sysv/linux/x86_64/64  -I../sysdeps/unix/sysv/linux/x86_64  -I../sysdeps/unix/sysv/linux/x86/include -I../sysdeps/unix/sysv/linux/x86  -I../sysdeps/x86/nptl  -I../sysdeps/unix/sysv/linux/wordsize-64  -I../sysdeps/x86_64/nptl  -I../sysdeps/unix/sysv/linux/include -I../sysdeps/unix/sysv/linux  -I../sysdeps/nptl  -I../sysdeps/pthread  -I../sysdeps/gnu  -I../sysdeps/unix/inet  -I../sysdeps/unix/sysv  -I../sysdeps/unix/x86_64  -I../sysdeps/unix  -I../sysdeps/posix  -I../sysdeps/x86_64/64  -I../sysdeps/x86_64/fpu/multiarch  -I../sysdeps/x86_64/fpu  -I../sysdeps/x86/fpu/include -I../sysdeps/x86/fpu  -I../sysdeps/x86_64/multiarch  -I../sysdeps/x86_64  -I../sysdeps/x86  -I../sysdeps/ieee754/float128  -I../sysdeps/ieee754/ldbl-96/include -I../sysdeps/ieee754/ldbl-96  -I../sysdeps/ieee754/dbl-64/wordsize-64  -I../sysdeps/ieee754/dbl-64  -I../sysdeps/ieee754/flt-32  -I../sysdeps/wordsize-64  -I../sysdeps/ieee754  -I../sysdeps/generic  -I.. -I../libio -I.   -D_LIBC_REENTRANT -include /home/yury/LFSC/cross1/src/bglibcn/libc-modules.h -DMODULE_NAME=libc -include ../include/libc-symbols.h       -DTOP_NAMESPACE=glibc -o /home/yury/LFSC/cross1/src/bglibcn/string/strstr.o -MD -MP -MF /home/yury/LFSC/cross1/src/bglibcn/string/strstr.o.dt -MT /home/yury/LFSC/cross1/src/bglibcn/string/strstr.o
$ objdump -d string/strstr.o > strstr2.asm
  1. les implémentations de fonctions ont généralement le même nom, alors trouvez la source:
$ make V=1 &>strstrr.txt

c'est strings / strstr.c

  1. désassembler la version compilée par défaut:
$ rm string/strstr.o`
  1. créer une nouvelle version avec une optimisation personnalisée: supprimer le fichier objet:
$ objdump -d string/strstr.o > strstr1.asm

remake avec la sortie des commandes:

$ find . -type f -name "strstr.c"
  • c'est pour "bash", sinon utilisez la commande "script"

récupérez la commande gcc de strstrr.txt, modifiez-la comme vous le souhaitez (optimisation, type de processeur ...), par exemple changez -O2 en O10, et exécutez:

$ ./configure --prefix=/usr --enable-kernel=4.0.0 --disable-profile --with-gnu-ld --enable-stack-protector=strong
$ make

le code sera différent:

$ apt-file search string.h

Donc, vous pouvez copier -Coller le code que vous voulez dans votre programme assembleur pour gagner du temps.


3 commentaires

La plupart des fonctions de chaîne de la glibc ont des implémentations asm x86. La compilation des solutions de secours génériques peut vous donner un certain asm pour une version trop compliquée qui fait probablement 4 octets à la fois avec un bithack pour vérifier une éventuelle fin de chaîne. Ou si vous trouvez code.woboq.org/userspace/ glibc / sysdeps / x86_64 / multiarch /… alors vous regardez le code de répartition CPU d'exécution de l'éditeur de liens dynamique qui sélectionne l'implémentation réelle à résoudre, en fonction de la machine hôte. Si vous souhaitez copier asm depuis la glibc, vous pouvez aussi simplement récupérer la source x86_64 / multiarch / strstr-sse2-unaligned.S


glibc est une librairie multiarch (sans mobile): `` `` $ find. -type f -name "strstr.c" ./sysdeps/x86_64/multiarch/strstr.c ./sysdeps/powerpc/powerpc64/multiarch/strstr.c ./sysdeps/s390/strstr.c ./string/strstr.c `` Une autre implémentation multiarchive de la bibliothèque C standard est en.wikipedia.org/wiki/Bionic_ ( logiciel) Plate-forme: x86, x86-64, ARM, ARM64, MIPS, MI ...


glibc «multiarch» ne veut pas dire «portable», cela signifie qu'il gère la distribution d'exécution pour quelques variantes de CPU x86-64, ou quelques variantes de PowerPC, où le meilleur choix d'implémentation est sélectionné au moment de la liaison dynamique. L'implémentation de secours C portable de Glibc est code.woboq .org / userspace / glibc / string / strstr.c.html . (Ce fichier est #include<> édité par le fichier x86 multiarch .c comme solution de secours pour les processeurs sans chargements / magasins non alignés SSE2 efficaces. D'autres fonctions comme strchr ne le font pas avoir une solution de secours C, toujours une version asm.)