metasploit-framework/lib/metasm/doc/core/DynLdr.txt

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