Как получить доступ к приватным методам без помощников?
В 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.
Есть еще способ использовать 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;
Я был поражен, что это так просто:-)