0x0 程序保护和流程
保护:
流程:
main()
echo_back()
存在一个格式化字符串漏洞。
0x1 利用过程
1.题目保护全开,程序中又没有可用的字符串,函数。所以只能通过格式化字符串漏洞泄露地址,写入有限数据。
2.既然有格式化字符串漏洞,肯定是要泄露栈上的信息。但是由于限制了字符串最长为7,所以只能逐位泄露。
选择使用ida的远程调试,因为可以丢弃alarm()发出的signal。选择在echo_back()的retn处下断点
直到到输入%6$p时在栈上发现了被输出的数据(为什么是第6个参数才输出在栈上的值呢?,这要结合x64的传参顺序,前6个参数在寄存器中,之后的再从栈上取数据。现在当第一个参数为输入的字符串,后5个都是寄存器中的数据,所以只有在偏移为6的时候开始从栈上取数据)。
此时在retn处程序停止执行。可以看到0x7FFEEF3D35E0处为偏移量为6。
rsp指向栈顶位置,也就是返回的地址位置为main+0x9C。
此时可以通过掌握的基础知识分析处几个点(结合上述图片食用)。
- retn指令在执行之前还有一个leave指令,而leave指令相当于。
mov rsp,rbp
pop rbp
通过寄存器信息可以得出main函数的rbp位于0x7FFEEF3D3640。main函数的返回地址位于0x7FFEEF3D3648。
- 发现栈上0x7FFEEF3D3638和0x7FFEEF3D3608中的数据是一样的,它们也同时位于各自rbp-0x8的位置。所以可以判断出这两个是canary的值。
- 可以找到之前输入的数据,结合echo_back()中的数据摆放位置可以轻松找到,输入的数据长度7位于0x7FFEEF3D35FC,"%6$p\n"位于0x7FFEEF3D3600=0x7FFEEF3D35FC+0x4。
- 可以知道main函数中的变量name存放的位置,rbp-0x10=0x7FFEEF3D3640-0x10=0x7FFEEF3D3630
3.通过以上分析可以知道libc的基地址,elf文件加载的基地址,main函数的返回地址。但是由于输入限制了字符串最长为7,导致无法大量的向程序中写入数据。通过查阅资料(ctf-wiki)得知,因为进程中包含了系统默认的三个文件流 stdin\stdout\stderr,因此这种方式可以不需要进程中存在文件操作,通过 scanf\printf 一样可以进行利用。并且可以在 libc.so 中找到 stdin\stdout\stderr 等符号,这些符号是指向 FILE 结构的指针,真正结构的符号是
_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_
在libc中的位置。
_IO_FILE 结构体
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
};
在_IO_FILE 中_IO_buf_base 表示操作的起始地址,_IO_buf_end 表示结束地址,结合对文件读写的源码,可以发现_IO_new_file_underflow 这个函数最终调用了_IO_SYSREAD系统调用来读取文件。所以可以通过控制这两个数据可以实现控制读写的操作。
int
_IO_new_file_underflow (_IO_FILE *fp)
{
_IO_ssize_t count;
#if 0
/* SysV does not make this test; take it out for compatibility */
if (fp->_flags & _IO_EOF_SEEN)
return (EOF);
#endif
if (fp->_flags & _IO_NO_READS)
{
fp->_flags |=<