Shellcode

com.etdon.winj.facade.hack.execute, com.etdon.winj.facade.op

Terminology

For the purpose of instruction related documentation in the context of the library, a raw instruction is referring to a whole instruction (consisting of one or multiple bytes) that is appended manually byte-for-byte using primitive builder methods like #instruction and #value. A managed instruction on the other hand is referring to a whole instruction (consisting of one or multiple bytes) that has a distinct named method in the builder with proper parameters.

Runner

The library features a ShellcodeRunner that can be used run shellcode in the context of the current process. To get started simply create a new instance providing a NativeContext instance as the only parameter:

final ShellcodeRunner shellcodeRunner = new ShellcodeRunner(nativeContext);

The runner itself is not allocated in process memory by default giving you the option to provide a pointer to an already allocated runner instead. In order to allocate the default runner invoke the #allocateRunner method, this will allocate executable memory in the current process and copy the runner's instructions into it. With everything ready you can now allocate and execute your shellcode by invoking the #execute method and providing the pointer to your runner as well as the shellcode byte array as parameters:

final byte[] shellcode = ...
final ShellcodeRunner shellcodeRunner = new ShellcodeRunner(nativeContext);
final MemorySegment runnerPointer = shellcodeRunner.allocateRunner();
shellcodeRunner.execute(runnerPointer, shellcode);

Helper

The ShellcodeHelper class provides various utilities related to shellcode generation and execution. It can for example be used to easily find the address of functions at runtime:

final ShellcodeHelper shellcodeHelper = new ShellcodeHelper(symbolLookupCache);
final long address = shellcodeHelper.getFunctionAddress(Library.USER_32, "GetForegroundWindow");

This also works for native functions that don't have an implemented binding yet.


Builder

While you can pre-generate the shellcode using external tools the library also allows you to use a fluent builder to easily construct it during runtime. The builder gives you full control without sacrificing convenience by allowing you to write raw values and instructions as well as utilize constants and easy-to-use managed instruction methods. Here is an example of generating shellcode to call the ExitProcess function with exit code 0 of kernel32 using the builder:

Shellcode.builder()
    .xor(Register64.RCX, Register64.RCX)
    .mov(Register64.R13, shellcodeHelper.getFunctionAddress(Library.KERNEL_32, "ExitProcess"))
    .call(Register64.R13)
    .build();

Architecture

While writing raw instructions and values is compatible with all popular architectures, named instruction methods of the shellcode builder as well as named constants related to instructions are currently only available for the x86-64 architecture.

Last updated