PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C: Argument vs. String, wo istder Unterschied?



Los_Andros
15-05-2007, 10:33
Hallo zusammen,
ich habe hier die rijndael AES Schlüssel Beispielprogramme und verzweifel dabei diese bei mir in mein Programm zu integrieren.

Das ist mein Problem:

Das C Programm encrypt verschlüsselt einen Text folgendermaßen:


echo "Hallo Welt" > text.txt
cat text.txt | encrypt hugo text.bin

Der Passphrase ist also Hugo und der verschlüsselte Text wird nach text.bin geschrieben.

Das C Programm decrypt entschlüsselt den Text wieder wie folgt und gibt den entschlüsselten Text nach stdout aus:


decrypt hugo text.bin
Hallo Welt


Jetzt will ich stufenweise die Funktion in mein C Programm integrieren (ohne popen oder so).
Nur scheitere ich schon am einfachsten Problem.



auszug encrypt.c:

int main(int argc, char **argv)
{
unsigned long rk[RKLENGTH(KEYBITS)];
unsigned char key[KEYLENGTH(KEYBITS)];
int i;
int nrounds;
char *password;
FILE *output;

password = argv[1];
for (i = 0; i < sizeof(key); i++)
key[i] = password != 0 ? *password++ : 0;
....


Ich habe mir gedacht ich tausche das in


password = "hugo";

kompiliere und teste, ob das noch geht. Ergebnis: ich kann des Text nicht mehr entschlüsseln, es handelt sich wohl um das falsche Passwort.

Nun meine Frage, wo ist der Unterschied in einem Argument und einem Text String?

nul
15-05-2007, 13:22
nur so ne Ahnung, versuchs aber mal mit

password = "hugo\0";

Los_Andros
15-05-2007, 13:47
leider schon in allen Varianten probiert:

password = "hugo\0";
password = "hugo\n\0";
password = "hugo\0\n";
password = "hugo\n";

Alles ohne Erfolg ..... :-(

lokicall
15-05-2007, 13:59
password = argv[1];

muss da nicht ein strcpy stehen, weil das ja Zeichenketten sind?

sommerfee
15-05-2007, 14:22
Nun meine Frage, wo ist der Unterschied in einem Argument und einem Text String?

Nirgendwo. (Ok, stimmt nicht ganz, die Unterschiede sind aber in der Regel irrelevant.)


password = argv[1]; und
password = "hugo"; sind identisch, wenn das erste Argument "hugo" ist.

Vielleicht liegt der Fehler woanders, wird noch argc abgefragt etc.?

Liebe Grüße,
Axel

Nachtrag: Hast du eine Bezugsquelle für dein encrypt.c, welches du verwendest, für uns?

P.S. @lokicall: Nein, kein strcpy, da "password" ein uninitialisierter Zeiger ist würde dies zum Absturz führen.

Los_Andros
15-05-2007, 14:39
also ich kopiers einfach mal rein, den Link hab ich grad nicht parat:

Als Argument übergebe ich beim aufruf
hugo

deshalb sollte das doch identisch sein mit
password = "hugo";



#include <stdio.h>
#include "rijndael.h"

#define KEYBITS 256

int main(int argc, char **argv)
{
unsigned long rk[RKLENGTH(KEYBITS)];
unsigned char key[KEYLENGTH(KEYBITS)];
int i;
int nrounds;
char *password;
FILE *output;

password = argv[1];
for (i = 0; i < sizeof(key); i++)
key[i] = password != 0 ? *password++ : 0;
output = fopen(argv[2], "wb");
if (output == NULL)
{
fputs("File write error", stderr);
return 1;
}
nrounds = rijndaelSetupEncrypt(rk, key, 256);
while (!feof(stdin))
{
unsigned char plaintext[16];
unsigned char ciphertext[16];
int j;
for (j = 0; j < sizeof(plaintext); j++)
{
int c = getchar();
if (c == EOF)
break;
plaintext[j] = c;
}
if (j == 0)
break;
for (; j < sizeof(plaintext); j++)
plaintext[j] = ' ';
rijndaelEncrypt(rk, nrounds, plaintext, ciphertext);
if (fwrite(ciphertext, sizeof(ciphertext), 1, output) != 1)
{
fclose(output);
fputs("File write error", stderr);
return 1;
}
}
fclose(output);
}

Los_Andros
15-05-2007, 14:40
ach ja, hier:
http://www.efgh.com/software/rijndael.htm

sommerfee
15-05-2007, 15:15
Mache mal folgendes:



decrypt hugo text.bin
copy text.bin aaa.bin
decrypt hugo aaa.bin


Daran sieht man sehr schön, daß der Originalcode schon murks ist.

Das Problem liegt hier:


for (i = 0; i < sizeof(key); i++)
key[i] = password != 0 ? *password++ : 0;


Er kopiert hier gnadenlos 8 Bytes, egal wie lang das Passwort ist oder nicht. Was aber genau hinter dem "hugo" (und dem abschließenden 0-Byte) steht, weiß aber nur der Wind, bzw. genauer gesagt der Startup-Code des Compilers. In der Regel werden hier ein paar 0-Bytes und "text.bin" (also das nächste Argument) stehen, daher klappt es in der Regel, solange der Dateiname ebenfalls gleich ist und für beide Programme der gleiche Compiler verwendet wurde.

Ändere das mal in encrypt.c und decrypt.c nach:


for (i = 0; i < sizeof(key); i++)
key[i] = *password != 0 ? *password++ : 0;


Und viola, jetzt klappt es auch, wenn man text.bin nach aaa.bin kopiert und es dort heraus dekodiert. Und jetzt mache mal weiter und ersetze


password = argv[1];

durch


passwort = "hugo";

und auch das wird dann auf wundersame Weise funktionieren...

Liebe Grüße,
Axel

Los_Andros
15-05-2007, 15:28
wow ..... danke.
Ich gebe zu, ich verstehe es nicht ganz, aber du hast recht, funktioniert einwandfrei.

Der Unterschied ist einmal password, dan andere Mal *password.

Und genau diese Zeile habe ich nicht richtig verstanden, kein wunder, dass es nicht geht. Habe vorher immer probiert sogar in Hex den string auszugeben, um Unterschiede festzustellen.

sommerfee
15-05-2007, 15:56
Ich gebe zu, ich verstehe es nicht ganz, aber du hast recht, funktioniert einwandfrei.

Der Unterschied ist einmal password, dan andere Mal *password.

Ich versuche es mal genauer zu erklären:



for (i = 0; i < sizeof(key); i++)
key[i] = password != 0 ? *password++ : 0;


Dieser Code versucht sich die ersten 8 (=sizeof(key)) Zeichen des Passwortes zu kopieren. Ist das Passwort kürzer, soll key[] mit passenden 0-Bytes aufgefüllt werden, deswegen die Fallunterscheidung bei der Zuweisung.

Daß der * vor "password != 0" fehlt, ist schlicht und einfach ein Bug. Die Abfrage "password != 0" (identisch mit "password != NULL") ergibt keinen Sinn, da argv[1] und damit auch password niemals ein NULL-Pointer ist. Stattdessen muß das aktuelle Zeichen (also *password) auf das Ende des Wortes abgetestet werden.

Also ist der "soll"-Zustand:

key[0] = 'h';
key[1] = 'u';
key[2] = 'g';
key[3] = 'o';
key[4] = '\0';
key[5] = '\0';
key[6] = '\0';
key[7] = '\0';

Den Code dafür könnte man übrigens auch viel einfacher und verständlicher kodieren:



strncpy( key, password, sizeof( key ) );


Kopiert max. sizeof( key ) Zeichen und füllt ggf. mit Leerzeichen auf, also genau das, was man haben möchte.

Zurück zum Bug: Durch den Bug ist aber z.B. folgender "ist"-Zustand daraus geworden:

key[0] = 'h';
key[1] = 'u';
key[2] = 'g';
key[3] = 'o';
key[4] = '\0';
key[5] = 't'; /* Vermutlich steht der Anfang von "text.bin" hinter "hugo"!?*/
key[6] = 'e';
key[7] = 'x';

Auch diesen Code könnte man einfacher haben, wenn man denn wollte:



memcpy( key, password, sizeof( key ) );


Was genau daraus geworden ist, weiß nur der Startup-Code des Compilers, aber auf jeden Fall waren die letzten 3 Bytes von key[] Murks und dieser Murks hing eben davon ab, was *hinter* dem Passwort stand. Wenn du also "password = "hugo"" geschrieben hattest, stand hinter "hugo" was anderes als wenn dort "password = argv[1]" stehen würde. Und deswegen kam ein anderes Ergebnis dabei heraus.

Stelle dir vor, du schreibst:


const char string[] = "abc";

und greifst anschließend auf string[4] zu, das wäre der gleiche Murks, weil nur string[0] bis string[3] ein wohldefiniertes Ergebnis haben.

Wenn noch Fragen offen sind: Immer her damit.

Liebe Grüße,
Axel

Los_Andros
15-05-2007, 16:21
Ahhhh
Katsching, verstanden, danke für die ausführliche Erklärung.
Hätte ich also zufällig als Passwort "12345678" genommen, hätte ich den Bug nicht bemerkt.
Was passiert in dem Fall, dass
password
größer 8 Zeichen ist?

Schneidet er hier ab oder nimmt er 8 Byte Blöcke ....?

sommerfee
15-05-2007, 18:55
Hätte ich also zufällig als Passwort "12345678" genommen, hätte ich den Bug nicht bemerkt.

Wenn das mit den 8 Zeichen stimmen würde, dann ja. Da es aber nicht stimmt (siehe unten), hätte man erst ab Passwörtern mit mindestens 32 Zeichen den Fehler nicht bemerkt.


Was passiert in dem Fall, dass password größer 8 Zeichen ist?

Schneidet er hier ab oder nimmt er 8 Byte Blöcke ....?

Er schneidet einfach ab.

Allerdings nicht nach 8 Zeichen, sondern nach KEYBITS/8 = 256/8 = 32 Zeichen, da war ich wohl zu blöde zum Rechnen, sorry.

Liebe Grüße,
Axel

Los_Andros
16-05-2007, 08:08
gracie.

danke für die Hilfe

jan61
16-05-2007, 23:07
...


strncpy( key, password, sizeof( key ) );

...


memcpy( key, password, sizeof( key ) );



Vorsicht! Das mag dann gut gehen, wenn alle Routinen, die key benutzen, explizit nur die Bytes key[0] bis key[sizeof(key)-1] auslesen. Jede String-Funktion ([s|f]printf, strcat, strcpy, ...) kann gegen die Wand fahren, wenn password>=sizeof(key) ist. Es fehlt nämlich das in C für char* verbindliche \0 am Ende, da in diesem Fall jedes Byte von key mit Werten aus password belegt wird. Ein typischer Fall für Stack-Overflow- oder Buffer-Overflow-Angriffe.

Sicherer ist es, die Größe des char-Array von vornherein um ein Byte größer als die gewünschte Anzahl zu deklarieren und bei Copy-Funktionen _immer_ sizeof(key)-1 anzugeben - vorher initialisieren ist auch nicht schlecht, nicht alle Compiler machen das bei der Deklaration automatisch.

Das wirkt sich besonders fatal dann aus, wenn key als char* an Funktionen übergeben wird, die kennen nämlich die key-Deklaration nicht und wissen also nicht, wie lang er sein soll.

Beispiel:

jan@jack:~/tmp> cat buf_ovfl.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char ziel[32];
char *quelle = "1234567890123456789012345678901234567890";
memcpy(ziel,quelle,sizeof(ziel));
printf("%s\n", ziel);
return(0);
}
jan@jack:~/tmp> make buf_ovfl
cc buf_ovfl.c -o buf_ovfl
jan@jack:~/tmp> ./buf_ovfl
12345678901234567890123456789012�@�


Besser:

jan@jack:~/tmp> cat buf_ovfl.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char ziel[33];
char *quelle = "1234567890123456789012345678901234567890";
memset(ziel, 0, sizeof(ziel));
memcpy(ziel,quelle,sizeof(ziel)-1);
printf("%s\n", ziel);
return(0);
}
jan@jack:~/tmp> make buf_ovfl
cc buf_ovfl.c -o buf_ovfl
jan@jack:~/tmp> ./buf_ovfl
12345678901234567890123456789012

Jan

Los_Andros
31-05-2007, 10:07
Irgendwie komme ich nicht so richtig auf nen richtigen Weg.
Also Passwort einbauen hat super funktioniert, jetzt habe ich aber noch das folgende Problem:

Ich erzeuge in meinem C Programm einen String mit ner Menge Systeminformationen und nem Zeitstempel.

Der Zeichenstring sieht beispeilsweise wie folgt aus:
guest ! system ! 2.6.18.8-0.3-default ! 1180405715 ! 08323584 ! ....


Bei dem ursprünglichen "encrypt" wurde das so gemacht:
cat datei.txt | encrypt binäres_output_file.bin
Entschlüsselt mit:
decrypt binäres_output_file.bin


Das hat prima funkitoniert, aber man sieht deutlich, das mit dem cat ist Blödsinn, ich will im C Programm direkt verschlüsseln, ohne externen Aufruf.
Also habe ich meinen encrypt wie folgt modifiziert:



...
char *license_key = "12345-hugo";
unsigned long rk[RKLENGTH(KEYBITS)];
unsigned char key[KEYLENGTH(KEYBITS)];
...
FILE *cryptfile;
for (i = 0; i < sizeof(key); i++)
key[i] = *license_key != 0 ? *license_key++ : 0;
cryptfile = fopen("encrypt.bin", "wb");
if (cryptfile == NULL) {
fputs("File write error", stderr);
return 1;
}
nrounds = rijndaelSetupEncrypt(rk, key, 256);
char plaintext[sizeof(textstring)] = "";
unsigned char ciphertext[256];
strcat(plaintext,textstring);
printf("verschluesselnder Text: %s\n",plaintext);
rijndaelEncrypt(rk, nrounds, plaintext, ciphertext);
if (fwrite(ciphertext, sizeof(ciphertext), 1, cryptfile) != 1) {
fclose(cryptfile);
fputs("File write error", stderr);
return 1;
}



soweit so gut, (das mit dem strcat(plaintext,textstring) war nur ein Test, habs auch direkt probiert)
Jetzt mache ich meinen "decrypt" und erhalte folgendes Ergebnis, hier mal soll und ist:

IST:
guest ! systemxy9¦û·mç·°öú·

SOLL
guest ! systemxyz ! 2.6.18.8-0.3-default ! 1180405715 ! 08323584 ! ....



Entschlüsselnt scheint also teilweise zu klappen, aber eben nur teilweise ... :confused: