3

I was given the following instructions:

  1. Make the first instruction in your code a jmp
  2. Make that jmp a relative jump to 0x51 bytes from its current position
  3. At 0x51 write the following code:
  4. Place the top value on the stack into register RDI
  5. jmp to the absolute address 0x403000

What I did so far is:

.intel_syntax noprefix
    .global _start
    _start:
        jmp $+0x51
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        pop rdi
        mov eax, 0x403000
        jmp rax

Isn't this exactly what is required by the assignment? Is there a problem with the relative jump or the absolute jump?

EDIT:

I tried to change the absolute jump with the following, but still no result:

    . . .
    nop
    pop rdi
    mov rax, 0x403000
    jmp fword ptr [eax]

If it can help, this is the object dump derived from the assembled file.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Samir Amir
  • 53
  • 7
  • 1
    I did, infact there are 79 `nop` and when running it the 0x51th instruction is the `pop` – Samir Amir Nov 02 '22 at 20:18
  • 1
    Why did you deleted your comment? – Samir Amir Nov 02 '22 at 20:24
  • What _is_ the problem? – 500 - Internal Server Error Nov 02 '22 at 20:28
  • 1
    I'm not really sure about the problem since i submit the code to an executable which checks the requirements – Samir Amir Nov 02 '22 at 20:32
  • Hang on, `mov eax, 0x403000`, `jmp rax` - do you see the problem here. – 500 - Internal Server Error Nov 02 '22 at 21:04
  • Uh i see, but even using `mov rax, . . .` does not work – Samir Amir Nov 02 '22 at 21:07
  • The way that address is given suggests to me that this is assumed to be a 32-bit program, but I obviously can't know that for sure. – 500 - Internal Server Error Nov 02 '22 at 21:10
  • What's the difference if it is a 32bit program? Does this affect how jump are handled? As I can recall jump are always 32bit address – Samir Amir Nov 02 '22 at 21:13
  • In a 32-bit program it would be `jmp eax`, no? – 500 - Internal Server Error Nov 02 '22 at 21:15
  • I get mismatch operand using `jmp eax` – Samir Amir Nov 02 '22 at 21:24
  • 3
    @500-InternalServerError - mov eax,... will clear the upper 32 bits of rax. – rcgldr Nov 02 '22 at 22:03
  • @rcgldr: Oh, that's right. – 500 - Internal Server Error Nov 02 '22 at 22:32
  • NASM and the GNU Assembler are two different incompatible assemblers. `.intel_syntax noprefix` means you're using the GNU assembler. (@rcgldr). See [How to represent hex value such as FFFFFFBB in x86 assembly programming?](https://stackoverflow.com/q/11733731) re: `0x403000` (NASM, GAS) vs. `403000h` (NASM and most DOS/Windows-only assemblers.) – Peter Cordes Nov 02 '22 at 22:37
  • *Make that jmp a relative jump to 0x51 bytes from its current position* is slightly ambiguous; x86 relative jumps are relative to the *end* of the instruction. So you'd want `jmp $+0x53` to jump 0x53 bytes forward from the start of the jmp, aka 0x51 forward from the end of the jump, with machine code `EB 51` (https://www.felixcloutier.com/x86/jmp). But they say to put code "at 0x51", which implies that offset within the section, and jumping to `$+0x53` would thus jump into the middle of an instruction and do weird things. – Peter Cordes Nov 02 '22 at 22:43
  • @PeterCordes I think https://stackoverflow.com/questions/73226551/absolute-indirect-far-jump-syntax-in-gas-x86-64-asm is related but the solution proposed in the comments didn't work either – Samir Amir Nov 02 '22 at 22:43
  • Your code looks correct, although I'd have used `.rept 0x51` ; `nop` ; `.endr` or something. Or `.fill 0x51, 1, 0x90` to fill with 0x51 repeats of 0x90 nop. (https://sourceware.org/binutils/docs/as/Fill.html). Or fill with zeros since you're jumping over them. Also you could just put a label at the branch target and `jmp after_padding`. – Peter Cordes Nov 02 '22 at 22:49
  • The normal way to do a *near* jump to an absolute address is indeed `mov reg, imm32` / `jmp reg`. There are other ways like push/ret but that's less efficient. If this will be linked into a position-dependent executable, you could also use a relative jump to reach that absolute address (from a known code address), but it's unclear if that's what they want. If some auto-grader program isn't accepting your submission, IDK. Are you sure they want 64-bit code? You originally tagged this [nasm], do they want NASM source instead of GAS? – Peter Cordes Nov 02 '22 at 22:52
  • 1
    I would do so, but as i pointed, _It is a bit strange I am running it on a linux based container, but the code is executed by a script_, so i think line number is also checked – Samir Amir Nov 02 '22 at 22:53
  • [Absolute indirect far jump syntax in GAS x86\_64 asm](https://stackoverflow.com/q/73226551) which you linked is about *far* jumps. You don't want to jump to a new CS:RIP (or CS:EIP like you wrote with 6-byte FWORD). And the instructions are telling you to jump "to 0x403000", not to load a function pointer from there. – Peter Cordes Nov 02 '22 at 22:55
  • @PeterCordes - For Windows, 0x403000 is in the range for 32 bit fixed address builds where `main` starts at 0x401000. For 64 bit fixed address builds, `main` starts at 0x140001000. – rcgldr Nov 02 '22 at 23:18

2 Answers2

3

$+0x51 looks like NASM syntax. For GAS you'd want jmp . + 0x51. But it turns out jmp $+0x51 happens to assemble correctly with GAS without even a warning. I don't know how GAS is interpreting the $ in Intel-syntax noprefix mode! See the GAS manual re: the "special dot symbol". The manual says you can even assign to it to skip bytes in the output, like . = . + 0x51-2. (-2 because the jump itself was 2 bytes.)

clang rejects $+0x51, but strangely also rejects . + 0x51. If the auto-grader script is running on a Mac, you might need to do something else.

Other than that, your code looks correct to me; that's how I'd interpret that assignment. You should email your instructor to ask what's wrong with your code and why the auto-grader program isn't accepting it. Are you sure they want 64-bit code? You originally tagged this [nasm], do they want NASM source instead of GAS? Is that why you used $ instead of . for the current position?


There are a couple minor ambiguities in the instructions, like "Make that jmp a relative jump to 0x51 bytes from its current position"; x86 relative jumps are relative to the end of the instruction. So do they perhaps want jmp . + 0x53 to jump 0x53 bytes forward from the start of the jmp, which is 0x51 forward from the end of the jump, with machine code EB 51 (https://felixcloutier.com/x86/jmp).

But they say to put code "at 0x51", which implies that offset within the section, and jumping to .+0x53 would thus jump into the middle of an instruction and do weird things. So I think they do mean that you should jump to 0x51 bytes ahead of the start of the jmp instruction, like you're doing.


When I assemble+link it into a static executable for Linux (gcc -nostdlib -static foo.S) and run it under gdb ./a.out (then layout reg / layout next / starti / stepi), I see a relative jump to 0x401051, skipping past all the NOPs. The next instruction is pop of argc into RDI, then mov+jmp leaving RIP=0x403000. (And then stepping again raises SIGSEGV, because code fetch from that address faulted.)

So it works for me, doing all the things the assignment says it should.


You don't need to write out each NOP separately

I'd have used .rept 0x51-2 ; nop ; .endr or something. Or .fill 0x51-2, 1, 0x90 to fill with 0x4f repeats of a 1-byte pattern with value 0x90 (nop). (https://sourceware.org/binutils/docs/as/Fill.html).

Or fill with zeros since you're jumping over them. (.space or .zero).

Also you could just put a label at the branch target and jmp after_padding! That would be a way to work around clang's built-in assembler not handling jmp . + 0x51.


You linked Absolute indirect far jump syntax in GAS x86_64 asm but that's about far jumps. You don't want to jump to a new CS:RIP (or CS:EIP like you wrote with 6-byte FWORD). And the assignment is telling you to jump "to 0x403000", not to load a function pointer from there.

The normal way to do a near jump to an absolute address is indeed mov reg, imm32 / jmp reg. There are other ways like push/ret but that's less efficient. For example:

  • Call an absolute pointer in x86 machine code - in GAS and NASM, jmp 0x403000 Just Works, assuming you link it into a non-PIE executable so that destination is in range of the jmp rel32. (which can reach +-2GiB)

    That's a relative jump to reach that absolute address (from a known code address, so it doesn't work with ASLR), but it's unclear if that's what they want.

  • How to jump to memory location in intel syntax for both x64 and x32 - jmp qword ptr [RIP+destination] ; destination: .quad 0x403000 to do a memory-indirect jump with a RIP-relative addressing mode to load an 8-byte pointer into RIP. You can even put the pointer right after the jmp, instead of in .rodata.

  • JMP to absolute address (op codes) - shows the inefficient push/ret way first, without pointing out the efficiency problem with branch prediction.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • I would assume the goal would be `jmp $+0x51`, followed by 0x4f nops (or 0x00 or 0xcc). I don't know if `jmp short $+0x51` is needed to force a 2 byte jump. I don't understand why the OP though that `mov rax,0x....` was an issue. For Windows with fixed based address and with normal program prefix, `main` would be at 0x140001000 for 64 bit build, 0x401000 for 32 bit build. – rcgldr Nov 02 '22 at 23:22
  • @rcgldr: I think they're just trying random variations because an automated homework-grading system is rejecting it. Agreed that `jmp . + 0x51` is the most likely interpretation. The filler they jump over doesn't have to be NOPs, though, as none of it is executed. It doesn't need to be a NOP slide. – Peter Cordes Nov 02 '22 at 23:25
  • Missed an edit in prior comment, nop, 0x00, or 0xcc (int 3), or ... . There's no filler for `jmp rax`, not sure what is supposed to happen there. In my now deleted answer, I padded with `main+2000h-$` . – rcgldr Nov 02 '22 at 23:26
  • @rcgldr: What do you mean "no filler"? You mean no memory mapped, so no contents filling that virtual page? If you single step this code, it does jump to RIP=0x403000. The next single-step segfaults because there's no executable memory there in the static executable I made, but that is what the assignment said to do. Perhaps the next step is using a linker script to put an executable page there, or just a large enough .text section in a non-PIE executable. (The OP commented that Linux is involved, with some kind of script checking something.) – Peter Cordes Nov 02 '22 at 23:31
  • @rcgldr: Linux is the same, that page is unmapped unless `.text` is larger or there's `.data` or `.bss` there. But no, the jump itself doesn't fail, you can still single-step it and see RIP=0x403000. I literally did that in GDB on my x86-64 Linux desktop. The *next* single-step after that is what faults, raising #PF from code-fetch from that address. The jump itself would only fault if the destination address was non-canonical, like `0x12345678abcdef` (not 48-bit sign-extended to 64). – Peter Cordes Nov 02 '22 at 23:40
  • I deleted my prior comment, as the jump din't fail with Visual Studio debugger when stepping through the code. Even if I run it from console window, I get exception at 0x403000, not on the jump. Still I wonder why a 64 bit code assignment has the code jump to 0x403000, which seems like a 32 bit code address. – rcgldr Nov 03 '22 at 00:14
  • @rcgldr: x86-64 Linux `ld` defaults to `0x400000` as the base address for non-PIE executables, with `.text` starting at `0x401000`. That's why I said earlier that getting something mapped at `0x403000` would only take a couple more pages of `.text`, or a `.data` or `.bss` page. [Why Linux/gnu linker chose address 0x400000?](https://stackoverflow.com/q/14314021) / [Why is address 0x400000 chosen as a start of text segment in x86\_64 ABI?](https://stackoverflow.com/q/39689516) – Peter Cordes Nov 03 '22 at 00:34
  • @rcgldr: as for why the fault isn't raised until after RIP=0x403000 - if there's a *valid* page fault, e.g. more code to page in from disk, the exception-return address should be 0x403000, not re-running the jump. Logically, code fetch for the next instruction doesn't happen until after the current instruction fully finishes everything. So the fault semantics preserve that order of execution. A `ret` to an unmapped page must not pop twice on page fault, and we don't want it to have to check before ending. Only jumps to non-canonical addresses fault, so RIP could really be only 48 bits wide. – Peter Cordes Nov 03 '22 at 01:00
  • 1
    Given that this is an automated system for them to test their code with, I wonder if they want people to use something like `mov rdi,[rsp]` rather than doing 'pop'. Maybe the automated system checks the stack after to see if it's original value? I'd wonder if @SamirAmir could try replacing `pop rdi` with `mov rdi,[rsp]` and see what happens? – Michael Petch Nov 03 '22 at 18:23
  • 1
    @MichaelPetch: Oh yes, good point, `pop` has other effects that the assignment didn't ask for. Well spotted. – Peter Cordes Nov 03 '22 at 20:37
1

The solution is somewhat irritating: the instructions used are actually correct, even the absolute jump using a simple register. It is the text of the request that is incorrect or otherwise ambiguous, the pop instruction is requested to be at position 0x51, but what the sentence really means (according to the organizer) is that there must be 0x51 nop instructions.

This means that the total and exact number of nops is 81 and that the offset of the first jump (in addition to taking into account the size of the jmp instruction itself) must take into account the added nops, so it becomes jmp $ + 0x53.

Samir Amir
  • 53
  • 7
  • In proper GAS syntax, that's `jmp . + 0x53` https://sourceware.org/binutils/docs/as/Dot.html . `$ + 0x53` seems to work like NASM, at least in `.intel_syntax noprefix` mode, but I don't think it's documented. – Peter Cordes Nov 03 '22 at 22:51
  • So the assignment wants the code to jump to the operand of `mov eax, 0x403000` . Without a better explanation I can see this is "somewhat irritating". – Sep Roland Nov 03 '22 at 23:50