How to Execute Raw Shellcode Using Ruby On Windows and Linux
As I'm working on some internal project, I was wondering how to run a raw shellcode directly from ruby. Usually, we use a C code to evaluate and test our shellcodes.
Why do we need this?
This is one of the ways that we can run any shellcode in memory without touching that shellcode touch the disk. But, we have the code in the script! Well that's for PoC, but in a real scenario, we might send the shellcode over the network as a raw shellcode so the ruby script stays clean and bypass the AV's, once the ruby script receives the shellcode it'll execute it in the memory.
Let's see how we're testing our shellcode in a traditional way, first
Compile on Windows
download and install TDM-GCC
compile the code
Compile On Linux
install mingw-w64
apt install mingw-w64
compile the code
i686-w64-mingw32-gcc shellcode-test.c -o shellcode-test.exe -lws2_32
Since we're testing a Windows Shellcode now, move the exe to Windows and run it.
You should get similar to this
Now we know that the shellcode is working properly and we're going explain how to call the shellcode from Ruby not C, using Windows APIs.
Calling Windows API to run Shellcode using Ruby
So in order to run a shellcode we basically need to follow the following steps
having a hexdicemial (raw) shellcode raw that executes anything (eg. MessageBoxA)
load
kernel32.dll
libraryallocate a virtual memory for this shellcode.
VirtualAlloc
create a buffer for the shellcode.
move the shellcode from the buffer to that allocation.
RtlMoveMemory
create a new thread to execute that shellcode.
CreateThread
wait for the execution/thread to end.
WaitForSingleObject
We're going to depend on Ruby Fiddle standard library to call and interact with Windows APIs. First Let's explain each function and how we can later use it since understanding the original C code is crucial at this stage. Our frind in this part is Microsoft Developer Documentation (MSDN).
kernel32.dll
Which the library that contains all the functions we need to achieve our task.
VirtualAlloc() function
Which creates/allocate an executable memory space in the virtual memory. We need that to copy our shellcode from the buffer to that allocation to be then executed.
Syntax (Source: MSDN)
It accepts 4 parameters
lpaddress
: The starting address of the region to allocate.dwSize
: The size of the region, in bytes, the shellcode size.flAllocationType
: The type of memory allocationflProtect
: The memory protection for the region of pages to be allocated.
It returns the pointer ID of the allocated memory.
VirtualProtect() function (Optional)
Which changes the protection on a region of committed pages in the virtual address space of the calling process.
Syntax (Source: MSDN)
It accepts 4 parameters
lpAddress
: A pointer an address that describes the starting page of the region of pages whose access protection attributes are to be changed.dwSize
: The size of the region whose access protection attributes are to be changed, in bytes, our shellcode size.flNewProtect
: The memory protection option.lpfloldProtect
: A pointer to a variable that receives the previous access protection value of the first page in the specified region of pages.
RtlMoveMemory() function
Which copies bytes from a source memory (shellcode's buffer) to a destination memory (our allocated memory)
Syntax (Source: MSDN)
It accepts 3 parameters
Destination
: A pointer to the destination memory block/allocation to copy the shellcode bytes to.Source
: A pointer to the source memory block to copy the bytes from, the shellcode's buffer.Length
: The number of bytes to copy from the source to the destination, the shellcode size.
CreateThread() function
Create a thread to execute within the virtual address space (our allocated memory) of the calling process.
Syntax (Source: MSDN)
It accepts 6 parameters
lpThreadAttributes
: A pointer to a Security Attributes for that thread to allow LSA can track.dwStackSize
: the initial size or the stacklpStartAddress
: the pointer to our allocated memorylpParameter
: A pointer to a variable to be passed to the thread.dwCreationFlags
: The flags that control the creation of the thread.lpThreadId
: A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
It returns the thread handle if succeeded, otherwise, it returns NULL
WaitForSingleObject() function
Which waits for the created thread to execute until it ends (wait for the shellcode to end or exit).
Syntax (Source: MSDN)
It accepts 2 parameters
hHandle
: The thread handledwMilliseconds
: The time to wait in milliseconds,-1
for infinite
Understanding Ruby Fiddle library
By definition, ruby Fiddle is an extension to translate a foreign function interface (FFI) with ruby. It wraps libffi, a popular C library that provides a portable interface that allows code written in one language to call code written in another language. In our case, it allows Ruby to call C code.
To load a C library from ruby we use Fiddle.dlopen('/path/class_name')
hence that there is no need for the library's path if it's core system library. I've explained a simple usage for Fiddle in Rubyfu (Calling Windows API Section), which gives you a simple and variant usage of Fiddle.
Once we open the foreign library (in our case kernel32.dll), we use Fiddle::Function
to call the designated functions. Note that you have to understand the function you call and its argument types to assign to. Fiddle::Function
returns a pointer that points to the return value of the called function. Each argument type has its own values (eg. Fiddle::Type_INT
value is 4). You can check all available types from ruby docs here, or check them and their values using the following line:
ProTip:
To avoid all the type hassling about which type is which, just use Fiddle::TYPE_VOIDP
for all variable types except for the handle which should be Integer Fiddle::TYPE_INT
.
The arguments types should be in an array while constructing the C function as follows
Notice the function's argument types in an array [ARG1TYPE, ARG2TYPE, ARG3TYPE, ARG4TYPE]
respectively. Then we call the call
the instance method of the Function
class to call the constructed function (eg. VirtualAlloc
function), with its arguments.
To create a buffer and fill it with the values you need, we use Fiddle::Pointer[DATABYTES]
which returns a pointer object to that buffer, the data bytes should be in the pointer value as we've mentioned before( Fiddle::Pointer[DATABYTES]
).
The Final Code
The following is wrapping all the C code into ruby. Read the comment in the code to understand each step.
Now, you can run the code
to get
The same concept applied on Linux, just started with Windows since Linux is much easier.
Happy Hacking!
Last updated