[소스코드 1]
- Buffer Overflow공격을 진행하기 전에 우선 아래의 소스코드를 분석해본다.
- Callee인 Func함수에서의 스택 구조는 아래와 같다.
0012FB68 |
BUF[4] 공간 |
=> | Test |
0012FB6C |
EBP |
=> | sta |
0012FB70 |
RET |
=> | ck B |
0012FB74 |
|
| uffe |
0012FB78 |
|
| r Ov |
0012FB7C |
|
| er F |
0012FB80 | low | ||
0012FB84 | 0000 | ||
0012FB88 | 0000 | ||
... | ... |
- memcpy함수를 실행하면 EBP는 물론 RET까지 attack배열의 문자열로 덮어 버린다. 즉, memcpy함수는 Buffer Overflow에 취약한 함수임을 알 수 있다.
[소스코드 2]
- 그렇다면 RET값을 임의의 값으로 변경할 수 있지 않을까? 라는 의문이 든다. AAAA라는 값으로 RET값을 변경시켜 보자.
0012FB68 | BUF[4] 공간 | => | Test |
0012FB6C | EBP | => | sta |
0012FB70 | RET | => | ck B |
0012FB74 |
|
| uffe |
0012FB78 |
|
| r Ov |
0012FB7C |
|
| er F |
0012FB80 | low | ||
0012FB84 | 0000 | ||
0012FB88 | 0000 | ||
... | ... |
- 소스코드를 수정하고 확인해보면 스택구조는 아래와 같다.
0012FB68 | BUF[4] 공간 | => | Test |
0012FB6C | EBP | => | sta |
0012FB70 | RET | => | AAAA |
0012FB74 |
|
| uffe |
0012FB78 |
|
| r Ov |
0012FB7C |
|
| er F |
0012FB80 | low | ||
0012FB84 | 0000 | ||
0012FB88 | 0000 | ||
... | ... |
[소스코드 3]
- RET도 내가 원하는 임의의 값으로 변경해봤다. 그러면 RET에 내가 작성한 코드의 시작 주소를 넣어 실행시킬 수 있도록 하려면 어떻게 해야 할까? 일단 "BBBBCCCCDDDD"가 내가 작성한 쉘코드라고 가정하고 스택에 삽입한다.
- 스택 공간은 아래와 같이 구성되어 있을 것이다.
0012FB68 | BUF[4] 공간 | => | Test |
0012FB6C | EBP | => | sta |
0012FB70 | RET | => | AAAA |
0012FB74 |
|
| BBBB |
0012FB78 |
|
| CCCC |
0012FB7C |
|
| DDDD |
0012FB80 | low | ||
0012FB84 | 0000 | ||
0012FB88 | 0000 | ||
... | ... |
[소스코드 4]
- "BBBBCCCCDDDD"라는 쉘코드가 스택에 삽입 되었으니 이번에는 쉘코드를 실행시키기 위해서 RET값을 쉘코드의 주소로 바꿔줘야 한다.
쉘코드의 주소는 표에서 나타나있듯이 0012FB74이다. 따라서 RET에 0012FB74를 넣어주면 되는데 리틀 엔디안 방식이기 때문에 역순으로 넣어주면 된다.
[소스코드 5]
- [소스코드 4]에서 쉘코드의 주소를 직접 넣어주었다. 하지만 다른 코드에서도 주소가 같을 것이라는 보장이 없다. 어디서나 RET에 쉘코드의 주소를 삽입하도록 할 수는 없을까?
RET할때 ESP값으로 JMP한다면 어떤 코드에서든지 쉘 코드를 실행시킬 수 있을 것이다. 즉, RET값을 JMP ESP가 실행되도록 바꿔줘야한다.
- 그렇다면 JMP ESP를 쓰고 있는 주소를 확인해보자. 일단 현재의 프로그램에서는 JMP ESP를 쓰고 있지 않으므로 모듈에서도 확인해주기 위해 alt+e키를 눌러준다. 그러면 아래와 같이 창이 뜨는데
여기서 찾아볼 모듈의 이름은 선택한 후 엔터를 눌러 들어간 후 JMP ESP가 있는지 찾아본다.
- 그러면 ntdll모듈에서 아래와 같이 JMP ESP주소를 확인할 수 있을 것이다.
- 알아낸 JMP ESP주소를 RET값에다가 넣어준다. 그러면 JMP ESP명령어가 있는 곳으로 RET할 것이고 JMP ESP 명령어가 실행되어 쉘코드가 있는 곳으로 JMP할 것이다.
[소스코드 6]
- 아래는 stdcall방식이지만 [소스코드 5]와 같은 결과가 나온다. 쉘코드가 들어가는 위치를 바꾸어 주었다.
- 이제 쉘코드가 실행되도록 RET를 변조해봤으니 쉘코드를 직접 작성해본다.
아래와 같이 새로운 프로젝트를 만든다.
- 프로젝트를 만든 후 FileView를 클릭한다. 작업은 shellcode.cpp에서 할 것이다.
[소스코드 7]
- 아래와 같은 코드를 작성하고 실행해본다.
- 그러면 아래와 같이 cmd창이 뜬다.
- 해당 코드의 실행파일을 ollydbg로 연다.
아래와 같이 main함수의 패턴을 찾아서 main함수로 따라들어간다.
- 블록 지정한 부분을 복사한다.
- 아까 작성한 소스코드를 주석처리하고 아래와 같이 복사한 어셈블리어를 붙여넣고 밑줄친 부분처럼 적당히 수정해준다. (참고로 EBP-8이 아닌 EBP-C라면 C앞에 0x를 붙여 16진수인 것을 표현해야 한다. 다른 것도 마찬가지이다.)
- 그리고 다시 실행해보면 아까와 같이 cmd창이 뜬다.
- 다시 이 코드의 실행파일을 ollydbg로 열어준다.
아까와 마찬가지로 main함수로 들어가서 아래의 블록지정한 부분을 복사하는데 복사할 때는 마우스 오른쪽버튼 Binary-Binary copy로 복사한다.
- 복사한 값을 메모장에 붙여넣으면 아래와 같이 넣고 공백 부분을 \x로 채워준다.
- [소스코드 5]의 "BBBBCCCCDDDD"을 지우고 만든 쉘코드를 넣어준다.
[소스코드 8]
- [소스코드 7]에서 작성한 코드를 실행해보면 cmd창은 정상적으로 뜨지만 main함수가 정상적으로 종료되지 않아서 아래와 같은 창이 뜬다.
- 이 창을 뜨지 않도록 하려면 [소스코드 5]에 ExitProcess(0);을 추가해준다.
- 이번에도 [소스코드 7 ]과 같은 방식으로 쉘코드를 만들면 된다.