我正在研究RAD Studio 2007中的一个项目,使用c ++中的VCL类。
TDBLookupControl是VCL& amp;的一部分。有一些不良行为,这是由使用内部变量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;
但是,SearchTickCount
从未在VCL中包含PACKAGEd,如下例所示。
extern PACKAGE int SearchTickCount;
我想在我的c ++代码中将SearchTickCount
设置为零(按需)。
在我的代码中对它进行外部化使得c ++编译。但是,链接器(显然)无法找到变量。
namespace Dbctrls
{
extern int SearchTickCount;
}
// later on, inside a function
Dbctrls::SearchTickCount = 0;
是否有任何方式/解决方法可以链接到此变量?
编辑: 不幸的是,我们还使用了一些源自TDBLookupControl的自定义控件,所以我想避免创建更多的自定义控件。
答案 0 :(得分:6)
SearchTickCount
是在单元的实现部分中声明的全局(单元级)变量,它不应该在该单元之外访问。如果您使用Delphi而不是C ++ Builder,则会遇到同样的问题。
TDBLookupControl
进行子类化,覆盖ProcessSearchKey()
并确保它使用您自己的SearchTickCount
,一个易于访问的ProcessSearchKey()
。令人高兴的是FListField
是虚拟的,理论上这应该有用,但实际上代码依赖于TDBLookupControl
,这是一个私有字段,所以我们将回到1。TMyDBLookupControl
复制到您自己的SearchTickCount
,并确保您可以访问SearchTickCount
。这肯定会有效。当然,黑客攻击更有趣。 CPU找到ProcessSearchKey's
没有问题,因为地址被编码到组成ProcessSearchKey
代码的ASM指令中。 CPU可以阅读的内容,我们可以阅读。
评估SearchTickCount
方法的代码,它只使用一个全局变量(if TickCount - SearchTickCount > 2000 then
),并在两个地方使用它。首先在这个测试中:
SearchTickCount := TickCount;
然后在这条指令中:
if
如果查看该例程的反汇编列表,很容易发现全局变量访问,因为它在方括号中给出了变量的地址,没有其他限定符。要使SUB EAX, [$000000]
起作用,编译器会执行以下操作:
MOV [$000000], EAX // or ESI on Delphi 7 with debug enabled
对于赋值,编译器执行如下操作:
2B0500000000
如果查看汇编指令的左侧,您可以用HEX表示法轻松查看实际的操作码。例如SUB EAX,[$ 000000]如下所示:
TDBLookupControl.ProcessSearchKey
我的hacky解决方案利用了这一点。我得到实际过程的地址(2B 05
),扫描代码寻找操作码(EAX
)并获取地址。就是这样,它确实有效。
当然,这有潜在的问题。这取决于使用那些确切的寄存器编译的代码(在我的示例中为EAX
)。编译器可以自由选择不同的寄存器。我使用Delphi7和Delphi 2010进行了测试,代码编译为Debug,编译时没有Debug。在所有4个案例中,编译器选择使用SUB
作为ESI
指令,并且在3/4个案例中选择使用MOV
作为SUB
指令的寄存器。因此,我的代码只查找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.
指令。
另一方面,如果代码一次,则代码每次都有效。代码一旦发布就不会改变,所以如果你可以在开发机器上正确测试,你就不会在客户的机器上得到令人讨厌的AV。但是使用风险自负,这毕竟是黑客攻击!
以下是代码:
var P: PInteger;
begin
P := GetSearchTickCountPointer;
if Assigned(P) then
P^ := 1; // change SearchTickCount value!
end;
你使用像这样的hacky版本:
{{1}}
答案 1 :(得分:0)
修复有问题的实现并确保所有包都使用我自己的VCL版本
TDBLookupControl
s 之前我错过了这个实现细节。仅当字段类型为fkData或fkInternalCalc时才会检查SearchTickCount
。计算字段(fkCalculated)应该完全避免这个问题。
答案 2 :(得分:0)
static int* s_TimerMemoryAddress;
union VTableHelper
{
char* pointer;
char** deref;
unsigned int adjustment;
};
#pragma pack(1)
struct TInstructionHack
{
WORD OpCodePrefix;
int* OpCodeAddresss;
};
union FuncPtr
{
TInstructionHack* Checker;
char* Increment;
};
#pragma pack()
由于TDBLookupControl :: ProcessSearchKey是虚拟的,因此指向此函数的指针 不返回实际的非静态成员函数指针地址。 相反,它返回一个vtable地址,指向thunk (一小段代码重定向虚函数以纠正派生 对象非静态成员函数)。下面的代码计算出最终(非虚拟) 成员函数TDBLookupControl :: * ProcessSearchKey的地址基于thunk
try
{
std::auto_ptr<TDBLookupControlHelper> hack(new TDBLookupControlHelper);
TDBLookupControlHelper* ptrptr = hack.get();
VTableHelper thunk;
thunk.pointer = reinterpret_cast<char*>(ptrptr);
thunk.pointer = *thunk.deref; //get virtual table pointer
//adjust for specific function pointer (TDBLookupControl::* ProcessSearchKey)
as specified by thunk
thunk.adjustment += 0xF4;
thunk.pointer = *thunk.deref;
thunk.adjustment += 0x02; //adjust for long jump instruction
thunk.pointer = *thunk.deref;
//get actual location of TDBLookupControl::ProcessSearchKey
thunk.pointer = *thunk.deref;
FuncPtr ptr;
ptr.Increment = thunk.pointer;
//2000 is completely arbitrary, only to prevent an infinite loop
for(int counter = 0; counter < 2000 && s_TimerMemoryAddress == NULL; ++counter)
{
// Looking for SUB EAX, [SearchTickCount]
if(ptr.Checker->OpCodePrefix == 0x052B)
s_TimerMemoryAddress = ptr.Checker->OpCodeAddresss;
else
ptr.Increment++;
counter++;
}
}
catch(...) // catch any illegal dereferences of VTableHelper
{
}