flat assembler
Message board for the users of flat assembler.

Index > Tutorials and Examples > Windows on ARM64 - simple example with fasmg

Author
Thread Post new topic Reply to topic
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 14 Oct 2022, 19:04
I have finally got my hands on an ARM64-based machine running Windows, and - obviously - one of the very first things I wanted to try was to assemble some new PE files.

It turned out to be very easy with already available resources. I used the existing aarch64 includes for fasmg (made by tthsqe) and the standard PE formatter that comes with fasmg.

My first working example, analogous to the basic PE demos from fasm packages:
Code:
format binary as 'exe'

PE.Settings.Magic = 0x20B
PE.Settings.Machine = IMAGE_FILE_MACHINE_ARM64
PE.Settings.ImageBase = 0x140000000
PE.Settings.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE + IMAGE_FILE_LARGE_ADDRESS_AWARE
PE.Settings.DllCharacteristics = IMAGE_DLLCHARACTERISTICS_NX_COMPAT + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
PE.Settings.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_GUI
include 'format/pe.inc'

include 'aarch64.inc'
define xIP0? x16        ; Windows-specific aliases
define xIP1? x17

section '.text' code readable executable

        entry $

                mov     x0,0
                adr     x1,_message
                adr     x2,_caption
                mov     x3,0
                bl      MessageBoxA

                bl      ExitProcess

        MessageBoxA:
                adr     xip0,imp__MessageBoxA
                ldr     xip0,[xip0]
                br      xip0

        ExitProcess:
                adr     xip0,imp__ExitProcess
                ldr     xip0,[xip0]
                br      xip0

section '.data' data readable writeable

  _caption db 'Windows on ARM64',0
  _message db 'Hello, world of assembly!',0

section '.idata' import data readable writeable

  dd 0,0,0,RVA kernel_name,RVA kernel_table
  dd 0,0,0,RVA user_name,RVA user_table
  dd 0,0,0,0,0

  kernel_table:
    imp__ExitProcess dq RVA _ExitProcess
    dq 0
  user_table:
    imp__MessageBoxA dq RVA _MessageBoxA
    dq 0

  kernel_name db 'KERNEL32.DLL',0
  user_name db 'USER32.DLL',0

  _ExitProcess dw 0
    db 'ExitProcess',0
  _MessageBoxA dw 0
    db 'MessageBoxA',0

section '.reloc' fixups data readable discardable

  if $=$$
    dd 0,8              ; if there are no fixups, generate dummy entry
  end if    
(Tested on Windows 11 on ARM.)

This might encourage me to work on CALM-based ARM64 instruction set myself... But no promises. And my PE tutorial deserves another chapter.
Post 14 Oct 2022, 19:04
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 17 Oct 2022, 16:12
Taking the universal_template.asm from the PE tutorial as a starting point, just a couple of minor changes suffices to turn it into a valid ARM64 PE.

Obviously, we need to change the CPU instruction set. Replace
Code:
use 'x64.inc'
use64    
with
Code:
use 'aarch64.inc'

define xIP0? x16
define xIP1? x17    
These additional register aliases are related to Windows conventions, x16 and x17 are designed as "intra-procedure call scratch registers" and we are going to use one of them under this alias to make import stubs for calling Windows API.

The default image base needs to be different for ARM64, with a value above 32-bit range:
Code:
DEFAULT_IMAGE_BASE := 0x140000000    
The change of target architecture should be quite obvious:
Code:
        .Machine                        dw IMAGE_FILE_MACHINE_ARM64    
Now all that is left is to replace the x86 code.
Code:
        EntryPoint:

                mov     x0,0
                adr     x1,MessageString
                adr     x2,CaptionString
                mov     x3,0
                bl      stub_MessageBoxA

                bl      stub_ExitProcess

        stub_MessageBoxA:
                adr     xip0,MessageBoxA
                ldr     xip0,[xip0]
                br      xip0

        stub_ExitProcess:
                adr     xip0,ExitProcess
                ldr     xip0,[xip0]
                br      xip0    
While x86 code could also use import stubs to avoid indirect call instruction (which has a slightly longer opcode that direct one), in case of ARM they are pretty much necessary - there is no such instruction as x86's indirect call. We are using ADR to get the address of the pointer, it is encoded with PC-relative value and does not require relocation. The standard stubs in Windows are similar, but they use ADRP to get the address of the page of pointers, and then LDR adding offset within the page, which would look something like this:
Code:
                adrp    xip0,MessageBoxA
                ldr     xip0,[xip0,(MessageBoxA-IMAGE_BASE) and 0FFFh]    
It allows a longer range between the stub code and the import table, since ADR only operates within 1M radius. For a tiny example constructed in assembly this does not matter, though.

Note: this variant does not work with tthsqe's aarch64.inc, because current implementation there tries to generate a superfluous relocation record for ADRP. But it is easy to fix, works for me with a simple correction. If this becomes the official part of the tutorial, I'm going to provide a corrected aarch64.inc in the tutorial packages.

And that is all - after applying these changes and assembling with fasmg, we get a working template for the new architecture (tested on Windows 11 on ARM).
Post 17 Oct 2022, 16:12
View user's profile Send private message Visit poster's website Reply with quote
alorent



Joined: 05 Dec 2005
Posts: 221
alorent 07 Jun 2023, 12:24
Hi,

I'm trying to create a OBJ following this template to link it with other Visual Studio 2022 C++ (ARM64) binaries.

The OBJ is created successfully but when VS link it, I get:

fatal error LNK1107: invalid or corrupt file: cannot read at 0x228

Not sure if I should post it on a separate topic or re-use this one, as it's related to this code.

Thanks in advanced!

Thanks!
Post 07 Jun 2023, 12:24
View user's profile Send private message Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 07 Jun 2023, 15:35
The COFF formatter differs from the PE one quite substantially, so it should not be surprising that following this template is not enough to get it working. I haven't tried this myself yet.
Post 07 Jun 2023, 15:35
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20303
Location: In your JS exploiting you and your system
revolution 07 Jun 2023, 15:39
I notice the data sizes are not ARM compatible. Is that a deliberate choice?

It looks very wrong to me to see this with "dd":
Code:
  dd 0,0,0,RVA kernel_name,RVA kernel_table ; <--- ARM uses dw for 32-bit values    
Post 07 Jun 2023, 15:39
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 07 Jun 2023, 16:50
To replace the x86-compatible names, the formatter modules that use them should either be moved to a separate namespace, or cleaned up to only use EMIT everywhere. I should do the latter anyway sometime, but for an easily patched up proof of concept they were all left unchanged. Note that the PE formatter is taken straight from the "fasm 1 compatibility package" which by definition has been x86-focused.
Post 07 Jun 2023, 16:50
View user's profile Send private message Visit poster's website Reply with quote
tthsqe



Joined: 20 May 2009
Posts: 767
tthsqe 08 Jun 2023, 02:23
Thomasz, I hope you got my message giving you the green lights. I didn't know what I was doing when I attempted the relocations, and hope you fixed some of the bugs, one of which I commented in the code.
Post 08 Jun 2023, 02:23
View user's profile Send private message Reply with quote
alorent



Joined: 05 Dec 2005
Posts: 221
alorent 08 Jun 2023, 10:49
Thanks guys.

I have been doing some tests and finally got the ARM64 .OBJ to link with VS2022 (ARM64).

Example:

Code:
include 'format/format.inc'

format MS COFF

COFF.Settings.Machine = IMAGE_FILE_MACHINE_ARM64
 
include 'cpu/aarch64.inc'
include 'win32ax.inc'

.code

public MyArmFunction as 'MyArmFunction'

proc MyArmFunction 

        mov             x1, 1
        mov             x2, 2

endp    


I had to change one line in the "coffms.inc" file. I changed this line:

Code:
if COFF.MACHINE = IMAGE_FILE_MACHINE_I386    


for

Code:
if COFF.MACHINE = IMAGE_FILE_MACHINE_I386 | COFF.MACHINE = IMAGE_FILE_MACHINE_ARM64    


And this is the C++ code in VS2022 to call it:

Code:
extern "C" void __stdcall MyArmFunction(void);

int main()
{
    MyArmFunction();
}    



Probably Tomasz has a more elegant way to do it Smile

Thanks!
Post 08 Jun 2023, 10:49
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.