DynLdr

DynLdr is a class that uses metasm to dynamically add native methods, or native method wrappers, available to the running ruby interpreter.

It leverages the built-in C parser / compiler.

It is implemented in `metasm/dynldr.rb`.

Currently only supported for <core/Ia32.txt> and <core/X86_64.txt> under Windows and Linux.

Basics


Native library wrapper ######################

The main usage is to generate interfaces to native libraries.

This is done through the `#new_api_c` method.

The following exemple will read the specified C header fragment, define ruby constants for all `#define`/`enum`, and define ruby method wrappers to call the native functions whose prototype is present in the header.

All referenced native functions must be exported by the given library file.

class MyInterface < DynLdr
  c_header = <<EOS
#define SomeConst 42
enum { V1, V2 };

__stdcall int methodist(char*, int);
EOS

  new_api_c c_header, 'mylib.dll'
end

Then you can call, from the ruby:

MyInterface.methodist("lol", MyInterface::SOMECONST)

Constant/enum names are converted to full uppercase, and method names are converted to full lowercase.

Dynamic native inline function ##############################

You can also dynamically compile native functions, that are compiled in memory and copied to RWX memory with the right ruby wrapper:

class MyInterface < DynLdr
  new_func_c <<EOS
int bla(char*arg) {
  if (strlen(arg) > 4)
     return 1;
  else
     return 0;
}
EOS
end

References to external functions are allowed, and resolved automatically.

The ruby objects used as arguments to the wrapper method are automatically converted to the right C type.

You can also write native functions in assembly, but you must specify a C prototype, used for argument and return value conversion.

class MyInterface < DynLdr
  new_func_asm "int increment(int i);", <<EOS
 mov eax, [esp+4]
 inc eax
 ret
EOS

p increment(4)

end

Structures


`DynLdr` handles C structures.

Once a structure is specified in the C part, you can create a ruby object using `MyClass.alloc_c_struct(structname)`, which will allocate an object of the right size to hold all the structure members, and with the right accessors.

To access/modify struct members, you can either use a `Hash`-style access

structobj['membername'] = 42

or `Struct`-style access

structobj.membername = 42

Member names are matched case-insensitively, and nested structures/unions are also searched.

The struct members can be initially populated by passing a `Hash` argument to the `alloc_c_struct` constructor. Additionally, this hash may use the special value `:size` to reference the byte size of the current structure.

class MyInterface < DynLdr
  new_api_c <<EOS
struct sname {
  int s_mysize;
  int s_value;
  union {
    struct {
      int s_bits:4;
      int s_bits2:4;
    };
    int s_union;
  }
};
EOS
end

# field s_mysize holds the size of the structure in bytes, ie 12
s_obj = MyInterface.alloc_c_struct('sname', :s_mysize => :size, :s_value => 42)

# we can access fields using Hash-style access
s_obj['s_UniOn'] = 0xa8

# or Struct-style access
puts '0x%x' % s_obj.s_BiTS2     #  =>  '0xa'

This object can be directly passed as argument to a wrapped function, and the native function will receive a pointer to this structure (that it can freely modify).

This object is a `C::AllocStruct`, defined in `metasm/parse_c.rb`. Internally, it is based on a ruby `String`, and has a reference to the parser's `Struct` to find the mapping membername -> offsets/length.

See <core/CParser.txt> for more details.

Callbacks


`DynLdr` handles C callbacks, with arbitrary ABI.

Any number of callbacks can be defined at any time.

C callbacks are backed by a ruby `Proc`, eg `lambda {}`.

class MyInterface < DynLdr
  new_api_c <<EOS
void qsort(void *, int, int, int(*)(void*, void*));
EOS

  str = "sanotheusnaonetuh"
  cmp = lambda { |p1, p2|
    memory_read(p1, 1) <=> memory_read(p2, 1)
  }
  qsort(str, str.length, 1, cmp)
  p str
end

Argument conversion


Ruby objects passed to a wrapper method are converted to the corresponding C type

accessible from the ruby through `DynLdr.str_ptr(obj)`

(`char`, `unsigned long long`, …)

Working with memory


DynLdr provides different ways to allocate memory.

`memory_alloc` works by calling `mmap` under linux and `VirtualAlloc` under windows, and is suitable for allocating memory where you want to control the memory permissions (read, write, execute). This is done through `memory_perm`.

`memory_perm` takes for argument the start address, the length, and the new permission, specified as a String (e.g. 'r', 'rwx')

To work with memory that may be returned by an API (e.g. `malloc`), DynLdr provides ways to read and write arbitrary pointers from the ruby interpreter memory. Take care, those may generate faults when called with invalid addresses that will crash the ruby interpreter.

e.g. 64 bit in a 64-bit interpreter)

Hacking


Internally, DynLdr relies on a number of features that are not directly available from the ruby interpreter.

So the first thing done by the script is to generate a binary native module that will act as a C extension to the ruby interpreter. This binary is necessarily different depending on the interpreter. The binary name includes the target architecture, in the format dynldr-arch-cpu-19.so, e.g.

This native module is (re)generated if it does not exist, or is older than the `dynldr.rb` script.

A special trick is used in this module, as it does not know the actual name of the ruby library used by the interpreter. So on linux, the `libruby` is removed from the `DT_NEEDED` library list, and on windows a special stub is assembled to manually resolve the ruby imports needed by the module from any instance of `libruby` present in the running process.

The native file is written to a directory writeably by the current user. The following list of directories are tried, until a suitable one is found: