ROP (Reverse 250)

Description

Who doesn’t like ROP?
Let’s try some new features introduced in 2.3.

rop.iseq

Hint

None

If the above link doesn’t work, please use this link.

New features?

Well, see the Ruby 2.3.0 news.

RubyVM::InstructionSequence#to_binary and .load_from_binary are introduced as experimental features. With these features, we can make a ISeq (bytecode) pre-compilation system.

Yes, so this is about using RubyVM::InstructionSequence.load_from_binary. Let’s just start with:

RubyVM::InstructionSequence.load_from_binary(File.read('rop.iseq'))

But you can face this kind of error:

RuntimeError: unmatched platform
        from (irb):1:in `load_from_binary'
        from (irb):1
        from /usr/bin/irb:11:in `<main>'

By checking strings rop.iseq, we can find x86_64-linux. So we need Ruby 2.3 on Linux x86_64 platform. You can see the platform by ruby --version. This is the version of my one:

ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]

Disassembling

Once the binary is loaded by .load_from_binary, we can get the instruction sequence as a String by #disasm:

puts RubyVM::InstructionSequence.load_from_binary(File.read('rop.iseq')).disasm

Then you get a readable instruction sequence!

== disasm: #<ISeq:<compiled>@<compiled>>================================
== catch table
| catch type: break  st: 0096 ed: 0102 sp: 0000 cont: 0102
| catch type: break  st: 0239 ed: 0245 sp: 0000 cont: 0245
|------------------------------------------------------------------------
local table (size: 3, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: [email protected], kwrest: -1])
[ 3] k          [ 2] xs
0000 trace            1                                               (   1)
0002 putself
0003 putstring        "digest"
0005 opt_send_without_block <callinfo!mid:require, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0008 pop
0009 trace            1                                               (   2)
0011 putself
0012 putstring        "prime"
0014 opt_send_without_block <callinfo!mid:require, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0017 pop
0018 trace            1                                               (   4)
0020 putspecialobject 3
0022 putnil
0023 defineclass      :String, <class:String>, 0
0027 pop
0028 trace            1                                               (  22)
0030 putspecialobject 1
0032 putobject        :gg
0034 putiseq          gg
0036 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>, <callcache>
0039 pop
0040 trace            1                                               (  27)
0042 putspecialobject 1
0044 putobject        :f
0046 putiseq          f
0048 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>, <callcache>
0051 pop
0052 trace            1                                               (  38)
0054 getglobal        $stdin
0056 opt_send_without_block <callinfo!mid:gets, argc:0, ARGS_SIMPLE>, <callcache>
0059 opt_send_without_block <callinfo!mid:chomp, argc:0, ARGS_SIMPLE>, <callcache>
0062 setlocal_OP__WC__0 3
0064 trace            1                                               (  39)
0066 getlocal_OP__WC__0 3
0068 putstring        "-"
0070 opt_send_without_block <callinfo!mid:split, argc:1, ARGS_SIMPLE>, <callcache>
0073 setlocal_OP__WC__0 2
0075 trace            1                                               (  40)
0077 getlocal_OP__WC__0 2
0079 opt_size         <callinfo!mid:size, argc:0, ARGS_SIMPLE>, <callcache>
0082 putobject        5
0084 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0087 branchif         94
0089 putself
0090 opt_send_without_block <callinfo!mid:gg, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0093 pop
0094 trace            1                                               (  41)
0096 getlocal_OP__WC__0 2
0098 send             <callinfo!mid:all?, argc:0>, <callcache>, block in <compiled>
0102 branchif         109
0104 putself
0105 opt_send_without_block <callinfo!mid:gg, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0108 pop
0109 trace            1                                               (  42)
0111 getlocal_OP__WC__0 2
0113 putobject_OP_INT2FIX_O_0_C_
0114 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0117 putobject        16
0119 opt_send_without_block <callinfo!mid:to_i, argc:1, ARGS_SIMPLE>, <callcache>
0122 putobject        31337
0124 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0127 branchif         134
0129 putself
0130 opt_send_without_block <callinfo!mid:gg, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0133 pop
0134 trace            1                                               (  43)
0136 getlocal_OP__WC__0 2
0138 putobject_OP_INT2FIX_O_1_C_
0139 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0142 opt_send_without_block <callinfo!mid:reverse, argc:0, ARGS_SIMPLE>, <callcache>
0145 putstring        "FACE"
0147 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0150 branchif         157
0152 putself
0153 opt_send_without_block <callinfo!mid:gg, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0156 pop
0157 trace            1                                               (  44)
0159 putself
0160 putobject        217
0162 getlocal_OP__WC__0 2
0164 putobject        2
0166 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0169 putobject        16
0171 opt_send_without_block <callinfo!mid:to_i, argc:1, ARGS_SIMPLE>, <callcache>
0174 putobject        314159
0176 opt_send_without_block <callinfo!mid:f, argc:3, FCALL|ARGS_SIMPLE>, <callcache>
0179 putobject        28
0181 opt_send_without_block <callinfo!mid:to_s, argc:1, ARGS_SIMPLE>, <callcache>
0184 opt_send_without_block <callinfo!mid:upcase, argc:0, ARGS_SIMPLE>, <callcache>
0187 putstring        "48D5"
0189 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0192 branchif         199
0194 putself
0195 opt_send_without_block <callinfo!mid:gg, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0198 pop
0199 trace            1                                               (  45)
0201 getlocal_OP__WC__0 2
0203 putobject        3
0205 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0208 putobject        10
0210 opt_send_without_block <callinfo!mid:to_i, argc:1, ARGS_SIMPLE>, <callcache>
0213 opt_send_without_block <callinfo!mid:prime_division, argc:0, ARGS_SIMPLE>, <callcache>
0216 putobject        :first
0218 send             <callinfo!mid:map, argc:0, ARGS_BLOCKARG>, <callcache>, nil
0222 opt_send_without_block <callinfo!mid:sort, argc:0, ARGS_SIMPLE>, <callcache>
0225 duparray         [53, 97]
0227 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0230 branchif         237
0232 putself
0233 opt_send_without_block <callinfo!mid:gg, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0236 pop
0237 trace            1                                               (  46)
0239 getlocal_OP__WC__0 2
0241 send             <callinfo!mid:map, argc:0>, <callcache>, block in <compiled>
0245 putobject        :^
0247 opt_send_without_block <callinfo!mid:inject, argc:1, ARGS_SIMPLE>, <callcache>
0250 opt_send_without_block <callinfo!mid:to_s, argc:0, ARGS_SIMPLE>, <callcache>
0253 opt_send_without_block <callinfo!mid:sha1, argc:0, ARGS_SIMPLE>, <callcache>
0256 putstring        "947d46f8060d9d7025cc5807ab9bf1b3b9143304"
0258 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0261 branchif         268
0263 putself
0264 opt_send_without_block <callinfo!mid:gg, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0267 pop
0268 trace            1                                               (  48)
0270 putself
0271 putobject        "Congratz! flag is "
0273 putstring        "bce410e85433ba94f0d832d99556f9764b220eeda7e807fe4938a5e6effa7d83c765e1795b6c26af8ad258f6"
0275 opt_send_without_block <callinfo!mid:dehex, argc:0, ARGS_SIMPLE>, <callcache>
0278 getlocal_OP__WC__0 3
0280 opt_send_without_block <callinfo!mid:sha1, argc:0, ARGS_SIMPLE>, <callcache>
0283 opt_send_without_block <callinfo!mid:dehex, argc:0, ARGS_SIMPLE>, <callcache>
0286 opt_send_without_block <callinfo!mid:^, argc:1, ARGS_SIMPLE>, <callcache>
0289 tostring
0290 concatstrings    2
0292 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0295 leave
== disasm: #<ISeq:<class:String>@<compiled>>============================
0000 trace            2                                               (   4)
0002 trace            1                                               (   5)
0004 putspecialobject 1
0006 putobject        :^
0008 putiseq          ^
0010 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>, <callcache>
0013 pop
0014 trace            1                                               (   9)
0016 putspecialobject 1
0018 putobject        :sha1
0020 putiseq          sha1
0022 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>, <callcache>
0025 pop
0026 trace            1                                               (  13)
0028 putspecialobject 1
0030 putobject        :enhex
0032 putiseq          enhex
0034 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>, <callcache>
0037 pop
0038 trace            1                                               (  17)
0040 putspecialobject 1
0042 putobject        :dehex
0044 putiseq          dehex
0046 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>, <callcache>
0049 trace            4                                               (  20)
0051 leave                                                            (  17)
== disasm: #<ISeq:^@<compiled>>=========================================
== catch table
| catch type: break  st: 0004 ed: 0015 sp: 0000 cont: 0015
|------------------------------------------------------------------------
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: [email protected], kwrest: -1])
[ 2] other<Arg>
0000 trace            8                                               (   5)
0002 trace            1                                               (   6)
0004 putself
0005 opt_send_without_block <callinfo!mid:bytes, argc:0, ARGS_SIMPLE>, <callcache>
0008 opt_send_without_block <callinfo!mid:map, argc:0, ARGS_SIMPLE>, <callcache>
0011 send             <callinfo!mid:with_index, argc:0>, <callcache>, block in ^
0015 putstring        "C*"
0017 opt_send_without_block <callinfo!mid:pack, argc:1, ARGS_SIMPLE>, <callcache>
0020 trace            16                                              (   7)
0022 leave                                                            (   6)
== disasm: #<ISeq:block in ^@<compiled>>================================
== catch table
| catch type: redo   st: 0002 ed: 0027 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0027 sp: 0000 cont: 0027
|------------------------------------------------------------------------
local table (size: 3, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: [email protected], kwrest: -1])
[ 3] x<Arg>     [ 2] i<Arg>
0000 trace            256                                             (   6)
0002 trace            1
0004 getlocal_OP__WC__0 3
0006 getlocal_OP__WC__1 2
0008 getlocal_OP__WC__0 2
0010 getlocal_OP__WC__1 2
0012 opt_size         <callinfo!mid:size, argc:0, ARGS_SIMPLE>, <callcache>
0015 opt_mod          <callinfo!mid:%, argc:1, ARGS_SIMPLE>, <callcache>
0018 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0021 opt_send_without_block <callinfo!mid:ord, argc:0, ARGS_SIMPLE>, <callcache>
0024 opt_send_without_block <callinfo!mid:^, argc:1, ARGS_SIMPLE>, <callcache>
0027 trace            512
0029 leave
== disasm: #<ISeq:[email protected]<compiled>>======================================
0000 trace            8                                               (   9)
0002 trace            1                                               (  10)
0004 getinlinecache   13, <is:0>
0007 getconstant      :Digest
0009 getconstant      :SHA1
0011 setinlinecache   <is:0>
0013 putself
0014 opt_send_without_block <callinfo!mid:hexdigest, argc:1, ARGS_SIMPLE>, <callcache>
0017 trace            16                                              (  11)
0019 leave                                                            (  10)
== disasm: #<ISeq:[email protected]<compiled>>=====================================
0000 trace            8                                               (  13)
0002 trace            1                                               (  14)
0004 putself
0005 putstring        "H*"
0007 opt_send_without_block <callinfo!mid:unpack, argc:1, ARGS_SIMPLE>, <callcache>
0010 putobject_OP_INT2FIX_O_0_C_
0011 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0014 trace            16                                              (  15)
0016 leave                                                            (  14)
== disasm: #<ISeq:[email protected]<compiled>>=====================================
0000 trace            8                                               (  17)
0002 trace            1                                               (  18)
0004 putself
0005 newarray         1
0007 putstring        "H*"
0009 opt_send_without_block <callinfo!mid:pack, argc:1, ARGS_SIMPLE>, <callcache>
0012 trace            16                                              (  19)
0014 leave                                                            (  18)
== disasm: #<ISeq:[email protected]<compiled>>========================================
0000 trace            8                                               (  22)
0002 trace            1                                               (  23)
0004 putself
0005 putstring        "Invalid Key @[email protected]"
0007 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0010 pop
0011 trace            1                                               (  24)
0013 putself
0014 putobject_OP_INT2FIX_O_1_C_
0015 opt_send_without_block <callinfo!mid:exit, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0018 trace            16                                              (  25)
0020 leave                                                            (  24)
== disasm: #<ISeq:[email protected]<compiled>>=========================================
== catch table
| catch type: break  st: 0021 ed: 0086 sp: 0000 cont: 0086
| catch type: next   st: 0021 ed: 0086 sp: 0000 cont: 0018
| catch type: redo   st: 0021 ed: 0086 sp: 0000 cont: 0021
|------------------------------------------------------------------------
local table (size: 6, argc: 3 [opts: 0, rest: -1, post: 0, block: -1, kw: [email protected], kwrest: -1])
[ 6] a<Arg>     [ 5] b<Arg>     [ 4] m<Arg>     [ 3] s          [ 2] r
0000 trace            8                                               (  27)
0002 trace            1                                               (  28)
0004 putobject_OP_INT2FIX_O_1_C_
0005 setlocal_OP__WC__0 3
0007 trace            1                                               (  29)
0009 getlocal_OP__WC__0 6
0011 setlocal_OP__WC__0 2
0013 trace            1                                               (  30)
0015 jump             75
0017 putnil
0018 pop
0019 jump             75
0021 trace            1                                               (  31)
0023 getlocal_OP__WC__0 5
0025 putobject_OP_INT2FIX_O_0_C_
0026 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0029 putobject_OP_INT2FIX_O_1_C_
0030 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0033 branchunless     49
0035 getlocal_OP__WC__0 3
0037 getlocal_OP__WC__0 2
0039 opt_mult         <callinfo!mid:*, argc:1, ARGS_SIMPLE>, <callcache>
0042 getlocal_OP__WC__0 4
0044 opt_mod          <callinfo!mid:%, argc:1, ARGS_SIMPLE>, <callcache>
0047 setlocal_OP__WC__0 3
0049 trace            1                                               (  32)
0051 getlocal_OP__WC__0 5
0053 putobject_OP_INT2FIX_O_1_C_
0054 opt_send_without_block <callinfo!mid:>>, argc:1, ARGS_SIMPLE>, <callcache>
0057 setlocal_OP__WC__0 5
0059 trace            1                                               (  33)
0061 getlocal_OP__WC__0 2
0063 getlocal_OP__WC__0 2
0065 opt_mult         <callinfo!mid:*, argc:1, ARGS_SIMPLE>, <callcache>
0068 getlocal_OP__WC__0 4
0070 opt_mod          <callinfo!mid:%, argc:1, ARGS_SIMPLE>, <callcache>
0073 setlocal_OP__WC__0 2
0075 getlocal_OP__WC__0 5                                             (  30)
0077 putobject_OP_INT2FIX_O_0_C_
0078 opt_neq          <callinfo!mid:!=, argc:1, ARGS_SIMPLE>, <callcache>, <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0083 branchif         21
0085 putnil
0086 pop
0087 trace            1                                               (  35)
0089 getlocal_OP__WC__0 3
0091 trace            16                                              (  36)
0093 leave                                                            (  35)
== disasm: #<ISeq:block in <compiled>@<compiled>>=======================
== catch table
| catch type: redo   st: 0002 ed: 0011 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0011 sp: 0000 cont: 0011
|------------------------------------------------------------------------
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: [email protected], kwrest: -1])
[ 2] x<Arg>
0000 trace            256                                             (  41)
0002 trace            1
0004 getlocal_OP__WC__0 2
0006 putobject        /^[0-9A-F]{4}$/
0008 opt_regexpmatch2 <callinfo!mid:=~, argc:1, ARGS_SIMPLE>, <callcache>
0011 trace            512
0013 leave
== disasm: #<ISeq:block in <compiled>@<compiled>>=======================
== catch table
| catch type: redo   st: 0002 ed: 0011 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0011 sp: 0000 cont: 0011
|------------------------------------------------------------------------
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: [email protected], kwrest: -1])
[ 2] x<Arg>
0000 trace            256                                             (  46)
0002 trace            1
0004 getlocal_OP__WC__0 2
0006 putobject        16
0008 opt_send_without_block <callinfo!mid:to_i, argc:1, ARGS_SIMPLE>, <callcache>
0011 trace            512
0013 leave

Quite long, but this is the summary:

class String
  def ^; end

  def sha1; end

  def enhex; end

  def dehex; end
end

def gg; end

def f; end

# main code

Note that the last two blocks are block code for #all? and #map.

Rebuilding the code

We got the instruction sequence, and we can also compile our own code with the following script:

puts RubyVM::InstructionSequence.compile_file('code.rb').disasm

For example, create code.rb with:

require 'digest'
require 'prime'

Then the result of script is:

== disasm: #<ISeq:<main>@code.rb>=======================================
0000 trace            1                                               (   1)
0002 putself
0003 putstring        "digest"
0005 opt_send_without_block <callinfo!mid:require, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0008 pop
0009 trace            1                                               (   2)
0011 putself
0012 putstring        "prime"
0014 opt_send_without_block <callinfo!mid:require, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0017 leave

Let me just skip the details of recovering, so here is the original code.

require 'digest'
require 'prime'

class String
  def ^(other)
    self.bytes.map.with_index { |x, i| x ^ other[i % other.size].ord }.pack('C*')
  end

  def sha1
    Digest::SHA1.hexdigest(self)
  end

  def enhex
    self.unpack('H*')[0]
  end

  def dehex
    [self].pack('H*')
  end
end

def gg
  puts 'Invalid Key @[email protected]'
  exit(1)
end

def f(a, b, m)
  s = 1
  r = a
  while b != 0
    s = (s * r) % m if b[0] == 1
    b = b >> 1
    r = (r * r) % m
  end
  s
end

k = $stdin.gets.chomp
xs = k.split('-')

gg unless xs.size == 5
gg unless xs.all? { |x| x =~ /^[0-9A-F]{4}$/ }
gg unless xs[0].to_i(16) == 31337
gg unless xs[1].reverse == 'FACE'
gg unless f(217, xs[2].to_i(16), 314159).to_s(28).upcase == '48D5'
gg unless xs[3].to_i(10).prime_division.map(&:first).sort == [53, 97]
gg unless xs.map { |x| x.to_i(16) }.inject(:^).to_s.sha1 == '947d46f8060d9d7025cc5807ab9bf1b3b9143304'

puts "Congratz! flag is #{'bce410e85433ba94f0d832d99556f9764b220eeda7e807fe4938a5e6effa7d83c765e1795b6c26af8ad258f6'.dehex ^ k.sha1.dehex}"

Finding the answer input

See the core part again:

gg unless xs.size == 5
gg unless xs.all? { |x| x =~ /^[0-9A-F]{4}$/ }
gg unless xs[0].to_i(16) == 31337
gg unless xs[1].reverse == 'FACE'
gg unless f(217, xs[2].to_i(16), 314159).to_s(28).upcase == '48D5'
gg unless xs[3].to_i(10).prime_division.map(&:first).sort == [53, 97]
gg unless xs.map { |x| x.to_i(16) }.inject(:^).to_s.sha1 == '947d46f8060d9d7025cc5807ab9bf1b3b9143304'

By the first two lines, we have to enter XXXX-XXXX-XXXX-XXXX-XXXX format.

  • 31337 is 0x7A69, so the first word is 7A69.
  • The second one is obviously ECAF.
  • For the third word, we can just do a brute force attack.

    (0...0xffff).each do |x|
      if f(217, x, 314159).to_s(28).upcase == '48D5'
        puts x.to_s(16).upcase
        break
      end
    end
    

    This returns 1BD2.

  • 53 * 97 = 5141, so the fourth is 5141.
  • The SHA1 in the last condition is the same with '5671'.sha1. So we can get the last word by computing XORs of all previous words and 5671.

    >> (0x7A69 ^ 0xECAF ^ 0x1BD2 ^ 0x5141 ^ 5671).to_s(16).upcase
    => "CA72"
    

So the answer input is 7A69-ECAF-1BD2-5141-CA72.

$ ruby code.rb
7A69-ECAF-1BD2-5141-CA72
Congratz! flag is hitcon{ROP = Ruby Obsecured Programming ^_<}

So the flag is hitcon{ROP = Ruby Obsecured Programming ^_<}.