5. passcode - Pwnable.kr 해설

2020. 3. 12. 21:21문제연습/Pwnable.kr (PWN)

반응형

 

티스토리 블로그는 PC 환경에 최적화되어 있습니다.

모바일 유저분들은 아래 네이버 블로그를 이용해 주세요.

ICMP의 블로그 : 네이버 블로그

사이버 보안에 관심이 많은 ICMP입니다. 우리 서로 보안에 관한 다양한 주제로 이야기를 나누어 봅시다.

blog.naver.com

 

 

 

안녕하세요, ICMP입니다.

이번 시간에는 Pwnable.kr의 다섯 번째 문제 passcode를 풀어보도록 하겠습니다.

 

 

문제 내용

Mommy told me to make a passcode based login system.

My initial C code was compiled without any error!

Well, there was some compiler warning, but who cares about that?

 

ssh passcode@pwnable.kr -p2222 (pw:guest)

 

들어가기 전

What is GOT and PLT?

이번 문제는 GOT와 PLT의 개념을 이해해야 풀 수 있습니다.

후반부에 다룰 ROP 메모리 보호 기법을 우회하기 위한 공격 기법에서도 응용되니 반드시 숙지해 주시길 바랍니다.

PLT & GOT

PLT ( Procedure Linkage Table ) 실제 호출 코드를 담고 있는 테이블로서, 해당 내용을 참조하여 "_dl_runtime_resolve"가 수행되고, 실제 시스템 라이브러리 호출이 이루어지게 된다. => 쉽게 말하면 외부 라이..

bob3rdnewbie.tistory.com

 

문제 해설

리눅스 터미널 화면을 열어서ssh passcode@pwnable.kr -p2222로 접속하고 pw인 guest를 입력하여 시작합니다.

​ ls 명령어를 이용해서 디렉터리 내용을 확인해보니 (터미널 창에 ls 입력 후 엔터) passcode passcode.c flag 세 가지가 존재합니다.

​우선 확장자가. c인 passcode.c 파일을 읽어 봅시다. (터미널 창에 cat passcode.c를 입력 후 엔터)

그럼 아래와 같은 소스코드가 보일 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
 
void login() {
    int passcode1;
    int passcode2;
 
    printf("enter passcode1 : ");
    scanf("%d", passcode1); //???
    fflush(stdin);
 
    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
    scanf("%d", passcode2); //???
 
    printf("checking...\n");
    if (passcode1 == 338150 && passcode2 == 13371337) {
        printf("Login OK!\n");
        system("/bin/cat flag");
    }
    else {
        printf("Login Failed!\n");
        exit(0);
    }
}
 
void welcome() {
    char name[100];
    printf("enter you name : ");
    scanf("%100s", name); //???
    printf("Welcome %s!\n", name);
}
 
int main() {
    printf("Toddler's Secure Login System 1.0 beta.\n");
 
    welcome();
    login();
 
    // something after login...
    printf("Now I can safely trust you that you have credential :)\n");
    return 0;
}
 

소스 코드의 기능은 간단합니다.

name과 passcode1, passcode2를 입력받아 각각338150, 13371337과 비교한 후 일치하면 system 함수를 통해 flag를 출력합니다.

 

그런데 여기서 이상한 부분이 있습니다. (제가 표시한 주석 부분을 확인해 주세요. //???)

 

scanf 함수를 사용할 때는 입력받는 변수의 주소가 인자로 들어가야 합니다.

즉, scanf("%d", &passcode1) 이런 형식입니다. 그런데 위 소스 코드에서 확인해보니 &가 빠져 있습니다.

만약 이 코드를 visual stdio에 넣으면 다음과 같은 오류와 경고가 발생합니다.

 

 

즉, scanf에 들어갈 인자가 변수의 주소가 들어가지 않았다는 오류(C4477), scanf 함수가 안전하지 않다는 경고 이 두 가지가 표시되었습니다.

 

scanf 함수의 경우 입력된 데이터의 길이 검사가 없기 때문에 오버플로 취약점을 가진 함수입니다.

이런 문제 때문에 경고를 발생시키는 것 같습니다.

아래는 오버플로 취약점을 가진 대표적인 함수입니다.

strcat(), strcpy(), gets(), scanf(), sscanf(), vscanf(), vsscanf(), sprintf(), vsprintf(), gethostbyname(), ...

 

일단 한번 passcode 파일을 실행해 보도록 하겠습니다.

 

 

정상적으로 입력을 받고 passcode2를 입력받기 전에 Segmentation fault (core dumped)을 출력하고 프로그램이 종료되어 버립니다.

 

여기서 우리가 좀 더 분석해 봅시다.

scanf("%d", &passcode1)는 passcode1라는 변수의 주소에 입력된 값을 저장한다는 뜻입니다.

그런데 소스 코드에서는 scanf("%d", passcode1)이라고 하면 변수 이름 "passcode" 자체를 주소로 인식하게 됩니다.(이 부분에 대해서 정확한 내용을 알고 계신 분은 댓글 부탁드립니다.)

 

즉 컴파일러는 passcode라는 주소?로 가서 표준 입력을 통해 얻은 input을 저장하자.라고 인식하게 됩니다.

 

Segmentation fault (core dumped)에 대한 내용은 아래 링크를 이용해 주세요.

[Ubuntu][해결중] segmentation fault (core dumped) error 원인

segmentation Fault는 세그멘테이션 위반, 세그멘테이션 실패라고도 하며 세그폴트로 줄여서 쓰기도 한다. 세그멘테이션 결함의 주된 원인은 다음과 같다. 1. 프로그램이 허용되지 않은 메모리 영역에 접근 시도 2..

ho-j.tistory.com

이제 공격을 위해 시나리오를 짜야 합니다. 힌트는 입력을 받는 scanf 함수, plt, got입니다.

 

 

만약 여기까지 보고 방법이 떠오르셨다면 아래 내용을 보지 말고 한 번 더 다시 풀어보세요!!!

그럼 본격적으로 문제를 풀어보도록 하겠습니다.

 

 

앞서 plt & got에 대해 언급했으나 다시 한번 설명해 드리겠습니다.

 

PLT(Procedure Linkage Table)

PLT는 일종의 실제 호출 코드를 담고 있는 테이블로써 이 내용 참조를 통해 _dl_runtime_resolve가 수행되고, 실제 시스템 라이브러리 호출이 이루어지게 됩니다..

 

GOT(Global Offset Table)

GOT는 PLT가 참조하는 테이블로써 프로시져들의 주소를 가지고 있습니다. PLT가 어떤 외부 프로시져를 호출할 때 이 GOT를 참조해서 해당 주소로 점프하게 됩니다.

출처:https://bbolmin.tistory.com/33[bbolmin]

 

 

출처:  https://bbolmin.tistory.com/33 [bbolmin]

 

제가 plt와 got를 언급한 이유는 위 사진에 나와 있습니다.

scanf로 값을 입력하게 되는데 만약 우리가 system 함수의 실제 주소를 찾고 passcode에 존재하는 함수 중 한 가지 plt를 조작하여 system 함수를 호출해 버리면 될 것 같습니다.

 

1. scanf를 통해 입력 내용이 저장되는 passcode1의 위치에 fflush got 주소를 넣어 바로 fflush 함수를 호출시킨다.

 

 

위 사진의 표시된 곳을 집중해서 보면, 처음 name이라는 배열에 100byte 내용을 저장할 공간이 존재해야 하나, 어셈블리 코드로 계산한 결과 0x70 - 0x10 = 0x60, 즉 96byte의 공간만 있습니다.

 

여기서 scanf를 이용하여 원하는 주소를 집어넣어 jump 시키는 것이 가능합니다.

96byte를 쓰레기 값으로 채워 넣고 나머지 4byte를 fflush의 got 주소를 넣어서 바로 fflush 함수를 호출 시킵시다.

 

함수의 got 주소를 찾는 방법은 plt 주소에서 참고하는 주소를 찾아 계속 들어가면 got 주소가 나옵니다.

(이유는 함수도 실행을 위해서는 실제 주로를 참조할 필요가 있기 때문입니다.)

 

 

fflush 함수의 got 주소를 찾았습니다. (0x804a004)

 

 

2. fflush 함수 got 주소를 login의 system 함수 got 주소로 변조시킵니다.

위에서 fflush 함수의 got 주소를 찾는 방법과 같습니다.

 

 

system 함수의 got 주소는 0x804a010입니다.

scanf 함수는 %d로 인자를 받기 때문에 system 함수의 got 주소를 10진수로 바꿀 필요가 있습니다.

0x804a010 =134520848

 

그럼 위 내용을 모두 정리하여 코드를 짜면 다음과 같습니다.

(python -c 'print "D"*96 + "\x04\xa0\x04\x08" + 134520848'; cat) | ./passcode

 

아래 flag가 출력된 모습입니다.

 

 

이번 문제는 제 기준에서는 많이 어려웠고 제대로 이해를 못한 부분도 있습니다.

이 문제는 계속해서 분석, 공부할 가치가 있는 문제입니다.

 

다음 시간에는 random해설을 진행하도록 하겠습니다..

이상 ICMP였습니다!!

 

 

반응형