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 Ia32 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

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.

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: