Как получить доступ к приватным методам без помощников?

В Delphi 10 Seattle я мог использовать следующий код, чтобы обойти чрезмерно строгие ограничения видимости.

Как мне получить доступ к приватным переменным?

type 
  TBase = class(TObject)
  private
    FMemberVar: integer;
  end;

И как мне получить доступ к обычным или виртуальным частным методам?

type
  TBase2 = class(TObject) 
  private
    procedure UsefullButHidden;  
    procedure VirtualHidden; virtual;
    procedure PreviouslyProtected; override;
  end;

Ранее я использовал вспомогательный класс, чтобы взломать базовый класс.

type
  TBaseHelper = class helper for TBase
    function GetMemberVar: integer;

В Delphi 10.1 Berlin помощники класса больше не имеют доступа к закрытым членам предметного класса или записи.

Есть ли альтернативный способ доступа к частным пользователям?

5 ответов

Решение

Если есть расширенная информация RTTI, сгенерированная для закрытых членов класса - полей и / или методов, вы можете использовать ее для получения доступа к ним.

Конечно, доступ через RTTI намного медленнее, чем через классных помощников.

Доступ к методам:

var
  Base: TBase2;
  Method: TRttiMethod;

  Method := TRttiContext.Create.GetType(TBase2).GetMethod('UsefullButHidden');
  Method.Invoke(Base, []);

Доступ к переменным:

var
  Base: TBase;
  v: TValue;

  v := TRttiContext.Create.GetType(TBase).GetField('FMemberVar').GetValue(Base);

Информация RTTI по ​​умолчанию, сгенерированная для классов RTL/VCL/FMX, следующая

  • Поля - private, protected, public, published
  • Методы - public, published
  • Свойства - public, published

К сожалению, это означает, что доступ к закрытым методам через RTTI для основных библиотек Delphi недоступен. Ответ @LU RD охватывает хак, который разрешает доступ к частным методам для классов без расширенного RTTI.

Работа с RTTI

Есть еще способ использовать class helpers для доступа к приватным методам в Delphi 10.1 Berlin:

type
  TBase2 = class(TObject) 
  private
    procedure UsefullButHidden;  
    procedure VirtualHidden; virtual;
    procedure PreviouslyProtected; override;
  end;

  TBase2Helper = class helper for TBase2
    procedure OpenAccess;
  end;

  procedure TBase2Helper.OpenAccess;
  var
    P : procedure of object;
  begin
    TMethod(P).Code := @TBase2.UsefullButHidden;
    TMethod(P).Data := Self;
    P; // Call UsefullButHidden;
    // etc
  end;

К сожалению, у Delphi 10.1 Berlin нет доступа к строгим приватным / приватным полям помощникам класса. RTTI - вариант, но его можно считать медленным, если производительность критична.

Вот способ определить смещение поля при запуске, используя помощники классов и RTTI:

type 
  TBase = class(TObject)
  private  // Or strict private
    FMemberVar: integer;
  end;

type
  TBaseHelper = class helper for TBase
  private
    class var MemberVarOffset: Integer;
    function GetMemberVar: Integer;
    procedure SetMemberVar(value: Integer);
  public
    class constructor Create;  // Executed at program start
    property MemberVar : Integer read GetMemberVar write SetMemberVar;
  end;

class constructor TBaseHelper.Create;
var
  ctx: TRTTIContext;
begin
  MemberVarOffset := ctx.GetType(TBase).GetField('FMemberVar').Offset;
end;

function TBaseHelper.GetMemberVar: Integer;
begin
  Result := PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^;
end;

procedure TBaseHelper.SetMemberVar(value: Integer);
begin
  PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^ := value;
end;

Это будет иметь преимущество в том, что медленная часть RTTI выполняется только один раз.


Примечание. Использование RTTI для доступа к защищенным / закрытым методам.

RTL/VCL/FMX не объявили видимость для доступа к защищенным / закрытым методам с RTTI. Он должен быть установлен локальной директивой {$ RTTI}.

Использование RTTI для доступа к закрытым / защищенным методам в другом коде требует, например, установки:

{$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}

Если вам нужен чистый способ, который не влияет на производительность, вы все равно можете получить доступ к закрытым полям из помощника записи, используя оператор with.

function TValueHelper.GetAsInteger: Integer;
begin
  with Self do begin
    Result := FData.FAsSLong;
  end;
end;

Я надеюсь, что они оставят этот метод открытым, потому что у нас есть код с высокими требованиями к производительности.

Если предположить, что расширенный RTTI недоступен, то, не прибегая к тому, что можно было бы считать взломом, вы не сможете получить доступ к закрытым членам из кода в другом модуле. Конечно, если RTTI доступен, его можно использовать.

Насколько я понимаю, возможность взломать частных пользователей с помощью помощников была непреднамеренной случайностью. Предполагается, что приватные члены будут видны только из кода в том же модуле, а строгие приватные члены будут видны только из кода в том же классе. Это изменение исправляет аварию.

Без возможности заставить компилятор взломать класс для вас, вам придется прибегнуть к другим способам сделать это. Например, вы могли бы повторно объявить достаточно TBase класс, чтобы можно было обмануть компилятор, чтобы он говорил вам, где живет член.

type
  THackBase = class(TObject)
  private
    FMemberVar: integer;
  end;

Теперь вы можете написать

var
  obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;  

Но это ужасно хрупко и сломается, как только макет TBase изменено

Это будет работать для членов данных, но для не виртуальных методов вам, вероятно, придется использовать методы разборки во время выполнения, чтобы найти местоположение кода. Для виртуальных членов этот метод может быть использован для поиска смещения VMT.

Дальнейшее чтение:

Если вам не нужна поддержка компилятора ARM, вы можете найти другое решение здесь.

С помощью встроенного асемблера вы можете легко получить доступ к закрытому полю или методу.

Я думаю, что ответ Дэвида лучше в большинстве случаев, но если вам нужно быстрое решение для огромного класса, этот метод может быть более полезным.

Обновление (17 июня): Я только что заметил, я забыл поделиться его примером кода для доступа к закрытым полям из его сообщения. извиняюсь.

unit UnitA;

type
  THoge = class
  private
    FPrivateValue: Integer;
    procedure PrivateMethod;
  end;
end.

unit UnitB;

type
  THogeHelper = class helper for THoge
  public
    function GetValue: Integer;
    procedure CallMethod;
  end;

function THogeHelper.GetValue: Integer;
asm
  MOV EAX,Self.FPrivateValue
end;

procedure THogeHelper.CallMethod;
asm
  CALL THoge.PrivateMethod
end;

Вот его пример кода для вызова приватного метода.

type
  THoge = class
  private
    procedure PrivateMethod (Arg1, Arg2, Arg3 : Integer);
  end;

// Method 1
// Get only method pointer (if such there is a need to assign a method pointer to somewhere)
type
  THogePrivateProc = procedure (Self: THoge; Arg1, Arg2, Arg3: Integer);
  THogePrivateMethod = procedure (Arg1, Arg2, Arg3: Integer) of object;

function THogeHelper.GetMethodAddr: Pointer;
asm
  {$ifdef CPUX86}
  LEA EAX, THoge.PrivateMethod
  {$else}
  LEA RAX, THoge.PrivateMethod
  {$endif}
end;

var
  hoge: THoge;
  proc: THogePrivateProc;
  method: THogePrivateMethod;
begin
  // You can either in here of the way,
  proc := hoge.GetMethodAddr;
  proc (hoge, 1, 2, 3);
  // Even here of how good
  TMethod (method) .Code := hoge.GetMethodAddr;
  TMethod (method) .Data := hoge;
  method (1, 2, 3) ;
end;

// Method 2
// To jump (here is simple if you just simply call)
procedure THogeHelper.CallMethod (Arg1, Arg2, Arg3 : Integer);
asm
  JMP THoge.PrivateMethod
end;

unit UnitA;

type
  THoge = class
  private
    FPrivateValue: Integer;
    procedure PrivateMethod;
  end;
end.

Просто используйте оператор with для доступа к закрытым полям!

См. Пример кода ниже, взятый из этой статьи, которую я заметил сегодня. (Спасибо, господин ДЕКО, как всегда!)

Об этом взломе изначально сообщалось на QualityPortal в августе 2019 года, как описано в статье выше. (требуется логин)


перед перезаписью (методом "asm")

function TPropertyEditorHelper.GetPropList: PInstPropList;
{$IF CompilerVersion < 31.0}
begin
  Result := Self.FPropList;
end;
{$ELSE}
// http://d.hatena.ne.jp/tales/20160420/1461081751
asm
  MOV EAX, Self.FPropList;
end;
{$IFEND}

переписать, используя 'с'

function TPropertyEditorHelper.GetPropList: PInstPropList;
begin
  with Self do
    Result := FPropList;
end;

Я был поражен, что это так просто:-)

Другие вопросы по тегам