[LINUX] Einbettung der Maschinensprache in die Sprache C.

Einführung

Vor kurzem hatte ich eine eingeschränkte C-Programmieraufgabe im Tweet unter https://twitter.com/reiya_2200/status/1130761526959267841.

image.png

Das, was mir in den Sinn kommt, ist die Hauptrekursion, aber sie ist nicht sehr interessant, also habe ich versucht, sie in die Maschinensprache einzubetten, also habe ich sie als Material ausprobiert.

Voraussetzung ist x86_64 Linux + gcc und die Reproduktionsumgebung ist Ubuntu18 (WSL / Windows10). ** Selbst unter demselben Linux kann sich das Verhalten in verschiedenen Umgebungen erheblich ändern **. Bitte seien Sie vorsichtig, um nicht schlecht zu sein.

Gehorsame Einbettung

Erster Code

Tweet war also ** der folgende Code, in den die Maschinensprache gehorsam eingebettet war **.

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));
}

Bei der Ausführung wird die Summe der im Argument angegebenen einstelligen Zahlen wie unten gezeigt ordnungsgemäß ausgegeben.

Kompilieren / ausführen


$ 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

Ich denke, es ist unnötig für diejenigen, die daran gewöhnt sind, aber ich werde es vorerst erklären.

Funktionsimplementierung und Maschinensprache

Die Richtlinie besteht darin, den Teil zu implementieren, der die Summe als Funktion berechnet und in eine Maschinensprache übersetzt.

Für eine einfache Implementierung können Sie sich Code wie folgt vorstellen:

sum.c


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

Das Argument "pv" geht davon aus, dass "argv" übergeben wird. Das durch "argv" angegebene Array "char *" wird am Ende mit einem NULL-Zeiger abgeschlossen, sodass es tatsächlich ohne "argc" verarbeitet werden kann.

Wenn Sie dies kompilieren und die Maschinensprache überprüfen, sieht es folgendermaßen aus:

Kompilieren / rückwärts montieren


$ 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

Dies bedeutet, dass die Maschinensprache des Funktionsteils eine Codesequenz von 31, c0,48,83, ... hexadezimal ist. Wenn dies in druckbaren Zeichen im ASCII-Zeichenbereich ausgedrückt wird, kann es als "1 \ xc0H \ x83 \ xc7 \ bH \ x8b \ 27H \ x85 \ xd2t \ t \ xf \ xbe \ 22 \ 215D \ 20 " ausgedrückt werden. Es wird xd0 \ xeb \ xeb \ xc3 "` sein.

Beachten Sie, dass gccs -Os (Größenoptimierung) und andere Optionen, die verhindern, dass zusätzliche Verarbeitung in den Code eingebettet wird, angegeben werden, um den Code zu verkürzen. Ich wollte eine Option **, um es wenn möglich als ASCII-Zeichen zu kürzen, aber ich kann nichts dagegen tun, weil es nicht existiert.

Maschinensprache einbetten

Also habe ich die Maschinensprache des Funktionsteils verstanden.

Sie können dies als normale Zeichenfolge in Ihren Code einbetten.

Jetzt müssen Sie nur noch die Zeichenfolgenadresse als Funktionsadresse behandeln und damit die Funktion aufrufen. In printf ist es ((int (*) (void *)) s) (argv) undint (*) (void *)ist der Adresstyp dieser Funktion. (Ein Zeiger auf eine Funktion, die "void *" als Argument verwendet und "int" zurückgibt) und in diese umwandelt.

emb1.c(Erneut veröffentlichen)


#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));
}

Es sind jedoch zwei Punkte zu beachten. Es geht um die Platzierung der Zeichenfolge s.

Derzeit gibt es einen Mechanismus, der verhindert, dass unerwartete Speicherbereiche als Code ausgeführt werden. Ohne die oben genannten Spezifikationen verursacht SEGV, selbst wenn Sie versuchen, den entsprechenden Maschinensprachencode auszuführen. Dies ist erforderlich, wenn der Compiler nicht korrekt feststellen kann, ob es sich um ausführbaren Code handelt, wie in diesem Fall.

Eingebettet direkt in die Hauptfunktion

Implementierungsübersicht

Ich bin jedoch ein wenig unzufrieden mit dem obigen Code. Dies liegt daran, dass das Attribut die ELF-Abschnittsstruktur sichtbar macht.

Gibt es eine intelligentere Möglichkeit, Maschinensprache einzubetten? Also habe ich den folgenden Code geschrieben.

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);
}

Sie können sehen, dass es immer noch funktioniert.

Kompilieren / ausführen


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

Samen und Spielereien

Natürlich sind die Samen einfach. Wenn Sie den Pseudobefehl ".string" mit dem Befehl "asm volatile" angeben, der den Assembler-Code in den C-Sprachcode einbettet, wird ** die Maschinensprache, die der Zeichenfolge entspricht, an dieser Stelle eingebettet **.

Lassen Sie uns das nach dem Kompilieren erstellte "a.out" rückwärts zusammenbauen und überprüfen.

Rückwärts montieren


$ 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>

Dies ist der Teil, in den 48,83, c6,08 an Adresse 659 bis b0,00 an Adresse 672 eingebettet sind. Der Befehl an der Adresse 672 wird nicht tatsächlich verarbeitet, sondern als Befehl mit der Endung 00 vorbereitet, damit das NUL-Zeichen (00) zur eingebetteten Zeichenfolge hinzugefügt werden kann.

Originalcode

Nun zu diesem eingebetteten Code.

Dies setzt einfach die folgende Verarbeitung voraus. Mit anderen Worten, speichern wir das Berechnungsergebnis so, wie es mit argc als Summe ist.

Originalcode


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

Übrigens wird gemäß der Funktionsaufrufkonvention von x86_64 Linux das erste Argument "argc" im rdi-Register gespeichert (32-Bit-Teil ist edi) und das zweite Argument "argv" wird im rsi-Register gespeichert. Wenn Sie sie also direkt schleifen, ist dies in Ordnung.

Anwendbare Montage


 659:   add    $0x8,%rsi      #Erweitern Sie argv um ein Element
 65d:   mov    (%rsi),%rax    #Laden Sie argv-Elemente in rax
 660:   test   %rax,%rax      #NULL Zeiger Urteil
 663:   je     674            #Wechseln Sie unmittelbar nach dem eingebetteten Teil, wenn ein NULL-Zeiger erkannt wird
 665:   movsbl (%rax),%eax    #Lesen Sie Zeichen in eax
 668:   lea    -0x31(%rdi,%rax,1),%edi # argc(edi)Hinzufügen
 66c:   mov    %edi,0xc(%rsp) #Speichern Sie edi im Stapelbereich
 670:   jmp    659            #Zum Anfang des eingebetteten Teils springen
 672:   mov    $0x0,%al       #Dummy-Anweisung

Ohne Optimierung schien das während printf verwendete argc den Wert zu verwenden, der einmal im Stapel gespeichert war, nicht direkt im Register. Daher wird das Ergebnis der Änderung des esi-Registers durch den Befehl an der Adresse 66c im Stapel wiedergegeben.

Im Gegensatz dazu wird bei der Optimierung der Stapelbereich zum Speichern von "argc" nicht vorbereitet, so dass der Befehl an der Adresse 66c den Stapel zerstört. Die Ausgabe ist also wie beabsichtigt, aber beachten Sie, dass sie SEGV am Ausgang von "main" verursacht.

Geben Sie Optimierungsoptionen an


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

Am Ende

Was haben Sie gedacht. Ich führte den Code ein und dachte, ich könnte das Gefühl fühlen, das Stufe -9 von [Korrupter C-Programmierer Stufe -10] entspricht (http://d.hatena.ne.jp/w_o/20060808#p4).

Schließlich werde ich vorerst auch die Hauptrekursive belassen, die die erwartete Lösung des Fragestellers zu sein scheint (weil sie vorerst zusammengestellt werden kann!).

Angenommene Lösung?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

Einbettung der Maschinensprache in die Sprache C.
Heap-Sortierung in C-Sprache
Modultest mit mehreren Instanzen in C-Sprache
Realisieren Sie die Schnittstellenklasse in der Sprache C.
Segfo mit 16 Zeichen in C-Sprache
Linkliste (list_head / queue) in C-Sprache
Generieren Sie mit Python eine C-Sprache aus dem S-Ausdruck
So steuern Sie Multiprozesse ausschließlich in C-Sprache
Richten Sie einen UDP-Server in der Sprache C ein
Verarbeiten Sie Signale in C-Sprache
C-Sprache ALDS1_3_B Warteschlange
Greifen Sie auf MongoDB in C zu
Weiter Python in C-Sprache
[C-Sprachalgorithmus] Endianness
C-API in Python 3
Versuchen Sie, ein Python-Modul in C-Sprache zu erstellen
Versuchen Sie, Python mit pybind11 in ein C ++ - Programm einzubetten
Gehen Sie in die Sprache, um Teil 7 C in der Sprache GO zu sehen und sich daran zu erinnern
[C-Sprachalgorithmus] Blockbewegung
Erweitern Sie Python in C ++ (Boost.NumPy)
C-Sprache ALDS1_4_B Binäre Suche
Maschinelles Lernen in Delemas (Praxis)
Verwenden Sie reguläre Ausdrücke in C.
Hinweise zum Einbetten der Skriptsprache in Bash-Skripte
Hinweis 2 zum Einbetten der Skriptsprache in ein Bash-Skript
Nachahmung von Pythons Numpy in C #
Binäre Suche in Python / C ++
Ich habe versucht, die Zeit und die Zeit der C-Sprache zu veranschaulichen
Hallo Welt in GO-Sprache
[C Sprache] readdir () vs readdir_r ()
Wird in EDA für maschinelles Lernen verwendet
C-Sprache ALDS1_4_A Lineare Suche
Minimaler Gesamtflächenbaum in C #
Einführung in die in C Language Part 1 Server Edition erlernte Socket-API
Schreiben Sie einen tabellengesteuerten Test in C.
Versuchen Sie, Yuma in der Sprache Go zu implementieren
Versuchen Sie, die in TensorFlow 0.12 hinzugefügte Visualisierung einzubetten
[Sprachverarbeitung 100 Schläge 2020] Kapitel 6: Maschinelles Lernen
Funktionszeiger und objdump ~ C language ~
100 Sprachverarbeitung Knock Kapitel 1 in Python
Schreiben der C-Sprache mit Sympy (Metaprogrammierung)
Automatisieren Sie Routineaufgaben beim maschinellen Lernen
ABC166 in Python A ~ C Problem
Programmiersprache für hohe Energieeffizienz C.
Einführung in Protobuf-c (C-Sprache ⇔ Python)
100 Sprachverarbeitung Knock 2020 Kapitel 6: Maschinelles Lernen
Klassifikation und Regression beim maschinellen Lernen
Beim Lesen der C ++ - Struktur mit Cython
100 Sprachverarbeitung Knock 2020 Kapitel 10: Maschinelle Übersetzung (90-98)
Wechseln Sie die in Django 1.9 angezeigte Sprache
Löse ABC036 A ~ C mit Python
Erste Schritte mit SQLite in einer Programmiersprache
So verpacken Sie C in Python
Maschinelles Lernen in Delemas (Datenerfassung)
Python: Vorverarbeitung beim maschinellen Lernen: Übersicht
[C-Sprachalgorithmus] binärer Suchbaum
Vorverarbeitung beim maschinellen Lernen 2 Datenerfassung
Löse ABC037 A ~ C mit Python