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

Вниз

Тонкости перехвата API   Найти похожие ветки 

 
Dmitry S ©   (2008-04-28 22:59) [0]

Конструктивно:

Перехватываю функцию CreateFileW путем записи нескольких первых байт. (точнее 10):
$68, $A1, $A2, $A3, $A4 // push A1A2A3A4h
$68, [@MyCreateFileW] // push ...
$C3 // ret

После чего, как я думал, мне необходимо в описание MyCreateFileW добавить еще один параметр (первый) резмером Integer, где и будет храниться переданное мной число A1A2A3A4h.
Однако параметр полченный в функцию "левый", а число A1A2A3A4h продолжает храниться в стеке. Почему так? Ведь я добавил еще один параметр, он ведь должен браться из стека?


 
Тыщ ©   (2008-04-28 23:04) [1]

Какое соглашение вызова у функции MyCreateFileW?


 
Dmitry S ©   (2008-04-28 23:04) [2]

stdcall


 
Dmitry S ©   (2008-04-28 23:11) [3]

А еще вопрос по теме.
Как сделать call по абслютному адресу? Т.е. в машинных инстукциях как?
E8 xx xx xx xx - это я так понимаю относительный. А как абсолютный?


 
Тыщ ©   (2008-04-28 23:15) [4]

Dmitry S ©   (28.04.08 23:11) [3]

call по абслютному адресу сделать нельзя, потому так как он изменит сегментный регистр cs, а это запрещено в Windows.
Да и не нужно, в общем-то.


 
Dmitry S ©   (2008-04-28 23:19) [5]

А как тогда быть??


 
Тыщ ©   (2008-04-28 23:19) [6]

mov eax, some_absolute_address
call eax


 
Dmitry S ©   (2008-04-28 23:23) [7]

окей.
А можно это в кодах команд?


 
Тыщ ©   (2008-04-28 23:25) [8]

B8 78563412 // mov eax, 12345678h
FFD0 // call eax


 
Dmitry S ©   (2008-04-28 23:43) [9]

Не понимаю вот чего:

Делаю простой вызов своей
MyCreateFileW

В итоге в окне асемблера наблюдаю:
делается куча push, потом call
а в теле функци pop-ов нет, значения переменных беруться из памяти. как так?


 
Anatoly Podgoretsky ©   (2008-04-28 23:52) [10]

> Dmitry S  (28.04.2008 23:43:09)  [9]

Есть такое понятие, как фрейм стека, вот отностительно его и берутся.
Сходи ко мне на сайт и возьми две статьи по basm

http://www.podgoretsky.com/Redir.aspx?id=142&DownloadFile=~/ftp/Docs/Delphi/Podgoretsky/basmru.zip
http://www.podgoretsky.com/Redir.aspx?id=129&DownloadFile=~/ftp/Docs/Delphi/Podgoretsky/BasmForBeginners-ru.doc

Поскольку ты занимаешься АСМ то информация будет интересна.


 
Dmitry S ©   (2008-04-29 00:11) [11]

Кажеться я понял в чем дело.
Объясните пожалуйста следующее.
Мне нужно вставить некое значение в стек ПЕРЕД текущим. Так правильно:
pop eax;
push $xxxxxxx;
push eax;
?


 
Dmitry S ©   (2008-04-29 00:21) [12]

Все!!! Я понял!! И исправил.

Дело в том, что я "добавлял" этот самый параметр ПОСЛЕ выполнения call. А последняя использует стек, видимо для хранения точки возврата. Поэтому мой параметр и адрес точки возврата, какбы поменялись местами.Вставил код из 11, все заработало =) уфф=)


 
Dmitry S ©   (2008-04-29 00:40) [13]

В итоге получился такой код:

pop eax  // 58
push $xxxxxxxx // 68xxxxxxxx
push eax  // 50
push $yyyyyyyy // 68yyyyyyyy
ret  // c3

Этот код я вставляю в начало перехватываемой функции.
Тут $xxxxxxxx - адрес какого либо параметра
$yyyyyyyy - мой функция, которая должна выполнить оригинальную...
Блин, совсем как с SetIntVec в паскале =)


 
KSergey ©   (2008-04-29 09:55) [14]

> Dmitry S ©   (29.04.08 00:21) [12]
> ПОСЛЕ выполнения call.
> А последняя использует стек, видимо для хранения точки возврата.

А может книжки-то почитать, а?
Ну зачем постигать компьютер методом проб и ошибок? Это ж жизни не хватит!


 
Rouse_ ©   (2008-04-29 10:18) [15]

Что-то ты сурово как-то сплайсинг делаешь... Вот тебе навскидку примерчик от руки написанный... Должен работать по идее

type
 TSpliceRec = packed record
   JMP_OpCode: Byte;
   ProcAddr: Pointer;
   RET_OpCode: Byte;
 end;

var
 FuncAddr: Pointer;
 OldSpliceData, NewSpliceData: TSpliceRec;

function SpliceEntry(const FuncAddr: Pointer;
 const NewData: TSpliceRec): Boolean;
var
 Dumme: DWORD;
begin
 Result := WriteProcessMemory(GetCurrentProcess, FuncAddr, @NewData,
   SizeOf(TSpliceRec), Dumme);
 if Result then
   Result := Dumme = SizeOf(TSpliceRec);
end;

type
 TCreateFileA = function(lpFileName: PAnsiChar;
   dwDesiredAccess, dwShareMode: DWORD;
   lpSecurityAttributes: PSecurityAttributes;
   dwCreationDisposition, dwFlagsAndAttributes: DWORD;
   hTemplateFile: THandle): THandle; stdcall;

function InterceptedCreateFileA(lpFileName: PAnsiChar;
 dwDesiredAccess, dwShareMode: DWORD;
 lpSecurityAttributes: PSecurityAttributes;
 dwCreationDisposition, dwFlagsAndAttributes: DWORD;
 hTemplateFile: THandle): THandle; stdcall;
begin
 SpliceEntry(FuncAddr, OldSpliceData);
 dwDesiredAccess := GENERIC_READ;
 dwShareMode := FILE_SHARE_READ or FILE_SHARE_WRITE;
 dwCreationDisposition := OPEN_EXISTING;
 Result := TCreateFileA(FuncAddr)(lpFileName, dwDesiredAccess,
   dwShareMode, lpSecurityAttributes, dwCreationDisposition,
   dwFlagsAndAttributes, hTemplateFile);
 SpliceEntry(FuncAddr, NewSpliceData);
end;


пример вызова:

 NewSpliceData.JMP_OpCode := $68;
 NewSpliceData.ProcAddr := @InterceptedCreateFileA;
 NewSpliceData.RET_OpCode := $C3;
 FuncAddr := GetProcAddress(GetModuleHandle(kernel32), "CreateFileA");
     ReadProcessMemory(GetCurrentProcess,
     FuncAddr,
     @OldSpliceData, SizeOf(TSpliceRec), Dumme);

 SpliceEntry(FuncAddr, NewSpliceData);
 try
   M := TFileStream.Create(Path, fmOpenRead);
   try
     M.Position := LogRecord[Index].Rec.DataOffset;
     Stream.CopyFrom(M, LogRecord[Index].Rec.DataSize);
     Result := True;
   finally
     M.Free;
   end;
 finally
   SpliceEntry(FuncAddr, OldSpliceData);
 end;


 
Дмитрий С   (2008-04-29 10:26) [16]


> Rouse_ ©   (29.04.08 10:18) [15]

Я делаю тоже самое, только передаю в InterceptedCreateFileA дополнительный параметр. Типа LPVOID lpParameter у CreateThread.


 
Дмитрий С   (2008-04-29 10:28) [17]

Вечером скину код, что получилось. Он дома.

ЗЫ. Не удается с тобой наладить связь. =(


 
Дмитрий С   (2008-04-29 10:29) [18]


> А может книжки-то почитать, а?
> Ну зачем постигать компьютер методом проб и ошибок? Это
> ж жизни не хватит!

Так ведь это просто! Мне АСМ в принципе не нужен. Для таких нехитрых задач достаточно тех знаний что есть:) Конечно возникают вопросы. Но они у всех возникают


 
Дмитрий С   (2008-04-29 10:35) [19]


> Поскольку ты занимаешься АСМ то информация будет интересна.
>
>

благодарю! :)


 
Rouse_ ©   (2008-04-29 10:38) [20]


> ЗЫ. Не удается с тобой наладить связь. =(

Дай свою асю сам свяжусь, у меня антиспам стоит, он почемуто только с QIP пропускает клиентов :)


 
Дмитрий С   (2008-04-29 10:40) [21]

Удалено модератором


 
Сергей М. ©   (2008-04-29 15:53) [22]


> Dmitry S ©   (28.04.08 22:59)


А нафих тебе сплайсинг сдался ?
Чем не устроил перехват методом модификации EAT+IAT ?


 
Дмитрий С   (2008-04-29 16:06) [23]

О вкусах не спорят


 
Сергей М. ©   (2008-04-29 17:08) [24]


> О вкусах не спорят


Не спорят, да)

А вот о блажи можно и поспорить.


 
Rouse_ ©   (2008-04-29 17:18) [25]

Через EAT+IAT доп.параметр не перекинешь, хотя я так и не понял зачем он тут сдался :)


 
Сергей М,   (2008-04-29 19:47) [26]


> зачем он тут сдался


Как зачем ? Блажь !) Самая что ни на есть настоящая).. Надежно прикрытая "вкусами, о которых не спорят")


 
Тыщ ©   (2008-04-29 22:47) [27]

Anatoly Podgoretsky ©   (28.04.08 23:52) [10]

> basmru.zip

Почитал. Что-то опечаток многовато, и некоторые неточности имеются.


 
Dmitry S ©   (2008-04-29 23:20) [28]

unit InterceptAPI;

interface
uses Windows;

type
 TInterceptProc = packed record
   instr_pop1: byte;               // pop eax;
   instr_push1: byte;              // push InterceptStruct;
   InterceptStruct: Pointer;
   instr_push2: byte;              // push eax;
   instr_push3: byte;              // push address;
   address: Pointer;
   instr_ret: byte;                // ret;
 end;

 PInterceptStruct = ^TInterceptStruct;

 TInterceptStruct = record
   OriginalFunction: Pointer;
   NewFunction: Pointer;

   Lock:RTL_CRITICAL_SECTION;

   NewFunctionCode: TInterceptProc;
   OldFunctionCode: TInterceptProc; // тип используется лишь для сорaзмерности
 end;

{ InterceptFunction устанавливает перехват функции по адресу OriginalFunction
функцией по адресу NewFunction. Под структуру InterceptStruct выделять память не нужно -
выделяется автоматически.
Важно!
Функции OriginalFunction и NewFunction должны быть stdcall.
Причем NewFunction имеет не только те же параметры, что и OriginalFunction, но
и дополнительный первый параметр: InterceptStruct: PInterceptStruct.
Например оригинальная функция:
 function MessageBoxW(hWnd: HWND; lpText, lpCaption: PWideChar; uType: UINT): Integer; stdcall;
тогда новая функция должна иметь вил:
 function InterMessageBoxW(InterceptStruct: PInterceptStruct; hWnd: HWND; lpText, lpCaption: PWideChar; uType: UINT): Integer; stdcall;

Данный параметр используется для вызовов LockFunction и UnLockFunction.
}
procedure InterceptFunction(var InterceptStruct: PInterceptStruct; const OriginalFunction, NewFunction: Pointer); stdcall;

{
 UnInterceptFunction восстанавливает оригинальную функцию и освобождает память
 стурктуры InterceptStruct.
}
procedure UnInterceptFunction(var InterceptStruct: PInterceptStruct); stdcall;

{
 LockFunction временно восстанавливает функцию.
}
procedure LockFunction(InterceptStruct: PInterceptStruct); stdcall;

{
 UnLockFunction снова перехватывает функцию
}
procedure UnLockFunction(InterceptStruct: PInterceptStruct); stdcall;

implementation

procedure InterceptFunction(var InterceptStruct: PInterceptStruct; const OriginalFunction, NewFunction: Pointer); stdcall;
var
 CurrentProcess: THandle;
 C: DWord;
 I: Integer;
 P:Pointer;
begin
 GetMem(InterceptStruct, SizeOf(TInterceptStruct));

 InterceptStruct^.OriginalFunction := OriginalFunction;
 InterceptStruct^.NewFunction := NewFunction;

 InitializeCriticalSection(InterceptStruct^.Lock);

 InterceptStruct^.NewFunctionCode.instr_pop1 := $58; // pop eax
 InterceptStruct^.NewFunctionCode.instr_push1 := $68; // push
 InterceptStruct^.NewFunctionCode.InterceptStruct := InterceptStruct;

 InterceptStruct^.NewFunctionCode.instr_push2 := $50; // push eax

 InterceptStruct^.NewFunctionCode.instr_push3 := $68; // push
 InterceptStruct^.NewFunctionCode.address := NewFunction;
 InterceptStruct^.NewFunctionCode.instr_ret := $C3; // ret

 CurrentProcess := GetCurrentProcess;
 ReadProcessMemory(CurrentProcess, OriginalFunction, @InterceptStruct^.OldFunctionCode, SizeOf(TInterceptProc), C);
 WriteProcessMemory(CurrentProcess, OriginalFunction, @InterceptStruct^.NewFunctionCode, SizeOf(TInterceptProc), C);
end;

procedure UnInterceptFunction(var InterceptStruct: PInterceptStruct); stdcall;
var
 C: DWord;
begin
 WriteProcessMemory(GetCurrentProcess, InterceptStruct^.OriginalFunction, @InterceptStruct^.OldFunctionCode, SizeOf(TInterceptProc), C);
 DeleteCriticalSection(InterceptStruct^.Lock);
 FreeMem(InterceptStruct);
end;

procedure LockFunction(InterceptStruct: PInterceptStruct); stdcall;
var
 C: DWord;
begin
 EnterCriticalSection(InterceptStruct^.Lock);
 WriteProcessMemory(GetCurrentProcess, InterceptStruct^.OriginalFunction, @InterceptStruct^.OldFunctionCode, SizeOf(TInterceptProc), C);
end;

procedure UnLockFunction(InterceptStruct: PInterceptStruct); stdcall;
var
 C: DWord;
begin
 WriteProcessMemory(GetCurrentProcess, InterceptStruct^.OriginalFunction, @InterceptStruct^.NewFunctionCode, SizeOf(TInterceptProc), C);
 LeaveCriticalSection(InterceptStruct^.Lock);
end;

end.


Вот код который я создал.
Тут у меня самого замечание.
EnterCriticalSection и LeaveCriticalSection стоят непоназначению.
Как бы сделать, чтобы когда я восстановил функцию, то все ее вызовы из других потоков блокировались EnterCriticalSection. И эта блокировка снималась тогда, когда я восстанавливал функцию?


 
Dmitry S ©   (2008-04-29 23:23) [29]

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


 
Сергей М. ©   (2008-04-30 09:02) [30]


> Неужели придеться действительно через таблицу импорта делать.
>
>


И не только "через" нее.
Потребуется еще и модификация таблицы экспорта.

Но этот способ ничуть не хуже сплайсинга и при прочих равных условиях имеет очевидное преимущество в простоте реализации и использования.


 
Дмитрий С   (2008-04-30 10:11) [31]

*ушел делать rtfm* =)


 
Дмитрий С   (2008-05-02 20:26) [32]


> Потребуется еще и модификация таблицы экспорта.

А это зачем?

Я нашел пример в сети. Понял, что с помощью подмены таблицы импорта можно "перекрыть" функции только в своем модуле.
Т.е. если функция вызывается из другого модуля (из какой либо подключенной dll), то вызывается оригинальная функция, а не перекрытая.

Получается, что нужно заранее загружать нужную библиотеку и выполнять перекрытие уже в ней?


 
Rouse_ ©   (2008-05-03 15:20) [33]


> Получается, что нужно заранее загружать нужную библиотеку
> и выполнять перекрытие уже в ней?

Это ко всем случаям относится, если хочешь делать глобально, а не только в своем адресном пространстве...


 
Дмитрий С   (2008-05-04 10:11) [34]


> Это ко всем случаям относится, если хочешь делать глобально,
>  а не только в своем адресном пространстве...

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


 
Rouse_ ©   (2008-05-04 10:24) [35]


> Как же так? Вышеприведенный способ (с помощью сплайсов)
> не требовал этого.

Что значит не требовал? Т.е. ты хочешь сказать, что в своем адресном пространстве ты сделал сплайсинг входной точки функции и это изменение автоматически спроецировалось на всю систему?


 
Дмитрий С   (2008-05-04 10:44) [36]


> Что значит не требовал? Т.е. ты хочешь сказать, что в своем
> адресном пространстве ты сделал сплайсинг входной точки
> функции и это изменение автоматически спроецировалось на
> всю систему?

Нет. Только на мой процесс. На все подключенные позже библиотеки (к моему процессу) перехват также распространялся.

А вот способ изменения IAT не распространяется на библиотеки вообще. Т.е. каждую библиотеку нужно хакать индивидуально.


 
Rouse_ ©   (2008-05-04 12:12) [37]


> А вот способ изменения IAT не распространяется на библиотеки
> вообще

Естественно, IAT ты же поправил у своего приложения, а не у библиотеки, тем более это актуально только для статической линковки функций, для динамической применяют правку EAT у библиотеки, хранящей функцию, и это будет справедливо для всех вызовов даной функции из любых модулей, загруженных в твое ВАП (опять же с учетом динамики, а не статики) :)


 
Дмитрий С   (2008-05-04 12:40) [38]

Вот оно что. Ясно.
Если вернуться к той же CreateFileW. Изменения в EAT придеться делать для Kernel32, а как это скажется на работу других процессов.
Честно говоря я не знаю точно сколько копий kernel32 храниться в памяти: одна на всех или по одной на каждый процесс.

ЗЫ. помоему для моей задачи я скоро начну изучать тему "как написать драйвер файловой системы" или типа того=)


 
Rouse_ ©   (2008-05-04 12:53) [39]


> Изменения в EAT придеться делать для Kernel32, а как это
> скажется на работу других процессов.

Никак не скажется, они об этом даже не узнают ибо:

> я не знаю точно сколько копий kernel32 храниться в памяти:
>  одна на всех или по одной на каждый процесс

образ маппится в ВАП твого процесса

> как написать драйвер файловой системы

а это уже из серии "из пушки по воробьям"...


 
oxffff ©   (2008-05-04 14:54) [40]


> Rouse_ ©   (04.05.08 12:53) [39]
>
> > Изменения в EAT придеться делать для Kernel32, а как это
>
> > скажется на работу других процессов.
>
> Никак не скажется, они об этом даже не узнают ибо:
>
> > я не знаю точно сколько копий kernel32 храниться в памяти:
>
> >  одна на всех или по одной на каждый процесс
>
> образ маппится в ВАП твого процесса
>
> > как написать драйвер файловой системы
>
> а это уже из серии "из пушки по воробьям"...


Ничего не мешает мапить разные ВАП на одно ФП.
А именно мапить кодовые сегменты PE образа.


 
Rouse_ ©   (2008-05-04 15:00) [41]


> Ничего не мешает мапить разные ВАП на одно ФП.

Ну тонкостей я уже не помню, но вроде как всеже не кусок ВАП мапится на образ а наоборот, хотя могу и ошибаться...


 
oxffff ©   (2008-05-04 16:07) [42]


> Rouse_ ©   (04.05.08 15:00) [41]


Ничего не мешает кодовую часть PE образа сделать разделяемой для системых DLL, т.е. избежать дублирования кода DLL в физической памяти и файле подкачки, хотя бы для системых библиотек.
И например запретить выгрузку из памяти, аля nonpaged pool, чтобы не создавать нагрузки на другие процессы при переключении.
Хотя это смахивает больше на режим ядра. :)


 
Игорь Шевченко ©   (2008-05-04 16:19) [43]

Я для своего процесса использую следующий метод:
(на примере функций из User и Gdi)

type
 TGetWindowRectProc = function (Wnd: HWND; Rect: PRect): Integer; stdcall;

type
 TInterceptHookData = packed record
   JmpOpCode : Byte;
   JmpAddress : Pointer;
   RetOpCode : Byte;
 end;
 TInterceptData = packed record
   Hook : TInterceptHookData;
   SaveCode : array[0..5] of Byte;
   ProcAddress : Pointer;
   Protection : DWORD;
 end;

procedure InitInterceptData (var InterceptData : TInterceptData;
  HookProc : Pointer);
begin
 with InterceptData.Hook do
 begin
   JmpOpCode := $68;
   JmpAddress := HookProc;
   RetOpCode := $C3;
 end;
end;

function InitInterceptDataEx (var InterceptData : TInterceptData;
  HookProc: Pointer; const AModuleName, AProcName: string): Boolean;
begin
 InitInterceptData(InterceptData, HookProc);
 InterceptData.ProcAddress := GetProcAddress(
   GetModuleHandle(PChar(AModuleName)), PChar(AProcName));
 Result := InterceptData.ProcAddress <> nil;
 if Result then begin
   VirtualProtect(InterceptData.ProcAddress, SizeOf(InterceptData.SaveCode),
      PAGE_EXECUTE_WRITECOPY, InterceptData.Protection);
   Move(InterceptData.ProcAddress^, InterceptData.SaveCode,
      SizeOf(InterceptData.SaveCode));
   Move(InterceptData.Hook, InterceptData.ProcAddress^,
      SizeOf(InterceptData.Hook));
 end;
end;

procedure DoneInterceptData (const InterceptData: TInterceptData);
var
 Dummy : DWORD;
begin
 with InterceptData do
   if Assigned(ProcAddress) then
   begin
     VirtualProtect(ProcAddress, SizeOf(SaveCode), PAGE_EXECUTE_WRITECOPY, Dummy);
     Move(SaveCode, ProcAddress^, SizeOf(SaveCode));
     VirtualProtect(ProcAddress, SizeOf(SaveCode), Protection, Dummy);
   end;
end;

Сам перехватчик (вполне конкретная функция)

var
 GetWindowRectHook: TInterceptData;

function HookedGetWindowRect (Wnd: HWND; Rect: PRect): Integer; stdcall;
var
 Callee: LongInt;
 OldProc: TGetWindowRectProc;
 CallString: string;
begin
 asm
   push dword ptr [ebp+4]
   pop  Callee
 end;
 Inc(Level);
 CallString := Format("GetWindowRect;(%s,%s);[%s]",
   [DecodeWindowHandle(Wnd),DecodeRectPtr(Rect), DecodeAddress(Callee)]);
 with GetWindowRectHook do
 begin
   Move(SaveCode, ProcAddress^, SizeOf(SaveCode));
   @OldProc := ProcAddress;
   Result := OldProc(Wnd,Rect);
   Move(Hook, ProcAddress^, SizeOf(Hook));
 end;
 if LogOpened then
   writeln(LogF, Level, ";",Format("%s;returns %s (%s)", [CallString,
     DecodeBool(Result), DecodeRectPtr(Rect)]));
 Dec(Level);
end;

initialization
 if EnableHooks and (Win32Platform = VER_PLATFORM_WIN32_NT) then
 begin
   InitInterceptDataEx(GetWindowRectHook, @HookedGetWindowRect,
     "user32.dll", "GetWindowRect");
   .....
 end;
finalization
 if EnableHooks and (Win32Platform = VER_PLATFORM_WIN32_NT) then
 begin
   DoneInterceptData(GetWindowRectHook);
   ...
 end;
end.

Пользуюсь уже много лет, а именно 6, в однопоточных приложениях


 
Сергей М. ©   (2008-05-04 17:15) [44]


> Дмитрий С   (02.05.08 20:26) [32]


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

Кр.того, получение адреса интересующей тебя ф-ции возможно и вызовом ф-ции kernel32.GetProcAddress, которая обращается к EAT модуля, экспортирующего перехватываемую тобой ф-цию. Значит, требуется либо модифицировать соотв.элемент EAT либо модифицировать IAT модуля kernel32 с целью перехвата т.входа в GetProcAddress, чтобы отслеживать запросы адреса перехватываемой тобой ф-ции и возвращать результатом нужный тебе адрес.



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

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

Наверх




Память: 0.61 MB
Время: 0.015 c
15-1209242005
TIF
2008-04-27 00:33
2008.06.15
InfoNetПоиск - пробная альфа-версия


2-1211180244
Canord
2008-05-19 10:57
2008.06.15
Конвертирования переменной из OleVariant в String


3-1199891890
Kolan
2008-01-09 18:18
2008.06.15
При Open у TQuery c RequestLive = True получаю AV&amp;#133


15-1209715515
Slider007
2008-05-02 12:05
2008.06.15
С днем рождения ! 2 мая 2008 пятница


15-1209883609
Maximus2002
2008-05-04 10:46
2008.06.15
Редактор для CommandText в RAD Studio 2007