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

Restricted Ruby - A CTF story

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.

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"}