Nieuws:

Welkom, Gast. Alsjeblieft inloggen of registreren.
Heb je de activerings-mail niet ontvangen?

Auteur Topic: [opgelost][C] string van 6karakters  (gelezen 1578 keer)

Offline gieterke

  • Lid
[opgelost][C] string van 6karakters
« Gepost op: 2010/12/25, 21:55:36 »
Hallo,

Ik ben C aan het leren. Nu wou ik eerst een tekst inlezen, en daarna terug printen in de console.

Mijn code is:
#include <stdio.h>

int main(void)
{
  char x[5];
  scanf("%s", &x);
  printf("adres: %x\nstring: %s\n", &x, x);
  system("pause");
  return 0;
}
Nu is het probleem dat als ik een string wil invoeren die ook spaties bevat alleen het deel voor de spatie gebruikt wordt en als ik een string intyp van meer dan 6karakters wordt deze wel geprint.
Kan iemand mij dit uitleggen alstublieft? Alvast bedankt.
« Laatst bewerkt op: 2010/12/29, 17:50:16 door gieterke »

Offline Joshua822

  • Lid
Re: [C] string van 6karakters
« Reactie #1 Gepost op: 2010/12/25, 23:05:52 »
Sorry dat ik het zeg, maar je doet het helemaal verkeerd!

Je mag nooit de scanf() functie gebruiken. Als je dat wel doet, loop je namelijk risico om een groot probleem genaamd buffer overflowing te creëren.

Want kijk, je hebt namelijk een character array met vijf element aangemaakt. Daar kun je slechts vijf karakters in opslaan. Wat gebeurt er nu als je probeert om een zesde karakter op te slaan? Dat karakter wordt zonder probleem opgeslagen! Maar waar wordt het dan opgeslagen als er maar ruimte is voor vijf karakters in de array? Het antwoord daarop is dat je dat niet weet. Het kan zijn dat je dat karakter opslaat in een ongebruikte geheugenlocatie en dat er niets aan de hand is, maar het kan ook zijn dat je het geheugen van een ander proces corrupt maakt en dat het andere proces hierdoor crasht.

scanf() zorgt voor dit potentiële probleem omdat het niet weet hoeveel karakters opgeslagen zouden mogen worden in de character array. Het krijgt alleen maar een adres waar het kan beginnen met het opslaan van de data die scanf() inleest.

Om dit probleem te voorkomen moet je in plaats van scanf() fgets() gebruiken.

Een call naar fgets() om de invoer van de gebruiker uit te lezen ziet er als volgt uit:
fgets ( char * bestemming, int nummer_van_te_lezen_karakters, stdin );
  • char * bestemming: een pointer naar het eerste element van de character array waar de gelezen karakters moeten worden opgeslagen ( als je onbekend bent met pointers, schrijf hier dan gewoon "&naam_van_de_character_array" )
  • int nummer_van_te_lezen_karakters: de hoeveelheid karakters die de functie moet lezen. Dit zal meestal de grootte zijn van de array die als bestemming is opgegeven.
  • stdin: als je in de terminal iets invoert en op Enter drukt, wordt dit niet meteen naar het actieve proces gestuurd, maar naar een bestand genaamd stdin gestuurd. Dat kan je programma dan weer uitlezen om te achterhalen wat de gebruiker heeft ingetypt. Hier kan eigenlijk een file descriptor komen naar eender welk bestand, maar als je een beginner bent en hiermee noch onbekend bent, moet je gewoon onthouden dat je hier "stdin" moet intypen.

Hier is jouw verbeterde programma:
#include <stdio.h>

int main(void)
{
  char x[5];
  fgets(&x, 5, stdin);
  printf("adres: %x\nstring: %s\n", &x, x);
  system("pause");
  return 0;
}

Nu zouden ook de problemen met je invoer opgelost moeten zijn. scanf() ziet namelijk volgens mij de spatie als een onzichtbaar teken dat het einde van één string aanduid, en stopt daarom bij de spatie met lezen. Omdat fgets() pas stopt met lezen als het het opgegeven aantal karakters heeft gelezen of er niets meer is om te lezen, zul je dit probleem niet hebben. Het vreemde met de zin van meer dan zes karakters was niet vreemd, je hebt hier gewoon een buffer overflow veroorzaakt.

Nog een kleine opmerking: gebruik de functie getchar() in plaats van de functie system() om het programma te pauzeren. system() is namelijk heel sterk gebonden aan het besturingssysteem en de omgeving. getchar() is platform en omgevingonafhankelijk.

Dus je kunt je programma beter als volgt schrijven:
#include <stdio.h>

int main(void)
{
  char x[5];
  fgets(&x, 5, stdin);
  printf("adres: %x\nstring: %s\n", &x, x);
  getchar();
  return 0;
}

(Als je pas begonnen bent met programmeren, dan spijt het me dat mijn post zo technisch is, maar hé, C is nu eenmaal technisch :) )
« Laatst bewerkt op: 2010/12/25, 23:10:02 door Joshua822 »

Offline gieterke

  • Lid
Re: [C] string van 6karakters
« Reactie #2 Gepost op: 2010/12/26, 11:08:38 »
En als ik nu de code gebruik die jij gebruikt gaat het nog niet helemaal goed. Dan print hij bv. alleen de eerste 4karakters van de string die ik ingeef. Aangezien ik op internet wat ben aan het opzoeken geweest en erop uitkwam dat hij stopt bij het 5de karakter, mag ik dan de regel van fgets() vervangen door: fgets(&x, 6, stdin); of krijg ik dan ook een buffer overflow? Want dan slaagt hij toch 5karakters op als ik deze code gebruik? Of wordt het vijfde karakter nog wel opgeslagen maar niet geprint?

Offline dropl

  • Lid
Re: [C] string van 6karakters
« Reactie #3 Gepost op: 2010/12/26, 11:47:56 »
Want kijk, je hebt namelijk een character array met vijf element aangemaakt. Daar kun je slechts vijf karakters in opslaan. Wat gebeurt er nu als je probeert om een zesde karakter op te slaan? Dat karakter wordt zonder probleem opgeslagen! Maar waar wordt het dan opgeslagen als er maar ruimte is voor vijf karakters in de array? Het antwoord daarop is dat je dat niet weet. Het kan zijn dat je dat karakter opslaat in een ongebruikte geheugenlocatie en dat er niets aan de hand is, maar het kan ook zijn dat je het geheugen van een ander proces corrupt maakt en dat het andere proces hierdoor crasht.
Dit klopt niet. Je weet wel waar die karakters opgeslagen worden, nl op de stack. Het is een locale variabele. De stack wordt corrupt of andere locale variabelen worden overschreven.
En geheugen van een ander proces kan je niet overschrijven aangezien ieder proces tegenwoordig draait in z'n eigen virtuele adres ruimte (=slechte vertaling van virtual address space, http://en.wikipedia.org/wiki/Virtual_address_space ).
En ik denk dat fgets(&x, 5, stdin); dit moet zijn: fgets(x, 5, stdin);En volgens mij is een pointer op een 64 bits systeem 64 bits en 32 bits op een 32 bits systeem. Dit geldt ook voor de 'long int', zou je beter printf zo doen: printf("adres: %lx\nstring: %s\n", &x, x); (met een l tussen %x dus, valt nauwelijks op)
Sorry dat ik even de wijsneus uithang, prima verhaal overigens!
« Laatst bewerkt op: 2010/12/26, 11:49:37 door dropl »
Ge wit dit oit noit nie

Offline Joshua822

  • Lid
Re: [C] string van 6karakters
« Reactie #4 Gepost op: 2010/12/26, 15:23:17 »
Citaat
En als ik nu de code gebruik die jij gebruikt gaat het nog niet helemaal goed. Dan print hij bv. alleen de eerste 4karakters van de string die ik ingeef. Aangezien ik op internet wat ben aan het opzoeken geweest en erop uitkwam dat hij stopt bij het 5de karakter, mag ik dan de regel van fgets() vervangen door:
fgets(&x, 6, stdin);of krijg ik dan ook een buffer overflow? Want dan slaagt hij toch 5karakters op als ik deze code gebruik? Of wordt het vijfde karakter nog wel opgeslagen maar niet geprint?
Dat komt door de (goede) manier waarop fgets() werkt, en hoe strings in C werken.

Eerst over strings in C: stel dat we de string "Hallo!" hebben. Hoeveel karakters hebben we nodig om deze string op te slaan in het geheugen? Zes, lijkt het logische antwoord. Maar het échte antwoord is zeven. Waarom dan? Omdat C moet kunnen weten wanneer een string eindigt. Om dit te weten moet na het laatste karakter van een string nog een karakter genaamd de null-terminator ( '\0' ) opgeslagen worden die C vertelt dat dit het einde van de string is.

Om te zien hoe dit werkt raad ik je aan om gewoon eens het volgende stukje code te compileren en uit te voeren:
#include <stdio.h>

int main ( )
{
    char test_string [ ] = "Hey! \n \0 Dit zal je nooit zien!";
    printf ( test_string );
    getchar ( );
    return ( 0 );
}


Die null-terminator moeten we dus ook opslaan. Maar we mogen deze niet buiten onze array opslaan, want dan krijgen we een buffer overflow en kunnen we problemen veroorzaken. Daarom moeten we voor de null-terminator plaats voorzien in onze array. Dit wil dus zeggen dat we een array moeten declareren met plaats voor alle karakters die willen opslaan plus één karakter voor het opslaan van de null-terminator. Als we dus een string van vijf karakters willen opslaan, moeten we een character array van zes elementen declareren, met één element voor de null-terminator.

Nu verder over fgets(): fgets zal dus, met dit in achterhoofd, het aantal opgegeven karakters min 1 lezen, en dan het laatste element van de array vulen met een null-terminator ( '\0' ) karakter ( of het zal lezen totdat het een newline karakter tegenkomt en dan het volgende element van de array vullen met een '\0' karakter, ook als is de array nog niet vol ).

Dus om jouw programma strings tot en met met vijf karakters groot te laten lezen, schrijf je het als volgt:
#include <stdio.h>

int main ( )
{
     char x [ 6 ];
     fgets( x, 6, stdin);
     printf("adres: %x \nstring: %s \n", &x, x);
     getchar ( );
     return ( 0 );
}
(kleine opmerking: je kunt inderdaad beter de '&' voor de x weghalen bij fgets(), deze notatie werkt ook prima, maar wordt weinig gebruikt. )

@dropl: je zit echt helemaal verkeerd. In theorie zou je enkel de eigen stack kunnen corrupt maken, inderdaad. Maar in de praktijk kun je als de buffer overflow groot genoeg is gewoon data van andere processen of zelfs het besturingssysteem overschrijven. Sterker nog, dat gaat zelfs heel goed, want de hele moderne hackerscene is op dat principe gebaseerd! Bijna alle exploits worden geschreven door een buffer overflow bug te vinden in software die via een netwerk communiceert en via deze bug genoeg data over de rand van de array heen te schrijven totdat je in de adress space van het besturingssysteem zit en je vandaar uit alle commando's kunt uitvoeren ( zoals het aanmaken van een rootaccount, het aanzetten van ssh, en alle processen die logbestanden bijhouden hiermee laten stoppen zodat een hacker toegang tot het systeem kan krijgen ). Buffer overflows zijn zeer, zeer, zéér gevaarlijk. Een buffer overflow is geen klein mankement, het is het ergste soort lek wat in een programma kan zitten.

Offline dropl

  • Lid
Re: [C] string van 6karakters
« Reactie #5 Gepost op: 2010/12/26, 15:54:15 »
Citaat
@dropl: je zit echt helemaal verkeerd. In theorie zou je enkel de eigen stack kunnen corrupt maken, inderdaad. Maar in de praktijk kun je als de buffer overflow groot genoeg is gewoon data van andere processen of zelfs het besturingssysteem overschrijven. Sterker nog, dat gaat zelfs heel goed, want de hele moderne hackerscene is op dat principe gebaseerd! Bijna alle exploits worden geschreven door een buffer overflow bug te vinden in software die via een netwerk communiceert en via deze bug genoeg data over de rand van de array heen te schrijven totdat je in de adress space van het besturingssysteem zit en je vandaar uit alle commando's kunt uitvoeren ( zoals het aanmaken van een rootaccount, het aanzetten van ssh, en alle processen die logbestanden bijhouden hiermee laten stoppen zodat een hacker toegang tot het systeem kan krijgen ). Buffer overflows zijn zeer, zeer, zéér gevaarlijk. Een buffer overflow is geen klein mankement, het is het ergste soort lek wat in een programma kan zitten.

Dat je met een buffer overflow in kernel space of address space van andere processen zou kunnen komen, heb ik nog nooit van gehoord. Het kan wel als je exploit ook nog andere zwakheden in het os weet uit te buiten, maar niet door een grote overflow. Dan krijg je een stack overflow en wordt je proces gewoon gekilled.
Overigens heb ik niet beweerd dat een buffer overflow niet gevaarlijk zou zijn, ik wou je alleen wijzen op het feit dat je niet vanuit het ene proces geheugen van het andere proces kan verminken.
Lees ook even dit: http://en.wikipedia.org/wiki/Buffer_overflow
Hier wordt uitgelegd hoe een buffer overflow exploit werkt.
Ge wit dit oit noit nie

Offline Joshua822

  • Lid
Re: [C] string van 6karakters
« Reactie #6 Gepost op: 2010/12/26, 16:04:29 »
Oké, je kan zo misschien niet het geheugen corrupt maken, maar je kan op die manier wel code gaan uitvoeren door het tekst segment van je proces te overschrijven en daarmee kun je ook erge zaken doen.

Offline ivo

  • Lid
Re: [C] string van 6karakters
« Reactie #7 Gepost op: 2010/12/27, 10:35:14 »
Ik ben jáááááren geleden ook eens met C begonnen.
En nu ik dit lees weet ik ook weer waarom ik ermee ben opgehouden.
Wanneer je door het aanroepen van een standaard functie al een buffer-overflow
kunt veroorzaken....................  :|
There are only 10 types of people in the world; those who understand binary and those who don't.

Offline gieterke

  • Lid
Re: [C] string van 6karakters
« Reactie #8 Gepost op: 2010/12/27, 18:38:46 »
Nen dikke merci voor de fantastische uitleg. Hiermee kan ik verder.

@ivo: Dat vind ik juist de uitdaging. Ik moet voor school microcontrollers programmeren met flowcode. Voor het geval je het niet kent: dat is een grafisch programma waar je m.b.v. blokken moet programmeren. Ook visual basic is al regelmatig aan de orde gekomen op school. Maar aangezien ik dit niet zo leuk vind omdat het te simpel is en teveel is voorgekauwd was ik op zoek naar een taal die sneller, sterker maar toch iets moeilijker was. Daardoor ben ik bij C uitgekomen.