cheat-engine/Cheat Engine/gnuassembler.pas

644 lines
18 KiB
ObjectPascal
Executable file

unit GnuAssembler;
{$mode objfpc}{$H+}
interface
uses
{$ifdef darwin}
macport,
{$endif}
{$ifdef windows}
windows,
{$endif}
Classes, SysUtils, NewKernelHandler, ProcessHandlerUnit, strutils,
dialogs, commonTypeDefs;
{
//scan for .extraparams_as <string>
//scan for .extraparams_ld <string>
//scan the script for .asection , .msection and .aobscan
//1:
//.aobscan <name> "ceaobscanformat"
//Internally ->:
//.msection <name> <aobscanresult>
//->see .msection
//2:
//.asection <name> <0xpreferedaddress>
//Internally ->:
//.section _<name>,"xa" : after assembly check the size and allocate at least the specific amount
//name:
//3:
//.msection <name> <address or ce symbol> <expectedsize OPT> <overridesize OPT> (error out if after linking the assumed size is bigger than expected. E.g veneers and sometimes alignments)
//Internally ->:
//.section _<name>,"xa" : after assembly add it to the section list with the given address
//name:
//4:
//scan for <xxx>: and add ".type <xxx>, %function" for each label found (
//assemble the script
//parse the ELF header
//enumerate the sections. Crossreference them with the list of asection and msection. Unknown sections will be handled as .asection with no prefered address
//allocate the sections (that need to be allocated. .msection sections are already allocated)
//write a linker script that sets these sections to their specific addresses
//parse the ELF header further and figure out which symbols are undefined
//call the linker with --defsym=symbolname=0xaddress for each symbol
//after the linker:
//extract the binary data for each section:
//objcopy -j<sectionname> -O binary <tempresultfile> <sectionbinary>
//write the sections to memory from top to bottom. So write your code injection destinations before the jump/branch to it. (Just like the auto assembler)
}
procedure gnuassemble(originalscript: tstrings);
implementation
uses simpleaobscanner, symbolhandler, binutils, elftypes, elfconsts, MemoryQuery,
globals, UnexpectedExceptionsHelper;
resourcestring
rsInvalidELFFile = 'Invalid ELF file';
rsDoesntSeemToBeAValidELFFile = ' doesn''t seem to be a valid ELF file';
rsConfigureAValidBinutilsSetupFirst = 'Configure a valid binutils setup first';
rsTheAOBFor = 'The AOB for ';
rsCouldNotBeFound = ' could not be found';
rsTheProperFormatOfAsectionIs = 'The proper format of .asection is : .asection <name> <preferedaddress>';
rsTheProperFormatOfMsectionIs = 'The proper format of .msection is : .msection <name> <address> <optional expected size>';
rsNoBinutilsInstalled = 'No binutils installed';
rsTook = ' took ';
rsBytesWhile = ' bytes while ';
rsWhereExpected = ' where expected';
rsGNUAssemblerError = 'GNU Assembler error';
type
PElf32Hdr=^TElf32Hdr;
PElf64Hdr=^TElf64Hdr;
PElfIdent=^TElfIdent;
PElf32SectHdr=^TElf32SectHdr;
procedure GetSectionsFromElf32(mem: PByte; size: integer; var sections: TSectionArray);
var
Header: PElf32Hdr;
SectionHeaders: PElf32SectHdr;
i: integer;
stringsection: integer;
sectionstrings: pchar;
sectionname: string;
begin
Header:=@mem[16];
if Header^.SectHdrOffset>size then raise exception.create(rsInvalidELFFile);
SectionHeaders:=@mem[Header^.SectHdrOffset];
if ptruint(@SectionHeaders[Header^.SectHdrNum-1])-ptruint(mem)>size then raise exception.create(rsInvalidELFFile);
stringsection:=Header^.NameTableIndex;
if stringsection>Header^.SectHdrNum then raise exception.create(rsInvalidELFFile);
sectionstrings:=@mem[SectionHeaders[stringsection].Offset];
for i:=0 to Header^.SectHdrNum-1 do
begin
sectionname:=pchar(@sectionstrings[SectionHeaders[i].NameIdx]);
if (SectionHeaders[i].Size>0) and ((SectionHeaders[i].Flags and SHF_ALLOC)=SHF_ALLOC) then
begin
setlength(sections, length(sections)+1);
sections[length(sections)-1].name:=sectionname;
sections[length(sections)-1].size:=SectionHeaders[i].Size;
end;
end;
end;
procedure GetSectionsFromElf64(mem: PByte; size: integer; var sections: TSectionArray);
var
Header: PElf64Hdr;
SectionHeaders: PElf64SectHdr;
i: integer;
stringsection: integer;
sectionstrings: pchar;
sectionname: string;
begin
Header:=@mem[16];
if Header^.SectHdrOffset>size then raise exception.create(rsInvalidELFFile);
SectionHeaders:=@mem[Header^.SectHdrOffset];
if ptruint(@SectionHeaders[Header^.SectHdrNum-1])-ptruint(mem)>size then raise exception.create(rsInvalidELFFile);
stringsection:=Header^.NameTableIndex;
if stringsection>Header^.SectHdrNum then raise exception.create(rsInvalidELFFile);
sectionstrings:=@mem[SectionHeaders[stringsection].Offset];
for i:=0 to Header^.SectHdrNum-1 do
begin
sectionname:=pchar(@sectionstrings[SectionHeaders[i].NameIdx]);
if (SectionHeaders[i].Size>0) and ((SectionHeaders[i].Flags and SHF_ALLOC)=SHF_ALLOC) then
begin
setlength(sections, length(sections)+1);
sections[length(sections)-1].name:=sectionname;
sections[length(sections)-1].size:=SectionHeaders[i].Size;
end;
end;
end;
procedure GetSectionsFromElf(elffile: string; var sections: TSectionArray);
var
ms: TMemorystream;
buf: PElfIdent;
begin
ms:=tmemorystream.Create;
try
ms.LoadFromFile(elffile);
buf:=PElfIdent(ms.Memory);
if buf^.Magic<>ELFMAGIC then raise exception.create(elffile+rsDoesntSeemToBeAValidELFFile);
if buf^.ElfClass=ELFCLASS32 then GetSectionsFromElf32(PByte(buf), ms.size, sections) else
if buf^.ElfClass=ELFCLASS64 then GetSectionsFromElf64(PByte(buf), ms.size, sections);
finally
ms.free;
end;
end;
function gnuassemble2(originalscript: tstrings; targetself: boolean; var CEAllocarray: TCEAllocArray): boolean;
var
nmdisabled: boolean;
i,j,k,l: integer;
line: string;
p1, p2, p3: string;
a: ptruint;
sections: array of record
name: string;
allocate: boolean;
address: ptruint;
expectedsize: dword;
actualsize: dword;
end;
binarysections: TBinarySections;
imports: TImportList;
elfsections: TSectionArray;
script: Tstringlist;
o: string;
found: boolean;
lnkfile: Tstringlist;
lnkfilename: string;
defined, undefinedimports: Tstringlist;
x: ptruint;
op: dword;
extraparams_as, extraparams_ld: string;
bu: TBinUtils;
vpe: boolean;
begin
result:=false;
if (binutilslist.count=0) then
raise exception.create(rsConfigureAValidBinutilsSetupFirst);
bu:=nil;
script:=tstringlist.create;
script.Assign(originalscript);
nmdisabled:=false;
setlength(imports,0);
setlength(sections,0);
try
try
i:=0;
// if processhandler.SystemArchitecture=archX86 then
// script.insert(0,'.intel_syntax');
while i<script.count do
begin
line:=lowercase(trim(script[i]));
if (length(line)>1) then
begin
if (line[1]='.') then
begin
case line[2] of
'a': //.aobscan / .asection
begin
if copy(line, 1, 9)='.aobscan ' then
begin
if wordcount(line, [' '])>=3 then //.aobscan counts as one too
begin
p1:=ExtractWord(2, line, [' ']);
j:=WordPosition(3, line, [' ']);
p2:=copy(line, j, length(line)); //the rest is the aob (using ce format aob text)
end;
a:=findaob(p2);
if a=0 then raise exception.create(rsTheAOBFor+p1+rsCouldNotBeFound);
script[i]:='.msection '+p1+' 0x'+inttohex(a,8);
continue;
end
else
if copy(line, 1,10)='.asection ' then
begin
j:=wordcount(line, [' ']);
if (j<=1) or (j>3) then
raise exception.create(rsTheProperFormatOfAsectionIs);
p1:=extractword(2, line, [' ']);
if j=3 then
p2:=extractword(3, line, [' '])
else
p2:='';
//add this section to the list
j:=length(sections);
setlength(sections, j+1);
sections[j].name:='_'+p1;
if p2<>'' then //has a prefered address
begin
try
sections[j].address:=StrToQWord(p2)
except
sections[j].address:=symhandler.getAddressFromName(p2);
end;
end
else
sections[j].address:=0;
sections[j].allocate:=true;
sections[j].expectedsize:=0;
script[i]:='.section _'+p1+',"xa"';
script.Insert(i+1,p1+':');
end;
end;
'b': //.binutils <name> (optional)
begin
if copy(line,1,10)='.binutils ' then
begin
p1:=trim(copy(line,11, length(line)-1));
for i:=0 to binutilslist.count-1 do
begin
if TBinUtils(binutilslist[i]).name=p1 then
begin
bu:=TBinUtils(binutilslist[i]);
break;
end;
end;
end;
end;
'e':
begin
if copy(line,1,13)='.extraparams_' then
begin
if length(line)>15 then
begin
if copy(line,14,3)='as ' then
extraparams_as:=copy(line,17,length(line))
else
if copy(line,14,3)='ld ' then
extraparams_ld:=copy(line,17,length(line));
end;
end;
end;
'i': //.import
begin
if copy(line,1,8)='.import ' then
begin
//add to the import table
j:=WordCount(line,[' ',',']);
for k:=2 to j do
begin
l:=length(imports);
setlength(imports, l+1);
imports[l].name:=ExtractWord(k, line, [' ',',']);
imports[l].address:=symhandler.getAddressFromName(imports[l].name);
end;
setlength(imports,j+1);
end;
end;
'm': //.msection
begin
if copy(line, 1,10)='.msection ' then
begin
j:=wordcount(line, [' ']);
if (j<=1) or (j>4) then
raise exception.create(rsTheProperFormatOfMsectionIs);
p1:=extractword(2, line, [' ']);
p2:=extractword(3, line, [' ']);
if j=4 then
p3:=extractword(4, line, [' '])
else
p3:='';
j:=length(sections);
setlength(sections, j+1);
sections[j].name:='_'+p1;
try
sections[j].address:=StrToQWord(p2)
except
sections[j].address:=symhandler.getAddressFromName(p2);
end;
sections[j].allocate:=false;
if p3<>'' then
sections[j].expectedsize:=strtoint(p3)
else
sections[j].expectedsize:=0;
script[i]:='.section _'+p1+',"xa"';
script.Insert(i+1,p1+':');
end;
end;
'n': if line='.nonm' then nmdisabled:=true;
end;
end;
{
if line[length(line)]=':' then
begin
//label definition
p1:=copy(line, 1, length(line)-1);
script.insert(0,'.type '+p1+', %function');
inc(i);
end;}
end;
inc(i);
end;
if (bu=nil) then
bu:=defaultBinutils;
if (bu=nil) and (binutilslist.count>0) then
bu:=TBinUtils(binutilslist[0]);
if bu=nil then
raise exception.create(rsNoBinutilsInstalled);
bu.assemble(script, extraparams_as, o);
//parse o to get the sections and their size
setlength(elfsections,0);
try
GetSectionsFromElf(o, elfsections); //elfsections now contains all allocatable sections
except
//I work best with ELF obj files, but I'll add some fallback by calling objdump to get the data needed
bu.GetSections(o, elfsections);
end;
//add the unknown ones
for i:=0 to length(elfsections)-1 do
begin
found:=false;
for j:=0 to length(sections)-1 do
begin
if sections[j].name=elfsections[i].name then
begin
sections[j].actualsize:=elfsections[i].size; //just temporarily
found:=true;
break;
end;
end;
if not found then
begin
//add it
j:=length(sections);
setlength(sections, j+1);
sections[j].name:=elfsections[i].name;
sections[j].actualsize:=elfsections[i].size;
sections[j].allocate:=true;
sections[j].expectedsize:=0;
sections[j].address:=0;
end;
end;
//allocate the ones that need allocating
for i:=0 to length(sections)-1 do
begin
if sections[i].allocate then
begin
if sections[i].address=0 then //any random address will do
sections[i].address:=ptrUint(virtualallocex(processhandle,nil,sections[i].actualsize+1024, MEM_RESERVE or MEM_COMMIT,page_execute_readwrite))
else //allocate this memory at the given address
sections[i].address:=ptrUint(virtualallocex(processhandle,FindFreeBlockForRegion(sections[i].address,sections[i].actualsize+1024),sections[i].actualsize+1024, MEM_RESERVE or MEM_COMMIT,page_execute_readwrite));
if allocsAddToUnexpectedExceptionList then
AddUnexpectedExceptionRegion(sections[i].address, sections[i].actualsize+1024);
end;
end;
//build the link file
lnkfile:=TStringList.create;
lnkfile.add('SECTIONS');
lnkfile.add('{');
for i:=0 to length(sections)-1 do
lnkfile.add(sections[i].name+' 0x'+inttohex(sections[i].address,8)+' : { *. }');
lnkfile.add('}');
lnkfilename:=GetTempFileName;
lnkfile.SaveToFile(lnkfilename);
lnkfile.Free;
if not nmdisabled then
begin
//if nm hasn't been disabled call nm and then check which symbols are undefined and add them to the import list (if they aren't in there already)
undefinedimports:=tstringlist.create;
defined:=tstringlist.create;
bu.nm(o, defined, undefinedimports);
for i:=0 to undefinedimports.count-1 do
begin
found:=false;
for j:=0 to length(imports)-1 do
begin
if imports[j].name=undefinedimports[i] then
found:=true;
end;
if not found then
begin
j:=length(imports);
setlength(imports, j+1);
imports[j].name:=undefinedimports[i];
imports[j].address:=symhandler.getAddressFromName(imports[j].name);
end;
end;
undefinedimports.free;
defined.free;
end;
//link the assembler file with the provided linker file and import list
setlength(binarysections, length(sections));
for i:=0 to length(sections)-1 do
binarysections[i].sectionname:=sections[i].name;
bu.ldAndExtract(o, lnkfilename, extraparams_ld, imports, binarysections);
//before writing, first check that the sections are of compatible size
for i:=0 to length(sections)-1 do
begin
if (sections[i].expectedsize>0) then
begin
for j:=0 to length(binarysections)-1 do
begin
if sections[i].name=binarysections[j].sectionname then
begin
//write it
if (sections[i].expectedsize>0) and (sections[i].expectedsize<length(binarysections[j].data)) then
raise exception.create(sections[i].name+rsTook+inttostr(length(binarysections[j].data))+rsBytesWhile+inttostr(sections[i].expectedsize)+rsWhereExpected);
break;
end;
end;
end;
end;
//write the binary sections to memory
for i:=0 to length(sections)-1 do
begin
for j:=0 to length(binarysections)-1 do
begin
if sections[i].name=binarysections[j].sectionname then
begin
vpe:=(SkipVirtualProtectEx=false) and virtualprotectex(processhandle, pointer(sections[i].address), length(binarysections[j].data),PAGE_EXECUTE_READWRITE,op);
WriteProcessMemory(processhandle, pointer(sections[i].address), @binarysections[j].data[0], length(binarysections[j].data), x);
if vpe then virtualprotectex(processhandle, pointer(sections[i].address), length(binarysections[j].data),op,op);
end;
end;
end;
deletefile(lnkfilename);
deletefile(o);
result:=true;
finally
script.free;
end;
except
on e: exception do
begin
MessageDlg(rsGNUAssemblerError,e.message,mtError, [mbok],0);
end;
end;
end;
{
example for x86_64:
.intel_syntax noprefix
.msection bla 0x00400500
duh:
nop
nop
nop
jmp [rip+xxx]
xxx:
.quad test
.asection mycode 0x200000000
nop
nop
test:
nop
nop
jmp test
nop
mov rax,qword ptr [rip+test2]
nop
nop
test2:
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
procedure gnuassemble(originalscript: tstrings);
var a: TCEAllocArray;
begin
//let's do something similar to autoassembler
gnuassemble2(originalscript, false, a);
end;
end.