Srop
운영 체제는 User Mode와 Kernel Mode로 나뉜다.
실제로 파일을 생성하고, 프로그램을 실행하는
모든 작업은 이 두개의 모드가 서로 상호 작용하면서 이뤄진다.
Signal : 프로세스에 특정 정보를 전달하는 매개체
운영체제는 자원관리를 위해 일반적으로는 유저 모드를 유지하다가 시그널이 발생하면 커널 모드로 진입한다.
시그널을 커널 모드에서 처리하고 다시 유저 모드로 돌아와 프로세스의 코드를 실행한다.
그러려면 유저 모드의 상태를 기억하고 돌아올 수 있어야 한다.
유저 모드의 상태란 시그널이 발생했을 때 프로세스의 메모리, 레지스터 등이 포함된다.
커널에서는 유저 모드로 되돌아가야 하는 상황을 고려해 유저 프로세스의 상태를 저장하는 코드가 구현되어 있다.
— 이에 대해 do_signal, handle_signal 추가 공부 필요
위의 설명처럼 프로세스가 바뀌는 것을 “Context Switching”이라고 한다.
커널 모드의 실행을 마치고 기억한 정보를 되돌려 복귀하려고 할 때, 사용되는 시스템 콜이 sigreturn 이다. 내부적으로는 restore_sigcontext 함수가 호출된다.
아키텍처마다 구조체와 커널 코드가 다르기 때문에 익스할 때는 구분해서 진행해야 한다.
SigReturn-Oriendted Programming (SROP)는 Context Switching을 위해 사용하는 sigreturn 시스템 콜을 이용한 ROP 기법이다.
ROP은 코드 가젯을 모아서 임의의 코드를 실행하는 것이기 때문에,
SROP은 sigreturn 시스템 콜을 호출하고, 레지스터에 복사할 값을 미리 스택에 저장해 임의 코드를 실행하는 것을 알 수 있다.
SROP을 하기 위해선 syscall, ret 이라는 가젯이 필요하고, 스택에는 커널이 기대하는 구조체 포맷대로 값을 쌓아야 한다.
1
2
3
4
5
6
7
frame = SigreturnFrame()
frame.rax = 59 # execve
frame.rdi = binsh # const char *filename
frame.rsi = 0 # argv
frame.rdx = 0 # envp
frame.rip = syscall_ret # syscall 실행 지점
frame.rsp = 0xdeadbeef # 다음 스택 주소
SigreturnFrame()을 사용하면 어떻게 레지스터를 조작해 스택의 값을 수정할 수 있게 되는가??