강의 내용을 보면 이런 부분이 있습니다.
addr of ("pop rdi; ret") << return address
addr of string "/bin/sh" << ret + 0x8
addr of "system" plt << ret + 0x10
리턴 가젯을 사용하여 반환 주소와 이후의 버퍼를 다음과 같이 덮으면,
pop rdi로 rdi를 "/bin/sh"의 주소로 설정하고,
이어지는 ret로 system 함수를 호출할 수 있습니다.
pop rdi를 사용해서 rdi에 "/bin/sh"의 주소가 어떻게 들어가는지 모르겠습니다.
그리고, ret 후 system() 함수가 어떻게 호출 가능한지...
설명에 앞서서 필요한 사전지식부터 설명드리면,
- pop 연산은 sp 레지스터의 값에 영향을 받습니다.
pop reg
에서는 sp가 가리키는 곳의 값을 reg에 넣은 뒤 sp 값을 갱신하게 됩니다. - 함수가 종료될 때는 에필로그라고 부르는 leave; ret 연산을 수행하게 됩니다. 이는 함수의 stack frame을 정리하고, 함수가 호출된 부분으로 실행흐름을 옮기는 작업을 합니다. (leave연산은 move rsp, rbp; pop rbp 명령어와 동일하고, ret 연산은 pop rip, jmp rip와 동일합니다)
- 64비트 기준 함수의 스택 프레임은 위에서부터 지역변수, SFP(8byte), return address(8byte) 이렇게 쌓여 있고, rsp는 지역변수의 가장 위쪽, 그리고 rbp는 SFP를 가리키고 있습니다.
return address에 위의 코드들을 삽입하게 된다면, 다음과 같은 일이 일어납니다.
함수 종료 시 leave 연산으로
SFP에 적혀져 있던 값이 rbp에 들어가게 되고 rsp는 ret address가 있는 곳을 가리키게 됩니다.
ret 연산으로 ret address에 적힌 값을 rip로 pop하게 됩니다.
이렇게 되면 rip에는 "pop rdi; ret" 가젯의 주소가 들어가게 되고, pop을 했기 때문에 rsp는 "/bin/sh"의 주소가 적힌 곳을 가리키게 됩니다.
이제 rip가 적힌 곳의 코드가 실행되기 때문에 pop rdi 연산이 수행되는데,
rsp가 가리키는 곳에는 "/bin/sh" 문자열의 주소가 적혀 있기 때문에 '/bin/sh"문자열의 주소가 rdi에 들어가게 됩니다.
그리고 pop 연산을 했기 때문에 rsp는 system_plt의 주소가 적힌 곳을 가리키고 있겠죠?
pop rdi 실행 후 ret이 실행되면서 다시 rsp가 가리키는 곳의 값이 rip에 들어갑니다. system_plt의 주소가 rip에 들어가게 되겠죠?
그리고 rip로 jmp하기 때문에 system_plt가 실행됩니다.
즉, 이렇게 되면 첫 번째 인자에 해당하는 rdi에 "/bin/sh"문자열의 주소를 삽입한 상태로 system함수를 실행시킬 수가 있게 되는 것이죠.