Search code examples
assemblyx86gnu-assemblerintel-syntax

Error: junk `0x7f' after expression when assembling using Gas


I'm trying to write a 64-bit shellcode to read a file called '/proc/flag'. However, I'm getting some random errors when I'm compiling the assembly and don't know why its occuring.

This is my assembly file readflag.S:

.intel_syntax noprefix
.global _start
.type _start, @function
_start:
  mov     dword [rsp], '/pro'     /* build filename on stack */
  mov     dword [rsp+4],  'c/fl'
  push    'ag'
  pop     rcx
  mov     [rsp+8], ecx
  lea     rdi, [rsp]              /* rdi now points to filename '/proc/flag' */
  xor     rsi, rsi                /* rsi contains O_RDONLY, the mode with which we'll open the file */
  xor     rax, rax
  inc     rax
  inc     rax                     /* syscall open = 2 */
  syscall
  mov     rbx, rax                /* filehandle of opened file */
  lea     rsi, [rsp]              /* rsi is the buffer to which we'll read the file */
  mov     rdi, rbx                /* rbx was the filehandle */
  push    byte 0x7f              /* read 127 bytes. if we stay below this value, the generated opcode will not contain null bytes */
  pop     rdx
  xor     rax, rax                /* syscall read = 0 */
  syscall
  lea     rsi, [rsp]              /* the contents of the file were on the stack */
  xor     rdi, rdi
  inc     rdi                     /* filehandle; stdout! */
  mov     rdx, rax                /* sys_read() returns number of bytes read in rax, so we move it to rdx */
  xor     rax, rax
  inc     rax
  syscall                     /* syscall write = 1 */
  push    byte 60                /* some bytes left... */
  pop     rax                     /* exit cleanly */
  syscall

These are the errors I'm getting when I compile the assembly:

readflag.S: Assembler messages:
readflag.S:7: Error: junk `pro10mov dword [rsp+4]' after expression
readflag.S:21: Error: junk `0x7f' after expression
readflag.S:33: Error: junk `60' after expression
objcopy: 'readflag.o': No such file

I thought push byte 60 was considered a valid instruction in Intel syntax. I'm not sure where the errors are coming from. Would appreciate any help.


Solution

  • When you specify the .intel_syntax noprefix option, you instruct the Gnu assembler that you will be using MASM syntax. What you've written is actually NASM syntax, which is similar to MASM syntax in many ways, but subtly different in others.

    For a complete discussion of the differences, see this section of the NASM manual.
    For a quick overview of NASM vs. MASM syntax, refer to this document.
    (This second document used to be hosted online, in a more readable HTML format, here, but the link has gone down and I unfortunately can't find a copy in the Wayback Machine.)

    The big thing that needs to change in your code is that you need to include the PTR directive after each of the size specifiers. So, for example, instead of:

        mov     dword [rsp],   '/pro'
        mov     dword [rsp+4], 'c/fl'
    

    you need to write:

        mov     dword ptr [rsp],   '/pro'
        mov     dword ptr [rsp+4], 'c/fl'
    

    Also, while MASM syntax would normally write hexadecimal constants with a trailing h, instead of the leading 0x, Gas's "MASM" mode doesn't support this, and you need to use the C-style 0x notation, even when using Intel syntax.

    I thought push byte 60 was considered a valid instruction in Intel syntax.

    No, not really. The only size values you can PUSH and POP from the stack are the processor's native register width. So, in 32-bit binaries, you must push and pop 32-bit values, whereas in 64-bit binaries, you must push and pop 64-bit values.* That means these lines of code are technically wrong:

    push    'ag'
    push    byte ptr 0x7f
    push    byte ptr 60
    

    MASM will give you a warning about an invalid operand size for the latter two instructions that have a size explicitly specified, but it will implicitly extend these constants to be 64-bit values and still assemble successfully. I assume the Gnu assembler can do this automatic value-extension, too, so you should just drop the size directive:

    push    'ag'
    push    0x7f
    push    60
    

    __
    * Technically, you can use an operand size override prefix to allow you to push a 16-bit immediate onto the stack, both in 32-bit and 64-bit mode. But you really shouldn't ever do that because it misaligns the stack, creating performance problems (and, if you're interoperating with code compiled in other languages, breaks the ABI). Only push 16-bit values onto the stack when writing 16-bit code. If you want to push a 16-bit value in 32-bit or 64-bit mode, just let the assembler extend it to 32-bit or 64-bit, accordingly.