CTF PWN-攻防世界Overflow整数溢出漏洞

本文介绍了整数溢出的概念,包括有符号和无符号整数的溢出、回绕以及截断情况,并通过C语言示例展示了漏洞。文章以攻防世界PWN题目int_overflow为例,分析了如何利用整数溢出来绕过长度检查,导致栈溢出,进而实现代码执行。最后,提出了EXP程序构造思路,强调了安全审计的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

滴水穿石,非一日之功。继续练习攻防世界 PWN 题目,此次练习的题目是 int_overflow,顾名思义是整数溢出类型的漏洞:
在这里插入图片描述

整数溢出

做题不是目的,目的是为了学习不懂的知识。So 先来看看什么整数溢出漏洞吧。

C 语言中整数的分类及各自的大小范围如下所示:
在这里插入图片描述
正是因为这些类型的大小范围的限制才导致可能发生整数溢出。

整数溢出的异常有3种:

分类描述
溢出只有有符号数才会发生溢出;溢出标志OF可检测有符号数的溢出
回绕环绕特指的是无符号数,比如0减1时会变成最大的数,1字节的话会变为255,而255+1时会变为0;进位标志CF可检测无符号数的回绕
截断就是将一个较大宽度的数放入一个宽度较小的数中,高位将发生截断

有符号整数溢出

有符号整数溢出被分为上溢出和下溢出两种。

#include <stdio.h>
int main()
{
   //上溢出  
   int i = 2147483647;//int的最大值
   i++;
   printf("%d\n",i); //输出为最小负数

   //下溢出
   int j = -2147483648;//int的最小值
   j--;
   printf("%d\n",j);//输出为最大正数
   return 0;
}

运算结果如下:
在这里插入图片描述
【漏洞示例】

char buf[80];
void vulnerable() {
    int len = read_int_from_network();
    char *p = read_string_from_network();
    if (len > 80) {
        error("length too large: bad dog, no cookie for you!");
        return;
    }
    memcpy(buf, p, len);
}

此案例中,如果给 len 赋值一个负数,就可以绕过 if 判断,但是到 memcpy 时,因为第三个参数是 size_t(unsigned int) 类型,负数的 len 会被认为是一个很大的正数,从而复制大量内容到 buf,导致缓存区溢出。

无符号整数回绕

无符号数的计算不会溢出,但是会发生回绕。

#include <stdio.h>

int main()
{
   unsigned int i = 4294967295;
   i++;
   printf("%d\n",i); //输出为0
   return 0;
}

【漏洞示例】

void vulnerable() {
    size_t len;
    char* buf;
    len = read_int_from_network();
    buf = malloc(len + 5);
    read(fd, buf, len);
    ...
}

相较于上一个漏洞例子,这个例子避开来缓冲区溢出的问题,但是如果 len 很大时,len+5 会回绕,比如若是 len = 0xFFFFFFFF,len + 5 = 0x00000004,这时只 malloc 了4个字节,然而之后会 read 大量数据,缓冲区溢出也会发生。

截断与宽度溢出

1、加法截断

0xffffffff + 0x00000001
= 0x0000000100000000 (long long)
= 0x00000000 (long)

【漏洞示例】

void main(int argc, char *argv[]) {
    unsigned short int total;
    total = strlen(argv[1]) + strlen(argv[2]) + 1;
    char *buf = (char *)malloc(total);
    strcpy(buf, argv[1]);
    strcat(buf, argv[2]);
    ...
}

这个例子计算了输入参数的长度为 total,程序分配了内存来存拼接后的字符串。这里 total 的类型为 unsigned short int,如果攻击者提供的两个字符串总长度无法用 total 表示,则会发生截断,从而导致后面的缓冲区溢出。

2、乘法截断

0x00123456 * 0x00654321
= 0x000007336BF94116 (long long)
= 0x6BF94116 (long)

3、宽度溢出与整数提升

#include<stdio.h>
void main() {
    int l;  
    short s;
    char c;
    l = 0xabcddcba;
    s = l;
    c = l;
    printf("宽度溢出\n");
    printf("l = 0x%x (%d bits)\n", l, sizeof(l) * 8);
    printf("s = 0x%x (%d bits)\n", s, sizeof(s) * 8);
    printf("c = 0x%x (%d bits)\n", c, sizeof(c) * 8);
    printf("整型提升\n");
    printf("s + c = 0x%x (%d bits)\n", s+c, sizeof(s+c) * 8);
}

OUT:    
$ ./test
宽度溢出
l = 0xabcddcba (32 bits)
s = 0xffffdcba (16 bits)
c = 0xffffffba (8 bits)
整型提升
s + c = 0xffffdc74 (32 bits)

综上可以看出,在整数转换的过程中,有可能导致下面的错误:

  1. 损失值:转换为值的大小不能表示的一种类型;
  2. 损失符号:从有符号类型转换为无符号类型,导致损失符号。

int_overflow

接下来开始练习攻防世界的整数溢出题目:int_overflow。下载并查看附件,是个 32 位小端程序:
在这里插入图片描述
开启了 NX 保护,但是未开启 PIE 程序内存加载基地址随机化保护机制(即静态反汇编的地址可以直接使用)。

运行 elf 文件,程序要求用户依次输入用户名和密码,然后结束运行:
在这里插入图片描述

题目漏洞分析

将 elf 拖进 IDA Pro 中进行分析,F5 查看主函数伪代码如下:
在这里插入图片描述
逻辑很简单,用户输入整数 1 则进入 login(),跟进查看 login 函数:
在这里插入图片描述
两处 read 函数依次获取用户名、密码很正常,均不存在溢出,但是密码 buf 传递给了 check_passwd 函数,那就继续跟进:
在这里插入图片描述
分析下上述函数逻辑:

  1. 第 5 行代码定义了一个无符号 8 位整数变量 unsigned _int8 v3(取值范围 0-255);
  2. 第 7 行代码 v3 = strlen(s),取 check_passwd 函数中用户传递进来的密码字符串的长度作为 v3 的值。但是这里一定要注意的是: strlen 函数的返回值是 size_t 类型,在 32 位文件中即 unsigned int 类型,也就是无符号 32 位整型
  3. 第 8 行代码又限制 v3 变量的值(即输入的密码字符串的长度)需要大于整数 3 且小于整数 8;

以上过程存在漏洞的地方就在于:第 7 行代码 v3 = strlen(s) 是将 32 位的数据赋值给 8 位的变量,所以当 password 的长度 strlen(s) 超过 255 个字符时,将发生整数溢出漏洞,编译器将会自动进行截断,只留下后 8 位。此时只要满足 255+3 < strlen(s) <= 255+8,也就是 s 的长度在 (258,263] 这个范围内就可以满足判断条件。

进一步检查 dest 的栈结构:
在这里插入图片描述
在这里插入图片描述
程序为 check_password() 函数中的 dest 变量分配了 0x14 字节的储存空间,由于 strcpy(dest, s) 的 s 参数来源于 login() 函数第二个 read 函数,回溯发现其却可以读取 0x199 字节数据:
在这里插入图片描述
所以在利用整数溢出漏洞绕过 if 判断后,check_password() 函数中的 strcpy(dest, s) 处便显而易见地发生栈溢出

因此我们可以在 check_password 函数中通过整数溢出绕过 if 条件判断对输入字符串长度的限制,然后通过 strcpy 函数将输入字符串 s 先复制给 dest,同时在拷贝过程借助栈溢出漏洞将后门函数的地址赋值给 result,接着将 result 作为返回值返回给上层函数,也就是 check_passwd(),最后该返回值再作为 login() 函数的返回值返回给主程序,主程序便可以 getshell!

接下来寻找程序中是否存在后门函数,发现确实存在后门函数what_is_this:
在这里插入图片描述
偏移地址为 0x804868B,因为此程序未开启 PIE 内存加载基地址随机化保护机制,所以静态反汇编的地址可以直接使用:
在这里插入图片描述

EXP程序构造

综上所述,exp 利用程序的思路如下:

  1. 输入 password 字符串,位数大小在 (258,263] 这个范围内,利用整数溢出漏洞,绕过对字符串长度的判断;
  2. 利用 strcpy(dest, s) 函数处的栈溢出漏洞,将后门函数 what_is_this() 的地址填入缓冲区中并作为返回地址返回给主程序。

更细致点说就是:

  • 首先利用栈溢出,填充 dest 的 0x14 字节内存;
  • 然后再发送 4 字节内容覆盖栈底指针 ebp;
  • 接着用 system 函数的地址覆盖返回值;
  • 最后利用整数溢出漏洞,将字符串长度补充至 (258,263] 位,以此绕过对字符串长度的判断。

最终整体的栈结构将构造如下:
在这里插入图片描述
EXP 程序:

from pwn import *

io = remote("161.147.171.105", 61383)
cat_flag_addr = 0x0804868B
io.recvuntil("choice:")
io.sendline("1")
io.recvuntil("username:")
io.sendline("name")
io.recvuntil("passwd:")
payload = b'A'*(0x14 + 0x4) + p32(cat_flag_addr) + b'A'*(256-0x14-4-4+3)
io.sendline(payload)
io.interactive()

成功获取 Flag:cyberpeace{dee51b69c2224dbf4814ac4f6f941d20}
在这里插入图片描述
在这里插入图片描述

总结

本文学习了缓冲区溢出漏洞常见的一种场景:整数溢出,一旦不认真考虑整数变量的范围,此类漏洞缺陷很容易在程序员的编码过程中发生,这也是安全工作人员需要注意审计的地方。

本文参考文章:

  1. 整数溢出-From Ghostasky;
  2. 【攻防世界pwn-int_overflow】;
  3. 攻防世界 pwn 新手练习区 int_overflow;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tr0e

分享不易,望多鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值