UAF analysis : using pykd

0x01 : 简介

分析一些情况比较复杂的UAF漏洞时,比如很多次分配、使用内存,费力寻找被UAF的对象的释放点,是比较麻烦的(对于我这种菜逼来说),这时候可以使用pykd来辅助这个工作,能使得漏洞分析工作变得更轻松。

0x02 : 脚本模版

这个脚本的原始版本在使用的时候有点问题,我本地测试的时候回调函数有点问题,不能正常使用。

后来咨询了无言学长之后,学长帮忙做了修改:

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import pykd

return_reg = "rax"
stack_pointer = "rsp"
arch_bits = 64


def get_address(localAddr):
res = pykd.dbgCommand("x " + localAddr)
result_count = res.count("\n")
if result_count == 0:
print localAddr + " not found."
return None
if result_count > 1:
print "[-] Warning, more than one result for", localAddr
return res.split()[0].replace('`','')


# RtlAllocateHeap(
# IN PVOID HeapHandle,
# IN ULONG Flags,
# IN ULONG Size );
class handle_allocate_heap(pykd.eventHandler):
def __init__(self):
addr = get_address("ntdll!RtlAllocateHeap")
if addr == None:
return
self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back)
self.bp_end = None

def enter_call_back(self):
self.out = "RtlAllocateHeap("
if arch_bits == 32:
esp = pykd.reg(stack_pointer)
self.out += hex(pykd.ptrPtr(esp + 4)) + " , "
self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , "
self.out += hex(pykd.ptrMWord(esp + 0xC)) + ") = "
else:
self.out += hex(pykd.reg("rcx")) + " , "
self.out += hex(pykd.reg("rdx")) + " , "
self.out += hex(pykd.reg("r8")) + ") = "
if self.bp_end == None:
disas = pykd.dbgCommand("uf ntdll!RtlAllocateHeap").split('\n')
for i in disas:
if 'ret' in i:
self.ret_addr = i.split()[0].replace('`','')
break
self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back)
return False

def return_call_back(self):
pykd.dprintln(self.out + hex(pykd.reg(return_reg)) + "\n")
return False


# RtlFreeHeap(
# IN PVOID HeapHandle,
# IN ULONG Flags OPTIONAL,
# IN PVOID MemoryPointer );
class handle_free_heap(pykd.eventHandler):
def __init__(self):
addr = get_address("ntdll!RtlFreeHeap")
if addr == None:
return
self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back)
self.bp_end = None

def enter_call_back(self):
self.out = "RtlFreeHeap("
if arch_bits == 32:
esp = pykd.reg(stack_pointer)
self.out += hex(pykd.ptrPtr(esp + 4)) + " , "
self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , "
self.out += hex(pykd.ptrPtr(esp + 0xC)) + ") = "
else:
self.out += hex(pykd.reg("rcx")) + " , "
self.out += hex(pykd.reg("rdx")) + " , "
self.out += hex(pykd.reg("r8")) + ") = "
if self.bp_end == None:
disas = pykd.dbgCommand("uf ntdll!RtlFreeHeap").split('\n')
for i in disas:
if 'ret' in i:
self.ret_addr = i.split()[0].replace('`','')
break
self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back)
return False

def return_call_back(self):
# returns a BOOLEAN which is a byte under the hood
ret_val = hex(pykd.reg("al"))
pykd.dprintln(self.out + ret_val + "\n")
return False


# RtlReAllocateHeap(
# IN PVOID HeapHandle,
# IN ULONG Flags,
# IN PVOID MemoryPointer,
# IN ULONG Size );

class handle_realloc_heap(pykd.eventHandler):
def __init__(self):
addr = get_address("ntdll!RtlReAllocateHeap")
if addr == None:
return
self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back)
self.bp_end = None

def enter_call_back(self):
self.out = "RtlReAllocateHeap("
if arch_bits == 32:
esp = pykd.reg(stack_pointer)
self.out += hex(pykd.ptrPtr(esp + 4)) + " , "
self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , "
self.out += hex(pykd.ptrPtr(esp + 0xC)) + " , "
self.out += hex(pykd.ptrMWord(esp + 0x10)) + ") = "
else:
self.out += hex(pykd.reg("rcx")) + " , "
self.out += hex(pykd.reg("rdx")) + " , "
self.out += hex(pykd.reg("r8")) + " , "
self.out += hex(pykd.reg("r9")) + ") = "
if self.bp_end == None:
disas = pykd.dbgCommand("uf ntdll!RtlReAllocateHeap").split('\n')
for i in disas:
if 'ret' in i:
self.ret_addr = i.split()[0].replace('`','')
break
self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back)
return False

def return_call_back(self):
pykd.dprintln(self.out + hex(pykd.reg(return_reg)) + "\n")
return False


try:
pykd.reg("rax")
except:
arch_bits = 32
return_reg = "eax"
stack_pointer = "esp"

#addr = get_address("ntdll!RtlReAllocateHeap")
#print addr

pykd.removeAllBp()
bp_a1 = handle_allocate_heap()
bp_a2 = handle_free_heap()
bp_a3 = handle_realloc_heap()
print 'bps=%x' %pykd.getNumberBreakpoints()

我基于这些模版,写了自己做漏洞分析的时候使用的脚本。 思路也比较简单,就是找到目标对象的分配、释放点,下断点,自动记录分配/释放的内存地址、大小,然后做一个输出供自己分析用。

效果当然是很舒服啦 :)

0x03 : 使用效果

目标是大型软件时,windbg会有点卡,等一下就好了。

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
> .load pykd
> !py -g path/to/script.py
> g
...
...
RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x276fbff0

RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x26af2ff0

RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x27657ff0

RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x26f1cff0

RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x26f1aff0

RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x257aaff0

RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x257bcff0

RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x2694aff0

RtlAllocateHeap(0x950000L , 0x8L , 0xcL) = 0x255cfff0

RtlFreeHeap(0x950000L , 0x0L , 0x255cfff0L) = 0x1

RtlFreeHeap(0x950000L , 0x0L , 0x2694aff0L) = 0x1

RtlFreeHeap(0x950000L , 0x0L , 0x257bcff0L) = 0x1

RtlFreeHeap(0x950000L , 0x0L , 0x257aaff0L) = 0x1

RtlFreeHeap(0x950000L , 0x0L , 0x26f1aff0L) = 0x1
...