248 lines
6.9 KiB
Plaintext
248 lines
6.9 KiB
Plaintext
|
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
|
||
|
|
||
|
* `Strings` are converted to a C pointer to the byte buffer (also directly
|
||
|
accessible from the ruby through `DynLdr.str_ptr(obj)`
|
||
|
* `Integers` are converted to their C equivalent, according to the prototype
|
||
|
(`char`, `unsigned long long`, ...)
|
||
|
* `Procs` are converted to a C callback
|
||
|
* `Floats` are not supported for now.
|
||
|
|
||
|
|
||
|
Working with memory
|
||
|
-------------------
|
||
|
|
||
|
DynLdr provides different ways to allocate memory.
|
||
|
|
||
|
* `alloc_c_struct` to allocate a C structure
|
||
|
* `alloc_c_ary` to allocate C array of some type
|
||
|
* `alloc_c_ptr`, which is just an ary of size 1
|
||
|
* `memory_alloc` allocates memory from a new memory page
|
||
|
|
||
|
`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.
|
||
|
|
||
|
* `memory_read` takes a pointer and a length, and returns a String
|
||
|
* `memory_read_int` takes a pointer, and returns an Integer (of pointer size,
|
||
|
e.g. 64 bit in a 64-bit interpreter)
|
||
|
* `memory_write` takes a pointer and a String, and writes it to memory
|
||
|
* `memory_write_int`
|
||
|
|
||
|
|
||
|
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.
|
||
|
|
||
|
* dynldr-linux-ia32.so
|
||
|
* dynldr-windows-x64-19.so
|
||
|
|
||
|
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:
|
||
|
|
||
|
* the `metasm` directory itself
|
||
|
* the `$HOME`/`$APPDATA`/`$USERPROFILE` directory
|
||
|
* the `$TMP`/`$TEMP`/current directory
|
||
|
|