Перейти из форума на сайт.

НовостиФайловые архивы
ПоискАктивные темыТоп лист
ПравилаКто в on-line?
Вход Забыли пароль? Первый раз на этом сайте? Регистрация
Компьютерный форум Ru.Board » Компьютеры » Прикладное программирование » Вопросы по Delphi (все версии) - часть 3

Модерирует : ShIvADeSt

ShIvADeSt (07-04-2008 02:27): продолжение в http://forum.ru-board.com/topic.cgi?forum=33&topic=9383  Версия для печати • ПодписатьсяДобавить в закладки
На первую страницук этому сообщениюк последнему сообщению

   

delover

Advanced Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору

Код:
program undeb32;
 
{$APPTYPE CONSOLE}
 
{%File 'build.bat'}
 
uses
  Windows,
  SysUtils,
  Classes,
  TlHelp32,
  ImageHlp,
  JclPeImage;
 
{$R *.res}
 
const
  PackageInfoResName = 'PACKAGEINFO';
 
var
  DebugPause: Boolean = False;
  ExeFile: string = '';
  ReportFile: string = '';
  SetChecksum: Boolean = False;
  ShowingFlag: Boolean = False;
  ShowMessages: Boolean = True;
  UseReport: Boolean = False;
 
procedure ShowCopyright;
begin
  if ShowingFlag then Exit;
  ShowingFlag := True;
  Writeln('UNDEB32 (C)  subreal.PIN  Check Utility.  Version 1.03  13-04-2007');
  Writeln('Copr. 2006-2007 Roman Silin. All Rights Reserved. Freeware Version');
  Writeln;
end;
 
procedure ShowUsage;
begin
  ShowCopyright;
  Writeln('Usage:  UNDEB32 exefile [/p | /n] [/c | /m [reportfile]]');
end;
 
procedure Pause;
begin
  if not DebugPause then exit;
  if not ShowMessages then exit;
  Writeln('Press enter...');
  Readln;
end;
 
procedure ShowError(const Msg: string; const Args: array of const);
begin
  ShowUsage;
  Writeln;
  Writeln('Error: ', Format(Msg, Args));
  Pause;
  Halt(0);
end;
 
procedure GetParameters;
var
  I: Integer;
  S: string;
begin
  ExeFile := ExpandFileName(ParamStr(1));
  I := 2;
  repeat
    S := ParamStr(I);
    if S = '' then Break;
    if (S[1] <> '/') and (S[1] <> '-') then
      ShowError('Unknown parameter "%s"', [S]);
    Delete(S, 1, 1);
    if S = '' then Break;
    case UpCase(S[1]) of
      'P': DebugPause := True;
      'N': ShowMessages := False;
      'C': SetChecksum := True;
      'M': UseReport := True;
    end;
    Inc(I);
    if UseReport then
    begin
      ReportFile := ParamStr(I);
      if ReportFile = '' then
        ReportFile := ChangeFileExt(ExeFile, '.rep') else
      if ExtractFileName(ReportFile) = ReportFile then
        ReportFile := ExtractFilePath(ExeFile) + ReportFile else
        ReportFile := ExpandFileName(ReportFile);
      Break;
    end;
  until 0 <> 0;
  if not FileExists(ExeFile) then
    ShowError('File not found "%s"', [ExeFile]);
end;
 
{$IFDEF REGION}{$REGION ' [ TJclPeBorInfo ] '}{$ENDIF}
type
  TJclPeBorInfoProgress = procedure(Position: DWORD);
  TJclPeBorInfo = class(TJclPeBorImage)
  private
    FBaseOfCode: DWORD;
    FEntryImport: DWORD;
    FExcludeUnits: TStringList;
    FMainUnit: string;
    FOnProgress: TJclPeBorInfoProgress;
    FPackageInfoData: DWORD;
    FPackageInfoSize: DWORD;
    FSearchResult: TStringList;
    FShortString: Boolean;
    FTempContains: TStringList;
    function GetFileName: TFileName;
    function GetOffset(OffseToData: DWORD): DWORD;
    function GetSearchCount: Integer;
    function GetSearchData(Index: Integer): DWORD;
    function GetSearchNames(Index: Integer): string;
    procedure SetFileName(const Value: TFileName);
  protected
    procedure ClearSearch;
    function DoSearch(const StartSearch, EndSearch: DWORD): Boolean;
  public
    constructor Create(ANoExceptions: Boolean = False); override;
    destructor Destroy; override;
    function CodeSearch: Boolean;
    function HasPackages: Boolean;
    function PackageInfoSearch: DWORD;
    property BaseOfCode: DWORD read FBaseOfCode;
    property EntryImport: DWORD read FEntryImport;
    property ExcludeUnits: TStringList read FExcludeUnits;
    property FileName: TFileName read GetFileName write SetFileName;
    property MainUnit: string read FMainUnit;
    property OnProgress: TJclPeBorInfoProgress read FOnProgress write FOnProgress;
    property PackageInfoData: DWORD read FPackageInfoData;
    property PackageInfoSize: DWORD read FPackageInfoSize;
    property SearchCount: Integer read GetSearchCount;
    property SearchData[Index: Integer]: DWORD read GetSearchData;
    property SearchNames[Index: Integer]: string read GetSearchNames;
  end;
 
{ TJclPeBorInfo }
 
procedure TJclPeBorInfo.ClearSearch;
var
  I: Integer;
begin
  FMainUnit := '';
  for I := 0 to FTempContains.Count - 1 do
    FreeMem(Pointer(FTempContains.Objects[I]));
  FTempContains.Clear;
  FSearchResult.Clear;
end;
 
function TJclPeBorInfo.CodeSearch: Boolean;
begin
  FShortString := True;
  Result := DoSearch(BaseOfCode, EntryImport);
end;
 
constructor TJclPeBorInfo.Create(ANoExceptions: Boolean);
begin
  inherited Create(ANoExceptions);
  FExcludeUnits := TStringList.Create;
  FSearchResult := TStringList.Create;
  FTempContains := TStringList.Create;
end;
 
destructor TJclPeBorInfo.Destroy;
begin
  ClearSearch;
  FTempContains.Free;
  FSearchResult.Free;
  FExcludeUnits.Free;
  inherited Destroy;
end;
 
function TJclPeBorInfo.DoSearch(const StartSearch, EndSearch: DWORD): Boolean;
 
  function DoTempContains: Integer;
  var
    I, L: Integer;
    S: ShortString;
    P: PShortString;
  begin
    Result := 0;
    ClearSearch;
    if FindResource(LibHandle, PackageInfoResName, RT_RCDATA) <> 0 then
    begin
      FTempContains.AddStrings(PackageInfo.Contains);
      for I := 0 to FTempContains.Count - 1 do
        if (PackageInfo.ContainsFlags[I] and ufMainUnit) <> 0 then
        begin
          FMainUnit := FTempContains[I];
          Break;
        end;
      if FShortString then
        for I := 0 to ExcludeUnits.Count - 1 do
        begin
          L := FTempContains.IndexOf(ExcludeUnits[I]);
          if L >= 0 then FTempContains.Delete(L);
        end;
      for I := 0 to FTempContains.Count - 1 do
      begin
        S := FTempContains[I];
        L := Length(S) + 1;
        GetMem(P, L);
        if FShortString then
          P^ := S else
          Move(PChar(FTempContains[I])^, P^, L);
        FTempContains.Objects[I] := Pointer(P);
        if Result < L then Result := L;
      end;
    end;
    if MainUnit = '' then
      FMainUnit := ChangeFileExt(ExtractFileName(FileName), '');
  end;
 
var
  I: DWORD;
  J, L, M, Step: Integer;
  P: Pointer;
  A: Byte;
  T, S: PShortString;
begin
  Result := False;
  M := DoTempContains;
  P := RawToVa(0);
  I := StartSearch + 2;
  Inc(DWORD(P), I);
  T := nil;
  repeat
    Step := 1;
    A := Byte(P^);
    if (A <> 0) and ((A < M) or not FShortString) then
      for J := 0 to FTempContains.Count - 1 do
      begin
        S := Pointer(FTempContains.Objects[J]);
        if FShortString then
          L := Length(S^) else
          L := StrLen(PChar(S));
        if not CompareMem(P, @S^[0], L + 1) then Continue;
        if FShortString then
          FSearchResult.AddObject(S^, TObject(I)) else
          FSearchResult.AddObject(PChar(S), TObject(I));
        if (T = nil) then T := S else
        if (T <> S) then
        begin
          FreeMem(T);
          FTempContains.Delete(FTempContains.IndexOfObject(Pointer(T)));
          T := S;
        end;
        Inc(Step, L);
        Result := True;
        if Assigned(FOnProgress) then
          FOnProgress(I);
        Break;
      end;
    Inc(I, Step); Inc(DWORD(P), Step);
  until I >= EndSearch - 2;
  if Assigned(FOnProgress) then
    FOnProgress(EndSearch);
end;
 
function TJclPeBorInfo.GetFileName: TFileName;
begin
  Result := inherited FileName;
end;
 
function TJclPeBorInfo.GetOffset(OffseToData: DWORD): DWORD;
var
  I: Int64;
begin
  I := DWORD(RvaToVa(OffsetoData)) -
    DWORD(LoadedImage.MappedAddress);
  Result := I;
end;
 
function TJclPeBorInfo.GetSearchCount: Integer;
begin
  Result := FSearchResult.Count;
end;
 
function TJclPeBorInfo.GetSearchData(Index: Integer): DWORD;
begin
  if (Index >=0) and (Index < SearchCount) then
    Result := DWORD(FSearchResult.Objects[Index]) else
    Result := 0;
end;
 
function TJclPeBorInfo.GetSearchNames(Index: Integer): string;
begin
  if (Index >=0) and (Index < SearchCount) then
    Result := FSearchResult[Index] else
    Result := '';
end;
 
function TJclPeBorInfo.HasPackages: Boolean;
var
  I: Integer;
begin
  for I := 0 to ImportList.Count - 1 do
  begin
    Result := SameText(ExtractFileExt(
      ImportList[i].Name), '.bpl');
    if Result then Exit;
  end;
  Result := False;
end;
 
function TJclPeBorInfo.PackageInfoSearch: DWORD;
var
  I: Integer;
  SaveOnProgress: TJclPeBorInfoProgress;
begin
  Result := 0;
  SaveOnProgress := FOnProgress;
  FOnProgress := nil;
  FShortString := False;
  if DoSearch(PackageInfoData, PackageInfoData + PackageInfoSize) then
  begin
    I := FSearchResult.IndexOf(MainUnit);
    if I = 0 then
      Result := SearchData[I];
  end;
  FOnProgress := SaveOnProgress;
end;
 
procedure TJclPeBorInfo.SetFileName(const Value: TFileName);
var
  ResItem: TJclPeResourceItem;
begin
  inherited FileName := Value;
 
//get start..end of Code
  FBaseOfCode := GetOffset(StrToInt('$' +
    HeaderValues[JclPeHeader_BaseOfCode]));
  FEntryImport := GetOffset(Directories[
    IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
 
//get start..end of PackageInfo
  ResItem := ResourceList.FindResource(rtRCData, PackageInfoResName);
  if ResItem <> nil then
  begin
    FPackageInfoData := GetOffset(ResItem.List[0].DataEntry.OffsetToData);
    FPackageInfoSize := ResItem.List[0].DataEntry.Size - 1;
  end else
  begin
    FPackageInfoData := 0;
    FPackageInfoSize := 0;
  end;
end;
{$IFDEF REGION}{$ENDREGION}{$ENDIF}
 
var
  BorInfo: TJclPeBorInfo;
  MemoryStream: TMemoryStream;
  ReportList: TStringList;
  SuccessOK: Boolean = False;
 
procedure LoadExeFile;
begin
  MemoryStream := TMemoryStream.Create;
  MemoryStream.LoadFromFile(ExeFile);
  BorInfo := TJclPeBorInfo.Create;
  BorInfo.FileName := ExeFile;
  BorInfo.ExcludeUnits.Add('Options');
  ReportList := TStringList.Create;
end;
 
procedure FreeExeFile;
begin
  ReportList.Free;
  BorInfo.Free;
  MemoryStream.Free;
end;
 
function DoCheckSum: Boolean;
var
  HeaderSum, CheckSum: DWORD;
  ImageNtHeaders: PImageNtHeaders;
begin
  Result := False;
  CheckSum := 0;
  ImageNtHeaders := CheckSumMappedFile(MemoryStream.Memory,
    MemoryStream.Size, @HeaderSum, @CheckSum);
  if ImageNtHeaders = nil then Exit;
  ImageNtHeaders.OptionalHeader.CheckSum := CheckSum;
  Result := True;
end;
 
procedure ShowStartEnd;
begin
  ReportList.Clear;
  with BorInfo do
    ReportList.Add(Format('Base of code .. entry import: %x..%x' +
      '; package info: %x..%x', [BaseOfCode, EntryImport,
      PackageInfoData, PackageInfoData + PackageInfoSize]));
  ReportList.Add('');
  if not ShowMessages then Exit;
  Writeln(ReportList[0]);
  Writeln(ReportList[1]);
end;
 
procedure ShowProgress(Position: DWORD);
var
  S, S1, T, U: string;
  I: Integer;
begin
  with BorInfo do
    S := Format('Count: %d (%d%%)', [SearchCount,
      (Position - BaseOfCode)*100 div (EntryImport - BaseOfCode)]);
  if Position = BorInfo.EntryImport then
  begin
    T := '';
    for I := 0 to BorInfo.SearchCount - 1 do
    begin
      U := BorInfo.SearchNames[I];
      if (Length(U) < 4) and (U <> 'DB') then Continue;
      if T <> U then
      begin
        if T <> '' then
          ReportList.Add(Format('%s: %s', [T, S1]));
        S1 := '';
        T := U;
      end;
      if S1 <> '' then
        S1 := S1 + ', ';
      S1 := S1 + Format('%x', [BorInfo.SearchData[I]]);
    end;
    if T <> '' then
      ReportList.Add(Format('%s: %s', [T, S1]));
    ReportList.Add(S);
  end;
  if not ShowMessages then Exit;
  Write(#13, S);
  if Position = BorInfo.EntryImport then
    Writeln;
end;
 
function UnDebugImage: Boolean;
const
  IllegalChars = '"*<>?|';
var
  I, J: Integer;
  P: PShortString;
begin
{ Find names of code }
  BorInfo.OnProgress := ShowProgress;
  Result := BorInfo.CodeSearch;
  if not Result then Exit;
 
{ Work }
  Randomize;
  for I := 0 to BorInfo.SearchCount - 1 do
  begin
    P := MemoryStream.Memory;
    Inc(DWORD(P), BorInfo.SearchData[I]);
    if (Length(P^) < 4) and (P^ <> 'DB') then Continue;
    for J := 1 to Length(P^) do
      P^[J] := Char(Random(221) + 33);
    P^[Random(Length(P^)) + 1] :=
      IllegalChars[Random(Length(IllegalChars)) + 1];
  end;
end;
 
procedure UnDebugPackageInfo;
var
  I, J, M: DWORD;
  P: Pointer;
begin
{ Find names of package info }
  M := BorInfo.PackageInfoSearch;
  if M = 0 then Exit;
 
{ Store one name of package and find start }
  P := MemoryStream.Memory;
  Inc(DWORD(P), M - 6);
  DWORD(P^) := 1;
 
{ Work }
  Randomize;
  for I := 1 to BorInfo.SearchCount - 1 do
  begin
    P := MemoryStream.Memory;
    Inc(DWORD(P), BorInfo.SearchData[I]);
    for J := 1 to Length(BorInfo.SearchNames[I]) do
    begin
      Byte(P^) := Byte(Random(221) + 33);
      Inc(DWORD(P));
    end;
    Byte(P^) := $FF;
  end;
end;
 
procedure ShowAndStoreFiles;
var
  S, S1: string;
  I, F: Integer;
begin
  ReportList.Add('');
  F := ReportList.Count;
  if SetChecksum then
    S := ChangeFileExt(ExeFile, '~.bak') else
    S := ChangeFileExt(ExeFile, '.bak');
  DeleteFile(S);
  RenameFile(ExeFile, S);
  MemoryStream.SaveToFile(ExeFile);
  if SetChecksum then
    S1 := 'checksum' else
    S1 := Format('"%s"', [BorInfo.MainUnit]);
  ReportList.Add(Format('Success %s. Copy to file "%s". Save "%s". OK', [
    S1, ExtractFileName(S), ExtractFileName(ExeFile)]));
  if ShowMessages then
    for I := F to ReportList.Count - 1 do
      Writeln(ReportList[I]);
  if not UseReport then Exit;
  DeleteFile(ReportFile);
  ReportList.SaveToFile(ReportFile);
end;
 
{$IFDEF REGION}{$REGION ' [ UnkStart ] '}{$ENDIF}
function DequotedStr(const S: string): string;
var
  P: Integer;
  D: string;
begin
  if (S <> '')and(S[1] = '"') then
  begin
    D := Copy(S, 2, MaxInt);
    P := Pos('"', D);
    if P > 0 then
      SetLength(D, P-1) else
      D := S;
    Result := D;
  end else
    Result := S;
end;
 
function TermProcess(const FileName: string): Boolean;
const
  TH32CS_SNAPPROCESS  = $00000002;
var
  SH: THandle;
  TP: TProcessEntry32;
  LID, PID: DWORD;
  I: Integer;
  ExeFile, S: string;
begin
  Result := False;
  if FileName = '' then Exit;
  ExeFile := UpperCase(ExtractFileName(DequotedStr(FileName)));
  SH := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if Integer(SH) < 0 then Exit;
  TP.dwSize := SizeOf(TProcessEntry32);
  if not Process32First(SH, TP) then Exit;
  PID := 0;
  LID := 0;
  for I := 0 to 999 do
  begin
    S := UpperCase(TP.szExeFile)+'[';
    if Pos('[', S) <> 1 then
      if Pos(ExeFile, S) > 0 then
      begin
        LID := PID;
        PID := TP.th32ProcessID;
      end;
    if not Process32Next(SH, TP) then Break;
  end;
  if PID = 0 then Exit;
  if (LID <> 0) and SameText(ExeFile, ExtractFileName(ParamStr(0))) then
    PID := LID;
  SH := OpenProcess(PROCESS_ALL_ACCESS, True, PID);
  Result := TerminateProcess(SH, 0);
  CloseHandle(SH);
end;
 
function GetFileVerValueName(const AFileName, AValueName: string): string;
var
  S: string;
  InfoSize, Wnd: DWORD;
  VerBuf, P: Pointer;
  VerSize: DWORD;
begin
  Result := '';
  // GetFileVersionInfo modifies the filename parameter data while parsing.
  // Copy the string const into a local variable to create a writeable copy.
  S := AFileName;
  UniqueString(S);
  InfoSize := GetFileVersionInfoSize(PChar(S), Wnd);
  if InfoSize <> 0 then
  begin
    GetMem(VerBuf, InfoSize);
    try
      if not GetFileVersionInfo(PChar(S), Wnd, InfoSize, VerBuf) then
        Exit;
      if not VerQueryValue(VerBuf, '\VarFileInfo\Translation', P, VerSize) then
        Exit;
      S := Format('%.8x', [Integer(P^)]);
      S := Format('\StringFileInfo\%s%s\%s',
        [Copy(S, 5, 4), Copy(S, 1, 4), AValueName]);
      if VerQueryValue(VerBuf, PChar(S), P, VerSize) then
        Result:= PChar(P);
    finally
      FreeMem(VerBuf);
    end;
  end;
end;
 
function FindPrevProcess(const ExeFile: string): string;
const
  TH32CS_SNAPPROCESS  = $00000002;
var
  SH: THandle;
  TP: TProcessEntry32;
  TM: TModuleEntry32;
  PID: DWORD;
  I: Integer;
  S: string;
begin
  Result := '';
  if ExeFile = '' then Exit;
  SH := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if Integer(SH) < 0 then Exit;
  TP.dwSize := SizeOf(TProcessEntry32);
  if not Process32First(SH, TP) then Exit;
  PID := 0;
  for I := 0 to 999 do
  begin
    S := UpperCase(TP.szExeFile);
    if SameText(ExeFile, S) then
      PID := TP.th32ParentProcessID;
    if not Process32Next(SH, TP) then Break;
  end;
  if PID = 0 then Exit;
  if not Process32First(SH, TP) then Exit;
  S := '';
  for I := 0 to 999 do
  begin
    if PID = TP.th32ProcessID then
    begin
      S := TP.szExeFile;
      Break;
    end;
    if not Process32Next(SH, TP) then Break;
  end;
  CloseHandle(SH);
  SH := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, PID);
  if Integer(SH) < 0 then Exit;
  TM.dwSize := SizeOf(TModuleEntry32);
  if not Module32First(SH, TM) then Exit;
  for I := 0 to 999 do
  begin
    if SameText(TM.szModule, S) then
    begin
      Result := TM.szExePath;
      Break;
    end;
    if not Module32Next(SH, TM) then Break;
  end;
end;
 
function GetPrevVersionName(const AValueName: string;
  var AProcessName: string): string;
begin
  AProcessName := FindPrevProcess(UpperCase(
    ExtractFileName(ParamStr(0))));
  if AProcessName <> '' then
    Result := GetFileVerValueName(AProcessName,
      AValueName) else
    Result := '';
end;
 
procedure GetVirtualProcess(var AValue: string;
  const AProcessName: string);
var
  PeImage: TJclPeImage;
  LibItem: TJclPeImportLibItem;
  I, J: Integer;
begin
  PeImage := TJclPeImage.Create;
  try
    PeImage.FileName := AProcessName;
    PeImage.TryGetNamesForOrdinalImports;
    for I := 0 to PeImage.ImportList.Count - 1 do
    begin
      LibItem := PeImage.ImportList[i];
      if not SameText(Copy(LibItem.Name, 1, 8), 'kernel32') then Continue;
      for J := 0 to LibItem.Count - 1 do
        if SameText(Copy(LibItem.Items[J].Name, 1, 8), 'virtualp') then
        begin
          AValue := '';
          Exit;
        end;
    end;
  finally
    PeImage.Free;
  end;
end;
 
function UnkStart(const AValueName: string): string;
var
  S: string;
begin
  S := GetPrevVersionName(AValueName, Result);
  if (S <> '') and (Result <> '') then
    GetVirtualProcess(S, Result);
  if (S = '') and (Result <> '') then
  begin
    Result := UpperCase(ExtractFileName(Result));
    S := Copy(ChangeFileExt(Result, ''), 3, MaxInt);
    if (Length(S) = 1) and (S[1] <> 'R') and
      (Copy(Result, 1, 2) <> 'FA') then Exit;
    TermProcess(Result);
    Halt;
  end;
end;
{$IFDEF REGION}{$ENDREGION}{$ENDIF}
 
begin
  UnkStart('ProductName');
  if (ParamCount < 1) or SameText(ParamStr(1), '/p') then
  begin
    ShowUsage;
    DebugPause := ParamCount > 0;
    Pause;
    exit;
  end;
  GetParameters;
  if ShowMessages then
    ShowCopyright;
  try
    LoadExeFile;
 
    if SetChecksum then
      SuccessOK := DoCheckSum else
    begin
      if not BorInfo.HasPackages then
        ShowStartEnd else
        ShowError('Build with runtime packages', []);
      if BorInfo.PackageInfoSize <> 0 then
        SuccessOK := UnDebugImage else
        ShowError('Package info not found', []);
      if SuccessOK then
        UnDebugPackageInfo;
    end;
 
    if SuccessOK then
    begin
      BorInfo.FreeLibHandle;
      ShowAndStoreFiles;
    end else
      ShowError('File already undebug', []);
 
    FreeExeFile;
  except
    on E: Exception do
      ShowError(E.Message, []);
  end;
 
  Pause;
end.

Всего записей: 1395 | Зарегистр. 25-06-2007 | Отправлено: 14:23 22-01-2008 | Исправлено: delover, 16:33 03-03-2008
   

На первую страницук этому сообщениюк последнему сообщению

Компьютерный форум Ru.Board » Компьютеры » Прикладное программирование » Вопросы по Delphi (все версии) - часть 3
ShIvADeSt (07-04-2008 02:27): продолжение в http://forum.ru-board.com/topic.cgi?forum=33&topic=9383


Реклама на форуме Ru.Board.

Powered by Ikonboard "v2.1.7b" © 2000 Ikonboard.com
Modified by Ru.B0ard
© Ru.B0ard 2000-2024

BitCoin: 1NGG1chHtUvrtEqjeerQCKDMUi6S6CG4iC

Рейтинг.ru