Форум: "WinAPI";
Текущий архив: 2006.03.26;
Скачать: [xml.tar.bz2];
Внизв чем я не прав ?о хуках Найти похожие ветки
← →
oleggar © (2005-12-30 11:11) [0]В Интернет нашел следующий замечательный пример перехвата вызовов функций.Все работает отлично .Только мои попытки добавить сюда же перехват findnexta с показом сообщения о вызове с именем файла и ,желательно отменой ,окончились неудачей.Первым делом заменил имя dll и функции ,но я не понял принцип декларирования параметров подменной функции.
Кто-то может помочь для примера добавить сюда же перехват "kernel32.dll", "FindNextFileA" или любой другой ?
////////////////////////////////////////////////////////////////////////////////
//
// Демонстрационная программа перехвата вызова API функций
// Автор: Джеффри РИХТЕР
// Адаптация для Delphi: Александр (Rouse_) Багель
//
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMessageBox = function(hWnd: HWND; lpText, lpCaption:
PChar; uType: UINT): Integer; stdcall;
PFARPROC = ^FARPROC;
TIIDUnion = record
case Integer of
0: (Characteristics: DWORD);
1: (OriginalFirstThunk: DWORD);
end;
PImageImportDescriptor = ^TImageImportDescriptor;
TImageImportDescriptor = record
Union: TIIDUnion;
TimeDateStamp: DWORD;
ForwarderChain: DWORD;
Name: DWORD;
FirstThunk: DWORD;
end;
PImageThunkData = ^TImageThunkData32;
TImageThunkData32 = packed record
_function : PDWORD;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
procedure ReplaceIATEntryInOneMod(const OldProc, NewProc: FARPROC);
procedure SetAPIHook;
procedure ResetAPIHook;
end;
{$EXTERNALSYM ImageDirectoryEntryToData}
function ImageDirectoryEntryToData(Base: Pointer; MappedAsImage: ByteBool;
DirectoryEntry: Word; var Size: ULONG): Pointer; stdcall; external "imagehlp.dll";
{$EXTERNALSYM HookMessageBox}
function HookMessageBox(hWnd: HWND; lpText, lpCaption: PChar;
uType: UINT): Integer; stdcall;
var
Form1: TForm1;
var
OldMessageBox: FARPROC = nil;
CurrentMessageBox: FARPROC;
implementation
{$R *.dfm}
// Перехват API посредством подмены в таблице импорта
procedure TForm1.ReplaceIATEntryInOneMod(const OldProc,
NewProc: FARPROC);
var
Size: DWORD;
ImportEntry: PImageImportDescriptor;
Thunk: PImageThunkData;
Protect, newProtect: DWORD;
DOSHeader: PImageDosHeader;
NTHeader: PImageNtHeaders;
begin
if OldProc = nil then Exit;
if NewProc = nil then Exit;
// Можно искать вот так
ImportEntry := ImageDirectoryEntryToData(Pointer(hInstance), BOOL(1),
IMAGE_DIRECTORY_ENTRY_IMPORT, Size);
// Или вот так
{DOSHeader := PImageDosHeader(hInstance);
if IsBadReadPtr(Pointer(hInstance), SizeOf(TImageNtHeaders)) then Exit;
if (DOSHeader^.e_magic <> IMAGE_DOS_SIGNATURE) then Exit;
NTHeader := PImageNtHeaders(DWORD(DOSHeader) + DWORD(DOSHeader^._lfanew));
if NTHeader^.Signature <> IMAGE_NT_SIGNATURE then Exit;
ImportEntry := PImageImportDescriptor(DWORD(hInstance) +
DWORD(NTHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
if DWORD(ImportEntry) = DWORD(NTHeader) then Exit; }
if ImportEntry <> nil then
begin
while ImportEntry^.Name <> 0 do
begin
Thunk := PImageThunkData(DWORD(hInstance) +
DWORD(ImportEntry^.FirstThunk));
while Thunk^._function <> nil do
begin
if (Thunk^._function = OldProc) then
begin
if not IsBadWritePtr(@Thunk^._function, sizeof(DWORD)) then
Thunk^._function := NewProc
else
begin
if VirtualProtect(@Thunk^._function, SizeOf(DWORD),
PAGE_EXECUTE_READWRITE, Protect) then
begin
Thunk^._function := NewProc;
newProtect := Protect;
VirtualProtect(@Thunk^._function, SizeOf(DWORD),
newProtect, Protect);
end;
end;
//Exit;
end
else
Inc(PChar(Thunk), SizeOf(TImageThunkData32));
end;
ImportEntry := Pointer(Integer(ImportEntry) + SizeOf(TImageImportDescriptor));
end;
end;
end;
function HookMessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall;
begin
Result := TMessageBox(OldMessageBox)(hWnd,
PChar(Format("Перехвачен текст: "%s"", [lpText])),
PChar(Format("Перехвачен текст: "%s"", [lpCaption])), uType);
end;
// Снятие хука
procedure TForm1.ResetAPIHook;
begin
ReplaceIATEntryInOneMod(CurrentMessageBox, OldMessageBox);
end;
// Установка хука
procedure TForm1.SetAPIHook;
begin
OldMessageBox :=
GetProcAddress(GetModuleHandle("User32.dll"), "MessageBoxA");
CurrentMessageBox := @HookMessageBox;
ReplaceIATEntryInOneMod(OldMessageBox, CurrentMessageBox);
end;
////////////////////////////////////////////////////////////////////////////////
procedure TForm1.Button1Click(Sender: TObject);
begin
MessageBox(0, "Текст сообщения", "Заголовок", 0);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
SetAPIHook;
MessageBox(0, "Текст сообщения", "Заголовок", 0);
ResetAPIHook;
end;
end.
← →
Rouse_ © (2005-12-30 11:22) [1]Ну блин :)
Вот тут же. Хотя этой демке сто лет в обед :)
> TMessageBox = function(hWnd: HWND; lpText, lpCaption:
> PChar; uType: UINT): Integer; stdcall;
И обработчик тут же показан:function HookMessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall;
begin
Result := TMessageBox(OldMessageBox)(hWnd,
PChar(Format("Перехвачен текст: "%s"", [lpText])),
PChar(Format("Перехвачен текст: "%s"", [lpCaption])), uType);
end;
Соответственно у тебя должно быть вроде того:TFindNextFileA = function (hFindFile: THandle; var lpFindFileData: TWIN32FindDataA): BOOL; stdcall;
function FindNextFileA(hFindFile: THandle; var lpFindFileData: TWIN32FindDataA): BOOL; stdcall;
begin
Result := TFindNextFileA (OldMessageBox)(параметры);
end;
← →
Rouse_ © (2005-12-30 11:30) [2]////////////////////////////////////////////////////////////////////////////////
//
// ****************************************************************************
// * Unit Name : HookDLL
// * Purpose : Демонстрационный пример хука и подмены API в приложениях...
// * Author : Александр (Rouse_) Багель
// * Version : 1.00
// ****************************************************************************
//
library HookDLL;
uses
Windows,
Messages,
Winsock;
const
GlobMapID = "Global Hook for API Interception {2E662583-74C4-45DB-B6DF-FE318C94258D}";
const // Константы нотификаций
NOTIFY_DLL_INJECT = 1;
NOTIFY_API_CALL = 2;
NOTIFY_API_INTERCEPT_SUCCESS = 3;
NOTIFY_API_INTERCEPT_FAILED = 4;
type
// Структура для нотификации приложения
TLogData = record
AppName: ShortString; // Имя приложения
FuncName: String[8]; // Имя функции
FuncPointer: Integer; // Адрес функции
IP: String[15]; // IP адрес
Port: Cardinal; // Порт
Buff: array [0..$FFFF] of Char; // Содержимое буффера
BuffSize: Word; // Размер буфера
end;
// Структура с рабочей информацией хука
PShareInf = ^TShareInf;
TShareInf = record
AppWndHandle: HWND;
OldHookHandle: HHOOK;
hm:THandle;
end;
// Структуры для работы с таблицей импорта
TIIDUnion = record
case Integer of
0: (Characteristics: DWORD);
1: (OriginalFirstThunk: DWORD);
end;
PImageImportDescriptor = ^TImageImportDescriptor;
TImageImportDescriptor = record
Union: TIIDUnion;
TimeDateStamp: DWORD;
ForwarderChain: DWORD;
Name: DWORD;
FirstThunk: DWORD;
end;
PImageThunkData = ^TImageThunkData32;
TImageThunkData32 = packed record
_function : PDWORD;
end;
function ImageDirectoryEntryToData(Base: Pointer; MappedAsImage: ByteBool;
DirectoryEntry: Word; var Size: ULONG): Pointer; stdcall; external "imagehlp.dll";
var
MapHandle: THandle = 0;
ShareInf: PShareInf = nil;
OldRecv: FARPROC = nil;
Replaced: Boolean;
AppTitle: ShortString;
// Перехват API посредством подмены в таблице импорта
// =============================================================================
function ReplaceIATEntryInOneMod(const OldProc,
NewProc: FARPROC): Boolean;
var
ImportEntry: PImageImportDescriptor;
Thunk: PImageThunkData;
Protect, newProtect: DWORD;
ImageBase: Cardinal;
DOSHeader: PImageDosHeader;
NTHeader: PImageNtHeaders;
begin
Result := False;
if OldProc = nil then Exit;
if NewProc = nil then Exit;
ImageBase := GetModuleHandle(nil);
// Зная структуру PE заголовка - находим начало таблицы импорта
DOSHeader := PImageDosHeader(ImageBase);
if IsBadReadPtr(Pointer(ImageBase), SizeOf(TImageNtHeaders)) then Exit;
if (DOSHeader^.e_magic <> IMAGE_DOS_SIGNATURE) then Exit;
NTHeader := PImageNtHeaders(DWORD(DOSHeader) + DWORD(DOSHeader^._lfanew));
if NTHeader^.Signature <> IMAGE_NT_SIGNATURE then Exit;
ImportEntry := PImageImportDescriptor(DWORD(ImageBase) +
DWORD(NTHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
if DWORD(ImportEntry) = DWORD(NTHeader) then Exit;
if ImportEntry <> nil then
begin
// Бежим по записям таблицы ...
while ImportEntry^.Name <> 0 do
begin
Thunk := PImageThunkData(DWORD(ImageBase) +
DWORD(ImportEntry^.FirstThunk));
// ... пока таблица не кончится ...
while Thunk^._function <> nil do
begin
// ... или не найдем нужную нам запись.
if (Thunk^._function = OldProc) then
begin
// Производим подмену, сначала так...
if not IsBadWritePtr(@Thunk^._function, sizeof(DWORD)) then
begin
Thunk^._function := NewProc;
Result := True;
end
else
begin // ... ну а если не получилось - тогда вот так
if VirtualProtect(@Thunk^._function, SizeOf(DWORD),
PAGE_EXECUTE_READWRITE, Protect) then
begin
Thunk^._function := NewProc;
newProtect := Protect;
VirtualProtect(@Thunk^._function, SizeOf(DWORD),
newProtect, Protect);
Result := True;
end;
end;
end
else
Inc(PChar(Thunk), SizeOf(TImageThunkData32));
end;
ImportEntry := Pointer(Integer(ImportEntry) + SizeOf(TImageImportDescriptor));
end;
end;
end;
// Наша функция которая будет работать вместо оригинальной ...
// =============================================================================
function InterceptedRecv(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
type
TrecvImage = function(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
var
CDS: TCopyDataStruct;
SockAddr: TSockAddr;
AddrLen: Integer;
Data: TLogData;
begin
// Первоначально вызываем оригинальную функцию, но данные будем писать в свой буффер...
Result := TrecvImage(OldRecv)(s, Data.Buff[0], len, flags);
// Получаем информацию кто с кем связался
if getpeername(s, SockAddr, AddrLen) = SOCKET_ERROR then Exit;
Data.IP := inet_ntoa(SockAddr.sin_addr);
Data.Port := ntohs(SockAddr.sin_port);
Data.BuffSize := Result;
Data.AppName := AppTitle;
// Тут можно встроить проверку (к примеру по какому нибудь порту)
if True then
Move(Data.Buff[0], Buf, Result) // проверка успешна - пишем в буфер полученные данные
else
Result := SOCKET_ERROR; // в противном случае говорим что вызов неуспешен.
// Отправляем полученные данные нашему приложению...
CDS.dwData := NOTIFY_API_CALL;
CDS.cbData := SizeOf(TLogData);
CDS.lpData := @Data;
SendMessage(ShareInf^.AppWndHandle, WM_COPYDATA, 0, Integer(@CDS));
end;
← →
Rouse_ © (2005-12-30 11:30) [3]
// Начало и завершение работы нашего хука ...
// =============================================================================
procedure DLLEntryPoint(dwReason: DWORD); //stdcall; <- вот это как раз не нужно...
var
CDS: TCopyDataStruct;
Data: TLogData;
ImageBase: Cardinal;
FileName: array [0..MAX_PATH - 1] of Char;
begin
case dwReason Of
DLL_PROCESS_ATTACH:
begin
// Все данные во избежании разрыва цепочки хуков храним в отображаемом в память процесса файле,
// только тогда все экземпляры хука будут владеть достоверной информацией
MapHandle := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(TShareInf), GlobMapID);
ShareInf := MapViewOfFile(MapHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TShareInf));
// Получаем информацию о процессе в который подгружена наша библиотека
Replaced := False;
OldRecv := GetProcAddress(GetModuleHandle("wsock32.dll"), "recv");
DisableThreadLibraryCalls(hInstance);
ImageBase := GetModuleHandle(nil);
ZeroMemory(@FileName, SizeOf(FileName));
GetModuleFileName(ImageBase, @FileName, SizeOf(FileName));
AppTitle := String(FileName);
// Нотифицируем приложение о успешном внедрении библиотеки
// И сообщаем информацию о процессе
ZeroMemory(@Data, SizeOf(TLogData));
Data.AppName := AppTitle;
Data.FuncName := "recv";
Data.FuncPointer := Integer(OldRecv);
CDS.dwData := NOTIFY_DLL_INJECT;
CDS.cbData := SizeOf(TLogData);
CDS.lpData := @Data;
SendMessage(ShareInf^.AppWndHandle, WM_COPYDATA, 0, Integer(@CDS));
// Подменяем процедуры своими (если это нужное нам приложение)
if Pos("NETCHAT.EXE", AnsiUpper(@FileName)) <>0 then
begin
if OldRecv <> nil then
// Смотрим - успешно ли подменилась запись в таблице импорта?
if ReplaceIATEntryInOneMod(OldRecv, @InterceptedRecv) then
begin
CDS.dwData := NOTIFY_API_INTERCEPT_SUCCESS; // Успешно...
Replaced := True; // Ставим флаг, что была замена...
end
else
CDS.dwData := NOTIFY_API_INTERCEPT_FAILED; // Не успешно...
// Нотифицируем наше приложение о результате подмены...
CDS.cbData := SizeOf(TLogData);
CDS.lpData := @Data;
SendMessage(ShareInf^.AppWndHandle, WM_COPYDATA, 0, Integer(@CDS));
end;
end;
DLL_PROCESS_DETACH:
begin
UnMapViewOfFile(ShareInf);
CloseHandle(MapHandle);
// Возвращаем изменения как они и были (если замена была удачна)
if Replaced then
ReplaceIATEntryInOneMod(@InterceptedRecv, OldRecv);
end;
end;
end;
// Это наш хук, он нужен только для внедрения в удаленный процесс ...
// =============================================================================
function Hook(Code: Integer; WParam: WPARAM; LParam: LPARAM): LRESULT; stdcall;
begin
Result := CallNextHookEx(ShareInf^.OldHookHandle, Code, WParam, LParam); // вызываем след. ловушку
end;
// Установка хука ...
// =============================================================================
function SetHook(Wnd: HWND): BOOL; stdcall;
begin
if ShareInf <> nil then
begin
ShareInf^.AppWndHandle := Wnd;
ShareInf^.OldHookHandle := SetWindowsHookEx(WH_GETMESSAGE, @Hook, HInstance, 0); // <- Обратите внимание, не допускаем главной ошибки
Result := ShareInf^.OldHookHandle <> 0;
end
else
Result:=False;
end;
// Снятие хука ...
// =============================================================================
function RemoveHook: BOOL; stdcall;
begin
Result := UnhookWindowsHookEx(ShareInf^.OldHookHandle);
CloseHandle(ShareInf^.hm);
end;
exports
SetHook, RemoveHook;
begin
DLLProc := @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
← →
Rouse_ © (2005-12-30 11:30) [4]Приложение:
////////////////////////////////////////////////////////////////////////////////
//
// ****************************************************************************
// * Unit Name : uMain
// * Purpose : Демонстрационный пример хука и подмены API в приложениях...
// * Author : Александр (Rouse_) Багель
// * Version : 1.00
// ****************************************************************************
//
unit uMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ActiveX;
const // Константы нотификаций
NOTIFY_DLL_INJECT = 1;
NOTIFY_API_CALL = 2;
NOTIFY_API_INTERCEPT_SUCCESS = 3;
NOTIFY_API_INTERCEPT_FAILED = 4;
type
TLogData = record
AppName: ShortString; // Имя приложения
FuncName: String[8]; // Имя функции
FuncPointer: Integer; // Адрес функции
IP: String[15]; // IP адрес
Port: Cardinal; // Порт
Buff: array [0..$FFFF] of Char; // Содержимое буффера
BuffSize: Word; // Размер буфера
end;
PLogData = ^TLogData;
THADemo = class(TForm)
memReport: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
procedure WMCopyData(var Msg: TMessage); message WM_COPYDATA;
end;
function SetHook(Wnd: HWND): BOOL; stdcall;
external "HookDLL.dll" name "SetHook";
function RemoveHook: BOOL; stdcall;
external "HookDLL.dll" name "RemoveHook";
var
HADemo: THADemo;
implementation
{$R *.dfm}
{ TForm1 }
procedure THADemo.FormCreate(Sender: TObject);
begin
if not SetHook(Handle) Then
MessageBox(Handle, "Невозможно установить хук.", PChar(Application.Title), MB_OK OR MB_ICONHAND);
end;
procedure THADemo.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if not RemoveHook Then
MessageBox(Handle, "Невозможно снять хук.", PChar(Application.Title), MB_OK OR MB_ICONHAND);
end;
procedure THADemo.WMCopyData(var Msg: TMessage);
const
ReportInject = "Библиотека внедрена в приложение "%s", функция "%s" имеет адрес: $%s";
ReportIntercept = "Приложение: "%s" IP %s:%d размер данных = %d буфер = "%s"";
ReportSucceeded = "Перехват функции "%s" в модуле "%s" успешен.";
ReportFailed = "Перехват функции "%s" в модуле "%s" неуспешен!!!";
var
Data: TLogData;
Buffer: String;
begin
Data := PLogData(PCopyDataStruct(Msg.LParam)^.lpData)^;
// Типы нотификаций
case PCopyDataStruct(Msg.LParam)^.dwData of
NOTIFY_DLL_INJECT: // Пришло уведомление о внедрении библиотеки в удаленный процесс
with Data do
memReport.Lines.Add(Format(ReportInject, [AppName, FuncName,
IntToHex(Data.FuncPointer, 8)]));
NOTIFY_API_CALL: // Уведомление о вызове функции
begin
SetLength(Buffer, Data.BuffSize);
Move(Data.Buff[0], Buffer[1], Data.BuffSize);
with Data do
memReport.Lines.Add(Format(ReportIntercept, [AppName, IP, Port, BuffSize, Buffer]));
end;
NOTIFY_API_INTERCEPT_SUCCESS: // Уведомление о удачной подмене таблицы импорта
with Data do
memReport.Lines.Add(Format(ReportSucceeded, [FuncName, AppName]));
NOTIFY_API_INTERCEPT_FAILED: // Уведомление о неудачной подмене таблицы импорта
with Data do
memReport.Lines.Add(Format(ReportFailed, [FuncName, AppName]));
end;
end;
end.
← →
Rouse_ © (2005-12-30 11:32) [5]Это пример комуто писался, может тут понятней будет, коментарии вроде все расставил :)
← →
Digitman © (2005-12-30 11:40) [6]
> Rouse_ © (30.12.05 11:22) [1]
Для общего случая (возможен как статический, так и динамический импорт) модификации только одной IAT недостаточно - следует модифицировать еще и EAT того модуля, обращения к функции которого нужно перехвататить
Впрочем, EAT можно и не модифицировать, но тогда вместо этого придется в обязательном порядке перехватить kernel32.GetProcAddress (она как раз и обращается к EAT), с тем чтобы вызов вернул не оригинальный адрес, а уже подмененный
← →
Rouse_ © (2005-12-30 11:42) [7]
> Digitman © (30.12.05 11:40) [6]
Ну конечно :)
Это же только пример для подмены адреса через IAT :)
С наступающим :)
← →
oleggar © (2005-12-30 11:43) [8]это я сделал :
TFindNextFileA = function (hFindFile: THandle; var lpFindFileData: TWIN32FindDataA): BOOL; stdcall;
function FindNextFileA(hFindFile: THandle; var lpFindFileData: TWIN32FindDataA): BOOL; stdcall;
begin
Result := TFindNextFileA (OldMessageBox)(параметры);
end;
только осталось еще изменить
ResetAPIHook ,setapihook .что там сделать ,можно в одной процедуре сразу несколько функций перехватить ,или нужно ставить хуки разными функциями ?
← →
Digitman © (2005-12-30 11:47) [9]Да , и еще : сама идея использования глоб.хука для внедрения библиотеки с логикой перехвата для общего случая опять же не пойдет - хук-ДЛЛ будет внедряться далеко не в каждый процесс.
Под НТ можно поступить иначе - инсталлировать драйвер режима ядра (см. соотв статью с примерами на wasm.ru), который будет следить за стартом процессов и посылать нотификации приложению-монитору. При получении нотификации приложение-монитор стартует в новоиспеченном процессе-жертве трэд, который загружает в АП "жертвы" образ библиотеки, собственно и осуществляющей перехват в ходе своей иниц-и
← →
oleggar © (2005-12-30 12:15) [10]еще один вопрос .перехват по 1 примеру будет работать во всей системе ,или его нужно внедрять в определенный чужой процесс ,и он будет перехватывать только там ?
← →
Rouse_ © (2005-12-30 13:28) [11]
> хук-ДЛЛ будет внедряться далеко не в каждый процесс.
Смотря что ловим? обычно WH_DEBUG хватает. С остальным согласен :)
> перехват по 1 примеру будет работать во всей системе
Только в своем приложении, по второму примеру - во всей системе
> ResetAPIHook ,setapihook .что там сделать ,можно в одной
> процедуре сразу несколько функций перехватить ,или нужно
> ставить хуки разными функциями ?
Сколько угодно перехватывай, только код допиши для этого...
← →
Digitman © (2005-12-30 14:26) [12]
> Смотря что ловим?
по полено что ловим)
важно что получаем управление при иниц-ции
а вот получить оное можно лишь в случаях, когда "жертва" претендует на хоть что-то связанное с окнами как с ОС-объектами
не думаю, что ты этого не осознаешь)
← →
Rouse_ © (2005-12-30 15:02) [13]Ну конечно :) Я ж поэтому и сказал что с остальным согласен, просто консоль частный и достаточно редкий случай, на остальное уже есть штопор в виде WH_DEBUG :)
С наступающим Сергей :)
← →
Digitman © (2005-12-30 15:16) [14]
> Rouse_ © (30.12.05 15:02) [13]
И тебя, Александр, с наступающим !
Страницы: 1 вся ветка
Форум: "WinAPI";
Текущий архив: 2006.03.26;
Скачать: [xml.tar.bz2];
Память: 0.55 MB
Время: 0.06 c