Sigreturn-Oriented Programming (SROP)

Notes on SROP: forging a sigcontext frame so a single sigreturn sets every register at once.

2026.08.18 Pwn original post

예전에는 셸코드를 직접 실행시켜 시스템 콜을 호출했다.

하지만 NX 가 등장하면서 스택/힙 같은 데이터 영역에서의 코드 실행이 막혔다. 그래서 공격 기법은 이런 순서로 발전해 왔다.

셸코드 직접 실행
  → NX 로 차단
  → RTL (Return-to-Libc)
  → ROP (Return-Oriented Programming)
  → SROP (Sigreturn-Oriented Programming)

SROP 는 ROP 를 기반으로, 리눅스의 시그널 처리 과정을 악용하는 기법이다.

시그널이란

시그널은 프로세스에 보내는 “이벤트 알림”이다.

시그널의미
SIGINTCtrl+C 입력
SIGSEGV잘못된 메모리 접근
SIGALRMalarm 시간 초과
SIGKILL강제 종료
SIGCHLD자식 프로세스 종료

운영체제는 크게 두 영역으로 나뉜다. 일반 프로그램이 도는 User Mode 와 OS 커널이 도는 Kernel Mode 다.

프로그램은 평소 User Mode 에서 실행되지만, 시그널이 발생하면 커널이 개입해야 한다.

시그널 발생 → Kernel Mode 진입 → 커널이 시그널 처리 준비
           → User Mode 복귀 → 등록된 Signal Handler 실행

여기서 중요한 점은, 시그널을 처리하려면 커널이 현재 프로세스의 레지스터와 스택 상태를 저장했다가 되돌려 놓아야 한다는 것이다.

커널 내부의 시그널 처리 흐름

시그널 발생
  → do_signal()
  → get_signal()
  → handle_signal()
  → setup_rt_frame()
  → User Mode 복귀
  → Signal Handler 실행

각 함수의 역할은 다음과 같다.

함수역할
do_signal() / arch_do_signal_or_restart()시그널 처리의 시작점
get_signal()처리할 시그널이 있는지 확인
handle_signal()실제 Signal Handler 실행 준비
setup_rt_frame()User Stack 에 Signal Frame 구성
sigreturn시그널 처리 후 원래 상태로 복귀

setup_rt_frame 이 중요한 이유

Signal Handler 를 실행하려면 커널은 다음 정보를 저장해 두어야 한다.

  • 현재 RIP/EIP
  • 현재 RSP/ESP
  • 레지스터 값들
  • 시그널 정보
  • 원래 실행 위치

이 정보는 User Stack 에 저장되고, 이 구조를 보통 Signal Frame 이라고 부른다. setup_rt_frame() 이 이 Signal Frame 을 만든다.

간단히 말하면, 커널은 현재 프로세스 상태를 스택에 저장해 두었다가 시그널 핸들러가 끝나면 그 상태로 되돌린다.

sigreturn

sigreturn 은 시그널 핸들러가 끝난 뒤 원래 실행 상태로 돌아가기 위한 시스템 콜이다.

시그널 발생
  → 커널 개입
  → 현재 레지스터/스택 상태 저장
  → Signal Handler 실행
  → sigreturn 호출
  → 저장된 상태 복원
  → 원래 코드로 복귀

sigreturn 의 역할은 스택에 저장된 CPU 상태를 읽어서 레지스터를 복원하는 것이다.

컨텍스트 스위칭 관점

Context Switching 은 실행 상태가 바뀌는 것을 말한다.

기존 프로그램 실행
  → 시그널 발생
  → 커널 코드 실행
  → 시그널 핸들러 실행
  → 원래 프로그램으로 복귀

이 과정에서 커널은 기존 상태를 잊으면 안 되기 때문에 레지스터 정보를 전부 저장해 둔다. 그래서 사실상 sigreturn 은 컨텍스트 스위칭 때문에 존재하는 것이라고 볼 수 있다.

공격으로 이어지는 지점

핵심은 이것이다. sigreturn스택에 있는 값을 그대로 믿고 레지스터에 복원한다. 그 스택이 진짜 커널이 만든 Signal Frame 인지는 검증하지 않는다.

따라서 공격자가 스택에 가짜 Signal Frame 을 직접 써 놓고 sigreturn 으로 넘어가면, RIP·RSP 를 포함한 모든 레지스터를 한 번에 원하는 값으로 세팅할 수 있다.

일반 ROP 는 레지스터 하나를 세팅하려고 가젯을 하나씩 찾아 이어 붙여야 하지만, SROP 는 syscall 가젯 하나와 스택에 쓸 수 있는 조건만 있으면 된다. 그래서 가젯이 거의 없는 정적 바이너리에서 특히 강력하다.