ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [pwnable.kr][Toddler's Bottle] 5. passcode
    Wargame/pwnable.kr 2019. 7. 19. 22:35

    Toddler's Bottle - passcode

     

    문제에서 passcode 기반의 로그인 시스템을 만들었는데, 컴파일 시에 오류는 없었지만 경고가 있었다고 한다. 아마 이 경고 때문에 로그인 시스템에 취약점이 있는 듯하다.

    ID: passcode, pw: guest로 로그인한 다음 파일을 확인했더니 아래와 같이 3개의 파일이 있었다.

     

    ls

     

    passcode 파일을 실행해보았다. 실행하면 이름을 먼저 입력하고, passcode1을 입력할 수 있다. 그런데 passcode2는 입력하라는 문구가 있는데도 입력이 되지 않고 바로 checking으로 넘어갔다.

     

    Login Failed!

     

    또한 passcode1에 숫자만 입력하면 Segmentation Fault가 일어났다.

     

    Segmentation fault

     

    원인을 알아보기 위해 passcode.c를 확인해보았다.

     

    passcode.c - login
    passcode - welcome, main

     

    main에서는 먼저 welcome 함수를 통해 사용자 이름을 입력받고, login 함수를 통해 로그인을 수행한다. welcome 함수는 단순히 이름만 입력받을 뿐이고, 입력받는 크기도 100 bytes로 고정되어 있기 때문에 BOF가 발생할 소지도 따로 없어서 login 함수를 살펴보았다.

     

    login 함수에서는 int형 변수인 passcode1과 passcode2를 scanf로 입력받는다.

     

    login 함수

     

    그런데 passcode1, passcode2 모두 초기화가 되어있지 않고, scanf 함수에도 앰퍼샌드(ampersand, &)가 없다. 앰퍼샌드(&)가 없기 때문에 사용자가 passcode1의 값을 입력하면, 입력값이 passcode1 변수에 저장되지 않고 passcode1라는 주소에 저장되어 버린다. 따라서 입력값을 숫자로 입력해버리면 passcode1이라는 쓰레기 주소값에 입력값이 들어가 Segmentation Fault가 발생한다.

     

    결국 passcode1과 passcode2 모두 주소값이 쓰레기값이기 때문에, 이 부분에서 입력값을 조작해도 프로그램을 내가 원하는 방향으로 조작할 수 없다. 즉, 실질적으로 내가 원하는 방향으로 입력값을 조작할 수 있는 부분은 welcome 함수의 name밖에 없으므로, 먼저 welcome 함수를 gdb로 살펴보았다.

     

    disas welcome

    <welcome+38>부터 살펴보면 eax와 edx가 scanf와 printf의 인자로 사용되는 것을 알 수 있다. 그 중 edx에 저장된 [ebp-0x70]은 scanf를 통해 받은 입력값의 시작 주소라고 추측할 수 있다. 즉, [ebp-0x70]은 name[100] 배열의 시작 주소인 것이다.

     

    scanf 이후부터 breakpoint를 걸고 실행한 후, 100 bytes의 문자열을 입력값으로 주어 스택을 확인해보면 더 확실히 알 수 있다.

     

    [ebp-0x70]부터 [ebp-0xc]까지 문자열이 삽입된 것을 알 수 있다. 따라서, 우리가 입력값을 조작할 수 있는 영역은 [ebp-0x70] ~ [ebp-0xc] 사이의 영역이다.

     

    값을 넣을 수 있는 영역은 찾았지만, 정확히 어떤 값을 넣어야할 지는 모르겠어서 이번에는 login 함수를 디버깅해보았다.

    disas login

    첫번째 scanf의 인자가 [ebp-0x10]인 것으로 보아, passcode1의 주소가 [ebp-0x10]임을 알 수 있다. 그런데 방금 전 welcome 함수에서 name 배열이 [ebp-0x70]에서 [ebp-0xc] 사이에 들어간다는 것을 확인했는데, passcode1의 주소가 그 범위 안에 들어간다.

    즉, name 배열의 맨 마지막 4 bytes(name[97] ~ name[100])는 passcode1의 주소가 들어가는 것이다. 

     

     

    따라서 name의 100 bytes 중 96 bytes는 아무 문자열이나 입력하고, 마지막 4 bytes에는 원하는 주소값을 입력하면 내가 원하는 방향으로 프로그램을 실행할 수 있다.

     

    그렇다면 마지막 4 bytes 부분에 어떤 값을 넣어야 할지 알아보자.

     

    login 함수의 if문을 다시 보면, passcode1이 338150이고 passcode2가 13371337이면 flag가 실행된다. 하지만 passcode2를 입력받는 부분에는 fflush(stdin)이 없어서 입력버퍼가 지워지지 않았기 때문에, passcode2에 원하는 입력값을 줄 수가 없다. 즉, if문의 조건을 만족시켜 flag를 실행하는 것은 아쉽지만 불가능하다.

     

    login 함수

    결국 flag를 실행시키기 위해서는 system("/bin/cat flag")의 시작 주소를 알아내서, 해당 주소로 점프할 수 있도록 passcode1을 조작해야 한다. 따라서 scanf("%d", passcode1)까지는 프로그램이 정상적으로 실행되고, 바로 다음 코드인 fflush(stdin)가 시작되는 부분, 즉 fflush 함수가 호출되는 부분에 system("/bin/cat flag")의 시작 주소를 덮어씌운다면 fflush 함수 대신 system 함수가 실행되어 flag가 나타날 것이다.

     

    위의 방식을 사용하기 위해서는 PLT와 GOT의 개념을 알아야 한다. (https://g-idler.tistory.com/57)

     

    fflush 함수가 호출되면 fflush 함수의 PLT로 이동한다. 그리고 그 PLT에서 GOT을 찾는데, 만약 함수가 처음 호출된 것이 아니면 GOT에 실제 함수의 주소가 적혀있고, 처음 호출되었다면 다시 PLT로 이동하여 실제 함수의 주소를 찾는 과정이 수행될 것이다. passcode1의 입력값을 받을 때에는 fflush 함수가 처음 호출된 것이므로, fflush의 GOT에는 해당 함수의 실제 주소가 없을 것이다.

     

    이러한 점을 이용하여, fflush 함수의 GOT에 system("/bin/cat flag")의 시작 주소를 덮어씌운다면 fflush 함수가 아닌 system 함수가 호출되어 flag가 나타날 것이다.

    다시 말하면, passcode1의 주소가 저장되어 있는 name[97]~name[100] 부분에 fflush 함수의 GOT 주소를 삽입하면 scanf("%d", &fflush)가 되고, scanf를 통해 fflush의 GOT에 system("/bin/cat flag")의 시작 주소를 삽입하면 바로 뒤의 코드인 fflush(stdin)이 수행될 때 GOT에 삽입한 해당 주소로 점프하여 flag가 실행될 것이다.

     

    따라서 최종 페이로드는 아래와 같이 된다.

     

    최종 payload

     

    그럼 먼저 fflush의 GOT 주소를 찾아보자.

    fflush 함수에 breakpoint를 걸고 실행한다.

     

    login 함수 내에서 fflush 함수 호출 부분

    실행한 후 <fflush@plt>의 주소인 0x8048430을 확인해보면, 0x804a004로 점프하라는 명령어가 있다. PLT는 GOT을 참조하므로 이 0x804a004가 GOT 주소이다. 해당 주소를 x/x로 확인해보면 <fflush@got.plt> 문구를 확인할 수 있다. 아직 fflush 함수가 호출되기 전이기 때문에, GOT에 저장된 주소는 PLT+6의 주소이다.

     

     

    GOT 주소를 알았으니 이제 system("/bin/cat flag")의 시작 주소를 찾아보자.

    <login+134>에서 system 함수가 호출되고, 바로 전 코드인 <login+127>에서 system 함수의 인자가 전달된다. 따라서, system("/bin/cat flag")의 시작 주소는 0x080485e3이다.

    login 함수 내의 system 함수 호출 부분

    인자로 전달된 주소를 x/s로 확인해보면 system 함수의 인자가 들어있는 것을 확인할 수 있다.

    system("/bin/cat flag")의 시작 부분

     

    이제 fflush의 GOT 주소와 system("/bin/cat flag")의 시작 주소를 모두 찾았으니 페이로드를 작성해보자. 이때 주의할 점은, passcode1은 10진수 int형이기 때문에 system("/bin/cat flag")의 시작 주소를 10진수 형태로 바꿔서 넣어야 한다.

    (0x080485e3 = 134514147)

     

    (python -c 'print "a"*96 + "\x04\xa0\x04\x08" + "134514147"') | ./passcode

     

    flag: Sorry mom.. I got confused about scanf usage :(

    [참고 자료]

    scanf와 앰퍼샌드(&): https://security-nanglam.tistory.com/194

    PLT와 GOT: https://g-idler.tistory.com/57

     

     

Designed by Tistory.