Style: Indent your operands to a consistent column, so mnemonics of different length don't make your code look so ragged. And use local .label labels inside functions.
Comment code that depends on non-standard behaviour: stdout is only guaranteed to be line-buffered, and isn't automatically flushed when you read stdin in ISO C. Some systems (like Linux) ...
There are numerous small things.
You don't have to keep subtracting and adding 32 from rsp. Allocate the space once at the start of the function (main), reuse it for the duration, and add it back at the end (but see below).
My personal preference would be to use mov ecx,offset question to make it clear that I want the address of the variable, and not the ...
You have all the elements. The only thing that can be eliminated is jmp .run, as follows:
mov si, Prompt
puts: mov ah, 0xe
mov bx, 7
or al, al
.post: int 0x10
Prompt: db 'Hello World', 0
The printing branches are identical, except that they load rcx with different addresses. Better setup rcx when you arrive to the conclusion, and unify printing.
Expanding on that, an idiomatic assembly program would first guess the right string, and correct it if it was wrong:
mov rcx, numberIsPrime
cmp cmp qword [isPrime], 1
There's plenty to optimize here!
In NASM you get the address simply by writing mov di, prompt. This has a shorter encoding than lea di, [prompt]. (In MASM this would be mov di, offset prompt giving the same benefit over the lea form).
Instead of writing the pair mov ah, 0x4c mov al, 0, you could combine these in 1 instruction as mov ax, 0x4C00. This ...
It's fine to write this scan macro for educational purposes, but I think that considering its length a subroutine would have been more appropriate. The macro's bytes are inserted everywhere the macro is invoked. That adds up! Having that many duplicated bytes in a program is a waste of space.
mov ah, 0Eh ; print the ...
This is a nice example of Cooperative Multitasking. Each task has to decide if and when it yields. However based on the fact that the individual tasks are placed in a single program, this could sooner be called Cooperative Multithreading.
In the putstring procedure you erroneously wrote jmp putstring where you clearly wanted to write jmp .continue. That's ...
In the face of all else the assembler assumes a 16bit flat binary, so all that is required is;
~$ nasm ?.asm -o?.com
Although not wrong, but even bits 16 is redundant. In operating system development you might use32 or use64 to utilize those instruction sets, but it would still be a flat binary file. Otherwise, the only thing that makes this type of ...
Move disk 1 from 1 to 3
Move disk 2 from 1 to 2
I think these messages would be more descriptive (less ambiguous) if you included the word peg:
Move disk 1 from peg 1 to peg 3
Move disk 2 from peg 1 to peg 2
mov r9d, r12d ; update r9d after hanoi call
mov r8d, esi ; update r8d after hanoi cal