cheat-engine/Cheat Engine/autoassemblerexeptionhandler.pas
2023-11-13 01:47:23 +01:00

594 lines
16 KiB
ObjectPascal
Executable File

unit autoassemblerexeptionhandler;
{$mode delphi}
interface
uses
{$ifdef darwin}
macport,
{$endif}
{$ifdef windows}
windows,
{$endif}
Classes, SysUtils, NewKernelHandler, Clipbrd, betterControls;
type
TAAExceptionInfo=record
trylabel: string;
exceptlabel: string;
end;
TAAExceptionInfoList=array of TAAExceptionInfo;
{$ifdef onebytejumps}
TAAExceptionRIPChangeInfo=record
originaddress: ptruint;
destinationlabel: string;
end;
TAAExceptionRIPChangeInfoList=array of TAAExceptionRIPChangeInfo;
{$endif}
procedure InitializeAutoAssemblerExceptionHandler;
procedure AutoAssemblerExceptionHandlerRemoveExceptionRange(startaddress: ptruint);
procedure AutoAssemblerExceptionHandlerAddExceptionRange(tryaddress: ptruint; exceptionAddress: ptruint);
{$ifdef onebytejumps}
procedure AutoAssemblerExceptionHandlerAddChangeRIPEntry(rip: ptruint; newrip: ptruint);
procedure AutoAssemblerExceptionHandlerRemoveChangeRIPEntry(rip: ptruint);
function IsAutoAssemblerExceptionRIPChanger(address: ptruint; out destination: ptruint): boolean;
{$endif}
procedure AutoAssemblerExceptionHandlerApplyChanges;
function AutoAssemblerExceptionHandlerHasEntries: boolean;
implementation
uses autoassembler, symbolhandler, ProcessHandlerUnit, commonTypeDefs;
type
TAAExceptionListEntry=record
tryaddress: ptruint;
exceptionaddress: ptruint;
{$ifdef onebytejumps}
entrytype: ptruint; //0=exception, 1=rip change
{$endif}
end;
TAAExceptionList=array of TAAExceptionListEntry;
TAAExceptionListEntry32=record
tryaddress: dword;
exceptionaddress: dword;
{$ifdef onebytejumps}
entrytype: dword;
{$endif}
end;
TAAExceptionListEntry64=record
tryaddress: qword;
exceptionaddress: qword;
{$ifdef onebytejumps}
entrytype: qword;
{$endif}
end;
TAAExceptionList32=array of TAAExceptionListEntry32;
TAAExceptionList64=array of TAAExceptionListEntry64;
var
pid: dword;
signatureaddress: ptruint;
setlistaddress: ptruint;
listaddress: ptruint;
exceptionlist: TAAExceptionList;
function AutoAssemblerExceptionHandlerHasEntries: boolean;
begin
result:=(pid=processid) and (length(Exceptionlist)>0);
end;
procedure AutoAssemblerExceptionHandlerAddExceptionRange(tryaddress: ptruint; ExceptionAddress: ptruint);
var i: integer;
begin
i:=length(exceptionlist);
setlength(exceptionlist, i+1);
exceptionlist[i].tryaddress:=tryaddress;
exceptionlist[i].exceptionaddress:=ExceptionAddress;
{$ifdef onebytejumps}
exceptionlist[i].entrytype:=0;
{$endif}
end;
procedure AutoAssemblerExceptionHandlerRemoveExceptionRange(startaddress: ptruint);
var i,j: integer;
begin
i:=0;
while i<length(exceptionlist) do
begin
if exceptionlist[i].tryaddress=startaddress then
begin
for j:=i to length(exceptionlist)-2 do
exceptionlist[j]:=exceptionlist[j+1];
setlength(exceptionlist, length(Exceptionlist)-1);
end
else
inc(i);
end;
end;
{$ifdef onebytejumps}
procedure AutoAssemblerExceptionHandlerAddChangeRIPEntry(rip: ptruint; newrip: ptruint);
var i: integer;
begin
for i:=0 to length(exceptionlist)-1 do
if (exceptionlist[i].tryaddress=rip) and (exceptionlist[i].entrytype=1) then
begin
//just update the destination
exceptionlist[i].tryaddress:=rip;
exceptionlist[i].exceptionaddress:=newrip;
exceptionlist[i].entrytype:=1;
exit;
end;
i:=length(exceptionlist);
setlength(exceptionlist, i+1);
exceptionlist[i].tryaddress:=rip;
exceptionlist[i].exceptionaddress:=newrip;
exceptionlist[i].entrytype:=1;
end;
procedure AutoAssemblerExceptionHandlerRemoveChangeRIPEntry(rip: ptruint);
begin
AutoAssemblerExceptionHandlerRemoveExceptionRange(rip);
end;
function IsAutoAssemblerExceptionRIPChanger(address: ptruint; out destination: ptruint): boolean;
var i: integer;
begin
result:=false;
for i:=0 to length(exceptionlist)-1 do
if (exceptionlist[i].entrytype=1) and (exceptionlist[i].tryaddress=address) then
begin
destination:=exceptionlist[i].exceptionaddress;
exit(true);
end;
end;
{$endif}
procedure AutoAssemblerExceptionHandlerApplyChanges;
var
params32: record
list: dword;
listsize: integer;
end;
params64: record
list: qword;
listsize: integer;
end;
params: pointer;
th: thandle;
templist32: TAAExceptionList32;
templist64: TAAExceptionList64;
i: integer;
oldlist: ptruint;
x: ptruint;
y: dword;
begin
//setList
InitializeAutoAssemblerExceptionHandler;
{$ifdef windows}
oldlist:=0;
readprocessmemory(processhandle, pointer(listaddress), @oldlist, 8,x);
if processhandler.is64Bit then
begin
setlength(templist64, length(exceptionlist));
for i:=0 to length(exceptionlist)-1 do
begin
templist64[i].tryaddress:=exceptionlist[i].tryaddress;
templist64[i].exceptionaddress:=exceptionlist[i].exceptionaddress;
{$ifdef onebytejumps}
templist64[i].entrytype:=exceptionlist[i].entrytype;
{$endif}
end;
params64.listsize:=length(exceptionlist);
if params64.listsize=0 then
params64.list:=0
else
begin
params64.list:=ptruint(VirtualAllocEx(processhandle, nil,length(templist64)*sizeof(TAAExceptionListEntry64),mem_commit or mem_Reserve, PAGE_READWRITE));
writeprocessmemory(processhandle, pointer(ptruint(params64.list)), @templist64[0], length(templist64)*sizeof(TAAExceptionListEntry64), x);
end;
params:=VirtualAllocEx(processhandle, nil,sizeof(params64),mem_commit or MEM_RESERVE, PAGE_READWRITE);
writeprocessmemory(processhandle, pointer(params), @params64, sizeof(params64), x);
end
else
begin
setlength(templist32, length(exceptionlist));
for i:=0 to length(exceptionlist)-1 do
begin
templist32[i].tryaddress:=exceptionlist[i].tryaddress;
templist32[i].exceptionaddress:=exceptionlist[i].exceptionaddress;
{$ifdef onebytejumps}
templist32[i].entrytype:=exceptionlist[i].entrytype;
{$endif}
end;
params32.listsize:=length(exceptionlist);
if params32.listsize=0 then
params32.list:=0
else
begin
params32.list:=ptruint(VirtualAllocEx(processhandle, nil,length(templist32)*sizeof(TAAExceptionListEntry32),mem_commit or mem_Reserve, PAGE_READWRITE));
writeprocessmemory(processhandle, pointer(ptruint(params32.list)), @templist32[0], length(templist32)*sizeof(TAAExceptionListEntry32), x);
end;
params:=VirtualAllocEx(processhandle, nil,sizeof(params32),mem_commit or MEM_RESERVE, PAGE_READWRITE);
writeprocessmemory(processhandle, pointer(params), @params32, sizeof(params32), x);
end;
th:=CreateRemoteThread(processhandle, nil, 0, pointer(setlistaddress),params,0,y);
if waitforsingleobject(th,5000)<>WAIT_OBJECT_0 then
raise exception.create('Failure to set the exception list. Thread error');
//free the old list if there was one
if oldlist<>0 then
VirtualFreeEx(processhandle, pointer(oldlist), 0, MEM_RELEASE);
//and the parameters
VirtualFreeEx(processhandle, params, 0, MEM_RELEASE); //(not the list, on purpose)
{$endif}
end;
procedure InitializeAutoAssemblerExceptionHandler;
var
init: tstringlist;
eh: ptruint;
signature: pchar;
x: ptruint;
ehallocated: boolean;
disableinfo: tdisableinfo;
i: integer;
begin
{$ifdef windows}
//check if CEAAExceptionHandler is already defined, and if not, define it here
ehallocated:=false;
if (pid=processid) and (signatureaddress<>0) then
begin
getmem(signature,5);
if readprocessmemory(processhandle, pointer(signatureaddress), signature,4,x) then
begin
signature[4]:=#0;
if signature='AAEH' then ehallocated:=true;
end;
FreeMemAndNil(signature);
end;
if ehallocated=false then
begin
setlength(exceptionlist,0);
init:=tstringlist.create;
init.add('alloc(Signature,4)');
init.add('registersymbol(debugnr)');
init.add('registersymbol(exceptioncount)');
init.add('registersymbol(exceptionhandler)');
init.add('registersymbol(vehid)');
init.add('alloc(MREW,8)'); //todo: in XP use Critical section instead
init.add('alloc(exceptioncount,4)');
init.add('alloc(debugnr,4)');
init.add('alloc(vehid,8)');
init.add('alloc(List,8)');
init.add('alloc(ListSize,8)');
init.add('alloc(ExceptionHandler,1024)');
init.add('alloc(SetList,1024)');
init.add('alloc(registereh,128)');
init.add('label(next)');
init.add('label(nomatch)');
init.add('label(match)');
init.add('label(ExceptionHandler_exit)');
init.add('');
init.add('Signature:');
init.add('db ''AAEH'''); //signature
init.add('');
init.add('MREW:');
init.add('dq 0');
init.add('List:');
init.add('dq 0');
init.add('ListSize:'); //number of entries in the list
init.add('dd 0');
init.add('SetList:');
if processhandler.is64bit then
begin
init.add('sub rsp,28');
//rsp=to call scratch space 1
//rsp+8=to call scratch space 2
//rsp+10=to call scratch space 3
//rsp+18=to call scratch space 4
//rsp+20=alignment
//rsp+28=return address
//rsp+30=local scratchspace 1 (params)
init.add('mov [rsp+30],rcx');
init.add('mov rcx,MREW');
init.add('call AcquireSRWLockExclusive');
init.add('mov rax,[rsp+30]');
init.add('mov r8,[rax]'); //list
init.add('mov r9,[rax+8]'); //listsize
init.add('mov [List],r8');
init.add('mov [ListSize],r9');
init.add('mov rcx,MREW');
init.add('call ReleaseSRWLockExclusive');
init.add('add rsp,28');
init.add('ret');
end
else
begin
init.add('push ebp');
init.add('mov ebp,esp');
init.add('push MREW');
init.add('call AcquireSRWLockExclusive');
init.add('mov eax,[ebp+8]');
init.add('mov ebx,[eax]'); //list
init.add('mov ecx,[eax+4]'); //listsize
init.add('mov [List],ebx');
init.add('mov [ListSize],ecx');
init.add('push MREW');
init.add('call ReleaseSRWLockExclusive');
init.add('pop ebp');
init.add('ret 4');
end;
init.add('ExceptionHandler:');
if processhandler.is64bit then
begin
init.add('sub rsp,28');
//free to edit: RCX, RDX, R8, R9, R10, R11
//rsp=to call scratch space 1
//rsp+8=to call scratch space 2
//rsp+10=to call scratch space 3
//rsp+18=to call scratch space 4
//rsp+20=alignment
//rsp+28=return address
//rsp+30=local scratchspace 1 (ExceptionInfo)
init.add('mov [rsp+30],rcx');
init.add('mov rcx,MREW');
init.add('call AcquireSRWLockShared');
init.add('cmp [List],0');
init.add('je ExceptionHandler_exit');
init.add('mov rax,[rsp+30]'); //rax=ExceptionInfo pointer
init.add('mov rax,[rax+8]'); //rax=ContextRecord
init.add('lea rax,[rax+f8]'); //rax points to RIP
init.add('mov r8,[List]');
init.add('mov rcx,[ListSize]');
init.add('next:');
init.add('mov r9,[r8]'); //try address
init.add('mov r10,[r8+8]'); //except address/new rip
{$ifdef onebytejumps}
init.add('cmp dword [r8+10],1');
init.add('jne exceptionhandlerentry');
//change rip entry
init.add('cmp [rax],r9');
init.add('je match');
init.add('exceptionhandlerentry:');
{$endif}
init.add('cmp [rax],r9');
init.add('jb nomatch'); //if before the try instruction, it's not a match
init.add('cmp [rax],r10');
init.add('jb match'); //if after try, but before except, then it is match
init.add('nomatch:');
{$ifdef onebytejumps}
init.add('add r8,18');
{$else}
init.add('add r8,10');
{$endif}
init.add('loop next');
init.add('xor rax,rax'); //end of the list and not in it
init.add('jmp ExceptionHandler_exit');
init.add('match:');
init.add('mov [rax],r10'); //change RIP to the exception handler address (or new RIP)
init.add('mov eax,ffffffff');
init.add('ExceptionHandler_exit:');
init.add('mov [rsp+30],rax'); //save the return value
init.add('mov rcx,MREW');
init.add('call ReleaseSRWLockShared');
init.add('mov rax,[rsp+30]'); //restore the return value
init.add('add rsp,28');
init.add('ret');
end
else
begin
init.add('push ebp');
init.add('mov ebp,esp'); //ebp+4 = return, ebp+8=ExceptionInfo
init.add('push ebx');
init.add('push ecx');
init.add('push edx');
init.add('push esi');
init.add('push MREW');
init.add('call AcquireSRWLockShared'); //read lock
init.add('cmp [List],0'); //check if it's an empty list
init.add('je ExceptionHandler_exit');
//check that the EIP/RIP falls within a known region
//if so, change rip to go to that region
//if not, continue searching
init.add('mov eax,[ebp+8]');
//[eax+0]=ExceptionRecord
//[eax+4]=ContextRecord
init.add('mov eax,[eax+4]');
//[eax+b8]=eip
init.add('lea eax,[eax+b8]');
//now check if this eip(stoed in eax) is in the list
init.add('mov esi,[List]');
init.add('mov ecx,[ListSize]');
init.add('next:');
init.add('mov ebx,[esi]'); //try address
init.add('mov edx,[esi+4]'); //except address
{$ifdef onebytejumps}
init.add('cmp dword [esi+8],1');
init.add('jne exceptionhandlerentry');
//change rip entry
init.add('cmp [eax],ebx');
init.add('je match');
init.add('exceptionhandlerentry:');
{$endif}
init.add('cmp [eax],ebx');
init.add('jb nomatch');
init.add('cmp [eax],edx');
init.add('jb match');
init.add('nomatch:');
{$ifdef onebytejumps}
init.add('add esi,c');
{$else}
init.add('add esi,8');
{$endif}
init.add('loop next');
init.add('mov eax,0'); //not in the list, continue unhandled
init.add('jmp ExceptionHandler_exit');
init.add('match:');
init.add('mov [eax],edx'); //change eip to the exception handler
init.add('mov eax,ffffffff'); //continue execution
init.add('ExceptionHandler_exit:');
init.add('push eax'); //store the return value
init.add('push MREW');
init.add('call ReleaseSRWLockShared'); //read lock release
init.add('pop eax'); //restore the return value
init.add('pop esi');
init.add('pop edx');
init.add('pop ecx');
init.add('pop ebx');
init.add('pop ebp');
init.add('ret 4');
end;
//addvectoredexceptionhandler
init.add('registereh:');
if processhandler.is64bit then
begin
init.add('sub rsp,28');
init.add('mov rcx,1');
init.add('mov rdx,ExceptionHandler');
init.add('call AddVectoredExceptionHandler');
init.add('mov [vehid],rax');
init.add('add rsp,28');
init.add('ret');
end
else
begin
init.add('push ExceptionHandler');
init.add('push 1');
init.add('call AddVectoredExceptionHandler');
init.add('mov [vehid],eax');
init.add('ret 4');
end;
init.add('createthreadandwait(registereh)');
//Clipboard.AsText:=init.text;
disableinfo:=TDisableInfo.create;
try
if autoassemble(init, false, true,false,false,disableinfo)=false then
raise exception.create('Failure to assemble exception handler');
for i:=0 to length(disableinfo.allocs)-1 do
begin
if lowercase(disableinfo.allocs[i].varname)='signature' then
signatureaddress:=disableinfo.allocs[i].address
else
if lowercase(disableinfo.allocs[i].varname)='list' then
listaddress:=disableinfo.allocs[i].address
else
if lowercase(disableinfo.allocs[i].varname)='setlist' then
setlistaddress:=disableinfo.allocs[i].address;
end;
finally
init.free;
disableinfo.free;
end;
pid:=processid;
end;
{$else}
raise exception.create('{$try}/{$except} is not yet implemented for this system');
{$endif}
end;
end.