1. 문제확인





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?


문제를 대충 해석해보면, 

엄마가 패스워드 넣는 로그인시스템을 만들어보라고 말했어~

내 초기 C코드는 에러없이 컴파일이 되었지!

뭐 워닝이 쫌있긴했는데 누가신경쓰겠냐? 라고 하고 있습니다.


우선 주어진 접속정보로 서버에 접속해서 passcode.c를 봐야겠네여



2. 문제 코드 분석


아래는 문제로 주어진 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
44
#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;    
}



2-1) main()

프로그램 실행에 필요한 인자는 특별히 없어보이네여?

가장 먼저, Toddler`s Secure Login System 1.0 beta. 라는 문자열을 출력한 후

welcome, login을 순차적으로 호출 하고

Now I can safely trust you that you have credential :) 출력해주면 프로그램이 끝나고있어여


2-2) welcome()

welcome함수에서는 이름을 입력받기위해 char 배열 100바이트짜리가 하나 선언이 되어 있어여

enter you name : 에 맞추어 이름을 입력하면,

Welcome [입력한 이름]! 을 출력해줍니다. 여기서 %100s로 100바이트만 받는 다는 점만 인지해두세여


2-3) login()

login 함수에서는 패스워드를 입력 받고 인증에 성공하면 플래그를 읽어주는 의도로 만들고 싶었던 것 같은 소스를 볼 수 있어여

꼼꼼하신 분이라면 바로 이상한 점을 아실 수 있겠져? scanf에서 입력을 받는 변수에 &가 빠져있어여 ㅠㅠ

그리고 리눅스에서는 제대로 동작 하지 않는 것으로 알고 있는뎅, passcode1을 입력 받은 다음 입력버퍼를 날려주는 fflush()를 호출하고 있어여


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
    }
}



원래 의도를 예상해서 코드를 수정해보면 이처럼 바꿔볼 수 있겠져?
scanf에서 변수를 저장할 때는 이렇게 해당 변수의 주소값을 인자로 넣어줘야돼여 아마 문제에서 언급했던 워닝은 여기서 났겠네여
게다가 패스워드를 입력받아 저장 하려고했던 passcode1과 passcode2는 초기화 과정을 거치고 있지 않기 때문에 garbage값이 들어 있을꺼에여
결과적으론 이 가비지 값을 주소로 읽어서 거기다가 입력값을 쓰게 될 꺼에여


3. 디버깅


3-1) main()


2-1) 에서 살펴봤듯이 별내용은 없어 보이네여 printf를 실행하기 위한 put을 각각 호출 하고, welcome과 login을 순차적으로 호출하네여?


움..참고로 이번에 알게된 건데 문제서버에서 peda를 사용하고 싶으면 gdb를 실행한 후 source /usr/share/peda/peda.py를 쳐주면 peda가 실행되여ㅠ 넘나 편한 것..


3-2) welcome()

여기서부턴 자세히 봐야 할 부분이 있어여

맨처음 함수 인트로에 해당하는 스택프레임 부분이 +0과 +1라인에서 이루어지고 있어여

바로 다음 +3라인에서는 소스에서 살펴봤던 login함수 내 지역변수인 name[100]을 위한 공간확보를 하고 있네여

0x88만큼 스택을 확보했으니까 dummy까지 합쳐서 136바이트를 확보한 것을 알 수 있져?



실행을 해서 확인을 해보도록 하져



welcome+9에 브레이크 포인트를 건 멈춘 모습이에여

ebp는 0xffb771e8, esp는 0xffb77160으로 (0xffb771e8 - 0xffb77160)0x88만큼 스택 공간이 확보되어있는 것을 확인해 볼 수 있어여

여기까지의 스택 상황을 한번 그려본다면 아래처럼 되겠져?


welcome+48에서 scanf을 호출해서 입력을 받고 welcome+68에서 print를 해주는 부분을 분석해서 정확한 더미부분과 name변수의 공간을

확인해 봅시당!

welcome+68에서 브레이크를 걸고 진행을 해보면,

0xffde9198에 입력한 값(저는 JSBach를 입력했어여)이 위치하고 있는 것을 알 수 있어여, 이 정보를 가지고 스택을 다시 그려보져


확보 된 0x88(136)사이즈가 이처럼 사용되고 있다는 것 까지 알아냈습니다. welcome에서 볼 수 있는 사항은 여기 까지구 login을 보면서

이 내용을 어떻게 써먹을 지! 확인을 해보면 되겠습니당



3-3) login()

login함수도 역시 스택프레임을 구성하는 것으로 시작하고있어여 그리고 passcode1과 passcode2를 위한 공간을 확보하고있져?

0x28만큼 확보하고 있네여 login+6에 브레이크를 잡고 자세히 한번 살펴봅시당



ebp가 0xffde9208이고, esp가 0xffde91e0기때문에 0x28(40바이트)만큼 공간이 확보된 것을 알 수 있습니다.

위와 같이 스택을 똑같이 그려보면?

요렇게 그려 볼 수 있겠져? 근데 잘보면 ebp가 welcome함수랑 똑같아여

welcome함수 콜 후에 바로 login함수를 콜 했기 때문에, 당연한 것일 수 있지만 이게 실마리입니다.


우선 0x28공간이 어떻게 구분되어지는지 확인을 해봐야겠져!

passcode1을 입력을 받는 login+34부분에 브레이크 포인트를 걸고 한번 봅시당


scanf에서 사용하는 첫 번째 인자인 %d가 스택 최상단에 있고, 바로 아래 passcode1가 있겠네여?

이 passcod1의 값을 어떻게 가져왔는지 scanf 호출 전(login+24, +27)을 보면 알 수 있어여


mov edx, DWORD PTR[ebp-0x10]            // edx에 ebp-0x10의 주소에 있는 값을 대입

mov DWORD PTR [esp+0x4], edx            // esp+0x4의 주소에 edx값을 대입


즉, ebp-0x10 -> 0xffde9208 - 0x10 = 0xffde91f8에 있는 값을 가져다가 쓴다는 게 되는거져

아까 welcome에서 봤던 스택 모습을 한번 더 볼까여?


ebp는 welcome과 login이 동일하게 0xffde9208을 쓰고있습니다.

그래서 ebp-0x10 의 위치라면! ebp로부터 16바이트가 떨어진 곳의 값이 되겠네여, 그럼...

name입력에서 끝 4바이트가 된다는 것을 알 수 있습니다.


요기까지 상황을 정리해보겠습니당!


1. scanf에는 메모리에 쓰고싶은 곳의 포인터를 넘겨준다.

2. 그런데 개발자가 실수로 & 기호를 빼먹어서 변수에 있는 값을 포인터로 사용하게되었다.

3. 그 변수에 있는 값은 앞의 함수에서 입력한 값의 영향을 받는다.


이름을 입력할 때 마지막 네 바이트에 입력하고 싶은 메모리 번지를 넣고 그 다음 scanf에서 값을 쓰면

원하는 메모리번지에 값을 넣을 수 있다! 라는 결론 까지 올 수 있습니다.


한번 확인한 사항이 맞는지 테스트 값을 넣어서 확인을 해보겠습니다.


입력값은 A가 96개 B 4개를 넣었습니다. 우리가 확인 한 대로라면 scanf의 두 번째인자에는 0x42424242가 들어가 있어야되여...



확인한 대로 잘들어가있네여 ㅠㅠ 


그런데 이걸 가지고 어떻게 익스플로잇 할 수 있을까여??

소스 상에서는 passcode1과 passcode2의 값이 모두 일치해야 조건을 만족하고 플래그를 읽어주는데...

결과부터 말씀드리면 passcod1을 입력받은 후 fflush함수를 호출 할 때 GOT의 주소를 바꿔줌으로써 플래그를 읽어볼 수 있습니다.


PLT / GOT가 무엇인지 자세히 알고 싶으신 분은 아래 링크를 참조 하세여!


PLT와 GOT 자세히 알기1

https://bpsecblog.wordpress.com/2016/03/07/about_got_plt_1/


PLT와 GOT 자세히 알기2

https://bpsecblog.wordpress.com/2016/03/09/about_got_plt_2/


[참조] https://bpsecblog.wordpress.com/


문제 풀이에 필요한 정도로만 간략히 설명을 드리자면

동적 라이브러리를 사용할 때 프로그램 내에 임포트한 라이브러리의 함수가 없기 때문에 (여기서는 fflush함수가 되겠습니다.)

해당 함수의 주소를 얻어 올 수 있는 테이블들입니다.


라이브러리 함수를 호출하게 되면 먼저 plt로 간 후, got로 점프를 뛰고 got에서 라이브러리의 함수 주소를 얻어오는 구조가 되겠습니다.


우리는 이 부분에서 fflush함수 호출 할 때, plt에서 got로 점프를 뛰는 부분의 주소를 login+127의 주소(system("/bin/cat flag"))로 바꾸어

익스플로잇을 시도해볼 수 있습니다.


그럼 수정해야 될 주소를 확인해 봅시당!

0x08048593 <+47>: call   0x8048430 <fflush@plt>


여기가 최초 plt테이블로 뛰는 부분이에여, 저 안에 가면 got로 점프 뛰는부분이 있을거에여


첫 줄에 바로 점프뛰는 부분을 보면 0x804a004로 점프를 뛰고 그 주소를 보니깐! got로 뛰어가고있는 모습을 볼 수 있네욥

여기서 우리가 바꿀 주소가! 바로 0x804a004에요, 여기다가 login+127의 주소인 0x080485e3을 넣어 줄꺼랍니다.



4. 페이로드 작성

작성해야될 페이로드를 프로토 타입으로 한번 보면 요렇게 될꺼에여

NOP 96개 + got로 뛰는 주소 + login+127의 주소


결과부터 보면 


(python -c 'print "\x90"*96+"\x04\xa0\x04\x08"+"134514147"') | ./passcode 이렇게 되겠습니다.


이제 찬찬히 또 뜯어봅시당


프로그램에 입력을 넣어 주는 부분은 파이썬을 사용했어여

NOP에 해당하는 \x90이 96개 들어갔습니당, 그리고 리틀엔디언 방식으로 주소를 때려박아야하니깐 한바이트씩 역순으로!

0x804a004 -> \x04\xa0\x04\x08이 되는거고여


그리고 scanf에서 포맷이 %d 즉, 정수형으로 받고있기 때문에~

0x080485e3을 십진수로 변환해준 134514147을 넣어줬어여


근데 뭐 이상한거 없으신가여? 제가 초보자여서 그런지 여기서 헤맸어여 ㅠㅠ

name[100]에 100바이트가 넘는 문자열을 입력하면 오버플로우가 안나나? 하는 생각을 했더랬져


제가 혹시 위에서 얘기했던 %100s 기억나시나요? 저렇게 사이즈를 정해줌으로써 딱 100바이트만 입력받고 100바이트이상 넘치는 문자열은

다음 인풋으로 넘어가더라고여..ㅎ;;;


그래서 정리해보면~


처음 이름을 입력하는 곳에 NOP + 0x804a004까지 들어가고 그 다음 passcode1을 입력하려고했던 로직에

0x080485e3이 입력이 되서 플래그를 읽을 수 있었습니다~







'Study > Pwnable' 카테고리의 다른 글

[HITCON Training] lab4 / return to library  (0) 2017.11.29
[HITCON Training] lab2 / shellcraft  (0) 2017.11.28
NX(No eXcutable) / ROP  (1) 2017.09.29
pwnable.kr / input / pwntools  (0) 2017.08.01
PEDA / pwnable.kr bof문제  (0) 2017.07.12

+ Recent posts