[LINUX] Intégration du langage machine en langage C

introduction

Récemment, j'ai eu une tâche de programmation C contrainte dans le tweet à https://twitter.com/reiya_2200/status/1130761526959267841.

image.png

Celui qui me vient à l'esprit est la récursivité principale, mais ce n'est pas très intéressant, alors j'ai essayé de l'intégrer en langage machine, donc je l'ai essayé en tant que matériau.

Comme prérequis, x86_64 Linux + gcc et l'environnement de reproduction est Ubuntu18 (WSL / Windows10). ** Même avec le même Linux, le comportement peut changer considérablement dans différents environnements **. Veuillez faire attention à ne pas être mauvais.

Intégration obéissante

Premier code

Donc tweet était ** le code suivant avec le langage machine obéissant incorporé **.

emb1.c


#include <stdio.h>
int main(int argc,char *argv[]) {
  static char __attribute__((section(".text"))) s[]="1\xc0H\x83\xc7\bH\x8b\27H\x85\xd2t\t\xf\xbe\22\215D\20\xd0\xeb\xeb\xc3";
  return !printf("%d\n",((int(*)(void*))s)(argv));
}

Lorsqu'il est exécuté, le total des nombres à un chiffre spécifiés dans l'argument est affiché correctement comme indiqué ci-dessous.

Compiler / exécuter


$ gcc emb1.c
/tmp/ccfyvQPW.s: Assembler messages:
/tmp/ccfyvQPW.s:3: Warning: ignoring changed section attributes for .text
$ ./a.out 4 6 4 9
23

Je pense que ce n'est pas nécessaire pour ceux qui y sont habitués, mais je vais l'expliquer pour le moment.

Implémentation des fonctions et langage machine

La politique consiste à implémenter la partie qui calcule la somme en tant que fonction et à la traduire dans un langage machine.

Pour une implémentation facile, vous pouvez penser à un code comme celui-ci:

sum.c


int sum(void *pv) {
  char **pc=pv;
  int s=0;
  while ( *++pc ) {
    s+=**pc-'0';
  }
  return s;
}

L'argument pv suppose que ʻargvest passé. Le tableauchar * indiqué par ʻargv se termine par un pointeur NULL à la fin, il peut donc être traité sans ʻargc`.

Si vous compilez ceci et vérifiez le langage machine, cela ressemblera à ceci:

Compiler / assembler à l'envers


$ gcc -c -Os -fno-asynchronous-unwind-tables -fno-stack-protector sum.c && objdump -SCr sum.o

sum.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <sum>:
   0:   31 c0                   xor    %eax,%eax
   2:   48 83 c7 08             add    $0x8,%rdi
   6:   48 8b 17                mov    (%rdi),%rdx
   9:   48 85 d2                test   %rdx,%rdx
   c:   74 09                   je     17 <sum+0x17>
   e:   0f be 12                movsbl (%rdx),%edx
  11:   8d 44 10 d0             lea    -0x30(%rax,%rdx,1),%eax
  15:   eb eb                   jmp    2 <sum+0x2>
  17:   c3                      retq

Cela signifie que le langage machine de la partie fonction sera une séquence de code de 31, c0,48,83, ... en hexadécimal. Si cela est exprimé en caractères imprimables dans la plage de caractères ASCII, il peut être exprimé sous la forme `` 1 \ xc0H \ x83 \ xc7 \ bH \ x8b \ 27H \ x85 \ xd2t \ t \ xf \ xbe \ 22 \ 215D \ 20 . Ce sera xd0 \ xeb \ xeb \ xc3 "`.

Notez que -Os (optimisation de la taille) de gcc et d'autres options qui empêchent un traitement supplémentaire d'être incorporé dans le code sont spécifiées pour raccourcir le code. Je voulais une option ** pour le raccourcir en tant que caractère ASCII si possible, mais je ne peux pas m'en empêcher car il n'existe pas.

Intégration du langage machine

J'ai donc compris le langage machine de la partie fonction.

Vous pouvez l'incorporer dans votre code sous forme de chaîne normale.

Tout ce que vous avez à faire maintenant est de traiter l'adresse de la chaîne comme l'adresse de la fonction et de l'utiliser pour appeler la fonction. Dans printf, c'est ((int (*) (void *)) s) (argv), et ʻint (*) (void *) est le type d'adresse de cette fonction. (Un pointeur vers une fonction qui prend void * comme argument et retourne ʻint) et y effectue une conversion.

emb1.c(Republier)


#include <stdio.h>
int main(int argc,char *argv[]) {
  static char __attribute__((section(".text"))) s[]="1\xc0H\x83\xc7\bH\x8b\27H\x85\xd2t\t\xf\xbe\22\215D\20\xd0\xeb\xeb\xc3";
  return !printf("%d\n",((int(*)(void*))s)(argv));
}

Cependant, il y a deux points à noter. Il s'agit du placement de la chaîne s.

À ce stade, il existe un mécanisme pour empêcher les zones de mémoire inattendues d'être exécutées en tant que code. Sans les spécifications ci-dessus, même si vous essayez d'exécuter le code de langage machine correspondant, cela provoquera SEGV. C'est une connaissance nécessaire au cas où le compilateur ne peut pas le juger correctement comme un code exécutable, comme dans ce cas.

Intégré directement dans la fonction principale

Aperçu de la mise en œuvre

Cependant, je ne suis pas satisfait du code ci-dessus. C'est parce que l'attribut rend visible la structure de la section ELF.

Existe-t-il un moyen plus intelligent d'intégrer le langage machine? J'ai donc écrit le code suivant.

emb2.c


#include <stdio.h>
int main(int argc,char *argv[]) {
  asm volatile(".string \"H\\x83\\xc6\\bH\\x8b\\x6H\\x85\\xc0t\\xf\\xf\\xbe\\0\\x8d|\\x7\\xcf\\x89|$\\f\\xeb\\xe7\\xb0\"");
  return !printf("%d\n",argc-1);
}

Vous pouvez voir que cela fonctionne toujours.

Compiler / exécuter


$ gcc emb2.c
$ ./a.out 4 6 4 9
23

Graines et gadgets

Bien sûr, les graines sont faciles. Si vous spécifiez la pseudo-instruction .string avec l'instruction ʻasm volatile` qui intègre le code d'assemblage dans le code de langage C, ** le langage machine correspondant à la chaîne de caractères est incorporé à cet endroit **.

Vérifions le ʻa.out` qui a été créé après la compilation en le désassemblant.

Assembler inversé


$ objdump -SCr a.out | sed -ne '/<main>:$/,+20p'
000000000000064a <main>:
 64a:   55                      push   %rbp
 64b:   48 89 e5                mov    %rsp,%rbp
 64e:   48 83 ec 10             sub    $0x10,%rsp
 652:   89 7d fc                mov    %edi,-0x4(%rbp)
 655:   48 89 75 f0             mov    %rsi,-0x10(%rbp)
 659:   48 83 c6 08             add    $0x8,%rsi
 65d:   48 8b 06                mov    (%rsi),%rax
 660:   48 85 c0                test   %rax,%rax
 663:   74 0f                   je     674 <main+0x2a>
 665:   0f be 00                movsbl (%rax),%eax
 668:   8d 7c 07 cf             lea    -0x31(%rdi,%rax,1),%edi
 66c:   89 7c 24 0c             mov    %edi,0xc(%rsp)
 670:   eb e7                   jmp    659 <main+0xf>
 672:   b0 00                   mov    $0x0,%al
 674:   8b 45 fc                mov    -0x4(%rbp),%eax
 677:   83 e8 01                sub    $0x1,%eax
 67a:   89 c6                   mov    %eax,%esi
 67c:   48 8d 3d a1 00 00 00    lea    0xa1(%rip),%rdi        # 724 <_IO_stdin_used+0x4>
 683:   b8 00 00 00 00          mov    $0x0,%eax
 688:   e8 93 fe ff ff          callq  520 <printf@plt>

C'est la partie où 48,83, c6,08 à l'adresse 659 à b0,00 à l'adresse 672 sont intégrés. L'instruction à l'adresse 672 n'est pas réellement traitée, mais elle est préparée comme une instruction se terminant par 00 afin que le caractère NUL (00) puisse être ajouté à la chaîne de caractères incorporée.

Code d'origine

Maintenant, à propos de ce code intégré.

Cela suppose simplement le traitement suivant. En d'autres termes, sauvegardons le résultat du calcul tel quel avec ʻargc` comme total.

Code d'origine


#include <stdio.h>
int main(int argc,char *argv[]) {
  while ( *++argv ) { argc+=**argv-'0'-1; }
  return !printf("%d\n",argc-1);
}

En passant, selon la convention d'appel de fonction de x86_64 Linux, le premier argument ʻargc est stocké dans le registre rdi (la partie 32 bits est edi), et le second argument ʻargv est stocké dans le registre rsi. Donc, si vous les bouclez directement, c'est OK.

Assemblage applicable


 659:   add    $0x8,%rsi      #Avance argv d'un élément
 65d:   mov    (%rsi),%rax    #Charger des éléments argv dans rax
 660:   test   %rax,%rax      #Jugement de pointeur NULL
 663:   je     674            #Passer immédiatement après la partie incorporée lorsqu'un pointeur NULL est détecté
 665:   movsbl (%rax),%eax    #Lire des caractères dans eax
 668:   lea    -0x31(%rdi,%rax,1),%edi # argc(edi)Ajouter à
 66c:   mov    %edi,0xc(%rsp) #Enregistrer edi dans la zone de la pile
 670:   jmp    659            #Aller au début de la pièce incorporée
 672:   mov    $0x0,%al       #Instruction factice

Cependant, sans optimisation, ʻargc` utilisé au moment de printf semblait utiliser la valeur une fois sauvegardée dans la pile, pas directement dans le registre. Par conséquent, le résultat du changement du registre esi est reflété dans la pile par l'instruction à l'adresse 66c.

Au contraire, si l'optimisation est effectuée, la zone de pile pour sauvegarder l'argc n'est pas préparée, donc l'instruction à l'adresse 66c détruira la pile. Donc, la sortie est comme prévu, mais sachez que cela provoquera SEGV à la sortie de main.

Spécifier les options d'optimisation


$ gcc -O3 emb2.c
$ ./a.out 4 6 4 9
23
Segmentation fault (core dumped)

À la fin

Qu'as-tu pensé. J'ai introduit le code, pensant que je pouvais ressentir la sensation équivalente au niveau -9 du Niveau de programmeur C corrompu -10.

Enfin, pour le moment, je laisserai également le principal récursif, qui semble être la solution attendue du questionneur (car il peut être assemblé pour le moment!).

Solution supposée?code


#include <stdio.h>
int main(int argc,char *argv[]) {
  return argc>0 ? !printf("%d\n",main(0,argv+1)) : *argv ? **argv-'0'+main(0,argv+1) : 0;
}

Recommended Posts

Intégration du langage machine en langage C
Tri de tas fait en langage C
Test de module multi-instance en langage C
Réaliser une classe d'interface en langage C
Segfo avec 16 caractères en langage C
Liste de liens (list_head / queue) en langage C
Générer un langage C à partir d'une expression S avec Python
Méthode de contrôle exclusive multi-processus en langage C
Configurer un serveur UDP en langage C
Gérer les signaux en langage C
File d'attente ALDS1_3_B langage C
Accéder à MongoDB en C
Next Python en langage C
[Algorithme de langage C] Endianness
API C en Python 3
Essayez de créer un module Python en langage C
Essayez d'incorporer Python dans un programme C ++ avec pybind11
Aller à la langue pour voir et se souvenir du langage Partie 7 C en langage GO
[Algorithme de langage C] bloquer le mouvement
Étendre python en C ++ (Boost.NumPy)
Recherche binaire ALDS1_4_B langage C
Apprentissage automatique dans Delemas (s'entraîner)
Utiliser des expressions régulières en C
Remarques sur l'intégration du langage de script dans les scripts bash
Remarque 2 pour intégrer le langage de script dans un script bash
Imiter Numpy de Python en C #
Recherche binaire en Python / C ++
J'ai essayé d'illustrer le temps et le temps du langage C
Hello World en langue GO
[Langage C] readdir () vs readdir_r ()
Utilisé en EDA pour l'apprentissage automatique
Recherche linéaire ALDS1_4_A en langage C
Arborescence de surface totale minimale en C #
Introduction à l'API Socket apprise en langage C, partie 1, édition serveur
Ecrire un test piloté par table en C
Essayez d'implémenter Yuma en langage Go
Essayez la visualisation d'incorporation ajoutée dans TensorFlow 0.12
[Traitement du langage 100 coups 2020] Chapitre 6: Machine learning
Pointeur de fonction et objdump ~ Langage C ~
100 Language Processing Knock Chapitre 1 en Python
Ecriture du langage C avec Sympy (métaprogrammation)
Automatisez les tâches de routine dans l'apprentissage automatique
ABC166 en Python A ~ C problème
Langage de programmation C à haute efficacité énergétique
Introduction à Protobuf-c (langage C ⇔ Python)
100 Language Processing Knock 2020 Chapitre 6: Apprentissage automatique
Classification et régression dans l'apprentissage automatique
Lors de la lecture d'une structure C ++ avec Cython
100 Language Processing Knock 2020 Chapitre 10: Traduction automatique (90-98)
Changer la langue affichée dans Django 1.9
Résoudre ABC036 A ~ C avec Python
Démarrez avec SQLite dans un langage de programmation
Comment envelopper C en Python
Apprentissage automatique dans Delemas (acquisition de données)
Python: prétraitement dans l'apprentissage automatique: présentation
[Algorithme de langage C] arbre de recherche binaire
Prétraitement dans l'apprentissage automatique 2 Acquisition de données
Résoudre ABC037 A ~ C avec Python