해당 포스팅은 드림핵의 강의를 정리한 글이다. 링크
밑에 있는 코드의 실행 파일에서 쉘을 따는 예제이다. buf의 주소가 주어지고 (아직 ASLR을 배우지 않은 상태이므로) buf와 rbp 사이의 거리가 주어진다. 따라서 gdb를 통해서 buf와 sfp간의 거리를 직접 찾아줄 필요가 없어졌다! 버퍼 오버플로우를 적당히 잘 일으켜서 카나리 값을 읽은 다음, 그 카나리 값을 카나리 위치에 잘 넣어서 __stack_chk_fail 함수를 건드리지 않으면서 ret 주소까지 overwrite 해주면 된다.
1 |
|
1. 카나리 읽기.
buf, canary, sfp, ret
이런 식으로 스택이 구성이 되어 있다. 우리는 각각이 얼마만큼의 크키를 차지하는지를 알아내고, 그 자리에 맞게 buffer overflow를 일으켜 카나리를 읽으면 된다. 여기서 좋은 점은 buf와 rbp(결국 sfp를 말한다) 사이의 거리를 주었다는 점이다. 64비트이므로 카나리의 크기는 8바이트임을 알고 있기 때문에, buf와 rbp 사이의 거리를 알고 있으면 buf와 sfp 사이의 거리 또한 알 수 있다. 따라서 우리는 저 distance between buf and $rbp 뒤에 오는 거리 값을 받아서, 거기서 8을 빼서 buf와 canary 사이의 거리를 구한 다음, 그만큼을 아무 값으로 채워 주면(오버플로우) 카나리를 읽을 수 있다.
1 |
|
2. return address 까지 덮기
위에서 카나리를 구했으므로, buf, canary, sfp, ret을 덮는 과정에서 canary를 보존하면서 ret 주소를 덮어주면 된다. 여기서 return 주소를 쉘을 실행하는 함수로 덮는 게 아니라, buf에 쉘코드를 주입한 후 ret 자리에 buf 주소를 넣어서 쉘코드가 실행되도록 하는 방법을 이용한다. 따라서 우리는
buf : buf2cnry만큼을 쉘코드로 덮고 나머지는 A로 채운다
canary : 위에서 구했으므로 그대로 넣어준다
sfp : 그냥 8바이트만큼 A로 채운다
ret : buf의 주소를 넣는다
이런 식으로 페이로드를 작성하면 된다.
1 |
|
따라서 전체 코드는 다음과 같다.
1 |
|