스택의 반환 주소를 덮는 공격은 스택 카나리, NX, ASLR이 도입되며 점점 어려워졌다. 공격 기법은 셸 코드의 실행에서 라이브러리 함수의 실행으로, 그리고 다수의 리턴 가젯을 연결해서 사용하는 Return Orinted Programming(ROP)로 발전했다.
NX의 도입으로 셸 코드를 사용할 수 없게 된것은 공격자에게 큰 제약이 되었다. 프로그래서가 작성한 코드만 실행해서 셸이 획득될 가능성은 거의 없는데, 임의의 코드를 주입해서 사용할 수도 없어졌으므로 공격자는 새로운 방법을 찾아야했다.
그래서 지난코스 RTL에서 살펴본 것과 같이 pop rdi; ret
같은 코드 가젯과 라이브러리의 system
함수를 사용하는 공격 기법이 새롭게 등장했다.
지난 코스에서 관련된 실습을 진행했는데, 사실 해당 실습에선 편의를 위해 바이너리의PLT에 system
함수를 포함시켰다. 그러나 이제는 많은 개발자가 해당 함수가 공격벡터로 사용될 수 있음을 알고 있으며, 여러 개발 도구들에서도 해당함수의 사용을 지양하도록 경고하고 있다. 그러므로 실제 바이너리에서 system
함수가 PLT에 포함될 가능성은 거의 없다.
따라서, 현실적으로 ASLR이 걸린 환경에서 system
함수를 사용하려면 프로세스에서 libc가 매핑된 주소를 찾고, 그 주소로부터 system
함수의 오프셋을 이용하여 함수의 주소를 계산해야 한다. ROP는 이런 복잡한 제약사항을 유연하게 해결할 수 있는 수단을 제공한다.
ROP는 리턴 가젯을 사용하여 복잡한 실행 흐름을 구현하는 기법이다. 공격자는 이를 이용해서 문제 상황에 맞춰 return to library, return to dl-resolve, GOT overwrite 등의 페이로드를 구성할 수 있다. 지난 코스에서 pop rdi; ret
를 사용하여 system("/bin/sh")
을 호출한 것도 ROP를 이용하여 return to library를 구현한 예다.
ROP 페이로드는 리턴 가젯으로 구성되는데, ret단위로 여러 코드가 연쇄적으로 실행되는 모습에서 ROP chain이라고 불린다.
카나리 우회
이 전과 같다.
system 함수의 주소 계산
system
함수는 libc.so.6에 정의 되어 있으며, 해당 라이브러리에는 이 바이너리가 호출하는read
,puts
,printf
도 정의 되어 있다. 라이브러리 파일은 메모리에 매핑될 때 전체가 매핑되므로, 다른 함수들과 함께 system
함수도 프로세스 메모리에 같이 적재된다.
바이너리가 system 함수를 직접 호출하지 않아서 system
함수가 GOT에는 등록되지 않는다. 그러나 read
, puts
, printf
는 GOT에 등록되어 있다. main
g함수에서 반환 될 때는 이 함수들을 모두 호출한 이후이므로, 이들의 GOT를 읽을수 있다면 libc.so.6가 매핑된 영역으리 주소를 구할수 있다.
libc에는 여러 버전이 있는데 같은 libc안에서 두 데이터 사이의 거리(Offset)는 항상 같다. 그러므로 사용하는 libc의 버전을 알 때, libc가 매핑된 영역의 임의 주소를 구할 수 있으면 다른데이터의 주소를 모두 계산할 수 있다.
예를 들어, Ubuntu GLIBC 2.27-3ubuntu1.2에서 read
함수와 system
함수 사이의 거리는 항상 0xc0ca0
입니다. 따라서 read
함수의 주소를 알 때, system=read-0xc0ca0
으로 system
함수의 주소를 구할 수 있습니다.
rop.c에서는 read
, puts
, printf
가 GOT에 등록되어 있으므로, 하나의 함수를 수정해서 그 함수의 GOT 값을 읽고, 그 함수의 주소와system
함수 사이의 거리를 이용해서 system
함수의 주소를 구할 수 있다.
"/bin/sh"
이 바이너리는 데이터 영역에"/bin/sh" 문자열이 없다. 따라서 이 문자열을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일에 포함된 것을 사용해야 한다. 후자의 방법을 선택할 때 많이 사용되는 것이 libc.so.6에 포한된 "/bin/sh" 문자열이다. 이 문자열의 주소도 system
함수의 주소를 계산할 때처럼 libc 영역의 임의 주소를 구하고, 그 주소로부터 거리를 더하거나 빼서 계산할 수 있다. 이방법은 주소를 알고 있는 버퍼에 "/bin/sh"를 입력하기 어려울때 차선책으로 사용될 수 있다.
GOT Overwrite
system
함수와 "/bin/sh" 문자열의 주소를 알고있으므로, 지난 코스에서 처럼 pop rdi; ret
가젯을 활용하여 system("/bin/sh")를 호출할 수 있다. 그러나 system
함수의 주소를 알았을 때는 ROP 페이로드가 전송된 이후이므로, 알아낸 system
함수의 주소를 페이로드에 사용하려면 main함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야한다. 이러한 공격패턴을 ret2main이라고 부르는데, 이번엔 GOT overwrite 기법을 통해 한번에 획득해본다.