Who doesn’t like ROP?
Let’s try some new features introduced in 2.3.
rop.iseq
HintNone
If the above link doesn’t work, please use thislink.
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] DisassemblingOnce 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')).disasmThen 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: -1@-1, 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: -1@-1, 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: -1@-1, 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:sha1@<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:enhex@<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:dehex@<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:gg@<compiled>>======================================== 0000 trace 8 ( 22) 0002 trace 1 ( 23) 0004 putself 0005 putstring "Invalid Key @_@" 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:f@<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: -1@-1, 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: -1@-1, 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: -1@-1, 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 leaveQuite 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 codeNote that the last two blocks are block code for #all? and #map .
Rebuilding the codeWe got the instruction sequence, and we can also compile our own code with the following script:
puts RubyVM::InstructionSequence.compile_file('code.rb').disasmFor 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 leaveLet 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 @_@' 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 inputSee 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 endThis 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 ^_<} .