Dieser verschleierte C-Code behauptet, ohne main() ausgeführt zu werden, aber was tut er wirklich?

Lesezeit: 9 Minuten

Benutzeravatar von Rajeev Singh
Rajeev Singh

#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)

int begin()
{
    printf("Ha HA see how it is?? ");
}

Ruft dies indirekt auf main? wie?

  • Die für expand definierten Makros beginnen, “main” zu sagen. Es ist nur ein Trick. Nichts Interessantes.

    – rghome

    6. April 2016 um 11:19 Uhr

  • Ihre Toolchain sollte die Möglichkeit haben, den vorverarbeiteten Code in einer Datei zu belassen – der eigentlichen Datei, die kompiliert wird – wo Sie sehen werden, dass sie tatsächlich eine main() hat.

    Benutzer1531971

    6. April 2016 um 14:48 Uhr

  • @rghome Warum nicht als Antwort posten? Und es ist angesichts der Anzahl der positiven Stimmen eindeutig interessant.

    – Matsemann

    10. April 2016 um 9:30 Uhr

  • @Matsemann Wow! Ich habe die positiven Stimmen nicht bemerkt. Ich könnte es in eine Antwort ändern, und wenn die Kommentar-Up-Votes Antwort-Up-Votes wären, wäre dies bei weitem meine beste Punktzahl, aber es gibt bereits eine detaillierte Antwort. Ich denke, der Punkt meines Kommentars ist, dass er nicht wirklich interessant ist und daher als Alternative für Leute dient, die die Antwort nicht positiv bewerten möchten. Danke aber für den Hinweis.

    – rghome

    10. April 2016 um 9:37 Uhr


  • Leute, es liegt am Linker als Betriebssystemtool, den Einstiegspunkt festzulegen, und nicht an der Sprache selbst. Sie können sogar unseren eigenen Einstiegspunkt setzen, und Sie können eine Bibliothek erstellen, die auch ausführbar ist! unix.stackexchange.com/a/223415/37799

    – Ho1

    18. April 2016 um 13:16 Uhr


Benutzeravatar von hackks
hackt

Die C-Sprache definiert die Ausführungsumgebung in zwei Kategorien: freistehend und bereitgestellt. In beiden Ausführungsumgebungen wird eine Funktion von der Umgebung zum Programmstart aufgerufen.
In einem freistehend Die Startfunktion des Umgebungsprogramms kann während der Implementierung definiert werden bereitgestellt Umgebung soll es sein main. Kein Programm in C kann ohne Programmstartfunktion auf den definierten Umgebungen laufen.

In Ihrem Fall, main wird durch die Präprozessordefinitionen verborgen. begin() wird erweitern decode(a,n,i,m,a,t,e) zu denen weiter ausgebaut wird main.

int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main() 

decode(s,t,u,m,p,e,d) ist ein parametrisiertes Makro mit 7 Parametern. Ersatzliste für dieses Makro ist m##s##u##t. m, s, u und t sind 4th1st3rd und 2nd Parameter, der in der Ersetzungsliste verwendet wird.

s, t, u, m, p, e, d
1  2  3  4  5  6  7

Rest nützt nichts (nur zum verschleiern). Argument übergeben an decode ist “a,n,ich,m,a,t,e” also die Bezeichner m, s, u und t werden durch Argumente ersetzt m, a, i und nbeziehungsweise.

 m --> m  
 s --> a 
 u --> i 
 t --> n

  • @GrijeshChauhan Alle C-Compiler verarbeiten die Makros, dies ist seit C89 für alle C-Standards erforderlich.

    – jdarthenay

    6. April 2016 um 11:27 Uhr

  • Das ist schlichtweg falsch. Unter Linux kann ich verwenden _start(). Oder noch einfacher kann ich versuchen, den Start meines Programms einfach an der Adresse auszurichten, auf die die IP nach dem Booten eingestellt ist. main() ist C-Standard Bibliothek. C selbst erlegt diesbezüglich keine Beschränkungen auf.

    – ljrk

    6. April 2016 um 12:00 Uhr

  • @hackks Der Standard Bibliothek definiert einen Einstiegspunkt. Die Sprache an sich ist egal

    – ljrk

    6. April 2016 um 12:39 Uhr

  • Können Sie bitte erklären, wie decode(a,n,i,m,a,t,e) werden m##a##i##n? Ersetzt es Zeichen? Können Sie einen Link zur Dokumentation der decode Funktion? Vielen Dank.

    – AL

    6. April 2016 um 14:59 Uhr


  • @Al zuerst begin ist definiert als ersetzt durch decode(a,n,i,m,a,t,e) was vorher definiert ist. Diese Funktion übernimmt die Argumente s,t,u,m,p,e,d und verkettet sie in dieser Form m##s##u##t (## bedeutet verketten). Das heißt, es ignoriert die Werte von p, e und d. Wie du “rufst” decode mit s=a, t=n, u=i, m=m ersetzt es effektiv begin mit main.

    – ljrk

    6. April 2016 um 15:15 Uhr

Benutzeravatar von jdarthenay
jdarthenay

Versuchen Sie es mit gcc -E source.cdie Ausgabe endet mit:

int main()
{
    printf("Ha HA see how it is?? ");
}

Also ein main() Die Funktion wird tatsächlich vom Präprozessor generiert.

Benutzeravatar von NlightNFotis
NightNFotis

Das betreffende Programm tut Anruf main() aufgrund der Makroerweiterung, aber Ihre Annahme ist fehlerhaft – es nicht muss anrufen main() überhaupt!

Genau genommen können Sie ein C-Programm haben und es kompilieren, ohne ein zu haben main Symbol. main ist etwas, das c library erwartet zu springen, nachdem es seine eigene Initialisierung beendet hat. Normalerweise springst du hinein main vom libc-Symbol bekannt als _start. Es ist immer möglich, ein sehr gültiges Programm zu haben, das einfach die Assemblierung ausführt, ohne ein Main zu haben. Schau dir das an:

/* This must be compiled with the flag -nostdlib because otherwise the
 * linker will complain about multiple definitions of the symbol _start
 * (one here and one in glibc) and a missing reference to symbol main
 * (that the libc expects to be linked against).
 */

void
_start ()
{
    /* calling the write system call, with the arguments in this order:
     * 1. the stdout file descriptor
     * 2. the buffer we want to print (Here it's just a string literal).
     * 3. the amount of bytes we want to write.
     */
    asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
    asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}

Kompilieren Sie das obige mit gcc -nostdlib without_main.cund sehen Sie, wie es gedruckt wird Hello World! auf dem Bildschirm, indem Sie einfach Systemaufrufe (Interrupts) in der Inline-Assemblierung ausgeben.

Weitere Informationen zu diesem speziellen Problem finden Sie unter ksplice-Blog

Ein weiteres interessantes Problem ist, dass Sie auch ein Programm haben können, das kompiliert, ohne das zu haben main symbol entsprechen einer C-Funktion. Zum Beispiel können Sie das folgende als sehr gültiges C-Programm haben, das den Compiler nur zum Jammern bringt, wenn Sie die Warnstufe erhöhen.

/* These values are extracted from the decimal representation of the instructions
 * of a hello world program written in asm, that gdb provides.
 */
const int main[] = {
    -443987883, 440, 113408, -1922629632,
    4149, 899584, 84869120, 15544,
    266023168, 1818576901, 1461743468, 1684828783,
    -1017312735
};

Die Werte im Array sind Bytes, die den Anweisungen entsprechen, die zum Drucken von „Hello World“ auf dem Bildschirm erforderlich sind. Für eine detailliertere Darstellung, wie dieses spezielle Programm funktioniert, werfen Sie einen Blick auf dies Blogeintragwo ich es auch zuerst gelesen habe.

Ich möchte eine letzte Bemerkung zu diesen Programmen machen. Ich weiß nicht, ob sie gemäß der C-Sprachspezifikation als gültige C-Programme registriert sind, aber diese zu kompilieren und auszuführen ist sicherlich sehr gut möglich, selbst wenn sie gegen die Spezifikation selbst verstoßen.

  • Ist der Name von _start Teil eines definierten Standards oder ist das nur implementierungsspezifisch? Sicherlich ist Ihr “main as an array” architekturspezifisch. Wichtig ist auch, dass es nicht unvernünftig wäre, dass Ihr “main as an array”-Trick zur Laufzeit aufgrund von Sicherheitseinschränkungen fehlschlägt (obwohl dies wahrscheinlicher wäre, wenn Sie die const Qualifier, und immer noch würden viele Systeme dies zulassen).

    – mah

    7. April 2016 um 20:21 Uhr

  • @mah: _start ist nicht im ELF-Standard, obwohl das AMD64 psABI einen Verweis darauf enthält _start bei 3.4 Prozessinitialisierung. Offiziell kennt ELF nur die Adresse unter e_entryim ELF-Header, _start ist nur ein Name, den die Implementierung gewählt hat.

    – ninjalj

    7. April 2016 um 20:58 Uhr


  • @mah Wichtig ist auch, dass es nicht unvernünftig wäre, dass Ihr „main as an array“-Trick zur Laufzeit aufgrund von Sicherheitseinschränkungen fehlschlägt (obwohl dies wahrscheinlicher wäre, wenn Sie den const-Qualifizierer nicht verwenden würden und viele Systeme dies noch zulassen würden). . Nur wenn die endgültige ausführbare Datei in irgendeiner Weise als etwas Unsicheres erkennbar ist – eine binäre ausführbare Datei ist eine binäre ausführbare Datei, egal wie sie dorthin gelangt ist. Und const spielt keine Rolle – der Symbolname in dieser ausführbaren Binärdatei ist main. Nicht mehr und nicht weniger. const ist ein C-Konstrukt, das zur Ausführungszeit nichts bedeutet.

    – Andreas Henle

    8. April 2016 um 1:01 Uhr

  • @Stewart: Es schlägt sicherlich auf ARMv6l fehl (Segmentierungsfehler). Aber es sollte auf jeder x86-64-Architektur funktionieren.

    – linksherum

    8. April 2016 um 14:36 ​​Uhr


  • @AndrewHenle Eine ausführbare Binärdatei ist eine ausführbare Binärdatei, egal wie sie dorthin gelangt ist – nicht ganz richtig. Eine ausführbare Binärdatei ist kein einzelnes Blob ausführbarer Anweisungen, sondern ein sorgfältig zugeordnetes Blob von Partitionen, von denen einige Anweisungen sind, einige schreibgeschützte Daten und einige Daten, die in Lese-Schreib-Daten initialisiert werden sollen. (Einige) Sicherheitshardware-MMUs können die Ausführung von Seiten verhindern, die nicht als solche gekennzeichnet sind, und dies ist eine gute Funktion, um beispielsweise Stapelüberläufe zu verhindern, die zur Ausführung von Code auf dem Stapel führen, aber leider ist das manchmal legitim oder oft nicht aktiviert.

    – mah

    8. April 2016 um 23:44 Uhr

Jemand versucht, sich wie ein Zauberer zu verhalten. Er glaubt, er kann uns austricksen. Aber wir alle wissen, dass die Ausführung von c-Programmen mit beginnt main().

Das int begin() wird durch ersetzt decode(a,n,i,m,a,t,e) durch einen Durchgang der Vorprozessorstufe. Dann wieder, decode(a,n,i,m,a,t,e) wird durch m##a##i##n ersetzt. Wie durch die Positionszuordnung des Makroaufrufs, s Wille hat einen Charakterwert a. Ebenfalls, u wird durch ‘i’ und ersetzt t wird durch ‘n’ ersetzt. Und so, m##s##u##t wird werden main

Bezüglich, ## Symbol in der Makroerweiterung, es ist der Vorverarbeitungsoperator und führt das Token-Einfügen durch. Wenn ein Makro erweitert wird, werden die beiden Token auf beiden Seiten jedes ‘##’-Operators zu einem einzigen Token kombiniert, das dann das ‘##’ und die beiden ursprünglichen Token in der Makroerweiterung ersetzt.

Wenn Sie mir nicht glauben, können Sie Ihren Code mit kompilieren -E Flagge. Es stoppt den Kompilierungsprozess nach der Vorverarbeitung und Sie können das Ergebnis des Token-Einfügens sehen.

gcc -E FILENAME.c

decode(a,b,c,d,[...]) mischt die ersten vier Argumente und verbindet sie, um eine neue Kennung in der Reihenfolge zu erhalten dacb. (Die verbleibenden drei Argumente werden ignoriert.) Beispiel: decode(a,n,i,m,[...]) gibt die Kennung an main. Beachten Sie, dass dies der Fall ist begin Makro ist definiert als.

deshalb, die begin Makro ist einfach definiert als main.

Benutzeravatar der Community
Gemeinschaft

In deinem Beispiel main() Funktion ist eigentlich vorhanden, weil begin ist ein Makro, durch das der Compiler ersetzt decode Makro, das wiederum durch den Ausdruck m##s##u##t ersetzt wird. Makroerweiterung verwenden ##Sie werden das Wort erreichen main aus decode. Dies ist eine Spur:

begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main

Es ist nur ein Trick main()aber unter Verwendung des Namens main() für die Eingabefunktion des Programms ist in der Programmiersprache C nicht erforderlich. Es hängt von Ihren Betriebssystemen und dem Linker als einem seiner Tools ab.

Unter Windows verwenden Sie nicht immer main()aber eher WinMain oder wWinMainobwohl Sie verwenden können main(), sogar mit Microsofts Toolchain. Unter Linux kann man verwenden _start.

Es liegt am Linker als Betriebssystemtool, den Einstiegspunkt festzulegen, und nicht an der Sprache selbst. Du kannst sogar setzen Sie unseren eigenen Einstiegspunkt, und Sie können eine Bibliothek erstellen, die auch ausführbar ist!

1419220cookie-checkDieser verschleierte C-Code behauptet, ohne main() ausgeführt zu werden, aber was tut er wirklich?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy