Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2004.08.08;
Скачать: CL | DM;

Вниз

ПЕрехват АПИ   Найти похожие ветки 

 
juiceman   (2004-06-27 23:20) [0]

Перехват АПИ-вызовов на Delphi. Существует ли исходник кроме как Ketmara ? На сях ведь их куча, а на паскале вообще никогда не видел. Delphi-программисты более ленивые ? )))


 
Игорь Шевченко ©   (2004-06-27 23:44) [1]


> Существует ли исходник кроме как Ketmara ?


Существует. Об исходнике Ketmar"а слышу впервые. Есть у Digitman"а, есть у меня.


 
Ihor Osov'yak ©   (2004-06-28 00:12) [2]

Ну, года два-три назад я баловался.. Даже чего-то перехватывало. Даже то, что нужно. В принцыпе, делалось не с нуля, отыскал какой-то не совсем рабочий прообраз на безымяном китайском сайте..


 
KilkennyCat ©   (2004-06-28 00:31) [3]


> Ihor Osov"yak ©   (28.06.04 00:12) [2]


Вау, китайские иероглифы в комментариях! :)


 
juiceman   (2004-06-28 00:35) [4]

2Игорь Шевченко ©
У меня тут все проблема с самими перехватом, внедрение - пройденый этап. Прочитал у Рихтера что заменяя байты на входе функции могут возникнуть проблемы, через отладку (это и есть как я выразился исходник Ketmara : "Использование Debug API: пример перехвата вызовов функций Win32 API  ") тоже нет желания делать, зачем загонять процесс под отладчик? Так вот, Рихтер говорит что наилучший способ это через таблицу импорта. Не получается.
А каким способом Вы эту часть реализовали?


 
Ihor Osov'yak ©   (2004-06-28 01:08) [5]

2 [3] KilkennyCat ©   (28.06.04 00:31)

да нет, это только сайт китайский, исходник - непонятно чей..

2 [4] juiceman   (28.06.04 00:35)

Извинит меня Игорь наверное, что я перед отца в пекло - примерно так и делалось.  Бросаю два ключевых модуля, как есть, наверное можно разобраться, за качество кода сорри, чем багаты - тем и рады (первый модулек - декларации - практически в первозданном виде), второй -я там основательно погулял. Также смутно припоминаю, что права на запись памяти -
GetRight - не очень то удачно сделано, но сейчас уже вспоминать неохота.
Во втором модуля оставлен перехват только нескольких функций, остальные - вырезаны, а то код слишком большой. Модуль после вырезания - компилируется.

WrTm - отладочный вывод, glob_e0capt - модуль, в котором декларировано несколько несущественніх глобальных переменных..

Внедрение - при помощи хука, это за пределами этих модульков

unit PEStuff;

interface
uses Windows;

type
 PImageDosHeader = ^TImageDosHeader;
 _IMAGE_DOS_HEADER = packed record { DOS .EXE  header           }
   e_magic: Word; { Magic umber  }
   e_cblp: Word; { Bytes on last page of file      }
   e_cp: Word; { Pages in file                   }
   e_crlc: Word; { Relocations                      }
   e_cparhdr: Word; { Size of header in paragraphs     }
   e_minalloc: Word; { Minimum extra paragraphs needed  }
   e_maxalloc: Word; { Maximum extra paragraphs needed  }
   e_ss: Word; { Initial (relative) SS value      }
   e_sp: Word; { Initial SP value                 }
   e_csum: Word; { Checksum                         }
   e_ip: Word; { Initial IP value                 }
   e_cs: Word; { Initial (relative) CS value      }
   e_lfarlc: Word; { File address of relocation table }
   e_ovno: Word; { Overlay number                   }
   e_res: array[0..3] of Word; { Reserved words                   }
   e_oemid: Word; { OEM identifier (for e_oeminfo)   }
   e_oeminfo: Word; { OEM information; e_oemid specific}
   e_res2: array[0..9] of Word; { Reserved words                   }
   _lfanew: LongInt; { File address of new exe header   }
 end;
 TImageDosHeader = _IMAGE_DOS_HEADER;

 PIMAGE_FILE_HEADER = ^IMAGE_FILE_HEADER;
 IMAGE_FILE_HEADER = packed record
   Machine: WORD;
   NumberOfSections: WORD;
   TimeDateStamp: DWORD;
   PointerToSymbolTable: DWORD;
   NumberOfSymbols: DWORD;
   SizeOfOptionalHeader: WORD;
   Characteristics: WORD;
 end;

 PIMAGE_DATA_DIRECTORY = ^IMAGE_DATA_DIRECTORY;
 IMAGE_DATA_DIRECTORY = packed record
   VirtualAddress: DWORD;
   Size: DWORD;
 end;

 PIMAGE_SECTION_HEADER = ^IMAGE_SECTION_HEADER;
 IMAGE_SECTION_HEADER = packed record
   Name: packed array[0..IMAGE_SIZEOF_SHORT_NAME - 1] of
   Char;
   VirtualSize: DWORD; // or VirtualSize (union);
   VirtualAddress: DWORD;
   SizeOfRawData: DWORD;
   PointerToRawData: DWORD;
   PointerToRelocations: DWORD;
   PointerToLinenumbers: DWORD;
   NumberOfRelocations: WORD;
   NumberOfLinenumbers: WORD;
   Characteristics: DWORD;
 end;

 PIMAGE_OPTIONAL_HEADER = ^IMAGE_OPTIONAL_HEADER;
 IMAGE_OPTIONAL_HEADER = packed record
   { Standard fields. }
   Magic: WORD;
   MajorLinkerVersion: Byte;
   MinorLinkerVersion: Byte;
   SizeOfCode: DWORD;
   SizeOfInitializedData: DWORD;
   SizeOfUninitializedData: DWORD;
   AddressOfEntryPoint: DWORD;
   BaseOfCode: DWORD;
   BaseOfData: DWORD;
   { NT additional fields. }
   ImageBase: DWORD;
   SectionAlignment: DWORD;
   FileAlignment: DWORD;
   MajorOperatingSystemVersion: WORD;
   MinorOperatingSystemVersion: WORD;
   MajorImageVersion: WORD;
   MinorImageVersion: WORD;
   MajorSubsystemVersion: WORD;
   MinorSubsystemVersion: WORD;
   Reserved1: DWORD;
   SizeOfImage: DWORD;
   SizeOfHeaders: DWORD;
   CheckSum: DWORD;
   Subsystem: WORD;
   DllCharacteristics: WORD;
   SizeOfStackReserve: DWORD;
   SizeOfStackCommit: DWORD;
   SizeOfHeapReserve: DWORD;
   SizeOfHeapCommit: DWORD;
   LoaderFlags: DWORD;
   NumberOfRvaAndSizes: DWORD;
   DataDirectory: packed array
   [0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of IMAGE_DATA_DIRECTORY;
   Sections: packed array[0..9999] of IMAGE_SECTION_HEADER;
 end;

 PIMAGE_NT_HEADERS = ^IMAGE_NT_HEADERS;
 IMAGE_NT_HEADERS = packed record
   Signature: DWORD;
   FileHeader: IMAGE_FILE_HEADER;
   OptionalHeader: IMAGE_OPTIONAL_HEADER;
 end;
 PImageNtHeaders = PIMAGE_NT_HEADERS;
 TImageNtHeaders = IMAGE_NT_HEADERS;

 {  PIMAGE_IMPORT_DESCRIPTOR = ^IMAGE_IMPORT_DESCRIPTOR;
   IMAGE_IMPORT_DESCRIPTOR = packed record
     Characteristics: DWORD; // or original first thunk
                             // 0 for terminating null import descriptor
                             // RVA to original unbound IAT  TimeDateStamp: DWORD;
                             // 0 if not bound,
                             // -1 if bound, and real date\time stamp
                             //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                             // O.W. date/time stamp of DLL bound to (Old BIND)
     Name: DWORD;
     FirstThunk: DWORD;      // PIMAGE_THUNK_DATA // RVA to IAT (if bound this IAT has actual addresses)
     ForwarderChain: DWORD;  // -1 if no forwarders
   end;
   TImageImportDescriptor = IMAGE_IMPORT_DESCRIPTOR;
   PImageImportDescriptor = PIMAGE_IMPORT_DESCRIPTOR;}

 PIMAGE_IMPORT_BY_NAME = ^IMAGE_IMPORT_BY_NAME;
 IMAGE_IMPORT_BY_NAME = record
   Hint: Word;
   Name: array[0..0] of Char;
 end;

 PIMAGE_THUNK_DATA = ^IMAGE_THUNK_DATA;
 IMAGE_THUNK_DATA = record
   Whatever: DWORD;
 end;

 PImage_Import_Entry = ^Image_Import_Entry;
 Image_Import_Entry = record
   Characteristics: DWORD;
   TimeDateStamp: DWORD;
   MajorVersion: Word;
   MinorVersion: Word;
   Name: DWORD;
   LookupTable: DWORD;
 end;

const
 IMAGE_DOS_SIGNATURE = $5A4D; // MZ
 IMAGE_OS2_SIGNATURE = $454E; // NE
 IMAGE_OS2_SIGNATURE_LE = $454C; // LE
 IMAGE_VXD_SIGNATURE = $454C; // LE
 IMAGE_NT_SIGNATURE = $00004550; // PE00

implementation

end.


 
Ihor Osov'yak ©   (2004-06-28 01:12) [6]

unit HookTextUnit;

interface
uses Windows, SysUtils, Classes, PEStuff;

type
 TConvertTextFunction = function(text: string): string;
 TTextOutA = function(hdc: HDC; x, y: Integer; text: PAnsiChar; len: Integer): BOOL; stdcall;
 TTextOutW = function(hdc: HDC; x, y: Integer; text: PWideChar; len: Integer): BOOL; stdcall;
 TExtTextOutA = function(hdc: HDC; x, y: Integer; Options: DWORD; Clip:
   PRect; text: PAnsiChar; len: Integer; dx: PInteger): BOOL; stdcall;
 TExtTextOutW = function(hdc: HDC; x, y: Integer; Options: DWORD;
   Clip: PRect; text: PWideChar; len: Integer; dx: PInteger): BOOL; stdcall;
 TDrawTextA = function(hdc: HDC; text: PAnsiChar; len: Integer; rect: PRect; Format: DWORD): Integer; stdcall;
 TDrawTextW = function(hdc: HDC; text: PWideChar; len: Integer; rect: PRect; Format: DWORD): Integer; stdcall;
 TDrawTextExA = function(hdc: HDC; text: PAnsiChar; len: Integer; rect: PRect; Format: DWORD;
   DTParams: PDrawTextParams): Integer; stdcall;
 TDrawTextExW = function(hdc: HDC; text: PWideChar; len: Integer; rect: PRect;
   Format: DWORD; DTParams: PDrawTextParams): Integer; stdcall;

 PPointer = ^Pointer;

 TImportCode = packed record
   JumpInstruction: Word; // should be $25FF
   AddressOfPointerToFunction: PPointer;
 end;
 PImportCode = ^TImportCode;

procedure HookTextOut(aConvertFunction: TConvertTextFunction);
procedure UnhookTextOut;

implementation

uses glob_e0capt, messages;

var
 ConvertTextFunction: TConvertTextFunction = nil;
 OldTextOutA: TTextOutA = nil;
 OldTextOutW: TTextOutW = nil;
 OldExtTextOutA: TExtTextOutA = nil;
 OldExtTextOutW: TExtTextOutW = nil;
 OldDrawTextA: TDrawTextA = nil;
 OldDrawTextW: TDrawTextW = nil;
 OldDrawTextExA: TDrawTextExA = nil;
 OldDrawTextExW: TDrawTextExW = nil;

function StrLenW(s: PWideChar): Integer;
var
 i: Integer;
begin
 if s = nil then
 begin
   Result := 0;
   exit;
 end;
 i := 0;
 try
   while (s[i] <> #0) do
     inc(i);
 except
 end;
 Result := i;
end;

var
 sumStr: string;
 countStep: integer;

procedure SendCapturedText(Y: integer; aStr: string);
var
 cd: TCopyDataStruct;

begin
 if sumStr <> "" then
   sumStr := sumStr + ";";
 inc(countStep);
 sumStr := sumStr + aStr;
 if countStep < 4 then
   exit;
 cd.dwData := Y;
 cd.cbData := Length(sumStr) + 1;
 cd.lpData := PChar(sumStr);
 SendMessage(toolsHandle, WM_COPYDATA,
   0,
   LParam(@cd));
 countStep := 0;
 sumStr := "";

end;

function NewTextOutA(hdc: HDC; x, y: Integer; text: PAnsiChar; len:
 Integer): BOOL; stdcall;
var
 s: string;
begin
 if enCapture then
   if WindowFromDC(hdc) = handleForCapture then
   begin
     try
       if len <= 0 then
         s := text
       else
       begin
         SetLength(s, len);
         FillChar(s[1], len + 1, 0);
         Move(text^, s[1], len);
       end;
       if x = 0 then
         SendCapturedText(Y, s);
       WrTm("hdc= " + IntToHex(hdc, 8) + "  x= " + IntToStr(x) + " y= " + IntToStr(y) + " text = " + s);
       SetLength(s, 0);
     except

     end;
   end;

 if @OldTextOutA <> nil then
   Result := OldTextOutA(hdc, x, y, text, len)
 else
   Result := false;

end;


далее пропускаю код остальных перехватчиков и далее снова содержательная часть


 
Ihor Osov'yak ©   (2004-06-28 01:13) [7]

function NewDrawTextExW(hdc: HDC; text: PWideChar; len: Integer; rect:
 PRect;
 Format: DWORD; DTParams: PDrawTextParams): Integer; stdcall;
var
 s: WideString;
begin
 Result := 0;
 exit;

 try
   if Len < 0 then
     Len := strlenW(text);
   if Len > 0 then
   begin
     SetLength(s, len);
     FillChar(s[1], len * 2 + 2, 0);
     Move(text^, s[1], len * 2);
     if @ConvertTextFunction <> nil then
       s := ConvertTextFunction(s);
     if @OldDrawTextExW <> nil then

       Result := OldDrawTextExW(hdc, PWideChar(s), length(s), rect, Format, DTParams)
     else
       Result := 0;
   end
   else
     Result := OldDrawTextExW(hdc, text, 0, rect, Format, DTParams);
 except
   Result := 0;
 end;
end;

function PointerToFunctionAddress(Code: Pointer): PPointer;
var
 func: PImportCode;
begin

 Result := nil;
 if Code = nil then
   exit;
 try
   func := code;
   if (func.JumpInstruction = $25FF) then
   begin
     Result := func.AddressOfPointerToFunction;
   end;
 except
   Result := nil;
 end;
end;

function FinalFunctionAddress(Code: Pointer): Pointer;
var
 func: PImportCode;
begin

 Result := Code;
 if Code = nil then
   exit;
 try
   func := code;
   if (func.JumpInstruction = $25FF) then
   begin
     Result := func.AddressOfPointerToFunction^;
   end;
 except
   Result := nil;
 end;
end;

procedure GetRight;
var
 si: TSYSTEMINFO;
 mi: TMEMORYBASICINFORMATION;
 p: cardinal;
 dw: dword;
 //cb:Tcb;
begin

 GetSystemInfo(si);
 p := cardinal(si.lpMinimumApplicationAddress);

 while (p < cardinal(si.lpMaximumApplicationAddress)) (*and (HeapsCount<MAX_HEAPS)*) do
 begin
   VirtualQueryEx(GetCurrentProcess, Pointer(p), mi, sizeof(mi));
   //Heaps[HeapsCount].Offset := p;
   //Heaps[HeapsCount].Size := mi.RegionSize;
   //Heaps[HeapsCount].Valid := (mi.State = MEM_COMMIT);
   //Data:=Pointer(HeapsCount);
   VirtualProtectEx(GetCurrentProcess, pointer(p), mi.RegionSize,
     PAGE_EXECUTE_READWRITE, @dw);

   Inc(p, mi.RegionSize);
   //inc(HeapsCount);
 end;
end;
var
 level: integer;

function PatchAddress(OldFunc, NewFunc: Pointer): Integer;
var
 BeenDone: TList;

 function PatchAddressInModule(hModule: THandle;
   OldFunc, NewFunc: Pointer): Integer;
 var
   Dos: PImageDosHeader;
   NT: PImageNTHeaders;
   ImportDesc: PImage_Import_Entry;
   rva: DWORD;
   Func: PPointer;
   DLL: string;
   f: Pointer;
   written: DWORD;
   sb: string;
   i: integer;
 begin
   inc(level);
   sb := " ";
   for i := 1 to level * 3 do
     sb := sb + " ";
   Result := 0;
   Dos := Pointer(hModule);

   //Wr("");
   //WrTm(sb+"cp745: start tru");

   if BeenDone.IndexOf(Dos) >= 0 then
   begin
     dec(level);
     exit;
   end;
   //WrTm(sb+"cp761");
   BeenDone.Add(Dos);
   OldFunc := FinalFunctionAddress(OldFunc);
   if IsBadReadPtr(Dos, SizeOf(TImageDosHeader)) then
   begin
     dec(level);
     exit;
   end;
   //WrTm(sb+"cp768");
   if Dos.e_magic <> IMAGE_DOS_SIGNATURE then
   begin
     dec(level);
     exit;
   end;
   //WrTm(sb+"cp773");
   NT := Pointer(Integer(Dos) + dos._lfanew);
   //  if IsBadReadPtr(NT,SizeOf(TImageNtHeaders)) then exit;

   RVA := NT^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

   if RVA = 0 then
     exit;
   //WrTm(sb+"cp780 before loop1");
   ImportDesc := pointer(dword(Dos) + RVA);
   while (ImportDesc^.Name <> 0) do
   begin
     DLL := PChar(dword(Dos) + ImportDesc^.Name);
     //WrTm(Dll);
     //WrTm(sb+"cp785 tru pach in "+Dll);

     PatchAddressInModule(GetModuleHandle(PChar(DLL)), OldFunc, NewFunc);
     //WrTm(sb+"cp788 after tru "+Dll);
     //WrTm(sb+"cp789 before loop2");
     Func := Pointer(dword(DOS) + ImportDesc.LookupTable);
     while Func^ <> nil do
     begin
       //WrTm(sb+"cp792 Func="+IntToHex(integer(Func),8)+" "+IntToHex(integer(Func^),8));
       f := FinalFunctionAddress(Func^);
       //WrTm(sb+"cp794 f   ="+IntToHex(integer(f),8));
       //WrTm(sb+"cp795 oldf   ="+IntToHex(integer(OldFunc),8));
       if f = OldFunc then
       begin
         //WrTm(sb+"cp794");
         //WrTm(sb+Dll);
         WriteProcessMemory(GetCurrentProcess, Func, @NewFunc, 4, written);
         //Move(NewFunc,Func^,4);
         //asm
         //  int 3
         //end;
         //integer(Func^):=integer(NewFunc);
         if Written > 0 then
         begin
           Inc(Result);
           //WrTm(sb+"res<>0");
         end
         else
         begin
           //i:=Getlasterror;
           //WrTm(sb+"res=0 err="+IntToHex(i,8));
         end
       end;
       Inc(Func);
     end;
     //WrTm(sb+"cp805  after loop2");
     Inc(ImportDesc);
   end;
   //WrTm(sb+"cp808  after loop1");
   //WrTm(sb+"cp808: end tru");
   dec(level);
 end;

begin //PatchAddress
 result := 0;
 if NewFunc = nil then
   exit;
 if OldFunc = nil then
   exit;
 if NewFunc = OldFunc then
   exit;
 BeenDone := TList.Create;
 try
   //Wr("");
   //enWr2 := ParamStr(0)="D:\!prj\SmallPrj\ScraperDB\test_v\test\Release\test.exe";
   //WrTm("inp   : "+IntToStr(GetModuleHandle(nil)));
   //WRTm("param0: "+ParamStr(0));
   Result := PatchAddressInModule(GetModuleHandle(nil), OldFunc, NewFunc);
 finally
   BeenDone.Free;
 end;
end; //PatchAddress

procedure HookTextOut(aConvertFunction: TConvertTextFunction);
begin
 //Wr("");

 if @OldTextOutA = nil then
   @OldTextOutA := FinalFunctionAddress(@TextOutA);
 if @OldTextOutW = nil then
   @OldTextOutW := FinalFunctionAddress(@TextOutW);

 if @OldExtTextOutA = nil then
   @OldExtTextOutA := FinalFunctionAddress(@ExtTextOutA);
 if @OldExtTextOutW = nil then
   @OldExtTextOutW := FinalFunctionAddress(@ExtTextOutW);

 if @OldDrawTextA = nil then
   @OldDrawTextA := FinalFunctionAddress(@DrawTextA);
 if @OldDrawTextW = nil then
   @OldDrawTextW := FinalFunctionAddress(@DrawTextW);

 if @OldDrawTextExA = nil then
   @OldDrawTextExA := FinalFunctionAddress(@DrawTextExA);
 if @OldDrawTextExW = nil then
   @OldDrawTextExW := FinalFunctionAddress(@DrawTextExW);

 @ConvertTextFunction := @aConvertFunction;

 GetRight;

 PatchAddress(@OldTextOutA, @NewTextOutA);
 PatchAddress(@OldTextOutW, @NewTextOutW);

end;

procedure UnhookTextOut;
begin
 //If @OldTextOutA<>nil then begin
 PatchAddress(@NewTextOutA, @OldTextOutA);
 PatchAddress(@NewTextOutW, @OldTextOutW);
 PatchAddress(@NewExtTextOutA, @OldExtTextOutA);
 PatchAddress(@NewExtTextOutW, @OldExtTextOutW);
 PatchAddress(@NewDrawTextA, @OldDrawTextA);
 PatchAddress(@NewDrawTextW, @OldDrawTextW);
 PatchAddress(@NewDrawTextExA, @OldDrawTextExA);
 PatchAddress(@NewDrawTextExW, @OldDrawTextExW);

 // end;
end;

initialization
finalization

 UnhookTextOut;
end.


 
juiceman   (2004-06-28 01:39) [8]

Ух, ну Вы мне приятный сюрприз преподнесли, буду копаться %) Огромное спасибо!!!


 
Ihor Osov'yak ©   (2004-06-28 01:56) [9]

2 [8] juiceman   (28.06.04 01:39)

Копайся, копайся. Это заведомо в лучшем состоянии, чем я получил тогда.. За код сильно не ругать, я в общем-то тогда системными вещами только начинал заниматься, так что в коде могут быть ляпы, но проект работоспособный.


 
Digitman ©   (2004-06-28 08:15) [10]


> juiceman


при реализации перехвата требуется правка не только таблицы импорта, но и таблицы экспорта.. последнее необходимо для случаев динамической загрузки библиотеки, эксп.ф-ция которой перехватывается ... если не править таблицу экспорта, то GetProcAddr() вернет старую (оригинальную) точку входа

таблица же импорта правится только для случая стат.загрузки


 
Игорь Шевченко ©   (2004-06-28 10:25) [11]

Неплохая методика перехвата была описана у Гэри Неббета, в его книжке "Справочник по функциям базового API для Windows NT", с примером. Но не на Delphi, разумеется


 
Ihor Osov'yak ©   (2004-06-28 12:40) [12]

2 [10] Digitman ©   (28.06.04 08:15)

> если не править таблицу экспорта, то GetProcAddr() вернет старую (оригинальную) точку входа

Несообразил. Вернее соображал, что GetProcAddr будет возращать оригинальную точку входа, но бороться с этим планировал в случае необходимости (в том проекте такой необходимомти не возникло) методом перехвата того же  GetProcAddr. Но правка таблицы экспорта заметно изящнее.  Спасибо. Если придется снова делать что-то похожее - учту.

> [11] Игорь Шевченко ©   (28.06.04 10:25)

Стоит на полке. Руки не доходят серьезно заняться. И что за жизнь такая - раньше доступа к информации не было, теперь информация есть, времени нету ее обработать :-(


 
Игорь Шевченко ©   (2004-06-28 13:38) [13]

Ihor Osov"yak ©   (28.06.04 12:40)

Хорошая книжка :)


> И что за жизнь такая - раньше доступа к информации не было,
> теперь информация есть, времени нету ее обработать


И не говори, аналогичная ситуация :)



Страницы: 1 вся ветка

Текущий архив: 2004.08.08;
Скачать: CL | DM;

Наверх




Память: 0.54 MB
Время: 0.026 c
3-1089656670
abakh
2004-07-12 22:24
2004.08.08
проблема с Yaffil 1.5 в Win9x


3-1089620796
Newuser
2004-07-12 12:26
2004.08.08
Вопрос по SQL.


1-1090508848
glGLU
2004-07-22 19:07
2004.08.08
ProgressBar


6-1086369875
Driver
2004-06-04 21:24
2004.08.08
ICQ


1-1090563325
goliath
2004-07-23 10:15
2004.08.08
Invalid canvas state request