상세 컨텐츠

본문 제목

01.SROP(Sigreturn-oriented programming) - x86

본문

SROP(Sigreturn-oriented programming)

  • SROP는 sigreturn 시스템 콜을 이용하여 레지스터에 원하는 값을 저장할 수 있다.
    • 해당 기법을 이용하여 원하는 시스템 함수를 호출할 수 있다.

Signal

  • signal은 프로세스에게 이벤트가 발생했음을 알린다.

  • signal은 다른 프로세스에게 시그널을 전송 할 수 있다.

    • signal은 원시적인 형태의 IPC(interprocess communication)로 사용 할 수 있다.
    • signal은 자기 자신에게 시그널을 보낼수도 있다.
  • signal은 일반적으로 커널이 송신하며, 다음과 같은 이벤트 종류가 있다.

    • 하드웨어 예외가 발생한 경우

    • 사용자가 시그널을 발생시키는 터미널 특수 문자 중 하나를 입력한 경우

      • Interrupt character(Control + c)

      • Suspend character(Control + z)
    • 소프트웨어 이벤트가 발생한 경우
      • 파일 디스크립터에 입력이 발생
      • 타이머 만료
      • 해당 프로세스의 자식 프로세스가 종료
  • signal은 생성되면 프로세스에 전달되고, 전달된 시그널의 종류에 따라 다음과 같은 동작이 실행된다.
    • 시그널 무시
    • 프로세스 종료
    • 코어 덤프 파일을 생성 후 프로세스 종료
    • 프로세스 중지
    • 프로세스의 실행을 재개

Signal handler

  • signal handler는 프로그램이 특정 시그널의 기본 동작을 수행하는 대신 프로그래머가 원하는 동작을 수행하도록 변경할 수 있다.
  • signal handler는 User Mode 프로세스에 정의되어 있고 User Mode 코드 세그먼트에 포함된다.
  • signal handler가 User Mode 에서 실행되는 동안 Kernel Mode에서 handle_signal() 함수가 실행 된다.
    • User Mode에서 Kernel Mode로 진입시 User Mode에서 사용중이던 context를 Kernel stack에 저장한다.
    • Kernel Mode에서 User Mode로 진입시 Kernel stack은 모두 초기화된다.
    • 이러한 문제를 해결하기 위해 setup_frame(), sigreturn() 함수를 사용한다.
      • setup_frame() : User Mode의 stack을 설정
      • sigreturn() : Kernel Mode stack에 hardware context를 복사하고, User Mode stack의 원래의 content를 저장한다.

  • Signal handler는 다음과 같이 처리된다.
    • 인터럽트 또는 예외가 발생하면 프로세스는 Kernel Mode로 전환된다. 
    • 커널은 User Mode로 돌아 가기 전에 do_signal() 함수를 실행한다.
      • do_signal() 함수는 handle_signal()을 호출하여 signal를 처리한다.
      • handle_signal() 함수는 setup_frame()을 호출하여 User Mode Stack에 context를 저장한다.
    • 프로세스가 User Mode로 다시 전환되면 signal handler가 실행된다.
    • signal handler가 종료되면 setup_frame() 함수에 의해 User Mode stack에 저장된 리턴 코드가 실행된다.
      • 해당 코드에 의해 sigreturn() 시스템 함수가 호출된다.
        • sigreturn() 시스템 함수에 의해 Kernel Mode Stack에서 일반 프로그램의 hardware context를 User Mode의 stack에 복사한다.
        • sigreturn() 함수는 restore_sigcontext() 을 호출하여 User Mode 스택을 원래 상태로 복원한다. 
    • 시스템 호출이 종료되면 일반 프로그램은 실행을 재개 할 수 있다.

Example

  • 다음과 같이 handle_signal 함수에 Break point를 설정한다.
  • 그리고 GDB가 인트럽트에 반응하지 않도록 설정한다.

다음과 같이 프로그램을 실행 후 "Ctrl + C"를 눌러서 Interrupt 신호를 발생시킨다.

  • bt명령어를 이용해 handle_signal 함수가 호출되기 전에 실행된 함수 목록을 확인 할 수 있다.
  • ebp+12 부터 struct sigcontext 시작

Frame 0 

다음과 같이 0번째 Frame에서 Stack에 저장된 각 각의 레지스터 값을 확인 할 수 있다.

 

Frame 1

다음과 같이 1번째 Frame의 내용을 보면 __kernel_sigreturn() 함수에서 에서 sys_sigreturn() 시스템 함수 호출한다.

  • x86에서 sys_sigreturn 시스템 함수의 번호는 0x77(119) 이다.

Frame 2

다음과 같이 signal에 대한 처리가 끝난 후에 Frame 0의 Stack에 저장된 값이 레지스터에 저장된 것을 확인 할 수 있다.

 

sigreturn()

  • sigreturn() 시스템 함수는 Signal을 처리하는 프로세스가 Kernel Mode에서 User Mode 돌아 올때 stack을 복원하기 위해 사용되는 함수 이다.
    • sigreturn() 함수는 stack을 복원하기 위해 restore_sigcontext()를 호출한다.

  • restore_sigcontext() 함수는 COPY_SEG(), COPY() 함수 등 을 이용하여 stack에 저장된 값을 각 레지스터에 복사한다.
    • 즉, ROP와 같이 값을 레지스터에 저장할 수 있는 Gadget이 없어도 sigreturn() 함수를 이용해 각 레지스터에 원하는 값을 저장할 수 있다.

    • stack에 저장된 레지스터 값들은 restore_sigcontext()함수의 인자값 &frame->sc에 의해 전달된다.

    • &frame->sc"는 sigcontext 구조체 이다.
      • 즉, SROP 를 이용할 때 Stack에 다음과 같은 형태로 값을 저장해야 한다.

Proof of concept

다음과 같이 Overflow를 확인할 수 있다.

  • Return address - buf 변수의 시작 주소 = 66

  • 즉, 66개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있다.

Exploit 순서

  1. sigreturn()함수를 이용해 레지스터에 필요한 값을 저장

    1. ESP : sigreturn() 함수 호출 후 이동할 주소("int 0x80" 명령어가 저장된 주소)
    2. EBX : "/bin/sh" 문자열이 저장된 주소
    3. EAX : execve() 함수의 시스템 콜 번호
    4. EIP : "int 0x80" 명령어가 저장된 주소
    5. CS : User Code(0x23)
    6. SS : User Data / Stack(0x2b)
  2. int 0x80 명령어 실행
더보기

sigreturn()

int 0x80

확인해야 할 정보 목록

  • Libc offset
    • printf
    • __kernel_sigreturn
    • "/bin/sh"명령가 저장된 영역

  • Gadgets

    • int 0x80

Libc offset

Offset of __kernel_sigreturn 

다음과 같이 __kernel_sigreturn() 함수를 Exploit에 사용할 수 있다.

  • 0xf7fd7ff0 주소를 사용할 경우 "pop eax" 명령어가 포함되어 있기 때문에 0xf7fd7ff0 호출 뒤에 임의의 값(4bit)이 저장되어야 합니다.
    • Ex) __kernel_sigreturn() + 임의의 값(4bit) + sigcontext 구조체
  • 0xf7fd7ff1 주소를 사용할 경우 "mov eax,0x77" 명령어가 실행되기 때문에 0xf7fd7ff1 호출 뒤에 sigcontext 구조체가 저장되어야 합니다.

    • Ex) __kernel_sigreturn() + sigcontext 구조체

Find Gadgets

32bit이기 때문에 sigreturn() 함수를 vdso 영역에서 확인 할 수 있다.

 

CS(Code segment) & SS(Stack Segment)

  • SROP의 Exploit code를 작성할 때 중요한 부분이 있다.

    • sigcontext 구조체 형태로 stack에 값을 저장할 때 최소한 CS, SS레지스터에 대한 값을 설정해야한다.

  • Linux kernel에는 4개의 세그먼트만 존재한다.

    • 공격 코드들은 User Mode에서 실행되기 때문에 User Code, User Data / Stack 값을 사용해야 한다.
    • 그리고 32bit 프로그램의 경우 실행되는 운영체제(32bit / 64bit) 환경에 따라 사용되는 세그먼트 값이 다르다.
      • 32bit 운영체제에서는 0x73, 0x7b가 사용용된다.
      • 62bit 운영체제에서는 실행되는 32bit 프로그램의 경우 0x23, 0x2b가 사용된다.
    • 이외의 값을 저장하게 되면 에러가 발생하게된다.

Segment

purpose Segment(32bit) Segment(64bit-32bit)
Kernel Code 0x60 0x8
Kernel Data/Stack 0x68 0x18
User Code 0x73 0x23
User Data/Stack 0x7b 0x2b

 

exploit

use pwntools

 

 

REF : https://www.lazenca.net/display/TEC/01.SROP%28Sigreturn-oriented+programming%29+-+x86

 

01.SROP(Sigreturn-oriented programming) - x86 - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List SROP(Sigreturn-oriented programming) SROP는 sigreturn 시스템 콜을 이용하여 레지스터에 원하는 값을 저장할 수 있습니다.해당 기법을 이용하여 원하는 시스템

www.lazenca.net

 

관련글 더보기

댓글 영역