ssongk
ssongk
ssongk
전체 방문자
오늘
어제

공지사항

  • resources
  • 분류 전체보기 (626)
    • CTF (24)
    • background (79)
      • fuzzing (5)
      • linux (29)
      • linux kernel (15)
      • windows (2)
      • web assembly (1)
      • embedded (0)
      • web (13)
      • crypto (9)
      • mobile (1)
      • AI (1)
      • etc.. (3)
    • write-up(pwn) (171)
      • dreamhack (102)
      • pwn.college (4)
      • pwnable.xyz (51)
      • pwnable.tw (3)
      • pwnable.kr (5)
      • G04T (6)
    • write-up(rev) (32)
      • dreamhack (24)
      • reversing.kr (8)
    • write-up(web) (195)
      • dreamhack (63)
      • LOS (40)
      • webhacking.kr (69)
      • websec.fr (3)
      • wargame.kr (6)
      • webgoat (1)
      • G04T (7)
      • suninatas (6)
    • write-up(crypto) (19)
      • dreamhack (16)
      • G04T (1)
      • suninatas (2)
    • write-up(forensic) (53)
      • dreamhack (5)
      • ctf-d (47)
      • suninatas (1)
    • write-up(misc) (13)
      • dreamhack (12)
      • suninatas (1)
    • development (31)
      • Linux (14)
      • Java (13)
      • Python (1)
      • C (2)
      • TroubleShooting (1)
    • 자격증 (8)
    • 이산수학 (1)
    • 정보보안 (0)
hELLO · Designed By 정상우.
ssongk

ssongk

SigReturn-Oriented Programming(SROP)
background/linux

SigReturn-Oriented Programming(SROP)

2022. 12. 30. 22:44

signal

대회 문제를 풀이해보면 프로그램의 정상 종료를 위해 alarm 함수를 호출하고
지정한 시간이 지나면 프로그램을 종료하기 위한 시그널 핸들러를 호출하는 코드를 자주 볼 수 있다.
아래는 예제 코드이다.

// Name: sig_alarm.c
// Compile: gcc -o sig_alarm sig_alarm.c 

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
 
void sig_handler(int signum){
  printf("sig_handler called.\n");
  exit(0);
}
int main(){
  signal(SIGALRM,sig_handler);
  alarm(5);
  getchar();
  return 0;
}


코드를 살펴보면, signal 함수를 사용해 SIGALRM 시그널이 발생하면 sig_handler 함수를 실행한다.
SIGALRM 시그널이 발생하면 커널 모드로 진입한다.
커널 모드에서 시그널을 처리하고 나면 다시 유저 모드로 돌아와 프로세스의 코드를 실행해야 한다.
즉, 유저 모드의 상태를 모두 기억하고 되돌아올 수 있어야 한다.
(여기서 말하는 상태는 시그널이 발생했을 때 프로세스의 메모리, 레지스터 등이 포함된다)
커널에서는 유저 모드로 되돌아가야 할 때를 위해 유저 프로세스의 상태를 저장하는 코드가 구현되어 있다.

 


do_signal

do_signal 함수는 시그널을 처리하기 위해 제일 먼저 호출되는 함수이다.
리눅스 커널 5.8 버전 이하에서는 do_signal,
리눅스 커널 5.10 이하 버전에서는 arch_do_signal,
상위 버전에서는 arch_do_signal_or_restart로 정의되어 있다.

아래 코드는 arch_do_signal_or_restart 함수다.
시그널이 발생했다면 시그널에 대한 정보를 인자로 get_signal 함수를 호출한다.
해당 함수에서는 시그널에 해당하는 핸들러가 등록되어 있는지 확인한다.
핸들러가 등록되어 있다면 시그널에 대한 정보와 레지스터 정보를 인자로 handle_signal 함수를 호출한다.

void arch_do_signal_or_restart(struct pt_regs *regs, bool has_signal)
{
	struct ksignal ksig;
	if (has_signal && get_signal(&ksig)) {
		/* Whee! Actually deliver the signal.  */
		handle_signal(&ksig, regs);
		return;
	}
	/* Did we come from a system call? */
	if (syscall_get_nr(current, regs) >= 0) {
		/* Restart the system call - no handlers present */
		switch (syscall_get_error(current, regs)) {
		case -ERESTARTNOHAND:
		case -ERESTARTSYS:
		case -ERESTARTNOINTR:
			regs->ax = regs->orig_ax;
			regs->ip -= 2;
			break;
		case -ERESTART_RESTARTBLOCK:
			regs->ax = get_nr_restart_syscall(regs);
			regs->ip -= 2;
			break;
		}
	}
	/*
	 * If there's no signal to deliver, we just put the saved sigmask
	 * back.
	 */
	restore_saved_sigmask();
}

 


handle_signal

아래는 handle_signal 함수의 일부인데
코드를 보면 setup_rt_frame 함수를 호출한다.
해당 함수는 시그널에 적용된 핸들러가 존재할 경우 핸들러의 주소를 다음 실행 주소로 삽입한다.
맨 위 예제 코드에서는 SIGALRM이 발생할 경우 해당 코드를 통해 sig_handler 함수가 호출된다.

static void
handle_signal(struct ksignal *ksig, struct pt_regs *regs)
{
    ...
	failed = (setup_rt_frame(ksig, regs) < 0);
	if (!failed) {
		fpu__clear_user_states(fpu);
	}
	signal_setup_done(failed, ksig, stepping);
}

 


sigreturn

현재 프로세스가 바뀌는 것을 컨텍스트 스위칭(Context Switching)이라고 한다.
커널은 유저가 실행한 프로세스를 관리하기 위해 다양한 코드를 실행한다.
컨텍스트 스위칭이 일어나면 다시 유저 프로세스로 복귀해야 한다.

따라서 스위칭이 일어날 때의 상황을 커널에서 기억하고
커널 코드의 실행을 마치면 기억한 정보를 되돌려 복귀해야 하는데
이때 사용되는 시스템 콜이 sigreturn이다.

아래는 restore_sigcontext 함수의 코드이다.
sigreturn 시스템 콜을 호출하면 내부적으로 해당 함수를 호출해
스택에 저장된 값을 각각의 레지스터에 복사하여 기존 상황과 실행할 코드를 기억하고 복귀한다.
코드를 살펴보면, sigcontext 구조체에 존재하는 각 멤버 변수에 값을 삽입한다.

static bool restore_sigcontext(struct pt_regs *regs,
			       struct sigcontext __user *usc,
			       unsigned long uc_flags)
{
	struct sigcontext sc;
	/* Always make any pending restarted system calls return -EINTR */
	current->restart_block.fn = do_no_restart_syscall;
	if (copy_from_user(&sc, usc, CONTEXT_COPY_SIZE))
		return false;
#ifdef CONFIG_X86_32
	set_user_gs(regs, sc.gs);
	regs->fs = sc.fs;
	regs->es = sc.es;
	regs->ds = sc.ds;
#endif /* CONFIG_X86_32 */
	regs->bx = sc.bx;
	regs->cx = sc.cx;
	regs->dx = sc.dx;
	regs->si = sc.si;
	regs->di = sc.di;
	regs->bp = sc.bp;
	regs->ax = sc.ax;
	regs->sp = sc.sp;
	regs->ip = sc.ip;
#ifdef CONFIG_X86_64
	regs->r8 = sc.r8;
	regs->r9 = sc.r9;
	regs->r10 = sc.r10;
	regs->r11 = sc.r11;
	regs->r12 = sc.r12;
	regs->r13 = sc.r13;
	regs->r14 = sc.r14;
	regs->r15 = sc.r15;
#endif /* CONFIG_X86_64 */
	/* Get CS/SS and force CPL3 */
	regs->cs = sc.cs | 0x03;
	regs->ss = sc.ss | 0x03;
	regs->flags = (regs->flags & ~FIX_EFLAGS) | (sc.flags & FIX_EFLAGS);
	/* disable syscall checks */
	regs->orig_ax = -1;
#ifdef CONFIG_X86_64
	/*
	 * Fix up SS if needed for the benefit of old DOSEMU and
	 * CRIU.
	 */
	if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) && user_64bit_mode(regs)))
		force_valid_ss(regs);
#endif
	return fpu__restore_sig((void __user *)sc.fpstate,
			       IS_ENABLED(CONFIG_X86_32));
}

 


sigcontext

아래 코드는 x86_64의 sigcontext 구조체이다.
레지스터 명칭을 가진 각각의 멤버 변수가 존재하는 것을 확인할 수 있다.
(구조체와 커널 코드는 아키텍처별로 상이한가보다)

/* __x86_64__: */
struct sigcontext {
  __u64               r8;
  __u64               r9;
  __u64               r10;
  __u64               r11;
  __u64               r12;
  __u64               r13;
  __u64               r14;
  __u64               r15;
  __u64               rdi;
  __u64               rsi;
  __u64               rbp;
  __u64               rbx;
  __u64               rdx;
  __u64               rax;
  __u64               rcx;
  __u64               rsp;
  __u64               rip;
  __u64               eflags;     /* RFLAGS */
  __u16               cs;
  __u16               gs;
  __u16               fs;
  union {
      __u16           ss; /* If UC_SIGCONTEXT_SS */
      __u16           __pad0; /* Alias name for old (!UC_SIGCONTEXT_SS) user-space */
  };
  __u64               err;
  __u64               trapno;
  __u64               oldmask;
  __u64               cr2;
  struct _fpstate __user      *fpstate;   /* Zero when no FPU context */
#  ifdef __ILP32__
  __u32               __fpstate_pad;
#  endif
  __u64               reserved1[8];
};

 


SigReturn-Oriented Programming (SROP)

SROP는 컨텍스트 스위칭을 위해 사용하는 sigreturn 시스템 콜을 이용한 ROP 기법이다.
sigreturn 시스템 콜을 호출하고 레지스터에 복사할 값을 미리 스택에 저장해 임의 코드를 실행한다.
모든 레지스터를 조작할 수 있는 만큼 익스플로잇 활용도가 매우 높다.

아래 코드은 sigreturn 시스템 콜을 호출해 레지스터를 스택의 값으로 조작하는 예제 코드이다.
예제를 컴파일하고 디버거에서 실행해보면 레지스터를 조작할 수 있는 것을 확인할 수 있다.

#include <string.h>
int main()
{
        char buf[1024];
        memset(buf, 0x41, sizeof(buf));
        asm("mov $15, %rax;"
            "syscall");
}
$ gdb -q ./sigrt_call
gdb-peda$ r
Starting program: sigrt_call 
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x4141414141414141 ('AAAAAAAA')
RCX: 0x4141414141414141 ('AAAAAAAA')
RDX: 0x4141414141414141 ('AAAAAAAA')
RSI: 0x4141414141414141 ('AAAAAAAA')
RDI: 0x4141414141414141 ('AAAAAAAA')
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x4141414141414141 ('AAAAAAAA')
RIP: 0x4141414141414141 ('AAAAAAAA')
R8 : 0x4141414141414141 ('AAAAAAAA')
R9 : 0x4141414141414141 ('AAAAAAAA')
R10: 0x4141414141414141 ('AAAAAAAA')
R11: 0x4141414141414141 ('AAAAAAAA')
R12: 0x4141414141414141 ('AAAAAAAA')
R13: 0x4141414141414141 ('AAAAAAAA')
R14: 0x4141414141414141 ('AAAAAAAA')
R15: 0x4141414141414141 ('AAAAAAAA')
EFLAGS: 0x10343 (CARRY parity adjust ZERO sign TRAP INTERRUPT direction overflow)

 


레퍼런스
https://dreamhack.io/lecture/courses/277

 

Background: SigReturn-Oriented Programming

이번 코스는 Signal의 동작 방식을 알아보고, SigReturn, SigReturn-Oriented Programming에 대해 소개합니다.

dreamhack.io

 

'background > linux' 카테고리의 다른 글

[glibc-2.27] fread & fgets  (0) 2023.01.09
[glibc-2.27] _IO_FILE & fopen  (1) 2023.01.07
Master Canary  (0) 2022.12.08
SECCOMP  (0) 2022.12.01
cyclic pattern for buffer overflow attack  (0) 2022.11.11
    'background/linux' 카테고리의 다른 글
    • [glibc-2.27] fread & fgets
    • [glibc-2.27] _IO_FILE & fopen
    • Master Canary
    • SECCOMP
    ssongk
    ssongk
    벌레 사냥꾼이 되고 싶어요

    티스토리툴바