By jeje | November 14, 2016
RVM(200pt)
이번에 poxx2016 본선에 참가하게 되었다.
생각보다 많이 못풀어서 결과는 별로였지만, 꽤 재미있는 몇몇 새로운 문제들이 있었어서- 주말에 좀 풀어보았다.
RVM 이라는 문제는 컴파일 된 ruby 파일을 복원하여 flag를 알아내는 문제인데, 대회때는 실행도 제대로 못 시켜봤었다-_-;; 이게뭐람
여튼, 꽤 재미있는 문제였고! 풀었으니 롸업을 올려본다.
아 루비 하나도 모르는데 이건 뭐람…
문제와 함께 rvm.rb 라는 루비 파일이 주어진다 ㅇ<-< 확인해보자.
$ ruby rvm.rb
rvm.rb:1: Invalid char `\x02' in expression
실행이 아름답게 되지 않는다.
확인 해 보면, 스크립트 언어인 ruby 로 작성된 rb파일이 컴파일 되어 바이트코드 형태로 되어있는 모습을 볼 수 있다.
$ cat rvm.rb
YARB�"9���!x86_64-linux-gnu*.*.*1
=���������*U ..
�����������*.*.*
W*
.*
.W*UGYG.@4.*UGG.@4.*UGZGUG@4.*UGGUG@4.*UGGUG@4.*UGGUG@4.*UYG.W*UZG@4.*UG@4.*UG@4.*U@4.*UG@4.*UYG@4.*UZG@4.*UG@4.*UG.@4.*UG..@4.*UG .W*UYG!@4.*UZG"@4.*#.1
3>^����0EZo���� �!�"
#&
$%&'()+,-./0123456E/home/saika/pox/rvm/rvm.rbEEbEwhat?��������Ef7��EEqwertyu7iop1asdf3ghjklzxcvbnmE:: saika's auth system ::E_�E62EH*E74�!�� �
ErE{�EuEyEis 8EgalE7d7468346eE1ErubEyaEsuccess!
auth your flag..EputsEexitEhexEscanEreverseEjoinE$stdinEiEsEtEttEcore#define_methodEgetsEchompEsplitE[]EpackE==EupcaseE..����� 7COdx����
!7MYeq}������6Lh}���� 6 Q i � � � � � � !/!H!^!v!�!�!�!SHA-1:�y�%H��J�
��ċi�
vi로 열어봐도 괴랄하다.
어떻게 해야하나.
우선 vi로 열었을 때, 파일 시그니처같이 박혀있는 저 YARB
부터 뭔지 확인 해 봐야겠지.
구글신을 영접하면 다음과 같은 결과를 얻을 수 있다.
Bytecode cache is experimentally released in Ruby2.3
Ruby trunk – New ISeq serialize binary format
뭐, 여기를 참고하는게 더 빠르긴 했다. 같은 컨셉의 문제인듯.
HITCON2016-ROP
위의 링크를 참고하여, 대략 아래와 같은 코드를 짜서 문제의 rvm.rb 파일을 돌려보도록 하자.
#exe.rb
iseq = nil
File.open("rvm.rb", "rb") do |file|
iseq = RubyVM::InstructionSequence.load_from_binary(file.read)
end
iseq.eval
위의 코드는 컴파일 된 루비 스크립트를 실행시켜준다. 실행시키면, 다음과 같은 결과를 얻을 수 있다.
$ ruby exe.rb
:: saika's auth system ::
aaaa
/home/saika/pox/rvm/rvm.rb:16:in `': undefined method `[]' for nil:NilClass (NoMethodError)
from exe.rb:6:in `eval'
from exe.rb:6:in `'
여차저차 실행까지는 왔다.
auth system 이라는 것을 보니, flag를 얻기 위해 프로그램에서 요구하는 문자열로 auth를 시도하면 되는 모양.
…근데 그래서 이다음엔 뭘 어째야하나.
여차저차 실행까지는 왔다.
auth system 이라는 것을 보니, flag를 얻기 위해 프로그램에서 요구하는 문자열로 auth를 시도하면 되는 모양.
…근데 그래서 이다음엔 뭘 어째야하나.
exe.rb 코드에 puts iseq.disasm
을 추가하면, 컴파일 된 ruby 스크립트의 디스어셈블 결과를 얻어낼 수 있다.
$ cat exe.rb
iseq = nil
File.open("rvm.rb", "rb") do |file|
iseq = RubyVM::InstructionSequence.load_from_binary(file.read)
end
puts iseq.disasm #add this line
#iseq.eval
디스어셈블 한 결과는 다음과 같다….는 좀 길다. 후딱 넘기자.
$ ruby exe.rb
== disasm: #<ISeq:@/home/saika/pox/rvm/rvm.rb>====================
local table (size: 6, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 6] f [ 5] i [ 4] s [ 3] t [ 2] tt
0000 trace 1 ( 1)
0002 putspecialobject 1
0004 putobject :b
0006 putiseq b
0008 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>,
0011 pop
0012 trace 1 ( 6)
0014 putspecialobject 1
0016 putobject :f
0018 putiseq f
0020 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>,
0023 pop
0024 trace 1 ( 10)
0026 putstring "qwertyu7iop1asdf3ghjklzxcvbnm"
0028 setlocal_OP__WC__0 6
0030 trace 1 ( 12)
0032 putself
0033 putstring ":: saika's auth system ::"
0035 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>,
0038 pop
0039 trace 1 ( 13)
0041 getglobal $stdin
0043 opt_send_without_block <callinfo!mid:gets, argc:0, ARGS_SIMPLE>,
0046 opt_send_without_block <callinfo!mid:chomp, argc:0, ARGS_SIMPLE>,
0049 setlocal_OP__WC__0 5
0051 trace 1 ( 14)
0053 getlocal_OP__WC__0 5
0055 putstring "_"
0057 opt_send_without_block <callinfo!mid:split, argc:1, ARGS_SIMPLE>,
0060 setlocal_OP__WC__0 4
0062 trace 1 ( 16)
0064 getlocal_OP__WC__0 4
0066 putobject 2
0068 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0071 putobject_OP_INT2FIX_O_0_C_
0072 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0075 putstring "62"
0077 newarray 1
0079 putstring "H*"
0081 opt_send_without_block <callinfo!mid:pack, argc:1, ARGS_SIMPLE>,
0084 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0087 branchif 94
0089 putself
0090 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0093 pop
0094 trace 1 ( 17)
0096 getlocal_OP__WC__0 4
0098 putobject 2
0100 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0103 putobject 2
0105 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0108 putstring "74"
0110 newarray 1
0112 putstring "H*"
0114 opt_send_without_block <callinfo!mid:pack, argc:1, ARGS_SIMPLE>,
0117 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0120 branchif 127
0122 putself
0123 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0126 pop
0127 trace 1 ( 18)
0129 getlocal_OP__WC__0 4
0131 putobject 2
0133 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0136 putobject_OP_INT2FIX_O_1_C_
0137 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0140 getlocal_OP__WC__0 6
0142 putobject 16
0144 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0147 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0150 branchif 157
0152 putself
0153 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0156 pop
0157 trace 1 ( 19)
0159 getlocal_OP__WC__0 4
0161 putobject 2
0163 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0166 putobject 3
0168 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0171 getlocal_OP__WC__0 6
0173 putobject 4
0175 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0178 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0181 branchif 188
0183 putself
0184 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0187 pop
0188 trace 1 ( 20)
0190 getlocal_OP__WC__0 4
0192 putobject 2
0194 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0197 putobject 4
0199 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0202 getlocal_OP__WC__0 6
0204 putobject 16
0206 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0209 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0212 branchif 219
0214 putself
0215 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0218 pop
0219 trace 1 ( 21)
0221 getlocal_OP__WC__0 4
0223 putobject 2
0225 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0228 putobject 5
0230 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0233 getlocal_OP__WC__0 6
0235 putobject 3
0237 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0240 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0243 branchif 250
0245 putself
0246 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0249 pop
0250 trace 1 ( 22)
0252 getlocal_OP__WC__0 4
0254 putobject_OP_INT2FIX_O_0_C_
0255 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0258 opt_send_without_block <callinfo!mid:reverse, argc:0, ARGS_SIMPLE>,
0261 setlocal_OP__WC__0 3
0263 trace 1 ( 23)
0265 getlocal_OP__WC__0 3
0267 putobject_OP_INT2FIX_O_1_C_
0268 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0271 putstring "b"
0273 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0276 branchif 283
0278 putself
0279 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0282 pop
0283 trace 1 ( 24)
0285 getlocal_OP__WC__0 3
0287 putobject 3
0289 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0292 putstring "r"
0294 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0297 branchif 304
0299 putself
0300 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0303 pop
0304 trace 1 ( 25)
0306 getlocal_OP__WC__0 3
0308 putobject 4
0310 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0313 putstring "{"
0315 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0318 branchif 325
0320 putself
0321 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0324 pop
0325 trace 1 ( 26)
0327 getlocal_OP__WC__0 3
0329 putobject 8
0331 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0334 putstring "f"
0336 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0339 branchif 346
0341 putself
0342 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0345 pop
0346 trace 1 ( 27)
0348 getlocal_OP__WC__0 3
0350 putobject 2
0352 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0355 putstring "u"
0357 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0360 branchif 367
0362 putself
0363 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0366 pop
0367 trace 1 ( 28)
0369 getlocal_OP__WC__0 3
0371 putobject_OP_INT2FIX_O_0_C_
0372 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0375 putstring "y"
0377 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0380 branchif 387
0382 putself
0383 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0386 pop
0387 trace 1 ( 29)
0389 getlocal_OP__WC__0 4
0391 putobject_OP_INT2FIX_O_1_C_
0392 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0395 putstring "is"
0397 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0400 branchif 407
0402 putself
0403 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0406 pop
0407 trace 1 ( 30)
0409 getlocal_OP__WC__0 3
0411 putobject 5..7
0413 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0416 putstring "gal"
0418 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0421 branchif 428
0423 putself
0424 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0427 pop
0428 trace 1 ( 31)
0430 getlocal_OP__WC__0 4
0432 putobject 5
0434 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0437 putstring "7d"
0439 newarray 1
0441 putstring "H*"
0443 opt_send_without_block <callinfo!mid:pack, argc:1, ARGS_SIMPLE>,
0446 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0449 branchif 456
0451 putself
0452 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0455 pop
0456 trace 1 ( 32)
0458 getlocal_OP__WC__0 4
0460 putobject 3
0462 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0465 putstring "7468346e"
0467 newarray 1
0469 putstring "H*"
0471 opt_send_without_block <callinfo!mid:pack, argc:1, ARGS_SIMPLE>,
0474 opt_send_without_block <callinfo!mid:upcase, argc:0, ARGS_SIMPLE>,
0477 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0480 branchif 487
0482 putself
0483 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0486 pop
0487 trace 1 ( 33)
0489 getlocal_OP__WC__0 4
0491 putobject 4
0493 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0496 putstring "1"
0498 opt_send_without_block <callinfo!mid:split, argc:1, ARGS_SIMPLE>,
0501 setlocal_OP__WC__0 2
0503 trace 1 ( 34)
0505 getlocal_OP__WC__0 2
0507 putobject_OP_INT2FIX_O_0_C_
0508 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0511 putstring "rub"
0513 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0516 branchif 523
0518 putself
0519 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0522 pop
0523 trace 1 ( 35)
0525 getlocal_OP__WC__0 2
0527 putobject_OP_INT2FIX_O_1_C_
0528 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>,
0531 putstring "ya"
0533 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>,
0536 branchif 543
0538 putself
0539 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>,
0542 pop
0543 trace 1 ( 38)
0545 putself
0546 putstring "success! auth your flag.."
0548 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>,
0551 leave
== disasm: #<ISeq:b@/home/saika/pox/rvm/rvm.rb>=========================
0000 trace 8 ( 1)
0002 trace 1 ( 2)
0004 putself
0005 putstring "what?"
0007 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>,
0010 pop
0011 trace 1 ( 3)
0013 putself
0014 putobject -1
0016 opt_send_without_block <callinfo!mid:exit, argc:1, FCALL|ARGS_SIMPLE>,
0019 trace 16 ( 4)
0021 leave ( 3)
== disasm: #<ISeq:f@/home/saika/pox/rvm/rvm.rb>=========================
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] hex
0000 trace 8 ( 6)
0002 trace 1 ( 7)
0004 getlocal_OP__WC__0 2
0006 putobject /../
0008 opt_send_without_block <callinfo!mid:scan, argc:1, ARGS_SIMPLE>,
0011 opt_send_without_block <callinfo!mid:reverse, argc:0, ARGS_SIMPLE>,
0014 putstring ""
0016 opt_send_without_block <callinfo!mid:join, argc:1, ARGS_SIMPLE>,
0019 trace 16 ( 8)
0021 leave
디스어셈블 한 결과를 원래 ruby 코드로 복구 해 flag 값을 얻어내면 되는 듯 하다.
….하면 되는것이다. ㅇ<-<
맨 윗줄부터 살펴보면 다음과 같다. 코드 내에서 사용하는 변수들이다.
[ 6] f [ 5] i [ 4] s [ 3] t [ 2] tt
선언된 순서대로 f, i, s, t, tt
총 5개의 변수가 사용된다.
디스어셈블리 코드를 전체적으로 훑어보면, 반복적으로 trace와 pop이 나온다. 코드 한 줄의 디스어셈블 결과가 trace-pop 으로 묶여있다. trace와 pop을 기준으로, 코드를 한 줄씩 복구 해 보면 되겠다.
먼저 0000부터 0011까지를 보면 다음과 같다.
0000 trace 1 ( 1)
0002 putspecialobject 1
0004 putobject :b
0006 putiseq b
0008 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>,
0011 pop
#define_method
라는 걸 보아하니, 함수를 정의 한 부분인 것 같다.
ruby로 다음과 같이 짠 후, 컴파일 하고 disasm 해보자.
먼저 대충 아래와 같은 코드를 휘갈기고,
#recover.rb
def b:
#blah
end
컴파일한 후, disasm 해본다.
irb(main):003:0> a = RubyVM::InstructionSequence.compile_file("recover.rb")
=> <RubyVM::InstructionSequence:<main>@recover.rb>
irb(main):004:0> puts a.disasm
== disasm: #<ISeq:@recover.rb>====================================
0000 trace 1 ( 1)
0002 putspecialobject 1
0004 putobject :b
0006 putiseq b
0008 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>,
0011 leave
== disasm: #<ISeq:b@recover.rb>=========================================
0000 trace 8 ( 1)
0002 putnil
0003 trace 16 ( 3)
0005 leave
=> nil
b라는 함수가 뭔 짓을 하는 함수인지는 밑에서 더 분석을 해 봐야 알 수 있을 것 같으니 함수 본체는 비워두고 컴파일 했다. 디스어셈블 한 결과를 보면, 0000부터 0011까지, 끝나는 instruction이 원본 문제는 pop, 내가 recover한 루비코드는 leave인 것만 제외하면 똑같다. 계속 코드로 바꿔보면 알게 되겠지만, 마지막 줄의 코드 외에는 leave 대신 pop으로 끝나는 것을 확인할 수 있으니 크게 신경쓰지 않아도 된다.
여튼. 이렇게 하나씩 분석하여 recover.rb 을 완성해보자.
0012부터 0023까지는 0000부터 0011까지와 같으므로, 원래 코드는 다음과 같을것이다.
#recover.rb
def b
#blah
end
def f
#blah
end
컴파일 한 후 디스어셈블 한 결과는 다음과 같다.
irb(main):003:0> a = RubyVM::InstructionSequence.compile_file("recover.rb")
=> <RubyVM::InstructionSequence:<main>@recover.rb>
irb(main):004:0> puts a.disasm
== disasm: #<ISeq:@recover.rb>====================================
0000 trace 1 ( 1)
0002 putspecialobject 1
0004 putobject :b
0006 putiseq b
0008 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>,
0011 pop
0012 trace 1 ( 5)
0014 putspecialobject 1
0016 putobject :f
0018 putiseq f
0020 opt_send_without_block <callinfo!mid:core#define_method, argc:2, ARGS_SIMPLE>,
0023 leave
0024~0028 는 변수에 “qwertyu7iop1asdf3ghjklzxcvbnm” 라는 문자열을 대입하는 루틴이다.
0024 trace 1 ( 10)
0026 putstring "qwertyu7iop1asdf3ghjklzxcvbnm"
0028 setlocal_OP__WC__0 6
setlocal_OPWC0 6 를 통해 6번째 로컬변수를 putstring 한 값으로 setting 하는 것을 알 수 있다. 6번째 로컬변수는 맨 위의 [ 6] f [ 5] i [ 4] s [ 3] t [ 2] tt 줄을 통해 변수 명이 f 라는 것을 알 수 있고. (우리가 위에서 f라는 이름의 함수를 선언했지만, 일단 신경쓰지 말자.)
다음, 0030부터 0038 까지.
0030 trace 1 ( 12)
0032 putself
0033 putstring ":: saika's auth system ::"
0035 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0038 pop
:: saika’s auth system :: 라는 문자열을 puts 함수로 출력하고 있다.
0039부터 0049까지.
0039 trace 1 ( 13)
0041 getglobal $stdin
0043 opt_send_without_block <callinfo!mid:gets, argc:0, ARGS_SIMPLE>, <callcache>
0046 opt_send_without_block <callinfo!mid:chomp, argc:0, ARGS_SIMPLE>, <callcache>
0049 setlocal_OP__WC__0 5
get global $stdin 을 통해, 글로벌 변수 $stdin에 gets와 chomp 메서드를 사용하여 사용자로부터 입력값을 받은 후, setlocal_OPWC0 5 를 통해 5번째 변수, 즉 i 라는 이름의 변수에 넣는 것을 알 수 있다. 참고로chomp는 문자열 뒤의 공백을 제거 해 주는 메서드라고.python에서 strip()과 같은 메서드라고 생각하면 될테다.
다음 0051부터 0060까지를 보면, split 을 한다.
0051 trace 1 ( 14)
0053 getlocal_OP__WC__0 5
0055 putstring "_"
0057 opt_send_without_block <callinfo!mid:split, argc:1, ARGS_SIMPLE>, <callcache>
0060 setlocal_OP__WC__0 4
getlocal_OPWC0 5, 즉 i 에 있는 값을 가져와서, “_“를 기준으로 split 을 한 후에, setlocal_OPWC0 4, s라는 이름의 변수에 넣는다. 고로, s는 사용자가 입력한 값을 _를 기준으로 split 한 결과값(배열 형태)을 가지고 있게 된다.
여기까지 디스어셈블한 결과를 ruby 코드로 복원하면 다음과 같다.
#recover.rb
def b
#blah
end
def f(hex)
#blah
end
f = "qwertyu7iop1asdf3ghjklzxcvbnm"
puts ":: saika's auth system ::"
i = $stdin.gets.chomp
s = i.split("_")
다음 라인들은 사용자의 입력값이자 split한 결과값들에 대한 조건식들이 나열되어 있다.
비슷한 패턴들이 반복되므로, 하나만 파악하면 나머지는 비슷하기 때문에 쉽게 복원할 수 있다.
0062 trace 1 ( 16)
0064 getlocal_OP__WC__0 4
0066 putobject 2
0068 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0071 putobject_OP_INT2FIX_O_0_C_
0072 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0075 putstring "62"
0077 newarray 1
0079 putstring "H*"
0081 opt_send_without_block <callinfo!mid:pack, argc:1, ARGS_SIMPLE>, <callcache>
0084 opt_eq <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0087 branchif 94
0089 putself
0090 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0093 pop
우선 0087과 0090은 branchif 와 사용자가 생성한 함수 b를 실행하는 라인이 있다. branchif는 94로 가라고 하는데, 0094는 다음 조건식인의 시작( 새로운 trace문) 이다. 그 위의 0084 에서 == 으로 비교하는 라인까지 있으므로, 문자열을 비교하여 맞으면 다음 비교구문으로- 그렇지 않으면 b 함수를 실행하는 조건식이 있겠구나 생각 할 수 있다.
이후의 코드에서도 계속해서 위와 같은 패턴이 나오는데, 여기서 b 함수는 프로그램 종료(exit) 쯤 된다는 것도 유추 해 볼 수 있을 것이다.
위의 루틴을 분석하면서 조금 애먹은 부분은 0071 라인의 putobject_OP_INT2FIX_O_0C 이다. 이후 코드에서도 해당 라인과 비슷한 putobject_OP_INT2FIX_O_10 같은 라인이 등장하고는 한다.
분석 결과, 맨 뒤의 O_0C, O_10 는 각각 배열의 인덱스 중에서도 0번과 1번을 뜻하는 것 같다. 물론, 다른 배열의 인덱스를 지정하고자 할 때는 배열 앞에 인덱스를 나타내는 숫자가 putobject 된다.
논리적으로 0062부터 0093 까지를 ruby syntax에 맞춰 작성할 수 있는 방법은 많겠지만, 여러가지 형태의 코드를 작성 해 보고 나면 다음과 같은 ruby 코드를 디스어셈블 했을 때 문제의 디스어셈블 결과와 같은 것을 확인할 수 있다.
b unless s[2][0] == ["62"].pack("H*")
s[2][0] == ["62"].pack("H*")
를 실행한 결과값이 false 이면 b 함수를 실행하라는 코드 되시겠다.
위와 같은 원리로 0094부터 0551 까지를 복원할 수 있다.
훅 넘어가서 0551 이후의 디스어셈블 결과를 살펴보자.
== disasm: #<ISeq:b@/home/saika/pox/rvm/rvm.rb>=========================
0000 trace 8 ( 1)
0002 trace 1 ( 2)
0004 putself
0005 putstring "what?"
0007 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0010 pop
0011 trace 1 ( 3)
0013 putself
0014 putobject -1
0016 opt_send_without_block <callinfo!mid:exit, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0019 trace 16 ( 4)
0021 leave ( 3)
== disasm: #<ISeq:f@/home/saika/pox/rvm/rvm.rb>=========================
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] hex<Arg>
0000 trace 8 ( 6)
0002 trace 1 ( 7)
0004 getlocal_OP__WC__0 2
0006 putobject /../
0008 opt_send_without_block <callinfo!mid:scan, argc:1, ARGS_SIMPLE>, <callcache>
0011 opt_send_without_block <callinfo!mid:reverse, argc:0, ARGS_SIMPLE>, <callcache>
0014 putstring ""
0016 opt_send_without_block <callinfo!mid:join, argc:1, ARGS_SIMPLE>, <callcache>
0019 trace 16 ( 8)
0021 leave
첫 번째 0000부터 0021 까지는 what?을 출력하고 exit을 한다. 사용자가 정의한 b 함수의 내용이다.
두 번째 0000부터 0021 까지는 사용자가 정의한 f 함수의 내용인 것 같다. hex 라는 argument를 가진 함수이고, hex를 scan하고 reverse하고 join 하는데, 사실상 전체 코드에서 한 번도 호출하지 않기 때문에 굳이 복원하지 않아도 되는 부분이다. 무쓸모
위의 내용을 아울러 전체 문제 코드를 복원하면 다음과 같다. 컴파일 후 디스어셈블 한 결과도 문제와 같다. 신남
def b
puts "what?"
exit(-1)
end
def f(hex)
return hex.scan("/../").reverse().join("")
end
f = "qwertyu7iop1asdf3ghjklzxcvbnm"
puts ":: saika's auth system ::"
i = $stdin.gets.chomp
s = i.split("_")
b unless s[2][0] == ["62"].pack("H*") #b
b unless s[2][2] == ["74"].pack("H*") #t
b unless s[2][1] == f[16] #3
b unless s[2][3] == f[4] #t
b unless s[2][4] == f[16] #3
b unless s[2][5] == f[3] #r
# "b3tt3r"
t = s[0].reverse()
b unless t[1] == "b"
b unless t[3] == "r"
b unless t[4] == "{"
b unless t[8] == "f"
b unless t[2] == "u"
b unless t[0] == "y"
#t = ("f___{ruby").reverse()
b unless s[1] == "is" # "is"
b unless t[5..7] == "gal" # "lag"
b unless s[5] == ["7d"].pack("H*") # }
b unless s[3] == ["7468346e"].pack("H*").upcase() #"TH4N"
tt = s[4].split("1")
b unless tt[0] == "rub"
b unless tt[1] == "ya"
# rub1ya
puts "success! auth your flag.." #flag{ruby_is_b3tt3r_TH4N_rub1ya_}
코드를 잘 해석하면, 입력값으로 flag{ruby_is_b3tt3r_TH4N_rub1ya_}
를 입력해야 모든 조건을 통과하여puts “success! auth your flag..” 를 띄우게 된다.
$ ruby exe.rb
:: saika's auth system ::
flag{ruby_is_b3tt3r_TH4N_rub1ya_}
success! auth your flag..
새로운 것을 빨리 습득해서 근성있게(?) 리버싱 해야 하는 문제였는데, 대회때 왜 저 hitcon writeup 페이지는 눈에 들어오지도 않았는지ㅋㅋ
그러고보니 이 문제 덕에 ruby 언어를 처음 사용해 봤다.
아무튼, 끝!