Ok, so after seeing the big bounties people were getting payed on hackerone for ruby fuzzing (https://hackerone.com/ibb?type=team) I decided to fuzz ruby (again).
I have actually fuzzed ruby before, but I deleted the directory which had all of my work, when I needed to free some space. Soo, we need to compile everything again. facepalm .
First git clone https://github.com/ruby/ruby.git and then compile it with afl. This is partually inspired by this: https://medium.com/fuzzstation/breaking-rubys-unmarshal-with-afl-fuzz-6b5f72b581d5 , but the guy just used a ruby script to load the marshal thing. This is actually quite a shit choice in my opinion, because this adds a lot of overhead because you invoke the ruby interpreter on each execution cycle when parsing the ruby source code.
To circumvent this problem and improve performance drastically, I decided to use the ruby C api. There is the slight problem that I do not know how to use it. I remember setting it up was a pain in the ass back then. I had linking problems etc for some reason. Now, let’s see what happens.
I am going to consult this guide here to compile ruby from source: https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html#label-Quick+start+guide
Let’s try to first compile and use without the instrumentation to see if we can get something to work.
I ran ./autogen.sh
and then made a dir called build/
then inside build I ran ../configure --prefix="MYINSTALLDIR"
. I tried this once before and I had problems linking the library to the c program which uses ruby. It didn’t find ruby_init and some other of the symbols even though they were in the library.
Then I am going to just run make -j8 install
to build and then install to the installation directory.
After that I then copied this example program from here: https://silverhammermba.github.io/emberb/embed/
Here it is:
#include <ruby.h>
int main(int argc, char* argv[])
{
/* construct the VM */
ruby_init();
/* Ruby goes here */
/* destruct the VM */
return ruby_cleanup(0);
}
The compilation process is this: gcc -I/usr/include/ruby-3.0.0 -I/usr/include/ruby-3.0.0/x86_64-linux -lruby
Notice the -lruby
? This is basically libruby.so
, but there isn’t a library of that name in the installation directory. See, we need to add --enable-shared
to our configure parameters. :D
After compiling again, we now have libruby.so
! Great!
cyberhacker@cyberhacker-h8-1131sc:~/Asioita/Hakkerointi/Rubydatetime/ruby/install$ find . | grep libruby
./lib/libruby.so.3.4
./lib/libruby.so
./lib/libruby.so.3.4.0
Now we just need to compile the actual program. Shouldn’t be that hard right? :D
Here is my command:
gcc -Iinstall/include/ruby-3.4.0+0 -Iinstall/include/ruby-3.4.0+0/x86_64-linux -Linstall/lib/ -lruby ./oof.c -o oof
and here is the error which I get:
/usr/bin/ld: /tmp/cckUnrHf.o: in function `main':
oof.c:(.text+0x23): undefined reference to `ruby_init'
/usr/bin/ld: oof.c:(.text+0x2d): undefined reference to `ruby_cleanup'
collect2: error: ld returned 1 exit status
sooooo what gives?
After a bit of googling I found this: https://stackoverflow.com/questions/59674947/linking-ruby-interpreter-into-c-program . It says that I need to link libruby.so (duh), but I am already doing that.
Now my computer just decided to automatically update. Thanks linux mint. Now I can not install new packages with apt-get because of it. Now in the stackoverflow question I when running pkg-config
After doing export PKG_CONFIG_PATH=$PWD
in the directory where I had the .pc
file, I got this:
cyberhacker@cyberhacker-h8-1131sc:~/Asioita/Hakkerointi/Rubydatetime/ruby$ pkg-config --cflags --libs ruby-3.3
-I/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/include/ruby-3.3.0+0/x86_64-linux -I/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/include/ruby-3.3.0+0 -L/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/lib -Wl,--compress-debug-sections=zlib -Wl,-rpath,/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/lib -lruby -lm -lpthread
Ok so let’s add this to the compile command? No. Fuck!
Ok so now it is the next day and someone answered (actually the author of the guide I am following!) my question on stackoverflow: https://stackoverflow.com/a/77970820/14577985
And holy shit I am retarded. As it turns out, the order of linking matters for some reason!
After running this instead:
gcc -c -I/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/include/ruby-3.3.0+0/x86_64-linux -I/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/include/ruby-3.3.0+0 -L/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/lib -Wl,--compress-debug-sections=zlib -Wl,-rpath,/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/lib -lruby -lm -lpthread -Iinstall/include/ruby-3.3.0+0 -Iinstall/include/ruby-3.3.0+0/x86_64-linux -Linstall/lib/ -L. ./oof.c -o oof.o
gcc ./oof.o -I/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/include/ruby-3.3.0+0/x86_64-linux -I/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/include/ruby-3.3.0+0 -L/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/lib -Wl,--compress-debug-sections=zlib -Wl,-rpath,/home/cyberhacker/Asioita/Hakkerointi/Rubydatetime/ruby/install/lib -lruby -lm -lpthread -Iinstall/include/ruby-3.3.0+0 -Iinstall/include/ruby-3.3.0+0/x86_64-linux -Linstall/lib/ -L. -o oof
it now links and we get an executable! Great!
Now it is time to make a program which takes input from stdin and then tries to use it as input to strftime.
Yeah, fuck that. Let’s fuzz unmarshalling instead. After compiling I do not find any crashes, but let’s instead use the vulnerable version and see what happens.
Ok, so I decided to check if the vulnerability is actually findable with afl, so I decided to compile the thing with the patch removed. Thankfully the guy also provided a crashing input, so let’s see what happens:
# xxd marshal-overflow
0000000: 0408 3afc ffff ff7f 3030 3030 3030 3030 ..:.....00000000
0000010: 3030 3030
I am going to remove the patch and compile again and see if it crashes.
Here is the thing before:
void
rb_str_modify_expand(VALUE str, long expand)
{
int termlen = TERM_LEN(str);
long len = RSTRING_LEN(str);
if (expand < 0) {
rb_raise(rb_eArgError, "negative expanding string size");
}
if (expand >= LONG_MAX - len) {
rb_raise(rb_eArgError, "string size too big");
}
if (!str_independent(str)) {
str_make_independent_expand(str, len, expand, termlen);
}
else if (expand > 0) {
RESIZE_CAPA_TERM(str, len + expand, termlen);
}
ENC_CODERANGE_CLEAR(str);
}
and here it is after:
void
rb_str_modify_expand(VALUE str, long expand)
{
int termlen = TERM_LEN(str);
long len = RSTRING_LEN(str);
if (expand < 0) {
rb_raise(rb_eArgError, "negative expanding string size");
}
if (!str_independent(str)) {
str_make_independent_expand(str, len, expand, termlen);
}
else if (expand > 0) {
RESIZE_CAPA_TERM(str, len + expand, termlen);
}
ENC_CODERANGE_CLEAR(str);
}
and let’s see if it crashes with the input.
Compile with afl-clang-fast ……
source “$HOME/.cargo/env”
Ok, so after a long while of searching I finally found this: https://stackoverflow.com/questions/3222171/ruby-c-extension-using-singleton which tells how to call DateTime
in ruby c api. Here is my current wrapper code:
#include "ruby.h"
// These are needed to load symbols from date_core.so ...
#include <stdio.h>
#include <dlfcn.h>
// static VALUE cDate, cDateTime;
// extern VALUE cDate, cDateTime;
/*
VALUE dangerous_func(VALUE buffer) {
//rb_funcall(rb_mKernel, rb_intern("Rational"), 1, buffer);
// rb_funcall(rb_mKernel, rb_intern("to_f"), 1, buffer); // Fuzz float parsing instead
VALUE parsed_date; // This is the datetime object. We are going to call "strftime" on this object later on...
VALUE formatted_date; // This is the result of parsed_date.strftime("someformat")
//rb_funcall(rb_mKernel, rb_intern("Complex"), 1, buffer);
// cDate,
printf("Trying to call parse...\n");
// parsed_date = rb_funcall(cDateTime, rb_intern("parse"), 1, rb_str_new_cstr("3rd Feb 2001 04:05:06+03:30")); // Just use some date.
//printf("Here is the address of cDateTime: %lx\n", cDateTime);
parsed_date = rb_funcall(cDateTime, rb_intern("now"), 0);
// strftime
printf("Calling strftime...\n");
rb_funcall(parsed_date, rb_intern("strftime"), 1, buffer); // Try to format the dateobject.
// Maybe print the result?????
printf("Calling puts...\n");
rb_funcallv(rb_mKernel, rb_intern("puts"), 1, &formatted_date);
return Qnil; // Just return Qnil
}
*/
// This is needed for rb_rescue
VALUE error_func(VALUE stuff) {
//printf("Called error_func...\n");
return Qnil;
}
// Main fuzzing wrapper.
#define DATE_CORE_NAME "date_core.so"
#define LOOP_COUNT 100000
__AFL_FUZZ_INIT();
int main(int argc, char **argv) {
printf("Hello world!\n");
int state;
//rb_protect(dangerous_func, dangerous_arg, &state);
VALUE hello_world_str;
VALUE date_module;
//ruby_setup();
ruby_sysinit(&argc, &argv);
RUBY_INIT_STACK;
ruby_init();
ruby_init_loadpath();
date_module = rb_require("date");
// VALUE singletonmodule = rb_const_get(rb_cObject,rb_intern("Singleton"));
VALUE singletonmodule = rb_const_get(rb_cObject,rb_intern("DateTime"));
// Need to initialize the date library, which we are fuzzing.
// Init_date_core();
__AFL_INIT();
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(LOOP_COUNT)) {
//while (true) {
int len = __AFL_FUZZ_TESTCASE_LEN;
state = 0;
//hello_world_str = rb_str_new_cstr(buf);
hello_world_str = rb_str_new_cstr(buf);
/*
rb_rescue(VALUE (* b_proc)(VALUE), VALUE data1,
VALUE (* r_proc)(VALUE, VALUE), VALUE data2)
*/
//printf("Calling the function...\n");
printf("Calling Init_date_core();\n");
// Now we need to load the shit from date_core.so
void *handle = NULL;
void* func = NULL;
handle = dlopen(DATE_CORE_NAME, RTLD_NOW | RTLD_GLOBAL);
// func = dlsym(handle, "library_function");
if (handle == NULL)
{
fprintf(stderr, "Unable to open lib: %s\n", dlerror());
return -1;
}
printf("Here is the address of handle: %lx\n", handle);
/*
if (cDateTime == NULL) {
fprintf(stderr, "Unable to get symbol\n");
return -1;
}
*/
//printf("Here is the address of cDateTime: %lx\n", cDateTime);
//Init_date_core();
//printf("Returned from Init_date_core();\n");
// Loading date_core.so ...
printf("Now calling rb_require...\n");
//rb_require("date"); // aka require 'date'
// Ok, so now the date object is date_module.
printf("Address of date_module: %lx\n", date_module);
//rb_funcall(date_module, rb_intern("now"), 0);
printf("Now calling rb_require...\n");
// rb_funcall(cDateTime, rb_intern("now"), 0);
printf("Now trying to call now...\n");
// rb_funcall(singletonmodule,rb_intern("included"),1,mouseclass);
printf("Now calling \"now\"...\n");
rb_funcall(singletonmodule,rb_intern("now"),0);
printf("Done!\n");
//printf("The address of cDateTime is this: %lx\n", cDateTime);
// rb_funcall(cDateTime, rb_intern("now"), 0); // Maybe this works???
printf("Fuck!\n");
// rb_rescue(dangerous_func, hello_world_str, error_func, Qnil);
//rb_funcall(rb_mKernel, rb_intern("Rational"), 1, hello_world_str);
//free(hello_world_str); // Try to free the allocated memory.
// rb_funcall(rb_mKernel, rb_intern("Rational"), 1, hello_world_str);
}
ruby_finalize();
return 0;
}
that actually calls DateTime.now
instead of the parsing function, but whatever.
Here is the actual parse function:
VALUE date_string = rb_str_new_cstr("2001-02-03T04:05:06.123456789+07:00"); // input string.
// rb_funcall(singletonmodule,rb_intern("now"),1, date_string);
rb_funcall(singletonmodule,rb_intern("parse"), 1, date_string);