Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

HITCON CTF 2016: ROP write-up

0
0
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 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] 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: -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 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 @_@' 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 ^_<} .


Viewing all articles
Browse latest Browse all 12749