Linux Write Sys Call Using String on Stack

Using write() to output string on stack

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

Linux write sys call using string on stack

Your syscall numbers seem WAY off.

From your use of movq and the "r" registers, I can guess you are trying on x86-64. Taking a look at /usr/include/asm/unistd_64.h, I can see the following:

#define __NR_write                              1
#define __NR_stat 4
#define __NR_exit 60

strace agrees with me:

$ strace ./abc
execve("./abc", ["./abc"], [/* 43 vars */]) = 0
stat("", NULL) = -1 EFAULT (Bad address)
write(-1698988341, NULL, 3 <unfinished ... exit status 0>

Note that the parameters are also way off. You are also using the wrong registers for the rest of the parameters. The calling convention on x86-64, AFAIK, uses the following registers for the parameters, in this order: rdi, rsi, rdx, r10, r8, r9.

Perhaps you are trying to do syscalls on x86-64 the way they are done on i386 and expecting it to be the same?

How to use the write system call using C langauge in Linux?


  • You need to ensure that open succeeded instead of blindly writing to the file-descriptor.

    • Always check the return value of a syscall (and most C standard library functions) and check errno if the return value indicated an error.
  • Your string literal will include a hidden \0 (NULL) character after the dot.

    • Writing text directly to the file will therefore include the trailing \0 which is what you're seeing.

These issues can be rectified by:

  • Always checking the return value of a syscall - and in this case: print a helpful error message to stdout and perform any necessary cleanup (the goto closeFile; statement).

    • Because C doesn't have a native try/catch or RAII it means its difficult to write terse error-handling and cleanup code, but using goto for common clean-up code is generally acceptable in C, hence the goto closeFile statement.
  • Using strlen to get the actual length of the string.

    • Though in a pinch it's okay to use sizeof(text) - 1 provided you're in a scope where the C compiler knows the length of text as using sizeof() won't work if you cross a function boundary due to array pointer decay.

Like so:

void writeToFile() {

int fd = open( "text1.txt", O_CREAT | O_WRONLY ); // Use `O_WRONLY` instead of `O_RDWR` if you're only writing to the file. Use `O_CREAT` to create a file if it doesn't already exist.
if( fd == -1 ) {
printf( "Error opening file: errno: %d - %s\n", errno, strerror( errno ) );
return;
}

size_t textLength = strlen( text );
size_t written = write( fd, text, textLength );
if( written == -1 ) {
printf( "Error writing text: errno: %d - %s\n", errno, strerror( errno ) );
goto closeFile;
}
else if( written < textLength ) {
printf( "Warning: Only %d of %d bytes were written.", written, textLength );
goto closeFile;
}
else {
// Carry on as normal.
}

closeFile:
if( close( fd ) == -1 ) {
printf( "Error closing file: errno: %d - %s\n", errno, strerror( errno ) );
}
}

Writing my own system call to count how many o's appear in string pointer

The correct way is to:

  • Pass pointer to string to the syscall

  • do a strlen_user(string) to find out the size of the string

  • Provide a buffer large enough to hold the string (for example with kmalloc())

  • use copy_from_user() to copy the string from userspace into a kernel buffer (check if the call succeeded!)

  • Perform the substitution operation on the kernel buffer

  • use copy_to_user() to copy the string back to userspace

  • Free the buffer if you allocated one with kmalloc().

You should not access userspace directly, since the kernel may oops if the memory pointed to by your pointer is not available (i.e. swapped out) or protected for read/write. This is for example the case in your program; trying to write to this memory directly (without copy_to_user) will result in a kernel page fault. Note : Direct access can work, but it is not safe!

Furthermore, you have to use strlen(string) (or strlen(string)+1 to account for the terminating zero byte) when operating with strings.

adding a simple system call to linux kernel

Your code is executed in kernel context whilst the buffer with data comes from the userspace. If you need to process some string from the userspace, copy it to the kernel memory using strncpy_from_user() function. If you don't follow the scheme and simply try to access the data directly, this will lead to a memory access violation.

A better solution (based on your code) would look somewhat like this:

asmlinkage long sys_hello(char* name) {
long nb_symbols;
char *name_internal;
long i;

/*
* Estimate the buffer length sufficient
* to accommodate the string
*/
for (i = 1; ; ++i) {
nb_symbols = strnlen_user(name, i);
if (nb_symbols <= 0)
return -EFAULT;

if (nb_symbols < i)
break;
}

/* Allocate the storage */
name_internal = kmalloc(nb_symbols + 1, GFP_KERNEL);
if (name_internal == NULL)
return -ENOMEM;

if (strncpy_from_user(name_internal, name, nb_symbols + 1) !=
nb_symbols) {
kfree(name_internal);
return -EFAULT;
}

printk("The 'name' is '%s'\n", name_internal);
kfree(name_internal);

return 0;
}

However, please note that such a loop (as one in my example) might not be an acceptable solution for buffer length estimation. Ideally, you could drop it and use a static char array of fixed length to use strncpy_from_user() with.

Mixing syscall write with printf on linux

That's because stdio (the C standard I/O package) uses line buffering for stdout if stdout goes to a terminal. That means that data you write is not immediately sent to the terminal but rather buffered until a whole line is available. What you observe in your first program (hello on a line) is that no character of Hello is actually written until you call printLetter with a line feed, causing the buffer for stdout to be flushed to the terminal.

I see the following approaches to solve your problem (any single of them does the trick, but use only one approach):

  • edit ansiSetRed etc. to call fwrite instead of directly executing a write system call. This should make buffering work as expected.
  • call setbuf(stdout, NULL) to turn off buffering before writing any data.
  • write to stderr instead of stdout as stderr is unbuffered
  • execute fflush(stdout) after each printf to manually flush stdout.
  • Rewrite printLetter to use the system call write instead of printf.

How to use the write() system call in C++?

c++ has its own input/output operation and for the output, the standard is std::cout.

But there is a function names write in c and also a write that uses file descriptor : int

here is the man 2 write

WRITE(2)                            Linux Programmer's Manual                            WRITE(2)

NAME
write - write to a file descriptor

SYNOPSIS
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

a system call is implemented in the Linux Kernel
And the user has no access to it. But A library like standard C library or like GNU C library wraps system calls that others can use it easily.

See this page: https://www.kernel.org/doc/man-pages/

then go to

2: System calls documents the system calls provided by the Linux kernel.

then at the bottom you will find write(2)

and a simple example is:

int fd = open( "file", O_RDONLY );
if( fd == -1 )
{
perror( "open()" );
close( fd );
exit( 1 );
}

char buffer[ 100 ];
ssize_t read_byte;

if( ( read_byte = read( fd, buffer, 100 ) ) == -1 )
{
perror( "read()" );
close( fd );
exit( 1 );
}

if( write( STDOUT_FILENO, buffer, read_byte ) == -1 )
{
perror( "write()" );
close( fd );
exit( 1 );
}

close( fd );

and you should use this header files as well:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <stdlib.h> // exit()
#include <stdio.h> // for perror()

and these functions also is known as Low-Level I/O function

Thus depending on what level you are coding, then you should decide to use what functionality is best for you. I do not think c++ programmers use these level for a standard c++ code.


A simple screenshot of where is system-called located.

system-call

source



Related Topics



Leave a reply



Submit