Haiku x86 Assembly: Simple Shellcode

Vegard Wærp
4 min readNov 28, 2018

Having written a simple “Hello, World” program in assembly, my next step was to write a simple shellcode that executes /bin/bash.

My plan:

  1. Research the Haiku syscalls for executing applications
  2. Write an assembly program that uses the syscall(s) to execute /bin/bash
  3. change the assembly code to remove any null bytes etc, and generate shellcode from it
  4. Create a simple c program to test the generated shellcode.

Researching Haiku syscalls

Looking at the syscalls.S.inc file I generated when writing the Hello, World program, we see the syscall _kern_exec():

After poking around a bit in the Haiku source, we find the signature for the syscall in headers/private/system/syscalls.h:

extern status_t  _kern_exec(const char *path, const char* const* flatArgs,
size_t flatArgsSize, int32 argCount, int32 envCount,
mode_t umask);

For the “flatArgs” argument, we have to do some more searching, and after looking we find the following description in src/system/libroot/os/image.cpp:

/*! Allocates a flat buffer and copies the argument and environment strings
into it. The buffer starts with a char* array which contains pointers to
the strings of the arguments and environment, followed by the strings. Both
arguments and environment arrays are NULL-terminated.
If executablePath is non-NULL, it should refer to the executable to be
executed. If the executable file specifies changes to environment variable
values, those will be performed.
*/
status_t
__flatten_process_args(const char* const* args, int32 argCount,
const char* const* env, int32* _envCount, const char* executablePath,
char*** _flatArgs, size_t* _flatSize)
{

To be sure to get the format correct, I copied __flatten_process_args() and some dependencies to get it to compile to a new C++ file. If we then generate the flatArgs needed for executing /bin/bash, we get the following:

flatArgs, total size 22 byte

for the umask argument, a constant with the octal value 022 was used elsewhere, so we use that.

With that, we have enough to try making an assembly program that executes the _kern_exec() syscall to execute “/bin/bash”

Assembly program that executes _kern_exec()

We can start with my previous hello world, and edit it to execute the _kern_exec syscall. I first tried adding the flatArgs buffer to the data section, but had problems with finding out how to define the first part, the address to the string, so I ended up just pushing everything to the stack.

For pushing “/bin/bash” to the stack, we can use python to quickly reverse the string and get the hex values:

~/Sources/asm/shellcode> python
Python 2.7.12 (default, Sep 10 2018, 14:51:14)
[GCC 2.95.3-haiku-2017_07_20] on haiku1
Type "help", "copyright", "credits" or "license" for more information.
>>> "/bin/bash"[::-1]
'hsab/nib/'
>>> "/bin/bash"[::-1].encode('hex')
'687361622f6e69622f'

We then end up with the following assembly code:

If we assemble, link and execute our shiny new assembly program, it executes bash as expected:

Removing null bytes and generating shellcode

Looking at our assembly program we see several null bytes. When writing shellcode we want to avoid null characters, as it marks the end of C strings. We also have to move _syscall to the end, as the shellcode starts executing from the start. But just moving the call instructions leads to more null bytes. So instead we just push a junk value on the stack and perform then int instruction without any call/ret. We can also remove the stack restoration to save some bytes. After doing that we get the following, with no null bytes:

So, our final assembly code is:

We can then extract the opcodes from the binary using these commands from commandlinefu.com, and get the our final shellcode:

"\x31\xc0\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x50\x50\x53\x89\xe1\x6a\x12\x50\x6a\x01\x6a\x16\x51\x53\xb0\x2b\x6a\x0c\xcd\x63\x31\xc0\x50\xb0\x26\x6a\x0c\xcd\x63"

Testing the shellcode

When trying to test the shellcode, I got segmentation faults even if I compiled the test program with -z execstack. I tried putting the code both on the stack and in the data segment, and tried different compilation options, but couldn’t get rid of the segfaults.

What I had to do in the end was to set the memory to RWX using mprotect() before executing the shellcode, so to test the shellcode we can use the following program:

And there you have it, our Haiku shellcode completed and working

--

--