1

On Ubuntu 20, in x86-assembly, I am attempting to output a string pushed onto the stack using the write syscall. The little endian hexadecimal bytes pushed onto the stack are "hello world" in ASCII. My goal is to output the string solely from the stack. Why? To understand the stack more and how arguments are passed to syscalls/functions via registers.

From my basic understanding I should push the arguments onto the stack and mov the esp register (pointing to the top of the stack) into registers in the order of write's required arguments.

Here is my attempt (this outputs nothing):

; compile: nasm -f elf32 -g test.asm && ld -melf_i386 test.o -o test
section .text
global  _start

_start:
    xor  eax, eax

    push 0xc
    mov  ebx, esp

    push eax
    push 0x00646c72
    push 0x6f77206f
    push 0x6c6c6568

    mov  ecx, esp
    mov  edx, 1
    mov  eax, 4
    int  0x80

    mov  eax, 1
    int  0x80

Expected output:

hello world

How do I correct my code to display the expected output? And what am I doing wrong?

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
ndks_43
  • 13
  • 3
  • 1
    As a start, https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-and-user-space-f/2538212#2538212. You've got all the arguments in the wrong registers. You should have the system call number (4) in eax, the file descriptor (1) in ebx, the data pointer in ecx (that one you've got right), and the byte count (11) in edx. I don't know where you got the info for what you have but it's totally bogus. – Nate Eldredge Nov 13 '20 at 02:38
  • Also, you probably want to invoke the exit system call after writing, otherwise the next thing your program will do is execute garbage and crash. – Nate Eldredge Nov 13 '20 at 02:39
  • I see, I modified the code and there's still no output. – ndks_43 Nov 13 '20 at 02:48
  • You are now calling `write(crazy number, buffer, 1)`. You still haven't put the file descriptor in `ebx`, and the byte count in `edx` is 1. – Nate Eldredge Nov 13 '20 at 02:49
  • Oh, is that `push 0xc` supposed to be the length count? It doesn't belong on the stack at all. System calls follow a different convention from function calls; everything is in registers, not on the stack. I think it should be 11 or 0xb instead, though, because there's no point in writing the trailing null byte. – Nate Eldredge Nov 13 '20 at 02:51
  • After modified the code it seems I print all of the environment variables now (the stack) – ndks_43 Nov 13 '20 at 02:52
  • Ok I got it! You can answer if you want your comments were helpful! – ndks_43 Nov 13 '20 at 02:54

1 Answers1

3

I suggest reading What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64. You need the system call number in eax and the arguments in ebx, ecx, edx, esi, edi, ebp in that order. write takes three arguments so you need:

  • system call number (4) in eax

  • file descriptor (1 for standard output) in ebx

  • pointer to buffer (esp) in ecx, this you have right

  • count of bytes to be written (11, since there is no reason to write the null byte) in edx. (By value, not a pointer to the length: write lets you know how many bytes were actually written via the return value in eax, not an output arg.)

There is no need to put anything else on the stack. The kernel doesn't look for arguments there, everything is in registers. This is a difference from the normal function calling convention.

Also, you probably want to invoke the exit system call afterwards, or your program will run off the end of its code and crash by executing garbage. That is system call number 1 and it takes one argument in ebx which is an exit code.

The following works:

section .text
global  _start

_start:
    push 0x00646c72
    push 0x6f77206f
    push 0x6c6c6568

    mov  ecx, esp
    mov  edx, 11
    mov  ebx, 1
    mov  eax, 4
    int  0x80

    mov  eax, 1
    xor  ebx, ebx
    int  0x80
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82