2015-08-29 01:43:18 +02:00
unit GnuAssembler;
{$mode objfpc} {$H+}
interface
uses
2019-12-18 12:07:17 +01:00
{$ifdef darwin}
macport,
{$endif}
{$ifdef windows}
windows,
{$endif}
Classes, SysUtils, NewKernelHandler, ProcessHandlerUnit, strutils,
2015-09-19 23:20:34 +02:00
dialogs, commonTypeDefs;
2015-08-29 01:43:18 +02:00
{
2015-09-09 00:12:18 +02:00
//scan for .extraparams_as <string>
//scan for .extraparams_ld <string>
2015-08-29 01:43:18 +02:00
//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:
2015-09-19 23:20:34 +02:00
//.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)
2015-08-29 01:43:18 +02:00
//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
2018-06-20 18:46:52 +02:00
uses simpleaobscanner, symbolhandler, binutils, elftypes, elfconsts, MemoryQuery,
2018-12-10 21:23:59 +01:00
globals, UnexpectedExceptionsHelper;
2015-08-29 01:43:18 +02:00
2016-06-25 10:35:44 +03:00
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' ;
2015-08-29 01:43:18 +02:00
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[ 1 6 ] ;
2016-06-25 10:35:44 +03:00
if Header^ . SectHdrOffset> size then raise exception. create( rsInvalidELFFile) ;
2015-08-29 01:43:18 +02:00
SectionHeaders: = @ mem[ Header^ . SectHdrOffset] ;
2016-06-25 10:35:44 +03:00
if ptruint( @ SectionHeaders[ Header^ . SectHdrNum- 1 ] ) - ptruint( mem) > size then raise exception. create( rsInvalidELFFile) ;
2015-08-29 01:43:18 +02:00
stringsection: = Header^ . NameTableIndex;
2016-06-25 10:35:44 +03:00
if stringsection> Header^ . SectHdrNum then raise exception. create( rsInvalidELFFile) ;
2015-08-29 01:43:18 +02:00
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[ 1 6 ] ;
2016-06-25 10:35:44 +03:00
if Header^ . SectHdrOffset> size then raise exception. create( rsInvalidELFFile) ;
2015-08-29 01:43:18 +02:00
SectionHeaders: = @ mem[ Header^ . SectHdrOffset] ;
2016-06-25 10:35:44 +03:00
if ptruint( @ SectionHeaders[ Header^ . SectHdrNum- 1 ] ) - ptruint( mem) > size then raise exception. create( rsInvalidELFFile) ;
2015-08-29 01:43:18 +02:00
stringsection: = Header^ . NameTableIndex;
2016-06-25 10:35:44 +03:00
if stringsection> Header^ . SectHdrNum then raise exception. create( rsInvalidELFFile) ;
2015-08-29 01:43:18 +02:00
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) ;
2016-06-25 10:35:44 +03:00
if buf^ . Magic< > ELFMAGIC then raise exception. create( elffile+ rsDoesntSeemToBeAValidELFFile) ;
2015-08-29 01:43:18 +02:00
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 ;
2015-09-19 23:20:34 +02:00
function gnuassemble2( originalscript: tstrings; targetself: boolean ; var CEAllocarray: TCEAllocArray) : boolean ;
2015-08-29 01:43:18 +02:00
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 ;
2018-05-17 22:08:35 +02:00
defined, undefinedimports: Tstringlist;
2015-08-29 01:43:18 +02:00
x: ptruint;
op: dword;
2015-09-09 00:12:18 +02:00
extraparams_as, extraparams_ld: string ;
2015-09-16 23:32:06 +02:00
bu: TBinUtils;
2018-05-31 22:42:27 +02:00
vpe: boolean ;
2015-08-29 01:43:18 +02:00
begin
2018-05-29 15:10:22 +02:00
result : = false ;
2015-09-16 23:32:06 +02:00
if ( binutilslist. count= 0 ) then
2016-06-25 10:35:44 +03:00
raise exception. create( rsConfigureAValidBinutilsSetupFirst) ;
2015-08-29 01:43:18 +02:00
2015-09-16 23:32:06 +02:00
bu: = nil ;
2015-08-29 01:43:18 +02:00
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) ;
2016-06-25 10:35:44 +03:00
if a= 0 then raise exception. create( rsTheAOBFor+ p1+ rsCouldNotBeFound) ;
2015-08-29 01:43:18 +02:00
script[ i] : = '.msection ' + p1+ ' 0x' + inttohex( a, 8 ) ;
continue;
end
else
if copy( line, 1 , 1 0 ) = '.asection ' then
begin
j: = wordcount( line, [ ' ' ] ) ;
if ( j< = 1 ) or ( j> 3 ) then
2016-06-25 10:35:44 +03:00
raise exception. create( rsTheProperFormatOfAsectionIs) ;
2015-08-29 01:43:18 +02:00
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 ;
2015-09-16 23:32:06 +02:00
'b' : //.binutils <name> (optional)
begin
if copy( line, 1 , 1 0 ) = '.binutils ' then
begin
p1: = trim( copy( line, 1 1 , 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 ;
2015-09-09 00:12:18 +02:00
'e' :
begin
if copy( line, 1 , 1 3 ) = '.extraparams_' then
begin
if length( line) > 1 5 then
begin
if copy( line, 1 4 , 3 ) = 'as ' then
extraparams_as: = copy( line, 1 7 , length( line) )
else
if copy( line, 1 4 , 3 ) = 'ld ' then
extraparams_ld: = copy( line, 1 7 , length( line) ) ;
end ;
end ;
end ;
2015-08-29 01:43:18 +02:00
'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 , 1 0 ) = '.msection ' then
begin
j: = wordcount( line, [ ' ' ] ) ;
if ( j< = 1 ) or ( j> 4 ) then
2016-06-25 10:35:44 +03:00
raise exception. create( rsTheProperFormatOfMsectionIs) ;
2015-08-29 01:43:18 +02:00
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
2021-08-11 08:42:55 +09:00
sections[ j] . address: = StrToQWord( p2)
2015-08-29 01:43:18 +02:00
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 ;
2015-09-16 23:32:06 +02:00
if ( bu= nil ) then
bu: = defaultBinutils;
if ( bu= nil ) and ( binutilslist. count> 0 ) then
bu: = TBinUtils( binutilslist[ 0 ] ) ;
if bu= nil then
2016-06-25 10:35:44 +03:00
raise exception. create( rsNoBinutilsInstalled) ;
2015-08-29 01:43:18 +02:00
2015-09-16 23:32:06 +02:00
bu. assemble( script, extraparams_as, o) ;
2015-08-29 01:43:18 +02:00
//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
2015-09-16 23:32:06 +02:00
bu. GetSections( o, elfsections) ;
2015-08-29 01:43:18 +02:00
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+ 1 0 2 4 , 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+ 1 0 2 4 ) , sections[ i] . actualsize+ 1 0 2 4 , MEM_RESERVE or MEM_COMMIT, page_execute_readwrite) ) ;
2018-12-10 21:23:59 +01:00
if allocsAddToUnexpectedExceptionList then
AddUnexpectedExceptionRegion( sections[ i] . address, sections[ i] . actualsize+ 1 0 2 4 ) ;
2015-08-29 01:43:18 +02:00
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;
2018-05-17 22:08:35 +02:00
defined: = tstringlist. create;
bu. nm( o, defined, undefinedimports) ;
2015-08-29 01:43:18 +02:00
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;
2018-05-17 22:08:35 +02:00
defined. free;
2015-08-29 01:43:18 +02:00
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 ;
2015-09-16 23:32:06 +02:00
bu. ldAndExtract( o, lnkfilename, extraparams_ld, imports, binarysections) ;
2015-08-29 01:43:18 +02:00
//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
2016-06-25 10:35:44 +03:00
raise exception. create( sections[ i] . name + rsTook+ inttostr( length( binarysections[ j] . data) ) + rsBytesWhile+ inttostr( sections[ i] . expectedsize) + rsWhereExpected) ;
2015-08-29 01:43:18 +02:00
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
2018-06-20 18:46:52 +02:00
vpe: = ( SkipVirtualProtectEx= false ) and virtualprotectex( processhandle, pointer( sections[ i] . address) , length( binarysections[ j] . data) , PAGE_EXECUTE_READWRITE, op) ;
2015-08-29 01:43:18 +02:00
WriteProcessMemory( processhandle, pointer( sections[ i] . address) , @ binarysections[ j] . data[ 0 ] , length( binarysections[ j] . data) , x) ;
2018-05-31 22:42:27 +02:00
if vpe then virtualprotectex( processhandle, pointer( sections[ i] . address) , length( binarysections[ j] . data) , op, op) ;
2015-08-29 01:43:18 +02:00
end ;
end ;
end ;
deletefile( lnkfilename) ;
deletefile( o) ;
2018-05-29 15:10:22 +02:00
result : = true ;
2015-08-29 01:43:18 +02:00
finally
script. free;
end ;
except
on e: exception do
begin
2016-06-25 10:35:44 +03:00
MessageDlg( rsGNUAssemblerError, e. message , mtError, [ mbok] , 0 ) ;
2015-08-29 01:43:18 +02:00
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
}
2015-09-19 23:20:34 +02:00
procedure gnuassemble( originalscript: tstrings) ;
var a: TCEAllocArray;
begin
//let's do something similar to autoassembler
gnuassemble2( originalscript, false , a) ;
end ;
2015-08-29 01:43:18 +02:00
end .