Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Restricted Ruby - A CTF story

Avatar for Rosa Rosa
January 26, 2017

Restricted Ruby - A CTF story

It's not common to find Ruby problems in security CTF (Capture The Flag) competitions, but luckily there are some out there. These type of problems are always a great opportunity to learn some Ruby internals or features of the language that aren't usually known. They are also a perfect excuse to do crazy things and get as creative as possible. In this talk I'll present some problems from the CTF organised by a famous Japanese security group in 2016 and some ways to solve them.

Avatar for Rosa

Rosa

January 26, 2017
Tweet

More Decks by Rosa

Other Decks in Technology

Transcript

  1. restricted_ruby/ ├── comment.rb ├── comment_flag.rb ├── local.rb ├── private.rb ├──

    restrict.rb └── run.sh ppc1.chal.ctf.westerns.tokyo 1111 (flag 1, “private”) ppc1.chal.ctf.westerns.tokyo 1112 (flag 2, “local”) ppc1.chal.ctf.westerns.tokyo 1113 (flag 3, “comment”)
  2. #!/bin/bash cd $(dirname "$0") /usr/bin/ruby2.0 $1.rb 2> /dev/null | head

    --bytes=512 # Ubuntu 14.04(64bit) ruby2.0 package run.sh
  3. restrict.rb (sandboxing) 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 # allow write ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 1, 0) # allow exit ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 60, 0) # allow close ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 3, 0) # allow brk ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 12, 0) # allow mprotect ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 10, 0) # allow mmap ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 9, 0) # allow munmap ret |= Seccomp::seccomp_rule_add(ctx, Seccomp::SCMP_ACT_ALLOW, 11, 0) ret |= Seccomp::seccomp_load(ctx) fail "Failed to setup syscall." unless ret == 0 end end require_relative 'restrict' Restrict.set_timeout ... Restrict.seccomp In each main file
  4. # FLAG is TWCTF{CENSORED} input = STDIN.gets fail unless input

    input.size > 60 && input = input[0, 60] require_relative 'comment_flag' STDOUT.puts eval(input) 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] STDOUT.puts eval(input) def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets fail unless input input.size > 60 && input = input[0,60] STDOUT.puts get_flag(eval(input)) private.rb local.rb comment.rb comment_flag.rb
  5. # FLAG is TWCTF{CENSORED} input = STDIN.gets fail unless input

    input.size > 60 && input = input[0, 60] require_relative 'comment_flag' STDOUT.puts eval(input) 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] STDOUT.puts eval(input) def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets fail unless input input.size > 60 && input = input[0,60] STDOUT.puts get_flag(eval(input)) private.rb local.rb comment.rb comment_flag.rb
  6. 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] STDOUT.puts eval(input)
  7. 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] STDOUT.puts eval(input) p.send(:flag)
  8. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1111 p.send(:flag) private.rb:24:in `eval': private method `send'

    called for :Private (NoMethodError) from private.rb:24:in `eval' from private.rb:24:in `<main>'
  9. 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] STDOUT.puts eval(input)
  10. 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] STDOUT.puts eval(input) def p.a;flag;end;p.a
  11. # FLAG is TWCTF{CENSORED} input = STDIN.gets fail unless input

    input.size > 60 && input = input[0, 60] require_relative 'comment_flag' STDOUT.puts eval(input) 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] STDOUT.puts eval(input) def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets fail unless input input.size > 60 && input = input[0,60] STDOUT.puts get_flag(eval(input)) private.rb local.rb comment.rb comment_flag.rb
  12. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input))
  13. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 binding.eval('flag') local.rb:25:in `eval': undefined local variable

    or method `flag' for main:Object (NameError) from (eval):1:in `eval' from (eval):1:in `<main>' from local.rb:25:in `eval' from local.rb:25:in `<main>'
  14. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input))
  15. class Method alias kall call def call(*a) kall(a) puts binding.local_variables

    end end f=method(:get_flag);def f.call(*a);binding.local_variables;end Method
  16. def hello puts "hello, world" end puts RubyVM::InstructionSequence.disasm(method(:hello)) == disasm:

    <RubyVM::InstructionSequence:hello@/tmp/method.rb>============ 0000 trace 8 ( 1) 0002 trace 1 ( 2) 0004 putself 0005 putstring "hello, world" 0007 send :puts, 1, nil, 8, <ic:0> 0013 trace 16 ( 3) 0015 leave ( 2) RubyVM::InstructionSequence
  17. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input))
  18. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input)) RubyVM::InstructionSequence.disasm(method(:get_flag))
  19. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 RubyVM::InstructionSequence.disasm(method(:get_flag)) == disasm: <RubyVM::InstructionSequence:[email protected]>============== 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
  20. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 RubyVM::InstructionSequence.disasm(method(:get_flag)) == disasm: <RubyVM::InstructionSequence:[email protected]>============== 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
  21. # FLAG is TWCTF{CENSORED} input = STDIN.gets fail unless input

    input.size > 60 && input = input[0, 60] require_relative 'comment_flag' STDOUT.puts eval(input) 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] STDOUT.puts eval(input) def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets fail unless input input.size > 60 && input = input[0,60] STDOUT.puts get_flag(eval(input)) private.rb local.rb comment.rb comment_flag.rb
  22. input = STDIN.gets fail unless input input.size > 60 &&

    input = input[0, 60] require_relative 'comment_flag' STDOUT.puts eval(input) comment_flag.rb # FLAG is TWCTF{CENSORED}
  23. /tmp/hello.rb puts "Hello, world!" # This is a comment RubyVM::InstructionSequence.compile_file("/tmp/hello.rb")

    #=> <RubyVM::InstructionSequence:<main>@/tmp/hello.rb> RubyVM::InstructionSequence.compile_file("/tmp/hello.rb").to_a #=> ["YARVInstructionSequence/SimpleDataFormat", 2, 0, 1, {:arg_size=>0, :local_size=>1, :stack_max=>2}, "<main>", "/tmp/hello.rb", "/private/tmp/hello.rb", 1, :top, [], 0, [], [1, [:trace, 1], [:putself], [:putstring, "Hello, world!"], [:opt_send_simple, {:mid=>:puts, :flag=>264, :orig_argc=>1, :blockptr=>nil}], [:leave]]] RubyVM::InstructionSequence
  24. a = 'hello world' count = ObjectSpace.each_object(String) { |x| p

    x } puts "Total count: #{count}" ... "initialize" "retval" "\"RubyVM::Env\"" "hello world" "$-a" "$-l" "$-p" "block in <main>" ... Total count: 11383 ObjectSpace#each_object
  25. input = STDIN.gets fail unless input input.size > 60 &&

    input = input[0, 60] require_relative 'comment_flag' STDOUT.puts eval(input) comment_flag.rb # FLAG is TWCTF{CENSORED}
  26. input = STDIN.gets fail unless input input.size > 60 &&

    input = input[0, 60] require_relative 'comment_flag' STDOUT.puts eval(input) comment_flag.rb # FLAG is TWCTF{CENSORED} ObjectSpace.each_object{|x| p x if x.to_s[/TWCTF\{.+\}/]}
  27. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1113 ObjectSpace.each_object{|x| p x if x.to_s[/TWCTF\{.+\}/]} #

    FLAG is TWCTF{Transformation_t0_Artificial_Satelite} "\"# FLAG is TWCTF{Transformation_t0_Artificial_Satelite}\\n\"" "\"# FLAG is TWCTF{Transformation_t0_Artificial_Satelite}\\n\"" "\"# FLAG is TWCTF{Transformation_t0_Artificial_Satelite}\\n\"" 9548
  28. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1113 ObjectSpace.each_object{|x| p x if x.to_s[/TWCTF\{.+\}/]} #

    FLAG is TWCTF{Transformation_t0_Artificial_Satelite} "\"# FLAG is TWCTF{Transformation_t0_Artificial_Satelite}\\n\"" "\"# FLAG is TWCTF{Transformation_t0_Artificial_Satelite}\\n\"" "\"# FLAG is TWCTF{Transformation_t0_Artificial_Satelite}\\n\"" 9548
  29. # FLAG is TWCTF{CENSORED} input = STDIN.gets require_relative 'comment_flag' STDOUT.puts

    eval(input) 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 STDOUT.puts eval(input) def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets STDOUT.puts get_flag(eval(input)) private.rb local.rb comment.rb comment_flag.rb
  30. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input))
  31. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input)) ObjectSpace.each_object{|x| p x if x.to_s[/TWCTF\{.+\}/]}
  32. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input))
  33. Kernel#set_trace_func class Test def test a = 1 end end

    p = proc do |event, file, line, id, binding, classname| printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname end set_trace_func(p) t = Test.new t.test
  34. Kernel#set_trace_func class Test def test a = 1 end end

    p = proc do |event, file, line, id, binding, classname| printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname end set_trace_func(p) t = Test.new t.test c-return test.rb:10 set_trace_func Kernel line test.rb:12 c-call test.rb:12 new Class c-call test.rb:12 initialize BasicObject c-return test.rb:12 initialize BasicObject c-return test.rb:12 new Class line test.rb:13 call test.rb:2 test Test line test.rb:3 test Test return test.rb:4 test Test
  35. Kernel#set_trace_func class Test def test a = 1 end end

    p = proc do |event, file, line, id, binding, classname| printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname end set_trace_func(p) t = Test.new t.test
  36. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input))
  37. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input)) set_trace_func proc{|*a| p a[4].eval('flag') rescue nil}
  38. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 set_trace_func proc{|*a| p a[4].eval('flag') rescue nil}

    nil nil "TWCTF{EnjoyC0untryLife}" "TWCTF{EnjoyC0untryLife}" #<Proc:0x007ff3b1853f48@(eval):1>
  39. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 set_trace_func proc{|*a| p a[4].eval('flag') rescue nil}

    nil nil "TWCTF{EnjoyC0untryLife}" "TWCTF{EnjoyC0untryLife}" #<Proc:0x007ff3b1853f48@(eval):1>
  40. TracePoint class Test def test a = 1 end end

    TracePoint.trace(:call, :return, :line) { |tp| p tp.inspect } t = Test.new t.test
  41. TracePoint class Test def test a = 1 end end

    TracePoint.trace(:call, :return, :line) { |tp| p tp.inspect } t = Test.new t.test "#<TracePoint:[email protected]:9>" "#<TracePoint:[email protected]:10>" "#<TracePoint:call `test'@test.rb:2>" "#<TracePoint:[email protected]:3 in `test'>" "#<TracePoint:return `test'@test.rb:4>"
  42. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input))
  43. def get_flag(x) flag = "TWCTF{CENSORED}" x end input = STDIN.gets

    fail unless input input.size > 60 && input = input[0, 60] STDOUT.puts get_flag(eval(input)) TracePoint.trace(:return){|a|puts a.binding.eval"flag"}