Usually I write assembly programs and then dump their contents to get the shellcode. What I was wondering is whether I could write a C++ program instead of assembly, then dump and use that instead of the assembly?
The Linux kernel can be viewed as a kind of ultimate shell code, since it is "injected" on a raw machine (which only has the BIOS code at that point) and then provides a lot of functionality. That kernel is written in C.
If you write shell code in C or C++, you will run into trouble with library calls and linking, which are two facets of the same issue.
Code produced by a C compiler may contain references to other parts of the code, or external functions or variables. For instance, if you access a
static variable, even if defined in your own code, then the assembly opcodes will contain a "hole": at the assembly level (that which you obtain with
gcc -S, if you use gcc as C compiler), you would see basic "mov" opcodes, and the variable name. When translated into binary, in the object file (the
.obj, depending on the platform), the hole is not filled yet, because the actual address of the variable is not known yet; the object file contains a table (the "relocations") which inform the linker of the position of each hole which is to be filled at the linking stage.
The problem, of course, is that you do not have the luxury of a linker when making a shell code. You have to produce a sequence of bytes which is already ready, with no hole to fill, and able to run at whatever address it will be loaded, address which is not necessarily known in advance (thanks, in part, to ASLR). You must write position-independent code. Compilers often offer a command-line flag for that (e.g.
gcc) but this is not "true" PIC: that kind of code still has holes to fill by a linker, but this time the dynamic linker; it is PIC only by virtue of the holes having been regrouped in a special table (the GOT, aka "Global Offsets Table"), making the task easier for the dynamic linker.
Unfortunately, C compilers, and C++ compilers even more so, will generate code with holes without your consent. For instance, a typical C compiler will produce code with hidden calls to
memcpy() (the standard library function) when dealing with
struct values. C++ makes that worse because of all the paraphernalia of C++ features, which are supported by hidden functions and static variables (e.g. the object vtables).
The Linux kernel solves these issues by using the MMU. The boot loading code (which is a piece of hand-written assembly) takes control of the MMU to make sure that the RAM pages where the kernel code is will be seen at a fixed, known address in the address space: the kernel, when compiled, is linked with that explicit assumption; the holes are filled with addresses which are correct only if the kernel is indeed at that exact position in the address space. Playing with the MMU is a kernel privilege, you cannot do that from userland code, let alone from a shell code.
You could envision making your shell code a piece of assembly which is, in fact, a dynamic linker, able to load a piece of binary code produced by a C or C++ compiler, patching it dynamically, i.e. filling the "holes" with the actual loading address. That's what occurs in the exploits provided with metasploit: the exploit itself installs that nifty piece of code, which can then install "payloads" which are generic DLL, written in higher-level languages, up to and including a full VNC server.
However you put it, though, you will have to do some PIC code in manual assembly at some point. Writing your own loader/linker is a great pedagogical exercise, and it can be used to leverage an initial exploit to arbitrary control levels, but at its core, handmade assembly will still be needed.
It is perfectly valid to write shellcode in any language that gets compiled down to machine code instructions. Provided no external libraries that are not linked by the victim program are required for its operation.
However, it is almost never the case that directly compiled code (even from just C) is a valid, injectable shellcode. The most common reason would be the requirement to remove
NULL bytes in a string-buffer injection.
Compiled code will also be bloated, performing various 'bookkeeping' type tasks such as function prologues, stack-frame setup and pulldown etc. All this makes it a poor choice for shellcode, which typically has to be very small.
So, you will need to write your program, compile it, and then tune it at the machine code level. This is actually quite a common approach that many people take who might find writing assembly from scratch a bit daunting.
In my experience however, you will end up messing around with the final shellcode so much that you will be very familiar with every byte it contains before you are finished, and it probably would have been quicker to use assembly directly, right from the start.
Of course, if you are trying to perform a large, complex task, then writing it in a higher level language can be appealing. In reality though, the better approach is usually to create a simple stager shellcode, which is injected by the exploit. The stager then downloads the real payload and executes it. This allows the real payload to be a complex, stand alone executable which links its own libraries at will.
Remember of course, where the name comes from; a shellcode is generally intended to perform the task of a stager, with the archetypal stager being
execve("/bin/sh", null, null).
As TildalWave points out, even when compiling from C++, it is possible to have fine-grained control over the output machine-code. The most direct way is to include inline assembly statements within the C++ source, for example;
__asm__ ("movl %eax, %ebx\n\t" "movb %ah, (%ecx)");
You can also alter the compiler directives, and when compiling code to be edited you should always disable all compiler optimisation. Few things are more confusing than the weird stuff compilers do to reduce the number of instructions.