SROP Note

SROP Sigreturn-Oriented Programming

Signal 機制

  • Signal 機制 (軟中斷訊號、軟中斷) 是類 UNIX 系統中 process 之間相互傳遞信息的一種方法。比方說: process 之間可以通過 system 調用 kill 來發送值軟中斷信號。

  • 信號機制常見步驟:

    • 步驟 1 :

      • Kernel 向某 process 發送 signal 機制,該 process 會暫時被 hang (掛起) ,進入 kernel 態。
    • 步驟 2 :

      • Kernel 會為該 process 保存一些資訊,會把以下資訊壓入 stack 中:

        • 所有 register 值。

        • Signal 信息。

        • 指向 sigreturn 的系統調用位址。

      • 此部分是在 user process 的 address area 。

      • 之後會跳轉到 Signal Handler 中處理相應的 signal 。

      • Signal Frame 會因為架構的不同而不同。

    • 步驟 3 :

      • 當 Signal Handler 執行完之後, Signal Handler 返回。
    • 步驟 4 :

      • Signal Handler 返回後, Kernel 會執行 return to sigreturn 所指向的地方的程式,為原先之 process 恢復之前所保存的資訊,像是將先前壓入 stack 的 register 值重新 pop 回對應的 register 。最後恢復 process 的執行。

攻擊原理

  • 一些關鍵:

    • Signal Frame 被保存在 user process 的 address area 中,所以 user process 是可以讀寫的。

    • 由於 "Kernel agnostic (不可知) about signal handlers." , Kernel 並不會去紀錄某 signal 對應的 Signal Frame ,所以,當執行 return to sigreturn 時,此刻的 Signal Frame 並不一定是之前 Kernel 為 user process 保存的 Signal Frame 。

  • DEMO : Get Shell

    • 這邊以 64 bit 為例。

    • 假設 attacker 可以控制 user process 的 stack ,他就可以 fake 一個 Signal Frame 。

    • 一些可能相關的結構:

      • 主要是 rt_sigframe 結構:

        1
        2
        3
        4
        5
        6
        7
        8
        #ifdef CONFIG_X86_64

        struct rt_sigframe {
        char __user *pretcode;
        struct ucontext uc;
        struct siginfo info;
        /* fp state follows here */
        };

        • https://elixir.bootlin.com/linux/latest/source/arch/x86/include/asm/sigframe.h
      • rt_sigframe 結構裡出現的 ucontext 結構:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
        #ifndef __ASM_GENERIC_UCONTEXT_H
        #define __ASM_GENERIC_UCONTEXT_H

        struct ucontext {
        unsigned long uc_flags;
        struct ucontext *uc_link;
        stack_t uc_stack;
        struct sigcontext uc_mcontext;
        sigset_t uc_sigmask; /* mask last for extensibility */
        };

        #endif /* __ASM_GENERIC_UCONTEXT_H */

        • https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ucontext.h
      • ucontext 結構裡出現的 sigcontext 結構:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        # else /* __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;

        /*
        * Prior to 2.5.64 ("[PATCH] x86-64 updates for 2.5.64-bk3"),
        * Linux saved and restored fs and gs in these slots. This
        * was counterproductive, as fsbase and gsbase were never
        * saved, so arch_prctl was presumably unreliable.
        *
        * These slots should never be reused without extreme caution:
        *
        * - Some DOSEMU versions stash fs and gs in these slots manually,
        * thus overwriting anything the kernel expects to be preserved
        * in these slots.
        *
        * - If these slots are ever needed for any other purpose,
        * there is some risk that very old 64-bit binaries could get
        * confused. I doubt that many such binaries still work,
        * though, since the same patch in 2.5.64 also removed the
        * 64-bit set_thread_area syscall, so it appears that there
        * is no TLS API beyond modify_ldt that works in both pre-
        * and post-2.5.64 kernels.
        *
        * If the kernel ever adds explicit fs, gs, fsbase, and gsbase
        * save/restore, it will most likely need to be opt-in and use
        * different context slots.
        */
        __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];
        };
        # endif /* __x86_64__ */

        • https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/sigcontext.h
      • 這邊注意到 rt_sigframe 結構裡還有一個 siginfo 結構, SROP 主要是可以 call 一系列的 syscall ,只有涉及到控制那些 register , ucontext --> sigcontext 裡面可以控制 register ,既然已經到可以控制 register 的地方,再去追 siginfo 結構應該也沒有甚麼意義了。 (2022/10/22)

    • 有了以上的結構資訊,我們便可以 "大致上" 排出在 stack 上的 Signal Frame 樣貌 (會說 "大致上" 是因為目前我只了解到會把相關資訊放到 stack 上,並未實際測試程式是如何放置這些值的,所以還是有些欄位參考網路上的 paper ) :

      START BYTE DATA (0x0~0x7) DATA (0x8~0xF)
      0x00 points to __restore_rt uc_flags
      0x10 &uc uc_stack.ss_sp
      0x20 uc_stack.ss_flags uc_stack.ss_size
      0x30 r8 r9
      0x40 r10 r11
      0x50 r12 r13
      0x60 r14 r15
      0x70 rdi rsi
      0x80 rbp rbx
      0x90 rdx rax
      0xA0 rcx rsp
      0xB0 rip eflags
      0xC0 cs / gs / fs err
      0xD0 trapno oldmask (unused)
      0xE0 cr2 (segfault addr) fpstate
      0xF0 __reserved sigmask
      • 表格說明:

        • START BYTE :

          • 每 16 (0x10) 個 byte 一數。
        • DATA (0x0~0x7) :

          • 設該列的 START BYTE 為 SB

          • DATA (0x0~0x7) 為 SB + 0x0 ~ SB + 0x7 的 data 。

        • DATA (0x8~0xF) :

          • 設該列的 START BYTE 為 SB

          • DATA (0x8~0xF) 為 SB + 0x8 ~ SB + 0xF 的 data 。

        • __restore_rt :

          • 此程式碼片段應該是會 call sigreturn (編號 0xf ) 這個 function 。
    • 偽造 Signal Frame :

      1
      2
      3
      4
      偽造的部分:
      rdi = &"/bin/sh"
      rax = 0x3b (execve)
      rip = &syscall

      START BYTE DATA (0x0~0x7) DATA (0x8~0xF)
      0x00 points to __restore_rt uc_flags
      0x10 &uc uc_stack.ss_sp
      0x20 uc_stack.ss_flags uc_stack.ss_size
      0x30 r8 r9
      0x40 r10 r11
      0x50 r12 r13
      0x60 r14 r15
      0x70 rdi = &"/bin/sh" rsi
      0x80 rbp rbx
      0x90 rdx rax = 0x3b (execve)
      0xA0 rcx rsp
      0xB0 rip = &syscall eflags
      0xC0 cs / gs / fs err
      0xD0 trapno oldmask (unused)
      0xE0 cr2 (segfault addr) fpstate
      0xF0 __reserved sigmask
    • 之後程式會執行一系列的 pop 指令,用來恢復 register 的值,當執行到 rip 時,就會指向 syscall 的 address ,在對應到 register 裡面的值,便會開 shell ,這是因為我們偽造了 Signal Frame 。

System Call Chains

  • 如果要執行 "一系列" 的函數,只要再修改我們偽造的 Signal Frame 就可以達成:

    • 控制 rsp 指向下一個 Signal Frame :

      • 讓很多 Signal Frame "連接" 起來。
    • rip 指向的 gadget 要是 syscall; ret gadget :

      • 因為 stack (rsp) 已經被修改了,所以 syscall; ret 之後會跳到下一個 Signal Frame 的 __restore_rt (會執行下一個 sigreturn ) 。

    加上以上動作便可以把很多 System Call 連接起來!