Exploring separators, code injection, and more
The Python programming language is extremely beginner-friendly thanks to its simple English-like syntax and powerful yet intuitive built-in functions. Chances are that your first line of Python code looked something like this:
As you get more comfortable with the language, you start learning about the different capabilities of the built-in
print() function. The more experience you get as a Python developer, the more edge cases, hacks, exotic syntax, and advanced features you discover.
In this article, I will show you what the
print() function can do, starting from its basic functionalities and then covering increasingly more advanced use cases and interesting hacks.
The most basic way to use the
print() statement, and also the most common is to log a message (string or another object) to the console.
And unsurprisingly, the console output would be the following:
Now, let’s get into less-known features.
print() function also allows you to specify a character to be appended at the end of the string with the
end parameter. For example, if you want to print the sequence
"***" after the main argument, you can do this:
And that would output the following text (without a newline at the end):
If you wish to exclude the newline from the output to print everything on the same line, you can set
end='' like this:
Separators are another key feature of the Python
print() statement. To begin, you can print multiple values with the same function call by separating them with commas:
print() function implicitly sets its parameter
sep=' ' so that arguments passed like this are separated by a space when printed. However, you can specify your own string to separate each argument:
Custom object representation
When printing complex objects in Python, you usually specify how they should look as a string instead of relying on the default undescriptive behavior:
<__main__.Entry object at 0x7fc3ddd5ffd0>
So, when you write a class, you can overwrite the
__repr__() dunder methods to let Python know how it should handle the printed object.
I always prefer to explicitly set both
__repr__(), but you don’t need to.
Have you ever thought about how
print() knows where to output the given message? The default behavior is to write the string to the
sys.stdout stream, but you can specify the output file with the
This code snippet creates a file named “output.txt” and writes “Hello World” to it. This is possible because
print() implicitly calls the
.write() method of the stream you pass in, so this would be equivalent to
With this in mind, you can create a rudimental logger using the
Or, much better, you can override the
print() function to extend its functionalities by logging every print statement to a file.
With this approach, you can easily make every
print() call in your file and automatically log a message to a file. However, if you’re into that kind of stuff, you would be better off using the
Execute Code by Printing to Files
What if you could hack the
print() function to make it execute arbitrary code? If you remember, I’ve mentioned that
print() calls the
.write() method of the file you pass in. With that in mind, you can overwrite or override this method with your own code.
This is the payload.
Payload function called with arguments: Hello World
This is the payload.
Payload function called with arguments
Alternatively, you can override
sys.stdout.write() to trigger the payload function for any default
Hello WorldExecuting payload
As you may have noticed, Python calls the
payload() function (and thus also the
.write() method) twice, so you may have to handle that.
This code injection technique may not have many real-world use cases, but who knows what people can come up with?
While the code injection shown before wouldn’t cause any harm, the next example could. If you’ve been programming for some time, you have heard how using third-party libraries could introduce some security threats into your source code.
In this example, I’ll show you how a vendor could include malicious code that gets triggered by the
And then, you would use this class in your programs:
Alternatively, you could override the global
sys.stdout.write() in your malicious module so that every
print() call executes your payload.
Now, you might argue that someone looking at the source code would immediately notice these malicious lines of code. I sometimes inspect the libraries I use myself. How about hiding the malicious code inside a C extension instead?
Python developers are much less likely to inspect a C source file from a Python package than a Python file. Plus, if you compile the extension locally upon installation, antiviruses (if you use any) should trust the resulting binary file as coming from the user. Or how about code obfuscation?
It’s funny to realize how vulnerable software developers can be. And then you get a reverse shell, run a keylogger with user permission to harvest admin/root password, and finally take over the system.
Although I won’t show how to achieve this specifically, you can take a look at this guide on writing C extensions for Python:
To wrap it up, the
print() function has many more capabilities than Python developers usually know of, from simply printing to the console to writing to files to executing arbitrary code. After all, the
print() function is just a tool, and as such, you can hack it to achieve different goals.
A paintbrush is just a stick with bristles attached to it. It’s up to the artist to employ it creatively.
And can we claim that an artist with a paintbrush is just a hacker with a stick?
I hope you enjoyed this article. If you know about any other practical or theoretical use case for the
print() function, let us know in a comment.
Thanks for reading!
If you’re interested in making your Python code more professional and maintainable, check out this story: