Wie kann eine Datei Nullbytes enthalten?

Lesezeit: 10 Minuten

Wie ist es möglich, dass Dateien in Betriebssystemen, die in einer Sprache mit nullterminierenden Zeichenfolgen (nämlich C) geschrieben sind, Nullbytes enthalten können?

Wenn ich zum Beispiel diesen Shell-Code ausführe:

$ printf "Hello\00, World!" > test.txt
$ xxd test.txt
0000000: 4865 6c6c 6f00 2c20 576f 726c 6421       Hello., World!

Ich sehe ein Null-Byte in test.txt (zumindest in OS X). Wenn C nullterminierende Zeichenfolgen verwendet und OS X in C geschrieben ist, warum wird die Datei dann nicht am Nullbyte beendet, was dazu führt, dass die Datei enthält Hello Anstatt von Hello\00, World!? Gibt es einen grundlegenden Unterschied zwischen Dateien und Strings?

  • Bei wiederholter Anwendung von fputc(0, ostream) oder fprintf(ostream, "%c", 0)ist es einfach, eine Datei mit vielen Nullzeichen zu erstellen.

    – chux – Wiedereinsetzung von Monica

    5. Januar 2016 um 20:52 Uhr


  • Strings sind nicht die einzigen Dinge, die in Dateien geschrieben werden können.

    – n. 1.8e9-wo-ist-meine-Aktie m.

    5. Januar 2016 um 20:57 Uhr

  • Hinweis: Viele Sprachen, die nicht C sind, deren Interpreter aber in C geschrieben sind, erlauben immer noch Null-Bytes in Strings. (Zum Beispiel Lua)

    – Benutzer253751

    6. Januar 2016 um 5:19 Uhr

  • Sie gehen davon aus, dass alle Dateien Textdateien sind. Warum ist das so?

    – Trojaner

    6. Januar 2016 um 15:31 Uhr

  • @SamusArin Byte ist die kleinste Menge, die mit der Standardbibliothek verfügbar ist, nicht standardmäßige Bibliotheken können durchaus mit Bits oder irgendetwas anderem umgehen.

    – n. 1.8e9-wo-ist-meine-Aktie m.

    7. Januar 2016 um 15:05 Uhr

Benutzer-Avatar
dbusch

Nullterminierte Zeichenfolgen sind ein C-Konstrukt, das verwendet wird, um das Ende einer Zeichenfolge zu bestimmen, die als Zeichenfolge verwendet werden soll. String-Manipulationsfunktionen wie z strcmp, strcpy, strchrund andere verwenden dieses Konstrukt, um ihre Aufgaben zu erfüllen.

Sie können jedoch weiterhin binäre Daten, die Nullbytes enthalten, innerhalb Ihres Programms sowie in und aus Dateien lesen und schreiben. Sie können sie einfach nicht als Zeichenfolgen behandeln.

Hier ist ein Beispiel dafür, wie das funktioniert:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp = fopen("out1","w");
    if (fp == NULL) {
        perror("fopen failed");
        exit(1);
    }

    int a1[] = { 0x12345678, 0x33220011, 0x0, 0x445566 };
    char a2[] =  { 0x22, 0x33, 0x0, 0x66 };
    char a3[] = "Hello\x0World";

    // this writes the whole array
    fwrite(a1, sizeof(a1[0]), 4, fp);
    // so does this
    fwrite(a2, sizeof(a2[0]), 4, fp);
    // this does not write the whole array -- only "Hello" is written
    fprintf(fp, "%s\n", a3);
    // but this does
    fwrite(a3, sizeof(a3[0]), 12, fp);
    fclose(fp);
    return 0;
}

Inhalt von out1:

[dbush@db-centos tmp]$ xxd out1
0000000: 7856 3412 1100 2233 0000 0000 6655 4400  xV4..."3....fUD.
0000010: 2233 0066 4865 6c6c 6f0a 4865 6c6c 6f00  "3.fHello.Hello.
0000020: 576f 726c 6400                           World.

Für das erste Array, weil wir die verwenden fwrite -Funktion und weisen Sie sie an, 4 Elemente in der Größe von an zu schreiben int, erscheinen alle Werte im Array in der Datei. Sie können der Ausgabe entnehmen, dass alle Werte geschrieben werden, die Werte 32-Bit sind und jeder Wert in Little-Endian-Byte-Reihenfolge geschrieben wird. Wir können auch sehen, dass das zweite und vierte Element des Arrays jeweils ein Null-Byte enthalten, während der dritte Wert, der 0 ist, 4 Null-Bytes hat und alle in der Datei erscheinen.

Wir benützen auch fwrite auf dem zweiten Array, das Elemente vom Typ enthält char, und wir sehen wieder, dass alle Array-Elemente in der Datei erscheinen. Insbesondere ist der dritte Wert im Array 0, der aus einem einzelnen Nullbyte besteht, das auch in der Datei erscheint.

Das dritte Array wird zuerst mit beschrieben fprintf Funktion mit a %s Formatbezeichner, der einen String erwartet. Es schreibt die ersten 5 Bytes dieses Arrays in die Datei, bevor es auf das Null-Byte trifft, wonach es aufhört, das Array zu lesen. Es druckt dann ein Zeilenumbruchzeichen (0x0a) gemäß dem Format.

Das dritte Array wurde erneut in die Datei geschrieben, diesmal mit fwrite. Die Zeichenfolgenkonstante "Hello\x0World" enthält 12 Bytes: 5 für “Hello”, eines für das explizite Null-Byte, 5 für “World” und eines für das Null-Byte, das implizit die String-Konstante beendet. Seit fwrite erhält die volle Größe des Arrays (12), schreibt es alle diese Bytes. Wenn wir uns den Dateiinhalt ansehen, sehen wir tatsächlich jedes dieser Bytes.

Als Randnotiz, in jedem der fwrite -Aufrufe habe ich die Größe des Arrays für den dritten Parameter fest codiert, anstatt einen dynamischeren Ausdruck wie z sizeof(a1)/sizeof(a1[0]) um deutlicher zu machen, wie viele Bytes jeweils geschrieben werden.

  • “NULL” wird am besten verwendet, um a zu beschreiben Nullzeiger konstant oder NULL. Empfehlen Sie “Nullzeichen” oder `’\0’“, um das abschließende Zeichen einer Zeichenfolge zu beschreiben.

    – chux – Wiedereinsetzung von Monica

    5. Januar 2016 um 21:16 Uhr


  • ascii(7) ruft es auf NUL Großbuchstaben, eins L. NULL ist ein anderer Name für (void *)0. Null ist ein Wort in der englischen Sprache, und wie üblich ist die Bedeutung etwas mehrdeutig.

    – Jassen

    6. Januar 2016 um 3:05 Uhr

  • Genauer gesagt, Sie können sie nicht als C-Saiten behandeln. C-Strings sind nicht die einzig mögliche Darstellung von Strings.

    – Aron

    6. Januar 2016 um 7:07 Uhr

  • @Aron: Beispiele (bitte)?

    – samus

    7. Januar 2016 um 15:37 Uhr


  • @SamusArin Zum Beispiel UTF-16, wenn Sie einen Standard-ASCII-C-String in einen UTF-16-String konvertieren, wäre jedes zweite Byte null.

    – Aron

    7. Januar 2016 um 16:01 Uhr

Benutzer-Avatar
Sergej Kalinitschenko

Null-terminierte Strings sind sicherlich nicht das einzige, was Sie in eine Datei einfügen können. Der Betriebssystemcode betrachtet eine Datei nicht als Mittel zum Speichern nullterminierter Zeichenfolgen: Ein Betriebssystem präsentiert eine Datei als eine Sammlung beliebiger Bytes.

Soweit C betroffen ist, existieren E/A-APIs zum Schreiben von Dateien im Binärmodus. Hier ist ein Beispiel:

char buffer[] = {0, 1, 0, 2, 0, 3, 0, 4, 0, 5};
FILE *f = fopen("data.bin","wb");  // "w" is for write, "b" is for binary
fwrite(buffer, 1, sizeof(buffer), f);

Dieser C-Code erstellt eine Datei namens “data.bin” und schreibt zehn Bytes hinein. Beachten Sie, dass obwohl buffer ein Zeichen-Array ist, ist es nicht eine nullterminierte Zeichenfolge.

  • @WeatherVane Danke! Das muss eine Oktalzehn gewesen sein 🙂

    – Sergej Kalinitschenko

    5. Januar 2016 um 21:03 Uhr

  • Nebenbei: Gibt es Wörter, die äquivalent sind zu "ten" = 10(decimal) zum 10(octal) und 10(hexadecimal)?

    – Wetterfahne

    5. Januar 2016 um 21:06 Uhr


  • @WeatherVane Ich habe keine Ahnung, aber es könnte eine gute Frage für english.stackexchange.com sein 🙂

    – Sergej Kalinitschenko

    5. Januar 2016 um 21:09 Uhr

  • @WeatherVane Das Wort für 10(octal) ist „acht“. Das Wort für 10(hexadecimal) ist „sechzehn“. “One Zero” funktioniert auch (für beide), aber nicht “zehn”.

    – Benutzer253751

    6. Januar 2016 um 5:17 Uhr

  • Beachten Sie, dass auf UNIX-ähnlichen Systemen (POSIX-Systemen) die b im offenen Modus ist unerheblich. Auf Windows-Systemen ist die b wirklich wichtig; es wirkt sich auf die Interpretation von Carriage Return- und Control-Z-Zeichen beim Lesen der Daten und auf die Interpretation von Newline-Zeichen beim Schreiben der Daten aus.

    – Jonathan Leffler

    6. Januar 2016 um 14:30 Uhr

Benutzer-Avatar
Jean-Baptiste Yunes

Da eine Datei nur ein Strom von Bytes ist, von irgendein Byte einschließlich Nullbyte. Einige Dateien werden als Textdateien bezeichnet, wenn sie nur eine Teilmenge aller möglichen Bytes enthalten: die druckbaren (grob alphanumerisch, Leerzeichen, Satzzeichen).

C-Strings sind eine Folge von Bytes, die durch ein Nullbyte abgeschlossen werden, nur eine Frage der Konvention. Sie sind zu oft die Quelle der Verwirrung; nur eine Sequenz, die mit null endet, bedeutet, dass jedes Nicht-Null-Byte, das mit null endet, ein korrekter C-String ist! Sogar eines, das ein nicht druckbares Byte oder ein Steuerzeichen enthält. Seien Sie vorsichtig, denn Ihr Beispiel ist kein C-Beispiel! In C printf("dummy\000foo"); wird nie drucken foo wie printf berücksichtigt die C-Saite ab d und endet mit dem Nullbyte in der Mitte. Einige Compiler beschweren sich über ein solches C-String-Literal.

Jetzt gibt es keine direkte Verbindung zwischen C-Strings (die im Allgemeinen auch nur druckbare Zeichen enthalten) und einer Textdatei. Während das Drucken eines C-Strings in eine Datei im Allgemeinen darin besteht, nur seine Teilsequenz von Nicht-Null-Bytes zu speichern.

Benutzer-Avatar
Danny_ds

Während Null-Bytes verwendet werden, um Strings zu beenden und für String-Manipulationsfunktionen benötigt werden (damit sie wissen, wo der String endet), in Binärdateien \0 Bytes können überall sein.

Stellen Sie sich zum Beispiel eine Binärdatei mit 32-Bit-Zahlen vor, sie enthalten alle Null-Bytes, wenn ihre Werte kleiner als 2^24 sind (zum Beispiel: 0x001a00c7 oder 64-Bit 0x0000000a00001a4d).

Dasselbe gilt für Unicode-16, wobei alle ASCII-Zeichen vorangestellt oder nachgestellt sind \0abhängig von ihrer Endianitätund Zeichenfolgen müssen mit enden \0\0.

Viele Dateien haben sogar Blöcke, die (auf 4kB oder sogar 64kB) aufgefüllt sind \0 Bytes, um schnell auf die gewünschten Blöcke zugreifen zu können.

Für noch mehr Null-Bytes in einer Datei werfen Sie einen Blick auf spärliche Dateienwo alle Bytes sind \0 standardmäßig, und Blöcke voller Null-Bytes werden nicht einmal auf der Festplatte gespeichert, um Platz zu sparen.

Betrachten Sie die üblichen C-Funktionsaufrufe zum Schreiben von Daten in Dateien — write(2):

ssize_t
write(int fildes, const void *buf, size_t nbyte);

… und fwrite(3):

size_t
fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);

Keine dieser Funktionen akzeptiert a const char * NUL-terminierte Zeichenfolge. Stattdessen nehmen sie ein Array von Bytes (a const void *) mit einer expliziten Größe. Diese Funktionen behandeln NUL-Bytes genauso wie jeden anderen Byte-Wert.

Bevor Sie irgendetwas beantworten, beachten Sie dies bitte

(Hinweis: laut nm (siehe Kommentar im OP) “a Byte ist die kleinste verfügbare Menge, die auf die Festplatte geschrieben werden kann mit der C-StandardbibliothekNicht-Standard-Bibliotheken können durchaus mit Bits oder irgendetwas anderem umgehen.” Was ich unten gesagt habe, dass WORD-Größen die kleinste Menge sind, ist wahrscheinlich nicht sehr wahr, bietet aber dennoch Einblick).

NULL ist immer 0_dezimal (praktisch)

dec: 0
hex: 0x00000000
bin: 00000000 00000000 00000000 00000000

obwohl der tatsächliche Wert durch die Spezifikation einer Programmiersprache definiert ist, verwenden Sie also eine definierte Konstante NULL statt Hardcoding 0 überall (falls es sich ändert, wenn die Hölle zufriert).

ASCII Die Codierung für das Zeichen „0“ ist 48_decimal

dec: 48
hex: 0x00000030
bin: 00000000 00000000 00000000 00110000

Das Konzept von NULL existiert nicht in einer Datei, sondern in der Programmiersprache der generierenden App. Nur die numerische Kodierung/Wert von NULL existiert in einer Datei.

Wie ist es möglich, dass Dateien in Betriebssystemen, die in einer Sprache mit nullterminierenden Zeichenfolgen (nämlich C) geschrieben sind, Nullbytes enthalten können?

Mit dem oben Gesagten wird diese Frage, Wie kann eine Datei 0 enthalten? Die Antwort ist jetzt trivial.

Wenn ich zum Beispiel diesen Shell-Code ausführe:

$ printf "Hello\00, World!" 
test.txt $ xxd test.txt 0000000: 4865
6c6c 6f00 2c20 576f 726c 6421            Hello., World!

Ich sehe ein Nullbyte in test.txt (zumindest in OS X). Wenn C nullterminierende Zeichenfolgen verwendet und OS X in C geschrieben ist, warum wird die Datei dann nicht am Nullbyte beendet, was dazu führt, dass die Datei enthält Hello Anstatt von Hello\00, World!?

Gibt es einen grundlegenden Unterschied zwischen Dateien und Strings?

Angenommen ein ASCII Zeichenkodierung (1-Byte/8-Bit-Zeichen im Dezimalbereich von 0 und 127):

  • Saiten sind Puffer/Zeichen-Arrays von 1-Byte-Zeichen (wobei NULL = 0_dezimal und ‘0’ = 48_dezimal)).
  • Dateien sind Sequenzen von entweder 32-Bit oder 64-Bit “WÖRTER” (abhängig von Betriebssystem und Hardware, dh x86 bzw. x64).

Daher eine 32-Bit-Betriebssystemdatei, die nur enthält ASCII Zeichenfolgen sind eine Folge von 32-Bit (4-Byte)-Wörtern, die zwischen den Dezimalwerten 0 und 127 liegen und im Wesentlichen nur das erste Byte des 4-Byte-Wortes verwenden (b2: Basis-2, Dezimal ist Basis-10 und Hex Base-16, zur Info)

  0_b2: 00000000 00000000 00000000 00000000
 32_b2: 00000000 00000000 00000000 00100000
 64_b2: 00000000 00000000 00000000 01000000
 96_b2: 00000000 00000000 00000000 01100000
127_b2: 00000000 00000000 00000000 11111111
128_b2: 00000000 00000000 00000001 00000000

Ob dieses Byte ganz links oder ganz rechts ist, hängt vom Betriebssystem ab Endianität.

Aber um Ihre Frage zu den Vermissten zu beantworten NULL nach Hello\00, World! Ich gehe davon aus, dass es durch die ersetzt wurde EOL/EOF (Ende der Datei) Wert, der höchstwahrscheinlich nicht druckbar ist und deshalb nicht im Ausgabefenster angezeigt wird.

Notiz: Ich bin sicher, dass moderne Betriebssysteme (und klassische Unix-basierte Systeme) die Speicherung von optimieren ASCII Zeichen, so dass 1 Wort (4 Bytes) in 4 Zeichen gepackt werden kann. Die Dinge ändern sich mit UTF Da diese Codierungen jedoch mehr Bits zum Speichern von Zeichen verwenden, da sie größere Alphabete/Zeichensätze darstellen müssen (wie 50.000 Kanji/japanische Zeichen). Ich finde UTF-8 ist analog zu ASCIIund aus Gründen der Einheitlichkeit umbenannt (with UTF-16 und UTF-32).

Notiz: C/C++ “packt” tatsächlich 4 Zeichen in ein einziges 4-Byte-Wort, indem es Zeichen-Arrays (dh Strings) verwendet. Da jedes Zeichen 1 Byte groß ist, weist der Compiler es dem Stack oder Heap arithmetisch als 1 Byte zu und behandelt es. Wenn Sie also ein Array in einer Funktion (dh einer Auto-Variablen) deklarieren, so

char[] str1[7] = {'H','e','l','l','o','!','\0'};

wo der Funktionsstapel bei Adresse 1000_b10 (Basis-10/dezimal) beginnt, dann haben Sie:

072 101 108 108 111 033

addr  char        binary   decimal
----  ----------- -------- -------
1000: str1[0] 'H' ‭01001000‬ (072)
1001: str1[1] 'e' ‭01100101‬ (101)
1002: str1[2] 'l' ‭01101100‬ (108)
1003: str1[3] 'l' ‭01101100‬ (108)
1004: str1[4] 'o' ‭01101111‬ (111)
1005: str1[5] '!' ‭00100001‬ (033)
1006: str1[6] '0' 00000000 (000)

Da RAM byteadressierbar ist, verweist jede Adresse auf ein einzelnes Byte.

1366090cookie-checkWie kann eine Datei Nullbytes enthalten?

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

Privacy policy