我正在研究RAD Studio 2007中的一个项目,使用c ++中的VCL类.
TDBLookupControl是VCL的一部分,并且有一些不良行为,这是由使用内部变量引起的 SearchTickCount
var
SearchTickCount: Integer = 0; //file scope in DBCtrls.pas
procedure TDBLookupControl.ProcessSearchKey(Key: Char);
var
TickCount: Integer;
S: string;
begin
//some code removed for brevity
TickCount := GetTickCount;
if TickCount - SearchTickCount > 2000 then SearchText := '';
SearchTickCount := TickCount;
//some code removed for brevity
end;
Run Code Online (Sandbox Code Playgroud)
但是,SearchTickCount
从未在VCL内部进行PACKAGEd,如下例所示.
extern PACKAGE int SearchTickCount;
Run Code Online (Sandbox Code Playgroud)
我想SearchTickCount
在我的c ++代码中设置为零(按需).在我的代码中对它进行外部化使得c ++编译.但是,链接器(显然)找不到变量.
namespace Dbctrls
{
extern int SearchTickCount;
}
// later on, inside a function
Dbctrls::SearchTickCount = 0;
Run Code Online (Sandbox Code Playgroud)
有没有办法/解决方法来链接到这个变量?
编辑:不幸的是,我们还使用了一些源自TDBLookupControl的自定义控件,所以我想避免创建更多的自定义控件.
SearchTickCount
是在单元的实现部分中声明的全局(单元级)变量,它不应该在该单元之外访问.如果你使用Delphi而不是C++ Builder,你会遇到同样的问题.
TDBLookupControl
,覆盖ProcessSearchKey()
并确保它使用您自己的SearchTickCount
,易于访问的.幸运的ProcessSearchKey()
是虚拟,理论上这应该有效,但在实践中代码依赖于FListField
,这是一个私有领域,所以我们将回到正方形1.TDBLookupControl
到您自己的TMyDBLookupControl
,并确保您可以访问SearchTickCount
.这肯定会起作用.当然,黑客攻击更有趣.CPU没有问题,SearchTickCount
因为地址被编码到组成ProcessSearchKey's
代码的ASM指令中.CPU可以读取什么,我们可以阅读.
评估ProcessSearchKey
方法的代码时,它只使用一个全局变量(SearchTickCount
),并在两个地方使用它.首先在这个测试中:
if TickCount - SearchTickCount > 2000 then
Run Code Online (Sandbox Code Playgroud)
那么在这个指令中:
SearchTickCount := TickCount;
Run Code Online (Sandbox Code Playgroud)
如果查看该例程的反汇编列表,则很容易发现全局变量访问,因为它在方括号中给出了变量的地址,没有其他限定符.为了if
工作,编译器执行以下操作:
SUB EAX, [$000000]
Run Code Online (Sandbox Code Playgroud)
对于赋值,编译器执行如下操作:
MOV [$000000], EAX // or ESI on Delphi 7 with debug enabled
Run Code Online (Sandbox Code Playgroud)
如果查看汇编程序指令的左侧,可以用HEX表示法轻松查看实际的操作码.例如SUB EAX,[$ 000000]如下所示:
2B0500000000
Run Code Online (Sandbox Code Playgroud)
我的hacky解决方案利用了这一点.我得到实际过程的地址(TDBLookupControl.ProcessSearchKey
),扫描代码寻找操作码(2B 05
)并获取地址.就是这样,它的确有效.
当然,这有潜在的问题.这取决于使用那些精确寄存器编译的代码(EAX
在我的示例中).编译器可以自由选择不同的寄存器.我使用Delphi7和Delphi 2010进行了测试,代码编译为Debug,编译时没有Debug.在所有4种情况下,编译器选择EAX
用于SUB
指令,并且在3/4情况下选择ESI
用作MOV
指令的寄存器.因此,我的代码只查找SUB
指令.
另一方面,如果代码工作一次,代码每次都有效.代码一旦发布就不会改变,所以如果你可以在开发机器上正确测试,你就不会在客户的机器上得到令人讨厌的AV.但是使用风险自负,这毕竟是黑客攻击!
这是代码:
unit Unit2;
interface
uses DbCtrls;
function GetSearchTickCountPointer: PInteger;
implementation
type
THackDbLookupControl = class(TDBLookupControl); // Hack to get address of protected member
TInstructionHack = packed record
OpCodePrefix: Word;
OpCodeAddress: PInteger;
end;
PInstructionHack = ^TInstructionHack;
function GetSearchTickCountPointer: PInteger;
var P: PInstructionHack;
N: Integer;
begin
P := @THackDbLookupControl.ProcessSearchKey;
N := 0; // Sentinel counter, so we don't look for the opcode for ever
while N < 2000 do
begin
if P.OpCodePrefix = $052B then // Looking for SUB EAX, [SearchTickCount]
begin
Result := P.OpCodeAddress;
Exit;
end;
Inc(N);
P := PInstructionHack(Cardinal(P)+1); // Move pointer 1 byte
end;
Result := nil;
end;
end.
Run Code Online (Sandbox Code Playgroud)
你使用像这样的hacky版本:
var P: PInteger;
begin
P := GetSearchTickCountPointer;
if Assigned(P) then
P^ := 1; // change SearchTickCount value!
end;
Run Code Online (Sandbox Code Playgroud)