Using printf in assembly leads to empty output when piping, but works on the terminal
Use call exit
instead of a raw _exit
syscall after using stdio functions like printf. This flushes stdio buffers (write
system call) before making an exit_group
system call).
(Or if your program defines a main
instead of _start
, returning from main
is equivalent to calling exit
. You can't ret
from _start
.) Calling fflush(NULL)
should also work.
As Michael explained, it is OK to link the C-library dynamically. This is also how it is introduced in the "Programming bottom up" book (see chapter 8).
However it is important to call exit
from the C-library in order to end the program and not to bypass it, which was what I wrongly did by calling exit-syscall
. As hinted by Michael, exit does a lot of clean up like flushing streams.
That is what happened: As explained here, the C-library buffers the the standard streams as follows:
- No buffering for standard error.
- If standard out/in is a terminal, it is line-buffered.
- If standard out/in is a not a terminal, it is fully-buffered and thus flush is needed before a raw exit system call.
Which case applies is decided when printf
is called for the first time for a stream.
So if printf_try
is called directly in the terminal, the output of the program can be seen because hello
has \n
at the end (which triggers the flush in the line-buffered mode) and it is a terminal, also the 2. case.
Calling printf_try
via $(./printf_try)
means that the stdout is no longer a terminal (actually I don't know whether is is a temp file or a memory file) and thus the 3. case is in effect - there is need for an explicit flush i.e. call to C-exit
.
Printf call from assembly do not print to stdout
If you want to use C library functions you shouldn't use _start
, but good old main
. If you steal _start
from libc, it won't be able to perform several setup and cleanup operations, such as flushing the open C files.
This is visible in this case only when writing to file, as when writing to a tty stdout is line-buffered by default, so it's flushed immediately. When redirecting to file, instead, the data is just copied in a temporary buffer, to be flushed when it's full enough, or at termination - but the C runtime cleanup functions aren't ever called in your case, so the data is never actually passed to the OS.
Another possibility is to exit using the exit
libc function (extern exit
at the beginning and call exit
at the end of the execution), which should handle the cleanup, but I'm not sure if stuff from libc is even required to work fine if it doesn't get the chance to be initialized in first place should be safe on Linux, see @PeterCordes comment and thanks to @Kirill_Zaitsev for trying it.
ASM printf: no output if string doesn't include \n newline
The exit syscall (equivalent to _exit
in C) doesn't flush the stdout buffer.
Outputting a newline causes a flush on line-buffered streams, which stdout will be if it is pointed to a terminal.
If you're willing to call printf
in libc, you shouldn't feel bad about calling exit
the same way. Having an int $0x80
in your program doesn't make you a bare-metal badass.
At minimum you need to push stdout;call fflush
before exiting. Or push $0;call fflush
. (fflush(NULL)
flushes all output streams)
Why didn't printf print out the string in this buffer overflow?
stdout defaults to line-buffered, and the string doesn't end with a newline. If you changed it to puts("Pwned!!");
then stdout
would get flushed before puts
returns.
But with printf
, the data is just sitting there in a stdio buffer until something else prints a newline, or until fflush(stdout)
. exit()
or cleanly returning from main
will cause fflush, but segfaulting will kill the process without ever making a system call to hand that I/O data to the OS.
This is exactly the same problem as Using printf in assembly leads to an empty ouput except that case was using an _exit(2)
system call instead of segfaulting.
If the goal is to force you to get win()
called without breaking later execution, that's another level of challenge.
But if win()
is supposed to represent something like a successful ROP attack that calls system()
or execve
with "/bin/sh"
then win()
is not well-written. execve
will happen on the spot, not at some later time.
Printf without newline in assembly
fflush() flushes buffered output in line or full-buffered stdio streams:
extern fflush
...
xor edi, edi ; RDI = 0
call fflush ; fflush(NULL) flushes all streams
...
Alternatively, mov rdi, [stdout]
/ call fflush
also works to flush only that stream. (Use default rel
for efficient RIP-relative addressing, and you'll need extern stdout
as well.)
Why can't I redirect the output of my binary to a file?
The correct way of exiting the program was to use a call to exit libc function instead of the raw syscall
How register offset affects printing
This code looks like it's set up for you to store the string bytes into buffer
, relative to RSI, so the %s
conversion in the format string will print it. But with the string in .data
instead of .rodata
where you should put read-only data, yes you can overwrite bytes of the format string at runtime.
When you overwrite the \n
, printf
doesn't flush the output buffer because stdout
is line buffered. You exit with sys_exit
(direct system call) instead of call exit
or returning from main
, leaving the data un-printed. See Using printf in assembly leads to an empty ouput
You can use ltrace ./my_program
to see the library function calls it makes.
Related Topics
How to Fix Java.Lang.Module.Findexception: Module Java.Se.Ee Not Found
32-Bit Absolute Addresses No Longer Allowed in X86-64 Linux
Sed In-Place Flag That Works Both on MAC (Bsd) and Linux
Environment Variable Substitution in Sed
How to Print a Number in Assembly Nasm
More Elegant "Ps Aux | Grep -V Grep"
Redirect Stderr/Stdout of a Process After It's Been Started, Using Command Line
Shell Command to Tar Directory Excluding Certain Files/Folders
Imagemagick Security Policy 'Pdf' Blocking Conversion
Connection Refused to Mongodb Errno 111
What Killed My Process and Why
Multiple Glibc Libraries on a Single Host
Use Expect in a Bash Script to Provide a Password to an Ssh Command
Using Printf in Assembly Leads to Empty Output When Piping, But Works on the Terminal
How to Preserve Quotes in Printing a Bash Script'S Arguments
How to Access Physical Addresses from User Space in Linux