Logo

Fuzyll


Hopelessly passionate husband, engineer, hacker, gamer, artist, and tea addict.


How Not To Solve a CTF Challenge II

I had to head into work this weekend to prepare for some upcoming travel, so I originally had no plans to play any CTFs. But, when Skolor told me there was a Ruby challenge in the Tokyo Westerns CTF 2016, I knew I had to check it out. Despite my insistence that Ruby is better than Python, I always seem to struggle with Ruby challenges. Sadly, this one was no different...

Restricted Ruby

This challenge (files here) had 3 parts:

Private ppc1.chal.ctf.westerns.tokyo 1111 (Flag 1)
Local ppc1.chal.ctf.westerns.tokyo 1112 (Flag 2)
Comment ppc1.chal.ctf.westerns.tokyo1113 (Flag 3)

Common to all of them was a file called restrict.rb:

require "fiddle/import"
module Libc
  extend Fiddle::Importer
  dlload "libc.so.6"
  extern "int alarm(int)"
end

module Seccomp
  extend Fiddle::Importer
  dlload "libseccomp.so.2"
  extern "void* seccomp_init(int)"
  extern "int seccomp_rule_add(void*, int, int, int)"
  extern "int seccomp_load(void*)"

  SCMP_ACT_KILL     = 0x00000000
  SCMP_ACT_TRAP     = 0x00030000
  SCMP_ACT_ERRNO_0  = 0x00050000 # ignore syscall
  SCMP_ACT_ALLOW    = 0x7fff0000
end

class Restrict
  def self.set_timeout
    #Libc.alarm(10)
  end

  def self.seccomp
    ctx = Seccomp.seccomp_init(Seccomp::SCMP_ACT_ERRNO_0)
    ret = 0
    ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 1, 0) # allow write
    ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 60, 0) # allow exit
    ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 3, 0) # allow close
    ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 12, 0) # allow brk
    ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 10, 0) # allow mprotect
    ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 9, 0) # allow mmap
    ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 11, 0) # allow munmap
    ret |= Seccomp::seccomp_load(ctx)

    fail "Failed to setup syscall." unless ret == 0
  end
end

This file uses Fiddle, an FFI wrapper that is part of Ruby's standard libraries, to call the alarm function from libc and to restrict access to syscalls with seccomp. We are given 10 seconds to input what we want to be evaluated for each challenge (not actually relevant for solving, just a way for them to automatically kill connections left open). We are also only given the syscalls write, exit, close, brk, mprotect, mmap, and munmap.

Each challenge was implemented in its own, separate file that imported this one as a module and started with run.sh:

#!/bin/bash
cd $(dirname "$0")
/usr/bin/ruby2.0 $1.rb 2> /dev/null | head --bytes=512 # Ubuntu 14.04(64bit) ruby2.0 package

This places an additional restriction on the challenge: We can only get 512 bytes back from their interpreter at a time. Again, not actually relevant for solving the challenge...but, limits the amount of information you can glean from their environment in a single connection.

Challenge One: Private

The first challenge was private.rb running on port 1111:

require_relative 'restrict'
Restrict.set_timeout

class Private
  private
  public_methods.each do |method|
    eval "def #{method.to_s};end"
  end

  def flag
    return "TWCTF{CENSORED}"
  end
end

p = Private.new
Private = nil

input = STDIN.gets
fail unless input
input.size > 24 && input = input[0, 24]

Restrict.seccomp

STDOUT.puts eval(input)

NOTE: For each of these challenges, the server's copy has the actual flag, and not "TWCTF{CENSORED}"

I started this challenge by heading down the entirely wrong path: Messing with interpreter internals. In hindsight, I should have solved this in about 30 seconds. Spoiler alert: I didn't.

The first thing I jumped to was thinking about how I could recover an instance of the Private class and display its source code. Normally, we could do something like p.class.instance_method(:flag).source_location to get the source file, then open and read it. Even if seccomp allowed that, this wouldn't work - class itself is marked private:

# output from my local copy of the script when run with "p.class" as the input
private.rb:24:in `eval': private method `class' called for :Private (NoMethodError)
    from private.rb:24:in `eval'
    from private.rb:24:in `<main>'

Next, I thought about how I might call a private method from an object. The Object class, which pretty much everything in Ruby inherits from, has a method that can be used for this called send. You simply pass a Symbol of the function name into send and voila! Unfortunately, that trick (and the same thing with __send__) won't work either for the same reason:

# output from my local copy of the script when run with "p.send(:flag)" as the input
private.rb:24:in `eval': private method `send' called for :Private (NoMethodError)
    from private.rb:24:in `eval'
    from private.rb:24:in `<main>'

I then decided to look into other things available in the interpreter. Most of the Python jail challenges in the past have required you to cleverly use aspects of the environment to solve them, after all. If you run global_variables, you get a lot of output:

$;
$-F
$@
$!
$SAFE
$~
$&
$`
$'
$+
$=
$KCODE
$-K
$,
$/
$-0
$\
$_
$stdin
$stdout
$stderr
$>
$<
$.
$FILENAME
$-i
$*
$?
$$
$:
$-I
$LOAD_PATH
$"
$LOADED_FEATURES
$VERBOSE
$-v
$-w
$-W
$DEBUG
$-d
$0
$PROGRAM_NAME
$-p
$-l
$-a
$1
$2
$3
$4
$5
$6
$7
$8
$9

Read through this page if you want to know what each of these are. One in particular, $", looked useful because I could get the path to a loaded module's source file with it. Here's the output of $".reverse (remember, we only get 512 bytes of output):

/home/ngltewpad1/restrict.rb
/usr/lib/ruby/2.0.0/fiddle/import.rb
/usr/lib/ruby/2.0.0/fiddle/cparser.rb
/usr/lib/ruby/2.0.0/fiddle/struct.rb
/usr/lib/ruby/2.0.0/fiddle/pack.rb
/usr/lib/ruby/2.0.0/fiddle/value.rb
/usr/lib/ruby/2.0.0/fiddle.rb
/usr/lib/ruby/2.0.0/fiddle/closure.rb
/usr/lib/ruby/2.0.0/fiddle/function.rb
/usr/lib/x86_64-linux-gnu/ruby/2.0.0/fiddle.so
/usr/lib/ruby/2.0.0/rubygems.rb
/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb
/usr/lib/ruby/2.0.0/monitor.rb
/usr/lib/ruby/2.0.0/thread.

Interestingly, we can get a File object returned here with File.open($"[-1], 'r'):

#<File:0x0000000152f6b0>

This shouldn't be possible due to the seccomp rules... I'm guessing this means File doesn't actually open the file when you create the object? Reading the file with File.open($"[-1]).read didn't give me any output. The shorter File.read($"[-1]) didn't work either.

Since the output of local_variables and instance_variables wasn't helpful, I temporarily ran out of ideas and went back to work for awhile.

On the way home from work later, I thought about trying to modify the p instance itself. Unfortunately, Soen beat me to it before I could get home. Here was what I had come up with as an answer to the problem:

class<<p;def x;flag;end;end;p.x

This modifies p's "singleton class" (a special parent class that only belongs to the object itself) to have a function x that calls flag and returns its result. Unfortunately, it's 8 characters too long... Fortunately, Ruby now has an even easier (and, more importantly, shorter) way of doing this. Here's Soen's solution:

def p.x;flag;end;p.x

To say I over-thought things earlier is...a bit of an over-statement. I probably could have guessed the flag itself (TWCTF{PrivatePreview}) faster than I was solving the challenge.

Anyway, why does this work? In Ruby, private simply means that the "receiver" must be self. So, we simply create a new function on the instance p and tell it to return the private value we want to access. Because we are in the same object, this access is allowed. You can always modify instances like this in Ruby and it's a pretty great feature - especially when faced with a challenge like this!

Challenge 2: Local

The second challenge was local.rb running on port 1112:

require_relative 'restrict'
Restrict.set_timeout

def get_flag(x)
  flag = "TWCTF{CENSORED}"
  x
end

input = STDIN.gets
fail unless input
input.size > 60 && input = input[0, 60]

Restrict.seccomp

STDOUT.puts get_flag(eval(input))

We can't use a function here because x takes no arguments. flag will be out-of-scope unless it's passed as an argument, so whatever solution we come up with has to be in the current scope. I started by trying a lambda (specifically, lambda { flag }):

#<Proc:0x000000014576c0@(eval):1 (lambda)>

...dammit. The lambda isn't executed...it's simply returned. Okay, so...what if we get this Proc called? Let's try get_flag(lambda { flag }).call:

# output from my local copy of the script
local.rb:15:in `eval': undefined local variable or method `flag' for main:Object (NameError)
    from (eval):1:in `<main>'
    from local.rb:15:in `eval'
    from local.rb:15:in `<main>'

Great. Now, flag is out-of-scope again because the call happens before it's passed to the function. In case you were wondering, you can't fix this by doing something like get_flag(get_flag(lambda { flag }).call). That still won't evaluate the Proc inside the proper context for the same reason. The inner call will evaluate out-of-scope before it would be passed to the outer call.

So, we need something that will be executed in the scope of the function. A block itself would probably work, but the argument to get_flag is x - not &x. Blocks aren't objects in Ruby. They're just an argument type for a method. Thus, we can only use a Proc here. But, a Proc clearly won't work...

Since this seemed to be the wrong path to head down, I decided to work on getting a handle to the function itself. I figured, once I had that, I could find a way to bash that little function into a string no matter how long it took.

How do we get a handle to our function? Simple: We get it from Kernel with Kernel.method(:get_flag):

#<Method: Module(Object)#get_flag>

See, get_flag was declared at top-level scope. You might think that means it's not attached to any object as a result, but you'd be wrong. The top-level scope in Ruby is actually just the main object. You can read more about this at the bottom of this page.

Anyway, it turns out you can also do self.method(:get_flag) to get (effectively) the same result:

#<Method: Object#get_flag>

This is important later because it's shorter.

Anyway, now that we've got a handle to our function, how can we get the value of flag out of it? I'll be honest: I have no idea. I tried calling virtually every method I could on it. There are lots:

# output from my local copy of the script when run with "self.method(:flag).methods" as the input
[:!, :!=, :!~, :<=>, :==, :===, :=~, :[], :__id__, :__send__, :arity, :call, :class, :clone, :curry,
:define_singleton_method, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?,
:get_flag, :hash, :inspect, :instance_eval, :instance_exec, :instance_of?,
:instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables,
:is_a?, :itself, :kind_of?, :method, :methods, :name, :nil?, :object_id, :original_name, :owner,
:parameters, :pretty_inspect, :pretty_print, :pretty_print_cycle, :pretty_print_inspect,
:pretty_print_instance_variables, :private_methods, :protected_methods, :public_method,
:public_methods, :public_send, :receiver, :remove_instance_variable, :respond_to?, :send,
:singleton_class, :singleton_method, :singleton_methods, :source_location, :super_method, :taint,
:tainted?, :tap, :to_enum, :to_proc, :to_s, :trust, :unbind, :untaint, :untrust, :untrusted?]

The source_location (local.rb), the instance_variables ([]), the class (Method)...they all seem like they're going to be useful or interesting, but they're not for a number of reasons. You might be able to use tap somehow? I never figured it out if you can.

Grasping for straws, I went back to the only thing I know I know how to do in Ruby: Disassemble bytecode. I never included it in the Sullivan Square write-up, but it turns out the default Ruby 2.0 interpreter (KRI) has a built-in disassembler. Hilariously, this gave me the solution! All I had to do was run RubyVM::InstructionSequence.disasm(self.method(:get_flag)):

== disasm: <RubyVM::InstructionSequence:get_flag@local.rb>==============
local table (size: 3, argc: 1 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 3] x<Arg>     [ 2] flag
0000 trace            8                                               (   4)
0002 trace            1                                               (   5)
0004 putstring        "TWCTF{EnjoyC0untryLife}"
0006 setlocal_OP__WC__0 2
0008 trace            1                                               (   6)
0010 getlocal_OP__WC__0 3

As you can see above, the flag is TWCTF{EnjoyC0untryLife}. I'm fairly confident that wasn't the intended solution...but, I couldn't figure out the right one. I'm just glad they gave me 60 characters to work with!

Challenge 3: Comment

The third and final challenge was comment.rb on port 1113:

require_relative 'restrict'
Restrict.set_timeout

input = STDIN.gets
fail unless input
input.size > 60 && input = input[0, 60]

require_relative 'comment_flag'
Restrict.seccomp

STDOUT.puts eval(input)

This does a require_relative of another file, comment_flag.rb, on line 8. Here's the contents of that file:

# FLAG is TWCTF{CENSORED}

Let's see what happens when I pair one of my ideas from the last challenge (disassembling bytecode) with one of the ideas from the first challenge (abusing interpreter globals)! Here's what we get when I run RubyVM::InstructionSequence.compile_file($"[-1]).to_a:

2
0
1
{:arg_size=>0, :local_size=>1, :stack_max=>1}
<main>
/home/ngltewpad3/comment_flag.rb
/home/ngltewpad3/comment_flag.rb
1
top
0
putnil
leave

As I suspected, we can't use disassembly to directly solve this one since we're looking for a comment. It's crazy that this worked at all, though! File.read($"[-1]) doesn't work, so this means I must have found a way around seccomp? Should probably take another look at this later...

Anyway, I played with this for a little while longer and then decided there was no way I could get the flag from here. Even if there were options to pass to RubyVM::InstructionSequence to include comments (after reading through the documentation, I don't think there are), we're out of available characters in our input string.

At this point, it was late and I decided to head to bed. I'd taken a break to get another questline done in World of WarCraft: Legion earlier. Wife's orders.

On Sunday, I took a crack at this again. I next looked at Marshal for a bit, hoping that it could maybe get me the comment. Unfortunately, as I suspected, the comments are gone well before you're able to Marshal data. I also didn't have a handle to the comment (since it's not an object), so Marshal wouldn't have done me much good. I then looked at Module, but Ruby modules aren't the same as Python modules. Calling Ruby's effective equivalent of Python's import won't get you a module or a new namespace or anything. You have to explicitly declare that your code is part of a module in Ruby. Even if they had, I don't think that would allow us to see the comment.

Unfortunately, the rest of my day was consumed by family activities, so I missed stumbling upon the answer until after the CTF was over. When I wrote up everything above (I figured I could at least get a blog post out of failing miserably), I left it at the last sentence in that last paragraph. Re-reading it that night, I thought: What if the comment was an object?

In Ruby, there's a handy module called ObjectSpace that I found when I was searching around for extra ideas for the first challenge above. I never tried it, though, because there was no way I could possibly get a solution in under 24 characters (ObjectSpace.each_object is already 23). Here, like the last challenge, we have 60.

Locally, I turned on script and tried ObjectSpace.each_object.each { |o| puts o } in comment.rb. each_object is an enumerator that lets you access every object still "living" in the interpreter. This line attempted to print every single object to my terminal that hadn't yet been garbage collected. I got a bunch of stuff, then an ArgumentError:

comment.rb:11:in `eval': NULL pointer given (ArgumentError)
    from (eval):1:in `puts'
    from (eval):1:in `puts'
    from (eval):1:in `block in <main>'
    from (eval):1:in `each_object'
    from (eval):1:in `<main>'
    from comment.rb:11:in `eval'
    from comment.rb:11:in `<main>'

Since I had no idea what caused this, I tried a few different things like { |o| puts o if o.to_s } and { |o| puts o if o.class == String } for the block. The last one worked, but only gave me String objects back. At least it had finished... I exited my script and did grep TWCTF typescript. Lo and behold, it gave me back: Binary file typescript matches. Success! ...kinda. grep -a TWCTF typescript showed me that I had, in fact, found the string I was looking for: # FLAG is TWCTF{CENSORED}. Who knew program comments were kept around as String objects in the interpreter?!

The last thing I had to figure out was how to get the string filtered out server-side. Remember: We only get 512 bytes of output from the server, so printing every string won't work. I decided a regex was the way to go and came up with ObjectSpace.each_object{|o|puts o if /TWCTF/ =~ o}. This, of course, didn't work - it's not operating only on String objects. Fortunately, the documentation told me that I could give each_object a class or module and it would only return instances of those.

Okay, final answer: ObjectSpace.each_object(String){|o|puts o if /TWCTF/ =~ o}. The server was still up, so I tried it out:

TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TWCTF
TW

Dammit! Why are there all these "TWCTF"s?! Locally, there were a few, but I thought it would be okay...

Fine. Final, final answer: ObjectSpace.each_object(String){|o|puts o if /TWCTF\{/ =~ o}. This gave me:

# FLAG is TWCTF{Transformation_t0_Artificial_Satelite}
# FLAG is TWCTF{Transformation_t0_Artificial_Satelite}
6270

Success! Better late than never, right..?

Lessons Learned

Similar to Sullivan Square last year, try the easiest possible solutions first. I should have had the first challenge finished very quickly, but I got side-tracked by trying to be clever. The straight-forward solution was the right one.

Also, if something won't work now, but still seems useful, keep track of it. I should have had the third challenge finished during the CTF because I'd already stumbled across the solution. I just forgot about it until after it was over...

Lastly, don't be afraid to try things! I never would have solved the second challenge (and wouldn't have bothered to write this up) if I hadn't given disassembling bytecode a shot. I'm hoping someone posts the "correct" solution, though, because I'm pretty sure that wasn't it!

If you're new to CTFs or programming or whatever, don't be afraid to screw up! As long as you analyze what you did and make changes to avoid the problem in the future, messing up is simply part of learning. Ask yourself: If you give up, how are you going to have content for your blog?