background image

 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

201

 

Rozdział 6. 

Podstawy programowania dla 
hakerów 

Język C 

Dla  kaŜdego  hakera,  młodego  czy  starego,  mniej  lub  bardziej  doświadczonego, 
znajomość języka C jest jednym z fundamentów wiedzy. Niemal wszystkie narzędzia 
i programy,  stosowane  w  trakcie  analiz  sieci  i  włamań,  powstają  właśnie  w  tym 
języku.  RównieŜ  w  niniejszej  ksiąŜce  większość  przedstawianego  kodu  to  właśnie 
kod  źródłowy  w  języku  C.  Programy  te  moŜna  modyfikować,  dostosowywać  do 
własnych potrzeb i odpowiednio kompilować. 

 

W  pracy  nad  niniejszym  rozdziałem  wykorzystano  obszerne  fragmenty  pracy  guru 
programowania  Matthew  Proberta.  Mają  one  pełnić  funkcję  wprowadzenia  do 
programowania  w  języku  C  i  umoŜliwić  stosowanie  przedstawianych  w  ksiąŜce  (i 
załączonych  na  CD-ROM-ie)  listingów  programów.  Pełny  kurs  języka  znajdziesz  w 
niejednej ksiąŜce wydawnictwa Helion. 

Język C wyróŜniają następujące cechy, które omawiamy niŜej. 









 

Blokowe konstrukcje sterowania wykonywaniem programu (typowe dla 
większości języków wysokiego poziomu). 









 

Swobodne operowanie podstawowymi obiektami „maszynowymi” (takimi jak 
bajty) i moŜliwość odwoływania się do nich przy uŜyciu dowolnej, wymaganej 
w danej sytuacji, perspektywy obiektowej (typowe dla języków asemblerowych). 









 

MoŜliwość wykonywania operacji zarówno wysokiego poziomu (na przykład 
arytmetyka zmiennoprzecinkowa), jak i niskiego poziomu (zbliŜonych 
do instrukcji języka maszynowego), co umoŜliwia tworzenie kodu wysoce 
zoptymalizowanego bez utraty jego przenośności. 

background image

 
202 

Hack Wars. Tom 1. Na tropie hakerów 

202 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Przedstawiony  w  niniejszym  rozdziale  opis  języka  C  bazować  będzie  na  funkcjach 
oferowanych  przez  większość  kompilatorów  dla  komputerów  PC.  Powinien  dzięki 
temu 

umoŜliwić 

rozpoczęcie 

tworzenia 

prostych 

programów 

osobom 

nieposiadającym  szerokiej  wiedzy  o  języku  (uwzględnimy  między  innymi  funkcje 
zapisane w pamięci ROM i funkcje DOS-u). 

 

Przyjmujemy  załoŜenie,  Ŝe  masz,  drogi  Czytelniku,  dostęp  do  kompilatora  C  i 
odpowiedniej  dokumentacji  funkcji  bibliotecznych.  Programy  przykładowe  powstały 
w Turbo  C  firmy  Borland;  większość  elementów  niestandardowych  tego  narzędzia 
uwzględniono równieŜ w późniejszych edycjach Microsoft C. 

Wersje języka C 

W  pierwotnej  edycji  języka  C  (jeszcze  przed  publikacją  Kernighana  i  Ritchie’ego, 
The C Programming Language, Prentice-Hall 1988 (polskie wydanie: Język ANSI C, 
Wydawnictwa  Naukowo-Techniczne  1994
))  zintegrowane  operatory  przypisania  (+=, 
*=  itd.)  definiowane  były  odwrotnie  (tj.  =+,  =*  itd.).  Znakomicie  utrudniało  to 
interpretację wyraŜeń takich jak: 

x=-y 

co mogłoby znaczyć 

x = x - y    

lub 

   x = (-y) 

Ritchie szybko zauwaŜył dwuznaczność takiego zapisu i zmodyfikował go do postaci 
znanej dzisiaj (+=, *= itd.). Mimo to wciąŜ stosowanych jest wiele odmian będących 
rodzajem wypośrodkowania między pierwotną wersją języka C Kernighana i Ritchie’ego 
a językiem ANSI C. RóŜnice między nimi dotyczą przede wszystkim: 









 

wprowadzenia prototypów funkcji i zmiany preambuły definicji funkcji, 
aby dostosować ją do stylu prototypów, 









 

wprowadzenia znaku wielokropka (...) do oznaczenia list argumentów o zmiennej 
długości, 









 

wprowadzenia słowa kluczowego 

void

 (dla funkcji, które nie zwracają 

wartości) i typu 

void *

 dla ogólnych zmiennych wskaźnikowych, 









 

wprowadzenie w preprocesorze mechanizmów scalania ciągów, wklejania 
elementu (token-pasting) i zamiany na ciąg (string-izing), 









 

dodanie w preprocesorze translacji „trygrafów” (trigraph) — trójznakowych 
sekwencji reprezentujących znaki specjalne, 









 

dodanie w preprocesorze dyrektywy 

#pragma

 i formalizacja pseudofunkcji 

declared()

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

203 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

203

 









 

wprowadzenie ciągów i znaków wielobajtowych, zapewniających obsługę 
języków narodowych, 









 

wprowadzenie słowa kluczowego 

signed

 (jako uzupełnienie słowa 

unsigned

, stosowane w deklaracjach liczb całkowitych) i 

jednoargumentowego operatora plus (

+

). 

Klasyfikowanie języka C 

Szerokie moŜliwości języka C, dopuszczenie bezpośredniego operowania na adresach 
i danych w pamięci oraz strukturalne podejście do programowania sprawiają, Ŝe język 
ten klasyfikuje się jako „język programowania średniego poziomu”. Znajduje to wyraz 
w mniejszej liczbie gotowych rozwiązań niŜ  w językach wysokiego poziomu, takich 
jak BASIC, ale wyŜszym poziomie strukturalnym niŜ niskopoziomowy Assembler. 

Słowa kluczowe 

Pierwotna  edycja  języka  C  definiuje  27  słów  kluczowych.  Komitet  ANSI  dodał  do 
nich  5  nowych.  Wynikiem  są  dwa  standardy  języka,  choć  norma  ANSI  przejęła 
większość elementów od Kerninghana i Ritchie’ego. Oto lista: 

auto 

double 

int 

struct 

break 

else 

long 

switch 

case 

enum 

register 

typedef 

char 

extern 

return 

union 

const 

float 

short 

unsigned 

continue 

for 

signed 

void 

default 

goto 

sizeof 

volatile 

do 

if 

static 

while 

Warto  zwrócić  uwagę,  Ŝe  niektóre  kompilatory  C  wprowadzają  dodatkowe  słowa 
kluczowe, specyficzne dla środowiska sprzętowego. Warto zapoznać się z nimi. 

Struktura języka C 

Język  C  wymaga  programowania  strukturalnego.  Oznacza  to,  Ŝe  na  program  składa 
się pewna grupa nawzajem wywołujących się bloków kodu. Dostępne są róŜnorodne 
polecenia słuŜące do konstruowania pętli i sprawdzania warunków: 

do-while, for, while, if, case 

Blok programu  w języku C  ujmowany jest  w  nawiasy  klamrowe (

{}

). MoŜe on być 

kompletną  procedurą,  nazywaną  funkcją  lub  częścią  kodu  funkcji.  Przyjrzyjmy  się 
przykładowi: 

background image

 
204 

Hack Wars. Tom 1. Na tropie hakerów 

204 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

if (x < 10) 

  a = 1; 
  b = 0; 

Instrukcje  wewnątrz  nawiasów  klamrowych  wykonane  zostaną  tylko  wtedy,  gdy 
spełniony zostanie warunek 

x < 10

Jako kolejny przykład przedstawimy pełny blok kodu funkcji, zawierający wewnątrz 
blok pętli: 

int GET_X() 

  int  x; 
 
  do 
  { 
    printf ("\nWprowadz liczbe z zakresu od 0 do 10 "); 
    scanf("%d",&x); 
  } 
  while(x < 0 || x > 10); 
  return(x); 

Zwróćmy uwagę, Ŝe kaŜdy wiersz instrukcji zakończony jest średnikiem, o ile nie jest 
sygnałem  początku  bloku  kodu  (w  takim  przypadku  kolejnym  znakiem  jest  nawias 
klamrowy).  Język  C  rozpoznaje  wielkość  liter,  ale  nie  bierze  pod  uwagę  białych 
znaków. Odstępy między poleceniami są pomijane, stąd konieczność uŜycia średnika, 
aby  oznaczyć  koniec  wiersza.  Tego  rodzaju  podejście  powoduje,  Ŝe  następujące 
polecenia interpretowane są jako identyczne: 

x = 0; 
x        =0; 
x=0; 

Ogólna postać programu w języku C jest następująca: 









 

instrukcje preprocesora kompilacji, 









 

globalne deklaracje danych. 









 

deklaracje i definicje funkcji (włączając w to zawartość programu): 

typ-zwracany main (lista parametrów

  instrukcje 

typ-zwracany f1 (lista parametrów

  instrukcje 

typ-zwracany f2 (lista parametrów

  instrukcje 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

205 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

205

 

typ-zwracany fn (lista parametrów

  instrukcje 

Komentarze 

Podobnie  jak  większość  języków,  C  pozwala  umieszczać  w  kodzie  programu 
komentarze. Ich ogranicznikami są symbole 

/*

 i 

*/

/* To jest wiersz komentarza w j

ę

zyku C */ 

(Równie  często  korzysta  się  z  komentarzy  jednoliniowych,  otrzymywanych  poprzez 
sekwencję //, np.: 

   //To te

Ŝ

 jest wiersz komentarza  P.B.) 

Biblioteki 

Programy  w  języku  C  kompiluje  się  i  łączy  z  funkcjami  bibliotecznymi, 
dostarczanymi wraz z kompilatorem. Na biblioteki składają się funkcje standardowe, 
których  działanie  zdefiniowane  zostało  w  normie  ANSI.  Ich  powiązanie  z 
konkretnym  kompilatorem  zapewnia  dostosowanie  do  platformy  sprzętowej.  Wynika 
stąd,  Ŝe  standardowa  funkcja  biblioteczna 

printf()

  działa  tak  samo  w  systemach 

DEC  VAX  i  IBM  PC,  choć  róŜni  się  jej,  zapisany  w  bibliotece,  kod  maszynowy. 
Programista  C  nie  musi  zagłębiać  się  w  zawartość  bibliotek,  wymagana  jest  jedynie 
umiejętność ich stosowania i znajomość działania funkcji, które pozostają niezmienne 
na kaŜdym komputerze. 

Tworzenie programów 

Kompilacja 

Zanim zajmiemy się funkcjami, poleceniami, sekwencjami i innymi zaawansowanymi 
zagadnieniami, przyjrzyjmy się praktycznemu przykładowi, w którym doprowadzimy 
do  skompilowania  kodu.  Kompilowanie  programów  C  jest  stosunkowo  prostą 
czynnością,  jednak  róŜni  się  zaleŜnie  od  stosowanego  kompilatora.  Kompilatory 
wyposaŜone  w  menu  umoŜliwią  skompilowanie,  skonsolidowanie  i  uruchomienie 
programu  jednym  wciśnięciem  klawisza.  Podchodząc  jednak  do  zagadnienia 
moŜliwie  uniwersalnie  i  tradycyjnie,  przeprowadzimy  poniŜej  całą  procedurę  w 
oparciu o wiersz poleceń. 

W dowolnym edytorze wprowadzamy poniŜszy fragment kodu i zapisujemy plik jako 
przyklad.c

/* 

background image

 
206 

Hack Wars. Tom 1. Na tropie hakerów 

206 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  przykładowy komunikat tekstowy 
*/ 
#include<stdio.h> 
void main() 

  printf( "Hello!\n" ); 

Kolejnym  krokiem  jest  skompilowanie  kodu  do  postaci  pliku  programu  —  dopiero 
wtedy  moŜna  będzie  go  uruchomić  (czy  teŜ  wykonać).  W  wierszu  poleceń  w  tym 
samym  katalogu,  w  którym  zapisaliśmy  plik  przyklad.c,  wprowadzamy  następujące 
polecenie kompilacji: 

cc przyklad.c 

Nie wolno zapominać, Ŝe składnia polecenia kompilacji zaleŜy od kompilatora. Nasz 
przykład  opiera  się  na  standardzie  języka  C.  Współcześnie  jednak  popularne  jest 
stosowanie składni wywodzącej się z kompilatora GNU C: 

gcc przyklad.c 

Po  wykonaniu  takiego  polecenia  nasz  kod  jest  juŜ  skompilowany  i  ma  postać  pliku 
programu,  który  moŜemy  uruchomić.  Wynik  jego  działania  łatwo  wydedukować 
z prostego kodu: 

Hello! 
Press any key to continue 

To wszystko! Kompilowanie małych programów  w C nie jest trudne, naleŜy jedynie 
mieć  świadomość  szkodliwych  niekiedy  efektów  ich  działania.  Programy 
przedstawiane  na  stronach  tej  ksiąŜki  i  załączone  na  CD-ROM-ie  są  oczywiście 
znacznie bardziej skomplikowane, jednak zasady pozostają te same. 

Typy danych 

W  języku  C  wyróŜnia  się  cztery  podstawowe  typy  danych:  znakowy,  całkowity, 
zmiennoprzecinkowy  i  nieokreślony.  Odpowiadają  im  słowa  kluczowe: 

char

int

float

  i 

void

.  Dalsze  typy  danych  tworzy  się  na  tej  podstawie,  dodając 

modyfikatory: 

signed

 (ze znakiem), 

unsigned

 (bez znaku), 

long

 (długa) i 

short

 

(krótka). Modyfikator 

signed

 jest elementem domyślnym, co sprawia, Ŝe jego uŜycie 

moŜe  się  okazać  konieczne  jedynie  w  wypadku  gdy  zastosowano  przełącznik 
kompilacji  nakazujący  domyślne  korzystanie  ze  zmiennych  bez  znaku.  Rozmiar 
kaŜdego typu danych zaleŜy od platformy sprzętowej, jednak norma ANSI wyznacza 
pewne zakresy minimalne, zestawione w tabeli 6.1. 

W  praktyce  tak  określone  konwencje  oznaczają,  Ŝe  typ  danych 

char

  nadaje  się 

najlepiej  do  przechowywania  zmiennych  typu  znacznikowego,  takich  jako  kody 
stanu, o ograniczonym zakresie wartości. MoŜna równieŜ korzystać z typu 

int

. Gdy 

jednak  zakres  wartości  nie  przekracza  127  (lub  255  dla 

unsigned  char

),  kaŜda 

deklarowana  w  ten  sposób  zmienna  przyczynia  się  do  niepotrzebnego  obciąŜania 
pamięci. 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

207 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

207

 

Natomiast trudniejsze jest pytanie o to, z którego typu liczb rzeczywistych korzystać 
— 

float

double

  czy 

long  double

.  Gdy  wymagana  jest  dokładność,  na 

przykład  w aplikacji  stosowanej  w  księgowości,  instynktownie  powinniśmy  uŜyć  typu 

long 

double

 

Tabela 6.1. Rozmiary i zakresy typów danych języka C 

Typ 

Rozmiar 

Zakres 

char 

–128 do 127 

unsigned char 

0 do 255 

int 

16 

–32 768 do 32 767 

unsigned int 

16 

0 do 65 535 

long int 

32 

–2 147 483 648 do 2 147 483 647 

unsigned long int 

32 

0 do 4 294 967 295 

float 

32 

precyzja 6-cyfrowa 

double 

64 

precyzja 10-cyfrowa 

long double 

80 

precyzja 10-cyfrowa 

wiąŜe się to jednak z wykorzystaniem przez kaŜdą zmienną 10 bajtów. Obliczenia na 
liczbach  rzeczywistych  nie  są  tak  dokładne  jak  na  liczbach  całkowitych,  warto  więc 
zawsze  rozwaŜyć  uŜycie  typu 

int

  i  „obejście”  problemu.  Typ  danych 

float

  nie  jest 

zbyt dobry, gdyŜ jego 6-cyfrowa precyzja nie zapewnia dokładności, na której zawsze 
będziemy  mogli  polegać.  Ogólną  zasadą  jest  korzystanie  z  typów  całkowitych  tak 
szeroko,  jak  tylko  jest  to  moŜliwe,  a  gdy  pojawia  się  konieczność  uŜycia  liczb 
rzeczywistych, wprowadzenie typu 

double

Deklarowanie zmiennej 

KaŜda  zmienna  musi  zostać  zadeklarowana  przed  uŜyciem.  Ogólną  postacią 
deklaracji zmiennej jest: 

typ nazwa

Aby  więc  przykładowo  zadeklarować  zmienną 

x

  typu 

int

,  przeznaczoną  do 

przechowywania wartości z zakresu od –32 768 do 32 767, uŜyjemy instrukcji: 

int x; 

Ciągi znakowe deklarować moŜna jako tabele znaków: 

char nazwa[liczba_elementów]; 

Deklaracja  ciągu  o  nazwie 

nazwisko

  i  długości  30  znaków,  wyglądać  będzie 

następująco: 

char nazwisko[30]; 

Tablice  danych  innych  typów  mogą  mieć  więcej  niŜ  jeden  wymiar.  Oto  deklaracja 
dwuwymiarowej tablicy liczb całkowitych: 

background image

 
208 

Hack Wars. Tom 1. Na tropie hakerów 

208 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

int x[10][10]; 

Elementy tablicy wywołujemy jako: 

x[0][0] 
x[0][1] 
x[n][n] 

WyróŜnia  się  trzy  poziomy  dostępu  do  zmiennych:  lokalny,  na  poziomie  modułu 
i globalny.  Zmienna  deklarowana  wewnątrz  bloku  kodu  będzie  dostępna  wyłącznie 
dla instrukcji wewnątrz tego bloku. Zmienna deklarowana poza blokami kodu funkcji, 
ale  poprzedzona  modyfikatorem 

static

,  będzie  dostępna  wyłącznie  instrukcjom 

wewnątrz  modułu  kodu  źródłowego.  Zmienna  deklarowana  poza  blokami  kodu 
funkcji i niepoprzedzona modyfikatorem będzie dostępna dla dowolnych instrukcji w 
dowolnym module programu. Na przykład: 

int blad; 
static int a; 
 
void main() (Co prawda funkcja main działa i bez deklaracji warto

ś

ci 

zwracanej, jednak w takim przypadku wy

ś

wietla si

ę

 ostrze

Ŝ

enie (bo 

kompilator domy

ś

lnie przyjmuje j

ą

 jako int i szuka funkcji return. 

Aby tego unikn

ąć

, w ka

Ŝ

dym nast

ę

pnym przykładzie dopisuj

ę

 void P.B.) 


  int x; 
  int y; 

 
funkcjaa() 

  /* Sprawdzenie czy zmienna a jest równa 0 */ 
  if (a == 0) 
  { 
    int b; 
    for(b = 0; b < 20; b++) 
      printf ("\nHello World"); 
  } 
 

W  powyŜszym  przykładzie  zmienna 

blad

  jest  dostępna  dla  wszystkich, 

kompilowanych  jako  jeden  program,  modułów  kodu  źródłowego.  Zmienna 

a

  jest 

osiągalna dla wszystkich instrukcji w funkcjach 

main()

 i 

funkcjaa()

, ale pozostaje 

niewidoczna  z poziomu  innych  modułów.  Zmienne 

x

  i 

y

  są  dostępne  wyłącznie 

instrukcjom wewnątrz funkcji 

main()

. Z kolei zmienna 

b

 moŜe być uŜyta wyłącznie 

przez instrukcje wewnątrz bloku kodu po instrukcji 

if

JeŜeli drugi blok kodu faktycznie ma skorzystać ze zmiennej 

blad

, wymagane będzie 

umieszczenie w nim deklaracji zmiennej globalnej 

extern

extern int blad; 
 
funkcjab() 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

209 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

209

 

Język C nie stawia szczególnych przeszkód w przypisywaniu do siebie róŜnych typów 
danych.  Przykładowo  moŜemy  zadeklarować  zmienną  typu 

char

,  co  spowoduje 

przypisanie  do  przechowywania  jej  wartości  jednego  bajtu  danych.  MoŜna  podjąć 
próbę przypisania do niej wartości spoza tego zakresu: 

void main() 

x = 5000; 
 

Zmienna 

x

  moŜe  przechowywać  wartości  z  zakresu  od  –127  do  128,  a  więc  wartość 

5000 nie zostanie przypisana. 

x

 przyjmie jednak wartość 136. 

Potrzeba  przypisania  róŜnych  typów  danych  nie  jest  niczym  oryginalnym.  Aby 
powstrzymać  kompilator  od  generowania  ostrzeŜeń  o  takich  operacjach,  moŜna 
skorzystać  z  instrukcji  konwersji  (cast  statement),  informując  kompilator  o  tym,  Ŝe 
operacja wykonywana jest świadomie. Instrukcję taką budujemy, umieszczając przed 
zmienną lub wyraŜeniem nazwę typu danych ujętą w nawiasy: 

void main() 

  float x; 
  int y; 
 
  x = 100 / 25; 
 
  y = (int)x; 

Operacja rzutowania 

(int)

 informuje kompilator o konieczności konwersji wartości 

zmiennej zmiennoprzecinkowej 

x

 do liczby całkowitej, zanim ta zostanie przypisana 

do zmiennej 

y

Parametry formalne 

Funkcja  w  języku  C  moŜe  przyjmować  parametry  przekazywane  przez  funkcję 
wywołującą.  Parametry  te  deklaruje  się  podobnie  jak  zmienne,  podając  ich  nazwy 
wewnątrz towarzyszących nazwie funkcji nawiasów: 

int MNOZ(int x, int y) 

  /* Zwró

ć

 parametr x pomno

Ŝ

ony przez parametr y */ 

  return(x * y); 

 
void main() 

  int a; 
  int b; 
  int c; 
 
  a = 5; 
  b = 7; 
  c = MNOZ(a,b); 

background image

 
210 

Hack Wars. Tom 1. Na tropie hakerów 

210 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

 
  printf("%d razy %d równa si

ę

 %d\n",a,b,c); 

Modyfikatory dostępu 

Stosuje  się  dwa  modyfikatory  dostępu: 

const

  i 

volatile

.  Wartość  zmiennej 

zadeklarowanej  jako 

const

  nie  moŜe  zostać  zmieniona  przez  program,  wartość 

zmiennej  zadeklarowanej  jako 

volatile

  moŜe  zostać  zmieniona  przez  program. 

Dodatkowo,  zadeklarowanie  zmiennej  jako 

volatile

  uniemoŜliwia  kompilatorowi 

zaalokowanie jej do rejestru i ogranicza przeprowadzaną na niej optymalizację. 

Typy klas przechowywania zmiennych 

Język  C  przewiduje  cztery  rodzaje  przechowywania  zmiennych: 

extern

static

auto

  i 

register

.  Typ 

extern

  umoŜliwia  modułowi  kodu  źródłowego  dostęp  do 

zmiennej zadeklarowanej w innym module. Zmienne 

static

 dostępne są wyłącznie 

z poziomu bloku kodu, w którym zostały zadeklarowane. Dodatkowo, jeŜeli zmienna 
ma  zasięg  lokalny,  zachowuje  swoją  wartość  między  kolejnymi  wywołaniami  bloku 
kodu. 

Zmienne  rejestrowe  (

register

)  są,  gdy  tylko  jest  to  moŜliwe,  przechowywane  w 

rejestrach  procesora.  Zapewnia  to  najszybszy  dostęp  do  ich  wartości.  Typ 

auto

 

stosuje  się  wyłącznie  w  odniesieniu  do  zmiennych  lokalnych.  Nakazuje  on 
zachowywanie  wartości  zmiennej  lokalnej.  PoniewaŜ  jest  to  modyfikator  domyślny, 
rzadko moŜna spotkać go w programach. 

Operatory 

Operatory  to  elementy  kodu,  które  nakazują  wykonanie  obliczeń  na  zmiennych.  W 
języku C dostępne są następujące: 

adres, 

pośredniość, 

plus jednoargumentowy, 

minus jednoargumentowy, 

dopełnienie bitowe, 

negacja logiczna, 

++ 

jako prefiks — preinkrementacja, jako sufiks — postinkrementacja, 

–– 

jako prefiks — predekrementacja, jako sufiks — postdekrementacja, 

dodawanie, 

odejmowanie, 

mnoŜenie, 

dzielenie, 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

211 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

211

 

reszta z dzielenia (modulo), 

<< 

przesunięcie w lewo, 

>> 

przesunięcie w prawo, 

bitowa operacja AND, 

bitowa operacja OR, 

bitowa operacja XOR, 

&& 

logiczna operacja AND, 

|| 

logiczna operacja OR, 

przypisanie, 

*= 

przypisanie iloczynu, 

/= 

przypisanie ilorazu, 

%= 

przypisanie reszty (modułu), 

+= 

przypisanie sumy, 

-= 

przypisanie róŜnicy, 

<<= 

przypisanie przesunięcia w lewo, 

>>= 

przypisanie przesunięcia w prawo, 

&= 

przypisanie wyniku bitowej operacji AND, 

|= 

przypisanie wyniku bitowej operacji OR, 

^= 

przypisanie wyniku bitowej operacji XOR, 

mniejsze niŜ, 

większe niŜ, 

<= 

mniejsze lub równe, 

>= 

większe lub równe, 

== 

równe, 

!= 

róŜne od, 

bezpośredni selektor składnika, 

-> 

pośredni selektor składnika, 

a ? x : 

jeŜeli 

a

 to prawda, to 

x

, w przeciwnym razie 

y,

 

[ ] 

definiowanie tablic, 

() 

nawiasy oddzielają warunki i wyraŜenia, 

... 

wielokropek wykorzystuje się w listach parametrów formalnych prototypów 
funkcji do deklarowania zmiennej liczby parametrów lub parametrów 
zmiennych typów. 

Aby  zilustrować  sposób  korzystania  z  podstawowych  operatorów,  przyjrzyjmy  się 
krótkiemu programowi: 

void main() 

  int a; 
  int b; 
  int c; 

background image

 
212 

Hack Wars. Tom 1. Na tropie hakerów 

212 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  a = 5;   /*Przypisanie zmiennej a warto

ś

ci 5*/ 

  b = a/2; /*Przypisanie zmiennej b warto

ś

ci a podzielonej przez 2*/ 

  c = b*2;  /*Przypisanie zmiennej c warto

ś

ci b pomno

Ŝ

onej przez 2*/ 

 
  if (a == c) /*Sprawdzenie czy a ma tak

ą

 sam

ą

 warto

ść

 jak c*/ 

 
    puts("Zmienna a jest parzysta"); 
  else 
    puts("Zmienna a jest nieparzysta"); 

Typowym sposobem zwiększenia wartości zmiennej o 1 jest wiersz: 

x = x + 1 

Język C dostarcza operatora inkrementacji, wystarczy więc napisać: 

x++ 

W  podobny  sposób  korzystamy  z  operatora  dekrementacji,  czyli  zmniejszania 
wartości o 1: 

x-- 

Pozostałe  operatory  matematyczne  wykorzystujemy  podobnie.  Warto  jednak 
pamiętać o wprowadzanych przez język C moŜliwościach zapisu skróconego: 

Zapis typowy 

Zapis w języku C 

x = x + 1 

x++ 

x = x - 1 

x-- 

x = x * 2 

x *= 2 

x = x / y 

x /= y 

x = x % 5 

x %= 5 

Funkcje 

Funkcje  to  procedury  kodu  źródłowego  tworzące  program  w  języku  C.  Ich  ogólną 
postacią jest: 

zwracany_typ nazwa_funkcji(lista_parametrów

  instrukcje 

Zwracany_typ

 to typ zwracanej przez funkcję wartości: 

char

int

double

void

 

itp. Kod wewnątrz funkcji C pozostaje niewidoczny dla innych funkcji C. Nie moŜna 
wykonywać  skoków  z  jednej  funkcji  do  wnętrza  innej.  Funkcje  mogą  jedynie 
wywoływać  inne  funkcje.  Nie  wolno  równieŜ  definiować  funkcji  wewnątrz  innych 
funkcji. Definicja musi zostać umieszczona bezpośrednio na poziomie modułu kodu. 

Parametry przekazywane są do funkcji jako wartości lub jako odwołania (wskaźniki). 
Gdy parametr jest przekazywany jako wartość, funkcja otrzymuje kopię tej wartości. 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

213 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

213

 

Parametr  przekazywany  jako  odwołanie  jest  jedynie  wskaźnikiem  do  właściwego 
parametru.  Pozwala  to  na  zmianę  jego  wartości  z  poziomu  wywołanej  funkcji.  W 
poniŜszym  przykładzie  przekazujemy  dwa  parametry  jako  wartość  do  funkcji 

funkcjaa()

,  która  następnie  podejmuje  próbę  zmiany  wartości  przekazanych 

zmiennych.  Drugim  krokiem  jest  przekazanie  tych  samych  parametrów  do  funkcji 

funkcjab()

, która równieŜ podejmuje próbę zmiany wartości zmiennych: 

#include <stdio.h> 
 
int funkcjaa(int x, int y) 

  /* Funkcja przyjmuje dwa parametry jako warto

ś

ci, x i y */ 

 
  x = x * 2; 
  y = y * 2; 
  
  printf ("\nWarto

ść

 x w funkcjaa() %d. Warto

ść

 y w funkcjaa() 

%d",x,y); 
 
  return(x); 

 
 
int funkcjab(int *x, int *y) 

  /* Funkcja przyjmuje dwa parametry jako odwołania, x i y */ 
 
  *x = *x * 2; 
  *y = *y * 2; 
  
  printf ("\nWarto

ść

 x w funkcjab() %d. Warto

ść

 y w funkcjab() 

%d",*x,*y); 
 
  return(*x); 

 
void main() 

  int x; 
  int y; 
  int z; 
 
  x = 5; 
  y = 7; 
 
  z = funkcjaa(x,y); 
  z = funkcjab(&x,&y); 
 
  printf ("\nWarto

ść

 x %d, warto

ść

 y %d, warto

ść

 z %d",x,y,z); 

funkcjab()

  nie  zmienia  wartości  otrzymanych  parametrów.  Modyfikowana  jest 

zawartość  wskazywanych  parametrami  adresów  pamięci.  O  ile 

funkcjaa()

 

otrzymuje  z funkcji 

main()

  wartości  zmiennych 

x

  i 

y

funkcjab()

  otrzymuje  z 

funkcji 

main()

 ich adresy w pamięci. 

background image

 
214 

Hack Wars. Tom 1. Na tropie hakerów 

214 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Przekazywanie tablicy do funkcji 

Następujący  program  przekazuje  do  funkcji  tablicę,  a  funkcja  nadaje  wartości 
elementom tablicy: 

#include <stdio.h> 
 
void funkcjaa(int x[]) 

  int n; 
 
  for(n = 0; n < 100; n++) 
  x[n] = n; 

 
void main() 

  int tablica[100]; 
  int licznik; 
 
  funkcjaa(tablica); 
 
  for(licznik = 0; licznik < 100; licznik++) 
    printf ("\nWarto

ść

 elementu %d wynosi %d",licznik, 

tablica[licznik]); 

Parametr  funkcji, 

int  x[]

,  jest  tablicą  dowolnej  długości.  Deklaracja  taka  jest 

moŜliwa,  poniewaŜ  kompilator  przekazuje  jedynie  adres  początkowy  tablicy,  a  nie 
wartości  poszczególnych  jej  elementów.  Konsekwencją  tego  jest  fakt,  Ŝe  funkcja 
moŜe zmieniać wartości elementów tablicy. Aby uniemoŜliwić funkcji wprowadzanie 
modyfikacji, konieczne jest uŜycie typu 

const

funkcjaa(const int x[]) 

Przy  takiej  deklaracji  wiersz  zmieniający  zawartość  tablicy  wywołałby  błąd 
kompilacji.  Określenie  parametru  jako  wartości  stałej  nie  likwiduje  jednak 
pośredniości jego przekazania. Ilustruje to poniŜszy program: 

#include <stdio.h> 
 
 void funkcjaa(const int x[]) 

  int *ptr; 
  int n; 
 
/*Ten wiersz generuje ostrze

Ŝ

enie 'suspicious pointer conversion'*/ 

/*(niebezpieczna konwersja wska

ź

nika)*/ 

/*x jest wska

ź

nikiem const, a ptr - nie*/ 

  ptr = x; 
 
  for(n = 0; n < 100; n++) 
  { 
    *ptr = n; 
    ptr++; 
  } 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

215 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

215

 

 
void main() 

  int tablica[100]; 
  int licznik; 
 
  funkcjaa(tablica); 
 
  for(licznik = 0; licznik < 100; licznik++) 
    printf("\nWarto

ść

 elementu %d wynosi 

%d",licznik,tablica[licznik]); 

Przekazywanie parametrów funkcji main() 

Język C umoŜliwia przekazanie parametrów do uruchamianego programu z poziomu 
systemu operacyjnego. Do ich odczytania wykorzystuje się zmienne 

argc

 i 

argv[]

#include <stdio.h> 
 
void main(int argc, char *argv[]) 

  int n; 
 
  for(n = 0; n < argc; n++) 
  printf ("\nWartosc parametru %d to %s",n,argv[n]); 

Parametr 

argc

 przechowuje liczbę przekazanych programowi parametrów. W tablicy 

argv[]

  zapisane  są  ich  adresy; 

argv[0]

  jest  zawsze  nazwą  uruchamianego 

programu.  Mechanizm  ten  ma  szczególne  znaczenie  dla  aplikacji  wymagających 
dostępu  do  plików  systemowych  i  danych.  RozwaŜmy  następującą  sytuację:  mała 
aplikacja obsługi baz danych przechowuje swoje dane w pojedynczym pliku dane.dat
aplikacja  ta  musi  zostać  tak  zaprojektowana,  aby  moŜna  było  uruchomić  ją  z 
dowolnego katalogu, czy to na dysku twardym, czy dyskietce; musi równieŜ zapewnić 
uruchamianie za pośrednictwem ścieŜki wyszukiwania DOS-u (

path

). Do poprawnej 

pracy  aplikacji  jest  więc  wymagane,  aby  zawsze  mogła  odnaleźć  plik  dane.dat
Rozwiązanie  takie  zapewni  przyjęcie  załoŜenia,  Ŝe  plik  danych  jest  zawsze  w 
identycznym  katalogu  co  sam  program.  PoniŜszy  fragment  ilustruje  wykorzystanie 
parametrów 

argc

 i 

argv

 w celu utworzenia ścieŜki do pliku danych aplikacji: 

#include <string.h> 
 
char nazwa_pliku[160]; 
 
void main(int argc, char *argv[]) 

  char *plik_danych = "DATA.DAT"; 
  char *p; 
 
  strcpy(nazwa_pliku,argv[0]); 
   
p = strstr(nazwa_pliku, ".exe"); (kompilator tworzy plik z 

rozszerzeniem o małych   literach P.B.) 
  if (p == NULL) 
  { 
    /* Plik uruchomieniowy jest plikiem .COM */ 

background image

 
216 

Hack Wars. Tom 1. Na tropie hakerów 

216 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

     
p = strstr(nazwa_pliku, ".com"); 
  } 
   
  /* Wyszukujemy ostatni uko

ś

nik */ 

  while(*(p-1) != '\\') 
    p--; 
 
  strcpy(p,plik_danych); 

Przedstawiony  program  tworzy  i  zapisuje  w  zmiennej 

nazwa_pliku

  ciąg  postaci 

ś

cieŜka\dane.dat.  JeŜeli  więc  przykładową  nazwą  pliku  uruchomieniowego  będzie 

test.exe  i  zostanie  on  umieszczony  w  katalogu  \borlandc,  zmiennej 

nazwa_pliku

 

przypisany zostanie ciąg 

\borlandc\dane.dat

Wyjście z funkcji 

Polecenie 

return

  powoduje  natychmiastowe  wyjście  z  funkcji.  JeŜeli  w  deklaracji 

funkcji  podano  typ  zwracanej  wartości,  w  poleceniu 

return

  naleŜy  uŜyć  parametru 

tego samego typu. 

Prototypy funkcji 

Prototypy  funkcji  umoŜliwiają  kompilatorowi  C  sprawdzanie  poprawności 
przekazywanych, do i z funkcji, danych. Ma to istotne znaczenie jako zabezpieczenie 
przed przekroczeniem zakresu zaalokowanego dla zmiennej obszaru pamięci. Prototyp 
funkcji  umieszcza  się  na  początku  programu  po  poleceniach  preprocesora  (takich  jak 

#include

) i przed deklaracjami funkcji. 

Polecenia preprocesora C 

W języku C w treści kodu źródłowego moŜna umieszczać polecenia dla kompilatora. 
Określa się je terminem polecenia preprocesora. Norma ANSI definiuje następujące: 

#if 
#ifdef 
#ifndef 
#else 
#elif 
#endif 
#include 
#define 
#undef 
#line 
#error 
#pragma 

Wszystkie  polecenia  preprocesora  rozpoczyna  znak  krzyŜyka  (hash),  czyli  #.  KaŜde 
wymaga osobnego wiersza kodu (uzupełnionego ewentualnie komentarzem). PoniŜej 
przedstawiamy krótkie omówienie. 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

217 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

217

 

#define 

Polecenie 

#define

 tworzy identyfikator, który kompilator zastąpi podanym ciągiem 

w danym module kodu źródłowego. Na przykład: 

#define FALSE 0 
#define TRUE !FALSE 

Kompilator zastąpi wszystkie dalsze wystąpienia ciągu 

FALSE

 znakiem 

0

, a wszystkie 

dalsze  wystąpienia  ciągu 

TRUE

  —  ciągiem 

!0

.  Zastępowaniu  nie  podlegają 

identyfikatory wewnątrz znaków cudzysłowu, a więc wiersz: 

printf ("TRUE"); 

nie zostanie zmieniony, ale 

printf ("%d",FALSE); 

podlega modyfikacji. 

Polecenie 

#define

  moŜe równieŜ zostać uŜyte do definiowania  makr, takŜe  makr z 

parametrami.  Do  zapewnienia  poprawności  zastąpień  zaleca  się  ujmowanie 
parametrów  w  nawiasy.  W  poniŜszym  przykładzie  deklarujemy  makro  o  nazwie 

larger()

, przyjmujące dwa parametry i zwracające ten z nich, którego wartość jest 

większa. 

#include <stdio.h> 
 
#define larger(a,b) (a > b) ? (a) : (b) 
 
int main() 

  printf("\n%d jest wi

ę

ksze",larger(5,7)); 

#error 

Polecenie 

#error

 powoduje przerwanie procesu kompilacji i wyświetlenie podanego 

tekstu, na przykład: 

#error SKOMPILOWANE DO MODULU B 

powoduje zatrzymanie kompilacji i wyświetlenie: 

SKOMPILOWANE DO MODULU B 

#include 

Polecenie 

#include

  nakazuje kompilatorowi odczytanie i przetworzenie zawartości 

dodatkowego  pliku  źródłowego.  Nazwa  pliku  musi  zostać  ujęta  w  cudzysłów  lub 
wstawiona między znaki 

<>

, na przykład: 

#include "module2.c" 
#include <stdio.h> 

background image

 
218 

Hack Wars. Tom 1. Na tropie hakerów 

218 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

JeŜeli  nazwa  pliku  została  wpisana  między  znaki 

<>

,  kompilator  wyszukuje  go  w 

katalogu określonym w konfiguracji. Jest to zasada ogólna. 

#if, #else, #elif, #endif 

Grupa  poleceń 

#if

  dostarcza  mechanizmu  kompilacji  warunkowej.  Stosowana  jest 

dość typowa składnia: 

#if wyra

Ŝ

enie_stale 

  instrukcje 
#else 
  instrukcje 
#endif 

Polecenie 

#elif

 to skrócona postać 

#else if

#if wyra

Ŝ

enie 

  instrukcje 
#elif wyra

Ŝ

enie 

  instrukcje 
#endif 

#ifdef, #ifndef 

Rozwinięciem  tych  poleceń  jest 

#if  defined

  (jeŜeli  zdefiniowano)  i 

#if  not 

defined

 (jeŜeli nie zdefiniowano). Konstrukcje składniowe są następujące: 

#ifdef nazwa_makra 
  instrukcje 
#else 
  instrukcje 
#endif 
 
#ifndef nazwa_makra 
  instrukcje 
#else 
  instrukcje 
#endif 

nazwa_makra

 to identyfikator utworzony za pomocą deklaracji 

#define

#undef 

Polecenie 

#undef

  usuwa  definicję  makra  utworzonego  przy  uŜyciu  wcześniejszej 

instrukcji 

#define

#line 

Polecenie 

#line

 modyfikuje zmienne globalne kompilatora 

__LINE__

 i 

__FILE__

Ogólną postacią instrukcji jest: 

#line numer "nazwa_pliku

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

219 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

219

 

Wartość 

numer

  zostaje  umieszczona  w  zmiennej 

__LINE__

,  a 

"nazwa_pliku"

  —  w 

zmiennej 

__FILE__

#pragma 

UmoŜliwia korzystanie z poleceń specyficznych dla kompilatora. 

Instrukcje sterujące 

Jak  w  kaŜdym  języku  programowania,  równieŜ  w  C,  znajdziemy  instrukcje 
sprawdzające wartość wyraŜenia. Wynikiem takiego sprawdzenia jest wartość 

TRUE

 lub 

FALSE

. Wartości 

FALSE

 odpowiada liczba 

0

, a 

TRUE

 — liczba róŜna od zera. 

Instrukcje wykonania warunkowego 

Podstawową instrukcją wykonania warunkowego jest 

if

 o następującej składni: 

if (wyra

Ŝ

enie

   instrukcje 
else 
   instrukcje 

gdzie 

instrukcje

 moŜe być instrukcją pojedynczą lub ujętym w nawiasy klamrowe 

blokiem kodu. Element 

else

 jest opcjonalny. JeŜeli wartością 

wyra

Ŝ

enie

 jest 

TRUE

wykonywana jest instrukcja podana bezpośrednio po nim. W pozostałych przypadkach 
wykonywana  jest  instrukcja  podana  po  słowie 

else

  (o  ile  ta  część  składni  została 

uŜyta). 

Alternatywą dla konstrukcji 

if...else

 jest polecenie 

?:

 w postaci: 

wyra

Ŝ

enie ? instrukcja_prawda : instrukcja_fałsz 

JeŜeli  wartością  wyraŜenia  jest 

TRUE

,  wykonywana  jest  pierwsza  instrukcja.  W 

pozostałych przypadkach wykonywana jest instrukcja druga. Ilustruje to przykład: 

#include <stdio.h> 
 
void main() 

  int x; 
  x = 6; 
  
  printf("\nx to liczba %s", x % 2 == 0 ? "parzysta" : 
"nieparzysta"); 

Język  C  oferuje  równieŜ  instrukcję 

switch

,  ułatwiającą  porównywanie  wyraŜenia 

z pewną listą wartości. Wykonywane są instrukcje powiązane z pierwszą dopasowaną 
wartością listy. Składnia polecenia 

switch

 jest następująca: 

switch (wyra

Ŝ

enie


  case warto

ść

1 :   instrukcje 

background image

 
220 

Hack Wars. Tom 1. Na tropie hakerów 

220 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

    break; 
  case warto

ść

2 :   instrukcje 

    break; 

  case warto

ść

n :   instrukcje 

    break; 
  default :   instrukcje 

UŜycie  instrukcji 

break

  nie  jest  wymagane,  ale  jej  pominięcie  powoduje  dalsze 

porównywanie wyraŜenia z kolejnymi elementami listy wartości. 

#include <stdio.h> 
 
void main() 

  int x; 
 
  x = 6; 
   
  switch (x) 
  { 
    case 0 : printf ("\nx równa si

ę

 zero"); 

      break; 
    case 1 : printf ("\nx równa si

ę

 jeden"); 

      break; 
    case 2 : printf ("\nx równa si

ę

 dwa"); 

      break; 
    case 3 : printf ("\nx równa si

ę

 trzy"); 

      break; 
    default : printf ("\nx jest wi

ę

ksze od trzech"); 

  } 

Instrukcje 

switch

 moŜna zagnieŜdŜać. 

Instrukcje iteracji 

W  języku  C  stosuje  się  trzy  instrukcje  pętli  (iteracji): 

for

while

  i 

do-while

Składnia pętli 

for

 jest następująca: 

for(inicjalizacja;warunek;inkrement
   instrukcje 

Jest  ona  szczególnie  przydatna,  gdy  korzystamy  z  licznika,  jak  w  poniŜszym 
przykładzie wyświetlającym zestaw znaków ASCII: 

#include <stdio.h> 
 
void main() 

  int x; 
 
  for(x = 32; x < 128; x++) 
    printf ("%d\t%c\t",x,x); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

221 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

221

 

Dopuszczalna jest równieŜ nieskończona pętla 

for

for(;;) 

  instrukcje 

Język  C  pozwala  uŜywać  teŜ  pustych  instrukcji.  PoniŜsza  pętla  usuwa  z  ciągu 
początkowe znaki odstępu: 

for(; *str == ' '; str++) 
  ; 

Warto zwrócić uwagę na średniki odpowiadające inicjalizacji pętli i pustej instrukcji. 

Pętla 

while

 ma konstrukcję nieco prostszą: 

while(warunek
   instrukcje 

Instrukcja lub blok instrukcji (ujęty w nawiasy klamrowe) będą powtarzane do czasu, 
gdy  wyraŜenie  warunku  przyjmie  wartość 

FALSE

.  JeŜeli  wyraŜenie  nie  jest 

prawdziwe  jeszcze  przed  wejściem  do  pętli,  instrukcje  nie  będą  wykonywane  w 
ogóle.  Jest  to  istotna  róŜnica  w  stosunku  do  pętli 

do-while

,  która  zawsze  zostaje 

wykonana co najmniej raz. Jej składnia to: 

do 

  instrukcje 

while(warunek); 

Instrukcje skoku 

Instrukcja 

return

  pozwala  powrócić  z  funkcji  wykonywanej  do  funkcji,  z  której  ta 

została wywołana. W zaleŜności od zadeklarowanego typu wartości zwracanej przez 
funkcję instrukcja 

return

 moŜe wymagać odpowiedniego parametru: 

int MULT(int x, int y) 

  return(x * y); 

lub 

void funkcjaa() 

  printf ("\nHello World"); 
  return; (w tym wypadku return nie jest konieczny P.B.) 

Instrukcja 

break

 słuŜy do wychodzenia z pętli lub instrukcji 

switch

. W przypadku 

pętli powoduje to jej przedwczesne zakończenie, jak w poniŜszym przykładzie: 

#include <stdio.h> 
 

background image

 
222 

Hack Wars. Tom 1. Na tropie hakerów 

222 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

void main() 

  int x; 

  for(x = 0; x < 256; x++) 
  { 
    if (x == 100) 
      break; 
 
    printf ("%d\t",x); 
  } 

Uzupełnieniem 

break

  jest  polecenie 

continue

,  wymuszające  przeprowadzenie 

następnej  iteracji  pętli.  Kolejną  wykonywaną  instrukcją  jest  w  tym  przypadku 
instrukcja  pętli  (dalsze  instrukcje  w  iterowanym  bloku  są  pomijane).  Dostępna  jest 
równieŜ  funkcja  przedwczesnego  zakończenia  wykonywania  programu  — 

exit()

MoŜna za jej pomocą przekazać wartość zwracaną do programu wywołującego: 

exit(warto

ść

_zwracana); 

Continue 

Słowo  kluczowe 

continue

  nakazuje  skok  do  instrukcji  kontrolnej  pętli.  W 

przypadku  pętli  zagnieŜdŜonych  jest  to  instrukcja  pętli  wewnętrznej  (

while

do...while()

).  To  sposób  na  łagodne  zakończenie  pętli  jak  w  poniŜszym 

przykładzie, gdzie odczytujemy zapisane w pliku ciągi: 

#include <stdio.h> 
 
void main() 

  FILE *fp; 
  char *p; 
  char buff[100]; 
 
  fp = fopen("dane.txt","r"); 
  if (fp == NULL) 
  { 
    fprintf(stderr,"Nie mo

Ŝ

na otworzy

ć

 pliku data.txt"); 

    exit(0); 
  } 
 
  do 
  { 
    p = fgets(buff,100,fp); 
    if (p == NULL) 
      /* Wymuszenie wyj

ś

cia */ 

      continue; 
    puts(p); 
  } 
  while(p); 

W  przypadku  pętli 

for

  instrukcja 

continue

  powoduje  najpierw  wykonanie 

wyraŜenia  inkrementacji,  a  dopiero  po  nim  następuje  sprawdzenie  warunku 
zakończenia.  

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

223 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

223

 

Wejście-wyjście 

Pobieranie danych 

Program  w  języku  C  moŜe  pobierać  dane  z  konsoli  (która  jest  standardowym 
urządzeniem wejściowym), pliku lub portu. Ogólnym poleceniem odczytu danych ze 
standardowego  strumienia  wejściowego 

stdin

  jest 

scanf()

.  Skanuje  ono  po  jednym 

znaku  kolejne  pola  wejściowe.  Podlegają  one  formatowaniu  zgodnie  z  pierwszym  z 
przekazanych  funkcji 

scanf()

  parametrów.  Następnie  pole  zostaje  zapisane  pod 

adresem  przekazanym  jako  kolejny  parametr  wywołania  funkcji.  Przykładowy 
program odczytuje pojedynczą liczbę całkowitą ze strumienia 

stdin

void main() 

  int x; 
  
  scanf("%d", &x); 

Warto zwrócić uwagę na operator uŜyty jako prefiks zmiennej 

x

 na liście parametrów 

wywołania  funkcji 

scanf()

.  Funkcja  ta  zapisuje  bowiem  wartość  pod  określonym 

adresem,  nie  posługując  się  mechanizmem  przypisywania  wartości  zmiennej. 
Ciągiem formatującym jest ciąg znakowy, który moŜe zawierać trzy typy danych: znaki 
odstępu
  (spacja,  tabulator,  przejście  do  nowego  wiersza),  znaki  właściwe  (wszystkie 
znaki  ASCII  z  wyjątkiem  znaku  %)  i  specyfikatory  formatowania.  Specyfikatory  te 
mają następującą składnię: 

%[*][szeroko

ść

][h|l|L]typ 

Oto przykład: 

#include <stdio.h> 
 
void main() 

  char nazwisko[30]; 
  int wiek; 
 
  printf ("Podaj nazwisko i wiek "); 
  scanf("%30s%d",nazwisko,&wiek); 
  printf ("\n%s %d",nazwisko,wiek); 

Zwróćmy  uwagę  na  wiersz 

#include  <stdio.h>

  —  nakazuje  on  kompilatorowi 

przetwarzanie  pliku  nagłówkowego  stdio.h,  w  którym  zawarte  są  prototypy  funkcji 

scanf()

  i 

printf()

.  Po  uruchomieniu  tego  prostego  programu  łatwo  przekonamy 

się, Ŝe uŜycie znaku odstępu przerwie wprowadzanie pierwszego pola danych. 

Alternatywną  funkcją  pobierania  danych  jest 

gets()

,  odczytująca  ciąg  znaków  ze 

strumienia 

stdin

 do momentu napotkania znaku nowego wiersza. W ciągu docelowym 

znak nowego wiersza zastąpiony zostaje znakiem 

NULL (0)

. Charakterystyczna dla tej 

funkcji  jest  moŜliwość  odczytywania  znaków  odstępu.  Oto  nowa  wersja  powyŜszego 
programu (korzystająca z 

gets()

 w miejsce 

scanf()

): 

background image

 
224 

Hack Wars. Tom 1. Na tropie hakerów 

224 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
 
void main() 

  char dane[80]; 
  char *p; 
  char nazwisko[30]; 
  int wiek; 
 
  printf ("\nPodaj nazwisko i wiek "); 
  /* Odczyt ci

ą

gu danych */ 

  gets(dane); 
 
  /* p jest wska

ź

nikiem do ostatniego znaku pobieranego ci

ą

gu */ 

  p = &dane[strlen(dane) - 1]; 
 
  /* Usuwamy spacje ko

ń

cowe, zast

ę

puj

ą

c je znakami NULL */ 

  while(*p == ' '){ 
    *p = 0; 
    p--; 
  } 
 
  /* Lokalizujemy ostatni

ą

 spacj

ę

 w ci

ą

gu */ 

  p = strrchr(dane,' '); 
 
  /* Odczytujemy wiek i zamieniamy na liczb

ę

 */ 

  wiek = atoi(p); 
 
  /* Wstawiamy znak ko

ń

ca ci

ą

gu przed polem wieku */ 

  *p = 0; 
 
  /* Kopiujemy ci

ą

g danych do zmiennej */ 

  strcpy(nazwisko, dane); 
 
  /* Wy

ś

wietlamy wyniki operacji */ 

  printf ("\nNazwisko: %s, wiek: %d", nazwisko, wiek); 

Wyprowadzanie danych 

Podstawową  funkcją  wyprowadzania  danych  jest 

printf()

.  Jest  ona  podobna  do 

scanf()

  z  tą  róŜnicą,  Ŝe  zapisuje  dane  do  standardowego  strumienia  wyjściowego 

stdout

.  Funkcja  pobiera  listę  pól  danych  wyjściowych,  odpowiednio  stosuje 

specyfikatory  formatowania  i  wyprowadza  wynik.  MoŜna  stosować  takie  same 
przekształcenia  formatujące  jak  w  przypadku  funkcji 

scanf()

,  jak  równieŜ 

dodatkowe znaczniki: 

wyrównuje dane wyjściowe do lewej, uzupełniając je z prawej strony 
znakami odstępu międzywyrazowego (spacji), 

wymusza poprzedzanie liczb znakiem. 

Nieco  odmienna  jest  takŜe  postać  specyfikatora  szerokości.  Jest  on  rozbudowany  o 
element określający precyzję: 

szeroko

ść

.precyzja 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

225 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

225

 

Aby  więc  wyświetlić  liczbę  zmiennoprzecinkową  z  dokładnością  do  trzech  miejsc 
dziesiętnych, piszemy: 

printf ("%.3f",x); 

PoniŜej przedstawiamy listę specjalnych stałych znakowych, które mogą pojawić się 
na liście parametrów funkcji 

printf()

\n 

nowy wiersz (NL), 

\r 

powrót karetki (CR), 

\t 

tabulator, 

\b 

znak cofania (backspace), 

\f 

znak nowej strony, 

\v 

tabulator pionowy, 

\\ 

ukośnik odwrotny (backslash), 

\' 

apostrof, 

\" 

cudzysłów, 

\? 

znak zapytania, 

 \o 

ciąg w notacji ósemkowej, 

\x 

ciąg w notacji szesnastkowej. 

Kolejny  program  ilustruje,  w  jaki  sposób  wyświetlić  liczbę  całkowitą  w  postaci 
dziesiętnej,  szesnastkowej  i  ósemkowej.  Liczba 

04

  po  znaku  procentów  (

%

)  w 

instrukcji 

printf()

  nakazuje  kompilatorowi  dopełnienie  wyświetlanej  liczby  do 

szerokości co najmniej czterech cyfr: 

/* Prosty program konwersji liczb dziesi

ę

tnych */ 

/* do postaci szesnastkowej i ósemkowej */ 
 
#include <stdio.h> 
 
void main() 

  int x; 
 
  do 
  { 
    printf ("\nPodaj liczb

ę

 (lub 0, aby zako

ń

czy

ć

) "); 

    scanf("%d",&x); 
    printf ("%04d %04X %04o",x,x,x); 
  } 
  while (x != 0); 
 

Do funkcji pokrewnych 

printf()

 naleŜy 

fprintf()

, której prototyp ma postać: 

fprintf(FILE *fp, char *format[,argument,...]); 

Jej  zadaniem  jest  przesyłanie  sformatowanych  danych  wyjściowych  do  określonego 
strumienia plikowego. 

background image

 
226 

Hack Wars. Tom 1. Na tropie hakerów 

226 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Kolejną tego rodzaju funkcją jest 

sprintf()

 o prototypie: 

sprintf(char *s, char *format[,argument,...]); 

Alternatywą  dla 

printf()

  jest 

puts()

,  funkcja  przesyłająca  prosty  ciąg  do 

strumienia 

stdout

.  Przesyłany  ciąg  zostaje  automatycznie  uzupełniony  znakiem 

nowego  wiersza. Jest to rozwiązanie szybsze od 

printf()

, jednak jego  moŜliwości 

są ograniczone. 

Bezpośrednia wymiana danych z konsolą 

Do  przesyłania  i  odczytu  danych  z  konsoli  (klawiatury  i  ekranu)  moŜna 
wykorzystywać  równieŜ  bezpośrednie  funkcje  we-wy.  WyróŜnia  je  litera  „c”  na 
początku  —  odpowiednikiem 

printf()

  jest  więc 

cprintf()

,  a  odpowiednikiem 

puts()

  —  funkcja 

cputs()

.  RóŜnice  między  funkcjami  bezpośredniej  wymiany 

danych a funkcjami standardowymi są następujące. 









 

Nie są wykorzystywane strumienie predefiniowane, nie moŜna więc przekierować 
danych przesyłanych funkcjami komunikacji bezpośredniej. 









 

Funkcji bezpośrednich nie moŜna przenosić między róŜnymi systemami 
operacyjnymi (m.in. nie moŜna z nich korzystać w programach dla Windows). 









 

Funkcje bezpośrednie są szybsze niŜ standardowe. 









 

Nie zapewniają współpracy ze wszystkimi trybami wyświetlania (zwłaszcza 
trybami graficznymi VESA). 

Wskaźniki 

Wskaźnik  to  zmienna,  która  przechowuje  adres  elementu  danych  w  pamięci. 
Deklaracja  wskaźnika  jest  podobna  do  deklaracji  zwykłej  zmiennej,  ale  nazwa 
poprzedzana jest znakiem gwiazdki (

*

), na przykład: 

char *p; 

PowyŜszy wiersz deklaruje zmienną 

p

 jako wskaźnik do zmiennej typu 

char

Wykorzystanie  wskaźników  dostarcza  szerokich  moŜliwości,  wymaga  jednak 
szczególnej 

uwagi. 

Skutki 

przypisania 

błędnego 

adresu 

są 

najczęściej 

nieprzewidywalne. Oto przykład prostego programu, w którym wykorzystywany jest 
wskaźnik: 

#include <stdio.h> 

 

void main() 

  int a; 
  int *x; 

 

  /* x jest wska

ź

nikiem do danych typu int */ 

 

  a = 100; 
  x = &a; 

 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

227 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

227

 

  printf("\nZmienna a przechowuje warto

ść

 %d pod adresem %p.",a,x); 

Wartości  wskaźników  moŜna zwiększać i zmniejszać, dopuszczalne  są równieŜ inne 
operacje  matematyczne.  Typowym  zastosowaniem  wskaźników  jest  zapewnienie 
dynamicznego  przydziału  pamięci.  W  trakcie  pracy  programu  często  pojawia  się 
potrzeba  przejściowego  (tymczasowego)  zaalokowania  bloku  pamięci.  Korzystamy 
wówczas z funkcji 

malloc()

wska

ź

nik_dowolnego_typu = malloc(liczba_bajtów); 

Funkcja 

malloc()

 zwraca wskaźnik typu 

void

, co oznacza, Ŝe moŜe on wskazywać 

dane dowolnego typu — 

int

char

float

 itd. W poniŜszym przykładzie alokujemy 

pamięć dla tabeli 1000 liczb całkowitych. 

#include <stdio.h> 
#include <stdlib.h> 
 
void main() 

  int *x; 
  int n; 
 
  /* x jest wska

ź

nikiem do danych typu int */ 

 
  /* Tworzymy tablic

ę

 1000-elementow

ą

 */ 

  /* sizeof() dostarcza kompilatorowi informacji o liczbie */ 
  /* bajtów wymaganej do przechowywania zmiennej typu int */ 
 
  x = malloc(1000 * sizeof(int)); 
 
  /* Sprawdzamy czy alokacja została wykonana */ 
  if (x == NULL) 
  { 
    printf("\nNie mo

Ŝ

na zaalokowa

ć

 pami

ę

ci dla 1000-elementowej 

tablicy warto

ś

ci  

     int"); 
    exit(0); 
  } 
 
  /* Przypisujemy warto

ś

ci poszczególnym elementom */ 

  for(n = 0; n < 1000; n++) 
  { 
    *x = n; 
    x++; 
  } 
 
  /* Przywracamy x warto

ść

 adresu pocz

ą

tkowego tabeli */ 

  x -= 1000; 
 
  /* Wy

ś

wietlamy warto

ś

ci tabeli */ 

  for(n = 0; n < 1000; n++){ 
    printf("\nElement %d przechowuje warto

ść

 %d",n,*x); 

    x++; 
  } 
  /* Po u

Ŝ

yciu, dealokujemy blok pami

ę

ci */ 

  free(x); 

background image

 
228 

Hack Wars. Tom 1. Na tropie hakerów 

228 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Wskaźniki  wykorzystuje  się  równieŜ  w  odniesieniu  do  tablic  znaków,  czyli  ciągów 
(strings).  PoniewaŜ  wszystkie  ciągi  w  programach  C  kończy  bajt  o  wartości  0, 
korzystając ze wskaźnika, moŜemy policzyć znaki w ciągu: 

#include <stdio.h> 
#include <string.h> 
 
void main() 

  char *p; 
  char tekst[100]; 
  int dlugosc; 
 
  /* Inicjujemy zmienn

ą

 'tekst' */ 

  strcpy(tekst,"To jest ciag znakowy"); 
 
  /* Ustawiamy warto

ść

 zmiennej p na pocz

ą

tek tekstu */ 

  p = tekst; 
 
  /* Inicjujemy zmienn

ą

 długo

ść

 */ 

  dlugosc = 0; 
  
  /* Zliczamy znaki w zmiennej tekst */ 
  while(*p) 
  { 
    dlugosc++; 
    p++; 
  } 
 
  /* Wy

ś

wietlamy wynik */ 

   printf("\nDlugosc ciagu znakowego to: %d",dlugosc); 

Wymaganą  do  zaadresowania  1  MB  pamięci  20-bitową  liczbę  dzieli  się  na  dwie 
wartości: przesunięcie (offset) i segment (kaŜdy segment to 64 kB). Do przechowywania 
numerów  segmentów  pamięci  komputer  IBM  PC  wykorzystuje  tzw.  rejestry 
segmentowe. Konsekwencją takiego rozwiązania są w języku C trzy dodatkowe słowa 
kluczowe: 









 

near

 — wskaźniki „bliskie” mają rozmiar 16 bitów i umoŜliwiają dostęp 

do danych bieŜącego segmentu, 









 

far

 — wskaźniki „dalekie” obejmują wartości określające przesunięcie 

i segment, umoŜliwiając dostęp do dowolnego adresu w pamięci, 









 

huge

 — wskaźniki „ogromne” to odmiana wskaźników dalekich, zapewniająca 

moŜliwość zwiększania i zmniejszania wartości w całym zakresie 1 MB 
(kompilator generuje odpowiedni kod modyfikujący wartość przesunięcia). 

Nie  będzie  zapewne  zaskakujące  stwierdzenie,  Ŝe  przetwarzanie  programu 
korzystającego ze wskaźników typu 

near

 będzie szybsze niŜ w przypadku programu, w 

którym  zastosowano  wskaźniki 

far

.  Wskaźniki 

huge

  są  oczywiście  największym 

obciąŜeniem.  Kompilatory  C  wyposaŜone  są  w  makro  zwracające  adres 
odpowiadający podanym wartościom numeru segmentu i przesunięcia: 

void far *MK_FP(unsigned segment, unsigned offset); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

229 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

229

 

Struktury 

Język C oferuje technikę grupowania zmiennych pod jedną nazwą, dostarczając w ten 
sposób  wygodnego  sposobu  przechowywania  powiązanych  ze  sobą  informacji  i 
strukturalizowania ich. Składnia definicji struktury jest następująca: 

typedef struct 

  typ_zmiennej nazwa_zmiennej
  typ_zmiennej nazwa_zmiennej; 
  . 
  . 
  .  

nazwa_struktury

UŜywanie  zmiennych  strukturalnych  jest  niezbędne  przy  korzystaniu  z  plików,  w 
których  występuje  uporządkowanie  oparte  na  rekordach  danych.  W  poniŜszym 
przykładzie operować będziemy na prostym pliku z listą adresów. Rozpoczniemy od 
deklaracji  struktury 

dane

,  złoŜonej  z  sześciu  pól: 

nazwisko

adres

miasto

wojewodztwo

poczta

 i 

nrtelefonu

typedef struct 

  char nazwisko[30]; 
  char adres[30]; 
  char miasto[30]; 
  char wojewodztwo[30]; 
  char kod[6]; 
  char nrtelefonu[15]; 

dane; 

Odwołania do pól zmiennej strukturalnej mają postać: 

zmienna_strukt.nazwa_pola

Nie  ma  ograniczenia  liczby  pól  struktury,  nie  jest  równieŜ  wymagane,  aby  typy  pól 
były takie same lub podobne, na przyklad: 

typedef struct 
{  
  char nazwisko[30]; 
  int wiek; 
  char *notatki; 

dp; 

Jest to poprawna deklaracja struktury obejmująca: pole tablicy znakowej, pole liczby 
całkowitej  i  pole  wskaźnika  do  zmiennej  znakowej.  Aby  przekazać  zmienną 
strukturalną jako parametr, korzystamy z jej adresu — poprzedzamy nazwę zmiennej 
operatorem 

&

. Oto przykładowy program wykorzystujący struktury w celu wykonania 

prostych operacji na pliku listy adresów: 

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

background image

 
230 

Hack Wars. Tom 1. Na tropie hakerów 

230 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

#include <string.h> 
#include <fcntl.h> 
#include <sys\stat.h> 
 
/* liczba_wierszy to liczba wierszy ekranu */ 
#define liczba_wierszy 25 
 
typedef struct 

  char nazwisko[30]; 
  char adres[30]; 
  char miasto[30]; 
  char wojewodztwo[30]; 
  char kod[6]; 
  char nrtelefonu[15]; 

dane; 
 
dane rekord; 
int handle; 
 
 
/* Prototypy funkcji */ 
 
void ADD_REC(void); 
void CLS(void); 
void DISPDATA(void); 
void FATAL(char *); 
void GETDATA(void); 
void MENU(void); 
void OPENDATA(void); 
int SEARCH(void); 
 
void CLS() 

  int n; 
 
  for(n = 0; n < liczba_wierszy; n++) 
    puts(""); 

 
void FATAL(char *blad) 

  printf(" \nBlad krytyczny: %s",blad); 
  exit(0); 

 
void OPENDATA() 

  /* Sprawd

ź

 czy istnieje plik danych. Je

Ŝ

eli nie, utwórz. */ 

  /* Je

Ŝ

eli tak, otwórz do odczytu-zapisu na ko

ń

cu pliku. */ 

 

  handle = open("address.dat",O_RDWR|O_APPEND,S_IWRITE); 

 

  if (handle == -1) 
  { 
    handle = open("address.dat",O_RDWR|O_CREAT,S_IWRITE); 
    if (handle == -1) 
      FATAL("Nie mo

Ŝ

na utworzy

ć

 pliku danych"); 

  } 

 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

231 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

231

 

void GETDATA() 

  /* Pobierz dane adresowe */ 

 

  CLS(); 

 

  printf("Nazwisko "); 
  gets(rekord.nazwisko); 
  printf("\nAdres "); 
  gets(rekord.adres); 
  printf("\nMiasto "); 
  gets(rekord.miasto); 
  printf("\nWojewództwo "); 
  gets(rekord.wojewodztwo); 
  printf("\nKod pocztowy "); 
  gets(rekord.kod); 
  printf("\nNumer telefonu "); 
  gets(rekord.nrtelefonu); 

 

void DISPDATA() 

  /* Wy

ś

wietl dane adresowe */ 

  char tekst[5]; 

 

  CLS(); 

 

  printf("Nazwisko %s",rekord.nazwisko); 
  printf("\nAdres %s",rekord.adres); 
  printf("\nMiasto %s",rekord.miasto); 
  printf("\nWojewództwo %s",rekord.wojewodztwo); 
  printf("\nKod pocztowy %s",rekord.kod); 
  printf("\nNumer telefonu %s\n\n",rekord.nrtelefonu); 

 

  puts(" Wci

ś

nij ENTER"); 

  gets(tekst); 

 

void ADD_REC() 

  /* Doł

ą

cz do pliku danych nowy rekord */ 

  int wynik; 

 

  wynik = write(handle,&rekord,sizeof(dane)); 

  if (wynik == -1) 
    FATAL("Zapis do pliku danych niemo

Ŝ

liwy"); 


 
int SEARCH() 

  char tekst[100]; 
  int wynik; 
 
  printf("Wprowad

ź

 wzór wyszukiwania "); 

  gets(tekst); 
  if (*tekst == 0) 
    return(-1); 
 
  /* Zlokalizuj pocz

ą

tek pliku */ 

  lseek(handle,0,SEEK_SET); 
 
  do 

background image

 
232 

Hack Wars. Tom 1. Na tropie hakerów 

232 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  { 
    /* Załaduj rekord do pami

ę

ci */ 

    wynik = read(handle,&rekord,sizeof(dane)); 
    if (wynik > 0) 
    { 
      /* Przeszukaj rekord */ 
      if (strstr(rekord.nazwisko,tekst) != NULL) 
        return(1); 
      if (strstr(rekord.adres,tekst) != NULL) 
        return(1); 
      if (strstr(rekord.miasto,tekst) != NULL) 
        return(1); 
      if (strstr(rekord.wojewodztwo,tekst) != NULL) 
        return(1); 
      if (strstr(rekord.kod,tekst) != NULL) 
        return(1); 
      if (strstr(rekord.nrtelefonu,tekst) != NULL) 
        return(1); 
    } 
  } 
  while(wynik > 0); 
  return(0); 

 
void MENU() 

  int opcja; 
  char tekst[10]; 
 
  do 
  { 
    CLS(); 
    puts("\n\t\t\tWybierz opcj

ę

"); 

    puts("\n\n\t\t\t1 Dodaj nowy rekord"); 
    puts("\n\n\t\t\t2 Przeszukiwanie danych"); 
    puts("\n\n\t\t\t3 Wyj

ś

cie"); 

    puts("\n\n\n\n\n"); 
    gets(tekst); 
    opcja = atoi(tekst); 

    switch(opcja) 
    { 
      case 1 : GETDATA(); 
        /* Przed doł

ą

czaniem rekordu przejd

ź

 do ko

ń

ca pliku */ 

        lseek(handle,0,SEEK_END); 
        ADD_REC(); 
        break; 
 
      case 2 : if (SEARCH()) 
          DISPDATA(); 
        else 
        { 
          puts("NIEZNALEZIONE!"); 
          puts("Wci

ś

nij ENTER"); 

          gets(tekst); 
        } 
        break; 
 
      case 3 : break; 
    } 
  } 
  while(opcja != 3); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

233 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

233

 


 
void main() 

  CLS(); 
  OPENDATA(); 
  MENU(); 

Pola bitowe 

Język C przewiduje moŜliwość korzystania w strukturach ze zmiennych o rozmiarze 
mniejszym niŜ 8 bitów. Określa się je mianem pól bitowych, a ich rozmiar moŜe być 
dowolny, od 1 bitu wzwyŜ. Deklaracja pola bitowego wygląda następująco: 

typ nazwa : liczba_bitów

Przykładem moŜe być deklaracja kilku jednobitowych znaczników stanu: 

typedef struct 

  unsigned przeniesienie : 1; 
  unsigned zero          : 1; 
  unsigned przepelnienie : 1; 
  unsigned parzystosc    : 1; 

df; 
 
df znaczniki; 

Zmienna 

znaczniki

 będzie zajmować w pamięci tylko 4 bity, mimo Ŝe składa się z 4 

pól, z których kaŜde dostępne jest jako osobne pole struktury. 

Union 

Kolejnym  ułatwieniem  języka  C,  pozwalającym  zapewnić  optymalne  wykorzystanie 
dostępnej  pamięci,  jest  struktura 

union

,  czyli  zbiór  zmiennych,  współuŜytkujących 

jeden  adres  pamięci.  Oznacza  to,  oczywiście,  Ŝe  w  danym  momencie  dostępna  jest 
tylko jedna ze zmiennych składowych. Deklaracja 

union

 ma następującą postać: 

union nazwa 

  typ nazwa_zmiennej
  typ nazwa_zmiennej
  . 
  . 
  . 
  typ nazwa_zmiennej
}; 

Wyliczenia 

Wyliczenie  (enumeracja)  to  przypisanie  liście  symboli  rosnących  wartości 
całkowitych. Wyliczenie deklarujemy: 

enum nazwa { lista } lista_zmiennych

background image

 
234 

Hack Wars. Tom 1. Na tropie hakerów 

234 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Przykładem moŜe być definicja listy kolorów: 

enum KOLORY 

  CZARNY, 
  NIEBIESKI, 
  ZIELONY, 
  CZERWONY, 
  BRAZOWY, 
  JASNOSZARY, 
  CIEMNOSZARY, 
  JASNONIEBIESKI, 
  JASNOZIELONY, 
  JASNOCZERWONY, 
  ZOLTY, 
  BIALY 
}; 

Operacje na plikach 

W  operacjach  dostępu  do  plików  język  C  posługuje  się  buforowanymi  strumieniami 
plikowymi.  Niektóre  z  platform  języka,  jak  UNIX  i  DOS,  oferują  równieŜ 
niebuforowane uchwyty plików. 

Strumienie buforowane 

Dostęp do strumieni buforowanych realizowany jest za pośrednictwem wskaźnika do 
zmiennej  typu 

FILE

.  Ten  szczególny  typ  danych  zdefiniowany  został  w  nagłówku 

stdio.h. Aby więc zadeklarować wskaźnik do pliku, wprowadzamy: 

#include <stdio.h> 
 
FILE *ptr; 

Aby  otworzyć  strumień,  uŜywamy  funkcji 

fopen()

.  Pobiera  ona  dwa  parametry: 

nazwę otwieranego pliku oraz tryb dostępu. Oto lista trybów dostępu. 

Tryb 

Opis 

otwórz tylko do odczytu (plik musi istnieć), 

utwórz do zapisu; zastąp, jeŜeli plik o podanej nazwie istnieje,  

otwórz do dołączania danych (dopisywania na końcu pliku); utwórz nowy plik, 
jeŜeli plik o podanej nazwie nie istnieje, 

r+ 

otwórz istniejący plik do odczytu i zapisu (plik musi istnieć), 

w+ 

utwórz do odczytu i zapisu; zastąp, jeŜeli istnieje, 

a+ 

otwórz do czytania i dołączania danych; utwórz nowy plik, jeŜeli nie istnieje. 

Aby  określić  tryb  tekstowy  lub  binarny,  do  opisu  trybu  moŜna  dołączyć 

t

  lub 

b

.  W 

przypadku pominięcia tego znacznika strumień zostanie otwarty w trybie określanym 
zmienną  globalną 

_fmode

.  Odczyt  i  zapis  danych  do  strumieni  plikowych  w  trybie 

tekstowym  wiąŜe  się  z  konwersją  —  podczas  zapisu  znaki  CR  i  LF  zamieniane  są  na 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

235 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

235

 

pary CR  LF, a przy odczycie  pary  CR  LF  ulegają zamianie na pojedynczy znak  LF. 
Tego rodzaju operacje nie są wykonywane w trybie binarnym. 

JeŜeli  funkcja 

fopen()

  nie  będzie  mogła  otworzyć  pliku,  zwróci  w  miejsce 

wskaźnika wartość 

NULL

 (zdefiniowaną w stdio.h). PoniŜszy program utworzy nowy 

plik dane.txt i udostępni go do odczytu i zapisu: 

#include <stdio.h> 
 
void main() 

  FILE *fp; 
   
  fp = fopen("dane.txt","w+"); 

Aby  zamknąć  strumień,  uŜywamy  funkcji 

fclose()

,  wymagającej  podania 

wskaźnika do pliku. 

fclose(fp); 

JeŜeli podczas zamykania strumienia wystąpi błąd, funkcja 

fclose()

 zwróci wartość 

niezerową  (znacznik  EOF  —  End  Of  File).  Do  przesyłania  i  odbierania  danych  ze 
strumieni  słuŜą  cztery  podstawowe  funkcje: 

fgetc()

fputc()

fgets()

  i 

fputs()

.  Funkcja 

fgetc()

  odczytuje  pojedynczy  znak  z  określonego  strumienia 

wejściowego (przekształcany do liczby całkowitej): 

 int fgetc(FILE *fp); 

Jej  odwrotnością  jest 

fputc()

,  zapisująca  pojedynczy  znak  do  określonego 

strumienia wyjściowego: 

 int fputc( int c, FILE *fp); 

Funkcja 

fgets()

 odczytuje ze strumienia wejściowego ciąg: 

char *fgets(char *s, int liczba_bajtów, FILE *fp); 

Odczyt  zostaje  przerwany  po  pobraniu 

liczba_bajtów-1

  znaków  lub  znaku 

nowego wiersza (równieŜ wstawianego do tablicy). Do odczytanego ciągu 

s

 dołączany 

jest  kończący  znak

  NULL  (znany  tak

Ŝ

e  pod  postaci

ą

  ’\0’)

.  W  przypadku 

wystąpienia błędów funkcja zwraca 

NULL

Funkcja 

fputs()

  zapisuje  do  strumienia  ciąg  zakończony  znakiem 

NULL

  (inaczej 

’\0’

): 

int fputs(const char *s, FILE *fp); 

Wszystkie  opisywane  funkcje  zwracają  w  przypadku  błędów  wartość 

EOF

 

(zdefiniowaną  w  stdio.h)  z  wyjątkiem  funkcji 

fgets()

,  która  w  przypadku 

wystąpienia  błędu  zwraca 

NULL

.  PoniŜszy  program  tworzy  kopię  pliku  dane.dat,  o 

nazwie dane.old, ilustrując zarazem uŜycie wszystkich czterech funkcji: 

#include <stdio.h> 
 

background image

 
236 

Hack Wars. Tom 1. Na tropie hakerów 

236 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

int main() 

  FILE *in; 
  FILE *out; 
 
  in = fopen("data.dat","r"); 
 
  if (in == NULL) 
  { 
    puts("\nNie mo

Ŝ

na otworzy

ć

 do odczytu pliku dane.dat"); 

    return(0); 
  } 
 
  out = fopen("dane.old","w+"); 
 
  if (out == NULL) 
  { 
    puts("\nNie mo

Ŝ

na utworzy

ć

 pliku dane.old"); 

    return(0); 
  } 
   
  /* Powtarzaj odczytywanie i zapisywanie pojedynczych bajtów */ 
  /* a

Ŝ

 do natrafienia na EOF  */ 

  while(!feof(in)) 
    fputc(fgetc(in),out); 
 
  /* Zamknij strumienie plikowe */ 
  fclose(in); 
  fclose(out); 
 
  return(0); 

W kolejnym przykładowym programie uŜywamy funkcji 

fputs

 do kopiowania tekstu 

ze  strumienia 

stdin

  (zazwyczaj  oznacza  to  znaki  wprowadzane  z  klawiatury)  do 

nowego pliku dane.txt

#include <stdio.h> 
 
int main() 

  FILE *fp; 
  char tekst[100]; 
 
  fp = fopen("dane.txt","w+"); 
 
  do 
  { 
    gets(tekst); 
    fputs(tekst,fp); 
  } 
  while(*tekst); 
 
  fclose(fp); 

Swobodny dostęp do danych strumieni 

Dostęp  swobodny  do  danych  dostarczanych  za  pośrednictwem  strumieni  zapewnia 
funkcja 

fseek()

 o prototypie: 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

237 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

237

 

int fseek(FILE *fp, long liczba_bajtów, int zacznij_od); 

Funkcja  zmienia  pozycję  wskaźnika  pliku  skojarzonego  ze  strumieniem  otwartym 
wcześniej  przez 

fopen()

.  Wskaźnik  ustawiany  jest  na 

liczba_bajtów

  za  (lub 

przed  w przypadku  wartości  ujemnej)  pozycją 

zacznij_od

.  Tą  ostatnią  moŜe  być 

początek pliku, bieŜące połoŜenie wskaźnika lub koniec pliku. Pozycje te symbolizują 
stałe 

SEEK_SET

SEEK_CUR

  i 

SEEK_END

.  Udaną  operację 

fseek()

  sygnalizuje 

zwrócenie  wartości 

0

.  Uzupełnieniem 

fseek()

  jest  funkcja 

ftell()

,  zwracająca 

wartość bieŜącej pozycji wskaźnika pliku: 

long int ftell(FILE *fp); 

Funkcja  zwraca  pozycję  wskaźnika  pliku,  określoną  jako  ilość  bajtów  od  początku 
pliku, lub 

-1

 w przypadku błędu. 

Uchwyty 

Uchwyty plików (handles) otwiera funkcja 

open()

 o prototypie: 

int open(char *nazwa_pliku, int dost

ę

p[, unsigned tryb]); 

Udaną operację sygnalizuje zwrócenie numeru uchwytu. W pozostałych przypadkach 
zwracane  jest 

–1

.  Na  wartość 

dost

ę

p

  składają  się  połączone  bitową  operacją  OR 

stałe  symboliczne,  odpowiadające  deklaracjom  w  pliku  fcntl.h.  RóŜnią  się  one  w 
zalezności od kompilatora. Do typowych naleŜą: 

O_APPEND 

przed kaŜdym zapisem wskaźnik pliku będzie ustawiany na końcu pliku, 

O_CREAT 

jeŜeli plik nie istnieje, zostanie utworzony, 

O_TRUNC 

obcina istniejący plik do długości 0 bajtów, 

O_EXCL 

uŜywane w połączeniu z 

O_CREAT

O_BINARY 

otwiera plik w trybie binarnym, 

O_TEXT 

otwiera plik w trybie tekstowym. 

Po  przypisaniu  uchwytu  pliku  za  pomocą  polecenia 

open()

  moŜna  korzystać  z 

funkcji 

read()

 i 

write()

. Prototyp 

read()

 jest następujący: 

int read(int handle, void *buf, unsigned liczba_bajtów); 

Funkcja  podejmuje  próbę  odczytu  podanej  liczby  bajtów  i  zwraca  liczbę  bajtów 
faktycznie  pobranych  przez  uchwyt  pliku.  Odczytane  dane  umieszczane  są  w  bloku 
pamięci  określonym  parametrem 

buf

.  Funkcja 

write()

  działa  podobnie,  nie  róŜni 

się  równieŜ  jej  prototyp  i  sposób  generowania  wartości  zwracanej.  Zapisuje  ona 
podaną  ilość  bajtów  z  określonego  wskaźnikiem  bloku  pamięci.  Pliki  otwierane 
funkcją 

open()

 zamykamy funkcją 

close()

int close(int handle); 

Funkcja 

close()

  zwraca 

0

  w  przypadku  operacji  udanej,  a 

–1

  w  przypadku 

wystąpienia błędów. 

background image

 
238 

Hack Wars. Tom 1. Na tropie hakerów 

238 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Dostęp  swobodny  zapewnia  funkcja 

lseek()

,  bardzo  podobna  do 

fseek()

,  ale 

pobierająca  jako  parametr  numer  uchwytu,  a  nie  wskaźnik  strumienia 

FILE

.  W 

poniŜszym  przykładzie  wykorzystujemy  uchwyt  pliku  do  zapisu  danych  z 

stdin

 

(czyli klawiatury) do nowego pliku o nazwie dane.txt

#include <io.h> 
#include <fcntl.h> 
#include <sys\stat.h> 
 
int main() 

  int handle; 
  char tekst[100]; 
 
  handle = open("dane.txt", O_RDWR|O_CREAT|O_TRUNC,S_IWRITE); 
 
  do 
  { 
    gets(tekst); 
    write(handle, &tekst, strlen(tekst)); 
  } 
  while(*tekst); 
 
  close(handle); 

Przegląd funkcji plikowych 

Norma  ANSI  definiuje  związane  z  plikami  operacje  we-wy  przy  uŜyciu  strumieni, 
opisując róŜnorodne funkcje. Prototyp funkcji 

fopen()

 ma postać: 

FILE *fopen(const char *nazwa, const char *tryb); 

Funkcja podejmuje próbę otwarcia strumienia łączącego z plikiem o podanej nazwie 
w określonym trybie. Udana operacja kończy się zwróceniem wskaźnika typu 

FILE

W  przypadku  niepowodzenia  funkcji  zwraca 

NULL

.  Na  wcześniejszych  stronach 

przedstawiony został opis parametru 

tryb

Funkcja 

fclose()

  słuŜy  do  zamykania  strumienia  otwartego  wcześniejszym 

wywołaniem 

fopen()

int fclose(FILE *fp); 

Udana  operacja 

fclose()

  kończy  się  opróŜnieniem  wszystkich  buforów  pliku  i 

zwróceniem wartości 

0

. W przypadku błędów zwracana jest wartość 

EOF

Wiele komputerów korzysta z buforowanego dostępu do plików. Oznacza to, Ŝe dane, 
zapisywane  do  strumienia,  wstępnie  umieszczane  są  w  pamięci,  a  faktyczny  zapis 
następuje dopiero po przekroczeniu pewnej granicznej ilości bajtów. JeŜeli w czasie, 
gdy  dane  nie  zostały  jeszcze  faktycznie  zapisane  do  strumienia,  nastąpi  awaria 
zasilania,  dane  zostaną  utracone.  Zabezpiecza  przed  tym  funkcja 

fflush()

wymuszająca zapisanie wszystkich danych oczekujących: 

int fflush(FILE *fp); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

239 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

239

 

JeŜeli  wywołanie 

fflush()

  jest  udane,  związane  ze  strumieniem  bufory  zostają 

opróŜnione  i  zwracana  jest  wartość 

0

.  W  przypadku  błędów  funkcja  zwraca  wartość 

EOF

.  

Kolejną funkcją jest 

ftell()

 zwracająca lokalizację wskaźnika pliku: 

long int ftell(FILE *fp); 

Funkcja zwraca przesunięcie wskaźnika pliku w stosunku do początku pliku lub 

–1L

 

w  przypadku  błędów.  Przesunięcie  wskaźnika  pliku  do  nowej  pozycji  umoŜliwia 

fseek()

int fseek(FILE *fp, long offset, int zacznij_od); 

Funkcja podejmuje próbę przesunięcia wskaźnika pliku o 

offset

 bajtów od pozycji 

zacznij_od

, określonej jedną ze stałych: 

SEEK_SET 

początek pliku, 

SEEK_CUR 

bieŜąca pozycja wskaźnika pliku, 

SEEK_END 

koniec pliku. 

Przesunięcie  (

offset

)  moŜe  być  wartością  dodatnią  (przesuwanie  wskaźnika  w 

stronę  końca  pliku)  lub  ujemną  (przesuwanie  wskaźnika  w  stronę  początku  pliku). 
Aby szybko przenieść  wskaźnik do początku pliku  i usunąć  wcześniejsze odwołania 
do błędów, C dostarcza funkcji 

rewind()

void rewind(FILE *fp); 

Funkcja ta działa podobnie jak 

fseek(fp,0L,SEEK_SET)

. Jednak 

fseek()

 usuwa 

znacznik 

EOF

,  a 

rewind()  dodatkowo

  wszystkie  sygnały  błędów.  Informacje  o 

błędach funkcji plikowych moŜna pobrać przy uŜyciu funkcji 

ferror()

int ferror (FILE *fp); 

Funkcja zwraca wartość niezerową, jeŜeli w określonym strumieniu wystąpił błąd. Po 
sprawdzeniu  wartości 

ferror()

  naleŜy  zadbać  o  usunięcie  sygnałów  błędów  za 

pomocą funkcji 

clearerr()

void clearerr(FILE *fp); 

Sprawdzenie,  czy  spełniony  jest  warunek  osiągnięcia  końca  pliku,  realizuje 
predefiniowane makro 

feof()

:  

int feof(FILE *fp); 

Makro zwraca wartość niezerową, gdy dla danego strumienia stwierdzono osiągnięcie 
końca pliku. W pozostałych przypadkach zwracaną wartością jest 

0

Dostępnych  jest  kilka  funkcji  realizujących  odczyt  danych  ze  strumienia  plikowego. 
Pojedyncze znaki moŜna odczytywać funkcją 

fgetc()

int fgetc(FILE *fp); 

background image

 
240 

Hack Wars. Tom 1. Na tropie hakerów 

240 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

fgetc()

  zwraca  wartość  ASCII  pobranego  znaku  lub  znak 

EOF

  w  przypadku 

wystąpienia  błędu.  Odczyt  ciągu  danych  umoŜliwia  funkcja 

fgets()

,  odczytująca 

ciąg zakończony znakiem nowego wiersza: 

char *fgets(char  *s, int n, FILE *fp); 

W  wyniku  udanego  wywołania  funkcji  w  zmiennej 

s

  umieszczany  jest  ciąg 

zakończony  znakiem  nowego  wiersza  lub  zawierający 

n-1

  znaków.  Funkcja 

zachowuje kończący ciąg znak nowego wiersza, dołączając do ciągu 

s

 bajt 

NULL

. W 

przypadku  nieudanego  wywołania  zwracany  jest  wskaźnik  pusty.  Ciągi  zapisujemy 
do strumienia funkcją 

fputs()

int fputs(const char *s, FILE *fp); 

Funkcja 

fputs()

  zapisuje  wszystkie  znaki  ciągu 

s

,  z  wyjątkiem  końcowego  bajtu 

NULL

,  do  strumienia 

fp

.  Standardowo  funkcja  zwraca  ostatni  zapisany  znak,  a  w 

przypadku wystąpienia błędów — 

EOF

. Dostępna jest równieŜ funkcja zapisująca do 

strumienia pojedynczy znak 

fputc()

int fputc(int c, FILE *fp); 

Funkcja zwraca zapisany znak lub, w przypadku wystąpienia błędów, znak 

EOF

Aby odczytać ze strumienia duŜy blok danych lub rekord, moŜna posłuŜyć się funkcją 

fread()

size_t fread(void *ptr, size_t rozmiar, size_t n, FILE *fp); 

Funkcja  podejmuje  próbę  odczytu 

n

  elementów,  z  których  kaŜdy  ma  długość 

rozmiar

,  ze  strumienia  plikowego 

fp

  do  bloku  pamięci  określonego  wskaźnikiem 

ptr

.  Aby  ustalić,  czy  operacja  przebiegła  bez  zakłóceń,  korzystamy  z  funkcji 

ferror()

Siostrzaną funkcją 

fread()

 jest 

fwrite()

size_t fwrite(const void *ptr, size_t rozmiar, size_t n, FILE *fp); 

Funkcja  zapisuje 

n

  elementów  o  długości 

rozmiar

  z  obszaru  pamięci  określonego 

wskaźnikiem 

ptr

 do strumienia 

fp

Funkcja 

fscanf()

 umoŜliwia odczyt danych formatowanych: 

int fscanf(FILE *fp, const char *format[,adres ...]); 

Funkcja zwraca  liczbę faktycznie odczytanych pól, a 

EOF

 w przypadku  końca pliku. 

PoniŜszy przykład ilustruje uŜyteczność funkcji 

fscanf()

 podczas odczytywania ze 

strumienia liczb: 

#include <stdio.h> 
 
void main() 

  FILE *fp; 
  int a; 
  int b; 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

241 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

241

 

  int c; 
  int d; 
  int e; 
  char tekst[100]; 
 
  fp = fopen("dane.txt","w+"); 
 
  if(!fp) 
  { 
    perror("Nie mo

Ŝ

na utworzy

ć

 pliku"); 

    exit(0); 
  } 
  fprintf(fp,"1 2 3 4 5 \"Wiersz liczb\""); 
 
  fflush(fp); 
 
  if (ferror(fp)) 
  { 
    fputs("Bł

ą

d przy zapisie strumienia", stderr); 

    exit(1); 
  } 
 
  rewind(fp); 
  if (ferror(fp)) 
  { 
    fputs("Bł

ą

d przy przewijaniu strumienia", stderr); 

    exit(1); 
  } 
 
  fscanf(fp,"%d %d %d %d %d %s", &a, &b, &c, &d, &e, tekst); 
  if (ferror(fp)) 
  { 
    fputs("Bł

ą

d odczytu ze strumienia", stderr); 

    exit(1); 
  } 
 
  printf("\nFunkcja fscanf() zwróciła %d %d %d %d %d 
%s",a,b,c,d,e,tekst); 

Jak łatwo zauwaŜyć, zapis formatowanych danych realizuje funkcja 

fprintf()

. Gdy 

pojawia  się  potrzeba  zapisania  połoŜenia  wskaźnika  pliku  i  późniejszego  jego 
przywrócenia, moŜna skorzystać z funkcji 

fgetpos()

 i 

fsetpos()

. Pierwsza z nich 

odczytuje bieŜącą pozycję wskaźnika pliku: 

int fgetpos(FILE *fp, fpos_t *pozycja); 

Funkcja 

fsetpos()

 ustawia wskaźniki pliku na określonej pozycji: 

int fsetpos(FILE *fp, const fpos_t *pozycja); 

Typ 

fpos_t

  zdefiniowany  został  w  nagłówku  stdio.h.  Funkcje  te  są  wygodniejsze 

w uŜyciu niŜ 

ftell()

 i 

fseek()

Z  otwartym  juŜ  strumieniem  moŜna  skojarzyć  nowy  plik.  UmoŜliwia  to  funkcja 

freopen()

FILE *freopen(const char *nazwa, const char *tryb, FILE *fp); 

background image

 
242 

Hack Wars. Tom 1. Na tropie hakerów 

242 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Funkcja zamyka strumień istniejący i podejmuje próbę jego ponownego otwarcia przy 
uŜyciu  podanej  nazwy  pliku.  Znajduje  to  zastosowanie  przy  przekierowywaniu 
strumieni  predefiniowanych 

stdin

stdout

  i 

stderr

  do  pliku  lub  urządzenia. 

Przykładowo,  gdy  pojawia  się  potrzeba  przekierowania  wszystkich  danych 
wyjściowych kierowanych do 

stdout

 na drukarkę, moŜna uŜyć polecenia: 

freopen("LPT1","w",stdout); 

Predefiniowane strumienie we-wy 

Wstępnie  zdefiniowane  zostały  trzy  strumienie  we-wy: 

stdin

stdout

  i 

stderr

Domyślnie 

stdin

  i 

stdout

  odpowiadają  klawiaturze  i  monitorowi.  Na  wielu 

platformach,  w  tym  systemów  DOS  i  UNIX,  dostępna  jest  moŜliwość  ich 
przekierowania.  Strumień 

stderr

  domyślnie  powiązany  jest  z  monitorem 

(wyświetlaczem).  Praktyka  jego  przekierowywania  nie  jest  raczej  stosowana.  Jego 
podstawowym  zadaniem  jest  zapewnienie  moŜliwości  wyświetlania  komunikatów 
błędów,  nawet  w  sytuacji  gdy  powiązanie  standardowego  wyjścia  (

stdout

)  zostało 

zmienione: 

fputs("Komunikat o bł

ę

dzie", stderr); 

Funkcje 

printf()

  i 

puts()

  przekazują  dane  do  strumienia 

stdout

.  Funkcje 

scanf()

  i 

gets()

  pobierają  dane  ze  strumienia 

stdin

.  Przekierowanie  tych 

strumieni zmienia sposób działania funkcji. 

Jako  przykład  plikowych  operacji  we-wy  na  platformie  PC,  korzystających  z 
moŜliwości przekierowania strumieni, przedstawimy prosty program przesyłający do 
strumienia 

stdout

  zawartość  określonego  pliku,  przedstawioną  jako  wartości 

szesnastkowe. Polecenie w postaci: 

dump nazwa_pliku.xxx > dane_wyj

ś

ciowe.xxx 

pozwoli zmienić domyślne powiązanie strumienia 

stdout

 z monitorem. 

#include <stdio.h> 
#include <fcntl.h> 
#include <io.h> 
#include <string.h> 

main(int argc, char *argv[]) 

  unsigned licznik; 
  unsigned char v1[20]; 
  int f1; 
  int x; 
  int n; 

  if (argc != 2) 
  { 
    fputs("\nBŁ

Ą

D. Poprawna składnia wywołania: dump f1\n",stderr); 

    return(1); 
  } 
 
  f1 = open(argv[1],O_RDONLY); 
 
  if (f1 == -1) 
  { 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

243 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

243

 

    fprintf(stderr, "\nBŁ

Ą

D. Nie mo

Ŝ

na otworzy

ć

 %s\n",argv[1]); 

  return(1); 
  } 
 
  fprintf(stdout,"\nZAWARTO

ŚĆ

 PLIKU %s\n\n",strupr(argv[1])); 

 
  licznik = 0; 
 
  while(1) 
  { 
    /* Wypełnienie bufora zerami */ 
    memset(v1,0,20); 
 
    /* Pobranie do bufora danych z pliku */ 
    x = _read(f1,&v1,16); 
    
    /* x = 0 to EOF, x = -1 oznacza bł

ą

d */ 

    if (x < 1) 
      break; 
 
    /* Wyprowad

ź

 offset w pliku */ 

    fprintf(stdout,"%06d(%05x) ",licznik,licznik); 
 
    licznik +=16; 
 
    /* Wyprowad

ź

 szesnastkowe warto

ś

ci bajtów z bufora */ 

    for(n = 0; n < 16; n++) 
      fprintf(stdout,"%02x ",v1[n]); 
 
    /* Wyprowad

ź

 warto

ś

ci ASCII bajtów z bufora */ 

    for(n = 0; n < 16; n++) 
    { 
      if ((v1[n] > 31) && (v1[n] < 128)) 
        fprintf(stdout,"%c",v1[n]); 
      else 
        fputs(".",stdout); 
    } 
 
    /* Zako

ń

cz znakiem nowego wiersza */ 

    fputs("\n",stdout); 
  } 
 
  /* zako

ń

czenie normalne */ 

  return(0); 

Ciągi 

Język  C  naleŜy  do  najlepiej  wyposaŜonych  w  funkcje  obsługi  ciągów  pośród 
uniwersalnych  języków  programowania.  Ciąg  to  jednowymiarowa  tablica  znaków 
zakończona bajtem zerowym. Ciągi moŜna inicjować dwoma sposobami. Pierwszym 
jest nadanie im stałej wartości w kodzie programu: 

int main() 

  char *p = "System 5"; 
  char nazwa[] = "Program testowy"; 
return(0); 

background image

 
244 

Hack Wars. Tom 1. Na tropie hakerów 

244 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Drugi  to  utworzenie  ciągu  w  czasie  wykonywania  programu  za  pomocą  funkcji 

strcpy()

char *strcpy(char *cel,const char *

ź

ródło); 

Funkcja 

strcpy()

 kopiuje ciąg źródłowy do lokalizacji docelowej, na przykład: 

#include <stdio.h> 
 
int main() 

  char nazwa[50]; 
 
  strcpy(nazwa,"Servile Software"); 
   
  printf("\nWarto

ść

 ci

ą

gu 'nazwa' to %s",nazwa); 

return 0; 

Język C umoŜliwia bezpośredni dostęp do kaŜdego bajtu ciągu: 

#include <stdio.h> 
 
int main() 

  char nazwa[50]; 
 
  strcpy(nazwa,"Servile Software"); 
 
  printf("\nWarto

ść

 ci

ą

gu 'nazwa' to %s",nazwa); 

 
  /* Zast

ą

pienie pierwszego bajtu liter

ą

 's' */ 

  nazwa[0] = 's'; 
 
  printf("\nWarto

ść

 ci

ą

gu 'nazwa' to %s",nazwa); 

return 0; 

Niektóre kompilatory C wyposaŜone zostały w funkcje konwersji ciągów do wielkich 
i małych liter, nie obejmuje ich jednak norma ANSI. W specyfikacji pojawiają się za 
to funkcje 

toupper()

 i 

tolower()

, zwracające pojedynczy znak ( w postaci wartości 

int

) zamieniony na literę wielką lub małą. Łatwo na tej podstawie utworzyć własne 

funkcje konwersji ciągów: 

#include <stdio.h> 
 
void strupr(char *zrodlo) 

  char *p; 
 
  p = zrodlo; 
  while(*p) 
  { 
if((*p)>=97 && (*p)<=122) 
    *p = toupper(*p); 
    p++; 
  } 

 
void strlwr(char *zrodlo) 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

245 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

245

 


  char *p; 
 
  p = zrodlo; 
  while(*p) 
  { 
if((*p)>=65 && (*p)<=90) 
    *p = tolower(*p); 
    p++; 
  } 

 
int main() 

  char nazwa[50]; 
 
  strcpy(nazwa,"Servile Software"); 
 
  printf("\nWarto

ść

 ci

ą

gu 'nazwa' to %s",nazwa); 

 
  strupr(nazwa); 
 
  printf("\nWarto

ść

 ci

ą

gu 'nazwa' to %s",nazwa); 

 
  strlwr(nazwa); 
 
  printf("\nWarto

ść

 ci

ą

gu 'nazwa' to %s",nazwa); 

return 0; 

(To niezupełnie tak. Funkcje toupper i tolower tworz

ą

 litery wielkie 

i małe nie sprawdzaj

ą

c, jaka jest posta

ć

 

ź

ródłowa. Konwersja odbywa 

si

ę

 odpowiednio poprzez odj

ę

cie lub dodanie do warto

ś

ci znaku 32 (bo 

taka jest ró

Ŝ

nica pomi

ę

dzy odpowiadaj

ą

cymi sobie literami wielkimi i 

małymi). Je

ś

li argument funkcji toupper b

ę

dzie ju

Ŝ

 liter

ą

 wielk

ą

wynik konwersji oka

Ŝ

e si

ę

 bezsensowny. W tym przypadku ‘S’ zostanie 

zamienione na ‘3’). W funkcjach strlwr i strupr potrzebne byłoby wi

ę

sprawdzanie, czy znak spełnia kryteria, np.: 
while (*p) 

if((*p)>=97 && (*p)<=122) 
 

*p = toupper(*p); 

p++; 

oraz  
while (*p) 

if((*p)>=65 && (*p)<=90) 
 

*p = tolower(*p); 

p++; 

 
Dodatkowe linie programu wstawiłem w kod P.B.). 

W  przeciwieństwie  do  innych  języków  programowania  C  nie  narzuca  ograniczenia 
długości  ciągu.  Jednak  w  przypadku  niektórych  procesorów  (CPU)  pojawia  się 
ograniczenie  wielkości  bloku  pamięci.  Oto  prosty  program  odwracający  kolejność 
znaków w ciągu: 

#include <stdio.h> 
#include <string.h> 
 

background image

 
246 

Hack Wars. Tom 1. Na tropie hakerów 

246 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

 
char *strrev(char *s) 

  /* Odwraca kolejno

ść

 znaków w ci

ą

gu, pozostawiaj

ą

c jedynie */ 

  /* ko

ń

cowy znak NULL */ 

 
  char *pocz; 
  char *koniec; 
  char tmp; 
  
  /* Ustaw wska

ź

nik 'koniec' na ostatni znak ci

ą

gu */ 

  koniec = s + strlen(s) - 1; 
 
  /* Zabezpiecz wska

ź

nik do pocz

ą

tku ci

ą

gu */ 

  pocz = s; 
 
  /* Zamiana */ 
  while(koniec >= s) 
  { 
    tmp = *koniec; 
    *koniec = *s; 
    *s = tmp; 
    koniec--; 
    s++; 
  } 
  return(pocz); 

void main() 

  char tekst[100]; 
  char *p; 
 
  strcpy(tekst,"To jest ci

ą

g"); 

 
  p = strrev(tekst); 
 
  printf("\n%s",p); 

strtok() 

Funkcja  strtok()  jest  istotną  funkcją  języka  C,  słuŜącą  do  wyłączania  fragmentów 
ciągu.  Stosuje  się  ją,  gdy  poszczególne  podciągi  rozdzielone  są  znanymi 
ogranicznikami, na przykład przecinkami: 

#include <stdio.h> 
#include <string.h> 
 
void main() 

  char dane[50]; 
  char *p; 
 
  strcpy(dane,"CZERWONY,POMARA

Ń

CZOWY,

ś

ÓŁTY,ZIELONY,NIEBIESKI"); 

 
  p =  strtok(dane,","); 
  while(p) 
  { 
    puts(p); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

247 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

247

 

    p =  strtok(NULL,","); 
  }; 

Program moŜna oprzeć teŜ na pętli 

for()

#include <stdio.h> 
#include <string.h> 
 
void main() 

  char dane[50]; 
  char *p; 
 
  strcpy(dane,"CZERWONY,POMARA

Ń

CZOWY,

ś

ÓŁTY,ZIELONY,NIEBIESKI"); 

 
  for( p = strtok(dane,","); p; p =  strtok(NULL,",")) 
  { 
    puts(p); 
  }; 

W  pierwszym  wywołaniu  funkcji 

strtok()

  podajemy  nazwę  zmiennej  ciągu  oraz 

ogranicznik.  Funkcja  zwraca  wówczas  wskaźnik  do  początku  pierwszego  podciągu 
i zastępuje pierwszy ogranicznik zerem. Kolejne wywołania 

strtok()

 wykonywane są 

w  pętli.  Pierwszym  parametrem  jest  wówczas 

NULL

,  a  funkcja  zwraca  kolejne 

podciągi.  PoniewaŜ  dopuszczalne  jest  podanie  listy  ograniczników,  funkcja 

strtok()

 moŜe posłuŜyć do utworzenia prostego programu zliczającego słowa: 

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
 
void main(int argc, char *argv[]) 

  FILE *fp; 
  char bufor[256]; 
  char *p; 
  long licznik; 
 
  if (argc != 2) 
  { 
    fputs("\nBŁ

Ą

D. Poprawna składnia wywołania: wordcnt 

f1\n",stderr); 
    exit(0); 
  } 
 
  /* Otwórz plik do odczytu */ 
  fp = fopen(argv[1],"r"); 
 
  /* Sprawd

ź

 czy plik został otwarty */ 

  if (!fp) 
  { 
    fputs("\nBŁ

Ą

D. Nie mo

Ŝ

na otworzy

ć

 pliku 

ź

ródłowego\n",stderr); 

    exit(0); 
  } 
 
  /* Inicjuj licznik */ 
  licznik = 0; 
 
  do 

background image

 
248 

Hack Wars. Tom 1. Na tropie hakerów 

248 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  { 
    /* Odczytaj z pliku wiersz danych */ 
    fgets(bufor, 255, fp); 
 
    /* Sprawd

ź

 czy nie wyst

ą

pił bł

ą

d lub znak EOF */ 

    if (ferror(fp)  || feof(fp)) 
      continue; 
 
    /* Zlicz słowa w pobranym wierszu */ 
    /* Słowa wyró

Ŝ

nia si

ę

 jako elementy rozdzielone znakami */ 

    /* \t (tab) \n (nowy wiersz) , ; : . ! ? ( ) - spacja */ 
    p =  strtok(bufor, "\t\n,;:.!?()- "); 
    while(p) 
    { 
      licznik++; 
      p = strtok(NULL,"\t\n,;:.!?()- "); 
    } 
  } 
  while(!ferror(fp) && !feof(fp)); 
 
  /* Odczyt zako

ń

czony. Bł

ą

d? */ 

  if (ferror(fp)) 
  { 
    fputs("\nBł

ą

d przy odczycie pliku 

ź

ródłowego\n",stderr); 

    fclose(fp); 
    exit(0); 
  } 
 
  /* Odczyt zako

ń

czony poprawnie, znakiem EOF */ 

  /* Wyprowadzamy liczb

ę

 słów */ 

  printf("\nPlik %s zawiera %ld słów(słowa)\n",argv[1],licznik); 
  fclose(fp); 

Zamiana liczb na ciągi i ciągów na liczby 

Wszystkie  kompilatory  C  zapewniają  moŜliwość  konwertowania  liczb  na  ciągi  przy 
uŜyciu  takich  funkcji  jak 

sprintf()

.  Funkcja  ta  ma  jednak  wiele  zastosowań,  co 

powoduje,  Ŝe  jest  rozbudowana  i  mało  wydajna.  MoŜe  ją  zastępować  funkcja 

ITOS()

, korzystająca z dwóch parametrów: liczby całkowitej ze znakiem i wskaźnika 

do ciągu znakowego. Funkcja kopiuje liczbę do określonego wskaźnikiem miejsca w 
pamięci. Podobnie jak 

sprintf()

, funkcja 

ITOS()

 nie sprawdza, czy ciąg docelowy 

ma  wystarczającą  do  przechowania  wyniku  konwersji  długość.  Oto  przykładowa 
funkcja, która kopiuje liczbę 

signed int

 do ciągu znakowego. 

void ITOS(long x, char *ptr) 

  /*Zamie

ń

 dziesi

ę

tn

ą

 liczb

ę

 całkowit

ą

 ze znakiem na ci

ą

g znaków */ 

  long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000, 
100, 10, 1 }; 
  int n; 
 
  /* Sprawd

ź

 znak */ 

  if (x < 0) 
  { 
    *ptr++ = '-'; 
    /* Zamie

ń

 x na warto

ść

 bezwzgl

ę

dn

ą

 */ 

    x = 0 - x; 
  } 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

249 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

249

 

 
  for(n = 0; n < 9; n++) 
  { 
    if (x > pt[n]) 
    { 
      *ptr++ = '0' + x / pt[n]; 
      x %= pt[n]; 
    } 
  } 
*ptr='\0'; 
 (zapewnia zako

ń

czenie ła

ń

cucha znakowego i zapobiega wypisywaniu 

głupot, gdy 

   zmienna tablicowa ma wi

ę

kszy wymiar, ni

Ŝ

 liczba tego potrzebuje 

P.B.) 
  return; 

 
(Powy

Ŝ

szy program działa nieprawidłowo, gdy w zamienianej liczbie 

znajduj

ą

 si

ę

 

 zera. Poni

Ŝ

ej przedstawiam proponowan

ą

 przeze mnie poprawn

ą

 wersj

ę

 

P.B.): 
 
void ITOS(long x, char *ptr) 

  /*Zamie

ń

 dziesi

ę

tn

ą

 liczb

ę

 całkowit

ą

 ze znakiem na ci

ą

g znaków */ 

  long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000, 
100, 10, 1 }; 
  int n; 
  int licznik=0; //licznik potrzebny do zliczania zer na pocz

ą

tku 

ci

ą

gu 

 
  /* Sprawd

ź

 znak */ 

  if (x < 0) 
  { 
    *ptr++ = '-'; 
    licznik++; 
    /* Zamie

ń

 x na warto

ść

 bezwzgl

ę

dn

ą

 */ 

    x = 0 - x; 
  } 

  for(n = 0; n < 9; n++) 
  { 
  licznik++; 
      *ptr++ = '0' + x / pt[n];  
      x %= pt[n]; 
  } 
  *ptr='\0'; 
  ptr=ptr-licznik; //powrót wska

ź

nika na pocz

ą

tek ci

ą

gu 

  licznik=0; 
  if(*ptr=='-') //omini

ę

cie minusa na pocz

ą

tku (je

ś

li jest) 

 

  ptr++; 

  while(*ptr=='0') {  //pomijanie pocz

ą

tkowych zer 

 

  licznik++; 

 

  ptr++; 

  } 
  while(*ptr!='\0') { 
 

  *(ptr-licznik)=*ptr; //przepisywanie ci

ą

gu ju

Ŝ

 bez zer na 

pocz

ą

tku 

 

ptr++; 

  } 
  *(ptr-licznik)='\0'; 
 

background image

 
250 

Hack Wars. Tom 1. Na tropie hakerów 

250 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  return; 

Język  C  oferuje  dwie  funkcje  do  zamiany  ciągów  znakowych  na  liczby 
zmiennoprzecinkowe: 

atof()

 i 

strtod()

. Prototyp funkcji 

atof()

 ma postać: 

double atof(const char *s); 

a prototyp funkcji 

strtod()

double strtod(const char *s, char **endptr); 

Obie funkcje przeglądają ciąg i przeprowadzają konwersję aŜ do momentu natrafienia 
na  niezrozumiały  znak.  RóŜnica  między  nimi  polega  na  tym,  Ŝe 

strtod()

  pobiera 

dodatkowy  parametr,  wskaźnik 

char

  ustawiany  na  pierwszy  znak  ciągu,  który  nie 

został  objęty  konwersją.  Znacznie  zwiększa  to  wygodę  sprawdzania  poprawności 
wykonania operacji. 

Aby zamienić ciąg na wartość całkowitą, moŜna uŜyć funkcji 

atoi()

int atoi(const char *s); 

NaleŜy  pamiętać,  Ŝe  funkcja 

atoi()

  nie  zapewnia  Ŝadnej  kontroli  przepełnienia 

zmiennej.  Nie  jest  zdefiniowana  wartość  zwracana  w  takiej  sytuacji.  W  podobny 
sposób  działa  funkcja 

atol()

,  zwracająca  wartość 

long

.  Odpowiedniki  z 

dodatkowym parametrem noszą nazwy 

strol()

 i 

stroul()

Obsługa tekstu 

Człowiek  zapisuje  informacje  jako  pewien  „tekst”,  złoŜony  ze  słów,  liczb  i  znaków 
przestankowych.  Słowa  złoŜone  są  z  liter  wielkich  i  małych,  odpowiednio  do 
wymagań gramatyki. Wszystko to sprawia, Ŝe komputerowe przetwarzanie tekstu nie 
jest  zadaniem  prostym.  Norma  ANSI  definiuje  wiele  funkcji  przetwarzania  ciągów 
znakowych,  które  z  natury  rozpoznają  wielkość  liter.  Oznacza  to,  Ŝe  litera  „A” 
rozpoznawana  jest  jako  róŜna  od  „a”.  Jest  to  pierwsze  zagadnienie,  którego 
rozwiązanie musi znaleźć programista pracujący nad programem przetwarzającym tekst. 
Na szczęście, zarówno kompilatory Borlanda, jak i Microsoftu wyposaŜone zostały w 
funkcje obsługi ciągów, które nie rozpoznają wielkości liter. 

Taką  odmianą  funkcji 

strcmp()

  jest 

stricmp()

,  a 

strncmp()

  — 

strnicmp()

Gdy jednak pojawia się kwestia przenośności kodu, niezbędna jest zgodność z ANSI 
C, co pociąga za sobą napisanie własnych funkcji. 

PoniŜej  przedstawiamy  prostą  implementację  nierozróŜniającej  wielkości  liter 
odmiany funkcji 

strstr()

. Tworzy ona kopie ciągów, zamienia je na wielkie litery i 

wykonuje  standardową  operację 

strstr()

.  Pozwala  to  określić  poszukiwaną 

wartość przesunięcia i utworzyć wskaźnik do ciągu źródłowego. 

char *stristr(char *s1, char *s2) 

  char c1[1000]; 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

251 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

251

 

  char c2[1000]; 
  char *p; 
 
  strcpy(c1,s1); 
  strcpy(c2,s2); 
 
  strupr(c1); 
  strupr(c2); 
 
  p = strstr(c1,c2); 
  if (p) 
    return s1 + (p - c1); 
  return NULL; 

Kolejna  funkcja  przegląda  ciąg 

s1

,  wyszukując  słowo  podane  jako 

s2

.  Aby  funkcja 

zwróciła  wartość 

TRUE

,  znalezione  musi  zostać  odrębne  słowo,  a  nie  jedynie 

sekwencja znaków. Wykorzystujemy przygotowaną wcześniej funkcję 

stristr()

int word_in(char *s1, char *s2) 

  /*zwraca warto

ść

 niezerow

ą

, je

Ŝ

eli s2 jest słowem zawartym w s1*/ 

  char *p; 
  char *q; 
  int ok; 
 
  ok = 0; 
  q = s1; 
 
  do 
  { 
    /* Lokalizuj wyst

ą

pienie sekwencji znaków s2 w s1 */ 

    p = stristr(q,s2); 
    if (p) 
    { 
      /* Znaleziony */ 
      ok = 1; 
 
      if (p > s1) 
      { 
        /* Sprawd

ź

 znak przed znalezionym ci

ą

giem*/ 

        if (*(p-1) >= 'A' && *(p-1) <= 'z') 
          ok = 0; 
      }  
 
      /* Niech p wskazuje koniec ci

ą

gu */ 

      p += strlen(s2); 
 
      if (*p) 
      { 
        /* Sprawd

ź

 znak za znalezionym ci

ą

giem */ 

      if (*p >= 'A' && *p <= 'z') 
        ok = 0; 
      } 
    } 
    q = p; 
  } 
  while(p && !ok); 
  return ok; 

background image

 
252 

Hack Wars. Tom 1. Na tropie hakerów 

252 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Szerokie  zastosowanie  znajdzie  kilka  dalszych  prostych  funkcji  znakowych. 

truncstr()

 obcina ciąg znakowy: 

void truncstr(char *p, int liczba) 

  /* Obcina 'liczba' znaków z ci

ą

gu 'p' */ 

  if (liczba < strlen(p)) 
    p[strlen(p) - liczba] = 0; 

trim()

 usuwa końcowe znaki spacji (odstępu międzywyrazowego) w ciągu: 

void trim(char *tekst) 

  /* usuwa spacje ko

ń

cowe */ 

  char *p; 
 
  p = &tekst[strlen(tekst) - 1]; 
  while(*p == 32 && p >= tekst) 
    *p-- = 0; 

strlench()

 zmienia długość ciągu: 

void strlench(char *p,int num) 

  /* Zmienia długo

ść

 ci

ą

gu, doł

ą

czaj

ą

c lub usuwaj

ą

c znaki */ 

 
  if (num > 0) 
    memmove(p + num,p,strlen(p) + 1); 
  else 
  { 
    num = 0 - num; 
    memmove(p,p + num,strlen(p) + 1); 
  } 

strins()

 umieszcza jeden ciąg w innym: 

void strins(char *p, char *q) 

  /* Wstaw ci

ą

g q do ci

ą

gu p */ 

  strlench(p,strlen(q)); 
  strncpy(p,q,strlen(q)); 

strchg()

 zastępuje wszystkie wystąpienia pewnego podciągu innym podciągiem: 

void strchg(char *dane, char *s1, char *s2) 

  /* Zast

ę

puje wszystkie wyst

ą

pienia s1 ci

ą

giem s2 */ 

  char *p; 
  char zmienione; 
 
  do 
  { 
    zmienione = 0; 
    p = strstr(dane, s1); 
    if (p) 
    { 
      /* Usu

ń

 ci

ą

g znaleziony */ 

      strlench(p, 0 - strlen(s1)); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

253 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

253

 

 
      /* Wstaw ci

ą

g */ 

      strins(p,s2); 
      zmienione = 1; 
    } 
  } 
  while(zmienione); 

Data i godzina 

Język  C  wyposaŜony  jest  w  funkcję 

time()

,  która  odczytuje  zegar  systemowy 

komputera  i  podaje  informację  o  dacie  i  godzinie  w  postaci  liczby  sekund,  która 
upłynęła  od  północy  1 stycznia  1970  roku.  Wartość  ta  moŜe  zostać  zamieniona  na 
czytelny dla człowieka ciąg znaków za pomocą funkcji 

ctime()

#include <stdio.h> 
#include <time.h> 
 
int main() 

  /* Struktura do przechowywania daty i godziny, z time.h */ 
  time_t t; 
  /* Pobierz dat

ę

 i godzin

ę

 systemu */ 

  t = time(NULL); 
  printf("Bie

Ŝą

ca data i godzina: %s\n",ctime(&t)); 

Na ciąg zwracany przez 

ctime()

 składa się siedem pól: 









 

dzień tygodnia, 









 

miesiąc roku, 









 

dzień miesiąca, 









 

godzina, 









 

minuty, 









 

sekundy, 









 

rok. 

Uzupełnieniem  jest  znak  nowego  wiersza  i  końcowe  0.  PoniewaŜ  pola  mają  stałą 
szerokość,  ciąg  zwracany  przez 

ctime()

  idealnie  nadaje  się  do  operacji 

wymagających wyodrębnienia elementów daty lub godziny. W poniŜszym programie 
definiujemy strukturę 

godzina

 oraz funkcję 

pobierz_godzine()

, której zadaniem 

jest wypełnienie struktury treścią pól ciągu 

ctime()

#include <stdio.h> 
#include <time.h> 
#include <string.h> 
 
struct godzina 

  int g_min;   /* Minuty */ 
  int g_godz;  /* Godziny */ 

background image

 
254 

Hack Wars. Tom 1. Na tropie hakerów 

254 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  int g_sek;   /* Sekundy */ 
}; 
 
void pobierz_godzine(struct godzina *teraz) 

  time_t t; 
  char temp[26]; 
  char *ts; 
 
  /* Pobierz dat

ę

 i godzin

ę

 systemu */ 

  t = time(NULL); 
 
  /* Przedstaw dat

ę

 i godzin

ę

 w postaci ci

ą

gu */ 

  strcpy(temp,ctime(&t)); 
 
  /* Obetnij ostatnie pole */ 
  temp[19] = 0; 
 
  ts = &temp[11]; 
 
  /* Przeszukaj ci

ą

g i skopiuj elementy do struktury */ 

  sscanf(ts, "%2d:%2d:%2d",&teraz->g_godz,&teraz->g_min,&teraz-
>g_sek); 

 
int main() 

  struct godzina teraz; 
   
  pobierz_godzine(&teraz); 
 
  printf("\nJest godzina 
%02d:%02d:%02d",&teraz.g_godz,&teraz.g_min,&teraz.g_sek); 

Norma  ANSI  przewidziała  równieŜ  funkcję  konwertującą  wartość  zwracaną  przez 
funkcję 

time()

  do  postaci  struktury.  Przedstawiony  poniŜej  przykład  zawiera 

deklarację struktury 

tm

 z nagłówka 

time.h

#include <stdio.h> 
#include <time.h> 
 
int main() 

  time_t t; 
  struct tm *tb; 
 
  /* Pobierz czas do t */ 
  t = time(NULL); 
 
  /* Zamie

ń

 warto

ść

 t na struktur

ę

 tb */ 

  tb = localtime(&t); 
 
  printf("\nJest godzina %02d:%02d:%02d",tb->tm_hour,tb->tm_min,tb-
>tm_sec); 
return (0); 

 

Struktura 

tm

 (zawarta w pliku 

time.h

) ma następującą postać: 

struct tm 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

255 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

255

 


  int tm_sec; 
  int tm_min; 
  int tm_hour; 
  int tm_mday; 
  int tm_mon; 
  int tm_year; 
  int tm_wday; 
  int tm_yday; 
  int tm_isdst; 
}; 
(Ta struktura nie mo

Ŝ

e by

ć

 cz

ęś

ci

ą

 programu, jak to zasugerowano, bo 

jest ju

Ŝ

 

 zdefiniowana w pliku nagłówkowym. W takiej sytuacji kompilator 

wy

ś

wietla bł

ą

d.   Mo

Ŝ

na j

ą

 zostawi

ć

 w tym miejscu z komentarzem, 

który podałem na górze, wzgl

ę

dnie   przenie

ść

 na stron

ę

 51 P.B.) 

Liczniki czasu 

Programy często korzystają z moŜliwości pobrania daty i czasu z nieulotnej pamięci 
RAM komputera. Norma ANSI przewiduje kilka róŜnych funkcji, które mogą zostać 
do tego celu wykorzystane. Pierwszą jest funkcja 

time()

, zwracająca liczbę sekund 

od 1 stycznia 1970 roku: 

time_t time(time_t *timer); 

Funkcja  wypełnia przekazaną jej jako parametr zmienną typu 

time_t

 

(

jeśli nie jest 

to 

NULL)

,  zwracając  tę  samą  wartość  równieŜ  jako  wartość  wyjściową.  MoŜna  więc 

wywoływać funkcję 

time()

 z parametrem 

NULL

 i korzystać z wartości zwracanej: 

#include <time.h> 

 

void main() 
{  
  time_t teraz; 

 

  teraz = time(NULL); 

Funkcja 

asctime()

 zamienia strukturę 

tm

 na 26-znakowy ciąg (przedstawiony przy 

opisie funkcji 

ctime()

): 

char *asctime(const struct tm *struktura); 

Funkcja 

ctime()

  zamienia  wartość  czasu  (zwracaną  przez 

time()

)  na  26-znakowy 

ciąg: 

#include <stdio.h> 
#include <time.h> 
#include <string.h> 

 
 

void main() 

  time_t teraz; 
  char data[30]; 

 

  teraz = time(NULL); 
  strcpy(data,ctime(&teraz)); 

background image

 
256 

Hack Wars. Tom 1. Na tropie hakerów 

256 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Kolejna funkcja, 

difftime()

, zwraca, liczoną w sekundach, róŜnicę między dwoma 

wartościami  typu 

time_t

.  SłuŜy  więc  do  wyznaczania  ilości  czasu,  jaki  upłynął 

między dwoma zdarzeniami, czasu  wykonywania funkcji lub generowania przerw  w 
pracy programu, na przykład: 

#include <stdio.h> 
#include <time.h> 

 

void DELAY(int okres) 

  time_t pocz; 

 

  pocz = time(NULL); 
  while(time(NULL) < pocz + okres) 
    ; 

 

void main() 

  printf("\nRozpoczynam oczekiwanie... (5 sekund)"); 

 

  DELAY(5); 

 

  puts("\nOczekiwanie zako

ń

czone."); 

Funkcja 

gmtime()

  zamienia  lokalną  wartość  czasu 

time_t

  na  wartość  GMT  o 

postaci  struktury 

tm

.  Działanie  tej  funkcji  zaleŜy  od  ustawienia  globalnej  zmiennej 

strefy  czasowej.  Struktura 

tm

  została  wstępnie  zdefiniowana  w  nagłówku 

time.h

Przedstawiliśmy ją kilka stron wcześniej. 

struct tm 

  int tm_sec; 
  int tm_min; 
  int tm_hour; 
  int tm_mday; 
  int tm_mon; 
  int tm_year; 
  int tm_wday; 
  int tm_yday; 
  int tm_isdst; 
}; 

Element  struktury 

tm_mday

  przechowuje  dzień  miesiąca  (od  1  do  31),  a 

tm_wday

  — 

dzień  tygodnia  (gdzie  niedzieli  odpowiada  0).  Czas  jest  mierzony  od  1900  roku. 
Wartość 

tm_isdst

 to znacznik, który informuje o tym, czy stosowany jest czas letni. 

Stosowane  nazwy  struktury  i  jej  elementów  mogą  róŜnić  się  w  zaleŜności  od 
kompilatora, jednak sama struktura zasadniczo pozostaje niezmieniona. 

Funkcja 

mktime()

 zamienia strukturę 

tm

 na wartość 

time_t

, uzupełniając wartości 

pól 

tm_wday

 i 

tm_yday

time_t mktime(struct tm *t); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

257 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

257

 

W  kolejnym  przykładzie  umoŜliwiamy  wprowadzanie  daty  i  uŜywamy  funkcji 

mktime()

  do  ustalenia  dnia  tygodnia.  NaleŜy  pamiętać,  Ŝe  funkcje  związane  z 

czasem rozpoznają wyłącznie daty późniejsze niŜ 1 stycznia 1970: 

#include <stdio.h> 
#include <time.h> 
#include <string.h> 
 
void main() 

  struct tm tstruct; 
  int okay; 
  char data[100]; 
  char *p; 
  char *wday[] = 
{"niedziela","poniedziałek","wtorek","

ś

roda","czwartek","pi

ą

tek","sob

ota", "przed   rokiem 1970 - nieznany"}; 
  do 
  { 
    okay = 0; 
    printf("\nWprowad

ź

 dat

ę

 w formacie dd/mm/rr "); 

    p = fgets(data, 9,stdin); 
    p = strtok(data,"/"); 
 
    if (p!= NULL) 
      tstruct.tm_mday = atoi(p); 
    else 
      continue; 

    p = strtok(NULL,"/"); 
    if (p != NULL) 
      tstruct.tm_mon = atoi(p); 
    else 
      continue; 
 
    p = strtok(NULL, "/"); 
 
    if (p != NULL) 
      tstruct.tm_year = atoi(p); 
    else 
      continue; 
    okay = 1; 
  } 
  while(!okay); 
 
  tstruct.tm_hour = 0; 
  tstruct.tm_min = 0; 
  tstruct.tm_sec = 1; 
  tstruct.tm_isdst = -1; 
 
  /* Teraz ustalimy dzie

ń

 tygodnia */ 

  if (mktime(&tstruct) == -1) 
    tstruct.tm_wday = 7; 
 
  printf ("Ten dzie

ń

 to %s\n", wday[tstruct.tm_wday]); 

Funkcja 

mktime()

  zapewnia  równieŜ  wprowadzenie  odpowiednich  poprawek  dla 

wartości  przekraczających  swój  dopuszczalny  zakres.  MoŜna  to  wykorzystać  do 
ustalenia dokładnej daty, odległej o 

n

 dni: 

background image

 
258 

Hack Wars. Tom 1. Na tropie hakerów 

258 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

#include <stdio.h> 
#include <time.h> 
#include <string.h> 
 
void main() 

  struct tm *tstruct; 
  time_t dzisiaj; 
 
  dzisiaj = time(NULL); 
  tstruct = localtime(&dzisiaj); 
 
  tstruct->tm_mday += 10; 
  mktime(tstruct); 
  if(tstruct->tm_year>99) 
   

   tstruct->tm_year%=100;  

 
(te dwie linie zapobiegaj

ą

 wy

ś

wietlaniu bł

ę

dnego roku w przypadku, 

gdy czas, jaki   upłyn

ą

ł od roku 1900 jest dłu

Ŝ

szy od 99 lat P.B.) 

 
  printf("Za dziesi

ęć

 dni b

ę

dzie %02d/%02d/%02d\n",tstruct-

>tm_mday,tstruct->tm_mon +  

    1,tstruct->tm_year); 

Pliki nagłówkowe 

W  plikach  nagłówkowych  umieszczone  są  prototypy  funkcji  bibliotecznych 
kompilatora  oraz  standardowe  makra.  Norma  ANSI  wymienia  następujące  pliki  tej 
grupy. 

Plik nagłówka 

Opis 

assert.h 

definicja makra wspomagającego analizę programu, 

assert

ctype.h 

makra do klasyfikowania i konwersji znaków, 

errno.h 

stałe kodów błędów, 

float.h 

specyficzne dla implementacji makra dla operacji 
zmiennoprzecinkowych, 

limits.h 

opisuje specyficzne dla implementacji ograniczenia dla wartości róŜnych 
typów, 

locale.h 

parametry dla ustawień narodowych, 

math.h 

prototypy funkcji matematycznych, 

setjmp.h 

definicja 

typedef

 oraz funkcji dla 

setjmp

 i 

longjmp

signal.h 

zawiera stałe i deklaracje wykorzystywane przez funkcje 

signal()

 i 

raise()

stdarg.h 

zawiera makra do obsługi list argumentów, 

stddef.h 

definicje podstawowych typów danych i makr, 

stdio.h 

typy i makra wymagane do obsługi standardowych funkcji we-wy, 

stdlib.h 

zbiór róŜnorodnych, często wykorzystywanych deklaracji i prototypów 
funkcji, 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

259 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

259

 

string.h 

prototypy funkcji operujących na łańcuchach znakowych, 

time.h 

struktury dla procedur konwersji wartości daty i godziny. 

Debugowanie programu 

Norma  ANSI  definiuje  wspomagające  debugowanie  makro 

assert()

.  Działa  ono  jak 

instrukcja 

if()

.  Spełnienie  podanego  warunku  powoduje  jednak  przerwanie 

wykonywania programu i wyprowadzenie na standardowy strumień informacji o błędach 
komunikatu: 

Assertion failed: <test>, file < nazwa_pliku>, line <numer_wiersza
Abnormal program termination 

RozwaŜmy przykład, w którym program omyłkowo przypisuje wskaźnikowi wartość 0: 

#include <stdio.h> 
#include <assert.h> 
 
void main() 

  /* Przykład zastosowania makra assert */ 
 
  int *ptr; 
  int x; 
 
  x = 0; 

  /* W tym wierszu jest bł

ą

d! */ 

  ptr=x; 
 
  assert(ptr != NULL); 

Program taki po uruchomieniu zakończy szybko pracę wyświetleniem komunikatu: 

Assertion failed: ptr != 0, file TEST.C, line 16 
Abnormal program termination 

Gdy program pracuje juŜ poprawnie, funkcja 

assert()

 moŜe zostać z niego usunięta 

przez proste dopisanie przed wierszem 

#include <assert.h>

#define NDEBUG 

Dyrektywa  taka  zapewni  oznaczenie,  w  przygotowywanym  do  kompilacji  kodzie, 
wszystkich funkcji 

assert

 jako komentarzy. 

Błędy wartości zmiennoprzecinkowych 

Liczby  zmiennoprzecinkowe  to  ułamki  dziesiętne,  które  nie  odpowiadają  dokładnie 
ułamkom  zwykłym  (nie  kaŜda  liczba  moŜe  zostać  równo  podzielona  przez  10). 
Konsekwencją  tego  jest  niebezpieczeństwo  błędów  zaokrągleń  w  obliczeniach. 
PoniŜszy program przedstawia przykładowy problem: 

#include <stdio.h> 

background image

 
260 

Hack Wars. Tom 1. Na tropie hakerów 

260 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

 
void main() 

  float liczba; 
 
  for(liczba = 1; liczba > 0.4; liczba -= 0.01) 
    printf ("\n%f",liczba); 

Poczynając  od  wartości  ok.  0,47  (zaleŜnie  od  komputera  i  kompilatora),  program 
zacznie generować niedokładne wartości. 

Zjawisko  to  moŜna  ograniczyć,  stosując  dłuŜsze  typy  zmiennoprzecinkowe, 

double

 

lub 

long double

, korzystające z większej liczby bitów. Gdy wymagana jest wysoka 

dokładność, 

naleŜy 

uŜywać 

wartości 

całkowitych, 

zamienianych 

na 

zmiennoprzecinkowe  tylko  przed  wyświetleniem  na  ekranie.  Nie  wolno  równieŜ 
zapominać,  Ŝe  większość  kompilatorów  C  jako  domyślny  typ  zmiennoprzecinkowy 
przyjmuje 

double

 i uŜycie innych typów moŜe wymagać konwersji. 

Obsługa błędów 

Gdy  w  trakcie  wykonywania  programu  wystąpi  błąd  systemowy  —  jak  na  przykład 
przy  nieudanej  próbie  otwarcia  pliku  —  powinien  zostać  wyświetlony  komunikat 
dostarczający informacji o zdarzeniu. Takie przygotowanie pomaga w duŜej mierze 
programiście,  dostarczając  mu,  jeŜeli  nie  informacji  o  przyczynie  błędu,  to 
przynamniej  pewnych  wskazówek.  Norma  C  opisuje  funkcję  obsługującą  wymianę 
tego rodzaju informacji, 

perror()

void perror(const char *s); 

Jako  parametr  podajemy  ciąg,  który  funkcja  wyświetli  jako  pierwszy.  Drugim 
ciągiem,  wyświetlanym  po  dwukropku,  jest  treść  systemowego  komunikatu  błędu. 
PoniŜej przedstawiamy prosty przykład uŜycia funkcji 

perror()

#include <stdio.h> 
 
void main() 

  FILE *fp; 
  char nazwa[] = "nic.xyz"; 
 
  fp = fopen(nazwa,"r"); 
 
  if(!fp) 
    perror(nazwa); 
  return; 

JeŜeli  operacja 

fopen()

  nie  zostanie  wykonana,  wyświetlany  jest  komunikat  w 

rodzaju: 

nic.xyz: No such file or directory 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

261 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

261

 

Funkcja 

perror()

  przesyła  dane  do  strumienia  predefiniowanego 

stderr

zazwyczaj  skojarzonego  z  kartą  graficzną  i  monitorem  komputera.  Komunikat 
systemowy  odnajdywany  jest  za  pośrednictwem  zmiennej  globalnej 

errno

,  której 

wartość ustawia większość funkcji systemowych (choć nie wszystkie). 

Jedynie  w  wyjątkowych  przypadkach  skorzystać  moŜna  z  funkcji 

abort()

,  która, 

przerywając wykonywanie programu, wyświetla komunikat w rodzaju „Niewłaściwe 
zakończenie  programu”  i  zwraca  procesowi  nadrzędnemu  lub  systemowi 
operacyjnemu kod 3. 

Obsługa błędów krytycznych na platformie IBM PC i w systemie DOS 

System  PC  DOS  przewiduje  moŜliwość  wpływu  uŜytkownika  na  działania 
podejmowane przez funkcje obsługi błędów krytycznych. Łatwo się o tym przekonać, 
próbując dokonać zapisu w pustej stacji dyskietek. Pojawia się wówczas znajome: 

Not ready; error writing drive A 
Abort Retry Ignore? 

PoniŜszy  program  przedstawia,  w  jaki  sposób  moŜna  przekierować  obsługę  tego 
rodzaju zdarzeń do własnych funkcji: 

#include <stdio.h> 
#include <dos.h> 
 
void interrupt nowe_int(); 
void interrupt (*stare_int)(); 

char stan; 
 
void main() 

  FILE *fp; 
 
  stare_int = getvect(0x24); 
 
  /* Skieruj obsług

ę

 bł

ę

dów krytycznych do mojej funkcji */ 

  setvect(0x24, nowe_int); 
 
  /* Generuj bł

ą

d braku dysku w stacji A */ 

  fp = fopen("a:\\dane.txt","w+"); 
 
  /* Wy

ś

wietl kod stanu */ 

  printf("\nKod stanu == &d",stan); 

 
void interrupt nowe_int() 

  /* zapisz warto

ść

 globalnego kodu bł

ę

du */ 

  stan = _DI; 
 
  /* ignoruj bł

ą

d i zako

ń

cz */ 

  _AL = 0; 

background image

 
262 

Hack Wars. Tom 1. Na tropie hakerów 

262 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Gdy wywoływane jest przerwanie błędu krytycznego systemu DOS, komunikat stanu 
umieszczany jest w młodszym bajcie rejestru DI. Lista kodów obejmuje wymienione 
poniŜej. 

Kod stanu operacji 

Znaczenie 

00 

zabezpieczenie przed zapisem, 

01 

jednostka nieznana, 

02 

dysk nie jest gotowy, 

03 

nieznane polecenie, 

04 

błąd danych, niezgodna wartość CRC, 

05 

niewłaściwa długość struktury Ŝądania, 

06 

błąd wyszukiwania, 

07 

nieznany nośnik danych, 

08 

nie znaleziono sektora, 

09 

brak papieru w drukarce, 

0A 

błąd zapisu, 

0B 

błąd odczytu, 

0C 

błąd ogólny. 

Własna  funkcja  obsługi  błędów  krytycznych  moŜe  przekazać  komunikat  stanu  do 
zmiennej globalnej i ustawić w rejestrze AL kod dalszych czynności. 

Kod 

Operacja 

00 

ignoruj błąd, 

01 

powtórz, 

02 

zakończ program, 

03 

zrezygnuj (dostępny od DOS 3.3). 

JeŜeli decydujemy się na ustawienie wartości rejestru AL na 02, musimy koniecznie 
zadbać o zamknięcie wszystkich plików — DOS kończy pracę programu natychmiast, 
pozostawiając otwarte pliki i poprzydzielane bloki pamięci. 

PoniŜej przedstawiamy całkiem praktyczną funkcję słuŜącą do sprawdzania, czy moŜna 
uzyskać  dostęp  do  określonego  dysku.  Ma  ona  współpracować  z  przedstawionym 
wcześniej programem obsługi błędów i zmienną globalną 

stan

int DISKOK(int dysk) 

  /* Sprawdza czy mo

Ŝ

na odczytywa

ć

 dane z okre

ś

lonego dysku */ 

  /* Zwraca false (0) w przypadku bł

ę

du */ 

  /* St

ą

d if(!DISKOK(dysk)) */ 

  /*   error(); */ 
 
  unsigned char bufor[25]; 
 
  /* Zakładamy, 

Ŝ

e wszystko jest OK */ 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

263 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

263

 

  stan = 0; 
 
  /* Je

Ŝ

eli znamy ju

Ŝ

 dysk, zwracamy OK */ 

  if ('A' + dysk == diry[0]) 
    return(1); 
 
  /* Próbuj odczytu z dysku */ 
  memset(bufor,0,20); 
  sprintf(bufor,"%c:$$$.$$$",'A'+dysk); 
  _open(bufor,O_RDONLY); 

  /* Sprawd

ź

 stan */ 

  if (stan == 0) 
    return(1); 
 
  /* Odczyt nie jest mo

Ŝ

liwy */ 

  return(0); 

Konwersja typów zmiennych 

Konwersja  typów  zmiennych  pozwala  poinformować  kompilator,  jakiego  typu  są 
wykorzystywane dane oraz zmieniać ich typ. RozwaŜmy następujący przykład: 

#include <stdio.h> 
 
void main() 

  int x; 
  int y; 
 
  x = 10; 
  y = 3; 
 
  printf("\n%1f", x / y); 

Poinformowaliśmy  funkcję 

printf()

,  Ŝe  powinna  oczekiwać  wartości 

double

Kompilator rozpoznaje jednak liczby całkowite 

x

 i 

y

 — generowany jest błąd (tylko 

w  niektórych  środowiskach  programistycznych  —  np.  Microsoft  Visual  C++  bez 
problemu skompiluje taki program, tyle, Ŝe wyświetlona wartość będzie równa 0 P.B.). 
Aby  program  mógł  działać,  naleŜy  poinformować  kompilator,  Ŝe  wartość  wyraŜenia 

x/y

 będzie miała typ 

double

#include <stdio.h> 
 
void main() 

  int x; 
  int y; 
 
  x = 10; 
  y = 3; 
 
  printf("\n%1f", (double)(x / y)); 

background image

 
264 

Hack Wars. Tom 1. Na tropie hakerów 

264 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Zwróćmy  uwagę  na  fakt,  Ŝe  w  nawiasy  ujęta  jest  zarówno  nazwa  typu,  jak  i  samo 
konwertowane  wyraŜenie.  Kompilator  traktuje  teraz  liczby 

x

  i 

y

  jako  całkowite,  a 

wynik  operacji  dzielenia  jako  wartość 

double

  —  oznacza  to,  Ŝe  przeprowadzone 

zostanie dzielenie całkowite (dające w tym wypadku wynik równy 3). Rozwiązaniem 
jest zatem konwersja obu zmiennych: 

#include <stdio.h> 
 
void main() 

  int x; 
  int y; 
 
  x = 10; 
  y = 3; 
 
  printf("\n%1f", (double)(x) / (double)(y)); 

Teraz,  gdy  obie  liczby  mają  typ 

double

,  oczywiste  jest,  Ŝe  i  wynik  ich  dzielenia 

będzie wartością 

double

Prototypy 

Zadaniem prototypów funkcji jest odpowiednio wczesne dostarczenie kompilatorowi 
informacji o tym, jakiego typu wartości funkcja pobiera i zwraca. Przyjrzyjmy się na 
przykład funkcji 

strtok()

 o prototypie: 

char *strtok(char *s1, const char *s2); 

Jest  to  informacja  dla  kompilatora,  Ŝe  funkcja 

strtok

  zwraca  wskaźnik  do  danych 

znakowych. Pierwszym parametrem będzie wskaźnik do ciągu znakowego i parametr 
ten  jest  modyfikowalny  wewnątrz  funkcji.  Drugi  parametr  to  wskaźnik  do  ciągu 
znakowego,  którego  zawartość  nie  moŜe  być  przez  funkcję  zmieniana.  Kompilator 
dysponuje  dzięki  temu  informacją  o  ilości  pamięci  wymaganej  przez  wartość  funkcji 

sizeof  (char  *)

.  Bez  dostępu  do  prototypu  funkcji  kompilator  przyjąłby 

załoŜenie,  Ŝe  funkcja  zwraca  wartość 

int

  i  zaalokował  pamięć  wielkości 

(sizeof(int))

. JeŜeli wartość 

int

 i wskaźnik korzystają na danym  komputerze z 

takiej samej liczby bajtów, nic złego się nie dzieje. Jednak jeŜeli rozmiar  wskaźnika 
jest  większy,  kompilator  nie  będzie  dysponował  odpowiednią  ilością  miejsca  dla 
wartości  zwróconej  przez  funkcję 

strtok()

.  Wykorzystywany  jest  wówczas 

nieoczekiwanie kolejny adres pamięci. 

Szczęśliwie  jednak  większość  kompilatorów  C  ostrzega  o  niepoprzedzonych 
prototypem  wywołaniach  funkcji.  Oto  prosty  przykład  programu,  na  którego 
kompilację nie pozwoli większość nowoczesnych kompilatorów C. 

#include <stdio.h> 
 
int funkcjaa(int x, int y) 

  return(MNOZ(x,y)); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

265 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

265

 

 
double MNOZ(double x, double y) 

  return(x * y); 

 
void main() 

  printf("\n%d",funkcjaa(5,5)); 

Gdy  kompilator  po  raz  pierwszy  napotyka  funkcję 

MNOZ()

,  jest  to  wywołanie  z 

wnętrza  bloku 

funkcjaa()

.  W  przypadku  braku  prototypu 

MNOZ()

  przyjmowane 

jest  załoŜenie,  Ŝe  funkcja  zwraca  wartość 

int

.  Kiedy  kompilator  napotyka  definicję 

funkcji 

MNOZ()

,  stwierdza,  Ŝe  deklarowany  jest  typ 

double

.  Generowany  jest 

wówczas błąd kompilacji: 

"Type mismatch in redeclaration of function 'MNOZ'" 

Jest  to  wyraźny  nakaz  wprowadzenia  prototypu  funkcji!  Gdyby  udało  się  taki 
program  skompilować  i  uruchomić,  najbardziej  prawdopodobnym  scenariuszem 
byłaby awaria stosu. 

Wskaźniki do funkcji 

Język C przewiduje, Ŝe wskaźnik moŜe odwoływać się do adresu funkcji. Co więcej, 
wskaźnik  taki  moŜe  być  stosowany  w  miejsce  jawnego  wywołania  funkcji. 
Wykorzystują  to  funkcje  modyfikujące  przerwania.  Mechanizm  ten  moŜna  równieŜ 
zastosować do indeksowania funkcji, na przykład: 

#include <stdio.h> 
#include <math.h> 
 
double (*fp[7])(double x); 
 
void main() 

  double x; 
  int p; 
 
  fp[0] = sin; 
  fp[1] = cos; 
  fp[2] = acos; 
  fp[3] = asin; 
  fp[4] = tan; 
  fp[5] = atan; 
  fp[6] = ceil; 
 
  p = 4; 
 
  x = fp[p](1.5); 
  printf("\nWynik: %1f",x); 

Definiujemy  tu  tablicę  wskaźników  do  funkcji 

(*fp[])()

,  które  wywoływane  są 

odpowiednio do wartości zmiennej 

p

. Identycznie działa program: 

background image

 
266 

Hack Wars. Tom 1. Na tropie hakerów 

266 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

#include <stdio.h> 
#include <math.h> 
 
void main() 

  double x; 
  int p; 
 
  p = 4; 
 
  switch (p) 
  { 
    case 0 : x = sin(1.5); 
      break; 
    case 1 : x = cos(1.5); 
      break; 
    case 2 : x = acos(1.5); 
      break; 
    case 3 : x = asin(1.5); 
      break; 
    case 4 : x = tan(1.5); 
      break; 
    case 5 : x = atan(1.5); 
      break; 
    case 6 : x = ceil(1.5); 
      break; 
  } 
  printf("\nWynik: %1f",x); 

Dzięki  zastosowaniu  wskaźników  do  funkcji  kod  w  pierwszym  przykładzie  jest 
zwięźlejszy  i  szybciej  wykonywany.  Tablica  wskaźników  do  funkcji  moŜe  być 
przydatnym  narzędziem  podczas  pisania  interpretera  języka.  Program  porównuje 
wówczas  wprowadzoną  instrukcję  z  tablicą  słów  kluczowych,  wyszukując 
odpowiednią wartość indeksującą. Następnie wywoływany jest odpowiedni wskaźnik 
do  funkcji.  Rozwiązanie  takie  znakomicie  zastępuje  rozbudowaną  instrukcję 

switch()

.  

Sizeof 

Instrukcja preprocesora 

sizeof

 zwraca rozmiar elementu danych, którym moŜe być 

struktura,  wskaźnik,  ciąg  lub  dowolny  inny  obiekt.  Jednak  i  w  tym  przypadku 
wymagana jest odrobina uwagi. Przyjrzyjmy się programowi: 

#include <stdio.h> 
#include <mem.h> 
 
char ciag1[80]; char *tekst = "To jest ciag znakowy"; 
 
void main() 

  /* Inicjujemy ciag1 */ 
  memset(ciag1, 0, sizeof(ciag1)); 
 
  /* Kopiujemy do ciagu pewien tekst ? */ 
  memcpy(ciag1, tekst, sizeof(tekst)); 
 
  /* Wy

ś

wietlamy ciag1 */ 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

267 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

267

 

  printf("\nCi

ą

g 1 = %s\n",ciag1); 

Rozpoczynamy  od  zainicjowania  ciągu 

ciag1

  80  zerami,  po  czym  kopiujemy  ciąg 

tekst

  do  zmiennej 

ciag1

.  Jednak  zmienna 

tekst

  jest  wskaźnikiem,  a  więc 

sizeof(tekst)

  zwraca  rozmiar  wskaźnika  (najczęściej  2 bajty),  a  nie  długość  ciągu, 

który wskazuje. Błąd taki moŜe pozostać niezauwaŜony, gdy długość ciągu jest zgodna 
z rozmiarem wskaźnika. 

Przerwania 

BIOS  komputera  PC  i  system  DOS  wyposaŜone  są  w  funkcje,  które  mogą  być 
wywoływane  przez  program  za  pośrednictwem  skojarzonego  z  nimi  numeru 
przerwania. 

Adresy 

przypisanych 

do 

poszczególnych 

przerwań 

funkcji 

przechowywane  są  w  tabeli  w  pamięci  RAM,  określanej  jako  tablica  wektorów 
przerwań
.  Zmiana  adresu  wektora  przerwania  umoŜliwia  programowi  zastąpienie 
standardowej funkcji obsługi przerwania funkcją własną. 

Borland  Turbo  C  oferuje  dwie  funkcje  biblioteczne  słuŜące  do  odczytywania  i 
modyfikowania  wartości  wektorów  przerwań: 

setvect()

  i 

getvect()

.  Ich 

odpowiedniki  w bibliotekach  Microsoftu  noszą  nazwy: 

_dos_setvect()

  i 

_dos_getvect()

Prototyp funkcji 

getvect()

 ma postać: 

void interrupt(*getvect(int nr_przerwania))(); 

a prototyp funkcji 

setvect()

void setvect(int nr_przerwania, void interrupt(*funkcja)()); 

Przy  odczytywaniu  i  zapisywaniu  adresów  uŜycie  funkcji 

getvect()

  wygląda 

następująco: 

void interrupt(*stary)(void); 
 
void main() 

  /* pobierz stary wektor przerwania */ 
  stary = getvect(0x1C); 
  . 
  . 
  . 

W  tym  przypadku  pobieranym  wektorem  jest  0x1C.  Aby  ustawić  wektor  na  adres 
własnej funkcji, uŜywamy 

setvect()

void interrupt nowa(void) 

  . 
  . 
  /* Nowa funkcja obsługi przerwania */ 
  . 
  . 

background image

 
268 

Hack Wars. Tom 1. Na tropie hakerów 

268 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  . 

 
void main() 

  . 
  . 
  . 
setvect(0x1C, nowa); 
  . 
  . 
  . 
  . 

Gdy  zajmujemy  się  przerwaniami,  nie  moŜemy  zapominać  o  dwóch  rzeczach. 
Pierwszą  jest  to,  Ŝe  przed  zmianą  wektora  przerwania  wywoływanego  zdarzeniami 
zewnętrznymi naleŜy wstrzymać moŜliwość takich wywołań poleceniem 

disable()

Po wprowadzeniu modyfikacji przywracamy funkcjonowanie przerwania poleceniem 

enable()

. Efekt wywołania przerwania w trakcie zmiany wektora (gdy nie uŜyjemy 

funkcji 

disable()

) jest nieprzewidywalny

Drugą  istotną  rzeczą  jest  zadbanie  o  przywrócenie  stanu  wektorów  przerwań  przed 
zakończeniem  pracy  programu  i  przywróceniem  kontroli  systemowi  operacyjnemu. 
Wyjątkiem jest jedynie wektor przerwania obsługi błędów krytycznych, przywracany 
przez system DOS automatycznie. 

PoniŜszy przykładowy program przejmuje przerwanie zegara systemowego (hooks an 
interrupt
): 

#include <stdio.h> 
#include <dos.h> 
#include <time.h> 
#include <conio.h> 
#include <stdlib.h> 
 
enum {FALSE, TRUE}; 
 
#define KOLOR (NIEBIESKI << 4 ) | ZOLTY 
 
#define BIOS_TIMER 0x1C 
 
static unsigned zainstalowany = FALSE; 
static void interrupt (*stary_tick) (void); 
 
 static void interrupt tick (void) 

  int i; 
  struct tm *teraz; 
  time_t godzina; 
  char bufor[9]; 
  static time_t godzina_pop = 0L; 
  static char bufor_wysw[20] =  
  { 
    ' ', KOLOR, '0', KOLOR, '0', KOLOR, ':', KOLOR, '0', KOLOR, 
    '0', KOLOR, ':', KOLOR, '0', KOLOR, '0', KOLOR, ' ', KOLOR 
  }; 
 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

269 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

269

 

  enable (); 
 
  if (time (&godzina) != godzina_pop) 
  { 
    godzina_pop = godzina; 
 
    teraz = localtime(&godzina); 
     
    sprintf(bufor,"02d:%02d.%02d",teraz->tm_hour,teraz->tm_min,teraz-
>tm_sec); 
 
    for (i = 0; i < 8; i++) 
    { 
      bufor_wysw[(i + 1) << 1] = bufor[i]; 
    } 
    
    puttext (71, 1, 80, 1, bufor_wysw); 
  } 
   
  stary_tick(); 

void zatrzymaj (void) 

  if (zainstalowany) 
  { 
    setvect(BIOS_TIMER, stary_tick); 
    zainstalowany = FALSE; 
  } 

 
void zegar_start (void) 

  static unsigned pierwszy_raz = TRUE; 
 
  if (!zainstalowany) 
  { 
    if (pierwszy_raz); 
    { 
      atexit (zatrzymaj); 
      pierwszy_raz = FALSE; 
    } 
   
    stary_tick = getvect (BIOS_TIMER); 
    setvect (BIOS_TIMER, tick); 
    zainstalowany = TRUE; 
  } 

Funkcja signal() 

Przerwania  moŜna  przechwytywać  i  wykorzystywać  na  róŜne  sposoby.  Jednym  z 
prostszych jest uŜycie funkcji 

signal()

. Korzysta ona z dwóch parametrów: 

void (*signal (int sygnał, void(*funkcja) (int))) (int); 

Pierwszy  parametr  określa  przechwytywany  sygnał,  drugi  —  wywoływaną  w 
momencie uaktywnienia sygnalizatora funkcję. Większość sygnałów zdefiniowanych 

background image

 
270 

Hack Wars. Tom 1. Na tropie hakerów 

270 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

zostało w nagłówku signal.h. W pliku tym umieszczonych zostało równieŜ kilka makr 
— wykonujących podstawowe operacje, takie jak ignorowanie sygnału — mogą one 
zostać uŜyte jako parametr 

sygnał

Na  platformie  PC  często  pojawia  się  potrzeba  uniemoŜliwienia  korzystania  z 
przerywającej  pracę  programu  kombinacji  klawiszy  CTRL+BREAK.  PoniŜsze 
wywołanie  funkcji 

signal()

  zastępuje  standardową  obsługę  predefiniowanego 

sygnału 

SIGINT

  (obsługującego  Ŝądanie  CTRL+BREAK)  równieŜ  predefiniowanym 

makrem 

SIG-IGN

,  co  prowadzi  do  ignorowania  wciskanych  przez  uŜytkownika 

klawiszy. 

 signal(SIGINT,SIG_IGN); 

W  poniŜszym  przykładzie  instalujemy  przechwytywanie  błędów  operacji 
zmiennoprzecinkowych i dzielenia przez 0 (na komputerze klasy PC): 

#include <stdio.h> 
#include <signal.h> 
 
void (*stary_sygnal)(int); 
  
void przechwyc(int sygnal) 

  printf("Funkcja przechwyc wywołana przez: %d\n",sygnal); 

 
void main() 

  int a; 
  int  b; 
   
  stary_sygnal = signal(SIGFPE,przechwyc); 
 
  a = 0; 
  b = 10 / a; 
 
  /* Przed wy

ś

ciem przywracamy obsług

ę

 standardow

ą

! */ 

  signal(SIGFPE, stary_sygnal); 

Dynamiczne alokowanie pamięci 

JeŜeli program wymaga pewnej tabeli, której rozmiar ulega zmianom (na przykład do 
przechowywania  listy  plików  katalogu  bieŜącego),  deklarowanie  jej  rozmiaru  jako 
największego 

dopuszczalnego 

czy 

wymaganego, 

nie 

jest 

ekonomicznym 

rozwiązaniem.  Istnieje  bowiem  moŜliwość  dynamicznego  alokowania  pamięci  w 
zaleŜności od pojawiających się wymagań. 

Pamięć dostępną do alokacji dynamicznej Turbo C przyznaje w obszarze nazywanym 
stertą  (heap).  Rozmiar  sterty  zaleŜy  od  przyjętego  modelu  pamięci.  Model  tiny 
przewiduje, Ŝe zajętych zostanie ogółem nie  więcej niŜ 64 kB. Model small przyznaje 
64 kB dla kodu programu i sterty, umoŜliwiając korzystanie ze sterty odległej, która 
moŜe  zajmować  pozostałą  pamięć  konwencjonalną.  Inne  modele  pamięci 
udostępniają  stercie  całą  pamięć  konwencjonalną.  Przy  korzystaniu  z  pierwszego 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

271 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

271

 

wymienionych  modeli,  tj.  tiny,  istotne  jest  ograniczenie  zuŜycia  pamięci.  Często 
redukuje się wówczas rozmiar sterty do minimum (czyli jednego bajtu). 

Język  C  oferuje  funkcję 

malloc()

,  pozwalającą  zaalokować  wolny  blok  pamięci  o 

określonym  rozmiarze.  Funkcja  zwraca  wskaźnik  do  początku  bloku.  Dostępna  jest 
równieŜ  funkcja 

free()

,  dealokująca  blok  przyznany  wcześniej  poleceniem 

malloc()

.  Podczas  korzystania  z  nich  nie  wolno  zapominać,  Ŝe  komputery  PC  nie 

zwalniają  poprawnie  bloków  pamięci  i  wielokrotne  uŜycie  obu  funkcji  prowadzi  do 
fragmentacji dostępnych zasobów, a w konsekwencji — niekiedy — braku pamięci. 

PoniŜszy  program  wyszukuje  we  wskazanym  pliku  określony  ciąg  znaków 
(rozróŜniając wielkość liter). Funkcja 

malloc()

 wykorzystywana jest do zaalokowania 

dokładnie  takiej  ilości  pamięci,  jaka  jest  wymagana,  aby  załadować  do  niej  zawartość 
pliku: 

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

char *bufor; 
 
void main(int argc, char *argv[]) 

  FILE *fp; 
  long flen; 
 
  /* Sprawd

ź

 liczb

ę

 parametrów */ 

  if (argc != 3) 
  { 
    fputs("Prawidłowa składnia wywołania: sgrep <tekst> 
<plik>",stderr); 
    exit(0); 
  } 
 
  /* Otwórz powi

ą

zany z plikiem strumie

ń

 fp */ 

  fp = fopen(argv[2],"r"); 
  if (!fp) 
  { 
    perror("Nie mo

Ŝ

na otworzy

ć

 pliku"); 

    exit(0); 
  } 
 
  /* Znajd

ź

 koniec pliku */ 

  if (fseek(fp,0L,SEEK_END)) 
  { 
    fputs("Nie mo

Ŝ

na okre

ś

li

ć

 długo

ś

ci pliku", stderr); 

    fclose(fp); 
    exit(0); 
  } 
 
  /* Okre

ś

l długo

ść

 pliku */ 

  flen = ftell(fp); 
 
  /* Sprawd

ź

 czy OK. */ 

  if (flen == -1L) 
  { 
    fputs("Nie mo

Ŝ

na okre

ś

li

ć

 długo

ś

ci pliku", stderr); 

background image

 
272 

Hack Wars. Tom 1. Na tropie hakerów 

272 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

    fclose(fp); 
    exit(0); 
  } 
 
  /* Ustaw wska

ź

nik na pocz

ą

tek pliku */ 

  rewind(fp); 
 
  /* Zaalokuj bufor pami

ę

ci */ 

  bufor = malloc(flen); 
 
  if (!bufor) 
  { 
    fputs("Nieudana alokacja pami

ę

ci", stderr); 

    fclose(fp); 
    exit(0); 
  } 
 
  /* Załaduj plik do bufora */ 
  fread(bufor,flen,1,fp); 
 
  /* Sprawd

ź

 czy odczyt udany */ 

  if(ferror(fp)) 
  { 
    fputs("Bł

ą

d odczytu pliku",stderr); 

     
    /* Dealokacja bloku pami

ę

ci */ 

    free(bufor); 
 
    fclose(fp); 
    exit(0); 
  } 
   
  printf("%s %s w pliku %s",argv[1],(strstr(bufor,argv[1])) ? 
"znalezione" : 

    "nieznalezione",argv[2]); 
 
  /* Przed wyj

ś

ciem dealokujemy blok pami

ę

ci */ 

  free(bufor); 
  fclose(fp); 

Funkcja atexit() 

Gdy program kończy pracę, powinien zamknąć wszystkie otwarte pliki (wyręcza w tym 
programistę kod rozpoczęcia i zakończenia programu, dołączany przez kompilator C) 
oraz przywrócić komputer do stanu pewnego uporządkowania. W przypadku duŜego 
programu,  z  którego  wyjście  moŜe  nastąpić  w  wielu  róŜnych  punktach,  wielokrotne 
wypisywanie  wywołań  do  procedury  oczyszczającej  wymaga  pewnego  wysiłku.  Nie 
jest on, na szczęście, konieczny. 

Norma  ANSI  opisuje  funkcję 

atexit()

,  która  rejestruje  określoną  funkcję, 

przekazaną poprzez parametr, jako funkcję, która zostanie wywołana przy kończeniu 
programu.  Zapewniamy  w  ten  sposób  jej  automatyczne  wywołanie.  PoniŜszy 
program,  bez  względu  na  to,  czy  wystąpił  w  nim  błąd,  czy  nie,  wywołuje  funkcję 

zakoncz()

#include <stdio.h> 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

273 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

273

 

#include <stdlib.h> 
 
void zakoncz() 

  puts("\nPrzetwarzanie zako

ń

czone."); 


 
void main() 

  FILE *fp; 
  int a; 
  int b; 
  int c; 
  int d; 
  int e; 
  char tekst[100]; 
 
  atexit(zakoncz); 
 
  fp = fopen("dane.txt","w"); 

  if (!fp) 
  { 
    perror("Nie mo

Ŝ

na utworzy

ć

 pliku"); 

    exit(0); 
  } 
 
  fprintf(fp,"1 2 3 4 5 \"Wiersz liczb\""); 
 
  fflush(fp); 
 
  if (ferror(fp)) 
  { 
    fputs("Bł

ą

d przy zapisywaniu danych strumienia",stderr); 

    exit(1); 
  } 
 
  rewind(fp); 
  if (ferror(fp)) 
  { 
    fputs("Bł

ą

d zerowania wska

ź

nika strumienia",stderr); 

    exit(1); 
  } 
 
  fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,tekst); 
  if (ferror(fp)) 
  { 
    /* Je

Ŝ

eli nie zauwa

Ŝ

yłe

ś

 wcze

ś

niejszego bł

ę

du */ 

    /* program ko

ń

czy prac

ę

 w tym miejscu */ 

    fputs("Bł

ą

d przy odczycie strumienia",stderr); 

    exit(1); 
  } 
 
  printf("\nFunkcja fscanf() zwróciła %d %d %d %d %d 
%s",a,b,c,d,e,tekst); 

background image

 
274 

Hack Wars. Tom 1. Na tropie hakerów 

274 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

Wydajność 

Gdy  pojawia  się  kwestia  skrócenia  czasu  wykonywania  programu,  istotna  jest 
znajomość  komputera,  na  którym  ten  będzie  uruchamiany.  Większość  systemów 
stosunkowo  wolno  wyświetla  informacje  na  ekranie.  Język  C  oferuje  róŜnorodne 
funkcje,  słuŜące  do  tego  celu.  Najbardziej  typową,  ale  zarazem  najwolniejszą,  jest 

printf()

.  Gdy  tylko  jest  to  moŜliwe,  warto  korzystać  z  konstrukcji 

puts(nazwa_zmiennej)

 w miejsce 

printf("%s\n",nazwa_zmiennej)

 (

puts()

 

dołącza do przesyłanego na ekran ciągu znak nowego wiersza automatycznie). 

Podczas mnoŜenia zmiennej przez stałą o wartości 2 wiele kompilatorów C rozpoznaje, 
Ŝ

e  w  kodzie  asemblera  jedyną  wymaganą  operacją  jest  przesunięcie  bitowe  w  lewo. 

Przy  mnoŜeniu  przez  inne  wartości  często  szybszą  pracę  zapewni  zastosowanie 
dodawania, a więc zamiast: 

x * 3 

piszemy: 

x + x + x 

Jest  to  korzystne,  gdy  mnoŜnikiem  jest  stała.  Nie  naleŜy  jednak  stosować  takiej 
konstrukcji z róŜnymi wartościami mnoŜnika w pętli. 

Kolejną  metodą  przyspieszenia  operacji  mnoŜenia  i  dzielenia  jest  odwołanie  się  do 
funkcji  przesunięcia  bitowego, 

<<

  i 

>>

.  Instrukcję 

x/=2

  moŜna  zapisać  jako 

x>>=1

 

(przesunięcie  bitowe  o  jedną  pozycję  w  prawo).  Wiele  kompilatorów  samodzielnie 
zamienia  operację  dzielenia  przez  dwa  na  operację  przesunięcia  w  prawo. 
Przesunięcia moŜna jednak stosować równieŜ przy mnoŜeniu i dzieleniu przez 2, 4, 8, 
16,  32,  64,  128,  256,  512,  1024  itd.  JeŜeli  polecenia  przesunięcia  są  Ci  obce, 
wystarczy rozwaŜenie dwójkowej postaci liczby: 

01001101 

czyli, dziesiętnie, 77. Przesunięcie w prawo prowadzi do wartości: 

00100110 

czyli, dziesiętnie, 38. 

Warto  stosować  w  miejsce  liczb  zmiennoprzecinkowych  wartości  całkowite.  Jest  to 
moŜliwe  w  większej  ilości  sytuacji,  niŜ  mogłoby  się  początkowo  wydawać. 
Przykładowo  do  przekształcenia  ułamka  na  liczbę  dziesiętną  normalnie  uŜywamy 
polecenia: 

procent = x / y * 100; 

Wymaga  to  zastosowania  zmiennych  zmiennoprzecinkowych.  MoŜna  jednak 
posłuzyć się konstrukcją: 

z = x * 100; 
procent = z / y; 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

275 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

275

 

Przeszukiwanie katalogów 

Funkcje  „znajdź  pierwszy”  i  „znajdź  następny”  stosuje  się  do  wyszukiwania  w 
katalogu systemu DOS określonej nazwy lub nazw plików. Pierwsza z nich, „znajdź 
pierwszy”,  realizowana  jest  za  pośrednictwem  przerwania  systemu  DOS  nr  21, 
funkcji 4E. Pobiera ona specyfikację nazwy pliku w postaci ciągu ASCII, który moŜe 
zawierać  symbole  wieloznaczne,  oraz  wymagane  atrybuty  pliku.  Funkcja  umieszcza 
informacje  o  pliku  w  obszarze  przesyłania  danych  dysku  (DTA,  disk  transfer  area
i zwraca  wyłączony  znacznik  przeniesienia  (carry  flag).  W  przypadku  wystąpienia 
błędu,  którego  przyczyną  moŜe  być  brak  plików  odpowiadających  podanemu 
wzorcowi, funkcja zwraca ustawiony znacznik przeniesienia. 

Po  udanym  wywołaniu  funkcji  „znajdź pierwszy”  program  moŜe  wywołać  funkcję 
„znajdź następny” (przerwanie systemu DOS nr 21, funkcja 4F), która wyszuka kolejny 
plik  odpowiadający  specyfikacji  podanej  w  wywołaniu  „znajdź  pierwszy”.  Udane 
wykonanie  kończy  umieszczenie  informacji  o  pliku  w  obszarze  DTA  i  zwrócenie 
wyłączonego  znacznika  przeniesienia.  W  pozostałych  przypadkach  znacznik  jest 
ustawiany. 

Większość  kompilatorów  C  dla  komputerów  PC  oferuje  niestandardowe  funkcje 
biblioteczne,  umoŜliwiające  korzystanie  z  tych  dwóch  funkcji  systemu  DOS.  W 
Turbo  C  noszą  one  nazwy 

findfirst()

  i 

findnext()

  (wykorzystanie  funkcji 

bibliotecznych  uwalnia  programistę  od  niezbyt  wygodnego,  bezpośredniego 
korzystania  z  DTA).  JeŜeli  korzystamy  z  Microsoft  C,  zamiast 

findfirst()

  i 

findnext()

 uŜywamy 

_dos_ findfirst()

 i 

_dos_findnext()

PoniŜszy  przykład  jest  próbą  najprostszej  implementacji  polecenia  systemu  DOS, 

dir

#include <stdio.h> 
#include <dir.h> 
#include <dos.h> 
 
void main(void) 

  /* Wy

ś

wietlanie zawarto

ś

ci katalogu bie

Ŝą

cego */ 

  int skonczone; 
  int dz; 
  int mies; 
  int rok; 
  int godz; 
  int min; 
  char amflag; 
  struct ffblk ffblk; 
  struct fcb fcb; 
 
  /* Rozpocznij od wy

ś

wietlenia podkatalogów */ 

  skonczone = findfirst("*.",&ffblk,16); 
 
  while (!skonczone) 
  { 
    rok = (ffblk.ff_fdate >> 9) + 80; 
    mies = (ffblk.ff_fdate >> 5) & 0x0f; 
    dz = ffblk.ff_fdate & 0x1f; 

background image

 
276 

Hack Wars. Tom 1. Na tropie hakerów 

276 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

    godz = (ffblk.ff_ftime >> 11); 
    min = (ffblk.ff_ftime >> 5) & 63; 
 
    amflag = 'a'; 
 
    if (godz > 12) 
    { 
      godz -= 12; 
      amflag = 'p'; 
    } 
 
    printf("&-11.11s <DIR>  %02d-%02d-%02d %2d:%02d%c\n", 
        ffblk.ff_name,dz,mies,rok,godz,min,amflag); 
    skonczone = findnext(&ffblk); 
  } 
 
  /* Teraz wszystkie pliki, z wyj

ą

tkiem katalogów */ 

  skonczone = findfirst("*.*",&ffblk,231); 
 
  while (!skonczone) 
  { 
    rok = (ffblk.ff_fdate >> 9) + 80; 
    mies = (ffblk.ff_fdate >> 5) & 0x0f; 
    dz = ffblk.ff_fdate & 0x1f; 
    godz = (ffblk.ff_ftime >> 11); 
    min = (ffblk.ff_ftime >> 5) & 63; 

    amflag = 'a'; 
 
    if (godz > 12) 
    { 
      godz -= 12; 
      amflag = 'p'; 
    } 
 
    parsfnm(ffblk.ff_name,&fcb,1); 
 
    printf("%-8.8s %-3.3s %8ld %02d-%02d-%02d %2d:%02d%c\n", 
        fcb.fcb_name,fcb.fcb_ext,ffblk.ff_fsize, 
        dz,mies,rok,godz,min,amflag); 
    skonczone = findnext(&ffblk); 
  } 

Funkcja 

parsfnm()

  to  polecenie  biblioteki  Turbo  C,  wykorzystujące  funkcję 

systemu  DOS  do  rozdzielenia  zawierającego  nazwę  pliku  ciągu  ASCII  na  części 
składowe.  Są  one  umieszczane  w  tzw.  bloku  kontrolnym  pliku  (FCB,  file  control 
block
), skąd mogą zostać łatwo pobrane i wyświetlone przez 

printf()

. Obszar DTA 

systemu DOS opiera się na następującej strukturze. 

Przesunięcie 

Długość 

Zawartość 

00 

15 

zarezerwowane, 

15 

Bajt 

atrybuty pliku, 

16 

Słowo 

godzina, 

18 

Słowo 

data, 

1A 

04 

rozmiar, 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

277 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

277

 

1E 

0D 

nazwa i rozszerzenie pliku jako ciąg ASCII. 

Pole godziny odnosi się do ostatniej modyfikacji pliku i zawiera następujące elementy. 

Bity 

Zawartość 

0 – 4 

liczba sekund podzielona przez 2, 

5 – 10 

minuty, 

11 – 15 

godziny. 

Pole daty równieŜ odnosi się do ostatniej modyfikacji pliku. 

Bity 

Zawartość 

0 – 4 

dzień, 

5 – 8 

miesiąc, 

9 – 15 

liczba lat od roku 1980. 

Pobranie  tych  danych  z  obszaru  DTA  wymaga  nieco  wysiłku,  co  widać  na 
wcześniejszym przykładzie. Szerszego komentarza wymaga jeszcze bajt atrybutów. 

Bity 

Zawartość 

tylko do odczytu, 

ukryty, 

systemowy, 

etykieta woluminu, 

katalog, 

archiwum 

Dostęp do pamięci rozbudowanej 

Pamięć  RAM  komputerów  PC  dostępna  jest  w  trzech  odmianach:  jako  pamięć 
konwencjonalna  (conventional),  rozbudowana  (expanded)  i  rozszerzona  (extended). 
Pamięć  konwencjonalna  to  początkowe  640  kB,  dostępne  dla  systemu  operacyjnego 
DOS.  Jest  to  pamięć  wykorzystywana  standardowo,  choć  często  niewystarczająca. 
Adresy  pamięci  rozbudowanej  pozostają  poza  zakresem  pamięci  konwencjonalnej. 
Dostępu  do  niej  nie  umoŜliwia  sam  system  DOS,  ale  dodatkowy  program,  tzw. 
program obsługi pamięci LIM EMS. Do jego wywoływania słuŜy przerwanie 67h. 

Głównym problemem w dostępie do pamięci rozbudowanej jest to, Ŝe bez względu na 
ilość tej pamięci w komputerze, jej adresowanie odbywa się za pośrednictwem 16-ki-
lobajtowych  bloków,  tzw.  stron.  JeŜeli  więc  programowi  przyznane  zostaną  2  MB 
pamięci  rozbudowanej,  będzie  to  128  stron  (128 

  16  kB  =  2  MB).  Dostępność 

sterownika  LIM  EMS  moŜna  ustalić,  otwierając  plik  (urządzenie  IOCTL) 

background image

 
278 

Hack Wars. Tom 1. Na tropie hakerów 

278 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

EMMXXXX0. Norma LIM  wymaga bowiem, aby plik taki istniał, gdy sterownik jest 
aktywny. 

PoniŜszy  przykład  ilustruje  sposób  korzystania  z  podstawowych  funkcji  kontroli  i 
dostępu do pamięci rozbudowanej. 

#include <dos.h> 
#include <string.h> 
#include <stdio.h> 
#include <stdlib.h> 
#define EMM 0x67 

 

char far *emmbase; 

 

emmtest() 

  /* 
  Sprawdza czy istnieje pami

ęć

 rozbudowana, 

  próbuj

ą

c otworzy

ć

 plik EMMXXXX0. 

  */ 
   
  union REGS regs; 
  struct SREGS sregs; 
  int error; 
  long handle; 

 

  /* Próba otwarcia urz

ą

dzenia plikowego EMMXXXX0 */ 

  regs.x.ax = 0x3d00; 
  regs.x.dx = (int)"EMMXXXX0"; 
  sregs.ds = _DS; 
  intdosx(&regs,&regs,&sregs); 
  handle = regs.x.ax; 
  error = regs.x.cflag; 
 
  if (!error) 
  { 
    regs.h.ah = 0x3e; 
    regs.x.bx = handle; 
    intdos(&regs,&regs); 
  } 
  return error; 

 
emmok() 

  /* 
  Sprawdza funkcjonowanie mened

Ŝ

era pami

ę

ci robudowanej 

  */ 
 
  union REGS regs; 
 
  regs.h.ah = 0x40; 
  int86(EMM,&regs,&regs); 
 
  if (regs.h.ah) 
    return 0; 
 
  regs.h.ah = 0x41; 
int86(EMM,&regs,&regs); 
 
  emmbase = MK_FP(regs.x.bx,0); 
  return 1; 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

279 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

279

 


 
long emmavail() 

  /* 
  Zwraca liczb

ę

 dost

ę

pnych (wolnych) 16-kilobajtowych stron 

  pami

ę

ci rozbudowanej lub -1 w przypadku bł

ę

du. 

  */ 
 
  union REGS regs; 
 
  regs.h.ah = 0x42; 
  int86(EMM,&regs,&regs); 
  if (!regs.h.ah) 
    return regs.x.bx; 
  return -1; 

 
long emmalloc(int n) 

  /* 
  

śą

da przyznania n stron pami

ę

ci rozbudowanej i zwraca 

  przypisany im uchwyt plikowy lub -1 w przypadku bł

ę

du 

  */ 

  union REGS regs; 
 
  regs.h.ah = 0x43; 
  regs.x.bx = n; 
  int86(EMM,&regs,&regs); 
  if (regs.h.ah) 
    return -1; 
  return regs.x.dx; 

 
emmmap(long handle, int phys, int page) 

  /* 
  Mapuje fizyczn

ą

 stron

ę

 pami

ę

ci rozbudowanej do ramki strony 

  w 16-kilobajtowym oknie pami

ę

ci konwencjonalnej, aby 

  umo

Ŝ

liwi

ć

 wymian

ę

 danych pomi

ę

dzy pami

ę

ci

ą

 EMS a 

  konwencjonaln

ą

  */ 
 
  union REGS regs; 
 
  regs.h.ah = 0x44; 
  regs.h.al = page; 
  regs.x.bx = phys; 
  regs.x.dx = handle; 
  int86(EMM,&regs,&regs); 
  return (regs.h.ah == 0); 

 
void emmmove(int page, char *str, int n) 

  /* 
  Przenosi n bajtów z pami

ę

ci konwencjonalnej do okre

ś

lonej 

  strony pami

ę

ci rozbudowanej. 

  */ 
 
  char far *ptr; 

background image

 
280 

Hack Wars. Tom 1. Na tropie hakerów 

280 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

 
  ptr = emmbase + page * 16384; 
  while(n-- > 0) 
    *ptr++ = *str++; 

 
void emmget(int page, char *str, int n) 

  /* 
  Przenosi n bajtów z okre

ś

lonej strony pami

ę

ci rozbudowanej 

  do pami

ę

ci konwencjonalnej 

  */ 
 
  char far *ptr; 
 
  ptr = emmbase + page * 16384; 
  while(n-- > 0) 
    *str++ = *ptr++; 

emmclose(long handle) 

  /* 
  Zwalnia strony pami

ę

ci EMS alokowane do uchwytu 'handle' 

  */ 
 
  union REGS regs; 
 
  regs.h.ah = 0x45; 
  regs.x.dx = handle; 
  int86(EMM,&regs,&regs); 
  return (regs.h.ah == 0); 

 
/* Funkcja testuj

ą

ca zdefiniowane wy

Ŝ

ej. */ 

 
void main() 

  long emmhandle; 
  long avail; 
  char teststr[80]; 
  int i; 

  if(!emmtest()) 
  { 
    printf("Pami

ęć

 EMS nie jest dost

ę

pna\n"); 

    exit(0); 
  } 
 
  if(!emmok()) 
  { 
    printf("Nie mo

Ŝ

na odnale

źć

 mened

Ŝ

era pami

ę

ci EMS\n"); 

    exit(0); 
  } 
 
  avail = emmavail(); 
  if (avail == -1) 
  {  
    printf("Bł

ą

d mened

Ŝ

era pami

ę

ci EMS\n"); 

    exit(0); 
  } 
  printf("Liczba dost

ę

pnych stron: %ld\n", avail); 

 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

281 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

281

 

  /* 

śą

danie 10 stron pami

ę

ci EMS */ 

  if((emmhandle = emmalloc(10)) < 0) 
  { 
    printf("Brak pami

ę

ci\n"); 

    exit(0); 
  } 
 
  for (i = 0; i < 10; i++) 
  { 
    sprintf(teststr,"%02d Dane testowe\n",i); 
    emmmap(emmhandle,i,0); 
    emmmove(0,teststr,strlen(teststr) + 1); 
  } 
 
  for (i = 0; i < 10; i++) 
  { 
    emmmap(emmhandle,i,0); 
    emmget(0,teststr,strlen(teststr) + 1); 
    printf("ODCZYT BLOKU %d: %s\n",i,teststr); 
  } 
 
  emmclose(emmhandle); 

Dostęp do pamięci rozszerzonej 

Pamięć rozszerzona wyparła niemal całkowicie pamięć rozbudowaną, bo jest szybsza 
i prostsza w uŜyciu. Podobnie jednak jak w przypadku pamięci rozbudowanej, nie jest 
moŜliwy  dostęp  do  pamięci  rozszerzonej  w  trybie  standardowym  systemu  DOS  — 
przesyłanie  danych  realizowane  jest  za  pośrednictwem  bufora  w  pamięci 
konwencjonalnej  (pamięci  „trybu  rzeczywistego”).  Proces  kopiowania  danych  do 
pamięci rozszerzonej jest więc dwuetapowy: skopiowanie danych do bufora i dopiero 
z niego — do pamięci rozszerzonej. 

Przed rozpoczęciem korzystania z pamięci rozszerzonej program powinien sprawdzić, 
czy jest ona dostępna. Przedstawiona funkcja 

XMS_init()

 przeprowadza taki test, po 

czym  wywołuje  kolejną  funkcję, 

GetXMSEntry()

,  która  inicjuje  korzystanie  z 

pamięci  rozszerzonej  przez  program.  Dodatkowo  alokowany  jest  bufor  przesyłania 
danych w pamięci konwencjonalnej. 

/* 
  BLOCKSIZE b

ę

dzie rozmiarem bufora w pami

ę

ci konwencjonalnej,  

  przeznaczonego do wymiany danych z pami

ę

ci

ą

 XMS 

  (wielokrotno

ść

 1-kilobajtowej jednostki alokacji XMS) 

*/ 
 
#ifdef __SMALL__ 
#define BLOCKSIZE (16L * 1024L) 
#endif 
 
 
#ifdef __MEDIUM__ 
#define BLOCKSIZE (16L * 1024L) 
#endif 
 
 
#ifdef __COMPACT__ 

background image

 
282 

Hack Wars. Tom 1. Na tropie hakerów 

282 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

#define BLOCKSIZE (64L * 1024L) 
#endif 
 
#ifdef __LARGE__ 
#define BLOCKSIZE (64L * 1024L) 
#endif 
 
 
char XMS_init() 

  /* 
    zwraca 0, gdy istnieje pami

ęć

 XMS, 

      1 - gdy nie istnieje 
      2 - je

Ŝ

eli nie mo

Ŝ

na zaalokowa

ć

 bufora wymiany danych w 

          pami

ę

ci konwencjonalnej 

  */ 
  unsigned char status; 
  _AX=0x4300; 
  geninterrupt(0x2F); 
  status = _AL; 
  if(status==0x80) 
  { 
    GetXMSEntry(); 
    XMSBuf = (char far *) farmalloc(BLOCKSIZE); 
    if (XMSBuf == NULL) 
      return 2; 
    return 0; 
  } 
  return 1; 

 
void GetXMSEntry(void) 

  /* 
    Ustawia XMSFunc na punkt wej

ś

cia mened

Ŝ

era XMS 

    co umo

Ŝ

liwi pó

ź

niejsze wywołania 

  */ 
 
  _AX=0x4310; 
  geninterrupt(0x2F); 
  XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX); 

Po  potwierdzeniu  obecności  pamięci  rozszerzonej  w  systemie  kolejna  funkcja  moŜe 
ustalić, jaka jej ilość jest dostępna: 

void XMSSize(int *kbAvail, int *largestAvail) 

  /* 
    Zwraca wielko

ść

 dost

ę

pnej pami

ę

ci oraz najwi

ę

kszego 

    dost

ę

pnego bloku (w kilobajtach) 

  */ 

 

  _AH=8; 
  (*XMSFunc)(); 
  *largestAvail=_DX; 
  *kbAvail=_AX; 

Kolejna  funkcja  alokuje  blok  pamięci  rozszerzonej,  w  podobny  sposób  jak  przy 
alokacji pamięci konwencjonalnej: 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

283 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

283

 

char AllocXMS(unsigned long numberBytes) 

  /* 
    Alokacja bloku pami

ę

ci XMS wielko

ś

ci 'numberBytes' 

    zwraca 1, gdy zako

ń

czona poprawnie 

      0 w przypadku bł

ę

du 

  */ 

  _DX = (int)(numberBytes / 1024); 
  _AH = 9; 
  (*XMSFunc)(); 
  if (_AX==0) 
  { 
    return 0; 
  } 
  XMSHandle=_DX; 
  return 1; 

System  DOS  nie  zwalnia  zaalokowanej  pamięci  rozszerzonej  automatycznie.  Przed 
zakończeniem program musi więc zadbać o „uwolnienie” przyznanych mu wcześniej 
bloków.  Wykona  to  kolejna  funkcja,  zwalniająca  blok  pamięci  zaalokowany 
wcześniej przez funkcję 

AllocXMS()

void XMS_free(void) 

  /* 
    Zwolnij pami

ęć

 XMS 

  */ 
  _DX=XMSHandle; 
  _AH=0x0A; 
  (*XMSFunc)(); 

Kolejne  dwie  dłuŜsze  funkcje  słuŜą  do  wymiany  danych:  jedna  zapisuje  dane  do 
pamięci  rozszerzonej,  druga  kopiuje  dane  z  pamięci  rozszerzonej  do  pamięci 
konwencjonalnej. 

/* 
  Struktura XMSParms słu

Ŝ

y do wymiany danych pomi

ę

dzy pami

ę

ci

ą

 

  trybu rzeczywistego, a XMS 
*/ 
 
struct parmstruct 

  /* 
    rozmiar kopiowanego bloku w bajtach 
  */ 
  unsigned long blockLength; 
 
  /* 
    sourceHandle to uchwyt XMS obszaru 

ź

ródłowego; 0 oznacza,  

    

Ŝ

e sourcePtr b

ę

dzie wska

ź

nikiem trybu rzeczywistego 

    (16:16); w pozostałych przypadkach sourcePtr jest 
    32-bitowym przesuni

ę

ciem od pocz

ą

tku obszaru XMS 

    okre

ś

lonego przez sourceHandle 

  */ 
 
  unsigned int sourceHandle; 
  far void *sourcePtr; 
 

background image

 
284 

Hack Wars. Tom 1. Na tropie hakerów 

284 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  /* 
    destHandle to uchwyt XMS obszaru docelowego; 0 oznacza,  
    

Ŝ

e destPtr b

ę

dzie wska

ź

nikiem trybu rzeczywistego 

    (16:16); w pozostałych przypadkach destPtr jest 
    32-bitowym przesuni

ę

ciem od pocz

ą

tku obszaru XMS 

    okre

ś

lonego przez destHandle 

  */ 

 

  unsigned int destHandle; 
  far void *destPtr; 

XMSParms; 

 
 

char XMS_write(unsigned long loc, char far *val, unsigned length) 

  /* 
    Zaokr

ą

glij długo

ść

 do kolejnej warto

ś

ci parzystej 

  */ 
  length += length % 2; 

 

  XMSParms.sourceHandle=0; 
  XMSParms.sourcePtr=val; 
  XMSParms.destHandle=XMSHandle; 
  XMSParms.destPtr=(void far *) (loc); 
  XMSParms.blockLength=length; /* Musi by

ć

 liczb

ą

 parzyst

ą

! */ 

  _SI = FP_OFF(&XMSParms); 
  _AH=0x0B; 
  (*XMSFunc)(); 
  if (_AX==0) 
  { 
    return 0; 
  } 
  return 1; 

 
 

void *XMS_read(unsigned long loc,unsigned length) 

  /* 
    Zwraca wska

ź

nik do danych 

    lub NULL w przypadku bł

ę

du 

  */ 

 

  /* 
    Zaokr

ą

glij długo

ść

 do kolejnej warto

ś

ci parzystej 

  */ 
  length += length % 2; 

 

  XMSParms.sourceHandle=XMSHandle; 
  XMSParms.sourcePtr=(void far *) (loc); 
  XMSParms.destHandle=0; 
  XMSParms.destPtr=XMSBuf; 
  XMSParms.blockLength=length;  /* Musi by

ć

 liczb

ą

 parzyst

ą

 */ 

  _SI=FP_OFF(&XMSParms); 
  _AH=0x0B; 
  (*XMSFunc)(); 
  if (_AX==0) 
  { 
    return NULL; 
  } 
  return XMSBuf; 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

285 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

285

 

Czas teraz połączyć przedstawione dotąd funkcje w jednym programie: 

/* Sekwencyjna tabela rekordów zmiennej długo

ś

ci 

   w pami

ę

ci XMS */ 

 
#include <dos.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <alloc.h> 
#include <string.h> 
 
#define TRUE 1 
#define FALSE 0 
 
/* 
  BLOCKSIZE b

ę

dzie rozmiarem bufora w pami

ę

ci konwencjonalnej,  

  przeznaczonego do wymiany danych z pami

ę

ci

ą

 XMS 

  (wielokrotno

ść

 1-kilobajtowej jednostki alokacji XMS) 

*/ 
 
#ifdef __SMALL__ 
#define BLOCKSIZE (16L * 1024L) 
#endif 
 
 
#ifdef __MEDIUM__ 
#define BLOCKSIZE (16L * 1024L) 
#endif 
 
 
#ifdef __COMPACT__ 
#define BLOCKSIZE (64L * 1024L) 
#endif 

 
 

#ifdef __LARGE__ 
#define BLOCKSIZE (64L * 1024L) 
#endif 

 
 

/* 
  Struktura XMSParms słu

Ŝ

y do wymiany danych pomi

ę

dzy pami

ę

ci

ą

 

  trybu rzeczywistego, a XMS 
*/ 

 

struct parmstruct 

  /* 
    rozmiar kopiowanego bloku w bajtach 
  */ 
  unsigned long blockLength; 

 

  /* 
    sourceHandle to uchwyt XMS obszaru 

ź

ródłowego; 0 oznacza,  

    

Ŝ

e sourcePtr b

ę

dzie wska

ź

nikiem trybu rzeczywistego 

    (16:16); w pozostałych przypadkach sourcePtr jest 
    32-bitowym przesuni

ę

ciem od pocz

ą

tku obszaru XMS 

    okre

ś

lonego przez sourceHandle 

  */ 

  unsigned int sourceHandle; 
  far void *sourcePtr; 

 

  /* 

background image

 
286 

Hack Wars. Tom 1. Na tropie hakerów 

286 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

    destHandle to uchwyt XMS obszaru docelowego; 0 oznacza,  
    

Ŝ

e destPtr b

ę

dzie wska

ź

nikiem trybu rzeczywistego 

    (16:16); w pozostałych przypadkach destPtr jest 
    32-bitowym przesuni

ę

ciem od pocz

ą

tku obszaru XMS 

    okre

ś

lonego przez destHandle 

  */ 

 

  unsigned int destHandle; 
  far void *destPtr; 

XMSParms; 

void far (*XMSFunc) (void); /* Posłu

Ŝ

y do wywołania mened

Ŝ

era XMS 

(himem.sys) */ 
char GetBuf(void); 
void GetXMSEntry(void); 

 

char *XMSBuf; /* Bufor wymiany danych, w pami

ę

ci konwencjonalnej */ 

 

unsigned int XMSHandle; /* uchwyt do zaalokowanego bloku XMS */ 

 
 

char XMS_init() 

  /* 
    zwraca 0, gdy istnieje pami

ęć

 XMS, 

      1 - gdy nie istnieje 
      2 - je

Ŝ

eli nie mo

Ŝ

na zaalokowa

ć

 bufora wymiany danych w 

          pami

ę

ci konwencjonalnej 

  */ 
  unsigned char status; 
  _AX=0x4300; 
  geninterrupt(0x2F); 
  status = _AL; 
  if(status==0x80) 
  { 
    GetXMSEntry(); 
    XMSBuf = (char far *) farmalloc(BLOCKSIZE); 
    if (XMSBuf == NULL) 
      return 2; 
    return 0; 
  } 
  return 1; 

 

void GetXMSEntry(void) 

  /* 
    Ustawia XMSFunc na punkt wej

ś

cia mened

Ŝ

era XMS 

    co umo

Ŝ

liwi pó

ź

niejsze wywołania 

  */ 

 

  _AX=0x4310; 
  geninterrupt(0x2F); 
  XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX); 

void XMSSize(int *kbAvail, int *largestAvail) 

  /* 
    Zwraca wielko

ść

 dost

ę

pnej pami

ę

ci oraz najwi

ę

kszego 

    dost

ę

pnego bloku (w kilobajtach) 

  */ 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

287 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

287

 

 
  _AH=8; 
  (*XMSFunc)(); 
  *largestAvail=_DX; 
  *kbAvail=_AX; 

char AllocXMS(unsigned long numberBytes) 

  /* 
    Alokacja bloku pami

ę

ci XMS wielko

ś

ci 'numberBytes' 

    Zwraca 1, gdy zako

ń

czona poprawnie 

      0 w przypadku bł

ę

du 

  */ 
 
  _DX = (int)(numberBytes / 1024); 
  _AH = 9; 
  (*XMSFunc)(); 
  if (_AX==0) 
  { 
    return FALSE; 
  } 
  XMSHandle=_DX; 
  return TRUE; 

 
void XMS_free(void) 

  /* 
    Zwolnij pami

ęć

 XMS 

  */ 
  _DX=XMSHandle; 
  _AH=0x0A; 
  (*XMSFunc)(); 

 
char XMS_write(unsigned long loc, char far *val, unsigned length) 

  /* 
    Zaokr

ą

glij długo

ść

 do kolejnej warto

ś

ci parzystej 

  */ 
  length += length % 2; 
 
  XMSParms.sourceHandle=0; 
  XMSParms.sourcePtr=val; 
  XMSParms.destHandle=XMSHandle; 
  XMSParms.destPtr=(void far *) (loc); 
  XMSParms.blockLength=length; /* Musi by

ć

 liczb

ą

 parzyst

ą

! */ 

  _SI = FP_OFF(&XMSParms); 
  _AH=0x0B; 
  (*XMSFunc)(); 
  if (_AX==0) 
  { 
    return FALSE; 
  } 
  return TRUE; 

 
 
void *XMS_read(unsigned long loc,unsigned length) 

  /* 
    Zwraca wska

ź

nik do danych 

background image

 
288 

Hack Wars. Tom 1. Na tropie hakerów 

288 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

    lub NULL w przypadku bł

ę

du 

  */ 

  /* 
    Zaokr

ą

glij długo

ść

 do kolejnej warto

ś

ci parzystej 

  */ 
  length += length % 2; 
 
  XMSParms.sourceHandle=XMSHandle; 
  XMSParms.sourcePtr=(void far *) (loc); 
  XMSParms.destHandle=0; 
  XMSParms.destPtr=XMSBuf; 
  XMSParms.blockLength=length;  /* Musi by

ć

 liczb

ą

 parzyst

ą

 */ 

  _SI=FP_OFF(&XMSParms); 
  _AH=0x0B; 
  (*XMSFunc)(); 
  if (_AX==0) 
  { 
    return NULL; 
  } 
  return XMSBuf; 

 
 
/* 
  Przykładowy program 
  Zapisuje ci

ą

gi ró

Ŝ

nej długo

ś

ci  

  do pojedycznego bloku XMS(EMB)  
  i odczytuje je 
*/ 
 
int main() 

  int kbAvail,largestAvail; 
  char buffer[80]; 
  char *p; 
  long pos; 
  long end; 
 
  if (XMS_init() == 0) 
    printf("Pami

ęć

 XMS dost

ę

pna ...\n"); 

  else 
  { 
    printf("Pami

ęć

 XMS nie jest dost

ę

pna\n"); 

    return(1); 
  } 

  XMSSize(&kbAvail,&largestAvail); 
  printf("Wielko

ść

 dost

ę

pnej pami

ę

ci XMS: %d; Largest block: 

    %dK\n",kbAvail,largestAvail); 
 
  if (!AllocXMS(2000 * 1024L)) 
    return(1); 
 
 
  pos = 0; 
 
  do 
  { 
    p = fgets(buffer,1000,stdin); 
    if (p != NULL) 
    { 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

289 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

289

 

      XMS_write(pos,buffer,strlen(buffer) + 1); 
      pos += strlen(buffer) + 1; 
    } 
  } 
  while(p != NULL); 
 
  end = pos; 
 
  pos = 0; 
 
  do 
  { 
    memcpy(buffer,XMS_read(pos,100),70); 
    printf("%s",buffer); 
    pos += strlen(buffer) + 1; 
  } 
  while(pos < end); 
 
  /* 
    Zwolnienie pami

ę

ci XMS jest BARDZO wa

Ŝ

ne! 

  */ 
  XMS_free(); 
  return 0; 

Tworzenie programów TSR 

Ostatnim  tematem  w  naszym  omówieniu  podstaw  języka  C,  mającym  zarazem 
szczególne  znaczenie  dla  zagadnień  zabezpieczeń,  są  wszechstronne  programy 
rezydentne
,  określane  równieŜ  skrótem  TSR  (terminate  and  stay  resident,  zakończ  i 
pozostań w pamięci). Programy takie pozostają aktywne w pamięci, w trakcie gdy „na 
pierwszym  planie”  pracują  inne  programy.  Tworzenie  programów  rezydentnych  to 
ulubione zajęcie wielu programistów i hakerów. 

Trudność  w  tworzeniu  programów  TSR  wynika  z  ograniczeń  DOS-u,  który  nie  jest 
wielozadaniowym  systemem  operacyjnym  i  nie  współpracuje  dobrze  z  kodem 
wielowątkowym,  przede  wszystkim  gdy  pojawiają  się  wzajemne  wywołania  funkcji 
systemowych (przerwań). Teoria programów rezydentnych jest dość prosta. Program 
taki  kończy  pracę  funkcją  DOS-u  keep  (przerwanie  27h),  a  nie  przy  uŜyciu 
standardowej  funkcji  terminate.  Zapewnia  to  zarezerwowanie  w  pamięci  obszaru 
wykorzystywanego  przez  program.  Nie  jest  to  skomplikowane,  koniecznie  trzeba 
jednak podać, jaka ilość pamięci ma zostać zarezerwowana. 

Głównym  źródłem  problemów  jest  brak  moŜliwości  korzystania  z  wywołań  funkcji 
DOS-u  od  momentu  przyjęcia  przez  programu  statusu  programu  rezydentnego. 
PoniŜej  przedstawiamy  kilka  podstawowych  zasad,  których  przestrzeganie  pomoŜe 
uniknąć problemów z programami TSR. 

1.

 

Unikanie wywołań funkcji DOS-u. 

2.

 

Monitorowanie znacznika zajętości DOS-u (busy flag). Gdy ma on wartość 
niezerową, oznacza to, Ŝe DOS wykonuje właśnie funkcję 21h i nie moŜna 
mu przeszkadzać! 

background image

 
290 

Hack Wars. Tom 1. Na tropie hakerów 

290 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

3.

 

Monitorowanie przerwania 28h. Sygnalizuje ono, Ŝe DOS jest zajęty 
oczekiwaniem na dane wejściowe z konsoli. W tym czasie moŜna wykonywać 
róŜne operacje bez względu na stan znacznika zajętości. 

4.

 

Sprawdzanie, czy program jest juŜ uruchomiony, aby uniknąć jego wielokrotnego 
ładowania do pamięci. 

5.

 

Nie wolno zapominać, Ŝe równieŜ inne programy TSR mogą przechwytywać 
przerwania. Konieczne jest więc tworzenie „łańcucha” — po wywołaniu własnej 
funkcji wywołujemy funkcję określoną wektorem zastanym przy uruchamianiu 
programu. 

6.

 

Program TSR musi korzystać z własnego stosu, a nie stosu uruchomionego 
procesu. 

7.

 

Programy TSR naleŜy kompilować, wybierając model pamięci small i wyłączając 
sprawdzanie stosu. 

8.

 

Po przejęciu kontroli program TSR musi poinformować DOS o zmianie procesu 
aktywnego. 

Przedstawione  poniŜej  trzy  moduły  kodu  stanowią  kompletny  program  rezydentny. 
Jest  to  prosta  baza  danych  typu  „ksiąŜka  adresowa”,  aktywowana  w  trakcie  pracy 
innych  programów  wciśnięciem  kombinacji  klawiszy  ALT  i  kropki  (.).  JeŜeli  praca 
systemu  operacyjnego  nie  moŜe  zostać  przerwana,  program  nie  reaguje  na 
kombinację klawiszy. Wówczas naleŜy spróbować ponownie. 

/*  
   Przykładowy program TSR (wyskakuj

ą

ca ksi

ąŜ

ka adresowa) 

   Kompilowa

ć

 z modelem pami

ę

ci 'small' i  

   wył

ą

czonym sprawdzaniem stosu (stack checking) 

*/ 
 
#include <dos.h> 
#include <stdio.h> 
#include <string.h> 
#include <dir.h> 
 
static union REGS rg; 

/*  
   Rozmiar programu pozostaj

ą

cego jako rezydentny 

   Aby okre

ś

li

ć

 wielko

ść

 minimaln

ą

, konieczne s

ą

 eksperymenty 

*/ 
unsigned sizeprogram = 28000/16; 
 
/* Aktywacja kombinacj

ą

 'Alt+.' */ 

unsigned scancode = 52;  /* . */ 
unsigned keymask = 8;    /* ALT */ 
 
char signature[]= "POPADDR"; 
char fpath[40]; 
 
/* 
  Prototypy funkcji 
*/ 
 
void curr_cursor(int *x, int *y); 
int resident(char *, void interrupt(*)()); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

291 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

291

 

void resinit(void); 
void terminate(void); 
void restart(void); 
void wait(void); 
void resident_psp(void); 
void exec(void); 
 
/* 
   Punkt wej

ś

cia z DOS-u 

*/ 
 
void main(int argc, char *argv[]) 

  void interrupt ifunc(); 
  int ivec; 
 
  /*  
     Dla uproszczenia zakładamy, 

Ŝ

e plik danych jest w 

     katalogu głównym dysku C: 
  */ 
  strcpy(fpath,"C:\\ADDRESS.DAT"); 
 
  if ((ivec = resident(signature,ifunc)) != 0) 
  { 
    /* Ju

Ŝ

 jest */ 

    if (argc > 1) 
    { 
      rg.x.ax = 0; 
      if (strcmp(argv[1],"quit") == 0) 
        rg.x.ax = 1; 
      else if (strcmp(argv[1],"restart") == 0) 
        rg.x.ax = 2; 
      else if (strcmp(argv[1],"wait") == 0) 
        rg.x.ax = 3; 
      if (rg.x.ax) 
      { 
        int86(ivec,&rg,&rg); 
        return; 
      } 
    } 
    printf("\nKsi

ąŜ

ka adresowa jest ju

Ŝ

 uruchomiona"); 

  } 
  else 
  { 
    /* Załadowanie programu TSR */ 
    printf("Ksi

ąŜ

ka adresowa została uruchomiona.\nWci

ś

nij 'Alt+.', 

aby 

      wywoła

ć

...\n"); 

    resinit(); 
  } 

void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax) 

  if(ax == 1) 
    terminate(); 
  else if(ax == 2) 
    restart(); 
  else if(ax == 3) 
    wait(); 

 

background image

 
292 

Hack Wars. Tom 1. Na tropie hakerów 

292 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

popup() 

  int x,y; 
 
  curr_cursor(&x,&y); 
 
  /* Wywołujemy program rezydentny C */ 
  exec(); 
  cursor(x,y); 

 
/* 
  Drugi moduł 
*/ 
 
#include <dos.h> 
#include <stdio.h> 
 
static union REGS rg; 
static struct SREGS seg; 
static unsigned mcbseg; 
static unsigned dosseg; 
static unsigned dosbusy; 
static unsigned enddos; 
char far *intdta; 
static unsigned intsp; 
static unsigned intss; 
static char far *mydta; 
static unsigned myss; 
static unsigned stack; 
static unsigned ctrl_break; 
static unsigned mypsp; 
static unsigned intpsp; 
static unsigned pids[2]; 
static int pidctr = 0; 
static int pp; 
static void interrupt (*oldtimer)(); 
static void interrupt (*old28)(); 
static void interrupt (*oldkb)(); 
static void interrupt (*olddisk)(); 
static void interrupt (*oldcrit)(); 
 
void interrupt newtimer(); 
void interrupt new28(); 
void interrupt newkb(); 
void interrupt newdisk(); 
void interrupt newcrit(); 

extern unsigned sizeprogram; 
extern unsigned scancode; 
extern unsigned keymask; 
 
static int resoff = 0; 
static int running = 0; 
static int popflg = 0; 
static int diskflag = 0; 
static int kbval; 
static int cflag; 
 
void dores(void); 
void pidaddr(void); 
 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

293 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

293

 

void resinit() 

  segread(&seg); 
  myss = seg.ss; 
 
  rg.h.ah = 0x34; 
  intdos(&rg,&rg); 
  dosseg = _ES; 
  dosbusy = rg.x.bx; 
 
  mydta = getdta(); 
  pidaddr(); 
  oldtimer = getvect(0x1c); 
  old28 = getvect(0x28); 
  oldkb = getvect(9); 
  olddisk = getvect(0x13); 
 
  setvect(0x1c,newtimer); 
  setvect(9,newkb); 
  setvect(0x28,new28); 
  setvect(0x13,newdisk); 
 
  stack = (sizeprogram - (seg.ds - seg.cs)) * 16 - 300; 
  rg.x.ax = 0x3100; 
  rg.x.dx = sizeprogram; 
  intdos(&rg,&rg); 

 
void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs) 

  diskflag++; 
  (*olddisk)(); 
  ax = _AX; 
  newcrit(); 
  flgs = cflag; 
  --diskflag; 

 
void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs) 

  ax = 0; 
  cflag = flgs; 

void interrupt newkb() 

  if (inportb(0x60) == scancode) 
  { 
    kbval = peekb(0,0x417); 
    if (!resoff && ((kbval & keymask) ^ keymask) == 0) 
    { 
      kbval = inportb(0x61); 
      outportb(0x61,kbval | 0x80); 
      outportb(0x61,kbval); 
      disable(); 
      outportb(0x20,0x20); 
      enable(); 
      if (!running) 
        popflg = 1; 
      return; 
    } 
  } 

background image

 
294 

Hack Wars. Tom 1. Na tropie hakerów 

294 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

  (*oldkb)(); 

 
void interrupt newtimer() 

  (*oldtimer)(); 
  if (popflg && peekb(dosseg,dosbusy) == 0) 
    if(diskflag == 0) 
    { 
      outportb(0x20,0x20); 
      popflg = 0; 
      dores(); 
    } 

 
void interrupt new28() 

  (*old28)(); 
  if (popflg && peekb(dosseg,dosbusy) != 0) 
  { 
    popflg = 0; 
    dores(); 
  } 

 
resident_psp() 

  intpsp = peek(dosseg,*pids); 
  for(pp = 0; pp < pidctr; pp++) 
    poke(dosseg,pids[pp],mypsp); 

 
interrupted_psp() 

  for(pp = 0; pp < pidctr; pp++) 
    poke(dosseg,pids[pp],intpsp); 

 
void dores() 

  running = 1; 
  disable(); 
  intsp = _SP; 
  intss = _SS; 
  _SP = stack; 
  _SS = myss; 
  enable(); 
  oldcrit = getvect(0x24); 
  setvect(0x24,newcrit); 
  rg.x.ax = 0x3300; 
  intdos(&rg,&rg); 
  ctrl_break = rg.h.dl; 
  rg.x.ax = 0x3301; 
  rg.h.dl = 0; 
  intdos(&rg,&rg); 
  intdta = getdta(); 
  setdta(mydta); 
  resident_psp(); 
  popup(); 
  interrupted_psp(); 
  setdta(intdta); 
  setvect(0x24,oldcrit); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

295 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

295

 

  rg.x.ax = 0x3301; 
  rg.h.dl = ctrl_break; 
  intdos(&rg,&rg); 
  disable(); 
  _SP = intsp; 
  _SS = intss; 
  enable(); 
  running = 0; 

 
static int avec = 0; 
 
unsigned resident(char *signature,void interrupt(*ifunc)()) 

  char *sg; 
  unsigned df; 
  int vec; 
 
  segread(&seg); 
  df = seg.ds-seg.cs; 
  for(vec = 0x60; vec < 0x68; vec++) 
  { 
    if (getvect(vec) == NULL) 
    { 
      if (!avec) 
        avec = vec; 
      continue; 
    } 
    for(sg = signature; *sg; sg++) 
    if (*sg != peekb(peek(0,2+vec*4)+df,(unsigned)sg)) 
      break; 
    if (!*sg) 
      return vec; 
  } 
  if (avec) 
    setvect(avec,ifunc); 
  return 0; 

 
static void pidaddr() 

  unsigned adr = 0; 
 
  rg.h.ah = 0x51; 
  intdos(&rg,&rg); 
  mypsp = rg.x.bx; 
  rg.h.ah = 0x52; 
  intdos(&rg,&rg); 
  enddos = _ES; 
  enddos = peek(enddos,rg.x.bx-2); 
  while(pidctr < 2 && (unsigned)((dosseg<<4) + adr) < (enddos <<4)) 
  { 
    if (peek(dosseg,adr) == mypsp) 
    { 
      rg.h.ah = 0x50; 
      rg.x.bx = mypsp + 1; 
      intdos(&rg,&rg); 
      if (peek(dosseg,adr) == mypsp + 1) 
        pids[pidctr++] = adr; 
      rg.h.ah = 0x50; 
      rg.x.bx = mypsp; 
      intdos(&rg,&rg); 

background image

 
296 

Hack Wars. Tom 1. Na tropie hakerów 

296 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

    } 
    adr++; 
  } 

 
static resterm() 

  setvect(0x1c,oldtimer); 
  setvect(9,oldkb); 
  setvect(0x28,old28); 
  setvect(0x13,olddisk); 
  setvect(avec,(void interrupt (*)()) 0); 
  rg.h.ah = 0x52; 
  intdos(&rg,&rg); 
  mcbseg = _ES; 
  mcbseg = peek(mcbseg,rg.x.bx-2); 
  segread(&seg); 
  while(peekb(mcbseg,0) == 0x4d) 
  { 
    if(peek(mcbseg,1) == mypsp) 
    { 
      rg.h.ah = 0x49; 
      seg.es = mcbseg+1; 
      intdosx(&rg,&rg,&seg); 
    } 
    mcbseg += peek(mcbseg,3) + 1; 
  } 

 
terminate() 

  if (getvect(0x13) == (void interrupt (*)()) newdisk) 
    if (getvect(9) == newkb) 
      if(getvect(0x28) == new28) 
        if(getvect(0x1c) == newtimer) 
        { 
          resterm(); 
          return; 
        } 
  resoff = 1; 

 
restart() 

  resoff = 0; 

 
wait() 

  resoff = 1; 

 
void cursor(int y, int x) 

  rg.x.ax = 0x0200; 
  rg.x.bx = 0; 
  rg.x.dx = ((y << 8) & 0xff00) + x; 
  int86(16,&rg,&rg); 

 
void curr_cursor(int *y, int *x) 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

297 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

297

 


  rg.x.ax = 0x0300; 
  rg.x.bx = 0; 
  int86(16,&rg,&rg); 
  *x = rg.h.dl; 
  *y = rg.h.dh; 

 
/*  
   Trzeci moduł, przykładowa ksi

ąŜ

ka adresowa 

   z obsług

ą

 myszy 

*/ 

#include <stdio.h> 
#include <stdlib.h> 
#include <io.h> 
#include <string.h> 
#include <fcntl.h> 
#include <sys\stat.h> 
#include <dos.h> 
#include <conio.h> 
#include <graphics.h> 
#include <bios.h> 
 
/* left nie mo

Ŝ

e mie

ć

 warto

ś

ci mniejszej od 3 */ 

#define left  4 
 
/* Struktura rekordu */ 
typedef struct 

  char name[31]; 
  char company[31]; 
  char address[31]; 
  char area[31]; 
  char town[31]; 
  char county[31]; 
  char post[13]; 
  char telephone[16]; 
  char fax[16]; 

data; 
 
extern char fpath[]; 
 
static char scr[4000]; 
 
static char sbuff[2000]; 
char stext[30]; 
data rec; 
int handle; 
int recsize; 
union REGS inreg,outreg; 
 
/*  
  Prototypy funkcji  
*/ 
void FATAL(char *); 
void OPENDATA(void); 
void CONTINUE(void); 
void EXPORT_MULTI(void); 
void GETDATA(int); 
int GETOPT(void); 

background image

 
298 

Hack Wars. Tom 1. Na tropie hakerów 

298 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

void DISPDATA(void); 
void ADD_REC(void); 
void PRINT_MULTI(void); 
void SEARCH(void); 
void MENU(void); 
 
int GET_MOUSE(int *buttons) 

  inreg.x.ax = 0; 
  int86(0x33,&inreg,&outreg); 
  *buttons = outreg.x.bx; 
  return outreg.x.ax; 

 
void MOUSE_CURSOR(int status) 

  /* Status = 0 kursor wył

ą

czony */ 

  /*      1 kursor wł

ą

czony */ 

 
  inreg.x.ax = 2 - status; 
  int86(0x33,&inreg,&outreg); 

 
int MOUSE_LOCATION(int *x, int *y) 

  inreg.x.ax = 3; 
  int86(0x33,&inreg,&outreg); 
 
  *x = outreg.x.cx / 8; 
  *y = outreg.x.dx / 8; 
 
  return outreg.x.bx; 

 
int GETOPT() 

  int result; 
  int x; 
  int y; 
 
  do 
  { 
    do 
    { 
      result = MOUSE_LOCATION(&x,&y); 
      if (result & 1) 
      { 
        if (x >= 52 && x <= 53 && y >= 7 && y <= 15) 
          return y - 7; 
        if (x >= 4 && x <= 40 && y >= 7 && y <= 14) 
          return y + 10; 
 
        if (x >= 4 && x <= 40 && y == 15) 
          return y + 10; 
      } 
    } 
    while(!bioskey(1)); 
 
    result = bioskey(0); 
    x = result & 0xff; 
    if (x == 0) 
    { 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

299 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

299

 

      result = result >> 8; 
      result -= 60; 
    } 
  } 
  while(result < 0 || result > 8); 
  return result; 

 
void setvideo(unsigned char mode) 

  /* Okre

ś

la tryb wy

ś

wietlania i czy

ś

ci ekran */ 

 
  inreg.h.al = mode; 
  inreg.h.ah = 0x00; 
  int86(0x10, &inreg, &outreg); 

 
 
int activepage(void) 

  /* Zwraca wybran

ą

 stron

ę

 ekranu */ 

 
  union REGS inreg,outreg; 
 
  inreg.h.ah = 0x0F; 
  int86(0x10, &inreg, &outreg); 
  return(outreg.h.bh); 

 
void print(char *str) 

  /*  
     Wyprowadza znaki bezpo

ś

ednio na bie

Ŝą

c

ą

 stron

ę

 ekranu,  

     poczynaj

ą

c od bie

Ŝą

cej pozycji kursora. Kursor nie jest 

     przenoszony. 
     Funkcja zakłada, 

Ŝ

e stosowana jest karta graficzna COLOR. 

     Aby korzysta

ć

 z monochromatycznej karty graficznej,  

     nale

Ŝ

y zmieni

ć

 0xB800 na 0xB000. 

  */ 
 
  int page; 
  int offset; 
  unsigned row; 
  unsigned col; 
  char far *ptr; 
 
  page = activepage(); 
  curr_cursor(&row,&col); 
 
  offset = page * 4000 + row * 160 + col * 2; 
 
  ptr = MK_FP(0xB800,offset); 
 
  while(*str) 
  { 
    *ptr++= *str++; 
    ptr++; 
  } 

background image

 
300 

Hack Wars. Tom 1. Na tropie hakerów 

300 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

void TRUESHADE(int lef, int top, int right, int bottom) 

  int n; 

 

  /* Cieniowanie */ 

 

  gettext(lef,top,right,bottom,sbuff); 
  for(n = 1; n < 2000; n+= 2) 
    sbuff[n] = 7; 
  puttext(lef,top,right,bottom,sbuff); 

 

void DBOX(int l, int t, int r, int b) 

  /* Rysowanie prostok

ą

ta podwójn

ą

 lini

ą

 */ 

 

  int n; 

 

  cursor(t,l); 
  print("É"); 
  for(n = 1; n < r - l; n++) 
  { 
    cursor(t,l + n); 
    print("Í"); 
  } 
  cursor(t,r); 
  print("»"); 

 

  for (n = t + 1; n < b; n++) 
  { 
    cursor(n,l); 
    print("ž"); 
    cursor(n,r); 
    print("ž"); 
  } 
  cursor(b,l); 
  print("E"); 
  for(n = 1; n < r - l; n++) 
  { 
    cursor(b,l+n); 
    print("Í"); 
  } 
  cursor(b,r); 
  print("È"); 

 

int INPUT(char *text,unsigned length) 

  /* Pobranie ci

ą

gu wprowadzonego przez u

Ŝ

ytkownika */ 

 

  unsigned key_pos; 
  int key; 
  unsigned start_row; 
  unsigned start_col; 
  unsigned end; 
  char temp[80]; 
  char *p; 

 

  curr_cursor(&start_row,&start_col); 

  key_pos = 0; 
  end = strlen(text); 
  for(;;) 
  { 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

301 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

301

 

    key = bioskey(0); 
    if ((key & 0xFF) == 0) 
    { 
      key = key >> 8; 
      if (key == 79) 
      { 
        while(key_pos < end) 
          key_pos++; 
        cursor(start_row,start_col + key_pos); 
      } 
      else 
      if (key == 71) 
      { 
        key_pos = 0; 
        cursor(start_row,start_col); 
      } 
      else 
      if ((key == 75) && (key_pos > 0)) 
      { 
        key_pos--; 
        cursor(start_row,start_col + key_pos); 
      } 
      else 
      if ((key == 77) && (key_pos < end)) 
      { 
        key_pos++; 
        cursor(start_row,start_col + key_pos); 
      } 
      else 
      if (key == 83) 
      { 
        p = text + key_pos; 
        while(*(p+1)) 
        { 
          *p = *(p+1); 
          p++; 
        } 
        *p = 32; 
        if (end > 0) 
          end--; 
        cursor(start_row,start_col); 
        cprintf(text); 
        cprintf(" "); 
        if ((key_pos > 0) && (key_pos == end)) 
          key_pos--; 
        cursor(start_row,start_col + key_pos); 
      } 
    } 
    else 
    { 
      key = key & 0xFF; 
      if (key == 13 || key == 27) 
        break; 
      else 
      if ((key == 8) && (key_pos > 0)) 
      { 
        end--; 
        key_pos--; 
        text[key_pos--] = '\0'; 
        strcpy(temp,text); 
        p = text + key_pos + 2; 

background image

 
302 

Hack Wars. Tom 1. Na tropie hakerów 

302 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

        strcat(temp,p); 
        strcpy(text,temp); 
        cursor(start_row,start_col); 
        cprintf("%-*.*s",length,length,text); 
        key_pos++; 
        cursor(start_row,start_col + key_pos); 
      } 
      else 
      if ((key > 31) && (key_pos < length)  &&  
         (start_col + key_pos < 80))  
      { 
        if (key_pos <= end) 
        { 
          p = text + key_pos; 
          memmove(p+1,p,end - key_pos); 
          if (end < length) 
            end++; 
          text[end] = '\0'; 
        } 
        text[key_pos++] = (char)key; 
        if (key_pos > end) 
        { 
          end++; 
          text[end] = '\0'; 
        } 
        cursor(start_row,start_col); 
        cprintf("%-*.*s",length,length,text); 
        cursor(start_row,start_col + key_pos); 
      } 
    } 
  } 
  text[end] = '\0'; 
  return key; 

 
void FATAL(char *error) 

  /* Wyst

ą

pił bł

ą

d krytyczny */ 

 
  printf("\nBŁ

Ą

D KRTYTYCZNY: %s",error); 

  exit(0); 

 
void OPENDATA() 

  /* Sprawd

ź

, czy istnieje plik danych; je

Ŝ

eli nie, utwórz*/ 

  /* je

Ŝ

eli tak, otwórz do odczytu-zapisu na ko

ń

cu pliku */ 

 
  handle = open(fpath,O_RDWR,S_IWRITE); 

  if (handle == -1) 
  { 
    handle = open(fpath,O_RDWR|O_CREAT,S_IWRITE); 
    if (handle == -1) 
      FATAL("Nie mo

Ŝ

na utworzy

ć

 pliku danych"); 

  } 
  /* Odczytaj pierwszy rekord */ 
  read(handle,&rec,recsize); 

 
void CLOSEDATA() 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

303 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

303

 

  close(handle); 

 
void GETDATA(int start) 

  /* Pobierz adres wprowadzany przez u

Ŝ

ytkownika */ 

 
  textcolor(BLACK); 
  textbackground(GREEN); 
  gotoxy(left,8); 
  print("Imi

ę

 i nazwisko "); 

  gotoxy(left,9); 
  print("Firma "); 
  gotoxy(left,10); 
  print("Adres "); 
  gotoxy(left,11); 
  print("Wojewodztwo "); 
  gotoxy(left,12); 
  print("Miasto "); 
  gotoxy(left,13); 
  print("Powiat "); 
  gotoxy(left,14); 
  print("Kod pocztowy "); 
  gotoxy(left,15); 
  print("Telefon "); 
  gotoxy(left,16); 
  print("Faks "); 
 
  switch(start) 
  { 
    case 0: gotoxy(left + 10,8); 
      if(INPUT(rec.name,30) == 27) 
        break; 
    case 1: gotoxy(left + 10,9); 
      if(INPUT(rec.company,30) == 27) 
        break; 
    case 2: gotoxy(left + 10,10); 
      if(INPUT(rec.address,30) == 27) 
        break; 
    case 3: gotoxy(left + 10,11); 
      if(INPUT(rec.area,30) == 27) 
        break; 
    case 4: gotoxy(left + 10,12); 
      if(INPUT(rec.town,30) == 27) 
        break; 
    case 5: gotoxy(left + 10,13); 
      if(INPUT(rec.county,30) == 27) 
        break; 
    case 6: gotoxy(left + 10,14); 
      if(INPUT(rec.post,12) == 27) 
        break; 
    case 7: gotoxy(left + 10,15); 
      if(INPUT(rec.telephone,15) == 27) 
        break; 
    case 8: gotoxy(left + 10,16); 
      INPUT(rec.fax,15); 
      break; 
  } 
  textcolor(WHITE); 
  textbackground(RED); 
  gotoxy(left + 23,21); 
  print("                     "); 

background image

 
304 

Hack Wars. Tom 1. Na tropie hakerów 

304 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

 
void DISPDATA() 

  /* Wy

ś

wietl dane adresowe */ 

  textcolor(BLACK); 
  textbackground(GREEN); 
  cursor(7,3); 
  cprintf("Imi

ę

 i nazwisko    %-30.30s",rec.name); 

  cursor(8,3); 
  cprintf("Firma   %-30.30s",rec.company); 
  cursor(9,3); 
  cprintf("Adres   %-30.30s",rec.address); 
  cursor(10,3); 
  cprintf("Województwo    %-30.30s",rec.area); 
  cursor(11,3); 
  cprintf("Miasto    %-30.30s",rec.town); 
  cursor(12,3); 
  cprintf("Powiat  %-30.30s",rec.county); 
  cursor(13,3); 
  cprintf("Kod pocztowy %-30.30s",rec.post); 
  cursor(14,3); 
  cprintf("Telefon %-30.30s",rec.telephone); 
  cursor(15,3); 
  cprintf("Faks     %-30.30s",rec.fax); 

 
int LOCATE(char *text) 

  int result; 
 
  do 
  { 
    /* Załaduj rekord do pami

ę

ci */ 

    result = read(handle,&rec,recsize); 
    if (result > 0) 
    { 
      /* Szukanie dopasowania w danym rekordzie */ 
      if (strstr(strupr(rec.name),text) != NULL) 
        return(1); 
      if (strstr(strupr(rec.company),text) != NULL) 
        return(1); 
      if (strstr(strupr(rec.address),text) != NULL) 
        return(1); 
      if (strstr(strupr(rec.area),text) != NULL) 
        return(1); 
      if (strstr(strupr(rec.town),text) != NULL) 
        return(1); 
      if (strstr(strupr(rec.county),text) != NULL) 
        return(1); 
      if (strstr(strupr(rec.post),text) != NULL) 
        return(1); 
      if (strstr(strupr(rec.telephone),text) != NULL) 
        return(1); 
      if (strstr(strupr(rec.fax),text) != NULL) 
        return(1); 
    } 
  } 
  while(result > 0); 
  return(0); 

 

void SEARCH() 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

305 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

305

 

  int result; 

 

  gotoxy(left,21); 
  textcolor(WHITE); 
  textbackground(RED); 
  cprintf("Wprowad

ź

 wzorzec wyszukiwania "); 

  strcpy(stext,""); 
  INPUT(stext,30); 
  if (*stext == 0) 
  { 
    gotoxy(left,21); 
    cprintf("%70c",32); 
    return; 
  } 
  gotoxy(left,21); 
  textcolor(WHITE); 
  textbackground(RED); 
  cprintf("Wyszukiwanie %s Prosz

ę

 czeka

ć

....",stext); 

  strupr(stext); 
  /* Zlokalizuj pocz

ą

tek pliku */ 

  lseek(handle,0,SEEK_SET); 
  result = LOCATE(stext); 
  if (result == 0) 
  { 
    gotoxy(left,21); 
    cprintf("%70c",32); 
    gotoxy(left + 27,21); 
    cprintf("Brak pasuj

ą

cych rekordów"); 

    gotoxy(left + 24,22); 
    cprintf("Wci

ś

nij ENTER, aby kontynuowa

ć

"); 

    bioskey(0); 
    gotoxy(left,21); 
    cprintf("%70c",32); 
    gotoxy(left,22); 
    cprintf("%70c",32); 
  } 
  else 
  { 
    lseek(handle,0 - recsize,SEEK_CUR); 
    read(handle,&rec,recsize); 
    DISPDATA(); 
  } 
  textcolor(WHITE); 
  textbackground(RED); 
  gotoxy(left,21); 
  cprintf("%70c",32); 
  textcolor(BLACK); 
  textbackground(GREEN); 

 
void CONTINUE() 

  int result; 
  long curpos; 
 
  curpos = tell(handle) - recsize; 
 
  result = LOCATE(stext); 
  textcolor(WHITE); 
  textbackground(RED); 
  if (result == 0) 
  { 

background image

 
306 

Hack Wars. Tom 1. Na tropie hakerów 

306 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

    gotoxy(left + 24,21); 
    cprintf("Brak dalszych rekordów"); 
    gotoxy(left + 24,22); 
    cprintf("Wci

ś

nij ENTER, aby kontynuowa

ć

"); 

    bioskey(0); 
    gotoxy(left,21); 
    cprintf("%70c",32); 
    gotoxy(left,22); 
    cprintf("%70c",32); 
    lseek(handle,curpos,SEEK_SET); 
    read(handle,&rec,recsize); 
    DISPDATA(); 
  } 
  else 
  { 
    lseek(handle,0 - recsize,SEEK_CUR); 
    read(handle,&rec,recsize); 
    DISPDATA(); 
  } 
  textcolor(WHITE); 
  textbackground(RED); 
  gotoxy(left,21); 
  cprintf("%70c",32); 
  gotoxy(left,22); 
  cprintf("                    "); 
  textcolor(BLACK); 
  textbackground(GREEN); 

 
void PRINT_MULTI() 

  data buffer; 
  char destination[60]; 
  char text[5]; 
  int result; 
  int ok; 
  int ok2; 
  int blanks; 
  int total_lines; 
  char *p; 
  FILE *fp; 
 
  textcolor(WHITE); 
  textbackground(RED); 
  gotoxy(left + 23,21); 
  cprintf("Podaj kryterium wyboru"); 
 
  /* Usu

ń

 wcze

ś

niejsze dane rekordu */ 

  memset(&rec,0,recsize); 
 
  DISPDATA(); 
  GETDATA(0); 
 
  textcolor(WHITE); 
  textbackground(RED); 
  gotoxy(left,21); 
  cprintf("Podaj, gdzie przesła

ć

 PRN"); 

  strcpy(destination,"PRN"); 
  gotoxy(left,22); 
  cprintf("Podaj długo

ść

 adresu w wierszach 18"); 

  strcpy(text,"18"); 
  gotoxy(left + 25,21); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

307 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

307

 

  INPUT(destination,40); 
  gotoxy(left +30,22); 
  INPUT(text,2); 
  gotoxy(left,21); 
  cprintf("%72c",32); 
  gotoxy(left,22); 
  cprintf("%72c",32); 
 
  total_lines = atoi(text) - 6; 
  if (total_lines < 0) 
    total_lines = 0; 
 
  fp = fopen(destination,"w+"); 
  if (fp == NULL) 
  { 
    gotoxy(left,21); 
    cprintf("Nie mo

Ŝ

na drukowa

ć

 do %s",destination); 

    gotoxy(left,22); 
    cprintf("Wci

ś

nij ENTER, aby kontynuowa

ć

"); 

    bioskey(0); 
    gotoxy(left,21); 
    cprintf("%78c",32); 
    gotoxy(left,22); 
    cprintf("            "); 
  } 

  /* Zlokalizuj pocz

ą

tek pliku */ 

  lseek(handle,0,SEEK_SET); 

  do 
  { 
    /* Załaduj rekord do pami

ę

ci */ 

    result = read(handle,&buffer,recsize); 
    if (result > 0) 
    { 
      ok = 1; 
      /* Szukaj danych w rekordzie */ 
      if (*rec.name) 
        if (stricmp(buffer.name,rec.name)) 
          ok = 0; 
      if (*rec.company) 
        if (stricmp(buffer.company,rec.company)) 
          ok = 0; 
      if (*rec.address) 
        if (stricmp(buffer.address,rec.address)) 
          ok = 0; 
      if (*rec.area) 
        if (stricmp(buffer.area,rec.area)) 
          ok = 0; 
      if (*rec.town) 
        if (stricmp(buffer.town,rec.town)) 
          ok = 0; 
      if (*rec.county) 
        if (stricmp(buffer.county,rec.county)) 
          ok = 0; 
      if (*rec.post) 
        if (stricmp(buffer.post,rec.post)) 
        ok = 0; 
      if (*rec.telephone) 
        if (stricmp(buffer.telephone,rec.telephone)) 
          ok = 0; 
      if (*rec.fax) 

background image

 
308 

Hack Wars. Tom 1. Na tropie hakerów 

308 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

        if (stricmp(buffer.fax,rec.fax)) 
          ok = 0; 
      if (ok) 
      { 
        blanks = total_lines; 
        p = buffer.name; 
        ok2 = 0; 
        while(*p) 
        { 
          if (*p != 32) 
          { 
            ok2 = 1; 
            break; 
          } 
          p++; 
        } 
        if (!ok2) 
          blanks++; 
        else 
          fprintf(fp,"%s\n",buffer.name); 
        p = buffer.company; 
        ok2 = 0; 
        while(*p) 
        { 
          if (*p != 32) 
          { 
            ok2 = 1; 
            break; 
          } 
          p++; 
        } 
        if (!ok2) 
          blanks++; 
        else 
          fprintf(fp,"%s\n",buffer.company); 
        p = buffer.address; 
        ok2 = 0; 
 
        while(*p) 
        { 
          if (*p != 32) 
          { 
            ok2 = 1; 
            break; 
          } 
          p++; 
        } 
        if (!ok2) 
          blanks++; 
        else 
          fprintf(fp,"%s\n",buffer.address); 
        p = buffer.area; 
        ok2 = 0; 
        while(*p) 
        { 
          if (*p != 32) 
          { 
            ok2 = 1; 
            break; 
          } 
          p++; 
        } 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

309 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

309

 

        if (!ok2) 
          blanks++; 
        else 
          fprintf(fp,"%s\n",buffer.area); 
        p = buffer.town; 
        ok2 = 0; 
        while(*p) 
        { 
          if (*p != 32) 
          { 
            ok2 = 1; 
            break; 
          } 
          p++; 
        } 
        if (!ok2) 
          blanks++; 
        else 
          fprintf(fp,"%s\n",buffer.town); 
        p = buffer.county; 
        ok2 = 0; 
 
        while(*p) 
        { 
          if (*p != 32) 
          { 
            ok2 = 1; 
            break; 
          } 
          p++; 
        } 
        if (!ok2) 
          blanks++; 
        else 
          fprintf(fp,"%s\n",buffer.county); 
        p = buffer.post; 
        ok2 = 0; 
        while(*p) 
        { 
          if (*p != 32) 
          { 
            ok2 = 1; 
            break; 
          } 
          p++; 
        } 
        if (!ok2) 
          blanks++; 
        else 
          fprintf(fp,"%s\n",buffer.post); 
        while(blanks) 
        { 
          fprintf(fp,"\n"); 
          blanks--; 
        } 
      } 
    } 
  } 
  while(result > 0); 
  fclose(fp); 
  lseek(handle,0,SEEK_SET); 
  read(handle,&rec,recsize); 
  DISPDATA(); 

background image

 
310 

Hack Wars. Tom 1. Na tropie hakerów 

310 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 


 
void EXPORT_MULTI() 

  data buffer; 
  char destination[60]; 
  int result; 
  int ok; 
  FILE *fp; 
 
  textcolor(WHITE); 
  textbackground(RED); 
  gotoxy(left + 23,21); 
  cprintf("Wprowad

ź

 kryterium wyboru"); 

  /* Usu

ń

 wcze

ś

niejsze dane rekordu */ 

  memset(&rec,0,recsize); 
 
  DISPDATA(); 
  GETDATA(0); 
 
  textcolor(WHITE); 
  textbackground(RED); 
  gotoxy(left,21); 
  cprintf("Wprowad

ź

 nazw

ę

 pliku address.txt"); 

  strcpy(destination,"address.txt"); 
  gotoxy(left + 18,21); 
  INPUT(destination,59); 
  gotoxy(left,21); 
  cprintf("%70c",32); 
 
  fp = fopen(destination,"w+"); 
  if (fp == NULL) 
  { 
    gotoxy(left,21); 
    cprintf("Nie mo

Ŝ

na drukowa

ć

 do %s",destination); 

    gotoxy(left,22); 
    cprintf("Wci

ś

nij ENTER, aby kontynuowa

ć

"); 

    bioskey(0); 
    gotoxy(left,21); 
    cprintf("%78c",32); 
    gotoxy(left,22); 
    cprintf("            "); 
  } 
  /* Zlokalizuj pocz

ą

tek pliku */ 

  lseek(handle,0,SEEK_SET); 
 
  do 
  { 
    /* Załaduj rekord do pami

ę

ci */ 

    result = read(handle,&buffer,recsize); 
    if (result > 0) 
    { 
      ok = 1; 
      /* Przeszukaj rekord */ 
      if (*rec.name) 
        if (stricmp(buffer.name,rec.name)) 
          ok = 0; 
      if (*rec.company) 
        if (stricmp(buffer.company,rec.company)) 
          ok = 0; 
      if (*rec.address) 
        if (stricmp(buffer.address,rec.address)) 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

311 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

311

 

          ok = 0; 
      if (*rec.area) 
        if (stricmp(buffer.area,rec.area)) 
          ok = 0; 
      if (*rec.town) 
        if (stricmp(buffer.town,rec.town)) 
          ok = 0; 
      if (*rec.county) 
        if (stricmp(buffer.county,rec.county)) 
          ok = 0; 
      if (*rec.post) 
        if (stricmp(buffer.post,rec.post)) 
        ok = 0; 
      if (*rec.telephone) 
        if (stricmp(buffer.telephone,rec.telephone)) 
          ok = 0; 
      if (*rec.fax) 
        if (stricmp(buffer.fax,rec.fax)) 
          ok = 0; 
      if (ok) 
      { 
        fprintf(fp,"\"%s\",",buffer.name); 
        fprintf(fp,"\"%s\",",buffer.company); 
        fprintf(fp,"\"%s\",",buffer.address); 
        fprintf(fp,"\"%s\",",buffer.area); 
        fprintf(fp,"\"%s\",",buffer.town); 
        fprintf(fp,"\"%s\",",buffer.county); 
        fprintf(fp,"\"%s\",",buffer.post); 
        fprintf(fp,"\"%s\",",buffer.telephone); 
        fprintf(fp,"\"%s\"\n",buffer.fax); 
 
      } 
    } 
  } 
 
  while(result > 0); 
  fclose(fp); 
  lseek(handle,0,SEEK_SET); 
  read(handle,&rec,recsize); 
  DISPDATA(); 

 
void MENU() 

  int option; 
  long result; 
  long end; 
  int new; 
 
  do 
  { 
    cursor(21,26); 
    print("Wybierz polecenie (F2 - F10)"); 
    cursor(7,52); 
    print("F2 Nast

ę

pny rekord"); 

    cursor(8,52); 
    print("F3 Poprzedni rekord"); 
    cursor(9,52); 
    print("F4 Popraw rekord"); 
    cursor(10,52); 
    print("F5 Dodaj nowy rekord"); 
    cursor(11,52); 

background image

 
312 

Hack Wars. Tom 1. Na tropie hakerów 

312 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

    print("F6 Szukaj"); 
    cursor(12,52); 
    print("F7 Szukaj dalej"); 
    cursor(13,52); 
    print("F8 Drukuj etykiety adresowe"); 
    cursor(14,52); 
    print("F9 Eksportuj rekordy"); 
    cursor(15,52); 
    print("F10 Wyj

ś

cie"); 

    MOUSE_CURSOR(1); 
    option = GETOPT(); 
    MOUSE_CURSOR(0); 
 
    switch(option) 
    { 
      case 0 : /* Nast

ę

pny rekord */ 

         result = read(handle,&rec,recsize); 
         if (!result) 
         { 
          lseek(handle,0,SEEK_SET); 
           result = read(handle,&rec,recsize); 
         } 
         DISPDATA(); 
         break; 
 
      case 1 : /* Poprzedni rekord */ 
          result = lseek(handle,0 - recsize * 2,SEEK_CUR); 
          if (result <= -1) 
            lseek(handle,0 - recsize,SEEK_END); 
          result = read(handle,&rec,recsize); 
          DISPDATA(); 
          break; 
 
      case 2 : /* Popraw bie

Ŝą

cy rekord */ 

           new = 1; 
           if (*rec.name) 
            new = 0; 
           else 
           if (*rec.company) 
            new = 0; 
           else 
           if (*rec.address) 
            new = 0; 
           else 
           if (*rec.area) 
            new = 0; 
           else 
           if (*rec.town) 
            new = 0; 
           else 
           if (*rec.county) 
            new = 0; 
           else 
           if (*rec.post) 
            new = 0; 
           else 
           if (*rec.telephone) 
            new = 0; 
         else 
         if (*rec.fax) 
          new = 0; 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

313 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

313

 

         result = tell(handle); 
         lseek(handle,0,SEEK_END); 
         end = tell(handle); 
 
         /* Powrót do pozycji pocz

ą

tkowej */ 

         lseek(handle,result,SEEK_SET); 
 
           /* Je

Ŝ

eli nie EOF && !new przesu

ń

 o jeden rekord */ 

           if (result != end || ! new) 
            result = lseek(handle,0 - recsize,SEEK_CUR); 
           result = tell(handle); 
           gotoxy(left + 22,21); 
           print(" Wprowad

ź

 dane adresu  "); 

           GETDATA(0); 
           if (*rec.name || *rec.company) 
            result =  write(handle,&rec,recsize); 
           break; 
 
      case 3 : /* Dodaj rekord */ 
           lseek(handle,0,SEEK_END); 
           memset(&rec,0,recsize); 
           DISPDATA(); 
 
      case 4 : /* Szukaj */ 
           gotoxy(left + 22,21); 
           print("            "); 
           SEARCH(); 
           break; 
 
      case 5 : /* Szukaj dalej */ 
           gotoxy(left + 22,21); 
           print("            "); 
           CONTINUE(); 
           break; 
 
      case 6 : /* Drukuj */ 
           gotoxy(left + 22,21); 
           print("            "); 
           PRINT_MULTI(); 
           break; 
 
      case 7 : /* Eksportuj */ 
           gotoxy(left + 22,21); 
           print("            "); 
           EXPORT_MULTI(); 
           break; 
 
      case 8 : /* Wyj

ś

cie */ 

           break; 
 
      default: /* Popraw rekord bie

Ŝą

cy */ 

           new = 1; 
           if (*rec.name) 
            new = 0; 
           else 
           if (*rec.company) 
            new = 0; 
           else 
           if (*rec.address) 
            new = 0; 
           else 

background image

 
314 

Hack Wars. Tom 1. Na tropie hakerów 

314 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc

 

           if (*rec.area) 
            new = 0; 
           else 
           if (*rec.town) 
            new = 0; 
           else 
           if (*rec.county) 
            new = 0; 
           else 
           if (*rec.post) 
            new = 0; 
           else 
           if (*rec.telephone) 
            new = 0; 
           else 
           if (*rec.fax) 
            new = 0; 
           result = tell(handle); 
           lseek(handle,0,SEEK_END); 
           end = tell(handle); 
 
           /* Powrót do pozycji pocz

ą

tkowej */ 

           lseek(handle,result,SEEK_SET); 
 
           /* Je

Ŝ

eli nie EOF && !new przesu

ń

 o jeden rekord */ 

           if (result != end || ! new) 
            result = lseek(handle,0 - recsize,SEEK_CUR); 
           result = tell(handle); 
           gotoxy(left + 22,21); 
           print(" Wprowad

ź

 dane adresowe  "); 

           GETDATA(option - 17); 
           if (*rec.name || *rec.company) 
            result = write(handle,&rec,recsize); 
           option = -1; 
           break; 
 
    } 
  } 
 
  while(option != 8); 

 
void exec() 

  gettext(1,1,80,25,scr); 
  setvideo(3); 
  textbackground(WHITE); 
  textcolor(BLACK); 
  clrscr(); 
  recsize = sizeof(data); 
 
  OPENDATA(); 
 
  TRUESHADE(left,3,79,5); 
  window(left - 2,2 ,78, 4); 
  textcolor(YELLOW); 
  textbackground(MAGENTA); 
  clrscr(); 
  DBOX(left - 3, 1, 77, 3); 
  gotoxy(3,2); 

background image

 
Rozdział 6. 

 Podstawy programowania dla hakerów 

315 

D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc 

315

 

  print("Servile Software       PC ADDRESS BOOK 5.2        (c) 
1994"); 
 
  TRUESHADE(left,8,left + 43,18); 
  window(left - 2,7 , left + 42, 17); 
  textcolor(BLACK); 
  textbackground(GREEN); 
  clrscr(); 
  DBOX(left - 3, 6, left + 41, 16); 
 
  TRUESHADE(left + 48,8,79,18); 
  window(left + 46, 7 , 78, 17); 
  textbackground(BLUE); 
  textcolor(YELLOW); 
  clrscr(); 
  DBOX(left + 45,6,77,16); 
 
  TRUESHADE(left ,21,79,24); 
  window(left - 2, 20 , 78, 23); 
  textbackground(RED); 
  textcolor(WHITE); 
  clrscr(); 
  DBOX(left - 3,19,77,22); 
 
  window(1,1,80,25); 
  textcolor(BLACK); 
  textbackground(GREEN); 
  DISPDATA(); 
 
  MENU(); 
 
  CLOSEDATA(); 
  puttext(1,1,80,25,scr); 
  return;