Search code examples
printfnasm

Printf in Nasm behavior


I would be grateful if can explain me what's happening in the following example using printf, compiling with nasm and gcc. Why is "sud" only printed on the screen? I don't understand, also, why is "sudobor" printed on the screen when I exchange "push 'sud'" with "push 'sudo'"? Can someone, also explain why do i need to push esp? Is it a null, that is required to be at the end of the string in printf? Thank you advance.

This is string.s file:

section .data

section .text
  global start
  extern printf

start:
     push    ebp           
     mov     ebp, esp  
     push 'bor'
     push 'sud'
     push esp
     call printf
     mov     esp, ebp
     pop     dword ebp

  ret

this is c file:

#include <stdio.h>
#include <stdlib.h>
extern void start();
int main(void) {
  start();

}


Solution

  • First off, thanks for blowing my mind. When I first looked at your code, I didn't believe it would work at all. Then I tried it and reproduced your results. Now it makes perfect sense to me, albeit in a twisted way. :-) I'll try to explain it.

    First, let's look at the more sane way to achieve this. Define a string in the data portion of the ASM file:

    section .data
    string: db "Hey, is this thing on?", 0
    

    Then push the address of that string on the stack before calling printf:

    push string
    call printf
    

    So, that first parameter to printf (last parameter pushed on the stack before the call) is the pointer to the format string. What your code did was push the string on the stack, followed by the stack pointer which then pointed to the string.

    Next, I'm going to replace your strings so that they are easier to track in disassembly:

    push '567'
    push '123'
    push esp
    call printf
    

    Assemble with nasm, and then disassemble with objdump:

    nasm string.s -f elf32 -o string.o
    objdump -d -Mintel string.o
    

    When you push, e.g., '123', that gets converted to a 32-bit hex digit-- 0x333231 in this case. Note that the full 32 bits are 0x00333231.

    3:  68 35 36 37 00          push   0x373635
    8:  68 31 32 33 00          push   0x333231
    d:  54                      push   esp
    

    Pushing onto the stack decrements the stack pointer. Assuming an initial stack pointer of 0x70 (contrived for simplicity), this is the state of the stack before calling printf:

    64:         68:         6c:         70:
    68 00 00 00 31 32 33 00 35 36 37 00 ...
    

    So, when print is called, it uses the first parameter as the string pointer and starts printing characters until it sees a NULL (0x00).

    That's why this example only prints "123" ("sud" in your original).

    So let's push "1234" instead of "123". This means we are pushing the value 0x34333231. When calling printf the stack now looks like:

    64:         68:         6c:         70:
    68 00 00 00 31 32 33 34 35 36 37 00 ...
    

    Now there is no NULL gap between those 2 strings on the stack and this example will print "1234567" (or "sudobor" in your original).

    Implications: Try pushing "5678" instead of "567". You will probably get a segmentation fault because printf will just keep reading characters to print until it tries to read memory it doesn't have permission to read. Also, try pushing a string that is longer than 4 characters (e.g., "push '12345'"). The assembler won't let you because it can't convert that to a 32-bit number.